Hezu06 commited on
Commit
ec8442a
·
verified ·
1 Parent(s): bbe8f42

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +193 -0
main.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.responses import FileResponse
4
+ from pydantic import BaseModel
5
+ from pydub import AudioSegment
6
+ import torch
7
+ import torch.nn as nn
8
+ import pickle
9
+ import numpy as np
10
+ from music21 import note, chord, stream, instrument
11
+ import os
12
+ os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
13
+ import random
14
+ import subprocess
15
+
16
+ app = FastAPI(title="Bio-Vibe AI Inference")
17
+ os.makedirs("music", exist_ok=True)
18
+ app.mount("/music", StaticFiles(directory="music"), name="music")
19
+
20
+ @app.get("/")
21
+ async def serve_frontend():
22
+ return FileResponse("index.html")
23
+
24
+ class HeartRateData(BaseModel):
25
+ bpm: int
26
+
27
+ # ==========================================
28
+ # 1. KHÔI PHỤC "BỘ NÃO" (PYTORCH MODEL)
29
+ # ==========================================
30
+ # Cấu trúc class phải y hệt lúc train
31
+ class BioVibeAI(nn.Module):
32
+ def __init__(self, vocab_size, embed_size=256, hidden_size=512, num_layers=2):
33
+ super(BioVibeAI, self).__init__()
34
+ self.embedding = nn.Embedding(vocab_size, embed_size)
35
+ self.lstm = nn.LSTM(embed_size, hidden_size, num_layers, batch_first=True)
36
+ self.fc = nn.Linear(hidden_size, vocab_size)
37
+
38
+ def forward(self, x):
39
+ embedded = self.embedding(x)
40
+ out, _ = self.lstm(embedded)
41
+ out = self.fc(out[:, -1, :])
42
+ return out
43
+
44
+ # Nạp từ điển
45
+ with open('mapping_dict.pkl', 'rb') as f:
46
+ int_to_note = pickle.load(f)
47
+ note_to_int = {note: number for number, note in int_to_note.items()}
48
+ vocab_size = len(int_to_note)
49
+
50
+ # Load trọng số mô hình
51
+ device = torch.device("cpu") # Server chạy CPU cho nhẹ
52
+ model = BioVibeAI(vocab_size=vocab_size)
53
+ model.load_state_dict(torch.load("biovibe_brain.pth", map_location=device))
54
+ model.eval() # Chuyển sang chế độ suy luận
55
+
56
+ # Nạp kho dữ liệu cũ để lấy Seed (đoạn nhạc mồi)
57
+ with open('notes_corpus.pkl', 'rb') as f:
58
+ corpus = pickle.load(f)
59
+
60
+ # ==========================================
61
+ # 2. HÀM SÁNG TÁC BẰNG NHIỆT ĐỘ (TEMPERATURE)
62
+ # ==========================================
63
+ def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 60):
64
+ # Logic phản hồi sinh học
65
+ if bpm > 80:
66
+ temperature = 0.7 # An toàn, ổn định
67
+ base_offset = 1.0 # Nốt đánh chậm
68
+ else:
69
+ temperature = 1.0 # Bay bổng, ngẫu hứng
70
+ base_offset = 0.5 # Nốt đánh nhanh hơn một chút
71
+
72
+ # Chọn bừa 100 nốt từ kho dữ liệu để làm "mồi" cho AI
73
+ start_idx = random.randint(0, len(corpus) - 100)
74
+ seed_sequence = corpus[start_idx:start_idx + 100]
75
+ pattern = [note_to_int[char] for char in seed_sequence]
76
+
77
+ generated_notes = []
78
+
79
+ print(f"Đang sáng tác với Temp={temperature}...")
80
+
81
+ # AI bắt đầu dự đoán
82
+ for i in range(num_notes):
83
+ sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device)
84
+
85
+ with torch.no_grad():
86
+ prediction = model(sequence_tensor)
87
+
88
+ # ÁP DỤNG TEMPERATURE
89
+ prediction = prediction / temperature
90
+ probabilities = torch.softmax(prediction, dim=1).numpy()[0]
91
+
92
+ # Chọn nốt dựa trên phân phối xác suất
93
+ index = np.random.choice(len(probabilities), p=probabilities)
94
+
95
+ result_note = int_to_note[index]
96
+ generated_notes.append(result_note)
97
+
98
+ # Cập nhật cửa sổ trượt (thêm nốt mới, xóa nốt cũ nhất)
99
+ pattern.append(index)
100
+ pattern = pattern[1:]
101
+
102
+ # ==========================================
103
+ # 3. CHUYỂN NGỮ AI THÀNH FILE MIDI
104
+ # ==========================================
105
+ offset = 0
106
+ output_stream = stream.Stream()
107
+ output_stream.append(instrument.Piano()) # Chọn nhạc cụ
108
+
109
+ for pattern_note in generated_notes:
110
+ # Nếu là hợp âm (có dấu chấm, ví dụ '4.7.11')
111
+ try:
112
+ # Xử lý Hợp âm
113
+ if ('.' in pattern_note) or pattern_note.isdigit():
114
+ notes_in_chord = pattern_note.split('.')
115
+ chord_notes = []
116
+ for current_note in notes_in_chord:
117
+ n = note.Note(int(current_note))
118
+ # --- CAN THIỆP TẠI ĐÂY ---
119
+ if n.pitch.ps > 72: # Nếu nốt cao hơn C5
120
+ n.pitch.ps -= 12 # Hạ xuống 1 quãng tám
121
+ n.volume.velocity = random.randint(50, 70) # Giảm độ đanh (Velocity)
122
+ # -------------------------
123
+ chord_notes.append(n)
124
+ new_obj = chord.Chord(chord_notes)
125
+
126
+ # Xử lý Nốt đơn
127
+ else:
128
+ new_obj = note.Note(pattern_note)
129
+ # --- CAN THIỆP TẠI ĐÂY ---
130
+ if new_obj.pitch.ps > 72:
131
+ new_obj.pitch.ps -= 12
132
+ new_obj.volume.velocity = random.randint(50, 70)
133
+ # -------------------------
134
+
135
+ new_obj.offset = offset
136
+ output_stream.append(new_obj)
137
+ offset += base_offset
138
+ except:
139
+ continue
140
+
141
+ output_stream.write('midi', fp=output_file)
142
+
143
+ # ==========================================
144
+ # 4. API ENDPOINT
145
+ # ==========================================
146
+
147
+ # Low-pass filter
148
+ def apply_audio_filters(wav_path):
149
+ # Đọc file âm thanh vừa render
150
+ sound = AudioSegment.from_wav(wav_path)
151
+
152
+ # 1. Áp dụng Low Pass Filter tại 2000Hz (Loại bỏ tiếng rít cao)
153
+ sound = sound.low_pass_filter(2000)
154
+
155
+ # 2. Fade in/Fade out nhẹ để âm thanh không bị ngắt đột ngột (tăng ADSR mượt)
156
+ sound = sound.fade_in(1000).fade_out(2000)
157
+
158
+ # Xuất đè lên file cũ
159
+ sound.export(wav_path, format="wav")
160
+ @app.post("/generate-vibe")
161
+ async def generate_vibe(data: HeartRateData, request: Request):
162
+ # Đường dẫn file
163
+ midi_file = f"music/ai_vibe_{data.bpm}.mid"
164
+ wav_file = f"music/ai_vibe_{data.bpm}.wav"
165
+ soundfont_path = "soundfonts/Piano.sf2" # Đường dẫn tới file sf2 bạn vừa tải
166
+
167
+ # Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
168
+ # Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
169
+ generate_ai_midi(data.bpm, midi_file)
170
+
171
+ # Bước 2: Tự viết lệnh Render bằng Subprocess chuẩn xác cho FluidSynth 2.5+
172
+ print("Đang render MIDI thành âm thanh Piano...")
173
+ try:
174
+ subprocess.run([
175
+ "fluidsynth",
176
+ "-ni", # Chạy ẩn không cần giao diện
177
+ "-r", "44100", # Chất lượng Sample rate
178
+ "-F", wav_file, # Xuất ra file thay vì phát ra loa laptop!
179
+ soundfont_path, # Dàn nhạc (Soundfont)
180
+ midi_file # Kịch bản (MIDI)
181
+ ], check=True)
182
+
183
+ # --- BƯỚC MỚI: LỌC ÂM THANH NHỨC ÓC ---
184
+ apply_audio_filters(wav_file)
185
+ # -------------------------------------
186
+ except Exception as e:
187
+ print(f"Lỗi khi render âm thanh: {e}")
188
+ return {"status": "error", "message": "Render failed"}
189
+
190
+ server_url = str(request.base_url).rstrip("/")
191
+
192
+ # Bước 3: Trả về link file âm thanh xịn xò cho App Flutter
193
+ return {"status": "success", "audio_url": f"{server_url}/{wav_file}"}