Hezu06 commited on
Commit
4f973f7
·
verified ·
1 Parent(s): bc9c018

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +61 -219
main.py CHANGED
@@ -2,18 +2,15 @@ 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
 
@@ -25,232 +22,77 @@ 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
- # Load trọng số mô hình từ file
54
- state_dict = torch.load("biovibe_brain.pth", map_location=device)
55
 
56
- # --- THÊM ĐOẠN NÀY: Lột bỏ vỏ bọc 'module.' của DataParallel ---
57
- clean_state_dict = {}
58
- for key, value in state_dict.items():
59
- if key.startswith('module.'):
60
- # Cắt bỏ 7 ký tự đầu tiên ('m', 'o', 'd', 'u', 'l', 'e', '.')
61
- clean_key = key[7:]
62
- clean_state_dict[clean_key] = value
63
- else:
64
- clean_state_dict[key] = value
65
-
66
- # Nạp bộ não đã được gọt dũa sạch sẽ vào model
67
- model.load_state_dict(clean_state_dict)
68
- model.eval() # Chuyển sang chế độ suy luận
69
 
70
- # Nạp kho dữ liệu cũ để lấy Seed (đoạn nhạc mồi)
71
- with open('notes_corpus.pkl', 'rb') as f:
72
- corpus = pickle.load(f)
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  # ==========================================
75
- # 2. HÀM SÁNG TÁC BẰNG NHIỆT ĐỘ (TEMPERATURE)
76
  # ==========================================
77
- def generate_ai_midi(bpm: int, output_file: str, num_notes: int = 60):
78
- # --- PHẦN THÊM MỚI: ĐỊNH NGHĨA THANG NGŨ CUNG (C Pentatonic) ---
79
- # Các nốt: C, D, E, G, A ở nhiều quãng tám (từ trầm đến trung)
80
- # Chỉ dùng các nốt từ C2 (36) đến C4 (60) - Tuyệt đối không để vượt quá 60
81
- # PENTATONIC_MS = [36, 38, 40, 43, 45, 48, 50, 52, 55, 57, 60]
82
-
83
- # def get_closest_pentatonic(pitch_input):
84
- # """Hàm ép một nốt bất kỳ về nốt Ngũ cung gần nhất"""
85
- # return min(PENTATONIC_MS, key=lambda x: abs(x - pitch_input))
86
- # -----------------------------------------------------------
87
- # Logic phản hồi sinh học
88
- if bpm > 80:
89
- temperature = 0.7 # An toàn, ổn định
90
- base_offset = 1.0 # Nốt đánh chậm
91
  else:
