feat: added waveform view for audio regions

This commit is contained in:
Xiaohan-Tian
2026-05-26 23:23:01 -07:00
parent 7fc5ac1590
commit c4e4470182
22 changed files with 497 additions and 69 deletions
@@ -46,6 +46,7 @@ const storeState = {
showPianoRoll: false,
openMidiPianoRoll: vi.fn(),
openMidiPianoRollWithSheetMusicView: vi.fn(),
openAudioWaveformViewer: vi.fn(),
openSpectrogramViewer: vi.fn(),
playheadPosition: 12,
refreshProjectState: vi.fn(),
@@ -165,6 +166,7 @@ describe('useGlobalKeyboardHandler region shortcuts', () => {
storeState.selectedRegionIds = ['region-a', 'region-b'];
storeState.openMidiPianoRoll.mockClear();
storeState.openMidiPianoRollWithSheetMusicView.mockClear();
storeState.openAudioWaveformViewer.mockClear();
storeState.openSpectrogramViewer.mockClear();
vi.mocked(showAlert).mockClear();
});
@@ -217,6 +219,7 @@ describe('useGlobalKeyboardHandler region shortcuts', () => {
fireEvent.keyDown(document.body, { key: 'e' });
expect(storeState.openMidiPianoRollWithSheetMusicView).toHaveBeenCalledWith('region-b', false);
expect(storeState.openAudioWaveformViewer).not.toHaveBeenCalled();
expect(storeState.openSpectrogramViewer).not.toHaveBeenCalled();
});
@@ -260,9 +263,29 @@ describe('useGlobalKeyboardHandler region shortcuts', () => {
expect(showAlert).toHaveBeenCalledWith('Sheet music view is only available for MIDI regions.');
expect(storeState.openSpectrogramViewer).not.toHaveBeenCalled();
expect(storeState.openAudioWaveformViewer).not.toHaveBeenCalled();
expect(storeState.openMidiPianoRollWithSheetMusicView).not.toHaveBeenCalled();
});
it('opens a selected audio region in waveform view on E', () => {
mockTracks = [
{
getRegions: () => [
{
getId: () => 'region-b',
},
],
},
];
render(<HookHarness />);
fireEvent.keyDown(document.body, { key: 'e' });
expect(storeState.openAudioWaveformViewer).toHaveBeenCalledWith('region-b');
expect(storeState.openMidiPianoRollWithSheetMusicView).not.toHaveBeenCalled();
expect(storeState.openSpectrogramViewer).not.toHaveBeenCalled();
});
it('does not close the editor when N is pressed while piano roll is already open', () => {
storeState.showPianoRoll = true;
mockTracks = [
+4 -3
View File
@@ -17,7 +17,7 @@ import { showAlert } from '../util/dialogUtil';
* Handles keyboard shortcuts defined in the configuration
*/
export const useGlobalKeyboardHandler = () => {
const { undo, redo, setStatus, isPlaying, startPlaying, stopTransport, toggleLoop, projectName, savedProjectName, setSavedProjectName, setProjectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll, showPianoRoll, openMidiPianoRollWithSheetMusicView, openSpectrogramViewer, playheadPosition, refreshProjectState } = useProjectStore();
const { undo, redo, setStatus, isPlaying, startPlaying, stopTransport, toggleLoop, projectName, savedProjectName, setSavedProjectName, setProjectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll, showPianoRoll, openMidiPianoRollWithSheetMusicView, openAudioWaveformViewer, openSpectrogramViewer, playheadPosition, refreshProjectState } = useProjectStore();
const lastSelectedRegionId = selectedRegionIds[selectedRegionIds.length - 1] ?? null;
useEffect(() => {
@@ -232,7 +232,7 @@ export const useGlobalKeyboardHandler = () => {
return;
}
// Check for edit/view shortcut (E) — open piano roll for MIDI, spectrogram for audio
// Check for edit/view shortcut (E) — open piano roll for MIDI, waveform for audio
if (event.key.toLowerCase() === 'e' && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
event.preventDefault();
if (showPianoRoll) {
@@ -258,7 +258,7 @@ export const useGlobalKeyboardHandler = () => {
if (foundMidi) {
openMidiPianoRollWithSheetMusicView(candidateId, false);
} else if (foundAudio) {
openSpectrogramViewer(candidateId);
openAudioWaveformViewer(candidateId);
}
return;
}
@@ -335,6 +335,7 @@ export const useGlobalKeyboardHandler = () => {
setShowPianoRoll,
showPianoRoll,
openMidiPianoRollWithSheetMusicView,
openAudioWaveformViewer,
openSpectrogramViewer,
playheadPosition,
refreshProjectState,
+13 -4
View File
@@ -34,9 +34,10 @@ interface UseMainContentRegionsParams {
setShowPianoRoll: (show: boolean) => void;
setActiveRegionId: (regionId: string | null) => void;
openMidiPianoRoll: (regionId: string) => void;
openAudioWaveformViewer: (regionId: string) => void;
openSpectrogramViewer: (regionId: string) => void;
openHybridMode: (midiRegionId: string, audioRegionId: string) => void;
pianoRollMode: 'midi-edit' | 'spectrogram' | 'hybrid';
pianoRollMode: 'midi-edit' | 'audio-waveform' | 'spectrogram' | 'hybrid';
updateTrack: (track: KGTrack) => void;
maxBars: number;
}
@@ -64,6 +65,7 @@ export interface UseMainContentRegionsResult {
handleRegionLassoCommit: () => void;
handleEmptyMainContentClick: (event: React.MouseEvent<HTMLDivElement>) => void;
handleOpenPianoRoll: (regionId: string) => void;
handleOpenWaveform: (regionId: string) => void;
handleOpenSpectrogram: (regionId: string) => void;
handleOpenHybrid: (regionId: string) => void;
}
@@ -80,6 +82,7 @@ export function useMainContentRegions({
setShowPianoRoll,
setActiveRegionId,
openMidiPianoRoll,
openAudioWaveformViewer,
openSpectrogramViewer,
openHybridMode,
pianoRollMode,
@@ -238,15 +241,15 @@ export function useMainContentRegions({
}
if (lastSelectedRegion instanceof KGAudioRegion) {
openSpectrogramViewer(lastSelectedRegionId);
openAudioWaveformViewer(lastSelectedRegionId);
} else if (lastSelectedRegion instanceof KGMidiRegion) {
openMidiPianoRoll(lastSelectedRegionId);
}
}, [
clearAllSelections,
globalTracks,
openAudioWaveformViewer,
openMidiPianoRoll,
openSpectrogramViewer,
setActiveRegionId,
showPianoRoll,
tracks,
@@ -729,6 +732,11 @@ export function useMainContentRegions({
openMidiPianoRoll(regionId);
}, [handleRegionClick, openMidiPianoRoll]);
const handleOpenWaveform = useCallback((regionId: string) => {
handleRegionClick(regionId, DEFAULT_REGION_CLICK_OPTIONS);
openAudioWaveformViewer(regionId);
}, [handleRegionClick, openAudioWaveformViewer]);
const handleOpenSpectrogram = useCallback((regionId: string) => {
handleRegionClick(regionId, DEFAULT_REGION_CLICK_OPTIONS);
openSpectrogramViewer(regionId);
@@ -737,7 +745,7 @@ export function useMainContentRegions({
const handleOpenHybrid = useCallback((regionId: string) => {
if (pianoRollMode === 'midi-edit' && activeRegionId) {
openHybridMode(activeRegionId, regionId);
} else if (pianoRollMode === 'spectrogram' && activeRegionId) {
} else if ((pianoRollMode === 'audio-waveform' || pianoRollMode === 'spectrogram') && activeRegionId) {
openHybridMode(regionId, activeRegionId);
}
}, [activeRegionId, openHybridMode, pianoRollMode]);
@@ -759,6 +767,7 @@ export function useMainContentRegions({
handleRegionLassoCommit,
handleEmptyMainContentClick,
handleOpenPianoRoll,
handleOpenWaveform,
handleOpenSpectrogram,
handleOpenHybrid,
};