fix: move ghost decls before totalBeats to fix TDZ
This commit is contained in:
+15
-45
@@ -4595,23 +4595,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
const [rollBeats, setRollBeats] = React.useState(Math.max(noteMaxBeat + 16, 64));
|
||||
const rollBeatsRef = React.useRef(rollBeats);
|
||||
rollBeatsRef.current = rollBeats;
|
||||
const totalBeats = sessionSyncMode ? Math.max(rollBeats, sessionLengthBars * 4 + 16, 64) : Math.max(rollBeats, noteMaxBeat + 16, 64);
|
||||
const drawWidth = totalBeats * pixelsPerBeat;
|
||||
const [rollViewWidth, setRollViewWidth] = React.useState(800);
|
||||
const viewWidth = Math.max(drawWidth, rollViewWidth);
|
||||
const viewBeats = Math.ceil(viewWidth / pixelsPerBeat) + 4;
|
||||
|
||||
const [notes, setNotes] = React.useState(st.notes || []);
|
||||
const notesRef = React.useRef(notes);
|
||||
notesRef.current = notes;
|
||||
React.useEffect(() => { if (!draggedNoteRef.current) setNotes(st.notes || []); }, [st.notes]);
|
||||
const brushVelocityRef = React.useRef(0.8);
|
||||
const [selectedNoteIds, setSelectedNoteIds] = React.useState([]);
|
||||
const [loopStartBeat, setLoopStartBeat] = React.useState(null);
|
||||
const [loopEndBeat, setLoopEndBeat] = React.useState(null);
|
||||
const [isLooping, setIsLooping] = React.useState(false);
|
||||
const [showGhostNotes, setShowGhostNotes] = React.useState(true);
|
||||
const [sessionSyncMode, setSessionSyncMode] = React.useState(true);
|
||||
const [ghostTrackFilter, setGhostTrackFilter] = React.useState(null);
|
||||
|
||||
const allMidiItems = React.useMemo(() => {
|
||||
const result = [];
|
||||
@@ -4640,8 +4627,6 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
const sessionStartBar = sessionSyncMode && activeTargetItem
|
||||
? (activeTargetItem.startTime / secondsPerBar) : 0;
|
||||
|
||||
const [ghostTrackFilter, setGhostTrackFilter] = React.useState(null);
|
||||
|
||||
const sessionLengthBars = React.useMemo(function() {
|
||||
var maxSec = 0;
|
||||
(activeTracks || []).forEach(function(tr) {
|
||||
@@ -4658,36 +4643,21 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
return ghostLayers.filter(function(l) { return ghostTrackFilter.has(l.track_id); });
|
||||
}, [ghostLayers, ghostTrackFilter]);
|
||||
|
||||
const handleSwitchMidiItem = React.useCallback(function(itemId) {
|
||||
if (itemId === st.target_id) return;
|
||||
if (st.isDirty) {
|
||||
onSaveNotes(st.id, st.trackId, st.target_id, notes);
|
||||
}
|
||||
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,
|
||||
isDirty: false
|
||||
});
|
||||
});
|
||||
});
|
||||
setSelectedNoteIds([]);
|
||||
setLoopStartBeat(null);
|
||||
setLoopEndBeat(null);
|
||||
}, [st.id, st.target_id, st.isDirty, allMidiItems, activeTracks, onSaveNotes, notes]);
|
||||
const totalBeats = sessionSyncMode ? Math.max(rollBeats, sessionLengthBars * 4 + 16, 64) : Math.max(rollBeats, noteMaxBeat + 16, 64);
|
||||
const drawWidth = totalBeats * pixelsPerBeat;
|
||||
const [rollViewWidth, setRollViewWidth] = React.useState(800);
|
||||
const viewWidth = Math.max(drawWidth, rollViewWidth);
|
||||
const viewBeats = Math.ceil(viewWidth / pixelsPerBeat) + 4;
|
||||
|
||||
const [notes, setNotes] = React.useState(st.notes || []);
|
||||
const notesRef = React.useRef(notes);
|
||||
notesRef.current = notes;
|
||||
React.useEffect(() => { if (!draggedNoteRef.current) setNotes(st.notes || []); }, [st.notes]);
|
||||
const brushVelocityRef = React.useRef(0.8);
|
||||
const [selectedNoteIds, setSelectedNoteIds] = React.useState([]);
|
||||
const [loopStartBeat, setLoopStartBeat] = React.useState(null);
|
||||
const [loopEndBeat, setLoopEndBeat] = React.useState(null);
|
||||
const [isLooping, setIsLooping] = React.useState(false);
|
||||
const rulerDragRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
const handler = (e) => {
|
||||
|
||||
@@ -132,7 +132,7 @@ const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeyword
|
||||
const[aiBarStart,setAiBarStart]=React.useState(0);const[aiBarEnd,setAiBarEnd]=React.useState(4);const canvasRef=React.useRef(null);const ccCanvasRef=React.useRef(null);const ccWrapperRef=React.useRef(null);const gridScrollRef=React.useRef(null);const keybedRef=React.useRef(null);const keybedMouseDownRef=React.useRef(false);const rulerScrollRef=React.useRef(null);React.useEffect(()=>{const up=()=>{keybedMouseDownRef.current=false;};window.addEventListener('mouseup',up);return()=>window.removeEventListener('mouseup',up);},[]);const NoteHeight=18;const PITCH_START=0;// C0 (render all 128 keys)
|
||||
const KeybedPixelHeight=(128-PITCH_START)*NoteHeight;const pixelsPerBeat=rollZoom;const timeSigNum=4;const noteMaxBeat=(st.notes||[]).reduce((max,n)=>Math.max(max,(n.start_beat||0)+(n.duration_beats||1)),0);const[selectionMarquee,setSelectionMarquee]=React.useState(null);// { startBeat, startPitch, currentBeat, currentPitch }
|
||||
const[draggedNote,setDraggedNote]=React.useState(null);// { mode: 'move'|'resize', idx, startOffsetBeat, originalStart }
|
||||
const draggedNoteRef=React.useRef(draggedNote);draggedNoteRef.current=draggedNote;const[hoveredResizeIdx,setHoveredResizeIdx]=React.useState(-1);const[rollBeats,setRollBeats]=React.useState(Math.max(noteMaxBeat+16,64));const rollBeatsRef=React.useRef(rollBeats);rollBeatsRef.current=rollBeats;const totalBeats=sessionSyncMode?Math.max(rollBeats,sessionLengthBars*4+16,64):Math.max(rollBeats,noteMaxBeat+16,64);const drawWidth=totalBeats*pixelsPerBeat;const[rollViewWidth,setRollViewWidth]=React.useState(800);const viewWidth=Math.max(drawWidth,rollViewWidth);const viewBeats=Math.ceil(viewWidth/pixelsPerBeat)+4;const[notes,setNotes]=React.useState(st.notes||[]);const notesRef=React.useRef(notes);notesRef.current=notes;React.useEffect(()=>{if(!draggedNoteRef.current)setNotes(st.notes||[]);},[st.notes]);const brushVelocityRef=React.useRef(0.8);const[selectedNoteIds,setSelectedNoteIds]=React.useState([]);const[loopStartBeat,setLoopStartBeat]=React.useState(null);const[loopEndBeat,setLoopEndBeat]=React.useState(null);const[isLooping,setIsLooping]=React.useState(false);const[showGhostNotes,setShowGhostNotes]=React.useState(true);const[sessionSyncMode,setSessionSyncMode]=React.useState(true);const allMidiItems=React.useMemo(()=>{const result=[];(activeTracks||[]).forEach(t=>{if(!t.midiItems||!t.midiItems.length)return;t.midiItems.forEach(m=>{var extended=Object.assign({},m,{_trackId:t.id,_trackName:t.name});result.push(extended);});});return result;},[activeTracks]);const ghostLayers=React.useMemo(function(){if(!activeTracks||!st||!st.target_id)return[];var fn=window.SonicGhost&&window.SonicGhost.extractGhostLayers;return fn?fn(activeTracks,st.trackId,st.target_id,parseInt(bpm)||120):[];},[activeTracks,st.trackId,st.target_id,bpm]);const secondsPerBar=60.0/(parseInt(bpm)||120)*4;const activeTargetItem=React.useMemo(function(){if(!activeTracks||!st)return null;var trk=activeTracks.find(function(t){return t.id===st.trackId;});return trk?(trk.midiItems||[]).find(function(m){return m.id===st.target_id;}):null;},[activeTracks,st.trackId,st.target_id]);const sessionStartBar=sessionSyncMode&&activeTargetItem?activeTargetItem.startTime/secondsPerBar:0;const[ghostTrackFilter,setGhostTrackFilter]=React.useState(null);const sessionLengthBars=React.useMemo(function(){var maxSec=0;(activeTracks||[]).forEach(function(tr){(tr.midiItems||[]).forEach(function(m){var end=(m.startTime||0)+(m.duration||4);if(end>maxSec)maxSec=end;});});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=React.useCallback(function(itemId){if(itemId===st.target_id)return;if(st.isDirty){onSaveNotes(st.id,st.trackId,st.target_id,notes);}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,isDirty:false});});});setSelectedNoteIds([]);setLoopStartBeat(null);setLoopEndBeat(null);},[st.id,st.target_id,st.isDirty,allMidiItems,activeTracks,onSaveNotes,notes]);const rulerDragRef=React.useRef(null);React.useEffect(()=>{const handler=e=>{if((e.ctrlKey||e.metaKey)&&e.key==='a'){const target=e.target;if(target&&(target.tagName==='INPUT'||target.tagName==='TEXTAREA'||target.isContentEditable))return;e.preventDefault();setSelectedNoteIds(notes.map(n=>n.id));}};window.addEventListener('keydown',handler);return()=>window.removeEventListener('keydown',handler);},[notes,setSelectedNoteIds]);// Undo/redo stacks
|
||||
const draggedNoteRef=React.useRef(draggedNote);draggedNoteRef.current=draggedNote;const[hoveredResizeIdx,setHoveredResizeIdx]=React.useState(-1);const[rollBeats,setRollBeats]=React.useState(Math.max(noteMaxBeat+16,64));const rollBeatsRef=React.useRef(rollBeats);rollBeatsRef.current=rollBeats;const[showGhostNotes,setShowGhostNotes]=React.useState(true);const[sessionSyncMode,setSessionSyncMode]=React.useState(true);const[ghostTrackFilter,setGhostTrackFilter]=React.useState(null);const allMidiItems=React.useMemo(()=>{const result=[];(activeTracks||[]).forEach(t=>{if(!t.midiItems||!t.midiItems.length)return;t.midiItems.forEach(m=>{var extended=Object.assign({},m,{_trackId:t.id,_trackName:t.name});result.push(extended);});});return result;},[activeTracks]);const ghostLayers=React.useMemo(function(){if(!activeTracks||!st||!st.target_id)return[];var fn=window.SonicGhost&&window.SonicGhost.extractGhostLayers;return fn?fn(activeTracks,st.trackId,st.target_id,parseInt(bpm)||120):[];},[activeTracks,st.trackId,st.target_id,bpm]);const secondsPerBar=60.0/(parseInt(bpm)||120)*4;const activeTargetItem=React.useMemo(function(){if(!activeTracks||!st)return null;var trk=activeTracks.find(function(t){return t.id===st.trackId;});return trk?(trk.midiItems||[]).find(function(m){return m.id===st.target_id;}):null;},[activeTracks,st.trackId,st.target_id]);const sessionStartBar=sessionSyncMode&&activeTargetItem?activeTargetItem.startTime/secondsPerBar:0;const sessionLengthBars=React.useMemo(function(){var maxSec=0;(activeTracks||[]).forEach(function(tr){(tr.midiItems||[]).forEach(function(m){var end=(m.startTime||0)+(m.duration||4);if(end>maxSec)maxSec=end;});});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 totalBeats=sessionSyncMode?Math.max(rollBeats,sessionLengthBars*4+16,64):Math.max(rollBeats,noteMaxBeat+16,64);const drawWidth=totalBeats*pixelsPerBeat;const[rollViewWidth,setRollViewWidth]=React.useState(800);const viewWidth=Math.max(drawWidth,rollViewWidth);const viewBeats=Math.ceil(viewWidth/pixelsPerBeat)+4;const[notes,setNotes]=React.useState(st.notes||[]);const notesRef=React.useRef(notes);notesRef.current=notes;React.useEffect(()=>{if(!draggedNoteRef.current)setNotes(st.notes||[]);},[st.notes]);const brushVelocityRef=React.useRef(0.8);const[selectedNoteIds,setSelectedNoteIds]=React.useState([]);const[loopStartBeat,setLoopStartBeat]=React.useState(null);const[loopEndBeat,setLoopEndBeat]=React.useState(null);const[isLooping,setIsLooping]=React.useState(false);const rulerDragRef=React.useRef(null);React.useEffect(()=>{const handler=e=>{if((e.ctrlKey||e.metaKey)&&e.key==='a'){const target=e.target;if(target&&(target.tagName==='INPUT'||target.tagName==='TEXTAREA'||target.isContentEditable))return;e.preventDefault();setSelectedNoteIds(notes.map(n=>n.id));}};window.addEventListener('keydown',handler);return()=>window.removeEventListener('keydown',handler);},[notes,setSelectedNoteIds]);// Undo/redo stacks
|
||||
const undoStackRef=React.useRef([]);const redoStackRef=React.useRef([]);const notesBeforeDragRef=React.useRef(null);const pushToUndo=React.useCallback(prevNotes=>{undoStackRef.current.push(JSON.parse(JSON.stringify(prevNotes)));redoStackRef.current=[];if(undoStackRef.current.length>50)undoStackRef.current.shift();},[]);const handleUndo=React.useCallback(()=>{const prev=undoStackRef.current.pop();if(!prev)return;redoStackRef.current.push(JSON.parse(JSON.stringify(notes)));setNotes(prev);setSelectedNoteIds([]);},[notes]);const handleRedo=React.useCallback(()=>{const next=redoStackRef.current.pop();if(!next)return;undoStackRef.current.push(JSON.parse(JSON.stringify(notes)));setNotes(next);setSelectedNoteIds([]);},[notes]);React.useEffect(()=>{const handler=e=>{if((e.ctrlKey||e.metaKey)&&e.key==='z'&&!e.shiftKey){e.preventDefault();handleUndo();}else if((e.ctrlKey||e.metaKey)&&(e.key==='y'||e.key==='z'&&e.shiftKey)){e.preventDefault();handleRedo();}else if((e.ctrlKey||e.metaKey)&&e.key==='s'){e.preventDefault();onSaveNotes(st.id,st.trackId,st.target_id,notes);showToast('Đã lưu MIDI notes','info');}else if(e.key==='Delete'||e.key==='Backspace'){if(selectedNoteIds.length>0&&e.target.tagName!=='INPUT'&&e.target.tagName!=='TEXTAREA'){e.preventDefault();pushToUndo(notes);setNotes(prev=>prev.filter(n=>!selectedNoteIds.includes(n.id)));setSelectedNoteIds([]);showToast(`Đã xóa ${selectedNoteIds.length} nốt!`,'info');}}};window.addEventListener('keydown',handler);return()=>window.removeEventListener('keydown',handler);},[handleUndo,handleRedo,notes,selectedNoteIds,onSaveNotes,showToast]);React.useEffect(()=>{onUpdateNotes(st.id,notes);},[notes]);const getSnapBeat=(beat,mode)=>{let q=0.25;if(mode==='free')return beat;if(mode==='1')q=4.0;else if(mode==='1/2')q=2.0;else if(mode==='1/4')q=1.0;else if(mode==='1/8')q=0.5;else if(mode==='1/16')q=0.25;else if(mode==='4')q=4.0;else if(mode==='1/32')q=0.125;return Math.round(beat/q)*q;};const getSnapDuration=mode=>{if(mode==='free')return 0.25;if(mode==='1')return 4.0;if(mode==='1/2')return 2.0;if(mode==='1/4')return 1.0;if(mode==='1/8')return 0.5;if(mode==='1/16')return 0.25;if(mode==='4')return 4.0;if(mode==='1/32')return 0.125;return 0.25;};// Local Zoom Wheel Event handler to block browser page zoom
|
||||
React.useEffect(()=>{const handleWheelRaw=e=>{if(e.ctrlKey){e.preventDefault();const zoomFactor=e.deltaY<0?1.15:0.85;setRollZoom(prev=>Math.max(15,Math.min(250,prev*zoomFactor)));}};const container=gridScrollRef.current;if(container){container.addEventListener('wheel',handleWheelRaw,{passive:false});}return()=>{if(container){container.removeEventListener('wheel',handleWheelRaw);}};},[]);// Alt + Scroll event listener: fast‑forward playhead + play notes
|
||||
React.useEffect(()=>{const handleCanvasWheel=e=>{const canvas=canvasRef.current;if(!canvas)return;const rect=canvas.getBoundingClientRect();const mx=e.clientX-rect.left;const my=e.clientY-rect.top;const pitch=127-Math.floor(my/NoteHeight);if(e.shiftKey){e.preventDefault();// Shift+scroll on note → change velocity
|
||||
|
||||
Reference in New Issue
Block a user