| 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 |
|
|
| |
| |
| |
| |
| 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 |
|
|
| |
| 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) |
|
|
| |
| device = torch.device("cpu") |
| model = BioVibeAI(vocab_size=vocab_size) |
| model.load_state_dict(torch.load("biovibe_brain.pth", map_location=device)) |
| model.eval() |
|
|
| |
| with open('notes_corpus.pkl', 'rb') as f: |
| corpus = pickle.load(f) |
|
|
| |
| |
| |
| def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 60): |
| |
| if bpm > 80: |
| temperature = 0.7 |
| base_offset = 1.0 |
| else: |
| temperature = 1.0 |
| base_offset = 0.5 |
| |
| |
| 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}...") |
| |
| |
| for i in range(num_notes): |
| sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device) |
| |
| with torch.no_grad(): |
| prediction = model(sequence_tensor) |
| |
| |
| prediction = prediction / temperature |
| probabilities = torch.softmax(prediction, dim=1).numpy()[0] |
| |
| |
| index = np.random.choice(len(probabilities), p=probabilities) |
| |
| result_note = int_to_note[index] |
| generated_notes.append(result_note) |
| |
| |
| pattern.append(index) |
| pattern = pattern[1:] |
| |
| |
| |
| |
| offset = 0 |
| output_stream = stream.Stream() |
| output_stream.append(instrument.Piano()) |
| |
| for pattern_note in generated_notes: |
| |
| try: |
| |
| 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)) |
| |
| if n.pitch.ps > 72: |
| n.pitch.ps -= 12 |
| n.volume.velocity = random.randint(50, 70) |
| |
| chord_notes.append(n) |
| new_obj = chord.Chord(chord_notes) |
|
|
| |
| else: |
| new_obj = note.Note(pattern_note) |
| |
| 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) |
|
|
| |
| |
| |
|
|
| |
| def apply_audio_filters(wav_path): |
| |
| sound = AudioSegment.from_wav(wav_path) |
|
|
| |
| sound = sound.low_pass_filter(2000) |
|
|
| |
| sound = sound.fade_in(1000).fade_out(2000) |
|
|
| |
| sound.export(wav_path, format="wav") |
| @app.post("/generate-vibe") |
| async def generate_vibe(data: HeartRateData, request: Request): |
| |
| midi_file = f"music/ai_vibe_{data.bpm}.mid" |
| wav_file = f"music/ai_vibe_{data.bpm}.wav" |
| soundfont_path = "soundfonts/Piano.sf2" |
| |
| |
| |
| generate_ai_midi(data.bpm, midi_file) |
| |
| |
| print("Đang render MIDI thành âm thanh Piano...") |
| try: |
| subprocess.run([ |
| "fluidsynth", |
| "-ni", |
| "-r", "44100", |
| "-F", wav_file, |
| soundfont_path, |
| midi_file |
| ], check=True) |
|
|
| |
| 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("/") |
| |
| |
| return {"status": "success", "audio_url": f"{server_url}/{wav_file}"} |