92
- temperature = 1.0 # Bay bổng, ngẫu hứng
93
- base_offset = 0.5 # Nốt đánh nhanh hơn một chút
94
-
95
- # Chọn bừa 100 nốt từ kho dữ liệu để làm "mồi" cho AI
96
- start_idx = random.randint(0, len(corpus) - 100)
97
- seed_sequence = corpus[start_idx:start_idx + 100]
98
- pattern = [note_to_int[char] for char in seed_sequence]
99
-
100
- generated_notes = []
101
-
102
- print(f"Đang sáng tác với Temp={temperature}...")
103
-
104
- # AI bắt đầu dự đoán
105
- for i in range(num_notes):
106
- sequence_tensor = torch.tensor([pattern], dtype=torch.long).to(device)
107
-
108
- with torch.no_grad():
109
- prediction = model(sequence_tensor)
110
-
111
- # ÁP DỤNG TEMPERATURE
112
- prediction = prediction / temperature
113
- probabilities = torch.softmax(prediction, dim=1).numpy()[0]
114
-
115
- # Chọn nốt dựa trên phân phối xác suất
116
- index = np.random.choice(len(probabilities), p=probabilities)
117
-
118
- result_note = int_to_note[index]
119
- # --- PHẦN SỬA ĐỔI: ÉP NỐT VỀ PENTATONIC ---
120
- # 1. Nếu là nốt đơn (ví dụ: 'C4', 'E5')
121
- # if not ('.' in result_note or result_note.isdigit()):
122
- # # Trong hàm generate_ai_midi, phần xử lý nốt:
123
- # temp_note = note.Note(result_note)
124
- # pitch_val = temp_note.pitch.ps
125
-
126
- # # Nếu nốt cao hơn nốt 60 (C4), hãy hạ nó xuống 1 hoặc 2 quãng tám
127
- # while pitch_val > 60:
128
- # pitch_val -= 12
129
-
130
- # # Sau đó mới tìm nốt gần nhất trong danh sách Pentatonic trầm
131
- # new_pitch = get_closest_pentatonic(pitch_val)
132
- # temp_note.pitch.ps = new_pitch
133
-
134
- # # 2. Nếu là hợp âm hoặc nốt dạng số (ví dụ: '60.64.67')
135
- # else:
136
- # parts = result_note.split('.')
137
- # new_parts = []
138
- # for p in parts:
139
- # p_int = int(p)
140
- # new_p = get_closest_pentatonic(p_int)
141
- # new_parts.append(str(int(new_p)))
142
- # result_note = '.'.join(new_parts)
143
- # -----------------------------------------
144
- generated_notes.append(result_note)
145
-
146
- # Cập nhật cửa sổ trượt (thêm nốt mới, xóa nốt cũ nhất)
147
- pattern.append(index)
148
- pattern = pattern[1:]
149
-
150
- # ==========================================
151
- # 3. CHUYỂN NGỮ AI THÀNH FILE MIDI
152
- # ==========================================
153
- offset = 0
154
- output_stream = stream.Stream()
155
- output_stream.append(instrument.Piano()) # Chọn nhạc cụ
156
-
157
- for pattern_note in generated_notes:
158
- # Nếu là hợp âm (có dấu chấm, ví dụ '4.7.11')
159
- try:
160
- # Xử lý Hợp âm
161
- if ('.' in pattern_note) or pattern_note.isdigit():
162
- notes_in_chord = pattern_note.split('.')
163
- chord_notes = []
164
- for current_note in notes_in_chord:
165
- n = note.Note(int(current_note))
166
- # --- CAN THIỆP TẠI ĐÂY ---
167
- # 1. Ép vùng âm trầm (Lower Pitch)
168
- if n.pitch.ps > 65:
169
- n.pitch.ps -= 12
170
-
171
- # 2. KỸ THUẬT 2: Humanize Velocity (Lực nhấn cực khẽ)
172
- n.volume.velocity = random.randint(30, 45)
173
- # -------------------------
174
- chord_notes.append(n)
175
- new_obj = chord.Chord(chord_notes)
176
-
177
- # Xử lý Nốt đơn
178
- else:
179
- new_obj = note.Note(pattern_note)
180
- # --- CAN THIỆP TẠI ĐÂY ---
181
- if new_obj.pitch.ps > 65:
182
- new_obj.pitch.ps -= 12
183
-
184
- # 2. KỸ THUẬT 2: Humanize Velocity
185
- new_obj.volume.velocity = random.randint(30, 45)
186
-
187
- # 3. KỸ THUẬT 2: Micro-timing (Lệch nhịp nhẹ để tạo sự uyển chuyển)
188
- # Thay vì offset cứng nhắc, ta cộng thêm một lượng siêu nhỏ ngẫu nhiên
189
- humanized_offset = offset + random.uniform(-0.03, 0.03)
190
- new_obj.offset = humanized_offset
191
-
192
- # Kéo dài nốt một chút (Duration) để chúng gối đầu lên nhau (Legato)
193
- new_obj.quarterLength = base_offset * 1.1
194
- output_stream.append(new_obj)
195
- offset += base_offset
196
- except:
197
- continue
198
-
199
- output_stream.write('midi', fp=output_file)
200
 
