Ctrl+S in SECTION-TAB (activeTab starts with session_):
calls handleSaveSectionTab(curTab).
Ctrl+A in MAIN/SECTION-TAB: collects sections, midiItems,
clips from activeTracksRef.current into selectedItemIds.
Ignores sub-tab contexts (PIANO_ROLL).
JSX rendering menu at line 17500 lacked the
!sessionTabs.some(s => s.id === activeTab) guard.
Added it - section tab context menu no longer offers
Insert Section (sections cannot contain section items).
_playNoteFluid called program_change synchronously at
call time, not at scheduled note time. startTrackPlayback
processes all MIDI items first, then all sections -> each
note's program_change overwritten by the last type processed.
All notes played with section instrument regardless of
actual time position. Moved program_change into doNote
(setTimeout callback) so it fires at correct time.
section cloned tracks have instrumentProgram:undefined.
Line 10283 defaulted to 0 -> FluidSynth program_change(ch,0)
-> plays whatever SoundFont is loaded (MAIN SF).
Added _isSectionClone flag; line 10283 skips program default
for section clones -> undefined -> _playNoteFluid silent return.
_playNoteFluid no-instrument branch previously called
_playNoteFallback (sine wave oscillator). Changed to
silent return. User explicitly requested no default sound.
Line 10283 defaulted to undefined when instrumentProgram
undefined, breaking Play for MAIN tracks with MIDI items
but no instrument. Restored: undefined ? instrumentProgram : 0.
Section sub-track (line 10393) keeps undefined -> oscillator
fallback to avoid loading MAIN SoundFont.
Revert two changes from 5d31563:
1. Explicit requestAnimationFrame in handlePlayPause (redundant,
useEffect already handles RAF start after setIsPlaying)
2. setCurrentTime(0) in handleEditSectionInTab (resets global
currentTime, interferes with MAIN session position)
Two fixes:
1. handlePlayPause: add explicit requestAnimationFrame(updatePlayhead)
after setIsPlaying(true) to ensure RAF starts immediately.
2. handleEditSectionInTab: add setCurrentTime(0) to reset playhead
to section start. Without this, currentTime may be outside
the section's timeline range (e.g. from main session playback).
Line 10283 same issue as 10393: when instrumentProgram is
undefined (section cloned tracks), program defaults to 0.
_playNoteFluid calls program_change(ch,0) on FluidSynth,
which loads program 0 from whatever SoundFont is loaded
(MAIN session's SF). Fix: pass undefined -> oscillator
fallback for all tracks without assigned instrument.
Line 10393 defaulted program to 0 when subTrack.instrumentProgram
was undefined (section clone with no instrument). _playNoteFluid
receives program=0, calls program_change(ch,0) on FluidSynth,
which picks program 0 from whatever SoundFont is loaded (MAIN
session's SF). Fix: pass undefined -> _playNoteFluid takes
oscillator fallback, no SoundFont loading.
815a6fa hid Synth when track has sections. But a track
can contain both section items AND midi items. Synth
applies to MIDI items regardless of sections. Section
instrument isolation is handled by section clone
nullifying all soundfont fields (9befc20).
...t in fallback branch copied soundfont_id, soundfont_bank,
soundfont_program, instrument_source from parent MAIN track
even though instrumentProgram and synth_engine were nulled.
FluidSynth picks up these fields and auto-loads the SoundFont.
Fix: override all soundfont/synth fields to null/undefined.
Previous fix f2ac70e inherited instrumentId/program/name
from parent MAIN track. This causes SECTION-TAB to auto-load
program 0 of whatever SoundFont is loaded on MAIN, even
though user didn't select any instrument for the section.
Section tracks now keep null instrument + synth_engine
undefined. User can still load instrument manually via
Synth button (routed by updateActiveTracks fix).
updateActiveTracksRef checks sessionTabs by currentActiveTab.
When activeTab is a PIANO_ROLL sub-tab (midi_xxx), none matches
-> falls to setTracks (main). Instrument changes are lost.
Fix: also check subTabs for PIANO_ROLL type, resolve parent
session tab via parent_tab_id, route to setSessionTabs.
Ctrl+Click handler toggled selection + set pending drag.
Once selected, drag system treated it as 'move selection'
instead of 'copy'. Removed selection toggle - Ctrl+Click
now only sets pending drag for copy operation.
When PIANO_ROLL sub-tab is opened from SECTION-TAB,
activeTab switches to midi_xxx, so activeTracks fell
back to main project tracks. Section trackIds don't
exist in main tracks -> dropdown empty, ghost layers
empty, instrument data wrong.
Fix: check subTabs for PIANO_ROLL type, resolve parent
section tab from parent_tab_id, return its tracks.
handleEditSectionInTab overwrote midiItems: [] when cloning
section.tracks, stripping all MIDI data from section context.
PianoRollTabEditor uses activeTracks which resolves section
tracks -- midiItems must be preserved for dropdown switcher,
ghost note extraction, and instrument settings to work.
Modal max-w-md -> max-w-4xl. Font 14px (text-sm).
Left column: soundfont list. Right column: instruments
of selected SF. All Instruments view when none selected.
Search filters both columns.
Background daemon (30s interval) tracks scanned files via
sf_scan_state.json (size+mtime). Only inspects new/changed SFs.
Catalog served from scanner cache, no full re-scan per request.
Upload/delete trigger immediate re-scan.
- Ctrl+drag on empty space: sweep range selection overlay
- Mouseup selects all items (MIDI, sections, clips) in sweep
- Selected items rendered with amber border highlight
- Ctrl+drag on section/MIDI item: duplicates the item
- Drag selected items: moves entire selected group together
- Ctrl+drag selected items: duplicates the entire group
handleRulerMouseDown's Ctrl+click branch previously only
cleared selection. Now it sets selectionMode='global',
records drag start, and enables isDraggingRulerRef so the
existing document-level mousemove handler updates the
selection range in real-time.
- Use filter(t => t.isArmed) instead of find() to route
MIDI input to ALL armed tracks simultaneously
- Use getTrackMidiChannel per track instead of raw
MIDI hardware channel (msg.data[0] & 0x0F)
- NoteOff, CC, PitchBend also routed to each armed
track's dedicated channel
- Previous code sent to channel 0 + first armed track only
6 call sites were missing channel parameter, causing FluidSynth
to use ch=0 for all tracks -> program_change on channel 0
overwrote instrument across tracks during playback.
Fixes: startTrackPlayback, section sub-track MIDI, playMidiPreviewNote,
piano roll Alt+scroll preview, note click preview, keybed preview.
All 4 rules confirmed working:
1. MIDI item inherits track instrument
2. Section item does NOT inherit
3. Track isolation preserved
4. Piano roll instrument change propagates back to track
- MIDI ghost notes: all non-selected items rendered at 25% opacity
- Dropdown at tab title: switch active edit target across all tracks
- Session Sync mode (default): viewport aligned to session bars
- Isolated mode toggle: bar 0, no ghost notes
- Ghost toggle: show/hide ghost layer (only in session mode)
- Auto-scroll to session position in session sync mode
- Bar labels show absolute session bar numbers