BioVibe / main.py
Hezu06's picture
Upload main.py
ec8442a verified
Raw
History Blame Contribute Delete
7.41 kB
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from pydub import AudioSegment
import torch
import torch.nn as nn
import pickle
import numpy as np
from music21 import note, chord, stream, instrument
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
import random
import subprocess
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, embed_size=256, hidden_size=512, num_layers=2):
super(BioVibeAI, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_size)
self.lstm = nn.LSTM(embed_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, vocab_size)
def forward(self, x):
embedded = self.embedding(x)
out, _ = self.lstm(embedded)
out = self.fc(out[:, -1, :])
return 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)
model.load_state_dict(torch.load("biovibe_brain.pth", map_location=device))
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)
# ==========================================
# 2. HÀM SÁNG TÁC BẰNG NHIỆT ĐỘ (TEMPERATURE)
# ==========================================
def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 60):
# Logic phản hồi sinh học
if bpm > 80:
temperature = 0.7 # An toàn, ổn định
base_offset = 1.0 # Nốt đánh chậm
else:
temperature = 1.0 # Bay bổng, ngẫu hứng
base_offset = 0.5 # Nốt đánh nhanh hơn một chút
# Chọn bừa 100 nốt từ kho dữ liệu để làm "mồi" cho AI
start_idx = random.randint(0, len(corpus) - 100)
seed_sequence = corpus[start_idx:start_idx + 100]
pattern = [note_to_int[char] for char in seed_sequence]
generated_notes = []
print(f"Đang sáng tác với Temp={temperature}...")
# AI bắt đầu dự đoán
for i in range(num_notes):
sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device)
with torch.no_grad():
prediction = model(sequence_tensor)
# ÁP DỤNG TEMPERATURE
prediction = prediction / temperature
probabilities = torch.softmax(prediction, dim=1).numpy()[0]
# Chọn nốt dựa trên phân phối xác suất
index = np.random.choice(len(probabilities), p=probabilities)
result_note = int_to_note[index]
generated_notes.append(result_note)
# Cập nhật cửa sổ trượt (thêm nốt mới, xóa nốt cũ nhất)
pattern.append(index)
pattern = pattern[1:]
# ==========================================
# 3. CHUYỂN NGỮ AI THÀNH FILE MIDI
# ==========================================
offset = 0
output_stream = stream.Stream()
output_stream.append(instrument.Piano()) # Chọn nhạc cụ
for pattern_note in generated_notes:
# Nếu là hợp âm (có dấu chấm, ví dụ '4.7.11')
try:
# Xử lý Hợp âm
if ('.' in pattern_note) or pattern_note.isdigit():
notes_in_chord = pattern_note.split('.')
chord_notes = []
for current_note in notes_in_chord:
n = note.Note(int(current_note))
# --- CAN THIỆP TẠI ĐÂY ---
if n.pitch.ps > 72: # Nếu nốt cao hơn C5
n.pitch.ps -= 12 # Hạ xuống 1 quãng tám
n.volume.velocity = random.randint(50, 70) # Giảm độ đanh (Velocity)
# -------------------------
chord_notes.append(n)
new_obj = chord.Chord(chord_notes)
# Xử lý Nốt đơn
else:
new_obj = note.Note(pattern_note)
# --- CAN THIỆP TẠI ĐÂY ---
if new_obj.pitch.ps > 72:
new_obj.pitch.ps -= 12
new_obj.volume.velocity = random.randint(50, 70)
# -------------------------
new_obj.offset = offset
output_stream.append(new_obj)
offset += base_offset
except:
continue
output_stream.write('midi', fp=output_file)
# ==========================================
# 4. API ENDPOINT
# ==========================================
# Low-pass filter
def apply_audio_filters(wav_path):
# Đọc file âm thanh vừa render
sound = AudioSegment.from_wav(wav_path)
# 1. Áp dụng Low Pass Filter tại 2000Hz (Loại bỏ tiếng rít cao)
sound = sound.low_pass_filter(2000)
# 2. Fade in/Fade out nhẹ để âm thanh không bị ngắt đột ngột (tăng ADSR mượt)
sound = sound.fade_in(1000).fade_out(2000)
# Xuất đè lên file cũ
sound.export(wav_path, format="wav")
@app.post("/generate-vibe")
async def generate_vibe(data: HeartRateData, request: Request):
# Đường dẫn file
midi_file = f"music/ai_vibe_{data.bpm}.mid"
wav_file = f"music/ai_vibe_{data.bpm}.wav"
soundfont_path = "soundfonts/Piano.sf2" # Đường dẫn tới file sf2 bạn vừa tải
# Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
# Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
generate_ai_midi(data.bpm, midi_file)
# Bước 2: Tự viết lệnh Render bằng Subprocess chuẩn xác cho FluidSynth 2.5+
print("Đang render MIDI thành âm thanh Piano...")
try:
subprocess.run([
"fluidsynth",
"-ni", # Chạy ẩn không cần giao diện
"-r", "44100", # Chất lượng Sample rate
"-F", wav_file, # Xuất ra file thay vì phát ra loa laptop!
soundfont_path, # Dàn nhạc (Soundfont)
midi_file # Kịch bản (MIDI)
], check=True)
# --- BƯỚC MỚI: LỌC ÂM THANH NHỨC ÓC ---
apply_audio_filters(wav_file)
# -------------------------------------
except Exception as e:
print(f"Lỗi khi render âm thanh: {e}")
return {"status": "error", "message": "Render failed"}
server_url = str(request.base_url).rstrip("/")
# Bước 3: Trả về link file âm thanh xịn xò cho App Flutter
return {"status": "success", "audio_url": f"{server_url}/{wav_file}"}