Files
SonicForgeStudio/md/53_VSTI_AUTOSAMPLE_RENDER_WALKTHROUGH.md
T

7.0 KiB
Raw Blame History

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

  1. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  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

  1. 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")).
  2. 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).
  3. app/core/audio_editor.py: cả 2 subtype map (L196, L257) thêm 32:"PCM_32" — mix multitrack + export.
  4. 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)

  1. 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.
  2. Thêm <option value="32">32</option> vào cả 2 dropdown export (JSX L11355 + compiled bản ~L28700).
  3. 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.
  4. 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

  1. 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".
  2. 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.
  3. 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_initpre-existing (machine VST3 override C:\Program Files\Common Files\VST3 vs expected /opt/daw_engine/vst3).
  4. 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

  1. 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).
  2. 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)