# WALKTHROUGH 53: VSTi AUTOSAMPLE + 24/32-BIT RENDER/EXPORT > Ghi lại các hành động đã thực hiện cho Task 53 (`md/53_VSTI_AUTOSAMPLE_RENDER.md`). > Môi trường: Windows 11, bash, Python 3.13.7, pedalboard==0.9.19, repo `C:/Users/locpham/SonicForgeStudio` branch `standalone`. --- ## Bước 0 — Đọc spec & khảo sát hiện trạng 1. Đọc spec "ALTERNATIVE SOLUTIONS FOR DIRECT VSTi USAGE IN STANDALONE DAW APPLICATIONS" → 2 option cần hiện thực: - Option 1: custom Python script auto-sample VSTi preset → `.sf3` (~3-8MB) cho client WASM live preview 0% IPC - Option 2: offline render/export qua Python backend, WAV 24-bit/32-bit 2. Khảo sát codebase: `app/core/vst_engine.py` (PluginManager, apply_preset_to_plugin, HAS_PEDALBOARD, midi_events_to_messages), `app/core/render_engine.py`, `app/core/audio_editor.py`, `app/api/v1/plugins.py` (RenderRequest, MidiRenderRequest, SoundfontRenderRequest), `app/static/js/app.jsx` (2 WAV encoder), `app/core/soundfont_converter.py` (SF2→SF3). ## Bước 1 — Khảo sát API pedalboard trên máy 3. `dir(pedalboard)` → **không có** NoteOn/MIDI classes trong build này. Xác định MIDI phải truyền dạng **raw tuple** `(bytes, seconds)`: - `dir(PluginManager)` và đọc `midi_events_to_messages` trong `vst_engine.py` → trả `(bytes, seconds)` tuples. - Quyết định: render MIDI qua `vst(messages, sample_rate=..., duration=...)` với `(bytes([0x90,note,vel]), 0.0)` (note-on) và `(bytes([0x80,note,0]), dur)` (note-off). ## Bước 2 — Viết `tools/autosample_vsti.py` 4. Viết script lần 1 bằng heredoc với nội dung có ký tự Unicode (dấu tiếng Việt) → **crash**: `open(p,'w')` trên cp1252 console gây `UnicodeEncodeError` giữa chừng → **file bị truncate/hỏng**. 5. **Viết lại từ đầu** với toàn bộ text ASCII-only (chuẩn style repo `tools/gen_icons.py`): dùng "duong dan", "khong", "not" thay vì dấu tiếng Việt; mọi in/help đều ASCII. 6. Cấu trúc script: - `_render_note(vst, note, sr, dur, release, velocity)`: build raw MIDI tuples → `vst(...)` → mean về mono → trim leading silence (ngưỡng `peak*0.001`) → normalize peak 0.9 → int16; trả `None` nếu silent. - `write_sf2(out_path, samples, sample_rate, name)`: tự sinh SF2 tối giản (16-bit mono PCM, 1 zone/nốt, preset 0/bank 0). - `main()`: argparse `--instrument --out --preset --low --high --step --duration --release --velocity --sample-rate --sf3`. 7. Verify từng record size SF2 bằng chuẩn spec + sf2utils: - `phdr` 38B, `inst` 22B, `pmod`/`imod` 10B, `pgen`/`igen` 4B, `ibag`/`pbag` 4B, `shdr` 46B + terminator records. - **Bug phát hiện**: INAM lẻ ("Nexus") → sf2utils báo "corrupted but salvageable" vì strict parser không skip RIFF pad byte của odd chunk. Fix: pad `\x00` **bên trong data** cho INFO text chunk chẵn. ## Bước 3 — Verify end-to-end auto-sample 8. Chạy thử với VSTi thật: ``` python tools/autosample_vsti.py --instrument Nexus.vst3 --out nexus_test.sf2 --low 60 --high 64 --step 2 ``` → 3 nốt (60/62/64) → SF2 **207KB**, sf2utils parse sạch, audio peak 29490. ✅ 9. Thử Kontakt.vst3 → load được nhưng silent (không có .nki — đúng dự kiến, không phải lỗi script). ## Bước 4 — Thêm bit_depth 16/24/32 backend 10. `app/core/render_engine.py` L453: `render_project(self, project_json, output_filepath, bit_depth=16)`; L479-481: `subtype_map={16:"PCM_16",24:"PCM_24",32:"PCM_32"}` → `sf.write(..., subtype=subtype_map.get(int(bit_depth), "PCM_16"))`. 11. `app/api/v1/plugins.py`: - `bit_depth: int = 16` vào `RenderRequest` (L493), `MidiRenderRequest` (L937), `SoundfontRenderRequest` (L1063). - `_render_midi_notes_pedalboard(..., bit_depth=16)` (L1001) → sf.write L1048. - `soundfont_render` sf.write L1100. - `render_project` API truyền `bit_depth=req.bit_depth` (L1200). 12. `app/core/audio_editor.py`: cả 2 subtype map (L196, L257) thêm `32:"PCM_32"` — mix multitrack + export. 13. Verify bằng `sf.info(path).subtype` (soundfile): 16→PCM_16, 24→PCM_24, 32→PCM_32. ✅ ## Bước 5 — Thêm bit_depth 32 frontend (`app/static/js/app.jsx` — CRLF) 14. **Chú ý CRLF**: `edit_file` multi-line fail nếu old_string không có `\r`. Dùng python heredoc: đọc `open(p, encoding='utf-8', newline='').read()`, assert count==1, replace bằng chuỗi chứa `\r\n`, ghi lại với `newline=''`. Không bao giờ `open(p,'w')` trước khi encode. 15. Thêm `` vào **cả 2** dropdown export (JSX L11355 + compiled bản ~L28700). 16. Thêm nhánh encoder 32-bit vào **cả 2** client WAV encoder: - realtime bounce (L25044-25045): `else if (bitDepth === 32) view.setInt32(o, Math.floor(s < 0 ? s * 0x80000000 : s * 0x7FFFFFFF), true);` - offline-session (L25363-25368): tương tự `setInt32`. 17. **Bug cũ được sửa ngầm**: trước đây chọn 24-bit bị rơi vào nhánh fallback 8-bit (thiếu nhánh `===24`); thêm nhánh 24-bit + 32-bit đúng thứ tự. ## Bước 6 — Tests 18. Viết `tests/test_autosampler.py` (mới, LF): - `test_write_sf2_valid_structure`: RIFF/sfbk, 3 samples, preset 0/bank 0, 8820 frames, `sample_rate==44100`. - `test_write_sf2_rejects_empty`: `write_sf2([], ...)` → ValueError. - `test_write_sf2_odd_name_no_corruption`: tên "Nexus" (5 ký tự lẻ) → sf2utils không log "corrupted". 19. Thêm `test_render_project_bit_depth` vào `tests/test_render_engine.py`: loop (16,24,32) → assert `sf.info(out).subtype == PCM_16/24/32`. 20. Chạy: ``` python -m pytest tests/ -q → 108 passed, 1 skipped, 1 failed ``` - 1 skipped: pyfluidsynth không có native lib trên máy (tests không yêu cầu). - 1 failed: `TestPluginManager::test_init` — **pre-existing** (machine VST3 override `C:\Program Files\Common Files\VST3` vs expected `/opt/daw_engine/vst3`). 21. Xác nhận fail đó không do mình: `git stash` → chạy test → fail y hệt trên code sạch → `git stash pop`. ✅ ## Bước 7 — Kiểm tra cuối & tài liệu 22. `git status` → các file modified/untracked đúng như dự kiến (không đụng file ngoài phạm vi; giữ nguyên các thay đổi pre-existing của user: `app.jsx`, `services/api.js`, `services/fluidsynthLoader.js`, `app.precompiled.js`). 23. Viết `md/53_VSTI_AUTOSAMPLE_RENDER.md` (task) + `md/53_VSTI_AUTOSAMPLE_RENDER_WALKTHROUGH.md` (file này). --- ## Tổng kết diff ``` M app/api/v1/plugins.py (bit_depth 3 requests + 3 sf.write + pass-through) M app/core/audio_editor.py (+32:"PCM_32" ở 2 subtype map) M app/core/render_engine.py (render_project bit_depth + subtype_map) M app/static/js/app.jsx (option 32 ×2 dropdown, encoder 32-bit ×2, fix 24-bit fallback) M tests/test_render_engine.py (+test_render_project_bit_depth) ?? tools/autosample_vsti.py (mới — auto-sample VSTi → SF2/SF3) ?? tests/test_autosampler.py (mới — 3 tests SF2 writer) ```