UNPKG

wave-roll

Version:

JavaScript Library for Comparative MIDI Piano-Roll Visualization

206 lines 7.15 kB
/** * Sampler Manager for MIDI playback * Handles Tone.js sampler creation, loading, and playback * Supports multi-instrument routing based on MIDI Program Number */ import * as Tone from "tone"; import { NoteData, InstrumentFamily } from "@/lib/midi/types"; /** * Loading state for an instrument family sampler. */ export interface InstrumentLoadState { family: InstrumentFamily; isLoading: boolean; isLoaded: boolean; error?: string; } export interface SamplerTrack { sampler: Tone.Sampler; delay: Tone.Delay | null; gate: Tone.Gain; panner: Tone.Panner; muted: boolean; /** Instrument family for this track (used for multi-instrument routing) */ instrumentFamily?: InstrumentFamily; /** Whether to use oscillator fallback (when sampler fails to load) */ useOscillatorFallback?: boolean; } export declare class SamplerManager { /** Legacy single sampler (used for single-file players) */ private sampler; /** Global panner for legacy single-sampler path */ private panner; /** Hard gate for legacy single-sampler path */ private gate; /** Map of fileId -> {sampler, panner, muted} for multi-file playback */ private trackSamplers; /** Current Tone.Part for scheduling note events */ private part; /** Notes to be played */ private notes; /** MIDI manager reference */ private midiManager; /** Alignment delay in seconds to compensate downstream (e.g., WAV PitchShift) latency */ private alignmentDelaySec; /** Map of MIDI Program Number -> loaded Tone.Sampler for auto-instrument routing */ private programSamplers; /** Map of MIDI Program Number -> loading Promise to prevent duplicate loads */ private programSamplerLoading; constructor(notes: NoteData[], midiManager?: any); private getTrack; private applySamplerVolume; private applyPannerPan; /** * Get or create a sampler for a specific MIDI Program Number (lazy loading). * Returns the sampler immediately if already loaded, otherwise starts loading * and returns a Promise that resolves when the sampler is ready. * @param program - The MIDI Program Number (0-127) to load * @returns Promise that resolves to the loaded Tone.Sampler */ getOrCreateProgramSampler(program: number): Promise<Tone.Sampler>; /** * Get the program sampler synchronously if already loaded. * Returns null if the sampler is not yet loaded. * @param program - The MIDI Program Number (0-127) * @returns The loaded sampler or null */ getProgramSamplerSync(program: number): Tone.Sampler | null; /** * Preload samplers for specific MIDI Program Numbers. * Useful for loading all programs used in a MIDI file at initialization. * @param programs - Array of MIDI Program Numbers to preload */ preloadProgramSamplers(programs: number[]): Promise<void>; /** Return notes filtered to intersect the [loopStart, loopEnd) window, or all if window inactive. */ private filterNotesByLoopWindow; /** Compute visual->transport scale factor. */ private computeScaleToTransport; /** Map notes to Part events without fileId. */ private notesToEvents; /** Map notes to Part events with fileId and trackId. */ private notesToEventsWithFileId; /** Build Tone.Part from events and callback, configure loop settings. */ private buildPart; /** * Initialize samplers - either multi-track or legacy single sampler */ initialize(options: { soundFont?: string; volume?: number; }): Promise<void>; /** * Set up per-track samplers for multi-file playback * @returns true if multi-file setup succeeded, false for fallback */ private setupTrackSamplers; /** * Fallback: Set up legacy single sampler */ private setupLegacySampler; /** * Create note part for Tone.js scheduling */ setupNotePart(loopStartVisual?: number | null, loopEndVisual?: number | null, options?: { repeat?: boolean; duration?: number; tempo?: number; originalTempo?: number; }): void; private setupMultiTrackPart; private setupLegacyPart; /** * Start the part at specified offset */ startPart(time: string | number, offset?: number): void; /** * Stop and cancel the part */ stopPart(): void; /** * Restart the part for seamless looping * This avoids recreating the Part which can cause gaps */ restartPartAtLoop(): void; /** * Set volume for all samplers */ setVolume(volume: number): void; /** * Set pan for all samplers */ setPan(pan: number): void; /** * Set alignment delay in seconds for all sampler outputs (compensate external FX latency). */ setAlignmentDelaySec(delaySec: number): void; /** * Set pan for a specific file */ setFilePan(fileId: string, pan: number): void; /** * Set mute state for a specific file */ setFileMute(fileId: string, mute: boolean): void; /** * Ensure the specified track is audible. If its sampler volume is effectively * silent (<= SILENT_DB), lift it to the provided masterVolume. */ ensureTrackAudible(fileId: string, masterVolume: number): void; /** * Retrigger any notes that are currently sustaining at `currentTime` for the given file. * Useful when a track is unmuted while the transport is already running so that * long-held notes become audible immediately without waiting for the next onset. */ retriggerHeldNotes(fileId: string, currentTime: number): void; /** * Retrigger held notes for all unmuted tracks at the current time * Useful after seeking to ensure long notes are audible at the new position */ retriggerAllUnmutedHeldNotes(currentTime: number): void; /** * Set volume for a specific file */ setFileVolume(fileId: string, volume: number, masterVolume: number): void; /** * Get file mute states */ getFileMuteStates(): Map<string, boolean>; /** * Get file volume states */ getFileVolumeStates(): Map<string, number>; /** * Check if all tracks have zero volume */ areAllTracksZeroVolume(): boolean; /** * Check if all tracks are muted */ areAllTracksMuted(): boolean; /** * Clean up resources */ destroy(): void; /** * Immediately stop/kill any currently sounding voices to prevent tail/bleed. * Best-effort: tries Sampler/PolySynth-specific release helpers if available. */ stopAllVoicesImmediate(): void; /** * Get the current part instance */ getPart(): Tone.Part | null; /** * Hard mute all track gates to instantly silence any residual sound. */ hardMuteAllGates(): void; /** * Unmute all track gates. */ hardUnmuteAllGates(): void; /** * Check if samplers are initialized */ isInitialized(): boolean; } //# sourceMappingURL=sampler-manager.d.ts.map