| 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()) |