201
  # ==========================================
202
  # 4. API ENDPOINT
203
  # ==========================================
204
-
205
- # Low-pass filter
206
- def apply_audio_filters(wav_path):
207
- sound = AudioSegment.from_wav(wav_path)
208
-
209
- # Cắt toàn bộ tần số trên 600Hz (Đây là mức "Dark/Warm" - cực kỳ ấm áp)
210
- # 600Hz sẽ loại bỏ hoàn toàn tiếng chói, chỉ để lại tiếng piano mờ ảo.
211
- sound = sound.low_pass_filter(600)
212
 
213
- # Giảm thêm âm lượng nốt để tránh nén tiếng
214
- sound = sound - 7
215
 
216
- # Làm mượt đầu cuối
217
- sound = sound.fade_in(2000).fade_out(3000)
 
218
 
219
- sound.export(wav_path, format="wav")
220
- @app.post("/generate-vibe")
221
- async def generate_vibe(data: HeartRateData, request: Request):
222
- # Đường dẫn file
223
- midi_file = f"music/ai_vibe_{data.bpm}.mid"
224
- wav_file = f"music/ai_vibe_{data.bpm}.wav"
225
- soundfont_path = "soundfonts/Piano.sf2" # Đường dẫn tới file sf2 bạn vừa tải
226
 
227
- # Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
228
- # Bước 1: PyTorch sinh ra cấu trúc nốt nhạc (MIDI)
229
- generate_ai_midi(data.bpm, midi_file)
 
 
230
 
231
- # Bước 2: Tự viết lệnh Render bằng Subprocess chuẩn xác cho FluidSynth 2.5+
232
- print("Đang render MIDI thành âm thanh Piano...")
233
- try:
234
- subprocess.run([
235
- "fluidsynth",
236
- "-ni", # Chạy ẩn không cần giao diện
237
- "-g", "0.6", # [NÂNG CẤP] Giảm âm lượng tổng để tiếng bớt "đanh"
238
- "-r", "44100", # Chất lượng Sample rate
239
- "--reverb", "yes", # [MỚI] Bật vang số để các nốt nối đuôi nhau (giảm rời rạc)
240
- "--chorus", "yes", # [MỚI] Làm dày âm thanh, tạo cảm giác êm ái hơn
241
- "-F", wav_file, # Xuất ra file thay vì phát ra loa laptop!
242
- soundfont_path, # Dàn nhạc (Soundfont)
243
- midi_file # Kịch bản (MIDI)
244
- ], check=True)
245
-
246
- # --- BƯỚC MỚI: LỌC ÂM THANH NHỨC ÓC ---
247
- apply_audio_filters(wav_file)
248
- # -------------------------------------
249
- except Exception as e:
250
- print(f"Lỗi khi render âm thanh: {e}")
251
- return {"status": "error", "message": "Render failed"}
252
 
253
  server_url = str(request.base_url).rstrip("/")
254
-
255
- # Bước 3: Trả về link file âm thanh xịn xò cho App Flutter
256
  return {"status": "success", "audio_url": f"{server_url}/{wav_file}"}
 
2
  from fastapi.staticfiles import StaticFiles
3
  from fastapi.responses import FileResponse
4
  from pydantic import BaseModel
 
5
  import torch
6
+ from transformers import AutoProcessor, MusicgenForConditionalGeneration
7
+ import scipy.io.wavfile
 
 
8
  import os
9
+ import time
 
 
10
 
11
+ app = FastAPI(title="Bio-Vibe MusicGen Inference")
12
+
13
+ # Tạo thư mục chứa nhạc
14
  os.makedirs("music", exist_ok=True)
15
  app.mount("/music", StaticFiles(directory="music"), name="music")
16
 
 
22
  bpm: int
23
 
24
  # ==========================================
25
+ # 1. TẢI BỘ NÃO MUSICGEN (META)
26
  # ==========================================
