feat: persist global track visibility status and metronome status

This commit is contained in:
Xiaohan-Tian
2026-06-02 23:25:30 -07:00
parent 6b0de74acd
commit 528e4ca7f9
9 changed files with 219 additions and 43 deletions
+50 -33
View File
@@ -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(<MainContent />);
};
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(<MainContent />);
it('toggles the persisted global tracks button active state through the store', () => {
const { rerender } = render(<MainContent />);
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(<MainContent />);
globalTracksButton = screen.getByRole('button', { name: 'Show global tracks' });
expect(globalTracksButton.className).toContain('active');
fireEvent.click(globalTracksButton);
expect(storeState.setShowGlobalTracks).toHaveBeenCalledWith(false);
rerender(<MainContent />);
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(<MainContent />);
it('renders the four global tracks only when the persisted toggle is on', () => {
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { rerender } = render(<MainContent />);
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(<MainContent />);
const { container, rerender } = render(<MainContent />);
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(() => {
+8 -7
View File
@@ -68,6 +68,8 @@ const MainContent: React.FC<MainContentProps> = ({
addAudioTrack,
projectName,
savedProjectName,
showGlobalTracks,
setShowGlobalTracks,
requestPianoRollScroll,
mainContentScrollRequest,
activeTrackAutomationTrackId,
@@ -84,7 +86,6 @@ const MainContent: React.FC<MainContentProps> = ({
const [draggedTrackIndex, setDraggedTrackIndex] = useState<number | null>(null);
const [dragOverTrackIndex, setDragOverTrackIndex] = useState<number | null>(null);
const [showCreateTrackModal, setShowCreateTrackModal] = useState(false);
const [showGlobalTracksMock, setShowGlobalTracksMock] = useState(false);
const mainContentRegions = useMainContentRegions({
tracks,
@@ -252,10 +253,10 @@ const MainContent: React.FC<MainContentProps> = ({
setShowCreateTrackModal(true);
}, []);
const handleToggleGlobalTracksMock = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
const handleToggleGlobalTracks = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
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<MainContentProps> = ({
</button>
<button
type="button"
className={`track-header-button${showGlobalTracksMock ? ' active' : ''}`}
className={`track-header-button${showGlobalTracks ? ' active' : ''}`}
aria-label={t('mainContent.showGlobalTracks')}
title={t('mainContent.showGlobalTracks')}
onClick={handleToggleGlobalTracksMock}
onClick={handleToggleGlobalTracks}
>
<FaSquareArrowUpRight />
</button>
@@ -385,7 +386,7 @@ const MainContent: React.FC<MainContentProps> = ({
</div>
<MainContentGlobalTracksSection
visible={showGlobalTracksMock}
visible={showGlobalTracks}
{...mainContentGlobalTracks.sectionProps}
chordLaneProps={{
...mainContentGlobalTracks.sectionProps.chordLaneProps,
+28 -2
View File
@@ -54,11 +54,19 @@ export class KGProject {
@WithDefault(1)
private pianoRollZoom: number = 1;
@Expose()
@WithDefault(false)
private showGlobalTracks: boolean = false;
@Expose()
@WithDefault(false)
private isMetronomeEnabled: boolean = false;
@Expose()
@WithDefault(0)
private projectStructureVersion: number = 0;
public static readonly CURRENT_PROJECT_STRUCTURE_VERSION: number = 15;
public static readonly CURRENT_PROJECT_STRUCTURE_VERSION: number = 16;
@Expose()
@Type(() => 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;
}
+14
View File
@@ -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);
@@ -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}`);
@@ -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);
});
});
+17
View File
@@ -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;
}
+54
View File
@@ -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) => {
+17 -1
View File
@@ -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()),
});