Commit Graph

290 Commits

Author SHA1 Message Date
3dtours 385d62cfaf fix: Insert Section visible in SECTION-TAB context menu
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).
2026-07-28 09:55:37 +07:00
3dtours 67551075be fix: sửa lỗi loaded instrument ở SECTION-TAB nhưng không play được ở MAIN SESSION 2026-07-28 09:39:18 +07:00
3dtours 43691a127e fix: section track plays GM Piano via program default 0
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.
2026-07-28 09:30:27 +07:00
3dtours 2dec94aa7e fix: restore program default 0 for MAIN track MIDI playback
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.
2026-07-28 09:25:50 +07:00
3dtours ea40f0c540 revert: playhead fixes cause cannot play on MAIN/SECTION-TAB
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)
2026-07-28 09:22:44 +07:00
3dtours 5d31563df6 fix: playhead not moving in SECTION-TAB with empty tracks
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).
2026-07-28 09:20:31 +07:00
3dtours 2f2f84066c fix: main playback default program 0 loads MAIN SoundFont
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.
2026-07-28 09:15:51 +07:00
3dtours 52ec9379d6 fix: section sub-track MIDI default program 0 loads MAIN SoundFont
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.
2026-07-28 09:10:33 +07:00
3dtours 9c2fa52e1b revert: Synth button conditional on section items - track can have both
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).
2026-07-28 08:50:13 +07:00
3dtours 815a6faaca fix: Synth button shown on section/audio tracks
Synth button was unconditional in TCP. Now gated:
track.midiItems?.length > 0 && !track.sections?.length
Section tracks: Synth hidden (sections are containers).
MIDI tracks: Synth shown.
Audio tracks: Synth hidden.
FX button stays unconditional for all track types.
2026-07-28 08:37:31 +07:00
3dtours 9befc20851 fix: section track inherits soundfont_id/bank/program via ...t spread
...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.
2026-07-28 08:32:21 +07:00
3dtours 18e177fa2d revert: section tracks inherit parent instrument, auto-loads SF prog0
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).
2026-07-28 08:27:27 +07:00
3dtours f2ac70eee0 fix: section sub-tracks nullify instrument instead of inheriting
handleEditSectionInTab fallback branch explicitly set
instrumentId: null, instrumentProgram: undefined,
instrumentName: null, overriding parent track's values
spread by ...t. Changed to inherit from parent track:
instrumentId: t.instrumentId || null,
instrumentProgram: t.instrumentProgram,
instrumentName: t.instrumentName || null
2026-07-28 08:18:43 +07:00
3dtours ed51cab765 fix: instrument load from PIANO_ROLL in SECTION-TAB writes to main
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.
2026-07-28 08:11:36 +07:00
3dtours 447504ea67 fix: Ctrl+Click on section/MIDI items toggled selection, locked item
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.
2026-07-28 08:07:49 +07:00
3dtours e4d6405b37 fix: PIANO_ROLL activeTracks resolves main tracks, not section
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.
2026-07-28 08:01:22 +07:00
3dtours 2baf034852 fix: section tab clears midiItems, breaks ghost+dropdown
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.
2026-07-28 07:51:08 +07:00
3dtours d40ac47d65 fix: scanner catalog empty + left column blank
Scanner: new instance with existing sf_scan_state.json
did not populate in-memory _catalog. Now re-inspects
unchanged files to fill catalog on first scan.
Frontend: left column reads instrumentSelectorData.
soundfonts instead of sfPresets (null when no presets).
Click SF in left column triggers on-demand fetch.
2026-07-28 07:32:40 +07:00
3dtours 14e3a561a3 feat: instrument selector modal 2-column layout
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.
2026-07-28 07:20:01 +07:00
3dtours 1dbece1fc7 fix: multi-item drag mousemove didn't handle clips
The multi-item drag mousemove handler only processed
info.type === 'section' and 'midiItem'. Added 'clip' support
(midClips slice + findIndex + update).
2026-07-27 22:32:11 +07:00
3dtours 1ca2ec6d01 fix: Ctrl+click+drag copies pre-toggle selection, not post-toggle
Ctrl+click toggle selection THEN drag started -> selIds.has(itemId)
returned false because the item was just removed from snapshot.
Fix: snapshot selectedItemIds BEFORE toggle, pass to pendingDrag.
Drag always copies the original group; toggle is only visual.
2026-07-27 22:31:29 +07:00
3dtours 292c787a31 fix: multi-item MOVE also discarded cross-track updates
Same bug as copy: return statement only updated
targetTrackId/drag.trackId, discarding items from other tracks.
Fix: always return { sections: midSections, midiItems: midMidis }
for ALL tracks in the map (arrays are sliced per-track).
2026-07-27 22:27:24 +07:00
3dtours b84507bd64 fix: multi-ids duplicate only returned changes for drag-start track
The updateActiveTracks return statement was:
  return t.id === trackId ? updated : t;
