feat: persist global track visibility status and metronome status
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { act } from '@testing-library/react';
|
||||
import { KGTrack } from '../core/track/KGTrack';
|
||||
import { KGMidiTrack } from '../core/track/KGMidiTrack';
|
||||
import { createDefaultGlobalTracks } from '../core/global-track';
|
||||
|
||||
const pianoRollStateMocks = vi.hoisted(() => ({
|
||||
setSheetMusicViewEnabled: vi.fn(),
|
||||
@@ -9,16 +10,27 @@ const pianoRollStateMocks = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
let mockTracks: KGTrack[] = [new KGMidiTrack('Track 1', 0, 'acoustic_grand_piano')];
|
||||
let mockIsMetronomeEnabled = false;
|
||||
let mockShowGlobalTracks = false;
|
||||
const mockProject = {
|
||||
getTimeSignature: () => ({ numerator: 4, denominator: 4 }),
|
||||
getMaxBars: () => 32,
|
||||
getBarWidthMultiplier: () => 1,
|
||||
getTracks: () => mockTracks,
|
||||
getGlobalTracks: () => createDefaultGlobalTracks(),
|
||||
getBpm: () => 120,
|
||||
getKeySignature: () => 'C major',
|
||||
getName: () => 'Test Project',
|
||||
getSelectedMode: () => 'major',
|
||||
getIsLooping: () => false,
|
||||
getIsMetronomeEnabled: () => mockIsMetronomeEnabled,
|
||||
setIsMetronomeEnabled: vi.fn((value: boolean) => {
|
||||
mockIsMetronomeEnabled = value;
|
||||
}),
|
||||
getShowGlobalTracks: () => mockShowGlobalTracks,
|
||||
setShowGlobalTracks: vi.fn((value: boolean) => {
|
||||
mockShowGlobalTracks = value;
|
||||
}),
|
||||
getLoopingRange: () => [0, 0] as [number, number],
|
||||
getPianoRollZoom: () => 1,
|
||||
};
|
||||
@@ -37,6 +49,7 @@ const mockAudioInterface = {
|
||||
setTrackVolume: vi.fn(),
|
||||
setTrackMute: vi.fn(),
|
||||
setTrackSolo: vi.fn(),
|
||||
setMetronomeEnabled: vi.fn(),
|
||||
};
|
||||
|
||||
const configValues = new Map<string, unknown>([
|
||||
@@ -126,6 +139,11 @@ describe('projectStore piano roll state', () => {
|
||||
mockAudioInterface.cancelAudioRecording.mockResolvedValue(undefined);
|
||||
mockAudioInterface.getTransportPosition.mockReset();
|
||||
mockAudioInterface.getTransportPosition.mockReturnValue(8);
|
||||
mockAudioInterface.setMetronomeEnabled.mockReset();
|
||||
mockIsMetronomeEnabled = false;
|
||||
mockShowGlobalTracks = false;
|
||||
mockProject.setIsMetronomeEnabled.mockClear();
|
||||
mockProject.setShowGlobalTracks.mockClear();
|
||||
configValues.set('audio.input_device_id', 'default');
|
||||
});
|
||||
|
||||
@@ -195,6 +213,42 @@ describe('projectStore piano roll state', () => {
|
||||
expect(state.requestedSheetMusicViewEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('initializes persisted metronome and global-track visibility state from the project', async () => {
|
||||
mockIsMetronomeEnabled = true;
|
||||
mockShowGlobalTracks = true;
|
||||
|
||||
const { useProjectStore } = await import('./projectStore');
|
||||
|
||||
const state = useProjectStore.getState();
|
||||
expect(state.isMetronomeEnabled).toBe(true);
|
||||
expect(state.showGlobalTracks).toBe(true);
|
||||
});
|
||||
|
||||
it('persists metronome toggles to the project and audio interface', async () => {
|
||||
const { useProjectStore } = await import('./projectStore');
|
||||
|
||||
act(() => {
|
||||
useProjectStore.getState().toggleMetronome();
|
||||
});
|
||||
|
||||
const state = useProjectStore.getState();
|
||||
expect(mockProject.setIsMetronomeEnabled).toHaveBeenCalledWith(true);
|
||||
expect(mockAudioInterface.setMetronomeEnabled).toHaveBeenCalledWith(true);
|
||||
expect(state.isMetronomeEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('persists global-track visibility changes to the project', async () => {
|
||||
const { useProjectStore } = await import('./projectStore');
|
||||
|
||||
act(() => {
|
||||
useProjectStore.getState().setShowGlobalTracks(true);
|
||||
});
|
||||
|
||||
const state = useProjectStore.getState();
|
||||
expect(mockProject.setShowGlobalTracks).toHaveBeenCalledWith(true);
|
||||
expect(state.showGlobalTracks).toBe(true);
|
||||
});
|
||||
|
||||
it('tracks playback preparation around startPlaying success', async () => {
|
||||
let resolveStart: (() => void) | null = null;
|
||||
mockCore.startPlaying.mockImplementationOnce(() => new Promise<void>((resolve) => {
|
||||
|
||||
@@ -92,6 +92,7 @@ interface ProjectState {
|
||||
selectedMode: string;
|
||||
isLooping: boolean;
|
||||
isMetronomeEnabled: boolean;
|
||||
showGlobalTracks: boolean;
|
||||
loopingRange: [number, number]; // [startBar, endBar] - bar indices (0-based)
|
||||
playheadPosition: number; // in beats
|
||||
isPlaying: boolean;
|
||||
@@ -194,6 +195,7 @@ interface ProjectState {
|
||||
stopTransport: () => Promise<void>;
|
||||
toggleLoop: () => void;
|
||||
toggleMetronome: () => void;
|
||||
setShowGlobalTracks: (show: boolean) => void;
|
||||
setBpm: (bpm: number) => void;
|
||||
setMaxBars: (maxBars: number) => void;
|
||||
setBarWidthMultiplier: (multiplier: number) => void;
|
||||
@@ -432,7 +434,8 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
keySignature: currentProject.getKeySignature(),
|
||||
selectedMode: currentProject.getSelectedMode(),
|
||||
isLooping: currentProject.getIsLooping(),
|
||||
isMetronomeEnabled: false,
|
||||
isMetronomeEnabled: currentProject.getIsMetronomeEnabled(),
|
||||
showGlobalTracks: currentProject.getShowGlobalTracks(),
|
||||
loopingRange: currentProject.getLoopingRange(),
|
||||
playheadPosition: KGCore.instance().getPlayheadPosition(),
|
||||
isPlaying: KGCore.instance().getIsPlaying(),
|
||||
@@ -870,6 +873,7 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
|
||||
// Setup audio synths for all tracks
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
audioInterface.setMetronomeEnabled(projectToLoad.getIsMetronomeEnabled());
|
||||
|
||||
// Clear any existing synths/buses first
|
||||
tracks.forEach(track => {
|
||||
@@ -949,6 +953,8 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
keySignature,
|
||||
selectedMode: projectToLoad.getSelectedMode(),
|
||||
isLooping: projectToLoad.getIsLooping(),
|
||||
isMetronomeEnabled: projectToLoad.getIsMetronomeEnabled(),
|
||||
showGlobalTracks: projectToLoad.getShowGlobalTracks(),
|
||||
loopingRange: projectToLoad.getLoopingRange(),
|
||||
activeTrackAutomationTrackId: null,
|
||||
activeTrackAutomationType: null,
|
||||
@@ -1455,6 +1461,8 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
toggleMetronome: () => {
|
||||
const { isMetronomeEnabled, isPlaying, timeSignature } = get();
|
||||
const newValue = !isMetronomeEnabled;
|
||||
const project = KGCore.instance().getCurrentProject();
|
||||
project.setIsMetronomeEnabled(newValue);
|
||||
set({ isMetronomeEnabled: newValue });
|
||||
|
||||
const audio = KGAudioInterface.instance();
|
||||
@@ -1470,6 +1478,12 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
}
|
||||
},
|
||||
|
||||
setShowGlobalTracks: (show: boolean) => {
|
||||
const project = KGCore.instance().getCurrentProject();
|
||||
project.setShowGlobalTracks(show);
|
||||
set({ showGlobalTracks: show });
|
||||
},
|
||||
|
||||
setBpm: (bpm: number) => {
|
||||
try {
|
||||
// Create and execute the change project property command
|
||||
@@ -1886,6 +1900,8 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
||||
bpm: project.getBpm(),
|
||||
keySignature: project.getKeySignature(),
|
||||
selectedMode: project.getSelectedMode(),
|
||||
isMetronomeEnabled: project.getIsMetronomeEnabled(),
|
||||
showGlobalTracks: project.getShowGlobalTracks(),
|
||||
currentTime: formatCurrentTime(project, core.getPlayheadPosition()),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user