File size: 7,406 Bytes
ec8442a | 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 | 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}"} |