This discarded duplicate items that belong to other tracks.
Fix: always return updated{ sections, midiItems, clips }
for EVERY track in the map (already sliced from originals,
so unchanged tracks are no-ops).
2026-07-27 22:26:55 +07:00
3dtours 0992879b49 fix: selectedItemIds snapshot includes toggled item for drag
React batches setSelectedItemIds, so when handleSetPendingDrag
snapshots selectedItemIds, the just-toggled item isn't included
yet. Fix: pass wasAlreadySelected boolean from TimelineTrack
mousedown; handleSetPendingDrag manually applies the toggle
(add/delete) to the snapshot so drag uses correct selection.
2026-07-27 22:25:11 +07:00
3dtours 74fdc2b303 fix: pending drag effect calls stale handleSectionItemDragStart
The useEffect with [] deps captured handleSectionItemDragStart
from initial render, which read an empty selectedItemIds.
Added handleSectionItemDragStartRef that stays current across
renders; the effect calls ref.current instead of the closure.
2026-07-27 22:21:17 +07:00
3dtours c99527e687 refactor: Ctrl+click toggles selection, Ctrl+click+drag copies group
Changed behavior:
- Ctrl+Click on item (no drag): toggle selection (add/remove)
- Ctrl+Click+Drag on item (movement > 5px): copy selected group
- Alt+Click: move item immediately
- No modifier + click selected item: move group

Added pendingDragRef + useEffect to detect mousemove threshold
before starting copy-drag. onAddToSelection + onSetPendingDrag
props wired through WaveformLane.
2026-07-27 22:14:01 +07:00
3dtours e055e8b5c8 feat: Ctrl+click deselect + global empty-area sweep
- Ctrl+click on selected item: deselect that item only
- Ctrl+click on empty space (via sweep start): deselect all items
- Ctrl+drag on empty space BETWEEN or BELOW tracks: creates
  sweep overlay scanning ALL tracks for intersecting items
- onDeselectItem handler + prop wired through WaveformLane
- Global onMouseDown on tracks container div for empty-area sweep
2026-07-27 21:59:33 +07:00
3dtours 85ad22ee4f fix: sweep select now covers all tracks
- Removed sweepTrackIdRef filter in mouseup handler:
  all tracks are scanned for intersecting items.
- Removed track ID check from sweep overlay rendering:
  overlay appears on every track (not just drag-start track).
- Fixed stale closure by adding sweepSelectRef.
2026-07-27 21:54:12 +07:00
3dtours b8dfc9b211 fix: sweep select stale closure + add sweepSelectRef
handleMouseUp captured stale sweepSelect state from render
closure. Added sweepSelectRef updated on every mousemove
so mouseup reads the latest sweep range correctly.
2026-07-27 21:51:53 +07:00
3dtours fd24c2dcd8 feat: Ctrl+drag sweep select + multi-item copy/drag
- 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
2026-07-27 21:46:20 +07:00
3dtours 4b732844c9 fix: sửa lỗi hiển thị icon trước menu item ở menu context trong MAIN SESSION 2026-07-27 21:38:10 +07:00
3dtours 430e87445e fix: context menu icons only appear on first open
useEffect watching [activeTool, activeTab] runs createIcons()
but contextMenu is NOT in deps. On first right-click, if
activeTool/activeTab also change (e.g. timeline click), the
effect fires and icons appear. Subsequent right-clicks don't
change activeTool/activeTab, so createIcons() never runs for
the new context menu DOM.

