10 KiB
10 KiB
Plan: Apply & Install md/34_VST_LINUX.md (Revised per md/35_VST_PLAN.md + md/35.1_VST_FIX.md)
Context Summary
- Dockerfile already has
libgl1,libasound2,libjack-jackd2-0,libfreetype6, Xvfb, fluidsynth. Missinglibcurl4. requirements.txthasmidobut missingsf2utils.app/core/vst_engine.pyhasPluginManagerwith FluidSynth C-API-basedlist_soundfont_instruments().app/core/render_engine.pyFluidSynth path hardcodesprogram_select(0, fid, 0, 0).app/static/js/services/aiGateway.js—generate_multitrack_miditool has NOsoundfont_id/soundfont_bank/soundfont_program.soundfontPlayer.js— oscillator-based, usesprogramonly for ADSR/osc-type selection; nocontrollerChange()orprogramChange()channel-state methods.- No
soundfont_catalog.jsongeneration or catalog API endpoint. docker-compose.ymlmounts.to/app.
5 Mandatory Refinements (from 35_VST_PLAN.md)
- Condensed Catalog for AI:
get_condensed_catalog_summary()→ max 40–50 instruments categorized by GM groups (Piano, Organ, Guitar, Bass, Strings, Ensemble, Brass, Reed, Pipe, Synth Lead, Synth Pad, Drum Kit). Avoids token overflow. - Non-Blocking Catalog Generation:
generate_full_catalog()runs on first API access, not on startup. Cache in memory; refresh on SF2 upload via background task. - Dual Directory Scanning: system
/opt/daw_engine/soundfonts/AND user uploadsapp/storage/uploads/soundfonts/. - DecentSampler CWD Fix:
os.chdir()to.dspresetparent directory beforeload_preset(), so relativesamples/*.wavpaths resolve. - Robust Error Handling: per-file
try/exceptinSoundFontInspector— skip corrupted SF2 files with warning instead of crashing.
Task A: SoundFont Inspection Engine (sf2utils)
A1 — Add dependency
- Add
sf2utils>=0.9.0torequirements.txt.
A2 — Create app/core/soundfont_inspector.py
inspect_sf2_file(filepath)— wrapped intry/exceptper Refinement 5. Returns{soundfont_id, filename, total_instruments, instruments: [{bank, program, name, is_percussion}]}.generate_full_catalog(output_json_path)— scans system dir/opt/daw_engine/soundfonts/+ user upload dir (Refinement 3). Writessoundfont_catalog.json.get_condensed_catalog_summary()— returns categorized dict with ≤50 entries per Refinement 1. Groups instruments by GM category (Piano=0-7, Chromatic Perc=8-15, Organ=16-23, Guitar=24-31, Bass=32-39, Strings=40-47, Ensemble=48-55, Brass=56-63, Reed=64-71, Pipe=72-79, Synth Lead=80-89, Synth Pad=90-103, Drum Kit=128).invalidate_catalog_cache()— resets in-memory cache; called after SF2 upload.
A3 — API Endpoint GET /api/v1/plugins/soundfonts/catalog
- In
app/api/v1/plugins.py:- Response shape:
{ full_catalog: {...}, condensed_catalog: {...} }. - Lazily generate on first call, cache in memory (Refinement 2).
POST /upload-soundfontsuccess handler: callsinvalidate_catalog_cache()+ triggers a background task (FastAPIBackgroundTasks) to re-scan. Does NOT block HTTP response.
- Response shape:
A4 — JS API wrapper
- In
app/static/js/services/api.js, addSonicAPI.getSoundfontCatalog()→GET /api/v1/plugins/soundfonts/catalog.
Task B: DecentSampler + Pianobook Support
B1 — Dockerfile updates
- Add
libcurl4toapt-get install. - Pre-create
/opt/daw_engine/vst3/and/opt/daw_engine/samples/pianobook/withmkdir -p.
B2 — Host dirs
- Create
vst_plugins/andsamples/pianobook/at repo root. Add both to.gitignore.
B3 — DecentSamplerManager in app/core/vst_engine.py
create_decent_sampler_instance(dspreset_path):- Resolve to absolute path with
os.path.abspath(). - Save original CWD with
os.getcwd(), thenos.chdir()to.dspresetparent dir beforeload_preset()(Refinement 4). - Restore original CWD in
finallyblock. - Return
VST3Plugininstance ready for rendering.
- Resolve to absolute path with
B4 — Wire into app/core/render_engine.py
- If track selects a Pianobook instrument (e.g.
instrument_source: "pianobook"), route throughDecentSamplerManagerinstead of FluidSynth or synth fallback. - The Pianobook path uses
pedalboard.Pedalboard([vst])with MIDI messages, same as other VST3 paths.
Task C: AI Tool Schema & Prompt Injection
C1 — Update generate_multitrack_midi tool in aiGateway.js
- Add to
parameters.properties.tracks.items.properties:soundfont_id:{ type: "string", description: "ID of the SoundFont file (e.g. 'generaluser_gs')" }soundfont_bank:{ type: "integer", default: 0, description: "MIDI Bank. 0 = melodic, 128 = drums/percussion" }soundfont_program:{ type: "integer", description: "MIDI Program number 0-127 from instrument catalog" }
- Add all 3 to
requiredarray.
C2 — Inject condensed catalog into system instruction
- Modify
buildUserMessage()inaiGateway.js:- When
systemInstructionis empty and globalwindow.__soundfontCatalogexists, prepend asystemmessage block containing the condensed catalog text. - Format: one line per GM category with bank/program examples.
- Enforce bank rule (per 35.1_VST_FIX.md §3.B): Add explicit instruction —
soundfont_bank: 0for all melodic instruments,soundfont_bank: 128for Drum Kits.
- When
C3 — Fetch catalog on frontend startup
- In
app/static/js/app.jsx, after auth check, callSonicAPI.getSoundfontCatalog(). - Store result in
window.__soundfontCatalog = { condensed_catalog, full_catalog }. - Re-fetch after any SF2 upload succeeds.
Task D: Server Render — Program Change & Channel Mapping
D1 — Read bank/program from track metadata
- In
render_engine.pyMIDI rendering block, extractsoundfont_bankandsoundfont_programfrom track dict. - Default: bank=0, program=0.
D2 — MIDI channel routing + FluidSynth update
- Channel rules (per 35.1_VST_FIX.md §5.B):
bank == 128or track hasis_percussion: true→midi_channel = 9(GM channel 10, percussion).- Otherwise → assign channels sequentially from 0–8, one per unique percussion-group track.
- Replace hardcoded
fl.program_select(0, fid, 0, 0)with:midi_channel = 9 if (bank == 128 or track.get("is_percussion")) else channel_counter fl.program_select(midi_channel, fid, bank, prog) - All note_on/note_off events for that track must use the same
midi_channel.
D3 — VST3/Pedalboard path: CC + PC insertion
- Extend
midi_events_to_messages()or add a wrapper that inserts two MIDI messages at sample_offset=0 before note messages:MidiMessage(control_change=0, value=bank, sample_offset=0)— CONTROL_CHANGE CC 0 (Bank Select MSB)MidiMessage(program_change=program, sample_offset=0)— PROGRAM_CHANGE
- These are prepended to the message list before
Pedalboard([vst])processes the buffer.
Task E: Client SoundFont Player — Program Change & Channel Allocation
E1 — Add channel-state tracking to soundfontPlayer.js
- Add internal
_channelsarray (size 16), each entry:{ bank: 0, program: 0 }. controllerChange(channel, controller, value):- If
controller === 0(Bank Select MSB), storebankfor that channel.
- If
programChange(channel, program):- Store
programfor that channel.
- Store
- Modify
playNote()to accept an optionalchannelparameter and use the stored bank/program for ADSR/osc-type selection.
E2 — Add applyAITrackInstrument(trackId, bank, program)
- New function in
soundfontPlayer.js:- Determine MIDI channel:
bank === 128 || isPercussion ? 9 : track_index % 9. - Call
controllerChange(channel, 0, bank). - Call
programChange(channel, program).
- Determine MIDI channel:
- Called from
app.jsxafter AI returnsgenerate_multitrack_midiwith track instrument data.
E3 — Wire into post-AI pipeline in app.jsx
- In the DAW command dispatch loop (around line 12853), after processing
generate_multitrack_midifunction call:- For each returned track with
soundfont_bank/soundfont_program, callapplyAITrackInstrument(). - Log the action to
aiActionLog.
- For each returned track with
Task F: Background Cache Invalidation on Upload
F1 — Update POST /upload-soundfont in plugins.py
- After saving the uploaded SF2 file:
- Call
SoundFontInspector.invalidate_catalog_cache(). - Use FastAPI
BackgroundTasksto queue a re-scan:background_tasks.add_task(generate_full_catalog). - Return HTTP 200 immediately (not block on scan).
- Call
F2 — Frontend catalog re-fetch after upload
- In
app.jsxupload handler, afterSonicAPI.uploadSoundFont()succeeds, callSonicAPI.getSoundfontCatalog()and updatewindow.__soundfontCatalog.
Task G: Validation
G1 — Catalog API
GET /api/v1/plugins/soundfonts/catalog→ valid JSON with{ full_catalog: {...}, condensed_catalog: {...} }.- Condensed catalog contains ≤50 entries, grouped by GM category.
G2 — AI generation
- Input: "Compose 8 bars of Brass horns and a drum kit"
- Verify AI returns
generate_multitrack_midicall with:- Brass track:
program: 56,bank: 0,soundfont_id: "generaluser_gs". - Drums track:
program: 0,bank: 128,soundfont_id: "generaluser_gs".
- Brass track:
G3 — Client instrument switching
- After AI response, verify
applyAITrackInstrumentis called with correct bank/program per track. - Verify MIDI channel allocation: melodic → ch0-8, drums → ch9.
- Verify
controllerChange(CC0)+programChange()dispatched per channel.
G4 — Server render
- Export WAV, verify correct Brass horn and Drum sound.
- For FluidSynth path: confirm
program_selectuses correct channel, bank, program. - For VST3 path: confirm CC0 + PC inserted before notes.
G5 — Upload cache invalidation
- Upload a new
.sf2file → verifycatalogendpoint updates without manual restart. - Upload a corrupted
.sf2file → verify it is skipped gracefully (Refinement 5).
G6 — Regression
pytest tests/passes with no regressions.
Implementation Order
- A1–A4 (sf2utils + soundfont_inspector + catalog API + JS wrapper) — foundational.
- C1–C3 (AI tool schema + condensed prompt injection + startup fetch) — depends on A3/A4.
- D1–D3 (server render program change + channel mapping + CC/PC insertion) — depends on C1 for field names.
- F1–F2 (background cache invalidation on upload) — depends on A3.
- E1–E3 (client program change + channel allocation + post-AI wiring) — independent of D, but shares channel routing logic.
- B1–B4 (DecentSampler) — last, requires manual VST3 binary download.
- G1–G6 (validation).