BioVibe_v2 / main.py
Hezu06's picture
Update main.py
161e79d verified
Raw
History Blame Contribute Delete
5.78 kB
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