Commit Graph

307 Commits

Author SHA1 Message Date
3dtours 7544ee2e5f feat: eager soundfont catalog scan at startup, cache presets locally
- App startup fetches full catalog with all instrument presets
- Merges presets into instrumentSelectorData.soundfonts
- Synth button shows presets from cache (no per-SF API call)
- Fallback: if cache miss, fetch individual SF presets on demand
2026-07-27 09:28:34 +07:00
3dtours d04442ad45 fix: click on ruler moves playhead only (no zero-width selection)
- Remove setSelectionStart/End from simple click (non-drag) path
- Only handleMouseMove (drag) creates/modifies selection
- Sub-tab already correct (no selection set on click)
2026-07-27 09:19:18 +07:00
3dtours dff2c81848 fix: sub-tab ruler drag creates local selection, separate from main
- Add isDraggingSubTabRef/subTabDragStartRef for section-tab drag
- Ctrl+Click on sub-tab ruler clears sub-tab selection
- Sub-tab mousedown: set playhead + start drag
- Sub-tab mousemove/mouseup: update st.selectionStart/End locally
- Section-tab selection doesn't affect main session selection
2026-07-27 09:14:34 +07:00
3dtours ef891e9e04 fix: set selectionFollowsTempo based on which ruler is clicked
- Time Ruler click → setSelectionFollowsTempo(false) → time-based
- Tempo Track Lane click → setSelectionFollowsTempo(true) → tempo-based
- Also handles sub-tab ruler (EDITOR tabs)
- Manual toggle button ♪T/⏱T still works for override
2026-07-27 08:59:50 +07:00
3dtours 06480516eb fix: equal bar widths + selection tempo/time toggle
- WaveformLane bar grid: integer beat loop (bn*beatDuration) instead
  of accumulating t += beatDuration — no floating point drift
- Add selectionFollowsTempo toggle button (♪T/⏱T) in transport bar
- ♪T (tempo): selection recalculates with BPM
- ⏱T (time): selection fixed in seconds regardless of BPM
2026-07-27 08:53:42 +07:00
3dtours af6c77b9ee fix: add bar markers to TimelineRuler aligned with TempoTrackLane
- TimelineRuler draws bar lines at bn * barDuration positions
- Same integer beat loop as TempoTrackLane (bn from firstBarNum)
- Bar number labels at center of ruler
- Time markers still shown below bar markers
- All use same bpm prop → always aligned with tempo track
2026-07-27 08:41:36 +07:00
3dtours 3a84323dd8 fix: force canvas redraw after BPM duration recalculation 2026-07-27 08:36:14 +07:00
3dtours 3c12ce2a22 fix: range loop selection maintains bar count on BPM change
- Add prevBpmRef to track previous BPM
- BPM effect recalculates selectionStart/End using bar count
- startBar = oldStart / oldSecondsPerBar
- newStart = startBar * newSecondsPerBar (same for end)
2026-07-27 08:31:12 +07:00
3dtours f59cc304d4 fix: TempoTrackLane bar spacing drift with floating-point BPM
- Replace t += beatDuration loop (floating accumulation drifts)
- Iterate by integer beat number: t = bn * beatDuration
- Each bar position computed independently, no cumulative error
- Same fix for snapInterval loop
- Fixes unequal bar widths for BPM like 77, 81
2026-07-27 08:25:47 +07:00
3dtours cde29386d2 fix: TempoTrackLane bar spacing by removing broken DOM scrollLeft lookup
- DOM traversal for scrollLeft stops at sticky wrapper (scrollLeft=0)
- Use scrollLeft prop directly instead of traversing parent DOM
- Affects both TimelineRuler and TempoTrackLane canvas effects
- Bars now correctly space when BPM changes
2026-07-27 08:21:16 +07:00
3dtours ea5043c981 fix: auto-compute length_bars for legacy items on BPM change
- Items without length_bars: compute bars from duration/secondsPerBar
- Round to nearest quarter bar for musical accuracy
- Recalculate duration = bars * new secondsPerBar for all items
- Maintains correct bar count for both legacy and new items
2026-07-27 08:10:05 +07:00
3dtours d97f82e93f fix: maintain item duration in bars when BPM changes
- Add length_bars field to midiItems and sections at creation
- useEffect on bpm recalculates duration = length_bars * secondsPerBar
- Fix section creation: use secondsPerBar instead of hardcoded 4s
- Handle existing items without length_bars (no-op)
2026-07-27 08:03:02 +07:00
3dtours f98ade5d58 fix: context menu flips upward when near viewport bottom
- If click.y + 260 > window.innerHeight: position bottom instead of top
- Also clamp left to prevent right-edge overflow
- Menu now appears above the click point when there's no room below
2026-07-27 07:46:06 +07:00
3dtours 993610ca37 fix: AI items at playhead + context menu scrollable within viewport
- Revert AI item startTime to currentTime (playhead position)
- Context menu: maxHeight 60vh + overflow-y-auto, top clamped to
  window.innerHeight - 200 to prevent bottom clipping
