File size: 7,169 Bytes
03005a1
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
 
 
 
 
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
 
1c73b09
03005a1
 
 
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
1c73b09
03005a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402edc2
03005a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402edc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import gradio as gr
import numpy as np, soundfile as sf, torch, math, time

GENRES = {
 "Хаус":"groovy house beat, warm analog bassline, punchy 4/4 kick, 122 bpm",
 "Техно":"driving hypnotic techno, dark warehouse, rolling kick, acid blips",
 "Драм-н-бэйс":"fast breakbeat, rolling amen drums, deep reese bass, 174 bpm",
 "Фонк":"aggressive phonk, cowbell melody, chopped soul samples, heavy 808",
 "Лоу-фай":"lofi hip hop, dusty vinyl crackle, mellow keys, tape warmth",
 "Синтвейв":"retro synthwave, neon 80s, lush pads, gated reverb drums",
 "Трэп":"dark trap, booming 808 slides, crisp hi-hat rolls, half-time",
 "Эмбиент":"ambient drone, vast space, slowly evolving textures",
 "Гиперпоп":"hyperpop, pitched vocals, distorted supersaws, chaotic energy",
 "Транс":"euphoric trance, rolling bassline, uplifting supersaw leads, 138 bpm",
 "Чиллвейв":"chillwave, hazy tape chorus, dreamy detuned synths",
 "Вэйпорвейв":"vaporwave, slowed funk sample, lush reverb, nostalgic",
 "Хардбасс":"hardbass, donk bass, energetic kicks, cheeky rave energy",
}

_demucs_model=None; _musicgen=None
def get_demucs():
    global _demucs_model
    if _demucs_model is None:
        from demucs.pretrained import get_model
        _demucs_model=get_model("htdemucs"); _demucs_model.eval()
    return _demucs_model
def get_musicgen():
    global _musicgen
    if _musicgen is None:
        from transformers import pipeline
        _musicgen=pipeline("text-to-audio","facebook/musicgen-small",
                           device=0 if torch.cuda.is_available() else "cpu")
    return _musicgen

