fix: piano roll spectrogram is not being zoomed correctly; switching from MIDI region to audio region with piano roll open might cause residual notes.
This commit is contained in:
@@ -32,6 +32,7 @@ interface PianoGridProps {
|
||||
bpm?: number;
|
||||
spectrogramThresholdDb?: number;
|
||||
spectrogramPower?: number;
|
||||
pianoRollZoom?: number;
|
||||
mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||
}
|
||||
|
||||
@@ -60,6 +61,7 @@ const PianoGrid: React.FC<PianoGridProps> = ({
|
||||
bpm = 120,
|
||||
spectrogramThresholdDb = -25,
|
||||
spectrogramPower = 0.5,
|
||||
pianoRollZoom = 1,
|
||||
}) => {
|
||||
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(null);
|
||||
const [isModifierPressed, setIsModifierPressed] = useState(false);
|
||||
@@ -240,6 +242,7 @@ const PianoGrid: React.FC<PianoGridProps> = ({
|
||||
bpm={bpm}
|
||||
thresholdDb={spectrogramThresholdDb}
|
||||
power={spectrogramPower}
|
||||
zoom={pianoRollZoom}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -158,22 +158,22 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the region in the tracks
|
||||
let found: KGMidiRegion | null = null;
|
||||
for (const track of tracks) {
|
||||
const regions = track.getRegions();
|
||||
const region = regions.find(r => r.getId() === regionId);
|
||||
|
||||
const region = track.getRegions().find(r => r.getId() === regionId);
|
||||
if (region && region instanceof KGMidiRegion) {
|
||||
setActiveRegion(region);
|
||||
|
||||
if (DEBUG_MODE.PIANO_ROLL) {
|
||||
console.log(`Active region set in PianoRoll: ${region.getId()}`);
|
||||
console.log(`Region details: name=${region.getName()}, trackId=${region.getTrackId()}, trackIndex=${region.getTrackIndex()}`);
|
||||
}
|
||||
|
||||
found = region;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Always update — clears stale MIDI region when switching to an audio region
|
||||
setActiveRegion(found);
|
||||
|
||||
if (found && DEBUG_MODE.PIANO_ROLL) {
|
||||
console.log(`Active region set in PianoRoll: ${found.getId()}`);
|
||||
console.log(`Region details: name=${found.getName()}, trackId=${found.getTrackId()}, trackIndex=${found.getTrackIndex()}`);
|
||||
}
|
||||
}, [regionId, tracks]);
|
||||
|
||||
// Sync local state with KGPianoRollState on mount
|
||||
@@ -965,6 +965,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
||||
bpm={bpm}
|
||||
spectrogramThresholdDb={spectrogramThresholdDb}
|
||||
spectrogramPower={spectrogramPower}
|
||||
pianoRollZoom={pianoRollZoom}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@@ -34,6 +34,7 @@ interface PianoRollContentProps {
|
||||
bpm?: number;
|
||||
spectrogramThresholdDb?: number;
|
||||
spectrogramPower?: number;
|
||||
pianoRollZoom?: number;
|
||||
}
|
||||
|
||||
const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
||||
@@ -56,6 +57,7 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
||||
bpm = 120,
|
||||
spectrogramThresholdDb = -25,
|
||||
spectrogramPower = 0.5,
|
||||
pianoRollZoom = 1,
|
||||
}) => {
|
||||
const isSpectrogram = mode === 'spectrogram';
|
||||
// Get KGCore instance
|
||||
@@ -234,7 +236,7 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
||||
/>
|
||||
);
|
||||
});
|
||||
}, [activeRegion, noteUpdateCounter, resizingNoteId, draggingNoteId, tempNoteStyles, selectedNoteIds, selectionBoxRender, tracks]);
|
||||
}, [mode, activeRegion, noteUpdateCounter, resizingNoteId, draggingNoteId, tempNoteStyles, selectedNoteIds, selectionBoxRender, tracks]);
|
||||
|
||||
const recordingNoteOverlays = useMemo(() => {
|
||||
if (!isRecording || !activeRegion || recordingNotes.length === 0) return null;
|
||||
@@ -281,6 +283,7 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
||||
bpm={bpm}
|
||||
spectrogramThresholdDb={spectrogramThresholdDb}
|
||||
spectrogramPower={spectrogramPower}
|
||||
pianoRollZoom={pianoRollZoom}
|
||||
>
|
||||
{memoizedNotes}
|
||||
{!isSpectrogram && recordingNoteOverlays}
|
||||
|
||||
@@ -12,6 +12,7 @@ interface SpectrogramCanvasProps {
|
||||
bpm: number;
|
||||
thresholdDb: number;
|
||||
power: number;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
const PITCH_BINS = 128;
|
||||
@@ -53,6 +54,7 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
|
||||
bpm,
|
||||
thresholdDb,
|
||||
power,
|
||||
zoom,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -62,6 +64,11 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
|
||||
const rawResultRef = useRef<SpectrogramResult | null>(null);
|
||||
const sampleRateRef = useRef<number>(44100);
|
||||
const regionDurationRef = useRef<number>(0);
|
||||
// Natural (1x) canvas pixel width — set after each draw, used to apply zoom as CSS stretch
|
||||
const naturalWidthRef = useRef<number>(0);
|
||||
// Always-current zoom without making it a renderSpectrogram dependency
|
||||
const zoomRef = useRef(zoom);
|
||||
useEffect(() => { zoomRef.current = zoom; }, [zoom]);
|
||||
|
||||
const renderSpectrogram = useCallback((
|
||||
result: SpectrogramResult,
|
||||
@@ -75,13 +82,12 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const beatWidth =
|
||||
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
|
||||
const noteHeight =
|
||||
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-height')) || 20;
|
||||
|
||||
const totalBeats = (regionDurationSeconds * bpm) / 60;
|
||||
const canvasWidth = Math.ceil(totalBeats * beatWidth);
|
||||
// Always draw at 1x resolution; zoom is applied as CSS width stretch
|
||||
const canvasWidth = Math.ceil(totalBeats * 40);
|
||||
const canvasHeight = PITCH_BINS * noteHeight;
|
||||
|
||||
// Convert dB threshold to linear: values below this → black
|
||||
@@ -131,6 +137,11 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.drawImage(offscreen, 0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// Store natural width and apply current zoom as CSS stretch (no pixel recompute on zoom)
|
||||
naturalWidthRef.current = canvasWidth;
|
||||
canvas.style.width = `${canvasWidth * zoomRef.current}px`;
|
||||
canvas.style.height = `${canvasHeight}px`;
|
||||
}, [bpm]);
|
||||
|
||||
// Re-render without re-running the worker when threshold or power changes
|
||||
@@ -146,6 +157,14 @@ const SpectrogramCanvas: React.FC<SpectrogramCanvasProps> = ({
|
||||
}
|
||||
}, [thresholdDb, power, renderSpectrogram]);
|
||||
|
||||
// Zoom changes: stretch width only, pin height to canvas pixel height
|
||||
useEffect(() => {
|
||||
if (canvasRef.current && naturalWidthRef.current > 0) {
|
||||
canvasRef.current.style.width = `${naturalWidthRef.current * zoom}px`;
|
||||
canvasRef.current.style.height = `${canvasRef.current.height}px`;
|
||||
}
|
||||
}, [zoom]);
|
||||
|
||||
// Load audio + run worker when the audio region itself changes
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
Reference in New Issue
Block a user