Update app.py
Browse files
app.py
CHANGED
|
@@ -1,30 +1,128 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
# + нужен ffmpeg в системе
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
)
|
| 19 |
-
wav.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import numpy as np, soundfile as sf, torch, math, time
|
|
|
|
| 3 |
|
| 4 |
+
GENRES = {
|
| 5 |
+
"Хаус":"groovy house beat, warm analog bassline, punchy 4/4 kick, 122 bpm",
|
| 6 |
+
"Техно":"driving hypnotic techno, dark warehouse, rolling kick, acid blips",
|
| 7 |
+
"Драм-н-бэйс":"fast breakbeat, rolling amen drums, deep reese bass, 174 bpm",
|
| 8 |
+
"Фонк":"aggressive phonk, cowbell melody, chopped soul samples, heavy 808",
|
| 9 |
+
"Лоу-фай":"lofi hip hop, dusty vinyl crackle, mellow keys, tape warmth",
|
| 10 |
+
"Синтвейв":"retro synthwave, neon 80s, lush pads, gated reverb drums",
|
| 11 |
+
"Трэп":"dark trap, booming 808 slides, crisp hi-hat rolls, half-time",
|
| 12 |
+
"Эмбиент":"ambient drone, vast space, slowly evolving textures",
|
| 13 |
+
"Гиперпоп":"hyperpop, pitched vocals, distorted supersaws, chaotic energy",
|
| 14 |
+
"Транс":"euphoric trance, rolling bassline, uplifting supersaw leads, 138 bpm",
|
| 15 |
+
"Чиллвейв":"chillwave, hazy tape chorus, dreamy detuned synths",
|
| 16 |
+
"Вэйпорвейв":"vaporwave, slowed funk sample, lush reverb, nostalgic",
|
| 17 |
+
"Хардбасс":"hardbass, donk bass, energetic kicks, cheeky rave energy",
|
| 18 |
+
}
|
| 19 |
|
| 20 |
+
_demucs_model=None; _musicgen=None
|
| 21 |
+
def get_demucs():
|
| 22 |
+
global _demucs_model
|
| 23 |
+
if _demucs_model is None:
|
| 24 |
+
from demucs.pretrained import get_model
|
| 25 |
+
_demucs_model=get_model("htdemucs"); _demucs_model.eval()
|
| 26 |
+
return _demucs_model
|
| 27 |
+
def get_musicgen():
|
| 28 |
+
global _musicgen
|
| 29 |
+
if _musicgen is None:
|
| 30 |
+
from transformers import pipeline
|
| 31 |
+
_musicgen=pipeline("text-to-audio","facebook/musicgen-small",
|
| 32 |
+
device=0 if torch.cuda.is_available() else "cpu")
|
| 33 |
+
return _musicgen
|
| 34 |
|
| 35 |
+
def analyze(path):
|
| 36 |
+
data,sr=sf.read(path,dtype="float32",always_2d=True)
|
| 37 |
+
mono=data.mean(axis=1); dur=len(mono)/sr
|
| 38 |
+
rms=float(np.sqrt(np.mean(mono[::max(1,len(mono)//4000)]**2)))
|
| 39 |
+
energy=min(99,int(rms*420)); hop,n=1024,len(mono)//1024
|
| 40 |
+
e=np.array([np.sum(mono[i*hop:(i+1)*hop:4]**2) for i in range(n)])
|
| 41 |
+
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])]
|
| 42 |
+
bpm=None
|
| 43 |
+
if len(peaks)>=8:
|
| 44 |
+
cnt={}
|
| 45 |
+
for a,b in zip(peaks,peaks[1:]):
|
| 46 |
+
dt=(b-a)*hop/sr
|
| 47 |
+
if .25<dt<2:
|
| 48 |
+
v=60/dt
|
| 49 |
+
while v<80:v*=2
|
| 50 |
+
while v>175:v/=2
|
| 51 |
+
cnt[round(v)]=cnt.get(round(v),0)+1
|
| 52 |
+
if cnt:bpm=max(cnt,key=cnt.get)
|
| 53 |
+
return dur,bpm,energy
|
| 54 |
|
| 55 |
+
def separate_vocals(path):
|
| 56 |
+
from demucs.apply import apply_model
|
| 57 |
+
from demucs.audio import AudioFile
|
| 58 |
+
model=get_demucs()
|
| 59 |
+
wav=AudioFile(path).read(streams=0,samplerate=model.samplerate,channels=model.audio_channels)
|
| 60 |
+
ref=wav.mean(0); wav=(wav-ref.mean())/ref.std()
|
| 61 |
+
with torch.no_grad():
|
| 62 |
+
src=apply_model(model,wav[None],device="cpu",split=True,overlap=.25)[0]
|
| 63 |
+
src=src*ref.std()+ref.mean()
|
| 64 |
+
return src[model.sources.index("vocals")].numpy().T, model.samplerate
|
| 65 |
|
| 66 |
+
def gen_groove(genre):
|
| 67 |
+
prompt=GENRES.get(genre,f'experimental "{genre}" style, bold unusual sound design')
|
| 68 |
+
out=get_musicgen()(prompt,forward_params={"max_new_tokens":1024})
|
| 69 |
+
return out["audio"][0],out["sampling_rate"]
|
| 70 |
|
| 71 |
+
def mix(voc,vsr,groove,gsr,aggr,out):
|
| 72 |
+
from scipy.signal import resample_poly
|
| 73 |
+
if gsr!=vsr:
|
| 74 |
+
g=math.gcd(vsr,gsr); groove=resample_poly(groove,vsr//g,gsr//g)
|
| 75 |
+
if groove.ndim==1:groove=np.stack([groove,groove],1)
|
| 76 |
+
groove=np.tile(groove,(len(voc)//len(groove)+1,1))[:len(voc)]
|
| 77 |
+
mx=voc*0.95+groove*min(1.1,0.35+aggr/200)
|
| 78 |
+
mx/=max(1e-6,np.max(np.abs(mx)))/0.95
|
| 79 |
+
sf.write(out,mx,vsr)
|
| 80 |
|
| 81 |
+
def remix(track,genre,custom,prompt,aggr):
|
| 82 |
+
if track is None:
|
| 83 |
+
yield "⚠ СИСТЕМА: сначала вставь трек в слот",None; return
|
| 84 |
+
log=[]; push=lambda m:(log.append(m),"\n".join(log))[1]; t0=time.time()
|
| 85 |
+
yield push("СИСТЕМА: трек в слоте ✔ сворм из 8 каналов проснулся"),None
|
| 86 |
+
dur,bpm,energy=analyze(track)
|
| 87 |
+
yield push(f"УШАН-3000: длина {int(dur//60)}:{int(dur%60):02d} · темп ≈ {bpm or '?'} BPM · энергия {energy}%"),None
|
| 88 |
+
g=(custom or "").strip() or genre
|
| 89 |
+
yield push(f"ОРКЕСТРАТОР: жанр «{g}». раздаю задачи моделям…"),None
|
| 90 |
+
if prompt.strip(): yield push(f"ХАЙП-КРИТИК: пожелание «{prompt.strip()[:80]}» — учтено 👌"),None
|
| 91 |
+
yield push("ВОКАЛ-ХИРУРГ: demucs режет трек на вокал/музыку… (2–6 мин на бесплатном CPU, это нормально)"),None
|
| 92 |
+
try: voc,vsr=separate_vocals(track)
|
| 93 |
+
except Exception as ex:
|
| 94 |
+
yield push(f"⚠ не смог прочитать файл: {ex}\nпопробуй загрузить WAV"),None; return
|
| 95 |
+
yield push("ВОКАЛ-ХИРУРГ: вокал отделён чисто ✔"),None
|
| 96 |
+
yield push("РИТМ-АРХИТЕКТОР: musicgen генерит грув… (1–3 мин)"),None
|
| 97 |
+
groove,gsr=gen_groove(g)
|
| 98 |
+
yield push(f"РИТМ-АРХИТЕКТОР: грув готов ✔ {len(groove)/gsr:.0f} сек"),None
|
| 99 |
+
yield push("МИКС-МАСТЕР: свожу вокал+грув, мастерю до красного 🔴"),None
|
| 100 |
+
out="/tmp/svorm8_remix.wav"; mix(voc,vsr,groove,gsr,aggr,out)
|
| 101 |
+
yield push(f"✔ ГОТОВО за {(time.time()-t0)/60:.1f} мин. максимальный REMIX подан ⚡"),out
|
| 102 |
+
|
| 103 |
+
CSS="""
|
| 104 |
+
@import url('https://fonts.googleapis.com/css2?family=Anton&family=JetBrains+Mono&display=swap');
|
| 105 |
+
.gradio-container{background:#101114!important;font-family:'JetBrains Mono',monospace}
|
| 106 |
+
h1,h2,.prose h1{font-family:'Anton',sans-serif;color:#ffb43a!important}
|
| 107 |
+
button.primary{background:#ff5a1f!important;border:3px solid #000!important}
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
with gr.Blocks(css=CSS,title="СВОРМ-8",theme=gr.themes.Base()) as demo:
|
| 111 |
+
gr.HTML("<h1 style='font-size:42px'>СВОРМ-8 · NEURO REMIX</h1>"
|
| 112 |
+
"<p style='color:#8a8f9c'>20 моделей в реестре · в работе: demucs + musicgen · "
|
| 113 |
+
"всё считается прямо здесь, на сервере HF · бесплатно = медленно, но честно</p>")
|
| 114 |
+
with gr.Row():
|
| 115 |
+
with gr.Column():
|
| 116 |
+
track=gr.Audio(type="filepath",label="🎧 СЛОТ ДЛЯ ТРЕКА")
|
| 117 |
+
with gr.Column():
|
| 118 |
+
genre=gr.Radio(list(GENRES),value="Хаус",label="ЖАНР")
|
| 119 |
+
custom=gr.Textbox(label="СВОЙ ЖАНР (перекрывает пэд)")
|
| 120 |
+
prompt=gr.Textbox(label="ПОЖЕЛАНИЯ НЕЙРОНАМ")
|
| 121 |
+
aggr=gr.Slider(40,160,value=100,label="АГРЕССИЯ")
|
| 122 |
+
btn=gr.Button("🔥 ЗАПУСТИТЬ REMIX",variant="primary")
|
| 123 |
+
log=gr.Textbox(label="📟 ЭФИР НЕЙРОСЕТЕЙ",lines=12,interactive=False)
|
| 124 |
+
out=gr.Audio(label="🔊 ГОТОВЫЙ REMIX (тут же можно скачать)",type="filepath")
|
| 125 |
+
btn.click(remix,inputs=[track,genre,custom,prompt,aggr],outputs=[log,out])
|
| 126 |
+
gr.HTML("<p style='color:#5b606b'>ни один музыкант не пострадал (почти) · remix максимальный гарантирован*</p>")
|
| 127 |
+
|
| 128 |
+
demo.queue(default_concurrency_limit=1).launch(show_api=False)
|