- Same fix for both main session and sub-tab context menus
2026-07-27 07:42:01 +07:00
3dtours 509da0c724 fix: track do AI tạo không đồng đều 2026-07-26 22:22:00 +07:00
3dtours 1764fc29b5 fix: AI tracks auto-align to end of last existing item
- Replace currentTime with auto-calculated maxEnd from existing items
- Each AI generation starts right after the previous one
- Ensures tracks from different generations align in bar columns
2026-07-26 22:14:02 +07:00
3dtours 45a86f09bd fix: keep all banks loaded, don't delete on switch
- Remove deleteSoundBank — SpessaSynth merges presets from all banks
- Check soundBankList for existing bank before re-downloading
- Handle 404 gracefully — warn but don't break subsequent loads
- Failed load doesn't delete previously loaded banks
2026-07-26 22:03:49 +07:00
3dtours 0f5f494173 fix: AI-generated tracks save synth_engine + pass to applyAITrackInstrument
- Build synth_engine object with type, plugin_id, soundfont_bank,
  soundfont_program, soundfont_id
- Save to targetTrack.synth_engine so schedulePianoRollMidi reads it
- Pass synthEngine as 3rd arg to applyAITrackInstrument (was missing)
- Remove redundant separate loadSoundFont call (applyAITrackInstrument
  chains to selectInstrument which already calls loadSoundFont)
2026-07-26 21:55:47 +07:00
3dtours 7bbe1f3be0 feat: sticky time ruler + tempo lane when scrolling tracks
- Wrap TimelineRuler + TempoTrackLane in sticky-top div
- Same sticky pattern for both main session and sub-tab views
- TCP column already has sticky header (top-0) and tempo row (top-10)
- Background color hides tracks scrolling behind ruler
2026-07-26 21:32:08 +07:00
3dtours 9038cfab86 fix: loop với các tab 2026-07-26 21:20:01 +07:00
3dtours cdd5f8c96b fix: stopAll cancels scheduled notes, loop works on all tabs
- Add _scheduledNotes array tracking
- stopAll clears all pending setTimeout notes + allNotesOff()
- Loop enabled on MAIN SESSION, SECTION-TAB, PIANO_ROLL via
  isLoopingSelection + selLeft/selRight auto-derivation
