UNPKG

wave-roll

Version:

JavaScript Library for Comparative MIDI Piano-Roll Visualization

228 lines 8.65 kB
/** * 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