fix: pre-roll metronome ticks are missing

This commit is contained in:
Xiaohan-Tian
2026-05-01 12:11:02 -07:00
parent 95ca3394a8
commit a474065450
5 changed files with 251 additions and 7 deletions
+51 -4
View File
@@ -42,6 +42,10 @@ export class KGAudioInterface {
private isPlaying: boolean = false;
private masterVolume: number = AUDIO_INTERFACE_CONSTANTS.DEFAULT_MASTER_VOLUME;
private scheduledEvents: Set<number> = new Set(); // Tone event IDs
private delayedTransportStartTimeoutId: number | null = null;
private delayedTransportStartMs: number = 0;
private virtualPrerollStartBeat: number | null = null;
private virtualPrerollStartTimeMs: number | null = null;
// Master volume control
private masterGain: Tone.Gain | null = null;
@@ -390,6 +394,7 @@ export class KGAudioInterface {
public preparePlayback(project: KGProject, startPosition: number): void {
// Clear any existing scheduled events
this.clearScheduledEvents();
this.clearDelayedTransportStart();
console.log("Preparing playback");
@@ -440,8 +445,18 @@ export class KGAudioInterface {
console.log("Loop mode disabled");
}
if (startPosition < 0) {
this.delayedTransportStartMs = Math.abs(startPosition) * secondsPerBeat * 1000;
this.virtualPrerollStartBeat = startPosition;
this.virtualPrerollStartTimeMs = null;
} else {
this.delayedTransportStartMs = 0;
this.virtualPrerollStartBeat = null;
this.virtualPrerollStartTimeMs = null;
}
// Set transport position (convert beats to Tone.js format)
this.setTransportPosition(startPosition);
this.setTransportPosition(Math.max(0, startPosition));
// Start metronome if enabled
if (this.isMetronomeEnabled) {
@@ -646,8 +661,19 @@ export class KGAudioInterface {
if (!this.isAudioContextStarted) {
throw new Error('Audio context not started');
}
Tone.Transport.start();
if (this.delayedTransportStartMs > 0 && this.virtualPrerollStartBeat !== null) {
this.virtualPrerollStartTimeMs = performance.now();
this.delayedTransportStartTimeoutId = window.setTimeout(() => {
this.delayedTransportStartTimeoutId = null;
this.virtualPrerollStartBeat = null;
this.virtualPrerollStartTimeMs = null;
Tone.Transport.start();
}, this.delayedTransportStartMs);
} else {
Tone.Transport.start();
}
this.isPlaying = true;
console.log('Audio playback started');
@@ -662,6 +688,7 @@ export class KGAudioInterface {
*/
public stopPlayback(): void {
try {
this.clearDelayedTransportStart();
Tone.Transport.stop();
this.metronome.stop();
@@ -789,7 +816,8 @@ export class KGAudioInterface {
public setTransportPosition(position: number): void {
try {
// Convert beats to Tone.js time format
const toneTime = this.beatsToToneTime(position);
const safePosition = Math.max(0, position);
const toneTime = this.beatsToToneTime(safePosition);
Tone.Transport.position = toneTime;
console.log(`Set transport position to ${position} beats (${toneTime})`);
} catch (error) {
@@ -802,6 +830,14 @@ export class KGAudioInterface {
*/
public getTransportPosition(): number {
try {
if (this.virtualPrerollStartBeat !== null && this.virtualPrerollStartTimeMs !== null) {
const project = KGCore.instance().getCurrentProject();
const secondsPerBeat = 60 / project.getBpm();
const elapsedSeconds = (performance.now() - this.virtualPrerollStartTimeMs) / 1000;
const elapsedBeats = elapsedSeconds / secondsPerBeat;
return Math.min(0, this.virtualPrerollStartBeat + elapsedBeats);
}
const position = Tone.Transport.position;
return this.toneTimeToBeats(position);
} catch (error) {
@@ -973,6 +1009,17 @@ export class KGAudioInterface {
return this.captureStream;
}
private clearDelayedTransportStart(): void {
if (this.delayedTransportStartTimeoutId !== null) {
window.clearTimeout(this.delayedTransportStartTimeoutId);
this.delayedTransportStartTimeoutId = null;
}
this.delayedTransportStartMs = 0;
this.virtualPrerollStartBeat = null;
this.virtualPrerollStartTimeMs = null;
}
// ===== PRIVATE UTILITY METHODS =====
/**