Commit Graph

612 Commits

Author SHA1 Message Date
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 d0def4bf1e feat: auto-scan soundfont for Synth instrument list
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.
2026-07-28 07:06:37 +07:00
3dtours e68fe27d7f fix: sửa lỗi ctrl+click+drag để copy các items trong MAIN SESSION và SECTION-TAB 2026-07-27 22:33:36 +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 80a5352645 feat: Ctrl+drag on ruler creates global selection
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.
2026-07-27 21:38:23 +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 74992f7b55 fix: sửa lỗi mỗi track đều chỉ chơi instrument của track mình 2026-07-27 21:35:42 +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 08cd4d72cf fix: sửa lỗi khi set instrument cho track 2 thì khi vẽ midi note ở track 1 cũng phát âm thanh từ track 2 2026-07-27 21:28:43 +07:00
3dtours 081a17d537 docs: add _playNoteFluid else branch fix to wiki 2026-07-27 21:27:04 +07:00
3dtours 6dec171af7 fix: _playNoteFluid else branch leaked other track's SF program 0
When a track has no instrument configured, _playNoteFluid called
program_change(ch, 0). FluidSynth searches ALL loaded SoundFonts
to resolve program 0 — if only another track's SoundFont is loaded,
that SF's program 0 gets applied to the wrong track.

Fix: use oscillator fallback directly instead of program_change.
2026-07-27 21:26:52 +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 f429f6b1d3 docs: verify instrument assignment rules for MAIN SESSION
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
2026-07-27 20:32:30 +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 b5344e41f7 fix: default instrument for tracks without instrument set
_playNoteFluid: when no synthEngine and no program, fallback to
program 0 on channel (default piano) so unconfigured tracks still
produce sound via FluidSynth
2026-07-27 20:09:49 +07:00
3dtours 8c5d71c2f0 fix: sửa lỗi hiển thị của midi ghost note 2026-07-27 20:09:10 +07:00
3dtours 4a9cbcdef1 fix: per-track instrument + ghost note visibility
soundfontPlayer.js:
- selectInstrument: respect passed channel (don't allocate new one)
- _playNoteFluid: only use _engineChMap when channel is undefined

ghostNoteExtractor.js:
- Include ALL notes from overlapping items (not clipped)
- relative_start_beat can be negative (notes before window)
- Keep original duration instead of clamping
2026-07-27 20:04:56 +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 9c0909c3fa fix: sửa lỗi hiển thị của midi ghost note 2026-07-27 19:39:01 +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 1628a34ea0 fix: ghost overlap use inclusive boundary for adjacent items
Changed itemEndBeat <= windowStartBeat → < and
noteAbsEnd <= windowStartBeat → < so abutting items
(Item 1 ends at bar 1, Item 2 starts at bar 1) show
ghost notes from the adjacent item.
2026-07-27 19:31:36 +07:00