Commit Graph

393 Commits

Author SHA1 Message Date
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 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
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 d21091b69b feat: hiển thị ghost note trong piano roll tab 2026-07-27 17:26:13 +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 c4a320a1c1 fix: AI panel hiển thị nội dung nhiều hơn 2026-07-27 16:43:17 +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 29206d62a3 fix(ai-copilot): tăng chiều cao prompt input rows=4 + resize-y, sửa ArrowUp/ArrowDown chỉ gọi history khi cursor ở đầu/cuối text 2026-07-27 16:16:26 +07:00
3dtours 47d1a14f50 fix: play các soundfont và có thể tắt các âm khi thả phím 2026-07-27 16:13:39 +07:00
3dtours 2b04b32ea8 fix: dùng fluid_synth_program_select thay vì bank_select+program_change
Khi load SF2, preset lookup của program_change dùng search order
(lần lượt qua các SF đã load). Track 1 load SF2→SF2 presets override
SF1 trên tất cả channel, track 2 chọn nhầm preset của SF2.
Fix: program_select(synth, ch, sfHandle, bank, prog) chỉ định rõ
SoundFont ID, không phụ thuộc search order.
2026-07-27 16:08:54 +07:00
3dtours 8965643450 cleanup: bỏ dead code stopNote + debug log _playNoteFluid 2026-07-27 16:00:33 +07:00
3dtours 16e33b2e6a fix: _activeNotes lưu array channel cho mỗi key
Cùng MIDI pitch 60, track 1→ch0, track 2→ch1 cùng key '0:60'
→ single value bị overwrite, stopNote chỉ stop 1 channel.
Fix: _activeNotes[key] = [ch1, ch2, ...], stopNote iterate all.
2026-07-27 16:00:11 +07:00
3dtours 838e173dd7 debug: log _playNoteFluid params + stopNote activeNotes keys 2026-07-27 15:56:28 +07:00
3dtours ce7d23c351 fix: _origChannel tránh channel bị ghi đè bởi mappedCh
Line 378: 'if (channel===undefined) channel=ch' gán channel = FluidSynth
channel (2), sau đó _activeNotes key dùng channel (2) thay vì MIDI
channel (0). stopNote lookup bằng 0 → key mismatch.
Fix: _origChannel = channel ngay đầu hàm, dùng _origChannel cho key.
2026-07-27 15:54:06 +07:00
3dtours 987ecffc6c fix: activeNotes key dùng MIDI channel gốc, không phải FluidSynth channel
Key _activeNotes[ch:midiPitch] dùng ch đã remap (FluidSynth channel),
stopNote lookup bằng MIDI channel (0) → không tìm thấy.
Fix: key = (channel ?? 0) + ':' + midiPitch (MIDI channel gốc).
2026-07-27 15:50:28 +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 638d56ce63 fix: engine->channel map cho multi-track ARM
_engineChMap: key=sfId:bank:prog → allocated MIDI channel.
Mỗi track ARM riêng biệt có engKey khác nhau → allocated channel
riêng (0-8 round-robin). _playNoteFluid lookup channel từ map
thay vì dùng MIDI channel gốc (luôn 0).
2026-07-27 15:40:45 +07:00
3dtours 3d7bae94f2 fix: schedule noteOff even cho immediate notes (durationMs < 60000)
Trước đây chỉ schedule noteOff khi delay > 0 (notes scheduled).
Virtual piano keyboard play note immediate (delay===0) với duration=500ms
nhưng không có noteOff → âm thanh play mãi không tắt.
Fix: schedule noteOff khi 0 < durationMs < 60000, không phụ thuộc delay.
2026-07-27 15:36:03 +07:00
3dtours b947237c3c fix: đã play được các soundfont với FluidSynth WASM 2026-07-27 15:33:20 +07:00
3dtours 00566e8ea6 fix: stopNote dùng noteoff+noteon(vel0) thay vì all_sounds_off
- Bỏ _fluid_synth_all_sounds_off (kill cả chord)
- Thử noteoff + noteon(vel=0) - MIDI noteOff alternate
- Log cả 2 return code
2026-07-27 15:27:53 +07:00