From 528e4ca7f90be63eff12e5898f3bdc82a177b0c7 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:25:30 -0700 Subject: [PATCH] feat: persist global track visibility status and metronome status --- src/components/MainContent.test.tsx | 83 +++++++++++-------- src/components/MainContent.tsx | 15 ++-- src/core/KGProject.ts | 30 ++++++- src/core/io/KGProjectStorage.test.ts | 14 ++++ .../project-upgrader/KGProjectUpgrader.ts | 5 ++ .../project-upgrader/upgradeToV16.test.ts | 26 ++++++ src/core/project-upgrader/upgradeToV16.ts | 17 ++++ src/stores/projectStore.test.ts | 54 ++++++++++++ src/stores/projectStore.ts | 18 +++- 9 files changed, 219 insertions(+), 43 deletions(-) create mode 100644 src/core/project-upgrader/upgradeToV16.test.ts create mode 100644 src/core/project-upgrader/upgradeToV16.ts diff --git a/src/components/MainContent.test.tsx b/src/components/MainContent.test.tsx index dd038f4..d278d14 100644 --- a/src/components/MainContent.test.tsx +++ b/src/components/MainContent.test.tsx @@ -58,6 +58,10 @@ const storeState = { addAudioTrack: vi.fn(), projectName: 'Test Project', savedProjectName: 'Test Project', + showGlobalTracks: false, + setShowGlobalTracks: vi.fn((show: boolean) => { + storeState.showGlobalTracks = show; + }), requestPianoRollScroll: vi.fn(), mainContentScrollRequest: null, activeTrackAutomationTrackId: null, @@ -82,6 +86,11 @@ const finishTrackCreateDialogClose = () => { } }; +const toggleGlobalTracksAndRerender = (rerender: (ui: React.ReactNode) => void) => { + fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + rerender(); +}; + vi.mock('../stores/projectStore', () => ({ useProjectStore: (selector?: StoreSelector) => ( selector ? selector(storeState) : storeState @@ -160,6 +169,7 @@ describe('MainContent', () => { storeState.selectedRegionIds = []; storeState.activeRegionId = null; storeState.showPianoRoll = false; + storeState.showGlobalTracks = false; storeState.playheadPosition = 0; storeState.timeSignature = { numerator: 4, denominator: 4 }; storeState.clearAllSelections.mockClear(); @@ -171,6 +181,7 @@ describe('MainContent', () => { storeState.openSpectrogramViewer.mockClear(); storeState.addTrack.mockClear(); storeState.addAudioTrack.mockClear(); + storeState.setShowGlobalTracks.mockClear(); executeCommandMock.mockClear(); }); @@ -385,29 +396,35 @@ describe('MainContent', () => { expect(screen.queryByRole('dialog', { name: 'Create New Track' })).not.toBeInTheDocument(); }); - it('toggles the mock global tracks button active state locally', () => { - render(); + it('toggles the persisted global tracks button active state through the store', () => { + const { rerender } = render(); - const globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); + let globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).not.toContain('active'); fireEvent.click(globalTracksButton); + expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(true); + rerender(); + globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).toContain('active'); fireEvent.click(globalTracksButton); + expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(false); + rerender(); + globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' }); expect(globalTracksButton.className).not.toContain('active'); }); - it('renders the four mock global tracks only when toggled on', () => { - render(); + it('renders the four global tracks only when the persisted toggle is on', () => { + const { rerender } = render(); expect(screen.queryByText('Marker')).not.toBeInTheDocument(); expect(screen.queryByText('Tempo')).not.toBeInTheDocument(); expect(screen.queryByText('Key Signature')).not.toBeInTheDocument(); expect(screen.queryByText('Chord')).not.toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); expect(screen.getByText('Marker')).toBeInTheDocument(); expect(screen.getByText('Tempo')).toBeInTheDocument(); @@ -416,7 +433,7 @@ describe('MainContent', () => { const globalTracksInfoShell = screen.getByRole('button', { name: 'Add Marker global track item' }).closest('.global-tracks-info-shell') as HTMLElement; - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.transitionEnd(globalTracksInfoShell); expect(screen.queryByText('Marker')).not.toBeInTheDocument(); @@ -427,9 +444,9 @@ describe('MainContent', () => { it('routes the tempo and chord global track add buttons through commands', () => { storeState.playheadPosition = 5; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Tempo global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); @@ -450,10 +467,10 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); fireEvent.click(screen.getByRole('button', { name: 'select-midi-region' })); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); expect(storeState.selectedRegionIds).toEqual(['chord-1']); @@ -471,9 +488,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('G'), { shiftKey: true }); @@ -492,9 +509,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); const firstChord = screen.getByText('Am'); const lastChord = screen.getByText('G'); @@ -524,9 +541,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('128 BPM'), { metaKey: true }); @@ -548,9 +565,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByText('Am')); fireEvent.click(screen.getByText('140 BPM'), { shiftKey: true }); @@ -566,9 +583,9 @@ describe('MainContent', () => { storeState.globalTracks = globalTracks; storeState.playheadPosition = 3; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Chord global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); @@ -586,9 +603,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' }); @@ -605,9 +622,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab' }); @@ -626,9 +643,9 @@ describe('MainContent', () => { ]); storeState.globalTracks = globalTracks; - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('G')); fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Tab', shiftKey: true }); @@ -639,18 +656,18 @@ describe('MainContent', () => { }); it('routes the signature global track add button through a command', () => { - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Key Signature global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); }); it('creates new marker regions with a one-bar default length', () => { - render(); + const { rerender } = render(); - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.click(screen.getByRole('button', { name: 'Add Marker global track item' })); expect(executeCommandMock).toHaveBeenCalledTimes(1); @@ -672,13 +689,13 @@ describe('MainContent', () => { return 1; }); - const { container } = render(); + const { container, rerender } = render(); const mainContent = container.querySelector('.main-content') as HTMLDivElement; Object.defineProperty(mainContent, 'scrollWidth', { configurable: true, value: 4000 }); Object.defineProperty(mainContent, 'clientWidth', { configurable: true, value: 900 }); mainContent.scrollLeft = 500; - fireEvent.click(screen.getByRole('button', { name: 'Show global tracks' })); + toggleGlobalTracksAndRerender(rerender); fireEvent.doubleClick(screen.getByText('Am')); await waitFor(() => { diff --git a/src/components/MainContent.tsx b/src/components/MainContent.tsx index e697776..5496747 100644 --- a/src/components/MainContent.tsx +++ b/src/components/MainContent.tsx @@ -68,6 +68,8 @@ const MainContent: React.FC = ({ addAudioTrack, projectName, savedProjectName, + showGlobalTracks, + setShowGlobalTracks, requestPianoRollScroll, mainContentScrollRequest, activeTrackAutomationTrackId, @@ -84,7 +86,6 @@ const MainContent: React.FC = ({ const [draggedTrackIndex, setDraggedTrackIndex] = useState(null); const [dragOverTrackIndex, setDragOverTrackIndex] = useState(null); const [showCreateTrackModal, setShowCreateTrackModal] = useState(false); - const [showGlobalTracksMock, setShowGlobalTracksMock] = useState(false); const mainContentRegions = useMainContentRegions({ tracks, @@ -252,10 +253,10 @@ const MainContent: React.FC = ({ setShowCreateTrackModal(true); }, []); - const handleToggleGlobalTracksMock = useCallback((event: React.MouseEvent) => { + const handleToggleGlobalTracks = useCallback((event: React.MouseEvent) => { event.stopPropagation(); - setShowGlobalTracksMock(previous => !previous); - }, []); + setShowGlobalTracks(!showGlobalTracks); + }, [setShowGlobalTracks, showGlobalTracks]); const handleGlobalChordDropToTrack = useCallback(async (draggedRegionId: string, trackIndex: number) => { const targetTrack = tracks[trackIndex]; @@ -348,10 +349,10 @@ const MainContent: React.FC = ({ @@ -385,7 +386,7 @@ const MainContent: React.FC = ({ KGTrack, { @@ -89,7 +97,7 @@ export class KGProject { private globalTracks: KGGlobalTrack[] = createDefaultGlobalTracks(); // Constructor - constructor(name: string = RESERVED_PROJECT_NAME, maxBars: number = 32, currentBars: number = 0, bpm: number = TIME_CONSTANTS.DEFAULT_BPM, timeSignature: TimeSignature = { numerator: 4, denominator: 4 }, keySignature: KeySignature = "C major", selectedMode: string = "ionian", isLooping: boolean = false, loopingRange: [number, number] = [0, 0], barWidthMultiplier: number = 2, tracks: KGTrack[] = [], projectStructureVersion: number = KGProject.CURRENT_PROJECT_STRUCTURE_VERSION, pianoRollZoom: number = 1, globalTracks: KGGlobalTrack[] = createDefaultGlobalTracks()) { + constructor(name: string = RESERVED_PROJECT_NAME, maxBars: number = 32, currentBars: number = 0, bpm: number = TIME_CONSTANTS.DEFAULT_BPM, timeSignature: TimeSignature = { numerator: 4, denominator: 4 }, keySignature: KeySignature = "C major", selectedMode: string = "ionian", isLooping: boolean = false, loopingRange: [number, number] = [0, 0], barWidthMultiplier: number = 2, tracks: KGTrack[] = [], projectStructureVersion: number = KGProject.CURRENT_PROJECT_STRUCTURE_VERSION, pianoRollZoom: number = 1, globalTracks: KGGlobalTrack[] = createDefaultGlobalTracks(), showGlobalTracks: boolean = false, isMetronomeEnabled: boolean = false) { this.name = name; this.maxBars = maxBars; this.currentBars = currentBars; @@ -104,6 +112,8 @@ export class KGProject { this.projectStructureVersion = projectStructureVersion; this.pianoRollZoom = pianoRollZoom; this.globalTracks = globalTracks; + this.showGlobalTracks = showGlobalTracks; + this.isMetronomeEnabled = isMetronomeEnabled; } // Getters @@ -188,6 +198,22 @@ export class KGProject { return this.projectStructureVersion; } + public getShowGlobalTracks(): boolean { + return this.showGlobalTracks; + } + + public setShowGlobalTracks(showGlobalTracks: boolean): void { + this.showGlobalTracks = showGlobalTracks; + } + + public getIsMetronomeEnabled(): boolean { + return this.isMetronomeEnabled; + } + + public setIsMetronomeEnabled(isMetronomeEnabled: boolean): void { + this.isMetronomeEnabled = isMetronomeEnabled; + } + public getIsLooping(): boolean { return this.isLooping; } diff --git a/src/core/io/KGProjectStorage.test.ts b/src/core/io/KGProjectStorage.test.ts index 385a5ff..9a29ada 100644 --- a/src/core/io/KGProjectStorage.test.ts +++ b/src/core/io/KGProjectStorage.test.ts @@ -163,6 +163,20 @@ describe('KGProjectStorage', () => { expect(loaded!.getPianoRollZoom()).toBe(5); }); + it('preserves persisted global-track visibility and metronome state when saving and loading', async () => { + const project = createTestProject('Toggle Song'); + project.setShowGlobalTracks(true); + project.setIsMetronomeEnabled(true); + + await storage.save('Toggle Song', project); + + const loaded = await storage.load('Toggle Song'); + + expect(loaded).not.toBeNull(); + expect(loaded!.getShowGlobalTracks()).toBe(true); + expect(loaded!.getIsMetronomeEnabled()).toBe(true); + }); + it('preserves global tracks and marker regions when saving and loading', async () => { const project = createTestProject('Marker Song'); const markerTrack = project.getGlobalTracks().find(track => track.getType() === GlobalTrackType.Marker); diff --git a/src/core/project-upgrader/KGProjectUpgrader.ts b/src/core/project-upgrader/KGProjectUpgrader.ts index 09e4fad..ec62425 100644 --- a/src/core/project-upgrader/KGProjectUpgrader.ts +++ b/src/core/project-upgrader/KGProjectUpgrader.ts @@ -14,6 +14,7 @@ import { upgradeToV12 } from './upgradeToV12'; import { upgradeToV13 } from './upgradeToV13'; import { upgradeToV14 } from './upgradeToV14'; import { upgradeToV15 } from './upgradeToV15'; +import { upgradeToV16 } from './upgradeToV16'; /** * Upgrade the given project to the latest structure version, one version at a time. @@ -93,6 +94,10 @@ export function upgradeProjectToLatest(project: KGProject): KGProject { workingProject = upgradeToV15(workingProject); break; } + case 16: { + workingProject = upgradeToV16(workingProject); + break; + } default: { // If an upgrader is missing, throw to prevent loading incompatible structures throw new Error(`No upgrader found for project structure version ${nextVersion}`); diff --git a/src/core/project-upgrader/upgradeToV16.test.ts b/src/core/project-upgrader/upgradeToV16.test.ts new file mode 100644 index 0000000..21bf400 --- /dev/null +++ b/src/core/project-upgrader/upgradeToV16.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; +import { KGProject } from '../KGProject'; +import { upgradeProjectToLatest } from './KGProjectUpgrader'; +import { upgradeToV16 } from './upgradeToV16'; + +describe('upgradeToV16', () => { + it('defaults persisted UI/audio toggles to false for legacy projects', () => { + const project = new KGProject('Legacy', 32, 0, 125, undefined, undefined, undefined, undefined, [0, 0], 1, [], 15, 1, []); + + upgradeToV16(project); + + expect(project.getProjectStructureVersion()).toBe(16); + expect(project.getShowGlobalTracks()).toBe(false); + expect(project.getIsMetronomeEnabled()).toBe(false); + }); + + it('preserves existing persisted toggle values through the main upgrader path', () => { + const project = new KGProject('Legacy', 32, 0, 125, undefined, undefined, undefined, undefined, [0, 0], 1, [], 15, 1, [], true, true); + + const upgraded = upgradeProjectToLatest(project); + + expect(upgraded.getProjectStructureVersion()).toBe(KGProject.CURRENT_PROJECT_STRUCTURE_VERSION); + expect(upgraded.getShowGlobalTracks()).toBe(true); + expect(upgraded.getIsMetronomeEnabled()).toBe(true); + }); +}); diff --git a/src/core/project-upgrader/upgradeToV16.ts b/src/core/project-upgrader/upgradeToV16.ts new file mode 100644 index 0000000..c227b8e --- /dev/null +++ b/src/core/project-upgrader/upgradeToV16.ts @@ -0,0 +1,17 @@ +import { KGProject } from '../KGProject'; + +export function upgradeToV16(project: KGProject): KGProject { + try { + if (typeof project.getShowGlobalTracks() !== 'boolean') { + project.setShowGlobalTracks(false); + } + + if (typeof project.getIsMetronomeEnabled() !== 'boolean') { + project.setIsMetronomeEnabled(false); + } + } finally { + project.setProjectStructureVersion(16); + } + + return project; +} diff --git a/src/stores/projectStore.test.ts b/src/stores/projectStore.test.ts index c4dd175..711c71d 100644 --- a/src/stores/projectStore.test.ts +++ b/src/stores/projectStore.test.ts @@ -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([ @@ -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((resolve) => { diff --git a/src/stores/projectStore.ts b/src/stores/projectStore.ts index a0d8179..7b7662e 100644 --- a/src/stores/projectStore.ts +++ b/src/stores/projectStore.ts @@ -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; 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((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((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((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((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((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((set, get) => { bpm: project.getBpm(), keySignature: project.getKeySignature(), selectedMode: project.getSelectedMode(), + isMetronomeEnabled: project.getIsMetronomeEnabled(), + showGlobalTracks: project.getShowGlobalTracks(), currentTime: formatCurrentTime(project, core.getPlayheadPosition()), });