fix: move computing spectrogram text to the center of current viewport of piano roll window

This commit is contained in:
Xiaohan-Tian
2026-05-02 00:09:33 -07:00
parent 5890a6d0b9
commit 6b8a470022
4 changed files with 89 additions and 62 deletions
+3
View File
@@ -34,6 +34,7 @@ interface PianoGridProps {
spectrogramPower?: number; spectrogramPower?: number;
pianoRollZoom?: number; pianoRollZoom?: number;
mode?: 'midi-edit' | 'spectrogram' | 'hybrid'; mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
onSpectrogramLoadingChange?: (loading: boolean) => void;
} }
interface CursorPosition { interface CursorPosition {
@@ -62,6 +63,7 @@ const PianoGrid: React.FC<PianoGridProps> = ({
spectrogramThresholdDb = -25, spectrogramThresholdDb = -25,
spectrogramPower = 0.5, spectrogramPower = 0.5,
pianoRollZoom = 1, pianoRollZoom = 1,
onSpectrogramLoadingChange,
}) => { }) => {
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(null); const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(null);
const [isModifierPressed, setIsModifierPressed] = useState(false); const [isModifierPressed, setIsModifierPressed] = useState(false);
@@ -243,6 +245,7 @@ const PianoGrid: React.FC<PianoGridProps> = ({
thresholdDb={spectrogramThresholdDb} thresholdDb={spectrogramThresholdDb}
power={spectrogramPower} power={spectrogramPower}
zoom={pianoRollZoom} zoom={pianoRollZoom}
onLoadingChange={onSpectrogramLoadingChange}
/> />
)} )}
+25 -1
View File
@@ -159,13 +159,37 @@
color: #ff4444; color: #ff4444;
} }
.piano-roll-content-outer {
position: relative;
flex: 1;
overflow: hidden;
}
.piano-roll-content { .piano-roll-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; height: 100%;
overflow: auto; overflow: auto;
} }
.spectrogram-loading-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 100;
}
.spectrogram-loading-label {
padding: 6px 10px;
background: rgba(0, 0, 0, 0.6);
color: #aaa;
font-size: 11px;
border-radius: 3px;
}
.piano-grid-header { .piano-grid-header {
display: grid; display: grid;
grid-template-columns: repeat(var(--max-number-of-bars), var(--region-grid-bar-width)); grid-template-columns: repeat(var(--max-number-of-bars), var(--region-grid-bar-width));
+45 -30
View File
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useRef, useEffect } from 'react'; import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';
import { DEBUG_MODE } from '../../constants'; import { DEBUG_MODE } from '../../constants';
import { KGMidiRegion } from '../../core/region/KGMidiRegion'; import { KGMidiRegion } from '../../core/region/KGMidiRegion';
import { KGMidiNote } from '../../core/midi/KGMidiNote'; import { KGMidiNote } from '../../core/midi/KGMidiNote';
@@ -60,6 +60,11 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
pianoRollZoom = 1, pianoRollZoom = 1,
}) => { }) => {
const isSpectrogram = mode === 'spectrogram'; const isSpectrogram = mode === 'spectrogram';
const [spectrogramLoading, setSpectrogramLoading] = useState(false);
const handleSpectrogramLoadingChange = useCallback((loading: boolean) => {
setSpectrogramLoading(loading);
}, []);
// Get KGCore instance // Get KGCore instance
const core = KGCore.instance(); const core = KGCore.instance();
@@ -257,38 +262,48 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
}, [isRecording, recordingNotes, activeRegion]); }, [isRecording, recordingNotes, activeRegion]);
return ( return (
<div <div className="piano-roll-content-outer">
className="piano-roll-content" <div
ref={contentRef} className="piano-roll-content"
> ref={contentRef}
<PianoGridHeader maxBars={maxBars} timeSignature={timeSignature} /> >
<PianoGridHeader maxBars={maxBars} timeSignature={timeSignature} />
<div className="piano-roll-body"> <div className="piano-roll-body">
<PianoKeys activeRegion={activeRegion} /> <PianoKeys activeRegion={activeRegion} />
<PianoGrid <PianoGrid
gridRef={pianoGridRef} gridRef={pianoGridRef}
onDoubleClick={isSpectrogram ? () => {} : handleGridDoubleClick} onDoubleClick={isSpectrogram ? () => {} : handleGridDoubleClick}
onClick={isSpectrogram ? () => {} : handleCombinedClick} onClick={isSpectrogram ? () => {} : handleCombinedClick}
onMouseDown={isSpectrogram ? () => {} : handleBackgroundMouseDown} onMouseDown={isSpectrogram ? () => {} : handleBackgroundMouseDown}
isBoxSelecting={isSpectrogram ? false : isBoxSelectingRef.current} isBoxSelecting={isSpectrogram ? false : isBoxSelectingRef.current}
selectionBox={isSpectrogram ? { startX: 0, startY: 0, endX: 0, endY: 0 } : selectionBoxRef.current} selectionBox={isSpectrogram ? { startX: 0, startY: 0, endX: 0, endY: 0 } : selectionBoxRef.current}
regionStartBeat={activeRegion?.getStartFromBeat() || 0} regionStartBeat={activeRegion?.getStartFromBeat() || 0}
selectedMode={selectedMode} selectedMode={selectedMode}
keySignature={keySignature} keySignature={keySignature}
chordGuide={chordGuide} chordGuide={chordGuide}
audioRegion={audioRegion} audioRegion={audioRegion}
trackId={trackId} trackId={trackId}
projectName={projectName} projectName={projectName}
bpm={bpm} bpm={bpm}
spectrogramThresholdDb={spectrogramThresholdDb} spectrogramThresholdDb={spectrogramThresholdDb}
spectrogramPower={spectrogramPower} spectrogramPower={spectrogramPower}
pianoRollZoom={pianoRollZoom} pianoRollZoom={pianoRollZoom}
> onSpectrogramLoadingChange={handleSpectrogramLoadingChange}
{memoizedNotes} >
{!isSpectrogram && recordingNoteOverlays} {memoizedNotes}
</PianoGrid> {!isSpectrogram && recordingNoteOverlays}
</PianoGrid>
</div>
</div> </div>
{spectrogramLoading && (
<div className="spectrogram-loading-overlay">
<div className="spectrogram-loading-label">
Computing spectrogram
</div>
</div>
)}
</div> </div>
); );
}; };
+14 -29
View File
@@ -13,6 +13,7 @@ interface SpectrogramCanvasProps {
thresholdDb: number; thresholdDb: number;
power: number; power: number;
zoom: number; zoom: number;
onLoadingChange?: (loading: boolean) => void;
} }
const PITCH_BINS = 128; const PITCH_BINS = 128;
@@ -55,6 +56,7 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
thresholdDb, thresholdDb,
power, power,
zoom, zoom,
onLoadingChange,
}) => { }) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -70,6 +72,8 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
const zoomRef = useRef(zoom); const zoomRef = useRef(zoom);
useEffect(() => { zoomRef.current = zoom; }, [zoom]); useEffect(() => { zoomRef.current = zoom; }, [zoom]);
useEffect(() => { onLoadingChange?.(loading); }, [loading, onLoadingChange]);
const renderSpectrogram = useCallback(( const renderSpectrogram = useCallback((
result: SpectrogramResult, result: SpectrogramResult,
sampleRate: number, sampleRate: number,
@@ -240,35 +244,16 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
}, [audioRegion, trackId, projectName, bpm]); }, [audioRegion, trackId, projectName, bpm]);
return ( return (
<> <canvas
<canvas ref={canvasRef}
ref={canvasRef} style={{
style={{ position: 'absolute',
position: 'absolute', top: 0,
top: 0, left: 0,
left: 0, zIndex: 0,
zIndex: 0, pointerEvents: 'none',
pointerEvents: 'none', }}
}} />
/>
{loading && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
zIndex: 1,
padding: '6px 10px',
background: 'rgba(0,0,0,0.6)',
color: '#aaa',
fontSize: '11px',
pointerEvents: 'none',
}}
>
Computing spectrogram
</div>
)}
</>
); );
}; };