fix: pause button while recording should stop recoding
This commit is contained in:
@@ -40,7 +40,7 @@ const Toolbar: React.FC = () => {
|
|||||||
projectName, setProjectName,
|
projectName, setProjectName,
|
||||||
savedProjectName, setSavedProjectName,
|
savedProjectName, setSavedProjectName,
|
||||||
bpm, timeSignature, keySignature, setStatus,
|
bpm, timeSignature, keySignature, setStatus,
|
||||||
isPlaying, startPlaying, stopPlaying, setPlayheadPosition,
|
isPlaying, startPlaying, stopTransport, setPlayheadPosition,
|
||||||
currentTime, setBpm, setTimeSignature, setKeySignature,
|
currentTime, setBpm, setTimeSignature, setKeySignature,
|
||||||
maxBars, setMaxBars,
|
maxBars, setMaxBars,
|
||||||
barWidthMultiplier, setBarWidthMultiplier,
|
barWidthMultiplier, setBarWidthMultiplier,
|
||||||
@@ -518,12 +518,10 @@ const Toolbar: React.FC = () => {
|
|||||||
console.log("Pause button clicked");
|
console.log("Pause button clicked");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
await stopTransport();
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
await stopRecording();
|
|
||||||
setStatus("Recording stopped — notes committed");
|
setStatus("Recording stopped — notes committed");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
await stopPlaying();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to stop playback:", error);
|
console.error("Failed to stop playback:", error);
|
||||||
setStatus("Failed to stop playback");
|
setStatus("Failed to stop playback");
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { KGMidiRegion } from '../core/region/KGMidiRegion';
|
|||||||
* Handles keyboard shortcuts defined in the configuration
|
* Handles keyboard shortcuts defined in the configuration
|
||||||
*/
|
*/
|
||||||
export const useGlobalKeyboardHandler = () => {
|
export const useGlobalKeyboardHandler = () => {
|
||||||
const { undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, toggleLoop, projectName, savedProjectName, setSavedProjectName, setProjectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll } = useProjectStore();
|
const { undo, redo, setStatus, isPlaying, startPlaying, stopTransport, toggleLoop, projectName, savedProjectName, setSavedProjectName, setProjectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll } = useProjectStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -134,8 +134,8 @@ export const useGlobalKeyboardHandler = () => {
|
|||||||
startPlaying();
|
startPlaying();
|
||||||
setStatus('Playback started');
|
setStatus('Playback started');
|
||||||
} else {
|
} else {
|
||||||
stopPlaying();
|
stopTransport();
|
||||||
setStatus('Playback stopped');
|
setStatus(isRecording ? 'Recording stopped — notes committed' : 'Playback stopped');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Play/pause failed:', error);
|
console.error('Play/pause failed:', error);
|
||||||
@@ -216,5 +216,5 @@ export const useGlobalKeyboardHandler = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyDown, { capture: true });
|
document.removeEventListener('keydown', handleKeyDown, { capture: true });
|
||||||
};
|
};
|
||||||
}, [undo, redo, setStatus, isPlaying, startPlaying, stopPlaying, toggleLoop, projectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll]); // Include dependencies for store actions
|
}, [undo, redo, setStatus, isPlaying, startPlaying, stopTransport, toggleLoop, projectName, savedProjectName, setSavedProjectName, setProjectName, isRecording, startRecording, stopRecording, activeRegionId, selectedRegionIds, setActiveRegionId, setShowPianoRoll]); // Include dependencies for store actions
|
||||||
};
|
};
|
||||||
@@ -137,6 +137,7 @@ interface ProjectState {
|
|||||||
setAutoScrollEnabled: (enabled: boolean) => void;
|
setAutoScrollEnabled: (enabled: boolean) => void;
|
||||||
startPlaying: () => Promise<void>;
|
startPlaying: () => Promise<void>;
|
||||||
stopPlaying: () => Promise<void>;
|
stopPlaying: () => Promise<void>;
|
||||||
|
stopTransport: () => Promise<void>;
|
||||||
toggleLoop: () => void;
|
toggleLoop: () => void;
|
||||||
toggleMetronome: () => void;
|
toggleMetronome: () => void;
|
||||||
setBpm: (bpm: number) => void;
|
setBpm: (bpm: number) => void;
|
||||||
@@ -821,6 +822,14 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
set({ isPlaying: false });
|
set({ isPlaying: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stopTransport: async () => {
|
||||||
|
if (get().isRecording) {
|
||||||
|
await get().stopRecording();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await get().stopPlaying();
|
||||||
|
},
|
||||||
|
|
||||||
startRecording: async () => {
|
startRecording: async () => {
|
||||||
const { activeRegionId, timeSignature, playheadPosition, setPlayheadPosition } = get();
|
const { activeRegionId, timeSignature, playheadPosition, setPlayheadPosition } = get();
|
||||||
|
|
||||||
@@ -1242,4 +1251,3 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Tests the critical data flow: Store Actions → Core Models → UI State Updates
|
* Tests the critical data flow: Store Actions → Core Models → UI State Updates
|
||||||
*/
|
*/
|
||||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
import { act, renderHook } from '@testing-library/react';
|
import { act } from '@testing-library/react';
|
||||||
|
|
||||||
// Import core classes
|
// Import core classes
|
||||||
import { KGCore } from '../../../core/KGCore';
|
import { KGCore } from '../../../core/KGCore';
|
||||||
@@ -315,6 +315,57 @@ describe('Project Store Synchronization Integration Tests', () => {
|
|||||||
expect(recordingCallbacksSpy).toHaveBeenCalled();
|
expect(recordingCallbacksSpy).toHaveBeenCalled();
|
||||||
expect(startPlayingSpy).toHaveBeenCalledWith({ preserveLoopPreroll: true });
|
expect(startPlayingSpy).toHaveBeenCalledWith({ preserveLoopPreroll: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should commit recording when transport is stopped during recording', async () => {
|
||||||
|
const testTrack = new KGMidiTrack('Recording Track', 0, 'acoustic_grand_piano');
|
||||||
|
const testRegion = new KGMidiRegion('record-region', 'track-0', 0, 'Recording Region', 16, 16);
|
||||||
|
testTrack.addRegion(testRegion);
|
||||||
|
testProject.setTracks([testTrack]);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await useProjectStore.getState().loadProject(testProject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const core = KGCore.instance();
|
||||||
|
vi.spyOn(core, 'startPlaying').mockResolvedValue(undefined);
|
||||||
|
const executeCommandSpy = vi.spyOn(core, 'executeCommand');
|
||||||
|
const setRecordingCallbacksSpy = vi.spyOn(KGMidiInput.instance(), 'setRecordingCallbacks');
|
||||||
|
vi.spyOn(mockAudioInterface, 'getTransportPosition').mockReturnValue(5);
|
||||||
|
|
||||||
|
const { setActiveRegionId, setPlayheadPosition, startRecording, stopTransport } = useProjectStore.getState();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
setActiveRegionId(testRegion.getId());
|
||||||
|
setPlayheadPosition(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await startRecording();
|
||||||
|
});
|
||||||
|
|
||||||
|
const noteOn = setRecordingCallbacksSpy.mock.calls.at(-1)?.[0];
|
||||||
|
const noteOff = setRecordingCallbacksSpy.mock.calls.at(-1)?.[1];
|
||||||
|
expect(noteOn).toBeTypeOf('function');
|
||||||
|
expect(noteOff).toBeTypeOf('function');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
noteOn?.(60);
|
||||||
|
noteOff?.(60);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await stopTransport();
|
||||||
|
});
|
||||||
|
|
||||||
|
const storeState = useProjectStore.getState();
|
||||||
|
expect(storeState.isRecording).toBe(false);
|
||||||
|
expect(storeState.isPlaying).toBe(false);
|
||||||
|
expect(storeState.recordingNotes).toHaveLength(0);
|
||||||
|
expect(executeCommandSpy).toHaveBeenCalled();
|
||||||
|
expect(mockAudioInterface.stopPlayback).toHaveBeenCalled();
|
||||||
|
expect(setRecordingCallbacksSpy).toHaveBeenLastCalledWith(null, null);
|
||||||
|
expect(testRegion.getNotes()).toHaveLength(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Selection State Synchronization', () => {
|
describe('Selection State Synchronization', () => {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const mockAudioInterface = {
|
|||||||
|
|
||||||
// Transport
|
// Transport
|
||||||
getCurrentBeat: vi.fn().mockReturnValue(0),
|
getCurrentBeat: vi.fn().mockReturnValue(0),
|
||||||
|
getTransportPosition: vi.fn().mockReturnValue(0),
|
||||||
setBpm: vi.fn().mockReturnValue(undefined),
|
setBpm: vi.fn().mockReturnValue(undefined),
|
||||||
|
|
||||||
// Singleton pattern
|
// Singleton pattern
|
||||||
|
|||||||
Reference in New Issue
Block a user