From 811b3a0936cfe4eda23bdf4981b8933e43201be4 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Fri, 1 May 2026 23:26:39 -0700 Subject: [PATCH] feat: added zoom-in/out feature for piano roll window. --- src/components/piano-roll/PianoRoll.css | 28 +++++++++++++ src/components/piano-roll/PianoRoll.tsx | 16 ++++++++ .../piano-roll/PianoRollToolbar.tsx | 41 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/components/piano-roll/PianoRoll.css b/src/components/piano-roll/PianoRoll.css index 6c421d1..84bf294 100644 --- a/src/components/piano-roll/PianoRoll.css +++ b/src/components/piano-roll/PianoRoll.css @@ -314,4 +314,32 @@ align-items: center; justify-content: center; z-index: 100; +} + +.piano-roll-zoom-popup { + position: absolute; + top: 100%; + right: 0; + background-color: #2d2d2d; + border: 1px solid #444; + border-radius: 3px; + padding: 8px 12px; + z-index: 1500; + display: flex; + align-items: center; + gap: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + white-space: nowrap; + margin-top: 2px; +} + +.piano-roll-zoom-popup input[type="range"] { + width: 120px; +} + +.piano-roll-zoom-value { + font-size: 12px; + color: #e0e0e0; + min-width: 20px; + text-align: center; } \ No newline at end of file diff --git a/src/components/piano-roll/PianoRoll.tsx b/src/components/piano-roll/PianoRoll.tsx index a63af15..18383a9 100644 --- a/src/components/piano-roll/PianoRoll.tsx +++ b/src/components/piano-roll/PianoRoll.tsx @@ -49,6 +49,9 @@ const PianoRoll: React.FC = ({ // Spectrogram controls (only used in spectrogram mode) const [spectrogramThresholdDb, setSpectrogramThresholdDb] = useState(-25); const [spectrogramPower, setSpectrogramPower] = useState(0.5); + + // Piano roll zoom (1x–8x); updates --region-grid-beat-width CSS variable + const [pianoRollZoom, setPianoRollZoom] = useState(1); // Quantization state const [quantPosition, setQuantPosition] = useState('1/8'); @@ -686,6 +689,17 @@ const PianoRoll: React.FC = ({ container.scrollLeft = clampedScrollLeft; }, [playheadPosition, isPlaying, autoScrollEnabled]); + // Update --region-grid-beat-width when zoom changes; reset on unmount + useEffect(() => { + document.documentElement.style.setProperty('--region-grid-beat-width', `${40 * pianoRollZoom}px`); + if (triggerNoteUpdateRef.current) { + triggerNoteUpdateRef.current(prev => prev + 1); + } + return () => { + document.documentElement.style.setProperty('--region-grid-beat-width', '40px'); + }; + }, [pianoRollZoom]); + // Scroll horizontally to the active region's starting bar useEffect(() => { if (pianoRollContentRef.current && activeRegion) { @@ -927,6 +941,8 @@ const PianoRoll: React.FC = ({ onThresholdChange={setSpectrogramThresholdDb} power={spectrogramPower} onPowerChange={setSpectrogramPower} + zoom={pianoRollZoom} + onZoomChange={setPianoRollZoom} /> void; power?: number; onPowerChange?: (power: number) => void; + zoom: number; + onZoomChange: (value: number) => void; } const PianoRollToolbar: React.FC = ({ @@ -49,11 +51,27 @@ const PianoRollToolbar: React.FC = ({ onThresholdChange, power = 0.5, onPowerChange, + zoom, + onZoomChange, }) => { const isSpectrogram = mode === 'spectrogram'; const showMidiControls = mode !== 'spectrogram'; // midi-edit and hybrid const showSpecControls = mode === 'spectrogram' || mode === 'hybrid'; + const [showZoomSlider, setShowZoomSlider] = React.useState(false); + const zoomSliderRef = React.useRef(null); + + React.useEffect(() => { + if (!showZoomSlider) return; + const handleClickOutside = (e: MouseEvent) => { + if (zoomSliderRef.current && !zoomSliderRef.current.contains(e.target as Node)) { + setShowZoomSlider(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [showZoomSlider]); + return (
{showMidiControls && ( @@ -148,6 +166,29 @@ const PianoRollToolbar: React.FC = ({ />
)} + +
+ + {showZoomSlider && ( +
+ onZoomChange(parseInt(e.target.value))} + /> + {zoom}x +
+ )} +
);