feat: add R as shortcut of recording button; fixed linter errors.
This commit is contained in:
@@ -1,115 +1,115 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createMockProject } from '../../test/utils/mock-data'
|
||||
import { MockTransport } from '../../test/mocks/tone'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createMockProject } from '../../test/utils/mock-data';
|
||||
import { MockTransport } from '../../test/mocks/tone';
|
||||
|
||||
vi.mock('tone', async () => {
|
||||
const { ToneMock } = await import('../../test/mocks/tone')
|
||||
return ToneMock
|
||||
})
|
||||
const { ToneMock } = await import('../../test/mocks/tone');
|
||||
return ToneMock;
|
||||
});
|
||||
|
||||
vi.mock('../KGCore', () => ({
|
||||
KGCore: {
|
||||
instance: vi.fn()
|
||||
}
|
||||
}))
|
||||
}));
|
||||
|
||||
vi.mock('../config/ConfigManager', () => ({
|
||||
ConfigManager: {
|
||||
instance: vi.fn()
|
||||
}
|
||||
}))
|
||||
}));
|
||||
|
||||
import { KGCore } from '../KGCore'
|
||||
import { ConfigManager } from '../config/ConfigManager'
|
||||
import { KGAudioInterface } from './KGAudioInterface'
|
||||
import { KGCore } from '../KGCore';
|
||||
import { ConfigManager } from '../config/ConfigManager';
|
||||
import { KGAudioInterface } from './KGAudioInterface';
|
||||
|
||||
describe('KGAudioInterface preroll playback', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(0)
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
vi.clearAllMocks();
|
||||
|
||||
MockTransport.position = 0
|
||||
MockTransport.start.mockClear()
|
||||
MockTransport.stop.mockClear()
|
||||
MockTransport.clear.mockClear()
|
||||
MockTransport.schedule.mockClear()
|
||||
MockTransport.bpm.value = 120
|
||||
MockTransport.position = 0;
|
||||
MockTransport.start.mockClear();
|
||||
MockTransport.stop.mockClear();
|
||||
MockTransport.clear.mockClear();
|
||||
MockTransport.schedule.mockClear();
|
||||
MockTransport.bpm.value = 120;
|
||||
|
||||
const project = createMockProject({
|
||||
bpm: 120,
|
||||
timeSignature: { numerator: 4, denominator: 4 },
|
||||
tracks: [],
|
||||
})
|
||||
});
|
||||
|
||||
vi.mocked(KGCore.instance).mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore)
|
||||
} as unknown as KGCore);
|
||||
|
||||
vi.mocked(ConfigManager.instance).mockReturnValue({
|
||||
get: (key: string) => {
|
||||
if (key === 'audio.playback_delay') return 0.2
|
||||
if (key === 'audio.lookahead_time') return 0.05
|
||||
return null
|
||||
if (key === 'audio.playback_delay') return 0.2;
|
||||
if (key === 'audio.lookahead_time') return 0.05;
|
||||
return null;
|
||||
},
|
||||
} as unknown as ConfigManager)
|
||||
|
||||
;(KGAudioInterface as unknown as { _instance: KGAudioInterface | null })._instance = null
|
||||
})
|
||||
;(KGAudioInterface as unknown as { _instance: KGAudioInterface | null })._instance = null;
|
||||
});
|
||||
|
||||
it('uses virtual negative beats until the delayed transport start reaches beat 0', () => {
|
||||
const project = KGCore.instance().getCurrentProject()
|
||||
const project = KGCore.instance().getCurrentProject();
|
||||
const audio = KGAudioInterface.instance()
|
||||
;(audio as unknown as { isInitialized: boolean }).isInitialized = true
|
||||
;(audio as unknown as { isAudioContextStarted: boolean }).isAudioContextStarted = true
|
||||
;(audio as unknown as { isAudioContextStarted: boolean }).isAudioContextStarted = true;
|
||||
|
||||
const metronomeStart = vi.spyOn((audio as unknown as { metronome: { start: (...args: unknown[]) => void } }).metronome, 'start')
|
||||
audio.setMetronomeEnabled(true)
|
||||
const metronomeStart = vi.spyOn((audio as unknown as { metronome: { start: (...args: unknown[]) => void } }).metronome, 'start');
|
||||
audio.setMetronomeEnabled(true);
|
||||
|
||||
audio.preparePlayback(project, -2)
|
||||
audio.startPlayback()
|
||||
audio.preparePlayback(project, -2);
|
||||
audio.startPlayback();
|
||||
|
||||
expect(MockTransport.position).toBe(0)
|
||||
expect(MockTransport.start).not.toHaveBeenCalled()
|
||||
expect(metronomeStart).toHaveBeenCalledWith(-2, 4, 0.2)
|
||||
expect(audio.getTransportPosition()).toBeCloseTo(-2, 2)
|
||||
expect(MockTransport.position).toBe(0);
|
||||
expect(MockTransport.start).not.toHaveBeenCalled();
|
||||
expect(metronomeStart).toHaveBeenCalledWith(-2, 4, 0.2);
|
||||
expect(audio.getTransportPosition()).toBeCloseTo(-2, 2);
|
||||
|
||||
vi.advanceTimersByTime(500)
|
||||
expect(audio.getTransportPosition()).toBeCloseTo(-1, 1)
|
||||
vi.advanceTimersByTime(500);
|
||||
expect(audio.getTransportPosition()).toBeCloseTo(-1, 1);
|
||||
|
||||
vi.advanceTimersByTime(500)
|
||||
expect(MockTransport.start).toHaveBeenCalledTimes(1)
|
||||
expect(audio.getTransportPosition()).toBe(0)
|
||||
})
|
||||
vi.advanceTimersByTime(500);
|
||||
expect(MockTransport.start).toHaveBeenCalledTimes(1);
|
||||
expect(audio.getTransportPosition()).toBe(0);
|
||||
});
|
||||
|
||||
it('cancels the delayed transport start when playback stops during preroll', () => {
|
||||
const project = KGCore.instance().getCurrentProject()
|
||||
const project = KGCore.instance().getCurrentProject();
|
||||
const audio = KGAudioInterface.instance()
|
||||
;(audio as unknown as { isInitialized: boolean }).isInitialized = true
|
||||
;(audio as unknown as { isAudioContextStarted: boolean }).isAudioContextStarted = true
|
||||
;(audio as unknown as { isAudioContextStarted: boolean }).isAudioContextStarted = true;
|
||||
|
||||
audio.preparePlayback(project, -2)
|
||||
audio.startPlayback()
|
||||
audio.stopPlayback()
|
||||
vi.runAllTimers()
|
||||
audio.preparePlayback(project, -2);
|
||||
audio.startPlayback();
|
||||
audio.stopPlayback();
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(MockTransport.start).not.toHaveBeenCalled()
|
||||
expect(MockTransport.stop).toHaveBeenCalledTimes(1)
|
||||
expect(audio.getTransportPosition()).toBe(0)
|
||||
})
|
||||
expect(MockTransport.start).not.toHaveBeenCalled();
|
||||
expect(MockTransport.stop).toHaveBeenCalledTimes(1);
|
||||
expect(audio.getTransportPosition()).toBe(0);
|
||||
});
|
||||
|
||||
it('allows a first-pass start before the loop start when explicitly requested', () => {
|
||||
const project = createMockProject({
|
||||
bpm: 120,
|
||||
timeSignature: { numerator: 4, denominator: 4 },
|
||||
tracks: [],
|
||||
})
|
||||
project.setIsLooping(true)
|
||||
project.setLoopingRange([4, 7])
|
||||
});
|
||||
project.setIsLooping(true);
|
||||
project.setLoopingRange([4, 7]);
|
||||
|
||||
const audio = KGAudioInterface.instance()
|
||||
audio.preparePlayback(project, 12, { allowStartBeforeLoopStart: true })
|
||||
const audio = KGAudioInterface.instance();
|
||||
audio.preparePlayback(project, 12, { allowStartBeforeLoopStart: true });
|
||||
|
||||
expect(MockTransport.position).toBe(6)
|
||||
})
|
||||
})
|
||||
expect(MockTransport.position).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { MockLoop, MockTransport } from '../../test/mocks/tone'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MockLoop, MockTransport } from '../../test/mocks/tone';
|
||||
|
||||
vi.mock('tone', async () => {
|
||||
const { ToneMock: toneMock } = await import('../../test/mocks/tone')
|
||||
return toneMock
|
||||
})
|
||||
const { ToneMock: toneMock } = await import('../../test/mocks/tone');
|
||||
return toneMock;
|
||||
});
|
||||
|
||||
import { KGMetronome } from './KGMetronome'
|
||||
import { KGMetronome } from './KGMetronome';
|
||||
|
||||
describe('KGMetronome', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(0)
|
||||
vi.clearAllMocks()
|
||||
MockTransport.bpm.value = 120
|
||||
MockTransport.getTicksAtTime.mockImplementation((time: number) => time * MockTransport.PPQ)
|
||||
})
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(0);
|
||||
vi.clearAllMocks();
|
||||
MockTransport.bpm.value = 120;
|
||||
MockTransport.getTicksAtTime.mockImplementation((time: number) => time * MockTransport.PPQ);
|
||||
});
|
||||
|
||||
it('schedules audible preroll clicks before beat 0 and keeps the beat 0 accent', () => {
|
||||
const metronome = new KGMetronome()
|
||||
const metronome = new KGMetronome();
|
||||
const triggerAttackRelease = vi.fn()
|
||||
;(metronome as unknown as { sampler: unknown }).sampler = {
|
||||
loaded: true,
|
||||
triggerAttackRelease,
|
||||
}
|
||||
};
|
||||
|
||||
metronome.start(-4, 4, 0.2)
|
||||
metronome.start(-4, 4, 0.2);
|
||||
|
||||
vi.advanceTimersByTime(200)
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[0]).toBe('C5')
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[1]).toBe('16n')
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[2]).toBeCloseTo(0.2, 5)
|
||||
vi.advanceTimersByTime(200);
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[0]).toBe('C5');
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[1]).toBe('16n');
|
||||
expect(triggerAttackRelease.mock.calls[0]?.[2]).toBeCloseTo(0.2, 5);
|
||||
|
||||
vi.advanceTimersByTime(1000)
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[0]).toBe('C4')
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[1]).toBe('16n')
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[2]).toBeCloseTo(0.7, 5)
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[0]).toBe('C4');
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[1]).toBe('16n');
|
||||
expect(triggerAttackRelease.mock.calls[1]?.[2]).toBeCloseTo(0.7, 5);
|
||||
|
||||
const transportLoop = MockLoop.mock.results[0]?.value
|
||||
expect(transportLoop).toBeDefined()
|
||||
const transportLoop = MockLoop.mock.results[0]?.value;
|
||||
expect(transportLoop).toBeDefined();
|
||||
|
||||
transportLoop.callback(0)
|
||||
expect(triggerAttackRelease).toHaveBeenLastCalledWith('C5', '16n', 0.2)
|
||||
})
|
||||
transportLoop.callback(0);
|
||||
expect(triggerAttackRelease).toHaveBeenLastCalledWith('C5', '16n', 0.2);
|
||||
});
|
||||
|
||||
it('cancels pending preroll clicks when stopped', () => {
|
||||
const metronome = new KGMetronome()
|
||||
const metronome = new KGMetronome();
|
||||
const triggerAttackRelease = vi.fn()
|
||||
;(metronome as unknown as { sampler: unknown }).sampler = {
|
||||
loaded: true,
|
||||
triggerAttackRelease,
|
||||
}
|
||||
};
|
||||
|
||||
metronome.start(-2, 4, 0.2)
|
||||
metronome.stop()
|
||||
vi.runAllTimers()
|
||||
metronome.start(-2, 4, 0.2);
|
||||
metronome.stop();
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(triggerAttackRelease).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
expect(triggerAttackRelease).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user