Download main.py from Hezu06/BioVibe: direct link, hf CLI and curl.
- Browser
- Download file 7.41 kB
-
https://huggingface.co/spaces/Hezu06/BioVibe/resolve/main/main.py
- Command line
-
hf download hf://spaces/Hezu06/BioVibe/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Hezu06/BioVibe/resolve/main/main.py
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") | |
| 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") | |
| 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}"} |