feat: persist track mute/solo status
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { KGCore } from '../../KGCore';
|
||||
import { KGProject } from '../../KGProject';
|
||||
import { KGAudioInterface } from '../../audio-interface/KGAudioInterface';
|
||||
import { KGTrack } from '../../track/KGTrack';
|
||||
import { UpdateTrackCommand } from './UpdateTrackCommand';
|
||||
|
||||
vi.mock('../../KGCore', () => ({
|
||||
KGCore: {
|
||||
instance: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../audio-interface/KGAudioInterface', () => ({
|
||||
KGAudioInterface: {
|
||||
instance: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('UpdateTrackCommand', () => {
|
||||
let track: KGTrack;
|
||||
let project: KGProject;
|
||||
const mockCore = {
|
||||
getCurrentProject: vi.fn(),
|
||||
};
|
||||
const mockAudioInterface = {
|
||||
setTrackVolume: vi.fn(),
|
||||
setTrackInstrument: vi.fn(),
|
||||
setTrackMute: vi.fn(),
|
||||
setTrackSolo: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
track = new KGTrack('Track 1', 1);
|
||||
project = new KGProject('Test', 32, 0, 125, undefined, undefined, undefined, undefined, undefined, 1, [track], 11);
|
||||
mockCore.getCurrentProject.mockReturnValue(project);
|
||||
vi.mocked(KGCore.instance).mockReturnValue(mockCore as unknown as KGCore);
|
||||
vi.mocked(KGAudioInterface.instance).mockReturnValue(mockAudioInterface as unknown as KGAudioInterface);
|
||||
});
|
||||
|
||||
it('updates muted state and propagates to the audio interface', () => {
|
||||
const command = new UpdateTrackCommand(1, { muted: true });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(track.getMuted()).toBe(true);
|
||||
expect(mockAudioInterface.setTrackMute).toHaveBeenCalledWith('1', true);
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['muted']));
|
||||
});
|
||||
|
||||
it('updates solo state and propagates to the audio interface', () => {
|
||||
const command = new UpdateTrackCommand(1, { solo: true });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(track.getSolo()).toBe(true);
|
||||
expect(mockAudioInterface.setTrackSolo).toHaveBeenCalledWith('1', true);
|
||||
expect(command.getChangedProperties()).toEqual(new Set(['solo']));
|
||||
});
|
||||
|
||||
it('restores muted and solo state on undo', () => {
|
||||
track.setMuted(true);
|
||||
track.setSolo(true);
|
||||
const command = new UpdateTrackCommand(1, { muted: false, solo: false });
|
||||
|
||||
command.execute();
|
||||
command.undo();
|
||||
|
||||
expect(track.getMuted()).toBe(true);
|
||||
expect(track.getSolo()).toBe(true);
|
||||
expect(mockAudioInterface.setTrackMute).toHaveBeenLastCalledWith('1', true);
|
||||
expect(mockAudioInterface.setTrackSolo).toHaveBeenLastCalledWith('1', true);
|
||||
});
|
||||
|
||||
it('treats unchanged mute and solo values as no-ops', () => {
|
||||
const command = new UpdateTrackCommand(1, { muted: false, solo: false });
|
||||
|
||||
command.execute();
|
||||
|
||||
expect(track.getMuted()).toBe(false);
|
||||
expect(track.getSolo()).toBe(false);
|
||||
expect(mockAudioInterface.setTrackMute).not.toHaveBeenCalled();
|
||||
expect(mockAudioInterface.setTrackSolo).not.toHaveBeenCalled();
|
||||
expect(command.getChangedProperties()).toEqual(new Set());
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,8 @@ export interface TrackUpdateProperties {
|
||||
instrument?: InstrumentType; // Only applies to MIDI tracks
|
||||
type?: TrackType;
|
||||
volume?: number;
|
||||
muted?: boolean;
|
||||
solo?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,8 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
name: this.targetTrack.getName(),
|
||||
type: this.targetTrack.getType(),
|
||||
volume: this.targetTrack.getVolume(),
|
||||
muted: this.targetTrack.getMuted(),
|
||||
solo: this.targetTrack.getSolo(),
|
||||
};
|
||||
|
||||
// Store original instrument if it's a MIDI track
|
||||
@@ -103,6 +107,32 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
updatedProperties.push(`volume: ${originalVolume} → ${newVolume}`);
|
||||
}
|
||||
|
||||
if (this.newProperties.muted !== undefined && this.newProperties.muted !== this.originalProperties.muted) {
|
||||
const newMuted = this.newProperties.muted;
|
||||
const originalMuted = this.originalProperties.muted;
|
||||
|
||||
this.targetTrack.setMuted(newMuted);
|
||||
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
audioInterface.setTrackMute(this.trackId.toString(), newMuted);
|
||||
|
||||
this.changedProperties.add('muted');
|
||||
updatedProperties.push(`muted: ${originalMuted} → ${newMuted}`);
|
||||
}
|
||||
|
||||
if (this.newProperties.solo !== undefined && this.newProperties.solo !== this.originalProperties.solo) {
|
||||
const newSolo = this.newProperties.solo;
|
||||
const originalSolo = this.originalProperties.solo;
|
||||
|
||||
this.targetTrack.setSolo(newSolo);
|
||||
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
audioInterface.setTrackSolo(this.trackId.toString(), newSolo);
|
||||
|
||||
this.changedProperties.add('solo');
|
||||
updatedProperties.push(`solo: ${originalSolo} → ${newSolo}`);
|
||||
}
|
||||
|
||||
if (updatedProperties.length > 0) {
|
||||
console.log(`Updated track ${this.trackId}: ${updatedProperties.join(', ')}`);
|
||||
} else {
|
||||
@@ -156,6 +186,24 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
restoredProperties.push(`volume: ${this.originalProperties.volume}`);
|
||||
}
|
||||
|
||||
if (this.changedProperties.has('muted') && this.originalProperties.muted !== undefined) {
|
||||
this.targetTrack.setMuted(this.originalProperties.muted);
|
||||
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
audioInterface.setTrackMute(this.trackId.toString(), this.originalProperties.muted);
|
||||
|
||||
restoredProperties.push(`muted: ${this.originalProperties.muted}`);
|
||||
}
|
||||
|
||||
if (this.changedProperties.has('solo') && this.originalProperties.solo !== undefined) {
|
||||
this.targetTrack.setSolo(this.originalProperties.solo);
|
||||
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
audioInterface.setTrackSolo(this.trackId.toString(), this.originalProperties.solo);
|
||||
|
||||
restoredProperties.push(`solo: ${this.originalProperties.solo}`);
|
||||
}
|
||||
|
||||
console.log(`Restored track ${this.trackId}: ${restoredProperties.join(', ')}`);
|
||||
}
|
||||
|
||||
@@ -175,6 +223,12 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
if (this.newProperties.volume !== undefined) {
|
||||
updatedProps.push('volume');
|
||||
}
|
||||
if (this.newProperties.muted !== undefined) {
|
||||
updatedProps.push('muted');
|
||||
}
|
||||
if (this.newProperties.solo !== undefined) {
|
||||
updatedProps.push('solo');
|
||||
}
|
||||
|
||||
if (updatedProps.length === 1) {
|
||||
return `Update track "${trackName}" ${updatedProps[0]}`;
|
||||
@@ -219,4 +273,4 @@ export class UpdateTrackCommand extends KGCommand {
|
||||
public getChangedProperties(): Set<keyof TrackUpdateProperties> {
|
||||
return new Set(this.changedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user