feat: MIDI note duration inheritance & Ctrl+Shift+click split

This commit is contained in:
2026-07-31 08:01:52 +07:00
parent 47e87c8363
commit 9c5507f7b2
2 changed files with 55 additions and 22 deletions
+49 -17
View File
@@ -6007,6 +6007,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
notesRef.current = notes; notesRef.current = notes;
React.useEffect(() => { if (!draggedNoteRef.current) setNotes(st.notes || []); }, [st.notes]); React.useEffect(() => { if (!draggedNoteRef.current) setNotes(st.notes || []); }, [st.notes]);
const brushVelocityRef = React.useRef(0.8); const brushVelocityRef = React.useRef(0.8);
const lastNoteDurationRef = React.useRef(null);
const previewPitchRef = React.useRef(null); const previewPitchRef = React.useRef(null);
const previewNodesRef = React.useRef(null); const previewNodesRef = React.useRef(null);
var stopPreviewNote = function() { var stopPreviewNote = function() {
@@ -6503,28 +6504,50 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
} }
} }
// Ctrl+Shift+click: duplicate selected + clicked notes // Ctrl+Shift+click on note split at click position
if (e.ctrlKey && e.shiftKey) { if (e.ctrlKey && e.shiftKey) {
if (clickedNoteIdx !== -1) { if (clickedNoteIdx !== -1) {
const target = notes[clickedNoteIdx];
const splitBeat = getSnapBeat(beat, snapValue);
if (splitBeat > target.start_beat + 0.03125 && splitBeat < target.start_beat + target.duration_beats - 0.03125) {
pushToUndo(notes);
const noteA = {
...JSON.parse(JSON.stringify(target)),
id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8),
duration_beats: splitBeat - target.start_beat
};
const noteB = {
...JSON.parse(JSON.stringify(target)),
id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8),
start_beat: splitBeat,
duration_beats: target.start_beat + target.duration_beats - splitBeat
};
setNotes(prev => prev.map(n => n.id === target.id ? noteA : n).concat([noteB]));
setSelectedNoteIds([noteA.id, noteB.id]);
showToast('Đã tách nốt!', 'info');
}
} else {
// Ctrl+Shift+click on empty space duplicate selected + clicked notes
pushToUndo(notes); pushToUndo(notes);
const clickedNote = notes[clickedNoteIdx]; const clones = notes.filter(n => selectedNoteIds.includes(n.id)).map(n => ({
const idsToClone = [...new Set([...selectedNoteIds, clickedNote.id])];
const clones = notes.filter(n => idsToClone.includes(n.id)).map(n => ({
...JSON.parse(JSON.stringify(n)), ...JSON.parse(JSON.stringify(n)),
id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8) id: 'note_' + Date.now() + Math.random().toString(36).substr(2, 8)
})); }));
setNotes(prev => [...prev, ...clones]); if (clones.length > 0) {
const cloneIds = clones.map(c => c.id); setNotes(prev => [...prev, ...clones]);
setSelectedNoteIds(cloneIds); const cloneIds = clones.map(c => c.id);
notesBeforeDragRef.current = JSON.parse(JSON.stringify(notes)); setSelectedNoteIds(cloneIds);
const cloneOffsets = clones.map(n => ({ id: n.id, originalStartBeat: n.start_beat, originalPitch: n.pitch })); notesBeforeDragRef.current = JSON.parse(JSON.stringify(notes));
setDraggedNote({ const cloneOffsets = clones.map(n => ({ id: n.id, originalStartBeat: n.start_beat, originalPitch: n.pitch }));
mode: 'move', setDraggedNote({
idx: -1, mode: 'move',
startOffsetBeat: beat, idx: -1,
startOffsetPitch: pitch, startOffsetBeat: beat,
selectedNotesOffset: cloneOffsets startOffsetPitch: pitch,
}); selectedNotesOffset: cloneOffsets
});
showToast('Đã nhân bản ' + clones.length + ' nốt!', 'info');
}
} }
return; return;
} }
@@ -6589,7 +6612,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
// Click on empty space with pen tool: DRAW a new note (brush mode with visitedPitches) // Click on empty space with pen tool: DRAW a new note (brush mode with visitedPitches)
pushToUndo(notes); pushToUndo(notes);
const start = getSnapBeat(beat, snapValue); const start = getSnapBeat(beat, snapValue);
const initialDur = getSnapDuration(snapValue); const initialDur = lastNoteDurationRef.current || getSnapDuration(snapValue);
const noteId = 'note_' + Date.now() + Math.random().toString(36).substr(2, 5); const noteId = 'note_' + Date.now() + Math.random().toString(36).substr(2, 5);
const newNote = { const newNote = {
id: noteId, id: noteId,
@@ -6874,6 +6897,15 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
}; };
const handleGridMouseUp = () => { const handleGridMouseUp = () => {
if (draggedNote && draggedNote.mode === 'draw') {
const dn = draggedNote;
const brushIds = dn.brushIds || [];
const lastBrushId = brushIds.length > 0 ? brushIds[brushIds.length - 1] : dn.drawNoteId;
if (lastBrushId) {
const lastNote = notes.find(n => n.id === lastBrushId);
if (lastNote) lastNoteDurationRef.current = lastNote.duration_beats;
}
}
setDraggedNote(null); setDraggedNote(null);
setSelectionMarquee(null); setSelectionMarquee(null);
rightClickDragRef.current = { active: false, startX: 0, startY: 0 }; rightClickDragRef.current = { active: false, startX: 0, startY: 0 };
File diff suppressed because one or more lines are too long