fix: selected dim note's MIDI item wins focus over opened item

This commit is contained in:
2026-07-31 10:13:49 +07:00
parent ee39dda5b9
commit 99a445b789
2 changed files with 14 additions and 1 deletions
+13
View File
@@ -6260,7 +6260,20 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
// Determine which MIDI item is focused (clicked/selected note its item; playing item under playhead)
var focusedItemId = focusItemId || st.target_id;
if (selectedNoteIds && selectedNoteIds.length > 0) {
// Most recently selected note wins: main note opened item; dim same-track note its item
focusedItemId = st.target_id;
for (var s2 = selectedNoteIds.length - 1; s2 >= 0; s2--) {
var sid = selectedNoteIds[s2];
if (notes.some(function(n) { return n.id === sid; })) { focusedItemId = st.target_id; break; }
var foundGhost2 = false;
for (var sg2 = 0; sg2 < ghostLayers.length; sg2++) {
var sl2 = ghostLayers[sg2];
if (!sl2.isSameTrack || !sl2.notes) continue;
var gnote2 = sl2.notes.find(function(g) { return g.id === sid; });
if (gnote2) { focusedItemId = gnote2.item_id || st.target_id; foundGhost2 = true; break; }
}
if (foundGhost2) break;
}
} else if (st.isPlaying && st.currentTime != null && activeTracks) {
var curSec = st.currentTime || 0;
var fidxTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
+1 -1
View File
@@ -179,7 +179,7 @@ React.useEffect(()=>{const handleCanvasWheel=e=>{const canvas=canvasRef.current;
const scrollBeat=mx/pixelsPerBeat-renderBeatOffset;const clickedNote=notes.find(n=>pitch===n.pitch&&scrollBeat>=n.start_beat&&scrollBeat<n.start_beat+n.duration_beats);if(clickedNote){const delta=e.deltaY<0?0.05:-0.05;if(selectedNoteIds.length>0){setNotes(prev=>prev.map(n=>selectedNoteIds.includes(n.id)?{...n,velocity:Math.max(0.05,Math.min(1,(n.velocity||0.8)+delta))}:n));}else{setNotes(prev=>prev.map(n=>n.id===clickedNote.id?{...n,velocity:Math.max(0.05,Math.min(1,(n.velocity||0.8)+delta))}:n));}}else{// Shift+scroll on empty space → horizontal scroll
const container=gridScrollRef.current;if(container)container.scrollLeft+=e.deltaY;}return;}if(e.altKey){e.preventDefault();const scrollDelta=e.deltaY;const beatSec=60.0/(parseInt(bpm)||120);const step=scrollDelta<0?-0.25:0.25;const currentBeat=(st.currentTime||0)/beatSec;const maxBeats=totalBeats;const newBeat=Math.max(0,Math.min(maxBeats,currentBeat+step));const newTime=newBeat*beatSec;setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:newTime}:s));if(window.SonicSF){const ctx=getAudioContext();const playing=notes.filter(n=>currentBeat<n.start_beat&&newBeat>=n.start_beat);var pvTrk=activeTracks.find(function(t){return t.id===st.trackId;});var pvCh=window.SonicPianoRoll?window.SonicPianoRoll.getTrackMidiChannel(pvTrk,activeTracks):pvTrk?pvTrk.midiChannel:0;playing.forEach(n=>{window.SonicSF.playNote(n.pitch,(n.velocity||0.8)*127,200,ctx.currentTime,st.instrumentProgram,null,pvCh,pvTrk?pvTrk.synth_engine:undefined);});}}};const canvas=canvasRef.current;if(canvas){canvas.addEventListener('wheel',handleCanvasWheel,{passive:false});}return()=>{if(canvas){canvas.removeEventListener('wheel',handleCanvasWheel);}};},[notes,st.currentTime,pixelsPerBeat,st.id,totalBeats,bpm]);React.useLayoutEffect(()=>{const canvas=canvasRef.current;if(!canvas)return;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const h=128*NoteHeight;canvas.width=viewWidth*dpr;canvas.height=h*dpr;ctx.scale(dpr,dpr);// Draw background rows
for(let pitch=0;pitch<128;pitch++){const y=(127-pitch)*NoteHeight;const isBlack=[1,3,6,8,10].includes(pitch%12);ctx.fillStyle=isBlack?'#1a1a1e':'#25252a';ctx.fillRect(0,y,viewWidth,NoteHeight);ctx.strokeStyle='#2d2d35';ctx.lineWidth=0.5;ctx.beginPath();ctx.moveTo(0,y+NoteHeight);ctx.lineTo(viewWidth,y+NoteHeight);ctx.stroke();}// Draw snap lines
let snapBeats=0.25;if(snapValue==='1')snapBeats=1.0;else if(snapValue==='1/2')snapBeats=0.5;else if(snapValue==='1/4')snapBeats=0.25;else if(snapValue==='1/8')snapBeats=0.125;else if(snapValue==='1/16')snapBeats=0.0625;else if(snapValue==='4')snapBeats=4.0;else if(snapValue==='1/32')snapBeats=0.03125;for(let beat=0;beat<=viewBeats;beat+=snapBeats){const x=beat*pixelsPerBeat;if(x>viewWidth)break;const isBar=beat%timeSigNum===0;ctx.strokeStyle=isBar?'#444450':'#2d2d35';ctx.lineWidth=isBar?1.2:0.6;ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,h);ctx.stroke();}var focusedItemId=focusItemId||st.target_id;if(selectedNoteIds&&selectedNoteIds.length>0){focusedItemId=st.target_id;}else if(st.isPlaying&&st.currentTime!=null&&activeTracks){var curSec=st.currentTime||0;var fidxTrk=activeTracks.find(function(t){return t.id===st.trackId;});var itemsArr=fidxTrk?(fidxTrk.midiItems||[]):[];for(var fi=0;fi<itemsArr.length;fi++){var fit=itemsArr[fi];var fStart=fit.startTime||0;var fEnd=fStart+(fit.duration||4);if(curSec>=fStart&&curSec<fEnd){focusedItemId=fit.id;break;}}}// Layer 2: Ghost Notes (background reference from other tracks)
let snapBeats=0.25;if(snapValue==='1')snapBeats=1.0;else if(snapValue==='1/2')snapBeats=0.5;else if(snapValue==='1/4')snapBeats=0.25;else if(snapValue==='1/8')snapBeats=0.125;else if(snapValue==='1/16')snapBeats=0.0625;else if(snapValue==='4')snapBeats=4.0;else if(snapValue==='1/32')snapBeats=0.03125;for(let beat=0;beat<=viewBeats;beat+=snapBeats){const x=beat*pixelsPerBeat;if(x>viewWidth)break;const isBar=beat%timeSigNum===0;ctx.strokeStyle=isBar?'#444450':'#2d2d35';ctx.lineWidth=isBar?1.2:0.6;ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,h);ctx.stroke();}var focusedItemId=focusItemId||st.target_id;if(selectedNoteIds&&selectedNoteIds.length>0){focusedItemId=st.target_id;for(var s2=selectedNoteIds.length-1;s2>=0;s2--){var sid=selectedNoteIds[s2];if(notes.some(function(n){return n.id===sid;})){focusedItemId=st.target_id;break;}var foundGhost2=false;for(var sg2=0;sg2<ghostLayers.length;sg2++){var sl2=ghostLayers[sg2];if(!sl2.isSameTrack||!sl2.notes)continue;var gnote2=sl2.notes.find(function(g){return g.id===sid;});if(gnote2){focusedItemId=gnote2.item_id||st.target_id;foundGhost2=true;break;}}if(foundGhost2)break;}}else if(st.isPlaying&&st.currentTime!=null&&activeTracks){var curSec=st.currentTime||0;var fidxTrk=activeTracks.find(function(t){return t.id===st.trackId;});var itemsArr=fidxTrk?(fidxTrk.midiItems||[]):[];for(var fi=0;fi<itemsArr.length;fi++){var fit=itemsArr[fi];var fStart=fit.startTime||0;var fEnd=fStart+(fit.duration||4);if(curSec>=fStart&&curSec<fEnd){focusedItemId=fit.id;break;}}}// Layer 2: Ghost Notes (background reference from other tracks)
if(showGhostNotes&&sessionSyncMode&&ghostLayers.length>0){ghostLayers.forEach(function(layer){ctx.save();var isSameTrackLayer=layer.isSameTrack;layer.notes.forEach(function(note){var isFocusedNote=isSameTrackLayer&&note.item_id===focusedItemId;var isGhostSelected=selectedNoteIds.indexOf(note.id)!==-1;if(isSameTrackLayer){ctx.globalAlpha=isFocusedNote?0.7:0.3;ctx.fillStyle=isGhostSelected?'rgba(96, 165, 250, 0.65)':(isFocusedNote?'rgba(253, 224, 71, 0.7)':'rgba(251, 191, 36, 0.3)');ctx.strokeStyle=isGhostSelected?'#60a5fa':(isFocusedNote?'#fde047':'rgba(245, 158, 11, 0.6)');}else{ctx.globalAlpha=0.25;ctx.fillStyle=layer.track_color||'#888';ctx.strokeStyle=layer.track_color||'#888';}var snapStart=snapValue!=='free'?getSnapBeat(note.relative_start_beat,snapValue):note.relative_start_beat;var rawEnd=note.relative_start_beat+note.duration_beats;var snapEnd=snapValue!=='free'?getSnapBeat(rawEnd,snapValue):rawEnd;var x=(renderBeatOffset+snapStart)*pixelsPerBeat;var y=(127-note.pitch)*NoteHeight;var w=Math.max(2,(snapEnd-snapStart)*pixelsPerBeat);var h=NoteHeight-1;ctx.fillRect(x,y,w,h);if(isSameTrackLayer&&(isFocusedNote||isGhostSelected))ctx.strokeRect(x+0.5,y+0.5,w-1,h-1);});ctx.restore();});}// Layer 3: Active notes with velocity layer representation
notes.forEach(note=>{const snapStart=snapValue!=='free'?getSnapBeat(note.start_beat,snapValue):note.start_beat;const rawEnd=note.start_beat+note.duration_beats;const snapEnd=snapValue!=='free'?getSnapBeat(rawEnd,snapValue):rawEnd;const x=(renderBeatOffset+snapStart)*pixelsPerBeat;const y=(127-note.pitch)*NoteHeight;const w=Math.max(2,(snapEnd-snapStart)*pixelsPerBeat);const isSelected=selectedNoteIds.includes(note.id);// Draw background of note
const playingNow=st.isPlaying;const mainFocused=focusedItemId===st.target_id;ctx.fillStyle=isSelected?'rgba(96, 165, 250, 0.6)':(mainFocused?(playingNow?'rgba(254, 240, 138, 0.75)':'rgba(253, 224, 71, 0.6)'):'rgba(120, 100, 40, 0.35)');ctx.strokeStyle=isSelected?'#60a5fa':(mainFocused?(playingNow?'#fef08a':'#fde047'):'#8a7504');ctx.lineWidth=isSelected?1.5:(mainFocused?1.2:0.8);ctx.fillRect(x+1,y+1,w-2,NoteHeight-2);ctx.strokeRect(x+1,y+1,w-2,NoteHeight-2);// Draw velocity layer (solid yellow/blue bar inside, proportional to velocity)