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
This commit is contained in:
+34
-21
@@ -4598,7 +4598,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
|
||||
const [showGhostNotes, setShowGhostNotes] = React.useState(true);
|
||||
const [sessionSyncMode, setSessionSyncMode] = React.useState(true);
|
||||
const [ghostTrackFilter, setGhostTrackFilter] = React.useState(null);
|
||||
const [activePlayTrackIds, setActivePlayTrackIds] = React.useState(null);
|
||||
|
||||
const allMidiItems = React.useMemo(() => {
|
||||
const result = [];
|
||||
@@ -4638,10 +4638,29 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
return Math.ceil((maxSec || 4) / secondsPerBar);
|
||||
}, [activeTracks, secondsPerBar]);
|
||||
|
||||
const filteredGhostLayers = React.useMemo(function() {
|
||||
if (ghostTrackFilter === null || !ghostLayers.length) return ghostLayers;
|
||||
return ghostLayers.filter(function(l) { return ghostTrackFilter.has(l.track_id); });
|
||||
}, [ghostLayers, ghostTrackFilter]);
|
||||
const handleSwitchMidiItem = function(itemId) {
|
||||
if (itemId === st.target_id) return;
|
||||
var match = allMidiItems.find(function(m) { return m.id === itemId; });
|
||||
if (!match) return;
|
||||
var trk = (activeTracks || []).find(function(t) { return t.id === match._trackId; });
|
||||
setSubTabs(function(prev) {
|
||||
return prev.map(function(s) {
|
||||
if (s.id !== st.id) return s;
|
||||
return Object.assign({}, s, {
|
||||
trackId: match._trackId,
|
||||
target_id: match.id,
|
||||
label: 'Piano Roll: ' + (match.name || 'MIDI'),
|
||||
notes: match.notes || [],
|
||||
duration: match.duration || 4,
|
||||
instrumentProgram: trk ? trk.instrumentProgram : undefined,
|
||||
instrumentName: trk ? trk.instrumentName : undefined,
|
||||
note_selection: [],
|
||||
currentTime: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
setSelectedNoteIds([]);
|
||||
};
|
||||
|
||||
const totalBeats = sessionSyncMode ? Math.max(rollBeats, sessionLengthBars * 4 + 16, 64) : Math.max(rollBeats, noteMaxBeat + 16, 64);
|
||||
const drawWidth = totalBeats * pixelsPerBeat;
|
||||
@@ -4880,8 +4899,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
}
|
||||
|
||||
// Layer 2: Ghost Notes (background reference from other tracks)
|
||||
if (showGhostNotes && sessionSyncMode && filteredGhostLayers.length > 0) {
|
||||
filteredGhostLayers.forEach(function(layer) {
|
||||
if (showGhostNotes && sessionSyncMode && ghostLayers.length > 0) {
|
||||
ghostLayers.forEach(function(layer) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.25;
|
||||
ctx.fillStyle = layer.track_color || '#888';
|
||||
@@ -4973,7 +4992,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}, [notes, snapVal, rollZoom, selectedNoteIds, selectionMarquee, st.currentTime, bpm, viewWidth, viewBeats, recordingState, recTempMidiNotes, showGhostNotes, sessionSyncMode, filteredGhostLayers]);
|
||||
}, [notes, snapVal, rollZoom, selectedNoteIds, selectionMarquee, st.currentTime, bpm, viewWidth, viewBeats, recordingState, recTempMidiNotes, showGhostNotes, sessionSyncMode, ghostLayers]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const canvas = ccCanvasRef.current;
|
||||
@@ -6113,27 +6132,21 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
|
||||
seenTracks[m._trackId] = true;
|
||||
var track = (activeTracks || []).find(function(t) { return t.id === m._trackId; });
|
||||
var isActive = m._trackId === st.trackId && m.id === st.target_id;
|
||||
var ghostEnabled = ghostTrackFilter === null || ghostTrackFilter.has(m._trackId);
|
||||
var hasGhost = ghostLayers.some(function(l) { return l.track_id === m._trackId; });
|
||||
var isPlayOn = activePlayTrackIds === null || activePlayTrackIds.has(m._trackId);
|
||||
els.push(React.createElement("div", {
|
||||
key: m._trackId,
|
||||
className: "flex items-center gap-1 px-1 h-[18px] border-b border-zinc-900 " + (isActive ? 'bg-yellow-900/20' : '')
|
||||
}, React.createElement("input", {
|
||||
type: "checkbox",
|
||||
checked: ghostEnabled,
|
||||
disabled: !hasGhost || isActive,
|
||||
onChange: function() {
|
||||
onClick: function() {
|
||||
if (isActive) return;
|
||||
setGhostTrackFilter(function(prev) {
|
||||
setActivePlayTrackIds(function(prev) {
|
||||
var s = prev === null ? new Set() : new Set(prev);
|
||||
if (ghostEnabled) s.delete(m._trackId);
|
||||
if (isPlayOn) s.delete(m._trackId);
|
||||
else s.add(m._trackId);
|
||||
return s.size === 0 ? null : s;
|
||||
});
|
||||
},
|
||||
className: "w-2.5 h-2.5 accent-purple-500 cursor-pointer shrink-0"
|
||||
}), React.createElement("span", {
|
||||
className: "text-[9px] font-mono truncate " + (isActive ? 'text-yellow-400 font-bold' : hasGhost ? (ghostEnabled ? 'text-zinc-300' : 'text-zinc-600') : 'text-zinc-700'),
|
||||
className: "flex items-center px-1.5 h-[18px] border-b border-zinc-900 cursor-pointer " + (isActive ? 'bg-yellow-900/20' : (isPlayOn ? 'bg-zinc-800/40' : 'bg-zinc-900/60'))
|
||||
}, React.createElement("span", {
|
||||
className: "text-[9px] font-mono truncate " + (isActive ? 'text-yellow-400 font-bold' : (isPlayOn ? 'text-zinc-300' : 'text-zinc-600')),
|
||||
title: track ? track.name : m._trackName
|
||||
}, track ? track.name : m._trackName)));
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user