muzic / app.py
NeonoV1's picture
Update app.py
402edc2 verified
Raw
History Blame Contribute Delete
7.17 kB
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())