feat: brush-pen scale snap + scale sub-menu hover + keybed swipe
- Brush (pen tool only) draws notes snapped to selected scale - Scale sub-menus open on hover instead of click - Keybed: swipe/drag across keys plays notes (mouseenter+mousedown) - Global mouseup resets keybed drag state
This commit is contained in:
+24
-4
@@ -4554,7 +4554,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
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)
|
||||
@@ -5043,8 +5049,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
startOffsetPitch: pitch - clickedNote.pitch,
|
||||
selectedNotesOffset: selectedNotesOffset
|
||||
});
|
||||
} else {
|
||||
// Click on empty space: DRAW a new note (brush mode with visitedPitches)
|
||||
} else if (activeRollTool === 'pen') {
|
||||
// Click on empty space with pen tool: DRAW a new note (brush mode with visitedPitches)
|
||||
pushToUndo(notes);
|
||||
const start = getSnapBeat(beat, snapVal);
|
||||
const initialDur = getSnapDuration(snapVal);
|
||||
@@ -5340,6 +5346,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
className: `w-[60px] shrink-0 border-b border-zinc-800 text-[9px] font-bold flex items-center justify-end pr-2 transition cursor-pointer ${isBlack ? 'bg-zinc-950 text-slate-500 border-zinc-900 hover:bg-zinc-800' : 'bg-white text-zinc-800 border-r border-zinc-400 hover:bg-amber-100'}`,
|
||||
onMouseDown: (e) => {
|
||||
e.stopPropagation();
|
||||
keybedMouseDownRef.current = true;
|
||||
try {
|
||||
if (window.SonicSF) {
|
||||
window.SonicSF.playNote(pitch, 100, 500, undefined, st.instrumentProgram, null);
|
||||
@@ -5348,6 +5355,16 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
console.error('playNote error:', err);
|
||||
}
|
||||
},
|
||||
onMouseEnter: (e) => {
|
||||
if (keybedMouseDownRef.current) {
|
||||
try {
|
||||
if (window.SonicSF) {
|
||||
window.SonicSF.playNote(pitch, 100, 200, undefined, st.instrumentProgram, null);
|
||||
}
|
||||
} catch (err) { console.error('playNote error:', err); }
|
||||
}
|
||||
},
|
||||
onMouseUp: () => { keybedMouseDownRef.current = false; },
|
||||
onContextMenu: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -5498,11 +5515,12 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
const closeMenu = () => setScaleMenuPos(null);
|
||||
const items = [];
|
||||
const isSameScale = (a, b) => { if (!a || !b) return a === b; if (a.length !== b.length) return false; return a.every((v,i)=>v===b[i]); };
|
||||
const pushItem = (label, onClick, indent) => {
|
||||
const pushItem = (label, onClick, indent, onHover) => {
|
||||
const isActive = onClick._scale && isSameScale(onClick._scale, selectedScale);
|
||||
items.push(React.createElement("div", {
|
||||
key: label,
|
||||
onClick: () => { onClick(); closeMenu(); },
|
||||
onMouseEnter: onHover || undefined,
|
||||
className: "px-3 py-1.5 text-xs cursor-pointer hover:bg-amber-600 hover:text-white whitespace-nowrap " + (indent ? "pl-6 " : "") + (isActive ? "bg-amber-800/40 text-amber-300" : "text-zinc-300")
|
||||
}, label));
|
||||
};
|
||||
@@ -5514,7 +5532,9 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
||||
} else {
|
||||
const parentKey = key;
|
||||
const isOpen = scaleMenuPos && scaleMenuPos.parentKey === parentKey;
|
||||
pushItem(key + " ▸", () => { setScaleMenuPos({ x: scaleMenuPos.x + 120, y: scaleMenuPos.y, parentKey }); }, false);
|
||||
pushItem(key + " ▸", () => setSelectedScale(null), false, () => {
|
||||
setScaleMenuPos({ x: scaleMenuPos.x + 120, y: scaleMenuPos.y, parentKey });
|
||||
});
|
||||
if (isOpen) {
|
||||
Object.keys(val).forEach(subKey => {
|
||||
pushItem(subKey, () => setSelectedScale(val[subKey]), true);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user