Commit Graph

269 Commits

Author SHA1 Message Date
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
3dtours 3a775fdda7 fix: track button toggle bug — init Set with all track IDs
When activePlayTrackIds was null (default: all play), clicking a track
created an empty Set and deleted nothing. Now initializes with all
track IDs, so click toggles the clicked track off/on correctly.
2026-07-27 18:37:25 +07:00
3dtours 509e171c20 fix: ghost border, track buttons clickable, per-track instrument silent
- Remove dashed border from ghost notes (fill-only at 25% opacity)
- Track column: replaced div overlay with natural flex child; use
  <button> elements with border-l-2 indicator; removed pointer-events
- Ghost playback: skip layers with no instrument assigned (silent)
  instead of playing with default piano
2026-07-27 18:37:25 +07:00
3dtours 0500d16bc7 feat: track column buttons, ghost playback, per-track channels
- Track column: centered 12px buttons, purple active / yellow highlight
- Ghost playback: ghostLayers synced to st.ghostPlayLayers via useEffect;
  schedulePianoRollMidi plays ghost notes from active tracks
- Per-track MIDI channels: each track gets a unique channel based on
  its index, enabling separate instruments per track
2026-07-27 18:37:25 +07:00
3dtours a98dd58744 fix: renderBeatOffset in beats (multiply by timeSigNum)
renderBeatOffset was in bars but used as beat offset in rendering.
Multiplying by timeSigNum=4 converts to beats so items appear at
correct session-absolute positions.
2026-07-27 18:03:44 +07:00
3dtours 1ba9e8379f feat(piano-roll): session bar0, fixed track column, renderBeatOffset
- sessionStartBar = 0 always; notes rendered at session-absolute positions
- renderBeatOffset aligns active/ghost notes to session timeline
- Track column uses absolute overlay with pointer-events for always-visible
- Removed auto-scroll (viewport starts at bar 0 in session mode)
- Grid spacer (120px) aligns grid with ruler track header
2026-07-27 17:57:47 +07:00
3dtours b9113f8a06 fix: dropdown switch keeps ghost notes from other tracks only; track column toggles MIDI play
- handleSwitchMidiItem no longer saves/sets previous item as ghost
- Removed ghostTrackFilter/filteredGhostLayers
- Track column shows clickable track names that toggle play state
- activePlayTrackIds Set controls which tracks' MIDI are playable
2026-07-27 17:40:31 +07:00
3dtours 91ddc3cb6f fix: move ghost decls before totalBeats to fix TDZ 2026-07-27 17:34:14 +07:00
3dtours fb0cd248ad feat(piano-roll): context menu flip, track column, session duration
- Context menu flips upward near viewport bottom edge
- Track column (120px) left of keybed: checkboxes per track to
  filter ghost notes; active track highlighted
- Session mode: canvas extends to full session duration
- Filtered ghost layers via ghostTrackFilter state (Set of track IDs)
- Fixed bar label seek in session mode (was using wrong offset)
2026-07-27 17:32:06 +07:00
3dtours 53a27b5af4 fix: move ghostLayers decl before useLayoutEffect to fix TDZ 2026-07-27 17:15:04 +07:00
3dtours 035d75e504 feat(piano-roll): ghost notes + dropdown item switcher + session sync
- 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
2026-07-27 17:08:59 +07:00
3dtours 095a0680e8 fix(build): remove extra brace at L6795, Babel build now succeeds; feat(ai-copilot): rows=4, resize-y, cursor-aware ArrowUp/Down 2026-07-27 16:36:26 +07:00
3dtours 4ed0b1e5be fix(ai-copilot): patch trực tiếp app.precompiled.js rows=4 + cursor check cho ArrowUp/Down 2026-07-27 16:21:24 +07:00
3dtours cb23d36bc8 fix: multi-track ARM + activeNotes map cho stopNote
1. precompiled: find(t=>isArmed) → filter(t=>isArmed) loop all tracks
2. soundfontPlayer: _activeNotes[ch:pitch] = mappedCh lưu khi noteOn
3. stopNote lookup mappedCh từ _activeNotes thay vì dùng MIDI ch 0
→ Fix: đổi SF track → channel mới → noteOff vẫn tìm đúng voice
2026-07-27 15:48:34 +07:00
3dtours d9ad60768e fix: patch precompiled JS - thêm stopNote + duration 60000
app.precompiled.js được build từ app.jsx cũ (10:54), thiếu:
- SonicSF.stopNote() trong MIDI note-off handler → note không tắt
- duration 500ms (quá ngắn, nên 60000ms = hold)
Patch thủ công bằng Python replace trên minified code.
2026-07-27 15:24:33 +07:00
3dtours 5c47b68a9d fix: single Synth button opens instrument modal, fix init race condition
- Remove small Synth button (already done earlier)
- Large Synth button now calls openInstrumentSelector (instrument modal)
- Fix _initPromise stale cache: reset to null when init fails
- Module not loaded yet → retries on next select instead of stuck
2026-07-27 10:54:58 +07:00
3dtours cf61b8431d revert: restore small Synth button to dropdown (preserve SF3 flow) 2026-07-27 10:49:42 +07:00
3dtours e5bd442b7c fix: small Synth button opens instrument modal (not SF list dropdown) 2026-07-27 10:40:20 +07:00
3dtours 55a27b5957 fix: remove sf.source references causing ReferenceError in PluginManagerModal 2026-07-27 10:35:42 +07:00
3dtours 3a6b44e195 feat: Synth button opens modal with flat instrument list from all SFs
- openInstrumentSelector builds flat list of all instrument presets
- Modal shows all instruments with search bar
- Each preset shows sf name + program name
- Remove old two-step (SF list -> presets) navigation
- Fix pre-existing closing paren mismatch in PluginManagerModal
2026-07-27 10:11:44 +07:00
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