Fix: add contextMenu to useEffect dependency array.
2026-07-27 21:35:53 +07:00
3dtours 8beef7cb4d fix: multi-track ARM routes MIDI to all armed tracks
- 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
2026-07-27 21:31:10 +07:00
3dtours e38391ac7d fix: brush draw preview note uses wrong instrument
Line 5273 called playNote with undefined program + no channel,
defaulting to channel 0 with last-set instrument (may be Track 2's).
Fix: resolve parent track from st.trackId, pass correct channel
and synth_engine/instrumentProgram.
2026-07-27 20:52:49 +07:00
3dtours b42190e6bc feat: implement md/41_INSTRUMENT.md spec
- PianoRollTabService: getParentTrackByItemId, buildActiveScope,
  getTrackMidiChannel helpers
- parent_track_id on all MIDI items for reverse track lookup
- handleSwitchMidiItem uses active_scope pattern
- Toolbar shows parent track context labels
- Channel assignment simplified to trackIndex % 16
- All playNote calls use unified getTrackMidiChannel
2026-07-27 20:48:14 +07:00
3dtours 40502eda86 fix: pass channel+synth_engine to playNote in all MIDI playback paths
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.
2026-07-27 20:42:45 +07:00
3dtours 912fb2d354 fix: fallback channel for unconfigured tracks uses index
When midiChannel is undefined (no instrument set), compute channel
from track index to avoid defaulting to 0 and clashing with
configured tracks that use channel 0.
2026-07-27 20:19:50 +07:00
3dtours 59cf1d67db fix: store midiChannel on track object for consistent per-track playback
- setTrackInstrumentWithProgram stores midiChannel on the track
- schedulePianoRollMidi reads track.midiChannel instead of
  recomputing from index (can be inconsistent across calls)
- selectInstrument also stores midiChannel if not already set
2026-07-27 20:19:20 +07:00
3dtours 0420eccb82 feat: auto-loop on range selection + per-track instrument isolation
- isLooping defaults to true on subTab creation (auto-loop when
  range selection is set)
- Per-track instrument confirmed: selectInstrument respects passed
  channel, _playNoteFluid bypasses _engineChMap when channel is
  explicit, no-instrument fallback sets program 0 (default piano)
  instead of inheriting another track's program
2026-07-27 20:16:55 +07:00
3dtours e14554a75a fix: session-absolute timing for playhead + MIDI scheduling
- st.currentTime is now session-absolute (0 = session bar 0)
- schedulePianoRollMidi adds sessionBeatOffset to note timing so
  notes at renderBeatOffset beats are scheduled with correct delay
- Playhead rendering: removed renderBeatOffset (uses st.currentTime
  directly as session-absolute beats)
- handleEditMidiInTab/handleSwitchMidiItem: currentTime = 0
- Ruler click: clickTime = clickBeat * beatSec (session-absolute)
2026-07-27 19:55:58 +07:00
3dtours a60607c673 fix: ghost playback only when track button is red
- When activePlayTrackIds is null (no button selected): no ghost play
- When activePlayTrackIds is set (one button red): only that track's
  ghost notes play, with the track's own instrument on its own channel
2026-07-27 19:49:10 +07:00
3dtours c9176a27fa fix: single-track active mode — only one button red at a time
activePlayTrackIds changed from Set to single trackId string.
All buttons default gray. Click toggles red on one track only.
2026-07-27 19:44:03 +07:00
3dtours db4bdd2471 fix: track buttons fit column width, gray default, red on click
- Removed w-full so button widths are auto (fit content)
- Default: bg-zinc-700 text-zinc-300 hover:bg-zinc-600
- Play-on click: bg-red-700 text-white
2026-07-27 19:42:28 +07:00
3dtours 60141cef3f style: track buttons with border, rounded, 20px height, 14px font, red active
- Each button: border border-zinc-600 rounded-md, h-[20px], text-[14px]
- Active/play-on track: bg-red-700 (red), selected track: bg-yellow-600
- Off track: bg-zinc-800 text-zinc-500
2026-07-27 19:41:06 +07:00
3dtours 6ff2f72c58 fix: playhead at bar 0 when opening MIDI item in session mode
- Removed st.currentTime >= 0 check from playhead rendering
- handleEditMidiInTab: currentTime = -beatOff * beatSec so phBeat=0
- handleSwitchMidiItem: same logic for dropdown switches
- Playhead draws at x=0 (bar 0) when item opens, moves right during
  playback from the initial negative position
2026-07-27 19:37:59 +07:00
3dtours ab4b91a158 fix: shift+scroll velocity changes all selected notes
When selectedNoteIds.length > 0, shift+scroll on any note changes
velocity for all selected notes. Also fixed hit-test to subtract
renderBeatOffset for correct positioning in session mode.
2026-07-27 19:36:54 +07:00
3dtours db45efe53c fix: sync piano roll playhead with main timeline position
handleEditMidiInTab now sets currentTime relative to item start:
currentTime = Math.max(0, mainTimelineCurrentTime - midiItem.startTime)
so playhead reflects the main timeline position instead of bar 0.
2026-07-27 19:29:51 +07:00
3dtours fd23b8c4b0 fix: align CC lane velocity label with keybed column
Added 120px spacer before the CC label div so it aligns with
the piano key column (60px) after the track column (120px).
2026-07-27 19:28:51 +07:00
3dtours 1d3c7a7955 fix: preserve playhead session position across dropdown switch
handleSwitchMidiItem now computes new currentTime from the delta
between old and new renderBeatOffset, keeping the playhead at
the same session-absolute time instead of resetting to bar 0.
2026-07-27 19:28:01 +07:00
3dtours 3ed62b3343 fix: subtract renderBeatOffset from mouse beat for correct positioning
- handleGridMouseDown/move: beat = x/pixelsPerBeat - renderBeatOffset
  so hit-testing and note creation use item-relative coordinates
- CC mouse handlers: same offset subtraction
- Ruler click: clickTime subtracts renderBeatOffset so st.currentTime
  remains item-relative
2026-07-27 19:25:29 +07:00