Nguyên nhân xuất hiện thông báo lỗi từ SpessaSynth Core: `basic_synthesizer_core.ts:169 No preset found for 0:0:0! Did you forget to add a sound bank?` Thông số `0:0:0` trong thông báo đại diện cho `Bank MSB : Bank LSB : Program Number` (cấu hình mặc định ban đầu của kênh MIDI). Lỗi này xảy ra do **3 nguyên nhân chính** sau: --- ### 1. Phân tích nguyên nhân kỹ thuật 1. **Chưa gửi lệnh `programChange` & `controllerChange` sang SpessaSynth khi chọn "Pipa"**: * Khi chọn nhạc cụ "Pipa" trên UI, ứng dụng mới chỉ cập nhật giao diện mà **chưa kích hoạt lệnh đổi tiếng** (`synthInstance.programChange(channel, program)`) sang kênh MIDI tương ứng của SpessaSynth. * Khi gõ phím trên `SE49` (nhận dữ liệu Raw MIDI `[144, 65, 78]`), SpessaSynth vẫn truy xuất bộ tiếng ở vị trí mặc định là `Bank 0 : Program 0` (thường là Grand Piano theo chuẩn General MIDI). * Trong SoundFont `DSK_Asian_DreamZ`, vị trí `0:0:0` không tồn tại hoặc không chứa thông tin nốt, khiến SpessaSynth thông báo không tìm thấy preset. 2. **Sai chỉ số Bank / Program của nhạc cụ "Pipa"**: * Tệp SoundFont `DSK_Asian_DreamZ` là bộ tiếng tùy chỉnh (Non-General MIDI). 8 nhạc cụ bên trong (*Pipa, Pipa Tremolo, Luan, Guzhen, Erhu, Koto, Ban Di, Percussion*) được đánh số `Program` riêng biệt. * Nếu UI gửi `program = 0` nhưng thực tế trong file SoundFont, tiếng Pipa nằm ở `program = 1` hoặc `bank = 1`, SpessaSynth sẽ không tìm thấy preset tương ứng. 3. **Lỗi Header/Mapping khi convert từ `.sf2` sang `.sf3**`: * Một số công cụ nén (như `mscore` CLI) khi convert các SoundFont tùy chỉnh có cấu trúc phi chuẩn có thể làm mất hoặc biến đổi bảng thông tin Preset Header. --- ### 2. Các bước khắc phục #### **Bước 1: Gọi `programChange` & `controllerChange` ngay khi chọn nhạc cụ trên UI** Đảm bảo khi người dùng chọn nhạc cụ trên giao diện, hàm chọn Bank/Program được kích hoạt trên kênh MIDI phát tiếng: ```javascript // Khi người dùng chọn "Pipa" trên Dropdown Synth UI async function onSelectTrackInstrument(trackChannel, sfId, bank, program) { // 1. Nạp file .sf3 vào SpessaSynth (nếu chưa nạp) await soundFontPlayerInstance.loadSoundFont(sfId); // 2. BẮT BUỘC: Gửi lệnh đổi Bank (CC 0) và Program Change sang SpessaSynth if (soundFontPlayerInstance.synthInstance) { soundFontPlayerInstance.synthInstance.controllerChange(trackChannel, 0, bank); soundFontPlayerInstance.synthInstance.programChange(trackChannel, program); console.log(`[SonicSF] Switched Channel ${trackChannel} -> Bank: ${bank}, Program: ${program}`); } } ``` #### **Bước 2: Kiểm tra chính xác chỉ số Bank & Program của Pipa từ Catalog API** Sử dụng API `GET /api/v1/plugins/soundfonts/catalog` (từ mô-đun `SoundFontInspector` đã xây dựng) để tra cứu vị trí chính xác của "Pipa": ```json "dsk_asian_dreamz": { "soundfont_id": "dsk_asian_dreamz", "instruments": [ { "bank": 0, "program": 0, "name": "Pipa" }, { "bank": 0, "program": 1, "name": "Pipa Tremolo" }, { "bank": 0, "program": 6, "name": "Ban Di" } ] } ``` *Lưu ý:* Nếu kết quả trả về tiếng Pipa nằm ở `program: 1` hoặc `bank: 1`, hãy truyền đúng thông số này vào hàm `programChange`. #### **Bước 3: Kiểm tra danh sách Presets mà SpessaSynth đọc được từ file `.sf3**` Để đảm bảo quá trình convert `.sf3` không làm hỏng dữ liệu Preset Header, bạn hãy log danh sách preset sau khi nạp tệp: ```javascript // Thêm log kiểm tra sau khi addSoundFont vào SpessaSynth try { await this.synthInstance.soundFontManager.addSoundFont(buffer); // In danh sách các preset đọc được ra console để kiểm tra const loadedSF = this.synthInstance.soundFontManager.soundFonts[0]; console.log("[SonicSF] Loaded Presets in SF3:", loadedSF.presets); } catch (e) { console.error("[SonicSF] Error parsing SF3:", e); } ``` * Nếu `loadedSF.presets` rỗng (`[]`), file `.sf3` đã bị hỏng khi nén. Bạn hãy thử nạp lại file `.sf2` gốc chưa nén để đối chiếu.