/** * Maximum width in CSS pixels for a single canvas chunk. * Canvas elements are split into chunks of this width to enable * horizontal virtual scrolling — only visible chunks are mounted. */ declare const MAX_CANVAS_WIDTH = 1000; /** * Spectrogram Types * * Types for frequency-domain visualization of audio data. */ /** Valid FFT sizes (must be power of 2, 256–8192) */ type FFTSize = 256 | 512 | 1024 | 2048 | 4096 | 8192; /** A single color map entry: [r, g, b] or [r, g, b, a] */ type ColorMapEntry = [number, number, number] | [number, number, number, number]; /** * Computed spectrogram data ready for rendering. */ interface SpectrogramData { /** Actual FFT length used for computation (includes zero padding) */ fftSize: number; /** Original analysis window size before zero padding */ windowSize: number; /** Number of frequency bins (fftSize / 2) */ frequencyBinCount: number; /** Sample rate of the source audio */ sampleRate: number; /** Hop size between FFT frames (in samples) */ hopSize: number; /** Number of time frames */ frameCount: number; /** dB values: frameCount * frequencyBinCount Float32Array (row-major, frame × bin) */ data: Float32Array; /** Display brightness boost in dB */ gainDb: number; /** Signal range in dB */ rangeDb: number; } /** * Configuration for spectrogram computation and rendering. */ interface SpectrogramConfig { /** FFT size: 256–8192, must be power of 2. Default: 2048 */ fftSize?: FFTSize; /** Hop size between frames in samples. Default: fftSize / 4 */ hopSize?: number; /** Window function applied before FFT. Default: 'hann' */ windowFunction?: 'hann' | 'hamming' | 'blackman' | 'rectangular' | 'bartlett' | 'blackman-harris'; /** Window function parameter (0-1), used by some window functions */ alpha?: number; /** Frequency axis scale. Default: 'mel' */ frequencyScale?: 'linear' | 'logarithmic' | 'mel' | 'bark' | 'erb'; /** Minimum frequency in Hz. Default: 0 */ minFrequency?: number; /** Maximum frequency in Hz. Default: sampleRate / 2 */ maxFrequency?: number; /** Display brightness boost in dB. Default: 20 */ gainDb?: number; /** Signal range in dB. Default: 80 */ rangeDb?: number; /** Zero padding factor: actual FFT length = fftSize * zeroPaddingFactor. Default: 2 */ zeroPaddingFactor?: number; /** Show frequency axis labels. Default: false */ labels?: boolean; /** Label text color */ labelsColor?: string; /** Label background color */ labelsBackground?: string; } /** Built-in color map names */ type ColorMapName = 'viridis' | 'magma' | 'inferno' | 'grayscale' | 'igray' | 'roseus'; /** Color map can be a named preset or a custom array of [r, g, b, a?] entries */ type ColorMapValue = ColorMapName | ColorMapEntry[]; /** Subset of SpectrogramConfig fields that affect FFT computation (used for cache keys) */ type SpectrogramComputeConfig = Pick; /** Subset of SpectrogramConfig fields that only affect display/rendering (not FFT computation) */ type SpectrogramDisplayConfig = Pick; /** Render mode for a track's visualization */ type RenderMode = 'waveform' | 'spectrogram' | 'both' | 'piano-roll'; /** Per-track overrides for spectrogram rendering (render mode, config, color map) */ interface TrackSpectrogramOverrides { renderMode: RenderMode; config?: SpectrogramConfig; colorMap?: ColorMapValue; } /** * Clip-Based Model Types * * These types support a professional multi-track editing model where: * - Each track can contain multiple audio clips * - Clips can be positioned anywhere on the timeline * - Clips have independent trim points (offset/duration) * - Gaps between clips are silent * - Clips can overlap (for crossfades) */ /** * WaveformData object from waveform-data.js library. * Supports resample() and slice() for dynamic zoom levels. * See: https://github.com/bbc/waveform-data.js */ interface WaveformDataObject { /** Sample rate of the original audio */ readonly sample_rate: number; /** Number of audio samples per pixel */ readonly scale: number; /** Length of waveform data in pixels */ readonly length: number; /** Bit depth (8 or 16) */ readonly bits: number; /** Duration in seconds */ readonly duration: number; /** Number of channels */ readonly channels: number; /** Get channel data */ channel: (index: number) => { min_array: () => number[]; max_array: () => number[]; }; /** Resample to different scale */ resample: (options: { scale: number; } | { width: number; }) => WaveformDataObject; /** Slice a portion of the waveform */ slice: (options: { startTime: number; endTime: number; } | { startIndex: number; endIndex: number; }) => WaveformDataObject; } /** * Generic effects function type for track-level audio processing. * * The actual implementation receives Tone.js audio nodes. Using generic types * here to avoid circular dependencies with the playout package. * * @param graphEnd - The end of the track's audio graph (Tone.js Gain node) * @param destination - Where to connect the effects output (Tone.js ToneAudioNode) * @param isOffline - Whether rendering offline (for export) * @returns Optional cleanup function called when track is disposed * * @example * ```typescript * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => { * const reverb = new Tone.Reverb({ decay: 1.5 }); * graphEnd.connect(reverb); * reverb.connect(destination); * * return () => { * reverb.dispose(); * }; * }; * ``` */ type TrackEffectsFunction = (graphEnd: unknown, destination: unknown, isOffline: boolean) => void | (() => void); /** * Represents a single audio clip on the timeline * * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers) * to avoid floating-point precision errors. Convert to seconds only when * needed for playback using: seconds = samples / sampleRate * * Clips can be created with just waveformData (for instant visual rendering) * and have audioBuffer added later when audio finishes loading. */ interface AudioClip { /** Unique identifier for this clip */ id: string; /** * The audio buffer containing the audio data. * Optional for peaks-first rendering - can be added later. * Required for playback and editing operations. */ audioBuffer?: AudioBuffer; /** Position on timeline where this clip starts (in samples at timeline sampleRate) */ startSample: number; /** * Position on timeline in ticks (authoritative when present). * When set, startSample is a derived cache recomputed from startTick via TempoMap. * Optional for backwards compatibility — engine enriches clips without startTick on ingestion. */ startTick?: number; /** Duration of this clip (in samples) - how much of the audio buffer to play */ durationSamples: number; /** Offset into the audio buffer where playback starts (in samples) - the "trim start" point */ offsetSamples: number; /** * Sample rate for this clip's audio. * Required when audioBuffer is not provided (for peaks-first rendering). * When audioBuffer is present, this should match audioBuffer.sampleRate. */ sampleRate: number; /** * Total duration of the source audio in samples. * Required when audioBuffer is not provided (for trim bounds calculation). * When audioBuffer is present, this should equal audioBuffer.length. */ sourceDurationSamples: number; /** Optional fade in effect */ fadeIn?: Fade; /** Optional fade out effect */ fadeOut?: Fade; /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */ gain: number; /** Optional label/name for this clip */ name?: string; /** Optional color for visual distinction */ color?: string; /** * Pre-computed waveform data from waveform-data.js library. * When provided, the library will use this instead of computing peaks from the audioBuffer. * Supports resampling to different zoom levels and slicing for clip trimming. * Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')` */ waveformData?: WaveformDataObject; /** * MIDI note data — when present, this clip plays MIDI instead of audio. * The playout adapter uses this field to detect MIDI clips and route them * to MidiToneTrack (PolySynth) instead of ToneTrack (AudioBufferSourceNode). */ midiNotes?: MidiNoteData[]; /** MIDI channel (0-indexed). Channel 9 = GM percussion. */ midiChannel?: number; /** MIDI program number (0-127). GM instrument number for SoundFont playback. */ midiProgram?: number; } /** * Represents a track containing multiple audio clips */ interface ClipTrack { /** Unique identifier for this track */ id: string; /** Display name for this track */ name: string; /** Array of audio clips on this track */ clips: AudioClip[]; /** Whether this track is muted */ muted: boolean; /** Whether this track is soloed */ soloed: boolean; /** Track volume (0.0 to 1.0+) */ volume: number; /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */ pan: number; /** Optional track color for visual distinction */ color?: string; /** Track height in pixels (for UI) */ height?: number; /** Optional effects function for this track */ effects?: TrackEffectsFunction; /** Visualization render mode. Default: 'waveform' */ renderMode?: RenderMode; /** Per-track spectrogram configuration (FFT size, window, frequency scale, etc.) */ spectrogramConfig?: SpectrogramConfig; /** Per-track spectrogram color map name or custom color array */ spectrogramColorMap?: ColorMapValue; } /** * Represents the entire timeline/project */ interface Timeline { /** All tracks in the timeline */ tracks: ClipTrack[]; /** Total timeline duration in seconds */ duration: number; /** Sample rate for all audio (typically 44100 or 48000) */ sampleRate: number; /** Optional project name */ name?: string; /** Optional tempo (BPM) for grid snapping */ tempo?: number; /** Optional time signature for grid snapping */ timeSignature?: { numerator: number; denominator: number; }; } /** * Options for creating a new audio clip (using sample counts) * * Either audioBuffer OR (sampleRate + sourceDurationSamples + waveformData) must be provided. * Providing waveformData without audioBuffer enables peaks-first rendering. */ interface CreateClipOptions { /** Audio buffer - optional for peaks-first rendering */ audioBuffer?: AudioBuffer; startSample: number; startTick?: number; durationSamples?: number; offsetSamples?: number; gain?: number; name?: string; color?: string; fadeIn?: Fade; fadeOut?: Fade; /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */ waveformData?: WaveformDataObject; /** Sample rate - required if audioBuffer not provided */ sampleRate?: number; /** Total source audio duration in samples - required if audioBuffer not provided */ sourceDurationSamples?: number; /** MIDI note data — passed through to the created AudioClip */ midiNotes?: MidiNoteData[]; /** MIDI channel (0-indexed). Channel 9 = GM percussion. */ midiChannel?: number; /** MIDI program number (0-127). GM instrument for SoundFont playback. */ midiProgram?: number; } /** * Options for creating a new audio clip (using seconds for convenience) * * Either audioBuffer OR (sampleRate + sourceDuration + waveformData) must be provided. * Providing waveformData without audioBuffer enables peaks-first rendering. */ interface CreateClipOptionsSeconds { /** Audio buffer - optional for peaks-first rendering */ audioBuffer?: AudioBuffer; startTime: number; startTick?: number; duration?: number; offset?: number; gain?: number; name?: string; color?: string; fadeIn?: Fade; fadeOut?: Fade; /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */ waveformData?: WaveformDataObject; /** Sample rate - required if audioBuffer not provided */ sampleRate?: number; /** Total source audio duration in seconds - required if audioBuffer not provided */ sourceDuration?: number; /** MIDI note data — passed through to the created AudioClip */ midiNotes?: MidiNoteData[]; /** MIDI channel (0-indexed). Channel 9 = GM percussion. */ midiChannel?: number; /** MIDI program number (0-127). GM instrument for SoundFont playback. */ midiProgram?: number; } /** * Options for creating a new audio clip from tick position. * startTick is authoritative; startSample is derived. * * Provide either: * - ticksToSeconds callback (for variable-tempo / multi-tempo), or * - bpm + ppqn (for single-tempo convenience) */ interface CreateClipOptionsTicks { startTick: number; ticksToSeconds?: (tick: number) => number; bpm?: number; ppqn?: number; audioBuffer?: AudioBuffer; durationSamples?: number; offsetSamples?: number; gain?: number; name?: string; color?: string; fadeIn?: Fade; fadeOut?: Fade; waveformData?: WaveformDataObject; sampleRate?: number; sourceDurationSamples?: number; midiNotes?: MidiNoteData[]; midiChannel?: number; midiProgram?: number; } declare function createClipFromTicks(options: CreateClipOptionsTicks): AudioClip; /** * Options for creating a new track */ interface CreateTrackOptions { name: string; clips?: AudioClip[]; muted?: boolean; soloed?: boolean; volume?: number; pan?: number; color?: string; height?: number; spectrogramConfig?: SpectrogramConfig; spectrogramColorMap?: ColorMapValue; } /** * Creates a new AudioClip with sensible defaults (using sample counts) * * For peaks-first rendering (no audioBuffer), sampleRate and sourceDurationSamples can be: * - Provided explicitly via options * - Derived from waveformData (sample_rate and duration properties) */ declare function createClip(options: CreateClipOptions): AudioClip; /** * Creates a new AudioClip from time-based values (convenience function) * Converts seconds to samples using the audioBuffer's sampleRate or explicit sampleRate * * For peaks-first rendering (no audioBuffer), sampleRate and sourceDuration can be: * - Provided explicitly via options * - Derived from waveformData (sample_rate and duration properties) */ declare function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip; /** * Creates a new ClipTrack with sensible defaults */ declare function createTrack(options: CreateTrackOptions): ClipTrack; /** * Creates a new Timeline with sensible defaults */ declare function createTimeline(tracks: ClipTrack[], sampleRate?: number, options?: { name?: string; tempo?: number; timeSignature?: { numerator: number; denominator: number; }; }): Timeline; /** * MIDI note data for clips that play MIDI instead of audio. * When present on an AudioClip, the clip is treated as a MIDI clip * by the playout adapter. */ interface MidiNoteData { /** MIDI note number (0-127) */ midi: number; /** Note name in scientific pitch notation ("C4", "G#3") */ name: string; /** Start time in seconds, relative to clip start */ time: number; /** Duration in seconds */ duration: number; /** Velocity (0-1 normalized) */ velocity: number; /** MIDI channel (0-indexed). Channel 9 = GM percussion. Enables per-note routing in flattened tracks. */ channel?: number; } /** * Utility: Get all clips within a sample range */ declare function getClipsInRange(track: ClipTrack, startSample: number, endSample: number): AudioClip[]; /** * Utility: Get all clips at a specific sample position */ declare function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[]; /** * Utility: Check if two clips overlap */ declare function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean; /** * Utility: Sort clips by startSample */ declare function sortClipsByTime(clips: AudioClip[]): AudioClip[]; /** * Utility: Find gaps between clips (silent regions) */ interface Gap { startSample: number; endSample: number; durationSamples: number; } declare function findGaps(track: ClipTrack): Gap[]; /** * Shared annotation types used across waveform-playlist packages */ /** * Base annotation data structure */ interface AnnotationData { id: string; start: number; end: number; lines: string[]; language?: string; /** Musical position (ticks). Authoritative when BOTH tick fields are set — * start/end seconds are then a derived cache (clip startTick pattern). */ startTick?: number; endTick?: number; } /** * Annotation format definition for parsing/serializing */ interface AnnotationFormat { name: string; parse: (data: unknown) => AnnotationData[]; serialize: (annotations: AnnotationData[]) => unknown; } /** * Options for annotation list behavior */ interface AnnotationListOptions { editable?: boolean; linkEndpoints?: boolean; isContinuousPlay?: boolean; } /** * Event handlers for annotation operations */ interface AnnotationEventMap { 'annotation-select': (annotation: AnnotationData) => void; 'annotation-update': (annotation: AnnotationData) => void; 'annotation-delete': (id: string) => void; 'annotation-create': (annotation: AnnotationData) => void; } /** * Configuration options passed to annotation action handlers. * Used by both browser and annotations packages. */ interface AnnotationActionOptions { /** Whether annotation endpoints are linked (moving one endpoint moves the other) */ linkEndpoints?: boolean; /** Whether to continue playing after an annotation ends */ continuousPlay?: boolean; /** Additional custom properties */ [key: string]: unknown; } /** * An action control shown on annotation items (e.g., delete, split). */ interface AnnotationAction { class?: string; text?: string; title: string; action: (annotation: AnnotationData, index: number, annotations: AnnotationData[], opts: AnnotationActionOptions) => void; } /** * Props passed to the renderAnnotationItem function for custom rendering. */ interface RenderAnnotationItemProps { annotation: AnnotationData; index: number; isActive: boolean; onClick: () => void; formatTime: (seconds: number) => string; } /** * Peaks type - represents a typed array of interleaved min/max peak data */ type Peaks = Int8Array | Int16Array; /** * Bits type - number of bits for peak data */ type Bits = 8 | 16; /** * PeakData - result of peak extraction */ interface PeakData { /** Number of peak pairs extracted */ length: number; /** Array of peak data for each channel (interleaved min/max) */ data: Peaks[]; /** Bit depth of peak data */ bits: Bits; } interface WaveformConfig { sampleRate: number; samplesPerPixel: number; waveHeight?: number; waveOutlineColor?: string; waveFillColor?: string; waveProgressColor?: string; } interface AudioBuffer$1 { length: number; duration: number; numberOfChannels: number; sampleRate: number; getChannelData(channel: number): Float32Array; } interface Track { id: string; name: string; src?: string | AudioBuffer$1; gain: number; muted: boolean; soloed: boolean; stereoPan: number; startTime: number; endTime?: number; fadeIn?: Fade; fadeOut?: Fade; cueIn?: number; cueOut?: number; } /** * Simple fade configuration */ interface Fade { /** Duration of the fade in seconds */ duration: number; /** Type of fade curve (default: 'linear') */ type?: FadeType; } type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential'; /** * Alias for Fade — used by media-element-playout and playout packages */ type FadeConfig = Fade; interface PlaylistConfig { samplesPerPixel?: number; waveHeight?: number; container?: HTMLElement; isAutomaticScroll?: boolean; timescale?: boolean; colors?: { waveOutlineColor?: string; waveFillColor?: string; waveProgressColor?: string; }; controls?: { show?: boolean; width?: number; }; zoomLevels?: number[]; } interface PlayoutState { isPlaying: boolean; isPaused: boolean; cursor: number; duration: number; } interface TimeSelection { start: number; end: number; } declare enum InteractionState { Cursor = "cursor", Select = "select", Shift = "shift", FadeIn = "fadein", FadeOut = "fadeout" } declare function samplesToSeconds(samples: number, sampleRate: number): number; declare function secondsToSamples(seconds: number, sampleRate: number): number; declare function samplesToPixels(samples: number, samplesPerPixel: number): number; declare function pixelsToSamples(pixels: number, samplesPerPixel: number): number; declare function pixelsToSeconds(pixels: number, samplesPerPixel: number, sampleRate: number): number; declare function secondsToPixels(seconds: number, samplesPerPixel: number, sampleRate: number): number; /** Default PPQN matching Tone.js Transport (192 ticks per quarter note) */ declare const PPQN = 192; /** Number of PPQN ticks per beat for the given time signature. */ declare function ticksPerBeat(timeSignature: [number, number], ppqn?: number): number; /** Number of PPQN ticks per bar for the given time signature. */ declare function ticksPerBar(timeSignature: [number, number], ppqn?: number): number; /** Convert PPQN ticks to sample count. Uses Math.round for integer sample alignment. */ declare function ticksToSamples(ticks: number, bpm: number, sampleRate: number, ppqn?: number): number; /** Convert sample count to PPQN ticks. Inverse of ticksToSamples. */ declare function samplesToTicks(samples: number, bpm: number, sampleRate: number, ppqn?: number): number; /** Snap a tick position to the nearest grid line (rounds to nearest). */ declare function snapToGrid(ticks: number, gridSizeTicks: number): number; /** * Convert a dB value to a normalized range. * * Maps dB values linearly: floor → 0, 0 dB → 1. * Values above 0 dB map to > 1 (e.g., +5 dB → 1.05 with default floor). * * @param dB - Decibel value (typically -Infinity to +5) * @param floor - Minimum dB value mapped to 0. Default: -100 (Firefox compat) * @returns Normalized value (0 at floor, 1 at 0 dB, >1 above 0 dB) */ declare function dBToNormalized(dB: number, floor?: number): number; /** * Convert a normalized value back to dB. * * Maps linearly: 0 → floor, 1 → 0 dB. * Values above 1 map to positive dB (e.g., 1.05 → +5 dB with default floor). * * @param normalized - Normalized value (0 = floor, 1 = 0 dB) * @param floor - Minimum dB value (maps from 0). Must be negative. Default: -100 * @returns dB value (floor at 0, 0 dB at 1, positive dB above 1) */ declare function normalizedToDb(normalized: number, floor?: number): number; /** * Convert a linear gain value to decibels. * * @param gain - Linear gain (0 = silence, 1 = unity) * @returns Decibel value (e.g., 0.5 → ≈ -6.02 dB) */ declare function gainToDb(gain: number): number; /** * Convert a linear gain value (0-1+) to normalized 0-1 via dB. * * Combines gain-to-dB (20 * log10) with dBToNormalized for a consistent * mapping from raw AudioWorklet peak/RMS values to the 0-1 range used * by UI meter components. * * @param gain - Linear gain value (typically 0 to 1, can exceed 1) * @param floor - Minimum dB value mapped to 0. Default: -100 * @returns Normalized value (0 at silence/floor, 1 at 0 dB, >1 above 0 dB) */ declare function gainToNormalized(gain: number, floor?: number): number; interface MeterEntry { tick: number; numerator: number; denominator: number; } /** * Scans a beat number sequence and detects meter (time signature) changes. * * Each beat in the input has a `beat` number (1-indexed). When the beat resets * to 1, we count how many beats were in the previous bar and derive the numerator. * * @param beats - Array of beat events with `time` (seconds) and `beat` (1-indexed number). * @param firstBeatTick - The tick position of beats[0] on the timeline. * @param ppqn - Ticks per quarter note (ticks per beat). * @returns Array of MeterEntry sorted by tick. Always includes an entry at tick 0. */ declare function detectMeterChanges(beats: { time: number; beat: number; }[], firstBeatTick: number, ppqn: number): MeterEntry[]; /** All supported snap-to-grid values. */ type SnapTo = 'bar' | 'beat' | '1/2' | '1/4' | '1/8' | '1/16' | '1/32' | '1/2T' | '1/4T' | '1/8T' | '1/16T' | 'off'; /** * Returns the tick interval for the given SnapTo value. * * Straight subdivisions (1/2, 1/4, 1/8, 1/16, 1/32) are always expressed as * fractions of a quarter note (ppqn), independent of the time signature * denominator. Triplet subdivisions use × 2/3 of the corresponding straight * value. 'bar' and 'beat' depend on the first meter entry's time signature. * 'off' returns 0. */ declare function snapToTicks(snapTo: SnapTo, timeSignature: [number, number], ppqn?: number): number; /** * Three-tier tick hierarchy (following Audacity's model): * major — Bar boundaries. Always labeled, strongest grid lines. * minor — Beat boundaries. Labeled when wide enough, medium grid lines. * minorMinor — Subdivisions (eighths, sixteenths). Never labeled, ruler ticks only (no grid). */ type TickType = 'major' | 'minor' | 'minorMinor'; /** Zoom level category used to select which subdivision to iterate at. */ type ZoomLevel = 'coarse' | 'bar' | 'beat' | 'eighth' | 'sixteenth'; /** A single musical tick with rendering metadata. */ interface MusicalTick { /** Pixel position of the tick in the timeline. */ pixel: number; /** Three-tier type: major (bar), minor (beat), minorMinor (subdivision). */ type: TickType; /** Human-readable label. Present for major ticks always; minor ticks when zoomed in. */ label?: string; /** 0-based global bar index (for alternating bar-level striping). */ barIndex: number; } /** Result of computeMusicalTicks(). */ interface MusicalTickData { ticks: MusicalTick[]; pixelsPerQuarterNote: number; zoomLevel: ZoomLevel; /** At 'coarse' zoom: how many quarter notes between rendered tick lines. */ coarseQuarterNoteStep?: number; } /** Parameters for computeMusicalTicks(). */ interface MusicalTickParams { meterEntries: MeterEntry[]; /** Ticks per pixel (zoom level — lower value = more zoomed in). */ ticksPerPixel: number; startPixel: number; endPixel: number; /** Pulses per quarter note. Defaults to 960. */ ppqn?: number; } /** Minimum pixels per musical unit before switching to a coarser zoom level. */ declare const MIN_PIXELS_PER_UNIT = 8; /** * Determines the zoom level and computes which tick lines to render for a * given viewport. Pure tick arithmetic — no BPM or sample rate required. * * Walks meter entries in segments, so bar/beat boundaries and labels are * correct across meter changes. */ declare function computeMusicalTicks(params: MusicalTickParams): MusicalTickData; /** * Convert an absolute tick to a 1-based bar.beat position, honoring meter * changes. Bars/beats are counted per meter segment (the same walk * `computeMusicalTicks` uses to build its `barOffset` accumulator). Beat is * the integer beat the tick falls WITHIN (floor, 1-based) — a tick exactly on * a bar line is beat 1. * * Guards: empty `meterEntries` → treat as 4/4 from tick 0. `ppqn <= 0` → * returns `{ bar: 1, beat: 1 }` (division-by-zero guard, matches * `computeMusicalTicks`'s ppqn guard). */ declare function ticksToBarBeat(tick: number, meterEntries: MeterEntry[], ppqn: number): { bar: number; beat: number; }; /** * Snaps a tick position to the nearest grid boundary defined by `snapTo`. * * Finds the meter entry active at the tick position and snaps relative to * that meter's segment start. * * Returns the original tick unchanged when `snapTo` is 'off'. */ declare function snapTickToGrid(tick: number, snapTo: SnapTo, meterEntries: MeterEntry[], ppqn?: number): number; /** * Peak generation for real-time waveform visualization during recording. * Matches the format used by webaudio-peaks: min/max pairs with bit depth. */ /** * Generate peaks from audio samples in standard min/max pair format. * * @param samples - Audio samples to process * @param samplesPerPixel - Number of samples to represent in each peak * @param bits - Bit depth for peak values (8 or 16) * @returns Int8Array or Int16Array of peak values (min/max pairs) */ declare function generatePeaks(samples: Float32Array, samplesPerPixel: number, bits?: 8 | 16): Int8Array | Int16Array; /** * Append new peaks to existing peaks array. * This is used for incremental peak updates during recording. */ declare function appendPeaks(existingPeaks: Int8Array | Int16Array, newSamples: Float32Array, samplesPerPixel: number, totalSamplesProcessed: number, bits?: 8 | 16): Int8Array | Int16Array; /** * Utility functions for working with AudioBuffers during recording */ /** * Concatenate multiple Float32Arrays into a single array */ declare function concatenateAudioData(chunks: Float32Array[]): Float32Array; /** * Convert channel data to AudioBuffer. * Accepts either per-channel Float32Array[] or a single Float32Array (mono, backwards compatible). */ declare function createAudioBuffer(audioContext: AudioContext, channelData: Float32Array[] | Float32Array, sampleRate: number, channelCount?: number): AudioBuffer; /** * Append new samples to an existing AudioBuffer (mono convenience) */ declare function appendToAudioBuffer(audioContext: AudioContext, existingBuffer: AudioBuffer | null, newSamples: Float32Array, sampleRate: number): AudioBuffer; /** * Calculate duration in seconds from sample count and sample rate */ declare function calculateDuration(sampleCount: number, sampleRate: number): number; /** * Sample count corresponding to the audible-latency window * (`outputLatency` + scheduler `lookAhead`). Used to skip leading silence in * recordings: between record-start and audible playback the user heard nothing, * so that prefix should be trimmed from buffers / peaks. Returns 0 for * non-finite or non-positive inputs. * * Single source of truth for `useIntegratedRecording` (buffer trim) and * `PlaylistVisualization` (live preview peak slice). Keep the two in lockstep * so the live preview width matches the finalized clip. */ declare function audibleLatencySamples(outputLatency: number, lookAhead: number, sampleRate: number): number; /** * Resolve the recording latency offset in samples. * * When `overrideSeconds` is provided it is an **absolute replacement** for the * auto-computed value — a latency in seconds, converted at `sampleRate` * (`0` disables compensation; negative/non-finite resolve to `0`). Otherwise the * offset is the auto-computed audible-latency window (`outputLatency + lookAhead`). * * Single source of truth for the override-vs-auto decision across dawcore * (`RecordingController`) and React (`useIntegratedRecording` finalization + * `PlaylistVisualization` live preview). The override branch is the same math as * the auto branch with `lookAhead = 0`, so both inherit the finite/positive * guards in `audibleLatencySamples`. */ declare function resolveRecordingOffsetSamples(params: { /** Public override (seconds). Absolute replacement when defined. */ overrideSeconds?: number; /** Browser-reported output latency (seconds). */ outputLatency: number; /** Scheduler look-ahead (seconds). Pass 0 for engines without one (native transport). */ lookAhead: number; /** Sample rate the recording was captured at. */ sampleRate: number; }): number; /** * Default values for `SpectrogramConfig` fields. Used by both the orchestrator * and the React controller so defaults can't drift between layers. * * Intentionally omitted: * - `maxFrequency` — defaults to `sampleRate / 2` at compute time (clip-dependent) * - `alpha` — window-specific (only used by the `hamming` window function); its * canonical default (0.54) lives in `windowFunctions.ts` where the math is * - `labelsColor`, `labelsBackground` — kept as optional `undefined` so consumers * can opt into label styling without forcing a color value */ declare const SPECTROGRAM_DEFAULTS: Required> & { labelsColor: string | undefined; labelsBackground: string | undefined; }; /** Default color map when none is specified. */ declare const DEFAULT_SPECTROGRAM_COLOR_MAP: ColorMapValue; /** * Punch-in replace: carve a sample range out of a clip list. * * Used when a newly recorded clip lands at the playhead and must REPLACE any * existing clip content between its start and end (issue #579): partial * overlaps are trimmed, fully-covered clips are removed, and a clip that * spans the whole range is split into two. */ /** * Return a new clip list with the sample range [rangeStart, rangeEnd) * removed from every clip that overlaps it. * * - Clips outside the range are returned by reference (untouched). * - Clips fully inside the range are dropped. * - A clip overlapping only the range's start keeps its head (right-trim). * - A clip overlapping only the range's end keeps its tail: its start moves * to rangeEnd and `offsetSamples` advances by the carved amount. * - A clip containing the whole range splits into head + tail; the tail is a * new clip (deterministic id `-carve-`) sharing the * same audioBuffer. * * Clips whose `startSample` changes lose their `startTick` — a sample-space * carve cannot recompute ticks, and the engine re-enriches clips without * `startTick` on ingestion (see AudioClip.startTick). * * Pure and immutable: input clips are never mutated. An empty or inverted * range returns the input list unchanged. */ declare function carveClipRange(clips: readonly AudioClip[], rangeStart: number, rangeEnd: number): AudioClip[]; /** Clip start position in seconds */ declare function clipStartTime(clip: AudioClip): number; /** Clip end position in seconds (start + duration) */ declare function clipEndTime(clip: AudioClip): number; /** Clip offset into source audio in seconds */ declare function clipOffsetTime(clip: AudioClip): number; /** Clip duration in seconds */ declare function clipDurationTime(clip: AudioClip): number; /** * Max audio channel count across a track's clips. * Used to set Panner channelCount and offline render output channels. */ declare function trackChannelCount(track: ClipTrack): number; /** * Clip width in pixels at a given samplesPerPixel. * Shared by Clip.tsx (container sizing) and ChannelWithProgress.tsx (progress overlay) * to ensure pixel-perfect alignment. Floor-based endpoint subtraction guarantees * adjacent clips have no pixel gaps. */ declare function clipPixelWidth(startSample: number, durationSamples: number, samplesPerPixel: number): number; /** * Canonical spectrogram canvas-ID contract. * * Spectrogram canvases are identified by `${clipId}-ch${channelIndex}-chunk${chunkIndex}`. * This format is produced (builders) and consumed (parsers) by several packages * — the React `SpectrogramProvider`/`SpectrogramChannel`, the `` * Lit element, and the `@dawcore/spectrogram` worker pool. Single-sourcing the * build + parse here prevents the drift class where a format change lands in some * sites but not others (the pool's no-match fallback then routes every channel to * worker 0 → channel 0's data rendered for all channels — #556). * * `@waveform-playlist/core` is the home because it is zero-dependency and already * a dependency of every producer and consumer (unlike `@dawcore/spectrogram`, * which `@waveform-playlist/ui-components` does not depend on). The format is a * pure string contract with no FFT/compute dependency. */ interface SpectrogramCanvasIdParts { clipId: string; channelIndex: number; chunkIndex: number; } /** Build a spectrogram canvas ID from its parts. */ declare function buildSpectrogramCanvasId(parts: SpectrogramCanvasIdParts): string; /** * Parse a spectrogram canvas ID into its parts, or `null` when it doesn't match * the `${clipId}-ch${channelIndex}-chunk${chunkIndex}` format. */ declare function parseSpectrogramCanvasId(canvasId: string): SpectrogramCanvasIdParts | null; /** * Fade curve utilities for Web Audio API * * Pure functions that generate fade curves and apply them to AudioParam. * No Tone.js dependency — works with native Web Audio nodes. */ /** * Generate a linear fade curve */ declare function linearCurve(length: number, fadeIn: boolean): Float32Array; /** * Generate an exponential fade curve */ declare function exponentialCurve(length: number, fadeIn: boolean): Float32Array; /** * Generate an S-curve (sine-based smooth curve) */ declare function sCurveCurve(length: number, fadeIn: boolean): Float32Array; /** * Generate a logarithmic fade curve */ declare function logarithmicCurve(length: number, fadeIn: boolean, base?: number): Float32Array; /** * Generate a fade curve of the specified type */ declare function generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array; /** * Apply a fade in to an AudioParam * * @param param - The AudioParam to apply the fade to (usually gain) * @param startTime - When the fade starts (in seconds, AudioContext time) * @param duration - Duration of the fade in seconds * @param type - Type of fade curve * @param startValue - Starting value (default: 0) * @param endValue - Ending value (default: 1) */ declare function applyFadeIn(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void; /** * Apply a fade out to an AudioParam * * @param param - The AudioParam to apply the fade to (usually gain) * @param startTime - When the fade starts (in seconds, AudioContext time) * @param duration - Duration of the fade in seconds * @param type - Type of fade curve * @param startValue - Starting value (default: 1) * @param endValue - Ending value (default: 0) */ declare function applyFadeOut(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void; /** * Framework-agnostic keyboard shortcut handling. * Used by both React (useKeyboardShortcuts) and Web Components (daw-editor). */ interface KeyboardShortcut { key: string; ctrlKey?: boolean; shiftKey?: boolean; metaKey?: boolean; altKey?: boolean; action: () => void; description?: string; preventDefault?: boolean; } /** A key + modifier combination, without an action. Used for remapping maps. */ type KeyBinding = Pick; /** * Does a keyboard event match a key binding? * `undefined` modifier = match any state; `false` = must NOT be pressed. */ declare function matchesKeyBinding(event: KeyboardEvent, binding: KeyBinding): boolean; /** * Handle a keyboard event against a list of shortcuts. * Pure function, no framework dependency. */ declare function handleKeyboardEvent(event: KeyboardEvent, shortcuts: KeyboardShortcut[], enabled: boolean): void; /** * Get a human-readable string representation of a keyboard shortcut. * * @param shortcut - The keyboard shortcut * @returns Human-readable string (e.g., "Cmd+Shift+S") */ declare const getShortcutLabel: (shortcut: KeyboardShortcut) => string; type RangeSupport = 'supported' | 'unsupported' | 'unknown'; type FetchLike = (url: string, init?: RequestInit) => Promise; /** * Probe whether an audio host honors HTTP range requests, so an on-demand * player can decide its seeking policy. Range *detection* lives here; range * *policy* (disable the scrubber, surface an error, play from start) stays with * the consuming component — the detection is generic, the UX is per-component. * * Sends `GET` with `Range: bytes=0-1` and reads only the status line, aborting * the body immediately: on a non-range host the `200` carries the entire * (possibly hours-long) file, which must never be downloaded just to detect the * failure. * * - `206` → `'supported'` * - `200` (host ignored Range, returned full body) → `'unsupported'` * - any throw (network error / CORS-opaque cross-origin) or other status → `'unknown'` * * **Positive-failure only:** only an observed `200` asserts a failure. A * CORS-opaque probe is `'unknown'`, NOT a failure — native `