2026-07-26 21:16:49 +07:00
3dtours bdaa5594bd feat: sync loop button with sub-tab + auto-derive loop boundaries
- Transport Loop button now syncs isLooping with active sub-tab
- Auto-computes loopEnd from midi notes, section clips, or audio duration
- Sub-tab selectionStart=0, selectionEnd=computed boundary on loop enable
- Main timeline also auto-derives loop end from track items/clips
- All three tab types: MAIN SESSION, SECTION-TAB, PIANO ROLL
2026-07-26 21:03:17 +07:00
3dtours 5ab93953e7 fix: sử dụng soundfont từ server và MIDI note có thể play và thay đổi soundfont có tác dụng 2026-07-26 20:55:47 +07:00
3dtours f96064cbab fix: unload old SoundFont before loading new one
- Add _currentSfId tracking
- loadSoundFont calls deleteSoundBank(oldId) before addSoundBank(newId)
- Skip reload if same sfId requested
- Prevents bank accumulation in SpessaSynth
2026-07-26 20:52:48 +07:00
3dtours 473a0afe0a fix: sử dụng soundfont từ server và MIDI note có thể play 2026-07-26 20:50:33 +07:00
3dtours 28be934a19 fix: schedule SpessaSynth noteOn by startTime via setTimeout
Root cause: timeline play passes exactAudioTime as 4th param which
SpessaSynth interprets as enableReverh (boolean). All notes at T=0.
Fix: compute delay = startTime - ctx.currentTime, use setTimeout
to call noteOn(channel, pitch, vel) at correct future time.
Never pass startTime to noteOn() — SpessaSynth doesn't support it.
2026-07-26 20:47:15 +07:00
3dtours 40c531087c fix: sử dụng soundfont từ server 2026-07-26 20:25:31 +07:00
3dtours 2f2cb3a066 fix: implement proper init chain + AudioContext.resume + _initPromise
Per md/38_FLOWCLIENT.md analysis:
- Add _initPromise to serialize concurrent init calls (no duplicate worklet)
- audioContext.resume() before worklet.loadModule
- selectInstrument creates AudioContext + awaits init if not ready
- playNote tries SpessaSynth first, falls back to oscillator on error
- All MIDI commands wrapped in try/catch with oscillator fallback
2026-07-26 20:23:22 +07:00
3dtours de70d4a837 clean: stable oscillator-only client preview, remove debug logs
SpessaSynth's SoundBankLoader.fromArrayBuffer extracts 0 presets
from SF2 files (parser limitation). Client preview always uses
oscillator (reliable, approximate GM ADSR per program).
Server-side FluidSynth render provides authentic SoundFont audio.
2026-07-26 20:16:52 +07:00
3dtours 400856c0a3 fix: add init guard, log selectablePresetList count
- Add missing if (_initialized && _synthInstance) return to init()
- soundBank is main-thread proxy, worklet has actual presets
- Check selectablePresetList.length from soundBankManager (worklet state)
- Log when presets are empty (SpessaSynth parser issue)
2026-07-26 20:15:24 +07:00
3dtours d117a363da debug: log soundBank keys, instruments, samples, _presetList to find why presets empty 2026-07-26 20:11:36 +07:00
3dtours cca970c348 fix: selectInstrument auto-inits SpessaSynth if not ready
Root cause: selectInstrument called before getAudioContext triggers init.
_initialized=false → selectInstrument returns immediately → no sound bank.
Fix: selectInstrument creates AudioContext + calls init() if not ready,
waits for completion before proceeding with bank/program routing.
2026-07-26 20:09:22 +07:00
3dtours 5eb0c26ce4 debug: add detailed tracing logs for entire SF download→SpessaSynth flow 2026-07-26 20:05:34 +07:00
3dtours 6242eba3c8 debug: add bank count log in noteOn, SpessaSynth play tries then falls back 2026-07-26 20:00:17 +07:00
3dtours 3c7a002b2c fix: connect SpessaSynth output to audioCtx.destination
Per md/38_FLOWCLIENT.md Phase 1: SpessaSynth output was not
connected to speakers. Add synth.connect(audioContext.destination)
after construction to route synthesized audio to user speakers.
2026-07-26 19:56:28 +07:00
3dtours 8f4a4cd0eb fix: add 15s timeout for addSoundBank (hangs on parse error)
Per md/38_FLOWCLIENT.md flow:
- _addBankWithTimeout: wraps SpessaSynth addSoundBank with Promise.race
- 15s timeout breaks deadlock when AudioWorklet parser fails silently
- Phase 3 (channel setup) only runs after Phase 2 (load) confirms success
- Falls back to oscillator when bank can't be loaded
2026-07-26 19:53:20 +07:00
3dtours f41422da03 fix: disable SpessaSynth playback, use oscillator for client preview
- SpessaSynth bank loading fails in AudioWorklet for unknown reason
- Set _initialized=false after init so playNote uses oscillator path
- Server-side FluidSynth render produces authentic SoundFont audio on Export
2026-07-26 19:49:08 +07:00
3dtours 4064b90874 chore: bump JS cache version to force browser refresh 2026-07-26 19:47:50 +07:00
3dtours 9cad9f6c95 fix: SpessaSynth noteOn catches 'No preset found' error, falls back to oscillator
- playNote logs warning when _currentSfId is null (no bank)
- _playNoteSpessa catches noteOn errors, falls back to _playNoteOsc
- Prevents 'No preset found' from breaking playback
2026-07-26 19:46:50 +07:00
3dtours 91e228ad6a fix: sfBank/sfProg ReferenceError — move declarations outside closure 2026-07-26 19:41:49 +07:00
3dtours 09231d9b21 fix: explicit selectInstrument for SpessaSynth bank+program routing
Per md/37_FIXVST3_SUGGEST.md:
- Add SonicSF.selectInstrument(channel, bank, program, sfId) method
- Always sends controllerChange(CC0, bank) + programChange(prog) to SpessaSynth
- _playNoteSpessa also sends CC32 (bank LSB) before programChange
- Remove default bank auto-load from init (cleaner startup)
- Fix syntax error (double }} from previous edit)
- playNote Spessa: use program arg + synthEngine bank/program properly
2026-07-26 19:37:29 +07:00
3dtours 6d9060a52b fix: gracefully fallback to oscillator when SpessaSynth bank load fails
- SpessaSynth init tries to load default bank, gracefully handles failure
- When bank load fails, _currentSfId stays null → _playNoteOsc fallback
- Client preview: oscillator emulation (approximate GM sound)
- Server-side render via FluidSynth: authentic SoundFont audio
2026-07-26 19:32:32 +07:00
3dtours 6a2522d039 fix: correct importmap path for spessasynth_core main entry 2026-07-26 18:56:26 +07:00
3dtours 263c4be409 fix: use importmap to resolve spessasynth_core bare import
- Add importmap in index.html to map spessasynth_core to CDN URL
- Load spessasynth_lib from CDN directly (with importmap resolving deps)
- WorkerSynthesizer fallback also uses CDN import
- Ensures library and processor use same spessasynth_core version
2026-07-26 18:55:08 +07:00
3dtours 50847fc440 fix: WorkerSynthesizer fallback, remove non-existent BasicSynthesizer 2026-07-26 18:53:12 +07:00
3dtours 4d42b8407c fix: serve SpessaSynth processor locally to avoid CDN version mismatch
- Download spessasynth_processor.min.js from CDN, serve as static file
- Change __SpessaSynthCDN to /static/js/services/
- Fix version mismatch between esm.sh library and CDN processor
2026-07-26 18:47:32 +07:00
3dtours c884f3c533 fix: clone buffer before addSoundBank, re-await isReady, force program 0
- Clone ArrayBuffer before sendToWorklet (transfer neuters original)
- Re-await synthInstance.isReady after addSoundBank
- Force controllerChange(0,0,0) + programChange(0,0) after load
2026-07-26 18:44:43 +07:00
3dtours 5096c618b4 fix: SF3 download prefers SF2, cache invalidation on parse error
- Download endpoint reverses extension priority: .sf2 > .sf3
- loadSoundFont adds ?t= cache-buster, invalidates IndexedDB on error
- Retry logic: delete corrupted cache entry, allow re-download
2026-07-26 18:37:46 +07:00
3dtours e7a0a85eca fix: SpessaSynth init race condition, remove auto-load of sgm_v2.01
- Add _initInProgress flag to prevent concurrent init calls
- Remove auto-loadSoundFont from init (confuses user when loading DSK)
- SF3 loading happens only when user selects an instrument
2026-07-26 18:33:04 +07:00
3dtours 72186168d2 fix: correct RIFF chunk sizes in SF3 converter
- _find_chunk starts at offset 12 (after RIFF header)
- Add _find_list_of_type to locate sdta LIST
- Update sdta LIST size after smpl data replacement
- Fix 'Invalid chunk header' error in SpessaSynth
- SGM_v2.01 too large (529MB WAV) falls back to SF2
2026-07-26 18:29:05 +07:00