27
+ print("Đang tải bộ não MusicGen-Small từ Meta (Khoảng 1.5GB)...")
28
+ # Server Hugging Face Free chạy CPU nên ta dùng CPU
29
+ device = "cpu"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ processor = AutoProcessor.from_pretrained("facebook/musicgen-small")
32
+ model = MusicgenForConditionalGeneration.from_pretrained("facebook/musicgen-small")
33
+ model.to(device)
34
+ print("✅ Tải xong! Hệ thống đã sẵn sàng.")
 
 
 
 
 
 
 
 
 
35
 
36
+ # ==========================================
37
+ # 2. HÀM DỌN RÁC (Tối ưu ổ cứng)
38
+ # ==========================================
39
+ def cleanup_old_audio(folder_path="music", max_age_seconds=600):
40
+ now = time.time()
41
+ for filename in os.listdir(folder_path):
42
+ file_path = os.path.join(folder_path, filename)
43
+ if filename.endswith('.wav'):
44
+ if os.path.getmtime(file_path) < now - max_age_seconds:
45
+ try:
46
+ os.remove(file_path)
47
+ print(f"🔥 Đã tự động dọn dẹp: {filename}")
48
+ except:
49
+ pass
50
 
51
  # ==========================================
52
+ # 3. KỊCH BẢN CHỮA LÀNH BẰNG NHỊP TIM
53
  # ==========================================
54
+ def get_prompt_from_bpm(bpm: int) -> str:
55
+ if bpm < 70:
56
+ return "Calm relaxing lo-fi piano music, slow tempo, ambient, soothing, healing therapy"
57
+ elif bpm <= 85:
58
+ return "Chillout electronic ambient music, smooth synth, moderate tempo, focus"
59
+ elif bpm <= 100:
60
+ return "Upbeat synthwave, energetic bassline, moderate fast tempo, driving rhythm"
 
 
 
 
 
 
 
61
  else:
62
+ return "Fast tempo, tense cinematic action music, heavy drums, energetic orchestra"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  # ==========================================
65
  # 4. API ENDPOINT
66
  # ==========================================
67
+ @app.post("/generate-vibe")
68
+ async def generate_vibe(data: HeartRateData, request: Request):
69
+ cleanup_old_audio()
 
 
 
 
 
70
 
71
+ unique_id = int(time.time())
72
+ wav_file = f"music/vibe_{data.bpm}_{unique_id}.wav"
73
 
74
+ # Dịch nhịp tim thành câu lệnh văn bản
75
+ music_prompt = get_prompt_from_bpm(data.bpm)
76
+ print(f"Nhịp tim {data.bpm} BPM -> Sáng tác theo chủ đề: '{music_prompt}'")
77
 
78
+ # Tiền xử lý văn bản
79
+ inputs = processor(
80
+ text=[music_prompt],
81
+ padding=True,
82
+ return_tensors="pt",
83
+ ).to(device)
 
84
 
85
+ # Cho AI sinh âm thanh
86
+ # max_new_tokens=256 tương đương khoảng 5 giây âm thanh (phù hợp để demo trên CPU)
87
+ # Tăng số này lên nếu muốn nhạc dài hơn (nhưng sẽ gen lâu hơn)
88
+ print("Đang ép xung CPU để render âm thanh... Vui lòng đợi!")
89
+ audio_values = model.generate(**inputs, do_sample=True, guidance_scale=3, max_new_tokens=256)
90
 
91
+ # Xuất ra file .wav chuẩn
92
+ sampling_rate = model.config.audio_encoder.sampling_rate
93
+ audio_data = audio_values[0, 0].cpu().numpy()
94
+ scipy.io.wavfile.write(wav_file, rate=sampling_rate, data=audio_data)
95
+ print(" Render hoàn tất!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  server_url = str(request.base_url).rstrip("/")
 
 
98
  return {"status": "success", "audio_url": f"{server_url}/{wav_file}"}