7.0 KiB
7.0 KiB
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, repoC:/Users/locpham/SonicForgeStudiobranchstandalone.
Bước 0 — Đọc spec & khảo sát hiện trạng
- Đọ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
- Option 1: custom Python script auto-sample VSTi preset →
- 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
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à đọcmidi_events_to_messagestrongvst_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
- 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âyUnicodeEncodeErrorgiữa chừng → file bị truncate/hỏng. - 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. - 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ưỡngpeak*0.001) → normalize peak 0.9 → int16; trảNonenế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.
- Verify từng record size SF2 bằng chuẩn spec + sf2utils:
phdr38B,inst22B,pmod/imod10B,pgen/igen4B,ibag/pbag4B,shdr46B + 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
\x00bên trong data cho INFO text chunk chẵn.
Bước 3 — Verify end-to-end auto-sample
- Chạy thử với VSTi thật:
→ 3 nốt (60/62/64) → SF2 207KB, sf2utils parse sạch, audio peak 29490. ✅
python tools/autosample_vsti.py --instrument Nexus.vst3 --out nexus_test.sf2 --low 60 --high 64 --step 2 - 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
app/core/render_engine.pyL453: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")).app/api/v1/plugins.py:bit_depth: int = 16vàoRenderRequest(L493),MidiRenderRequest(L937),SoundfontRenderRequest(L1063)._render_midi_notes_pedalboard(..., bit_depth=16)(L1001) → sf.write L1048.soundfont_rendersf.write L1100.render_projectAPI truyềnbit_depth=req.bit_depth(L1200).
app/core/audio_editor.py: cả 2 subtype map (L196, L257) thêm32:"PCM_32"— mix multitrack + export.- 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)
- Chú ý CRLF:
edit_filemulti-line fail nếu old_string không có\r. Dùng python heredoc: đọcopen(p, encoding='utf-8', newline='').read(), assert count==1, replace bằng chuỗi chứa\r\n, ghi lại vớinewline=''. Không bao giờopen(p,'w')trước khi encode. - Thêm
<option value="32">32</option>vào cả 2 dropdown export (JSX L11355 + compiled bản ~L28700). - 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.
- realtime bounce (L25044-25045):
- 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
- 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".
- Thêm
test_render_project_bit_depthvàotests/test_render_engine.py: loop (16,24,32) → assertsf.info(out).subtype == PCM_16/24/32. - 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 overrideC:\Program Files\Common Files\VST3vs expected/opt/daw_engine/vst3).
- 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
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).- 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)