Files
SonicForgeStudio/md/53_VSTI_AUTOSAMPLE_RENDER_WALKTHROUGH.md
T

96 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<option value="32">32</option>` 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)
```