diff --git a/src/components/piano-roll/PianoGrid.tsx b/src/components/piano-roll/PianoGrid.tsx index 7f181fc..a406497 100644 --- a/src/components/piano-roll/PianoGrid.tsx +++ b/src/components/piano-roll/PianoGrid.tsx @@ -34,6 +34,7 @@ interface PianoGridProps { spectrogramPower?: number; pianoRollZoom?: number; mode?: 'midi-edit' | 'spectrogram' | 'hybrid'; + onSpectrogramLoadingChange?: (loading: boolean) => void; } interface CursorPosition { @@ -62,6 +63,7 @@ const PianoGrid: React.FC = ({ spectrogramThresholdDb = -25, spectrogramPower = 0.5, pianoRollZoom = 1, + onSpectrogramLoadingChange, }) => { const [cursorPosition, setCursorPosition] = useState(null); const [isModifierPressed, setIsModifierPressed] = useState(false); @@ -243,6 +245,7 @@ const PianoGrid: React.FC = ({ thresholdDb={spectrogramThresholdDb} power={spectrogramPower} zoom={pianoRollZoom} + onLoadingChange={onSpectrogramLoadingChange} /> )} diff --git a/src/components/piano-roll/PianoRoll.css b/src/components/piano-roll/PianoRoll.css index 84bf294..6931e83 100644 --- a/src/components/piano-roll/PianoRoll.css +++ b/src/components/piano-roll/PianoRoll.css @@ -159,13 +159,37 @@ color: #ff4444; } +.piano-roll-content-outer { + position: relative; + flex: 1; + overflow: hidden; +} + .piano-roll-content { display: flex; flex-direction: column; - flex: 1; + height: 100%; 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 { display: grid; grid-template-columns: repeat(var(--max-number-of-bars), var(--region-grid-bar-width)); diff --git a/src/components/piano-roll/PianoRollContent.tsx b/src/components/piano-roll/PianoRollContent.tsx index e46fd4e..e0858dd 100644 --- a/src/components/piano-roll/PianoRollContent.tsx +++ b/src/components/piano-roll/PianoRollContent.tsx @@ -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 { KGMidiRegion } from '../../core/region/KGMidiRegion'; import { KGMidiNote } from '../../core/midi/KGMidiNote'; @@ -60,6 +60,11 @@ const PianoRollContent: React.FC = ({ pianoRollZoom = 1, }) => { const isSpectrogram = mode === 'spectrogram'; + const [spectrogramLoading, setSpectrogramLoading] = useState(false); + const handleSpectrogramLoadingChange = useCallback((loading: boolean) => { + setSpectrogramLoading(loading); + }, []); + // Get KGCore instance const core = KGCore.instance(); @@ -257,38 +262,48 @@ const PianoRollContent: React.FC = ({ }, [isRecording, recordingNotes, activeRegion]); return ( -
- - -
- - - {} : handleGridDoubleClick} - onClick={isSpectrogram ? () => {} : handleCombinedClick} - onMouseDown={isSpectrogram ? () => {} : handleBackgroundMouseDown} - isBoxSelecting={isSpectrogram ? false : isBoxSelectingRef.current} - selectionBox={isSpectrogram ? { startX: 0, startY: 0, endX: 0, endY: 0 } : selectionBoxRef.current} - regionStartBeat={activeRegion?.getStartFromBeat() || 0} - selectedMode={selectedMode} - keySignature={keySignature} - chordGuide={chordGuide} - audioRegion={audioRegion} - trackId={trackId} - projectName={projectName} - bpm={bpm} - spectrogramThresholdDb={spectrogramThresholdDb} - spectrogramPower={spectrogramPower} - pianoRollZoom={pianoRollZoom} - > - {memoizedNotes} - {!isSpectrogram && recordingNoteOverlays} - +
+
+ + +
+ + + {} : handleGridDoubleClick} + onClick={isSpectrogram ? () => {} : handleCombinedClick} + onMouseDown={isSpectrogram ? () => {} : handleBackgroundMouseDown} + isBoxSelecting={isSpectrogram ? false : isBoxSelectingRef.current} + selectionBox={isSpectrogram ? { startX: 0, startY: 0, endX: 0, endY: 0 } : selectionBoxRef.current} + regionStartBeat={activeRegion?.getStartFromBeat() || 0} + selectedMode={selectedMode} + keySignature={keySignature} + chordGuide={chordGuide} + audioRegion={audioRegion} + trackId={trackId} + projectName={projectName} + bpm={bpm} + spectrogramThresholdDb={spectrogramThresholdDb} + spectrogramPower={spectrogramPower} + pianoRollZoom={pianoRollZoom} + onSpectrogramLoadingChange={handleSpectrogramLoadingChange} + > + {memoizedNotes} + {!isSpectrogram && recordingNoteOverlays} + +
+ {spectrogramLoading && ( +
+
+ Computing spectrogram… +
+
+ )}
); }; diff --git a/src/components/piano-roll/SpectrogramCanvas.tsx b/src/components/piano-roll/SpectrogramCanvas.tsx index 9aa3ac0..0675b18 100644 --- a/src/components/piano-roll/SpectrogramCanvas.tsx +++ b/src/components/piano-roll/SpectrogramCanvas.tsx @@ -13,6 +13,7 @@ interface SpectrogramCanvasProps { thresholdDb: number; power: number; zoom: number; + onLoadingChange?: (loading: boolean) => void; } const PITCH_BINS = 128; @@ -55,6 +56,7 @@ const SpectrogramCanvas: React.FC = ({ thresholdDb, power, zoom, + onLoadingChange, }) => { const canvasRef = useRef(null); const [loading, setLoading] = useState(true); @@ -70,6 +72,8 @@ const SpectrogramCanvas: React.FC = ({ const zoomRef = useRef(zoom); useEffect(() => { zoomRef.current = zoom; }, [zoom]); + useEffect(() => { onLoadingChange?.(loading); }, [loading, onLoadingChange]); + const renderSpectrogram = useCallback(( result: SpectrogramResult, sampleRate: number, @@ -240,35 +244,16 @@ const SpectrogramCanvas: React.FC = ({ }, [audioRegion, trackId, projectName, bpm]); return ( - <> - - {loading && ( -
- Computing spectrogram… -
- )} - + ); };