Spaces:
Sleeping
Sleeping
File size: 5,776 Bytes
161e79d 8b0e7e2 161e79d 3608a6c 161e79d 3608a6c f6b9480 161e79d f6b9480 4f973f7 3608a6c 8b0e7e2 161e79d 56b4a52 161e79d 56b4a52 161e79d 3608a6c 9d6d506 c688f4d 161e79d 9d6d506 161e79d 9d6d506 c688f4d 161e79d c688f4d 161e79d c688f4d 161e79d 3608a6c 161e79d 56b4a52 161e79d 56b4a52 3608a6c 161e79d 56b4a52 161e79d 0858961 161e79d 56b4a52 161e79d 56b4a52 161e79d 56b4a52 161e79d 3608a6c 161e79d 5136ade 161e79d ed44796 161e79d 359910b ed44796 359910b ed44796 359910b ed44796 161e79d 491886c 359910b ed44796 491886c 161e79d 457981a 161e79d 491886c 161e79d 491886c 161e79d f5c3ac2 ed44796 161e79d 359910b 161e79d 754fcda 161e79d ed44796 161e79d 754fcda ed44796 161e79d 754fcda 161e79d 754fcda 491886c 161e79d 754fcda 161e79d 754fcda 491886c 161e79d 56b4a52 161e79d | 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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
import torch
import torch.nn as nn
import pickle
import numpy as np
from music21 import note
import random
import asyncio
import math
import os
app = FastAPI(title="Bio-Vibe AI Inference")
os.makedirs("music", exist_ok=True)
app.mount("/music", StaticFiles(directory="music"), name="music")
@app.get("/")
async def serve_frontend():
return FileResponse("index.html")
class HeartRateData(BaseModel):
bpm: int
# ==========================================
# 1. KHÔI PHỤC "BỘ NÃO" (PYTORCH MODEL)
# ==========================================
# Cấu trúc class phải y hệt lúc train
class BioVibeAI(nn.Module):
def __init__(self, vocab_size):
super().__init__()
self.embedding = nn.Embedding(vocab_size, 256)
self.lstm = nn.LSTM(
256,
512,
num_layers=3,
batch_first=True,
dropout=0.3
)
self.norm = nn.LayerNorm(512)
self.fc = nn.Linear(512, vocab_size)
def forward(self, x):
x = self.embedding(x)
out, _ = self.lstm(x)
out = out[:, -1, :]
out = self.norm(out)
return self.fc(out)
# Nạp từ điển
with open('mapping_dict.pkl', 'rb') as f:
int_to_note = pickle.load(f)
note_to_int = {note: number for number, note in int_to_note.items()}
vocab_size = len(int_to_note)
# Load trọng số mô hình
device = torch.device("cpu") # Server chạy CPU cho nhẹ
model = BioVibeAI(vocab_size=vocab_size)
# Load trọng số mô hình từ file
state_dict = torch.load("model.pth", map_location=device)
# --- THÊM ĐOẠN NÀY: Lột bỏ vỏ bọc 'module.' của DataParallel ---
clean_state_dict = {}
for key, value in state_dict.items():
if key.startswith('module.'):
# Cắt bỏ 7 ký tự đầu tiên ('m', 'o', 'd', 'u', 'l', 'e', '.')
clean_key = key[7:]
clean_state_dict[clean_key] = value
else:
clean_state_dict[key] = value
# Nạp bộ não đã được gọt dũa sạch sẽ vào model
model.load_state_dict(clean_state_dict)
model.eval() # Chuyển sang chế độ suy luận
# Nạp kho dữ liệu cũ để lấy Seed (đoạn nhạc mồi)
with open('notes_corpus.pkl', 'rb') as f:
corpus = pickle.load(f)
# ==========================================
# 4. API ENDPOINT
# ==========================================
@app.websocket("/ws/vibe")
async def websocket_vibe(websocket: WebSocket):
await websocket.accept()
current_bpm = 75
is_playing = True
async def listen():
nonlocal current_bpm, is_playing
try:
while True:
data = await websocket.receive_json()
bpm = int(data.get("bpm", current_bpm))
if 40 <= bpm <= 180:
current_bpm = bpm
except:
is_playing = False
asyncio.create_task(listen())
SEQ_LEN = 50
# 👉 chọn 1 bài nhạc random
song = random.choice(corpus)
# 👉 đảm bảo đủ độ dài
if len(song) < SEQ_LEN:
song = random.choice(corpus)
# 👉 lấy đoạn đầu
seed_tokens = song[:SEQ_LEN]
# 👉 ép style (QUAN TRỌNG)
seed_tokens[0] = "STYLE_LOFI"
# 👉 convert sang int
pattern = [note_to_int[n] for n in seed_tokens]
inp = torch.zeros(1, SEQ_LEN, dtype=torch.long)
note_count = 0
CHORDS = [
[48,52,55,59],
[45,48,52,55],
[41,45,48,52],
[43,47,50,53]
]
def snap(midi, chord):
return min(chord + [c+12 for c in chord], key=lambda x: abs(x - midi))
try:
STEPS_PER_BATCH = 16
while is_playing:
beat = 60.0 / current_bpm
step = beat / 2
batch = []
for i in range(STEPS_PER_BATCH):
inp[0] = torch.tensor(pattern)
with torch.no_grad():
pred = model(inp)
probs = torch.softmax(pred / 0.8, dim=1).numpy()[0]
idx = np.random.choice(len(probs), p=probs)
tok = int_to_note[idx]
pattern = pattern[1:] + [idx]
midi = None
try:
if tok.startswith("NOTE"):
midi = note.Note(tok.split("_")[1]).pitch.midi
except:
pass
chord = CHORDS[((note_count + i) // 8) % len(CHORDS)]
notes = []
# chord
if (note_count + i) % 8 == 0:
for c in chord:
notes.append({
"note": c,
"velocity": 40,
"duration": beat * 4,
"type": "chord",
"time": i * step
})
# melody
if midi:
while midi < 55: midi += 12
while midi > 75: midi -= 12
notes.append({
"note": midi,
"velocity": random.randint(55, 80),
"duration": step * 1.2,
"type": "melody",
"time": i * step
})
batch.extend(notes)
await websocket.send_json({
"notes": batch,
"step": step
})
note_count += STEPS_PER_BATCH
await asyncio.sleep(step * STEPS_PER_BATCH * 0.8)
except:
pass
|