fix: pause button while recording should stop recoding
This commit is contained in:
@@ -40,7 +40,7 @@ const Toolbar: React.FC = () => {
|
||||
projectName, setProjectName,
|
||||
savedProjectName, setSavedProjectName,
|
||||
bpm, timeSignature, keySignature, setStatus,
|
||||
isPlaying, startPlaying, stopPlaying, setPlayheadPosition,
|
||||
isPlaying, startPlaying, stopTransport, setPlayheadPosition,
|
||||
currentTime, setBpm, setTimeSignature, setKeySignature,
|
||||
maxBars, setMaxBars,
|
||||
barWidthMultiplier, setBarWidthMultiplier,
|
||||
@@ -518,12 +518,10 @@ const Toolbar: React.FC = () => {
|
||||
console.log("Pause button clicked");
|
||||
}
|
||||
try {
|
||||
await stopTransport();
|
||||
if (isRecording) {
|
||||
await stopRecording();
|
||||
setStatus("Recording stopped — notes committed");
|
||||
return;
|
||||
}
|
||||
await stopPlaying();
|
||||
} catch (error) {
|
||||
console.error("Failed to stop playback:", error);
|
||||
setStatus("Failed to stop playback");
|
||||
@@ -1109,4 +1107,4 @@ const Toolbar: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Toolbar;
|
||||
export default Toolbar;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { KGMidiRegion } from '../core/region/KGMidiRegion';
|
||||
* Handles keyboard shortcuts defined in the configuration
|
||||
*/
|
||||
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(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -134,8 +134,8 @@ export const useGlobalKeyboardHandler = () => {
|
||||
startPlaying();
|
||||
setStatus('Playback started');
|
||||
} else {
|
||||
stopPlaying();
|
||||
setStatus('Playback stopped');
|
||||
stopTransport();
|
||||
setStatus(isRecording ? 'Recording stopped — notes committed' : 'Playback stopped');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Play/pause failed:', error);
|
||||
@@ -216,5 +216,5 @@ export const useGlobalKeyboardHandler = () => {
|
||||
return () => {
|
||||
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;
|
||||
startPlaying: () => Promise<void>;
|
||||
stopPlaying: () => Promise<void>;
|
||||
stopTransport: () => Promise<void>;
|
||||
toggleLoop: () => void;
|
||||
toggleMetronome: () => void;
|
||||
setBpm: (bpm: number) => void;
|
||||
@@ -821,6 +822,14 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
set({ isPlaying: false });
|
||||
},
|
||||
|
||||
stopTransport: async () => {
|
||||
if (get().isRecording) {
|
||||
await get().stopRecording();
|
||||
return;
|
||||
}
|
||||
await get().stopPlaying();
|
||||
},
|
||||
|
||||
startRecording: async () => {
|
||||
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
|
||||
*/
|
||||
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 { KGCore } from '../../../core/KGCore';
|
||||
@@ -315,6 +315,57 @@ describe('Project Store Synchronization Integration Tests', () => {
|
||||
expect(recordingCallbacksSpy).toHaveBeenCalled();
|
||||
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', () => {
|
||||
|
||||
@@ -30,6 +30,7 @@ export const mockAudioInterface = {
|
||||
|
||||
// Transport
|
||||
getCurrentBeat: vi.fn().mockReturnValue(0),
|
||||
getTransportPosition: vi.fn().mockReturnValue(0),
|
||||
setBpm: vi.fn().mockReturnValue(undefined),
|
||||
|
||||
// Singleton pattern
|
||||
@@ -37,4 +38,4 @@ export const mockAudioInterface = {
|
||||
};
|
||||
|
||||
// Mock the class constructor
|
||||
export const mockKGAudioInterfaceClass = vi.fn(() => mockAudioInterface);
|
||||
export const mockKGAudioInterfaceClass = vi.fn(() => mockAudioInterface);
|
||||
|
||||
Reference in New Issue
Block a user