def analyze(path):
    data,sr=sf.read(path,dtype="float32",always_2d=True)
    mono=data.mean(axis=1); dur=len(mono)/sr
    rms=float(np.sqrt(np.mean(mono[::max(1,len(mono)//4000)]**2)))
    energy=min(99,int(rms*420)); hop,n=1024,len(mono)//1024
    e=np.array([np.sum(mono[i*hop:(i+1)*hop:4]**2) for i in range(n)])
    peaks=[i for i in range(2,n-2) if e[i]>e[i-1] and e[i]>=e[i+1] and e[i]>1.3*np.mean(e[max(0,i-22):i+22])]
    bpm=None
    if len(peaks)>=8:
        cnt={}
        for a,b in zip(peaks,peaks[1:]):
            dt=(b-a)*hop/sr
            if .25<dt<2:
                v=60/dt
                while v<80:v*=2
                while v>175:v/=2
                cnt[round(v)]=cnt.get(round(v),0)+1
        if cnt:bpm=max(cnt,key=cnt.get)
    return dur,bpm,energy

def separate_vocals(path):
    from demucs.apply import apply_model
    from demucs.audio import AudioFile
    model=get_demucs()
    wav=AudioFile(path).read(streams=0,samplerate=model.samplerate,channels=model.audio_channels)
    ref=wav.mean(0); wav=(wav-ref.mean())/ref.std()
    with torch.no_grad():
        src=apply_model(model,wav[None],device="cpu",split=True,overlap=.25)[0]
    src=src*ref.std()+ref.mean()
    return src[model.sources.index("vocals")].numpy().T, model.samplerate

def gen_groove(genre):
    prompt=GENRES.get(genre,f'experimental "{genre}" style, bold unusual sound design')
    out=get_musicgen()(prompt,forward_params={"max_new_tokens":1024})
    return out["audio"][0],out["sampling_rate"]

def mix(voc,vsr,groove,gsr,aggr,out):
    from scipy.signal import resample_poly
    if gsr!=vsr:
        g=math.gcd(vsr,gsr); groove=resample_poly(groove,vsr//g,gsr//g)
    if groove.ndim==1:groove=np.stack([groove,groove],1)
    groove=np.tile(groove,(len(voc)//len(groove)+1,1))[:len(voc)]
    mx=voc*0.95+groove*min(1.1,0.35+aggr/200)
    mx/=max(1e-6,np.max(np.abs(mx)))/0.95
    sf.write(out,mx,vsr)

def remix(track,genre,custom,prompt,aggr):
    if track is None:
        yield "⚠ СИСТЕМА: сначала вставь трек в слот",None; return
    log=[]; push=lambda m:(log.append(m),"\n".join(log))[1]; t0=time.time()
    yield push("СИСТЕМА: трек в слоте ✔ сворм из 8 каналов проснулся"),None
    dur,bpm,energy=analyze(track)
    yield push(f"УШАН-3000: длина {int(dur//60)}:{int(dur%60):02d} · темп ≈ {bpm or '?'} BPM · энергия {energy}%"),None
    g=(custom or "").strip() or genre
    yield push(f"ОРКЕСТРАТОР: жанр «{g}». раздаю задачи моделям…"),None
    if prompt.strip(): yield push(f"ХАЙП-КРИТИК: пожелание «{prompt.strip()[:80]}» — учтено 👌"),None
    yield push("ВОКАЛ-ХИРУРГ: demucs режет трек на вокал/музыку… (2–6 мин на бесплатном CPU, это нормально)"),None
    try: voc,vsr=separate_vocals(track)
    except Exception as ex:
        yield push(f"⚠ не смог прочитать файл: {ex}\nпопробуй загрузить WAV"),None; return
    yield push("ВОКАЛ-ХИРУРГ: вокал отделён чисто ✔"),None
    yield push("РИТМ-АРХИТЕКТОР: musicgen генерит грув… (1–3 мин)"),None
    groove,gsr=gen_groove(g)
    yield push(f"РИТМ-АРХИТЕКТОР: грув готов ✔ {len(groove)/gsr:.0f} сек"),None
    yield push("МИКС-МАСТЕР: свожу вокал+грув, мастерю до красного 🔴"),None
    out="/tmp/svorm8_remix.wav"; mix(voc,vsr,groove,gsr,aggr,out)
    yield push(f"✔ ГОТОВО за {(time.time()-t0)/60:.1f} мин. максимальный REMIX подан ⚡"),out

CSS="""
@import url('https://fonts.googleapis.com/css2?family=Anton&family=JetBrains+Mono&display=swap');
.gradio-container{background:#101114!important;font-family:'JetBrains Mono',monospace}
h1,h2,.prose h1{font-family:'Anton',sans-serif;color:#ffb43a!important}
button.primary{background:#ff5a1f!important;border:3px solid #000!important}
"""

with gr.Blocks() as demo:
    gr.HTML("<h1 style='font-size:42px'>СВОРМ-8 · NEURO REMIX</h1>"
            "<p style='color:#8a8f9c'>20 моделей в реестре · в работе: demucs + musicgen · "
            "всё считается прямо здесь, на сервере HF · бесплатно = медленно, но честно</p>")
    with gr.Row():
        with gr.Column():
            track=gr.Audio(type="filepath",label="🎧 СЛОТ ДЛЯ ТРЕКА")
        with gr.Column():
            genre=gr.Radio(list(GENRES),value="Хаус",label="ЖАНР")
            custom=gr.Textbox(label="СВОЙ ЖАНР (перекрывает пэд)")
            prompt=gr.Textbox(label="ПОЖЕЛАНИЯ НЕЙРОНАМ")
            aggr=gr.Slider(40,160,value=100,label="АГРЕССИЯ")
    btn=gr.Button("🔥 ЗАПУСТИТЬ REMIX",variant="primary")
    log=gr.Textbox(label="📟 ЭФИР НЕЙРОСЕТЕЙ",lines=12,interactive=False)
    out=gr.Audio(label="🔊 ГОТОВЫЙ REMIX (тут же можно скачать)",type="filepath")
    btn.click(remix,inputs=[track,genre,custom,prompt,aggr],outputs=[log,out])
    gr.HTML("<p style='color:#5b606b'>ни один музыкант не пострадал (почти) · remix максимальный гарантирован*</p>")

demo.queue(max_size=5).launch(css=CSS,theme=gr.themes.Base())