wave-roll
Version:
JavaScript Library for Comparative MIDI Piano-Roll Visualization
228 lines • 8.65 kB
TypeScript
/**
* Transport Sync Manager
* Handles synchronization between Tone.js Transport and piano roll visualization
*/
import { PianoRollSync, AudioPlayerState, OperationState } from "../player-types";
export interface TransportSyncOptions {
syncInterval: number;
originalTempo: number;
}
export declare class TransportSyncManager {
private static DEBUG;
private performanceMetrics;
private pianoRoll;
private syncRafId;
private syncScheduler;
private _schedulerToken;
private _lastSeekTimestamp;
private options;
private state;
private operationState;
private onEndCallback?;
constructor(pianoRoll: PianoRollSync, state: AudioPlayerState, operationState: OperationState, originalTempo: number);
/**
* Effective duration considering both MIDI notes and visible WAV buffers.
* Falls back to state.duration when registry/audio buffers are unavailable.
*/
private getEffectiveDuration;
/**
* Update seek timestamp for guard in event handlers
*/
updateSeekTimestamp(): void;
/**
* Measure drift between WAV and MIDI playback heads
* Returns drift in milliseconds (positive = WAV ahead, negative = MIDI ahead)
*/
private measureDrift;
/**
* Apply drift correction if needed
* This is called when drift exceeds acceptable threshold
*/
private correctDrift;
/**
* Get current drift statistics for debugging
*/
getDriftStats(): {
currentDriftMs: number;
avgDriftMs: number;
maxDriftMs: number;
violations: number;
measurements: number[];
};
/**
* Sync Inspector - comprehensive debugging information
* Call this from browser console: window._debugSync?.getInspectorData()
*/
getInspectorData(): {
transport: any;
state: any;
drift: any;
performance: any;
generation: number;
};
/**
* Enable sync inspector - adds global debug access
*/
enableSyncInspector(): void;
/**
* Disable sync inspector - removes global debug access
*/
disableSyncInspector(): void;
/**
* ========================================================================
* WAV/MIDI SYNCHRONIZATION TESTING GUIDE
* ========================================================================
*
* This guide describes how to test all synchronization scenarios to ensure
* WAV and MIDI remain perfectly synchronized under all conditions.
*
* SETUP:
* 1. Load multi-track WAV and MIDI files
* 2. Enable sync inspector: window._debugSync.enableDebug()
* 3. Start monitoring: window._debugSync.startMonitoring(500)
*
* TEST SCENARIOS:
*
* ## 1. BASIC SYNCHRONIZATION
* Expected: WAV and MIDI play as one unified sound
* - Play audio and verify no doubling or echo
* - Check drift: window._debugSync.getDriftStats()
* - Drift should be <= 10ms consistently
*
* ## 2. MUTE/UNMUTE SYNCHRONIZATION
* Expected: Unmuted tracks remain perfectly in sync, no restart artifacts
* Test steps:
* - Start playback, let run for 5+ seconds
* - Mute all WAV tracks
* - Wait 2-3 seconds (MIDI continues)
* - Unmute one WAV track
* - Verify: No audio doubling, WAV immediately in phase with MIDI
* - Check drift after unmute - should remain <= 10ms
*
* ## 3. SEEK SYNCHRONIZATION
* Expected: After seek, no ghost audio, all media synchronized at new position
* Test steps:
* - Start playback
* - While playing, seek to 50% position
* - Verify: Immediate silence, then synchronized restart
* - Pause immediately after seek
* - Verify: Complete silence, no residual audio
* - Check generation token incremented: window._debugSync.getInspectorData().generation
*
* ## 4. TEMPO CHANGE SYNCHRONIZATION
* Expected: Only one unified sound at new tempo, no overlapping audio
* Test steps:
* - Start playback at 120 BPM
* - While playing, change to 140 BPM
* - Verify: Brief silence, then single unified sound at new tempo
* - Check that totalTime updated: window._debugSync.getInspectorData().state
* - Verify A/B markers scaled appropriately
* - Check generation token incremented
*
* ## 5. A/B LOOP SYNCHRONIZATION
* Expected: Loop transitions are seamless with no drift accumulation
* Test steps:
* - Set A marker at 20% position, B marker at 35%
* - Enable A/B loop mode
* - Start playback and let loop 10+ times
* - Check drift doesn't accumulate: window._debugSync.getDriftStats()
* - Verify smooth loop transitions with no gaps or overlaps
*
* ## 6. RAPID OPERATION STRESS TEST
* Expected: No ghost audio or system instability
* Test steps:
* - Rapidly click play/pause (10+ times in 2 seconds)
* - Rapidly seek to different positions (10+ seeks quickly)
* - Rapidly change tempo multiple times
* - Verify: Only latest operation produces audio
* - Check generation tokens increase appropriately
* - No accumulated scheduled events: check console for Transport clear messages
*
* ## 7. MIXED OPERATION SEQUENCE
* Expected: Complex sequences work correctly
* Test sequence:
* - Play → Seek to 30% → Change tempo to 150 BPM → Enable A/B loop → Mute WAV → Unmute WAV
* - Verify each step: proper sync, no ghost audio, drift <= 10ms
* - Final verification: Single unified sound with all media in sync
*
* ## 8. RESOURCE CLEANUP VERIFICATION
* Expected: No memory leaks or accumulated timers
* Test steps:
* - Perform multiple play/seek/tempo cycles
* - Check browser dev tools → Performance → Memory for leaks
* - Console should show "Transport events cleared" messages after each stop
* - No accumulated setTimeout timers in system
*
* ACCEPTANCE CRITERIA:
* Drift <= 10ms maintained in all scenarios
* No ghost audio (doubling, echo, overlap) in any scenario
* Mute/unmute preserves synchronization without restart artifacts
* Seek provides immediate silence followed by synchronized restart
* Tempo changes produce single unified audio stream
* A/B looping works without drift accumulation
* Rapid operations handled gracefully with generation token system
* No memory leaks or resource accumulation
*
* DEBUGGING COMMANDS:
* - window._debugSync.logCurrentState() - Current sync status
* - window._debugSync.getDriftStats() - Detailed drift metrics
* - window._debugSync.getInspectorData() - Full system state
* - Check console for generation token messages during operations
*
* ========================================================================
*/
/**
* Check if we should suppress transport stop event
*/
shouldSuppressStop(): boolean;
/**
* Schedule a visual update at the next safe opportunity
*/
scheduleVisualUpdate(callback: () => void): void;
/**
* Start playhead synchronization scheduler
*/
startSyncScheduler(): void;
/**
* Stop playhead synchronization scheduler
*/
stopSyncScheduler(): void;
/**
* Handle transport stop event
*/
handleTransportStop(pausedTime: number): boolean;
/**
* Handle transport pause event
*/
handleTransportPause(pausedTime: number): void;
/**
* Handle transport loop event
*/
handleTransportLoop(loopStartVisual: number | null, loopEndVisual: number | null): void;
/**
* Calculate visual time from transport time
*/
transportToVisualTime(transportSeconds: number): number;
/**
* Calculate transport time from visual time
*/
visualToTransportTime(visualSeconds: number): number;
/**
* Calculate transport time from visual time using a specific tempo
* Useful for tempo changes where we need to calculate with new tempo before updating state
*/
visualToTransportTimeWithTempo(visualSeconds: number, targetTempo: number): number;
/**
* Update state reference (for when main state object changes)
*/
updateState(state: AudioPlayerState): void;
/**
* Update operation state reference
*/
updateOperationState(operationState: OperationState): void;
/**
* Set callback for when playback reaches the end
*/
setEndCallback(callback: () => void): void;
}
//# sourceMappingURL=transport-sync-manager.d.ts.map