{"version":3,"sources":["../src/constants.ts","../src/types/clip.ts","../src/types/index.ts","../src/utils/conversions.ts","../src/utils/beatsAndBars.ts","../src/utils/dBUtils.ts","../src/utils/musicalTicks.ts","../src/utils/meterDetection.ts","../src/utils/peaksGenerator.ts","../src/utils/audioBufferUtils.ts","../src/utils/latency.ts","../src/utils/spectrogram-defaults.ts","../src/utils/carveClipRange.ts","../src/clipTimeHelpers.ts","../src/spectrogramCanvasId.ts","../src/fades.ts","../src/keyboard.ts","../src/probeRangeSupport.ts","../src/enumerateMicrophones.ts","../src/annotations/boundaries.ts","../src/annotations/shortcuts.ts"],"sourcesContent":["/**\n * Maximum width in CSS pixels for a single canvas chunk.\n * Canvas elements are split into chunks of this width to enable\n * horizontal virtual scrolling — only visible chunks are mounted.\n */\nexport const MAX_CANVAS_WIDTH = 1000;\n","/**\n * Clip-Based Model Types\n *\n * These types support a professional multi-track editing model where:\n * - Each track can contain multiple audio clips\n * - Clips can be positioned anywhere on the timeline\n * - Clips have independent trim points (offset/duration)\n * - Gaps between clips are silent\n * - Clips can overlap (for crossfades)\n */\n\nimport { Fade } from './index';\nimport type { RenderMode, SpectrogramConfig, ColorMapValue } from './spectrogram';\n\n/**\n * WaveformData object from waveform-data.js library.\n * Supports resample() and slice() for dynamic zoom levels.\n * See: https://github.com/bbc/waveform-data.js\n */\nexport interface WaveformDataObject {\n  /** Sample rate of the original audio */\n  readonly sample_rate: number;\n  /** Number of audio samples per pixel */\n  readonly scale: number;\n  /** Length of waveform data in pixels */\n  readonly length: number;\n  /** Bit depth (8 or 16) */\n  readonly bits: number;\n  /** Duration in seconds */\n  readonly duration: number;\n  /** Number of channels */\n  readonly channels: number;\n  /** Get channel data */\n  channel: (index: number) => {\n    min_array: () => number[];\n    max_array: () => number[];\n  };\n  /** Resample to different scale */\n  resample: (options: { scale: number } | { width: number }) => WaveformDataObject;\n  /** Slice a portion of the waveform */\n  slice: (\n    options: { startTime: number; endTime: number } | { startIndex: number; endIndex: number }\n  ) => WaveformDataObject;\n}\n\n/**\n * Generic effects function type for track-level audio processing.\n *\n * The actual implementation receives Tone.js audio nodes. Using generic types\n * here to avoid circular dependencies with the playout package.\n *\n * @param graphEnd - The end of the track's audio graph (Tone.js Gain node)\n * @param destination - Where to connect the effects output (Tone.js ToneAudioNode)\n * @param isOffline - Whether rendering offline (for export)\n * @returns Optional cleanup function called when track is disposed\n *\n * @example\n * ```typescript\n * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => {\n *   const reverb = new Tone.Reverb({ decay: 1.5 });\n *   graphEnd.connect(reverb);\n *   reverb.connect(destination);\n *\n *   return () => {\n *     reverb.dispose();\n *   };\n * };\n * ```\n */\nexport type TrackEffectsFunction = (\n  graphEnd: unknown,\n  destination: unknown,\n  isOffline: boolean\n) => void | (() => void);\n\n/**\n * Represents a single audio clip on the timeline\n *\n * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers)\n * to avoid floating-point precision errors. Convert to seconds only when\n * needed for playback using: seconds = samples / sampleRate\n *\n * Clips can be created with just waveformData (for instant visual rendering)\n * and have audioBuffer added later when audio finishes loading.\n */\nexport interface AudioClip {\n  /** Unique identifier for this clip */\n  id: string;\n\n  /**\n   * The audio buffer containing the audio data.\n   * Optional for peaks-first rendering - can be added later.\n   * Required for playback and editing operations.\n   */\n  audioBuffer?: AudioBuffer;\n\n  /** Position on timeline where this clip starts (in samples at timeline sampleRate) */\n  startSample: number;\n\n  /**\n   * Position on timeline in ticks (authoritative when present).\n   * When set, startSample is a derived cache recomputed from startTick via TempoMap.\n   * Optional for backwards compatibility — engine enriches clips without startTick on ingestion.\n   */\n  startTick?: number;\n\n  /** Duration of this clip (in samples) - how much of the audio buffer to play */\n  durationSamples: number;\n\n  /** Offset into the audio buffer where playback starts (in samples) - the \"trim start\" point */\n  offsetSamples: number;\n\n  /**\n   * Sample rate for this clip's audio.\n   * Required when audioBuffer is not provided (for peaks-first rendering).\n   * When audioBuffer is present, this should match audioBuffer.sampleRate.\n   */\n  sampleRate: number;\n\n  /**\n   * Total duration of the source audio in samples.\n   * Required when audioBuffer is not provided (for trim bounds calculation).\n   * When audioBuffer is present, this should equal audioBuffer.length.\n   */\n  sourceDurationSamples: number;\n\n  /** Optional fade in effect */\n  fadeIn?: Fade;\n\n  /** Optional fade out effect */\n  fadeOut?: Fade;\n\n  /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */\n  gain: number;\n\n  /** Optional label/name for this clip */\n  name?: string;\n\n  /** Optional color for visual distinction */\n  color?: string;\n\n  /**\n   * Pre-computed waveform data from waveform-data.js library.\n   * When provided, the library will use this instead of computing peaks from the audioBuffer.\n   * Supports resampling to different zoom levels and slicing for clip trimming.\n   * Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')`\n   */\n  waveformData?: WaveformDataObject;\n\n  /**\n   * MIDI note data — when present, this clip plays MIDI instead of audio.\n   * The playout adapter uses this field to detect MIDI clips and route them\n   * to MidiToneTrack (PolySynth) instead of ToneTrack (AudioBufferSourceNode).\n   */\n  midiNotes?: MidiNoteData[];\n\n  /** MIDI channel (0-indexed). Channel 9 = GM percussion. */\n  midiChannel?: number;\n\n  /** MIDI program number (0-127). GM instrument number for SoundFont playback. */\n  midiProgram?: number;\n}\n\n/**\n * Represents a track containing multiple audio clips\n */\nexport interface ClipTrack {\n  /** Unique identifier for this track */\n  id: string;\n\n  /** Display name for this track */\n  name: string;\n\n  /** Array of audio clips on this track */\n  clips: AudioClip[];\n\n  /** Whether this track is muted */\n  muted: boolean;\n\n  /** Whether this track is soloed */\n  soloed: boolean;\n\n  /** Track volume (0.0 to 1.0+) */\n  volume: number;\n\n  /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */\n  pan: number;\n\n  /** Optional track color for visual distinction */\n  color?: string;\n\n  /** Track height in pixels (for UI) */\n  height?: number;\n\n  /** Optional effects function for this track */\n  effects?: TrackEffectsFunction;\n\n  /** Visualization render mode. Default: 'waveform' */\n  renderMode?: RenderMode;\n\n  /** Per-track spectrogram configuration (FFT size, window, frequency scale, etc.) */\n  spectrogramConfig?: SpectrogramConfig;\n\n  /** Per-track spectrogram color map name or custom color array */\n  spectrogramColorMap?: ColorMapValue;\n}\n\n/**\n * Represents the entire timeline/project\n */\nexport interface Timeline {\n  /** All tracks in the timeline */\n  tracks: ClipTrack[];\n\n  /** Total timeline duration in seconds */\n  duration: number;\n\n  /** Sample rate for all audio (typically 44100 or 48000) */\n  sampleRate: number;\n\n  /** Optional project name */\n  name?: string;\n\n  /** Optional tempo (BPM) for grid snapping */\n  tempo?: number;\n\n  /** Optional time signature for grid snapping */\n  timeSignature?: {\n    numerator: number;\n    denominator: number;\n  };\n}\n\n/**\n * Options for creating a new audio clip (using sample counts)\n *\n * Either audioBuffer OR (sampleRate + sourceDurationSamples + waveformData) must be provided.\n * Providing waveformData without audioBuffer enables peaks-first rendering.\n */\nexport interface CreateClipOptions {\n  /** Audio buffer - optional for peaks-first rendering */\n  audioBuffer?: AudioBuffer;\n  startSample: number; // Position on timeline (in samples)\n  startTick?: number; // Timeline position in ticks (optional)\n  durationSamples?: number; // Defaults to full buffer/source duration (in samples)\n  offsetSamples?: number; // Defaults to 0\n  gain?: number; // Defaults to 1.0\n  name?: string;\n  color?: string;\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n  waveformData?: WaveformDataObject;\n  /** Sample rate - required if audioBuffer not provided */\n  sampleRate?: number;\n  /** Total source audio duration in samples - required if audioBuffer not provided */\n  sourceDurationSamples?: number;\n  /** MIDI note data — passed through to the created AudioClip */\n  midiNotes?: MidiNoteData[];\n  /** MIDI channel (0-indexed). Channel 9 = GM percussion. */\n  midiChannel?: number;\n  /** MIDI program number (0-127). GM instrument for SoundFont playback. */\n  midiProgram?: number;\n}\n\n/**\n * Options for creating a new audio clip (using seconds for convenience)\n *\n * Either audioBuffer OR (sampleRate + sourceDuration + waveformData) must be provided.\n * Providing waveformData without audioBuffer enables peaks-first rendering.\n */\nexport interface CreateClipOptionsSeconds {\n  /** Audio buffer - optional for peaks-first rendering */\n  audioBuffer?: AudioBuffer;\n  startTime: number; // Position on timeline (in seconds)\n  startTick?: number; // Timeline position in ticks (optional)\n  duration?: number; // Defaults to full buffer/source duration (in seconds)\n  offset?: number; // Defaults to 0 (in seconds)\n  gain?: number; // Defaults to 1.0\n  name?: string;\n  color?: string;\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n  waveformData?: WaveformDataObject;\n  /** Sample rate - required if audioBuffer not provided */\n  sampleRate?: number;\n  /** Total source audio duration in seconds - required if audioBuffer not provided */\n  sourceDuration?: number;\n  /** MIDI note data — passed through to the created AudioClip */\n  midiNotes?: MidiNoteData[];\n  /** MIDI channel (0-indexed). Channel 9 = GM percussion. */\n  midiChannel?: number;\n  /** MIDI program number (0-127). GM instrument for SoundFont playback. */\n  midiProgram?: number;\n}\n\n/**\n * Options for creating a new audio clip from tick position.\n * startTick is authoritative; startSample is derived.\n *\n * Provide either:\n * - ticksToSeconds callback (for variable-tempo / multi-tempo), or\n * - bpm + ppqn (for single-tempo convenience)\n */\nexport interface CreateClipOptionsTicks {\n  startTick: number;\n  ticksToSeconds?: (tick: number) => number;\n  bpm?: number;\n  ppqn?: number;\n  audioBuffer?: AudioBuffer;\n  durationSamples?: number;\n  offsetSamples?: number;\n  gain?: number;\n  name?: string;\n  color?: string;\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  waveformData?: WaveformDataObject;\n  sampleRate?: number;\n  sourceDurationSamples?: number;\n  midiNotes?: MidiNoteData[];\n  midiChannel?: number;\n  midiProgram?: number;\n}\n\nexport function createClipFromTicks(options: CreateClipOptionsTicks): AudioClip {\n  const { startTick, ticksToSeconds, bpm, ppqn } = options;\n\n  if (startTick < 0) {\n    throw new Error('createClipFromTicks: startTick must be non-negative');\n  }\n\n  let toSeconds: (tick: number) => number;\n  if (ticksToSeconds) {\n    toSeconds = ticksToSeconds;\n  } else if (bpm !== undefined && ppqn !== undefined) {\n    toSeconds = (tick: number) => (tick * 60) / (ppqn * bpm);\n  } else {\n    throw new Error(\n      'createClipFromTicks: either ticksToSeconds callback or both bpm and ppqn are required'\n    );\n  }\n\n  const sampleRate =\n    options.audioBuffer?.sampleRate ?? options.sampleRate ?? options.waveformData?.sample_rate;\n  if (sampleRate === undefined) {\n    throw new Error('createClipFromTicks: sampleRate is required when audioBuffer is not provided');\n  }\n\n  const startSample = Math.round(toSeconds(startTick) * sampleRate);\n\n  return createClip({\n    audioBuffer: options.audioBuffer,\n    startSample,\n    startTick,\n    durationSamples: options.durationSamples,\n    offsetSamples: options.offsetSamples,\n    gain: options.gain,\n    name: options.name,\n    color: options.color,\n    fadeIn: options.fadeIn,\n    fadeOut: options.fadeOut,\n    waveformData: options.waveformData,\n    sampleRate: options.sampleRate,\n    sourceDurationSamples: options.sourceDurationSamples,\n    midiNotes: options.midiNotes,\n    midiChannel: options.midiChannel,\n    midiProgram: options.midiProgram,\n  });\n}\n\n/**\n * Options for creating a new track\n */\nexport interface CreateTrackOptions {\n  name: string;\n  clips?: AudioClip[];\n  muted?: boolean;\n  soloed?: boolean;\n  volume?: number;\n  pan?: number;\n  color?: string;\n  height?: number;\n  spectrogramConfig?: SpectrogramConfig;\n  spectrogramColorMap?: ColorMapValue;\n}\n\n/**\n * Creates a new AudioClip with sensible defaults (using sample counts)\n *\n * For peaks-first rendering (no audioBuffer), sampleRate and sourceDurationSamples can be:\n * - Provided explicitly via options\n * - Derived from waveformData (sample_rate and duration properties)\n */\nexport function createClip(options: CreateClipOptions): AudioClip {\n  const {\n    audioBuffer,\n    startSample,\n    startTick,\n    offsetSamples = 0,\n    gain = 1.0,\n    name,\n    color,\n    fadeIn,\n    fadeOut,\n    waveformData,\n    midiNotes,\n    midiChannel,\n    midiProgram,\n  } = options;\n\n  // Determine sample rate: audioBuffer > explicit option > waveformData\n  const sampleRate = audioBuffer?.sampleRate ?? options.sampleRate ?? waveformData?.sample_rate;\n\n  // Determine source duration: audioBuffer > explicit option > waveformData (converted to samples)\n  const sourceDurationSamples =\n    audioBuffer?.length ??\n    options.sourceDurationSamples ??\n    (waveformData && sampleRate ? Math.ceil(waveformData.duration * sampleRate) : undefined);\n\n  if (sampleRate === undefined) {\n    throw new Error(\n      'createClip: sampleRate is required when audioBuffer is not provided (can use waveformData.sample_rate)'\n    );\n  }\n  if (sourceDurationSamples === undefined) {\n    throw new Error(\n      'createClip: sourceDurationSamples is required when audioBuffer is not provided (can use waveformData.duration)'\n    );\n  }\n\n  if (audioBuffer && waveformData && audioBuffer.sampleRate !== waveformData.sample_rate) {\n    console.warn(\n      '[waveform-playlist] \"' +\n        (name ?? 'unnamed') +\n        '\": pre-computed peaks at ' +\n        waveformData.sample_rate +\n        ' Hz do not match decoded audio at ' +\n        audioBuffer.sampleRate +\n        ' Hz — peaks may be recomputed from decoded audio'\n    );\n  }\n\n  // Default duration to full source duration\n  const durationSamples = options.durationSamples ?? sourceDurationSamples;\n\n  return {\n    id: generateId(),\n    audioBuffer,\n    startSample,\n    startTick,\n    durationSamples,\n    offsetSamples,\n    sampleRate,\n    sourceDurationSamples,\n    gain,\n    name,\n    color,\n    fadeIn,\n    fadeOut,\n    waveformData,\n    midiNotes,\n    midiChannel,\n    midiProgram,\n  };\n}\n\n/**\n * Creates a new AudioClip from time-based values (convenience function)\n * Converts seconds to samples using the audioBuffer's sampleRate or explicit sampleRate\n *\n * For peaks-first rendering (no audioBuffer), sampleRate and sourceDuration can be:\n * - Provided explicitly via options\n * - Derived from waveformData (sample_rate and duration properties)\n */\nexport function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip {\n  const {\n    audioBuffer,\n    startTime,\n    offset = 0,\n    gain = 1.0,\n    name,\n    color,\n    fadeIn,\n    fadeOut,\n    waveformData,\n    midiNotes,\n    midiChannel,\n    midiProgram,\n  } = options;\n\n  // Determine sample rate: audioBuffer > explicit option > waveformData\n  const sampleRate = audioBuffer?.sampleRate ?? options.sampleRate ?? waveformData?.sample_rate;\n  if (sampleRate === undefined) {\n    throw new Error(\n      'createClipFromSeconds: sampleRate is required when audioBuffer is not provided (can use waveformData.sample_rate)'\n    );\n  }\n\n  // Determine source duration: audioBuffer > explicit option > waveformData\n  const sourceDuration = audioBuffer?.duration ?? options.sourceDuration ?? waveformData?.duration;\n  if (sourceDuration === undefined) {\n    throw new Error(\n      'createClipFromSeconds: sourceDuration is required when audioBuffer is not provided (can use waveformData.duration)'\n    );\n  }\n\n  // Note: audioBuffer and waveformData may have different sample rates (e.g., Opus at 48000\n  // decoded on 44100 hardware). This is expected — callers fall back to worker to recompute peaks.\n\n  // Default clip duration to full source duration\n  const duration = options.duration ?? sourceDuration;\n\n  return createClip({\n    audioBuffer,\n    startSample: Math.round(startTime * sampleRate),\n    startTick: options.startTick,\n    durationSamples: Math.round(duration * sampleRate),\n    offsetSamples: Math.round(offset * sampleRate),\n    sampleRate,\n    sourceDurationSamples: Math.ceil(sourceDuration * sampleRate),\n    gain,\n    name,\n    color,\n    fadeIn,\n    fadeOut,\n    waveformData,\n    midiNotes,\n    midiChannel,\n    midiProgram,\n  });\n}\n\n/**\n * Creates a new ClipTrack with sensible defaults\n */\nexport function createTrack(options: CreateTrackOptions): ClipTrack {\n  const {\n    name,\n    clips = [],\n    muted = false,\n    soloed = false,\n    volume = 1.0,\n    pan = 0,\n    color,\n    height,\n    spectrogramConfig,\n    spectrogramColorMap,\n  } = options;\n\n  return {\n    id: generateId(),\n    name,\n    clips,\n    muted,\n    soloed,\n    volume,\n    pan,\n    color,\n    height,\n    spectrogramConfig,\n    spectrogramColorMap,\n  };\n}\n\n/**\n * Creates a new Timeline with sensible defaults\n */\nexport function createTimeline(\n  tracks: ClipTrack[],\n  sampleRate: number = 44100,\n  options?: {\n    name?: string;\n    tempo?: number;\n    timeSignature?: { numerator: number; denominator: number };\n  }\n): Timeline {\n  // Calculate total duration from all clips across all tracks (in seconds)\n  const durationSamples = tracks.reduce((maxSamples, track) => {\n    const trackSamples = track.clips.reduce((max, clip) => {\n      return Math.max(max, clip.startSample + clip.durationSamples);\n    }, 0);\n    return Math.max(maxSamples, trackSamples);\n  }, 0);\n\n  const duration = durationSamples / sampleRate;\n\n  return {\n    tracks,\n    duration,\n    sampleRate,\n    name: options?.name,\n    tempo: options?.tempo,\n    timeSignature: options?.timeSignature,\n  };\n}\n\n/**\n * Generates a unique ID for clips and tracks\n */\nfunction generateId(): string {\n  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * MIDI note data for clips that play MIDI instead of audio.\n * When present on an AudioClip, the clip is treated as a MIDI clip\n * by the playout adapter.\n */\nexport interface MidiNoteData {\n  /** MIDI note number (0-127) */\n  midi: number;\n  /** Note name in scientific pitch notation (\"C4\", \"G#3\") */\n  name: string;\n  /** Start time in seconds, relative to clip start */\n  time: number;\n  /** Duration in seconds */\n  duration: number;\n  /** Velocity (0-1 normalized) */\n  velocity: number;\n  /** MIDI channel (0-indexed). Channel 9 = GM percussion. Enables per-note routing in flattened tracks. */\n  channel?: number;\n}\n\n/**\n * Utility: Get all clips within a sample range\n */\nexport function getClipsInRange(\n  track: ClipTrack,\n  startSample: number,\n  endSample: number\n): AudioClip[] {\n  return track.clips.filter((clip) => {\n    const clipEnd = clip.startSample + clip.durationSamples;\n    // Clip overlaps with range if:\n    // - Clip starts before range ends AND\n    // - Clip ends after range starts\n    return clip.startSample < endSample && clipEnd > startSample;\n  });\n}\n\n/**\n * Utility: Get all clips at a specific sample position\n */\nexport function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[] {\n  return track.clips.filter((clip) => {\n    const clipEnd = clip.startSample + clip.durationSamples;\n    return sample >= clip.startSample && sample < clipEnd;\n  });\n}\n\n/**\n * Utility: Check if two clips overlap\n */\nexport function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean {\n  const clip1End = clip1.startSample + clip1.durationSamples;\n  const clip2End = clip2.startSample + clip2.durationSamples;\n\n  return clip1.startSample < clip2End && clip1End > clip2.startSample;\n}\n\n/**\n * Utility: Sort clips by startSample\n */\nexport function sortClipsByTime(clips: AudioClip[]): AudioClip[] {\n  return [...clips].sort((a, b) => a.startSample - b.startSample);\n}\n\n/**\n * Utility: Find gaps between clips (silent regions)\n */\nexport interface Gap {\n  startSample: number;\n  endSample: number;\n  durationSamples: number;\n}\n\nexport function findGaps(track: ClipTrack): Gap[] {\n  if (track.clips.length === 0) return [];\n\n  const sorted = sortClipsByTime(track.clips);\n  const gaps: Gap[] = [];\n\n  for (let i = 0; i < sorted.length - 1; i++) {\n    const currentClipEnd = sorted[i].startSample + sorted[i].durationSamples;\n    const nextClipStart = sorted[i + 1].startSample;\n\n    if (nextClipStart > currentClipEnd) {\n      gaps.push({\n        startSample: currentClipEnd,\n        endSample: nextClipStart,\n        durationSamples: nextClipStart - currentClipEnd,\n      });\n    }\n  }\n\n  return gaps;\n}\n","/**\n * Peaks type - represents a typed array of interleaved min/max peak data\n */\nexport type Peaks = Int8Array | Int16Array;\n\n/**\n * Bits type - number of bits for peak data\n */\nexport type Bits = 8 | 16;\n\n/**\n * PeakData - result of peak extraction\n */\nexport interface PeakData {\n  /** Number of peak pairs extracted */\n  length: number;\n  /** Array of peak data for each channel (interleaved min/max) */\n  data: Peaks[];\n  /** Bit depth of peak data */\n  bits: Bits;\n}\n\nexport interface WaveformConfig {\n  sampleRate: number;\n  samplesPerPixel: number;\n  waveHeight?: number;\n  waveOutlineColor?: string;\n  waveFillColor?: string;\n  waveProgressColor?: string;\n}\n\nexport interface AudioBuffer {\n  length: number;\n  duration: number;\n  numberOfChannels: number;\n  sampleRate: number;\n  getChannelData(channel: number): Float32Array;\n}\n\nexport interface Track {\n  id: string;\n  name: string;\n  src?: string | AudioBuffer; // Support both URL strings and AudioBuffer objects\n  gain: number;\n  muted: boolean;\n  soloed: boolean;\n  stereoPan: number;\n  startTime: number;\n  endTime?: number;\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  cueIn?: number;\n  cueOut?: number;\n}\n\n/**\n * Simple fade configuration\n */\nexport interface Fade {\n  /** Duration of the fade in seconds */\n  duration: number;\n  /** Type of fade curve (default: 'linear') */\n  type?: FadeType;\n}\n\nexport type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential';\n\n/**\n * Alias for Fade — used by media-element-playout and playout packages\n */\nexport type FadeConfig = Fade;\n\nexport interface PlaylistConfig {\n  samplesPerPixel?: number;\n  waveHeight?: number;\n  container?: HTMLElement;\n  isAutomaticScroll?: boolean;\n  timescale?: boolean;\n  colors?: {\n    waveOutlineColor?: string;\n    waveFillColor?: string;\n    waveProgressColor?: string;\n  };\n  controls?: {\n    show?: boolean;\n    width?: number;\n  };\n  zoomLevels?: number[];\n}\n\nexport interface PlayoutState {\n  isPlaying: boolean;\n  isPaused: boolean;\n  cursor: number;\n  duration: number;\n}\n\nexport interface TimeSelection {\n  start: number;\n  end: number;\n}\n\nexport enum InteractionState {\n  Cursor = 'cursor',\n  Select = 'select',\n  Shift = 'shift',\n  FadeIn = 'fadein',\n  FadeOut = 'fadeout',\n}\n\n// Export clip-based model types\nexport * from './clip';\n\n// Export spectrogram types\nexport * from './spectrogram';\n\n// Export annotation types\nexport * from './annotations';\n","export function samplesToSeconds(samples: number, sampleRate: number): number {\n  return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number): number {\n  return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number): number {\n  return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number): number {\n  return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(\n  pixels: number,\n  samplesPerPixel: number,\n  sampleRate: number\n): number {\n  return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(\n  seconds: number,\n  samplesPerPixel: number,\n  sampleRate: number\n): number {\n  return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n","/** Default PPQN matching Tone.js Transport (192 ticks per quarter note) */\nexport const PPQN = 192;\n\n/** Number of PPQN ticks per beat for the given time signature. */\nexport function ticksPerBeat(timeSignature: [number, number], ppqn = PPQN): number {\n  const [, denominator] = timeSignature;\n  return ppqn * (4 / denominator);\n}\n\n/** Number of PPQN ticks per bar for the given time signature. */\nexport function ticksPerBar(timeSignature: [number, number], ppqn = PPQN): number {\n  const [numerator] = timeSignature;\n  return numerator * ticksPerBeat(timeSignature, ppqn);\n}\n\n/** Convert PPQN ticks to sample count. Uses Math.round for integer sample alignment. */\nexport function ticksToSamples(\n  ticks: number,\n  bpm: number,\n  sampleRate: number,\n  ppqn = PPQN\n): number {\n  return Math.round((ticks * 60 * sampleRate) / (bpm * ppqn));\n}\n\n/** Convert sample count to PPQN ticks. Inverse of ticksToSamples. */\nexport function samplesToTicks(\n  samples: number,\n  bpm: number,\n  sampleRate: number,\n  ppqn = PPQN\n): number {\n  return Math.round((samples * ppqn * bpm) / (60 * sampleRate));\n}\n\n/** Snap a tick position to the nearest grid line (rounds to nearest). */\nexport function snapToGrid(ticks: number, gridSizeTicks: number): number {\n  return Math.round(ticks / gridSizeTicks) * gridSizeTicks;\n}\n","const DEFAULT_FLOOR = -100;\n\n/**\n * Convert a dB value to a normalized range.\n *\n * Maps dB values linearly: floor → 0, 0 dB → 1.\n * Values above 0 dB map to > 1 (e.g., +5 dB → 1.05 with default floor).\n *\n * @param dB - Decibel value (typically -Infinity to +5)\n * @param floor - Minimum dB value mapped to 0. Default: -100 (Firefox compat)\n * @returns Normalized value (0 at floor, 1 at 0 dB, >1 above 0 dB)\n */\nexport function dBToNormalized(dB: number, floor: number = DEFAULT_FLOOR): number {\n  if (Number.isNaN(dB)) {\n    console.warn('[waveform-playlist] dBToNormalized received NaN');\n    return 0;\n  }\n  if (floor >= 0) {\n    console.warn('[waveform-playlist] dBToNormalized floor must be negative, got:', floor);\n    return 0;\n  }\n  if (!isFinite(dB) || dB <= floor) return 0;\n  return (dB - floor) / -floor;\n}\n\n/**\n * Convert a normalized value back to dB.\n *\n * Maps linearly: 0 → floor, 1 → 0 dB.\n * Values above 1 map to positive dB (e.g., 1.05 → +5 dB with default floor).\n *\n * @param normalized - Normalized value (0 = floor, 1 = 0 dB)\n * @param floor - Minimum dB value (maps from 0). Must be negative. Default: -100\n * @returns dB value (floor at 0, 0 dB at 1, positive dB above 1)\n */\nexport function normalizedToDb(normalized: number, floor: number = DEFAULT_FLOOR): number {\n  if (!isFinite(normalized)) return floor;\n  if (floor >= 0) {\n    console.warn('[waveform-playlist] normalizedToDb floor must be negative, got:', floor);\n    return DEFAULT_FLOOR;\n  }\n  const clamped = Math.max(0, normalized);\n  return clamped * -floor + floor;\n}\n\n/**\n * Convert a linear gain value to decibels.\n *\n * @param gain - Linear gain (0 = silence, 1 = unity)\n * @returns Decibel value (e.g., 0.5 → ≈ -6.02 dB)\n */\nexport function gainToDb(gain: number): number {\n  return 20 * Math.log10(Math.max(gain, 0.0001));\n}\n\n/**\n * Convert a linear gain value (0-1+) to normalized 0-1 via dB.\n *\n * Combines gain-to-dB (20 * log10) with dBToNormalized for a consistent\n * mapping from raw AudioWorklet peak/RMS values to the 0-1 range used\n * by UI meter components.\n *\n * @param gain - Linear gain value (typically 0 to 1, can exceed 1)\n * @param floor - Minimum dB value mapped to 0. Default: -100\n * @returns Normalized value (0 at silence/floor, 1 at 0 dB, >1 above 0 dB)\n */\nexport function gainToNormalized(gain: number, floor: number = DEFAULT_FLOOR): number {\n  if (gain <= 0) return 0;\n  // Use raw log10 (no clamp) — gain > 0 is guaranteed above,\n  // and dBToNormalized handles -Infinity via its floor check.\n  const db = 20 * Math.log10(gain);\n  return dBToNormalized(db, floor);\n}\n","import { ticksPerBeat, ticksPerBar } from './beatsAndBars';\nimport type { MeterEntry } from './meterDetection';\n\nexport type { MeterEntry } from './meterDetection';\n\n/** All supported snap-to-grid values. */\nexport type SnapTo =\n  | 'bar'\n  | 'beat'\n  | '1/2'\n  | '1/4'\n  | '1/8'\n  | '1/16'\n  | '1/32'\n  | '1/2T'\n  | '1/4T'\n  | '1/8T'\n  | '1/16T'\n  | 'off';\n\n/**\n * Returns the tick interval for the given SnapTo value.\n *\n * Straight subdivisions (1/2, 1/4, 1/8, 1/16, 1/32) are always expressed as\n * fractions of a quarter note (ppqn), independent of the time signature\n * denominator.  Triplet subdivisions use × 2/3 of the corresponding straight\n * value.  'bar' and 'beat' depend on the first meter entry's time signature.\n * 'off' returns 0.\n */\nexport function snapToTicks(snapTo: SnapTo, timeSignature: [number, number], ppqn = 960): number {\n  const ts = timeSignature;\n  switch (snapTo) {\n    case 'bar':\n      return ticksPerBar(ts, ppqn);\n    case 'beat':\n      return ticksPerBeat(ts, ppqn);\n    case '1/2':\n      return ppqn * 2;\n    case '1/4':\n      return ppqn;\n    case '1/8':\n      return ppqn / 2;\n    case '1/16':\n      return ppqn / 4;\n    case '1/32':\n      return ppqn / 8;\n    case '1/2T':\n      return Math.round((ppqn * 2 * 2) / 3);\n    case '1/4T':\n      return Math.round((ppqn * 2) / 3);\n    case '1/8T':\n      return Math.round((ppqn * 2) / 6);\n    case '1/16T':\n      return Math.round((ppqn * 2) / 12);\n    case 'off':\n      return 0;\n  }\n}\n\n/**\n * Three-tier tick hierarchy (following Audacity's model):\n *   major      — Bar boundaries. Always labeled, strongest grid lines.\n *   minor      — Beat boundaries. Labeled when wide enough, medium grid lines.\n *   minorMinor — Subdivisions (eighths, sixteenths). Never labeled, ruler ticks only (no grid).\n */\nexport type TickType = 'major' | 'minor' | 'minorMinor';\n\n/** Zoom level category used to select which subdivision to iterate at. */\nexport type ZoomLevel = 'coarse' | 'bar' | 'beat' | 'eighth' | 'sixteenth';\n\n/** A single musical tick with rendering metadata. */\nexport interface MusicalTick {\n  /** Pixel position of the tick in the timeline. */\n  pixel: number;\n  /** Three-tier type: major (bar), minor (beat), minorMinor (subdivision). */\n  type: TickType;\n  /** Human-readable label. Present for major ticks always; minor ticks when zoomed in. */\n  label?: string;\n  /** 0-based global bar index (for alternating bar-level striping). */\n  barIndex: number;\n}\n\n/** Result of computeMusicalTicks(). */\nexport interface MusicalTickData {\n  ticks: MusicalTick[];\n  pixelsPerQuarterNote: number;\n  zoomLevel: ZoomLevel;\n  /** At 'coarse' zoom: how many quarter notes between rendered tick lines. */\n  coarseQuarterNoteStep?: number;\n}\n\n/** Parameters for computeMusicalTicks(). */\nexport interface MusicalTickParams {\n  meterEntries: MeterEntry[];\n  /** Ticks per pixel (zoom level — lower value = more zoomed in). */\n  ticksPerPixel: number;\n  startPixel: number;\n  endPixel: number;\n  /** Pulses per quarter note. Defaults to 960. */\n  ppqn?: number;\n}\n\n/** Minimum pixels per musical unit before switching to a coarser zoom level. */\nexport const MIN_PIXELS_PER_UNIT = 8;\n\n/** Minimum pixels between beat labels for readable text. */\nconst MIN_PIXELS_PER_LABEL = 60;\n\n/**\n * Determines the zoom level and computes which tick lines to render for a\n * given viewport. Pure tick arithmetic — no BPM or sample rate required.\n *\n * Walks meter entries in segments, so bar/beat boundaries and labels are\n * correct across meter changes.\n */\nexport function computeMusicalTicks(params: MusicalTickParams): MusicalTickData {\n  const { meterEntries, ticksPerPixel, startPixel, endPixel, ppqn = 960 } = params;\n\n  const firstMeter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };\n\n  // Guard against invalid inputs that would cause division by zero or infinite loops\n  if (ticksPerPixel <= 0 || ppqn <= 0 || firstMeter.denominator <= 0) {\n    return { ticks: [], pixelsPerQuarterNote: 0, zoomLevel: 'coarse' };\n  }\n\n  // pixelsPerQuarterNote is constant across meters — only depends on ppqn and zoom\n  const pixelsPerQuarterNote = ppqn / ticksPerPixel;\n\n  // All zoom thresholds derived from pixelsPerQuarterNote (meter-independent).\n  // Quarter note subdivisions: half = 2×, eighth = 0.5×, sixteenth = 0.25×.\n  // \"Bar\" threshold uses 4× quarter note (a 4/4 bar) — conservative minimum.\n  const tpEighth = ppqn / 2;\n  const tpSixteenth = ppqn / 4;\n  const pixelsPerEighth = tpEighth / ticksPerPixel;\n  const pixelsPerSixteenth = tpSixteenth / ticksPerPixel;\n\n  let zoomLevel: ZoomLevel;\n  if (pixelsPerQuarterNote * 4 < MIN_PIXELS_PER_UNIT) {\n    zoomLevel = 'coarse';\n  } else if (pixelsPerQuarterNote < MIN_PIXELS_PER_UNIT) {\n    zoomLevel = 'bar';\n  } else if (pixelsPerEighth < MIN_PIXELS_PER_UNIT) {\n    zoomLevel = 'beat';\n  } else if (pixelsPerSixteenth < MIN_PIXELS_PER_UNIT) {\n    zoomLevel = 'eighth';\n  } else {\n    zoomLevel = 'sixteenth';\n  }\n\n  // Coarse step in quarter notes — meter-independent so all segments\n  // render at the same visual density regardless of time signature.\n  let coarseQuarterNoteStep: number | undefined;\n  let coarseQuarterNotes = 0;\n  if (zoomLevel === 'coarse') {\n    coarseQuarterNotes = 2;\n    while ((coarseQuarterNotes * ppqn) / ticksPerPixel < MIN_PIXELS_PER_UNIT) {\n      coarseQuarterNotes *= 2;\n    }\n    coarseQuarterNoteStep = coarseQuarterNotes;\n  }\n\n  const startTick = startPixel * ticksPerPixel;\n  const endTick = endPixel * ticksPerPixel;\n\n  // Build the list of meter segments: [start, end) in ticks, with per-segment bar offset\n  const segments: Array<{\n    segmentStartTick: number;\n    segmentEndTick: number;\n    meter: MeterEntry;\n    barOffset: number; // cumulative bar count before this segment\n  }> = [];\n\n  {\n    let cumulativeBars = 0;\n    for (let i = 0; i < meterEntries.length; i++) {\n      const meter = meterEntries[i];\n      const segmentStart = meter.tick;\n      const segmentEnd =\n        i + 1 < meterEntries.length ? meterEntries[i + 1].tick : Number.MAX_SAFE_INTEGER;\n\n      const ts: [number, number] = [meter.numerator, meter.denominator];\n      const tpBar = ticksPerBar(ts, ppqn);\n\n      segments.push({\n        segmentStartTick: segmentStart,\n        segmentEndTick: segmentEnd,\n        meter,\n        barOffset: cumulativeBars,\n      });\n\n      // Count how many whole bars fit in this segment (for finite segments)\n      if (segmentEnd !== Number.MAX_SAFE_INTEGER) {\n        const segmentLen = segmentEnd - segmentStart;\n        cumulativeBars += Math.floor(segmentLen / tpBar);\n      }\n    }\n  }\n\n  const ticks: MusicalTick[] = [];\n\n  // Walk each meter segment and emit ticks within the visible range\n  for (const { segmentStartTick, segmentEndTick, meter, barOffset } of segments) {\n    const ts: [number, number] = [meter.numerator, meter.denominator];\n    const tpBeat = ticksPerBeat(ts, ppqn);\n    const tpBar = ticksPerBar(ts, ppqn);\n\n    // Determine step size for this segment\n    let stepTicks: number;\n    if (zoomLevel === 'coarse') {\n      stepTicks = coarseQuarterNotes * ppqn;\n    } else if (zoomLevel === 'bar') {\n      stepTicks = tpBar;\n    } else if (zoomLevel === 'beat') {\n      stepTicks = tpBeat;\n    } else if (zoomLevel === 'eighth') {\n      stepTicks = tpEighth;\n    } else {\n      stepTicks = tpSixteenth;\n    }\n\n    // Find first step within this segment that is >= startTick\n    // Steps are aligned to segmentStartTick\n    const segmentTickStart = Math.max(segmentStartTick, startTick);\n    const segmentTickEnd = Math.min(segmentEndTick - 1, endTick);\n\n    if (segmentTickStart > segmentTickEnd) {\n      continue;\n    }\n\n    // First step: align to step boundary relative to segment start\n    const offsetIntoSegment = segmentTickStart - segmentStartTick;\n    const firstStepOffset = Math.floor(offsetIntoSegment / stepTicks) * stepTicks;\n    const firstStepTick = segmentStartTick + firstStepOffset;\n\n    for (\n      let tick = firstStepTick;\n      tick <= segmentTickEnd && tick < segmentEndTick;\n      tick += stepTicks\n    ) {\n      const pixel = tick / ticksPerPixel;\n\n      if (pixel < startPixel || pixel > endPixel) {\n        continue;\n      }\n\n      // Classify into three-tier hierarchy\n      const tickOffsetInSegment = tick - segmentStartTick;\n      let type: TickType;\n      if (tickOffsetInSegment % tpBar === 0) {\n        type = 'major';\n      } else if (tickOffsetInSegment % tpBeat === 0) {\n        type = 'minor';\n      } else {\n        type = 'minorMinor';\n      }\n\n      // Cumulative bar index for zebra striping\n      const barIndexInSegment = Math.floor(tickOffsetInSegment / tpBar);\n      const barIndex = barOffset + barIndexInSegment;\n\n      // Labels: major always, minor only when wide enough, minorMinor never\n      let label: string | undefined;\n      if (type === 'major') {\n        label = `${barIndex + 1}`;\n      } else if (type === 'minor' && tpBeat / ticksPerPixel >= MIN_PIXELS_PER_LABEL) {\n        const beatInBar = Math.floor((tickOffsetInSegment % tpBar) / tpBeat) + 1;\n        label = `${barIndex + 1}.${beatInBar}`;\n      }\n\n      ticks.push({ pixel, type, barIndex, ...(label !== undefined ? { label } : {}) });\n    }\n  }\n\n  // Sort by pixel (segments are ordered, but floating point steps may cause slight reordering)\n  ticks.sort((a, b) => a.pixel - b.pixel);\n\n  const result: MusicalTickData = {\n    ticks,\n    pixelsPerQuarterNote,\n    zoomLevel,\n    ...(coarseQuarterNoteStep !== undefined ? { coarseQuarterNoteStep } : {}),\n  };\n\n  return result;\n}\n\n/**\n * Convert an absolute tick to a 1-based bar.beat position, honoring meter\n * changes. Bars/beats are counted per meter segment (the same walk\n * `computeMusicalTicks` uses to build its `barOffset` accumulator). Beat is\n * the integer beat the tick falls WITHIN (floor, 1-based) — a tick exactly on\n * a bar line is beat 1.\n *\n * Guards: empty `meterEntries` → treat as 4/4 from tick 0. `ppqn <= 0` →\n * returns `{ bar: 1, beat: 1 }` (division-by-zero guard, matches\n * `computeMusicalTicks`'s ppqn guard).\n */\nexport function ticksToBarBeat(\n  tick: number,\n  meterEntries: MeterEntry[],\n  ppqn: number\n): { bar: number; beat: number } {\n  if (ppqn <= 0) return { bar: 1, beat: 1 };\n\n  const entries =\n    meterEntries.length > 0 ? meterEntries : [{ tick: 0, numerator: 4, denominator: 4 }];\n\n  let barOffset = 0;\n  for (let i = 0; i < entries.length; i++) {\n    const meter = entries[i];\n    const segmentStart = meter.tick;\n    const segmentEnd = i + 1 < entries.length ? entries[i + 1].tick : Number.MAX_SAFE_INTEGER;\n    const ts: [number, number] = [meter.numerator, meter.denominator];\n    const tpBar = ticksPerBar(ts, ppqn);\n    const tpBeat = ticksPerBeat(ts, ppqn);\n\n    if (tick >= segmentStart && tick < segmentEnd) {\n      const offset = tick - segmentStart;\n      const barIndexInSegment = Math.floor(offset / tpBar);\n      const beatInBar = Math.floor((offset % tpBar) / tpBeat);\n      return { bar: barOffset + barIndexInSegment + 1, beat: beatInBar + 1 };\n    }\n\n    if (segmentEnd !== Number.MAX_SAFE_INTEGER) {\n      const segmentLen = segmentEnd - segmentStart;\n      barOffset += Math.floor(segmentLen / tpBar);\n    }\n  }\n\n  // tick precedes the first meter entry (non-standard input) — fall back to\n  // the first meter's math from its own start tick.\n  const first = entries[0];\n  const ts: [number, number] = [first.numerator, first.denominator];\n  const tpBar = ticksPerBar(ts, ppqn);\n  const tpBeat = ticksPerBeat(ts, ppqn);\n  const offset = tick - first.tick;\n  const barIndexInSegment = Math.floor(offset / tpBar);\n  const beatInBar = Math.floor((offset % tpBar) / tpBeat);\n  return { bar: barIndexInSegment + 1, beat: beatInBar + 1 };\n}\n\n/**\n * Snaps a tick position to the nearest grid boundary defined by `snapTo`.\n *\n * Finds the meter entry active at the tick position and snaps relative to\n * that meter's segment start.\n *\n * Returns the original tick unchanged when `snapTo` is 'off'.\n */\nexport function snapTickToGrid(\n  tick: number,\n  snapTo: SnapTo,\n  meterEntries: MeterEntry[],\n  ppqn = 960\n): number {\n  if (snapTo === 'off') return tick;\n\n  // Find the active meter entry for this tick\n  let meter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };\n  for (const entry of meterEntries) {\n    if (entry.tick <= tick) {\n      meter = entry;\n    } else {\n      break;\n    }\n  }\n\n  const ts: [number, number] = [meter.numerator, meter.denominator];\n  const gridSize = snapToTicks(snapTo, ts, ppqn);\n  if (gridSize <= 0) return tick;\n\n  // Snap relative to the meter's start tick\n  const offset = tick - meter.tick;\n  return meter.tick + Math.round(offset / gridSize) * gridSize;\n}\n","export interface MeterEntry {\n  tick: number;\n  numerator: number;\n  denominator: number;\n}\n\n/**\n * Scans a beat number sequence and detects meter (time signature) changes.\n *\n * Each beat in the input has a `beat` number (1-indexed). When the beat resets\n * to 1, we count how many beats were in the previous bar and derive the numerator.\n *\n * @param beats - Array of beat events with `time` (seconds) and `beat` (1-indexed number).\n * @param firstBeatTick - The tick position of beats[0] on the timeline.\n * @param ppqn - Ticks per quarter note (ticks per beat).\n * @returns Array of MeterEntry sorted by tick. Always includes an entry at tick 0.\n */\nexport function detectMeterChanges(\n  beats: { time: number; beat: number }[],\n  firstBeatTick: number,\n  ppqn: number\n): MeterEntry[] {\n  const DEFAULT_NUMERATOR = 4;\n  const DENOMINATOR = 4;\n\n  const defaultResult: MeterEntry[] = [\n    { tick: 0, numerator: DEFAULT_NUMERATOR, denominator: DENOMINATOR },\n  ];\n\n  if (beats.length === 0) {\n    return defaultResult;\n  }\n\n  // Find the index of the first downbeat (beat === 1)\n  const firstDownbeatIndex = beats.findIndex((b) => b.beat === 1);\n\n  if (firstDownbeatIndex === -1) {\n    // No downbeat found — default to 4/4\n    return defaultResult;\n  }\n\n  // Collect completed bars: for each bar, record its start beat index and beat count\n  const bars: { beatIndex: number; count: number }[] = [];\n  let barStartBeatIndex = firstDownbeatIndex;\n\n  for (let i = firstDownbeatIndex + 1; i < beats.length; i++) {\n    if (beats[i].beat === 1) {\n      bars.push({ beatIndex: barStartBeatIndex, count: i - barStartBeatIndex });\n      barStartBeatIndex = i;\n    }\n  }\n\n  if (bars.length === 0) {\n    // Only one downbeat observed — can't determine meter\n    return defaultResult;\n  }\n\n  // Build raw meter entries: emit a new entry only when numerator changes\n  const rawEntries: MeterEntry[] = [];\n  let prevNumerator = -1;\n\n  for (const bar of bars) {\n    const numerator = bar.count;\n    if (numerator !== prevNumerator) {\n      const tick = firstBeatTick + bar.beatIndex * ppqn;\n      rawEntries.push({ tick, numerator, denominator: DENOMINATOR });\n      prevNumerator = numerator;\n    }\n  }\n\n  // Ensure tick 0 is always present\n  if (rawEntries.length === 0) {\n    return defaultResult;\n  }\n\n  if (rawEntries[0].tick === 0) {\n    return rawEntries;\n  }\n\n  // First entry is after tick 0 — prepend tick 0 with the same numerator\n  const tick0Entry: MeterEntry = {\n    tick: 0,\n    numerator: rawEntries[0].numerator,\n    denominator: DENOMINATOR,\n  };\n\n  // If the first raw entry has the same numerator as tick 0, we don't need it as a\n  // separate entry (they'd be adjacent duplicates). Drop the first raw entry and use\n  // the tick-0 entry to represent it.\n  const rest = rawEntries[0].numerator === tick0Entry.numerator ? rawEntries.slice(1) : rawEntries;\n\n  return [tick0Entry, ...rest];\n}\n","/**\n * Peak generation for real-time waveform visualization during recording.\n * Matches the format used by webaudio-peaks: min/max pairs with bit depth.\n */\n\n/**\n * Generate peaks from audio samples in standard min/max pair format.\n *\n * @param samples - Audio samples to process\n * @param samplesPerPixel - Number of samples to represent in each peak\n * @param bits - Bit depth for peak values (8 or 16)\n * @returns Int8Array or Int16Array of peak values (min/max pairs)\n */\nexport function generatePeaks(\n  samples: Float32Array,\n  samplesPerPixel: number,\n  bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n  const numPeaks = Math.ceil(samples.length / samplesPerPixel);\n  const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n  const maxValue = 2 ** (bits - 1);\n\n  for (let i = 0; i < numPeaks; i++) {\n    const start = i * samplesPerPixel;\n    const end = Math.min(start + samplesPerPixel, samples.length);\n\n    let min = 0;\n    let max = 0;\n\n    for (let j = start; j < end; j++) {\n      const value = samples[j];\n      if (value < min) min = value;\n      if (value > max) max = value;\n    }\n\n    // Store as min/max pairs scaled to bit depth\n    // Clamp to valid range: Int16 is [-32768, 32767], Int8 is [-128, 127]\n    peakArray[i * 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n    peakArray[i * 2 + 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n  }\n\n  return peakArray;\n}\n\n/**\n * Append new peaks to existing peaks array.\n * This is used for incremental peak updates during recording.\n */\nexport function appendPeaks(\n  existingPeaks: Int8Array | Int16Array,\n  newSamples: Float32Array,\n  samplesPerPixel: number,\n  totalSamplesProcessed: number,\n  bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n  const maxValue = 2 ** (bits - 1);\n\n  // Check if we have a partial peak from the last update\n  const remainder = totalSamplesProcessed % samplesPerPixel;\n  let offset = 0;\n\n  // If there's a partial peak, we need to update the last peak\n  if (remainder > 0 && existingPeaks.length > 0) {\n    const samplesToComplete = samplesPerPixel - remainder;\n    const endIndex = Math.min(samplesToComplete, newSamples.length);\n\n    // Get current min/max from last peak\n    let min = existingPeaks[existingPeaks.length - 2] / maxValue;\n    let max = existingPeaks[existingPeaks.length - 1] / maxValue;\n\n    // Update with new samples\n    for (let i = 0; i < endIndex; i++) {\n      const value = newSamples[i];\n      if (value < min) min = value;\n      if (value > max) max = value;\n    }\n\n    // Update last peak\n    const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);\n    updated.set(existingPeaks);\n    updated[existingPeaks.length - 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n    updated[existingPeaks.length - 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n\n    offset = endIndex;\n\n    // Generate peaks for remaining samples and concatenate\n    const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n    const result = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks.length);\n    result.set(updated);\n    result.set(newPeaks, updated.length);\n    return result;\n  }\n\n  // No partial peak, just concatenate\n  const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n  const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);\n  result.set(existingPeaks);\n  result.set(newPeaks, existingPeaks.length);\n  return result;\n}\n","/**\n * Utility functions for working with AudioBuffers during recording\n */\n\n/**\n * Concatenate multiple Float32Arrays into a single array\n */\nexport function concatenateAudioData(chunks: Float32Array[]): Float32Array {\n  const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n  const result = new Float32Array(totalLength);\n\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(chunk, offset);\n    offset += chunk.length;\n  }\n\n  return result;\n}\n\n/**\n * Convert channel data to AudioBuffer.\n * Accepts either per-channel Float32Array[] or a single Float32Array (mono, backwards compatible).\n */\nexport function createAudioBuffer(\n  audioContext: AudioContext,\n  channelData: Float32Array[] | Float32Array,\n  sampleRate: number,\n  channelCount: number = 1\n): AudioBuffer {\n  // Backwards compatibility: single Float32Array → wrap as mono\n  const channels: Float32Array[] =\n    channelData instanceof Float32Array ? [channelData] : channelData;\n\n  const length = channels[0]?.length ?? 0;\n  const buffer = audioContext.createBuffer(channelCount, length, sampleRate);\n\n  for (let ch = 0; ch < Math.min(channelCount, channels.length); ch++) {\n    buffer.copyToChannel(new Float32Array(channels[ch]), ch);\n  }\n\n  return buffer;\n}\n\n/**\n * Append new samples to an existing AudioBuffer (mono convenience)\n */\nexport function appendToAudioBuffer(\n  audioContext: AudioContext,\n  existingBuffer: AudioBuffer | null,\n  newSamples: Float32Array,\n  sampleRate: number\n): AudioBuffer {\n  if (!existingBuffer) {\n    return createAudioBuffer(audioContext, [newSamples], sampleRate);\n  }\n\n  // Get existing samples\n  const existingData = existingBuffer.getChannelData(0);\n\n  // Concatenate using concatenateAudioData helper\n  const combined = concatenateAudioData([existingData, newSamples]);\n\n  // Create new buffer\n  return createAudioBuffer(audioContext, [combined], sampleRate);\n}\n\n/**\n * Calculate duration in seconds from sample count and sample rate\n */\nexport function calculateDuration(sampleCount: number, sampleRate: number): number {\n  return sampleCount / sampleRate;\n}\n","/**\n * Sample count corresponding to the audible-latency window\n * (`outputLatency` + scheduler `lookAhead`). Used to skip leading silence in\n * recordings: between record-start and audible playback the user heard nothing,\n * so that prefix should be trimmed from buffers / peaks. Returns 0 for\n * non-finite or non-positive inputs.\n *\n * Single source of truth for `useIntegratedRecording` (buffer trim) and\n * `PlaylistVisualization` (live preview peak slice). Keep the two in lockstep\n * so the live preview width matches the finalized clip.\n */\nexport function audibleLatencySamples(\n  outputLatency: number,\n  lookAhead: number,\n  sampleRate: number\n): number {\n  const total = outputLatency + lookAhead;\n  if (!Number.isFinite(total) || !Number.isFinite(sampleRate)) return 0;\n  if (total <= 0 || sampleRate <= 0) return 0;\n  return Math.floor(total * sampleRate);\n}\n\n/**\n * Resolve the recording latency offset in samples.\n *\n * When `overrideSeconds` is provided it is an **absolute replacement** for the\n * auto-computed value — a latency in seconds, converted at `sampleRate`\n * (`0` disables compensation; negative/non-finite resolve to `0`). Otherwise the\n * offset is the auto-computed audible-latency window (`outputLatency + lookAhead`).\n *\n * Single source of truth for the override-vs-auto decision across dawcore\n * (`RecordingController`) and React (`useIntegratedRecording` finalization +\n * `PlaylistVisualization` live preview). The override branch is the same math as\n * the auto branch with `lookAhead = 0`, so both inherit the finite/positive\n * guards in `audibleLatencySamples`.\n */\nexport function resolveRecordingOffsetSamples(params: {\n  /** Public override (seconds). Absolute replacement when defined. */\n  overrideSeconds?: number;\n  /** Browser-reported output latency (seconds). */\n  outputLatency: number;\n  /** Scheduler look-ahead (seconds). Pass 0 for engines without one (native transport). */\n  lookAhead: number;\n  /** Sample rate the recording was captured at. */\n  sampleRate: number;\n}): number {\n  const { overrideSeconds, outputLatency, lookAhead, sampleRate } = params;\n  return overrideSeconds !== undefined\n    ? audibleLatencySamples(overrideSeconds, 0, sampleRate)\n    : audibleLatencySamples(outputLatency, lookAhead, sampleRate);\n}\n","import type { SpectrogramConfig, ColorMapValue } from '../types/spectrogram';\n\n/**\n * Default values for `SpectrogramConfig` fields. Used by both the orchestrator\n * and the React controller so defaults can't drift between layers.\n *\n * Intentionally omitted:\n * - `maxFrequency` — defaults to `sampleRate / 2` at compute time (clip-dependent)\n * - `alpha` — window-specific (only used by the `hamming` window function); its\n *   canonical default (0.54) lives in `windowFunctions.ts` where the math is\n * - `labelsColor`, `labelsBackground` — kept as optional `undefined` so consumers\n *   can opt into label styling without forcing a color value\n */\nexport const SPECTROGRAM_DEFAULTS: Required<\n  Omit<SpectrogramConfig, 'maxFrequency' | 'alpha' | 'labelsColor' | 'labelsBackground'>\n> & {\n  labelsColor: string | undefined;\n  labelsBackground: string | undefined;\n} = {\n  fftSize: 2048,\n  hopSize: 512,\n  windowFunction: 'hann',\n  frequencyScale: 'mel',\n  minFrequency: 0,\n  gainDb: 20,\n  rangeDb: 80,\n  zeroPaddingFactor: 2,\n  labels: false,\n  labelsColor: undefined,\n  labelsBackground: undefined,\n};\n\n/** Default color map when none is specified. */\nexport const DEFAULT_SPECTROGRAM_COLOR_MAP: ColorMapValue = 'viridis';\n","/**\n * Punch-in replace: carve a sample range out of a clip list.\n *\n * Used when a newly recorded clip lands at the playhead and must REPLACE any\n * existing clip content between its start and end (issue #579): partial\n * overlaps are trimmed, fully-covered clips are removed, and a clip that\n * spans the whole range is split into two.\n */\n\nimport type { AudioClip } from '../types/clip';\n\n/**\n * Return a new clip list with the sample range [rangeStart, rangeEnd)\n * removed from every clip that overlaps it.\n *\n * - Clips outside the range are returned by reference (untouched).\n * - Clips fully inside the range are dropped.\n * - A clip overlapping only the range's start keeps its head (right-trim).\n * - A clip overlapping only the range's end keeps its tail: its start moves\n *   to rangeEnd and `offsetSamples` advances by the carved amount.\n * - A clip containing the whole range splits into head + tail; the tail is a\n *   new clip (deterministic id `<original>-carve-<rangeEnd>`) sharing the\n *   same audioBuffer.\n *\n * Clips whose `startSample` changes lose their `startTick` — a sample-space\n * carve cannot recompute ticks, and the engine re-enriches clips without\n * `startTick` on ingestion (see AudioClip.startTick).\n *\n * Pure and immutable: input clips are never mutated. An empty or inverted\n * range returns the input list unchanged.\n */\nexport function carveClipRange(\n  clips: readonly AudioClip[],\n  rangeStart: number,\n  rangeEnd: number\n): AudioClip[] {\n  if (!(rangeEnd > rangeStart)) {\n    return [...clips];\n  }\n\n  const result: AudioClip[] = [];\n\n  for (const clip of clips) {\n    const clipStart = clip.startSample;\n    const clipEnd = clip.startSample + clip.durationSamples;\n\n    // [start, end) overlap test — shared boundaries don't overlap\n    if (clipEnd <= rangeStart || clipStart >= rangeEnd) {\n      result.push(clip);\n      continue;\n    }\n\n    const keepHead = clipStart < rangeStart;\n    const keepTail = clipEnd > rangeEnd;\n\n    if (keepHead) {\n      result.push({\n        ...clip,\n        durationSamples: rangeStart - clipStart,\n      });\n    }\n\n    if (keepTail) {\n      const carvedFromClipStart = rangeEnd - clipStart;\n      const tail: AudioClip = {\n        ...clip,\n        // Head + tail from one clip must not share an id\n        id: keepHead ? `${clip.id}-carve-${rangeEnd}` : clip.id,\n        startSample: rangeEnd,\n        durationSamples: clipEnd - rangeEnd,\n        offsetSamples: clip.offsetSamples + carvedFromClipStart,\n      };\n      // startSample changed — the stale authoritative tick position must not\n      // survive (the engine re-derives it from startSample on ingestion).\n      delete tail.startTick;\n      result.push(tail);\n    }\n\n    // Neither head nor tail: fully covered, dropped.\n  }\n\n  return result;\n}\n","import type { AudioClip, ClipTrack } from './types';\n\n/** Clip start position in seconds */\nexport function clipStartTime(clip: AudioClip): number {\n  return clip.startSample / clip.sampleRate;\n}\n\n/** Clip end position in seconds (start + duration) */\nexport function clipEndTime(clip: AudioClip): number {\n  return (clip.startSample + clip.durationSamples) / clip.sampleRate;\n}\n\n/** Clip offset into source audio in seconds */\nexport function clipOffsetTime(clip: AudioClip): number {\n  return clip.offsetSamples / clip.sampleRate;\n}\n\n/** Clip duration in seconds */\nexport function clipDurationTime(clip: AudioClip): number {\n  return clip.durationSamples / clip.sampleRate;\n}\n\n/**\n * Max audio channel count across a track's clips.\n * Used to set Panner channelCount and offline render output channels.\n */\nexport function trackChannelCount(track: ClipTrack): number {\n  return track.clips.reduce(\n    (max, clip) => Math.max(max, clip.audioBuffer?.numberOfChannels ?? 1),\n    1\n  );\n}\n\n/**\n * Clip width in pixels at a given samplesPerPixel.\n * Shared by Clip.tsx (container sizing) and ChannelWithProgress.tsx (progress overlay)\n * to ensure pixel-perfect alignment. Floor-based endpoint subtraction guarantees\n * adjacent clips have no pixel gaps.\n */\nexport function clipPixelWidth(\n  startSample: number,\n  durationSamples: number,\n  samplesPerPixel: number\n): number {\n  return (\n    Math.floor((startSample + durationSamples) / samplesPerPixel) -\n    Math.floor(startSample / samplesPerPixel)\n  );\n}\n","/**\n * Canonical spectrogram canvas-ID contract.\n *\n * Spectrogram canvases are identified by `${clipId}-ch${channelIndex}-chunk${chunkIndex}`.\n * This format is produced (builders) and consumed (parsers) by several packages\n * — the React `SpectrogramProvider`/`SpectrogramChannel`, the `<daw-spectrogram>`\n * Lit element, and the `@dawcore/spectrogram` worker pool. Single-sourcing the\n * build + parse here prevents the drift class where a format change lands in some\n * sites but not others (the pool's no-match fallback then routes every channel to\n * worker 0 → channel 0's data rendered for all channels — #556).\n *\n * `@waveform-playlist/core` is the home because it is zero-dependency and already\n * a dependency of every producer and consumer (unlike `@dawcore/spectrogram`,\n * which `@waveform-playlist/ui-components` does not depend on). The format is a\n * pure string contract with no FFT/compute dependency.\n */\n\nexport interface SpectrogramCanvasIdParts {\n  clipId: string;\n  channelIndex: number;\n  chunkIndex: number;\n}\n\n/**\n * Anchored to the trailing `-ch{N}-chunk{M}` segment. The greedy first group lets\n * the clip ID contain hyphens — and even a `-ch{N}-chunk{M}`-looking substring —\n * without misrouting: it always binds to the LAST such segment.\n */\nconst CANVAS_ID_RE = /^(.+)-ch(\\d+)-chunk(\\d+)$/;\n\n/** Build a spectrogram canvas ID from its parts. */\nexport function buildSpectrogramCanvasId(parts: SpectrogramCanvasIdParts): string {\n  return `${parts.clipId}-ch${parts.channelIndex}-chunk${parts.chunkIndex}`;\n}\n\n/**\n * Parse a spectrogram canvas ID into its parts, or `null` when it doesn't match\n * the `${clipId}-ch${channelIndex}-chunk${chunkIndex}` format.\n */\nexport function parseSpectrogramCanvasId(canvasId: string): SpectrogramCanvasIdParts | null {\n  const match = canvasId.match(CANVAS_ID_RE);\n  if (!match) return null;\n  return {\n    clipId: match[1],\n    channelIndex: parseInt(match[2], 10),\n    chunkIndex: parseInt(match[3], 10),\n  };\n}\n","/**\n * Fade curve utilities for Web Audio API\n *\n * Pure functions that generate fade curves and apply them to AudioParam.\n * No Tone.js dependency — works with native Web Audio nodes.\n */\n\nimport type { FadeType } from './types';\n\n/**\n * Generate a linear fade curve\n */\nexport function linearCurve(length: number, fadeIn: boolean): Float32Array {\n  const curve = new Float32Array(length);\n  const scale = length - 1;\n\n  for (let i = 0; i < length; i++) {\n    const x = i / scale;\n    curve[i] = fadeIn ? x : 1 - x;\n  }\n\n  return curve;\n}\n\n/**\n * Generate an exponential fade curve\n */\nexport function exponentialCurve(length: number, fadeIn: boolean): Float32Array {\n  const curve = new Float32Array(length);\n  const scale = length - 1;\n\n  for (let i = 0; i < length; i++) {\n    const x = i / scale;\n    const index = fadeIn ? i : length - 1 - i;\n    curve[index] = Math.exp(2 * x - 1) / Math.E;\n  }\n\n  return curve;\n}\n\n/**\n * Generate an S-curve (sine-based smooth curve)\n */\nexport function sCurveCurve(length: number, fadeIn: boolean): Float32Array {\n  const curve = new Float32Array(length);\n  const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;\n\n  for (let i = 0; i < length; i++) {\n    curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n  }\n\n  return curve;\n}\n\n/**\n * Generate a logarithmic fade curve\n */\nexport function logarithmicCurve(length: number, fadeIn: boolean, base: number = 10): Float32Array {\n  const curve = new Float32Array(length);\n\n  for (let i = 0; i < length; i++) {\n    const index = fadeIn ? i : length - 1 - i;\n    const x = i / length;\n    curve[index] = Math.log(1 + base * x) / Math.log(1 + base);\n  }\n\n  return curve;\n}\n\n/**\n * Generate a fade curve of the specified type\n */\nexport function generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array {\n  switch (type) {\n    case 'linear':\n      return linearCurve(length, fadeIn);\n    case 'exponential':\n      return exponentialCurve(length, fadeIn);\n    case 'sCurve':\n      return sCurveCurve(length, fadeIn);\n    case 'logarithmic':\n      return logarithmicCurve(length, fadeIn);\n    default:\n      return linearCurve(length, fadeIn);\n  }\n}\n\n/**\n * Apply a fade in to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 0)\n * @param endValue - Ending value (default: 1)\n */\nexport function applyFadeIn(\n  param: AudioParam,\n  startTime: number,\n  duration: number,\n  type: FadeType = 'linear',\n  startValue: number = 0,\n  endValue: number = 1\n): void {\n  if (duration <= 0) return;\n\n  if (type === 'linear') {\n    param.setValueAtTime(startValue, startTime);\n    param.linearRampToValueAtTime(endValue, startTime + duration);\n  } else if (type === 'exponential') {\n    param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n    param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n  } else {\n    const curve = generateCurve(type, 10000, true);\n    const scaledCurve = new Float32Array(curve.length);\n    const range = endValue - startValue;\n    for (let i = 0; i < curve.length; i++) {\n      scaledCurve[i] = startValue + curve[i] * range;\n    }\n    param.setValueCurveAtTime(scaledCurve, startTime, duration);\n  }\n}\n\n/**\n * Apply a fade out to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 1)\n * @param endValue - Ending value (default: 0)\n */\nexport function applyFadeOut(\n  param: AudioParam,\n  startTime: number,\n  duration: number,\n  type: FadeType = 'linear',\n  startValue: number = 1,\n  endValue: number = 0\n): void {\n  if (duration <= 0) return;\n\n  if (type === 'linear') {\n    param.setValueAtTime(startValue, startTime);\n    param.linearRampToValueAtTime(endValue, startTime + duration);\n  } else if (type === 'exponential') {\n    param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n    param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n  } else {\n    const curve = generateCurve(type, 10000, false);\n    const scaledCurve = new Float32Array(curve.length);\n    const range = startValue - endValue;\n    for (let i = 0; i < curve.length; i++) {\n      scaledCurve[i] = endValue + curve[i] * range;\n    }\n    param.setValueCurveAtTime(scaledCurve, startTime, duration);\n  }\n}\n","/**\n * Framework-agnostic keyboard shortcut handling.\n * Used by both React (useKeyboardShortcuts) and Web Components (daw-editor).\n */\n\nexport interface KeyboardShortcut {\n  key: string;\n  ctrlKey?: boolean;\n  shiftKey?: boolean;\n  metaKey?: boolean;\n  altKey?: boolean;\n  action: () => void;\n  description?: string;\n  preventDefault?: boolean;\n}\n\n/** A key + modifier combination, without an action. Used for remapping maps. */\nexport type KeyBinding = Pick<\n  KeyboardShortcut,\n  'key' | 'ctrlKey' | 'shiftKey' | 'metaKey' | 'altKey'\n>;\n\n/**\n * Does a keyboard event match a key binding?\n * `undefined` modifier = match any state; `false` = must NOT be pressed.\n */\nexport function matchesKeyBinding(event: KeyboardEvent, binding: KeyBinding): boolean {\n  const keyMatch =\n    event.key.toLowerCase() === binding.key.toLowerCase() || event.key === binding.key;\n  const ctrlMatch = binding.ctrlKey === undefined || event.ctrlKey === binding.ctrlKey;\n  const shiftMatch = binding.shiftKey === undefined || event.shiftKey === binding.shiftKey;\n  const metaMatch = binding.metaKey === undefined || event.metaKey === binding.metaKey;\n  const altMatch = binding.altKey === undefined || event.altKey === binding.altKey;\n  return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;\n}\n\n/**\n * Handle a keyboard event against a list of shortcuts.\n * Pure function, no framework dependency.\n */\nexport function handleKeyboardEvent(\n  event: KeyboardEvent,\n  shortcuts: KeyboardShortcut[],\n  enabled: boolean\n): void {\n  if (!enabled) return;\n\n  // Ignore key repeat events — holding a key fires keydown repeatedly.\n  // Without this guard, holding Space rapidly toggles play/pause.\n  if (event.repeat) return;\n\n  const target = event.target as HTMLElement;\n  if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n    return;\n  }\n\n  const matchingShortcut = shortcuts.find((shortcut) => matchesKeyBinding(event, shortcut));\n\n  if (matchingShortcut) {\n    if (matchingShortcut.preventDefault !== false) {\n      event.preventDefault();\n    }\n    matchingShortcut.action();\n  }\n}\n\n/**\n * Get a human-readable string representation of a keyboard shortcut.\n *\n * @param shortcut - The keyboard shortcut\n * @returns Human-readable string (e.g., \"Cmd+Shift+S\")\n */\nexport const getShortcutLabel = (shortcut: KeyboardShortcut): string => {\n  const parts: string[] = [];\n\n  // Use Cmd on Mac, Ctrl on other platforms\n  const isMac = typeof navigator !== 'undefined' && navigator.platform.includes('Mac');\n\n  if (shortcut.metaKey) {\n    parts.push(isMac ? 'Cmd' : 'Ctrl');\n  }\n\n  if (shortcut.ctrlKey && !shortcut.metaKey) {\n    parts.push('Ctrl');\n  }\n\n  if (shortcut.altKey) {\n    parts.push(isMac ? 'Option' : 'Alt');\n  }\n\n  if (shortcut.shiftKey) {\n    parts.push('Shift');\n  }\n\n  parts.push(shortcut.key.toUpperCase());\n\n  return parts.join('+');\n};\n","export type RangeSupport = 'supported' | 'unsupported' | 'unknown';\n\ntype FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/**\n * Probe whether an audio host honors HTTP range requests, so an on-demand\n * player can decide its seeking policy. Range *detection* lives here; range\n * *policy* (disable the scrubber, surface an error, play from start) stays with\n * the consuming component — the detection is generic, the UX is per-component.\n *\n * Sends `GET` with `Range: bytes=0-1` and reads only the status line, aborting\n * the body immediately: on a non-range host the `200` carries the entire\n * (possibly hours-long) file, which must never be downloaded just to detect the\n * failure.\n *\n * - `206` → `'supported'`\n * - `200` (host ignored Range, returned full body) → `'unsupported'`\n * - any throw (network error / CORS-opaque cross-origin) or other status → `'unknown'`\n *\n * **Positive-failure only:** only an observed `200` asserts a failure. A\n * CORS-opaque probe is `'unknown'`, NOT a failure — native `<audio>` seeking\n * works cross-origin *without* CORS, so seeking should stay enabled. A naive\n * \"no 206 ⇒ failure\" would false-positive on every CORS-opaque host.\n *\n * @param url       The audio URL to probe.\n * @param fetchImpl Injectable fetch (defaults to the global `fetch`) — pass a\n *                  stub in tests so no real network is hit, and pass an explicit\n *                  implementation on runtimes without a global `fetch`.\n */\nexport async function probeRangeSupport(\n  url: string,\n  fetchImpl: FetchLike = fetch\n): Promise<RangeSupport> {\n  const controller = new AbortController();\n  try {\n    const res = await fetchImpl(url, {\n      method: 'GET',\n      headers: { Range: 'bytes=0-1' },\n      // Bypass the HTTP cache: RFC 7234 lets a UA satisfy a range request from a\n      // cached full 200, which would falsely read as the server ignoring Range.\n      cache: 'no-store',\n      signal: controller.signal,\n    });\n    if (res.status === 206) return 'supported';\n    if (res.status === 200) return 'unsupported';\n    return 'unknown';\n  } catch {\n    return 'unknown';\n  } finally {\n    // Never consume the body — abort the moment we have the status line.\n    controller.abort();\n  }\n}\n","/**\n * Framework-agnostic microphone enumeration.\n *\n * Works without requesting a stream, but browsers redact device information\n * until microphone permission is granted: labels are empty everywhere, and\n * Chrome/Safari typically also redact deviceIds (often exposing a single\n * placeholder entry). The `hasLabels` flag tells consumers whether the list\n * is a usable picker or just a \"microphones exist\" signal — re-enumerate\n * after a successful getUserMedia to get the real list.\n *\n * Follows the probeRangeSupport pattern: the browser API is injectable for\n * tests and non-browser environments degrade gracefully.\n */\n\nexport interface MicrophoneDeviceInfo {\n  deviceId: string;\n  /** Real device label, or a generated fallback name when redacted. */\n  label: string;\n  groupId: string;\n}\n\nexport interface MicrophoneEnumeration {\n  /** False when enumeration is unavailable — SSR/Node, insecure context\n   * (mediaDevices requires HTTPS or localhost), or a very old browser. */\n  supported: boolean;\n  /** True when the browser exposed real device labels. False before\n   * microphone permission is granted (labels — and often deviceIds — are\n   * redacted); the `devices` labels are then generated fallbacks. */\n  hasLabels: boolean;\n  devices: MicrophoneDeviceInfo[];\n}\n\nconst UNSUPPORTED: MicrophoneEnumeration = Object.freeze({\n  supported: false,\n  hasLabels: false,\n  devices: [],\n});\n\nfunction resolveMediaDevices(mediaDevices?: MediaDevices): MediaDevices | undefined {\n  if (mediaDevices) return mediaDevices;\n  return typeof navigator !== 'undefined' ? navigator.mediaDevices : undefined;\n}\n\n/**\n * List audio input devices without requesting a stream.\n *\n * @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.\n */\nexport async function enumerateMicrophones(\n  mediaDevices?: MediaDevices\n): Promise<MicrophoneEnumeration> {\n  const md = resolveMediaDevices(mediaDevices);\n  if (!md || typeof md.enumerateDevices !== 'function') {\n    return UNSUPPORTED;\n  }\n\n  const all = await md.enumerateDevices();\n  const inputs = all.filter((device) => device.kind === 'audioinput');\n  const hasLabels = inputs.some((device) => device.label.length > 0);\n\n  return {\n    supported: true,\n    hasLabels,\n    devices: inputs.map((device, index) => ({\n      deviceId: device.deviceId,\n      label:\n        device.label ||\n        // Pre-permission fallbacks: Chrome redacts deviceId to '' as well,\n        // so fall through to a positional name when there's nothing to slice.\n        (device.deviceId ? `Microphone ${device.deviceId.slice(0, 8)}` : `Microphone ${index + 1}`),\n      groupId: device.groupId,\n    })),\n  };\n}\n\n/**\n * Subscribe to the microphone device list: the listener fires once with the\n * initial enumeration and again on every `devicechange` (hot-plug, permission\n * grant in some browsers). Returns an unsubscribe function.\n *\n * Enumeration failures are logged and skipped — the subscription survives a\n * transient failure and delivers the next successful enumeration.\n *\n * @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.\n */\nexport function watchMicrophoneDevices(\n  listener: (enumeration: MicrophoneEnumeration) => void,\n  mediaDevices?: MediaDevices\n): () => void {\n  const md = resolveMediaDevices(mediaDevices);\n  if (!md || typeof md.enumerateDevices !== 'function') {\n    // Deliver asynchronously so subscribe-then-unsubscribe never re-enters\n    // the caller synchronously.\n    queueMicrotask(() => listener(UNSUPPORTED));\n    return () => {};\n  }\n\n  let active = true;\n\n  const deliver = () => {\n    enumerateMicrophones(md)\n      .then((result) => {\n        if (active) listener(result);\n      })\n      .catch((err) => {\n        console.warn('[waveform-playlist] Microphone enumeration failed:', String(err));\n      });\n  };\n\n  md.addEventListener('devicechange', deliver);\n  deliver();\n\n  return () => {\n    active = false;\n    md.removeEventListener('devicechange', deliver);\n  };\n}\n","import type { AnnotationData } from '../types/annotations';\n\n/** Edges within this distance (seconds) are considered \"linked\". */\nexport const LINK_THRESHOLD = 0.01;\n\n/** Minimum annotation duration (seconds) enforced by boundary edits. */\nexport const MIN_ANNOTATION_DURATION = 0.1;\n\n/** Link threshold for INTEGER TICK positions — closer than half a tick means equal. */\nexport const ANNOTATION_LINK_THRESHOLD_TICKS = 0.5;\n\n/** Minimum tick-annotation duration: a 128th note, floored at 1 tick. */\nexport function annotationMinDurationTicks(ppqn: number): number {\n  return Math.max(1, Math.round(ppqn / 32));\n}\n\n/** Unit constants for a boundary-math run. Defaults are the seconds-domain values. */\nexport interface AnnotationBoundaryOptions {\n  linkThreshold?: number;\n  minDuration?: number;\n}\n\nexport interface AnnotationBoundaryUpdate {\n  annotationIndex: number;\n  newTime: number;\n  isDraggingStart: boolean;\n  annotations: AnnotationData[];\n  duration: number;\n  linkEndpoints: boolean;\n}\n\n/**\n * Pure boundary-update logic shared by the React annotations package and the\n * dawcore annotation web components. Handles linked endpoints (moving one edge\n * drags the linked neighbor edge, with cascade) and collision push-back.\n * Returns a NEW array; never mutates inputs.\n */\nexport function updateAnnotationBoundaries(\n  params: AnnotationBoundaryUpdate,\n  options: AnnotationBoundaryOptions = {}\n): AnnotationData[] {\n  const { annotationIndex, newTime, isDraggingStart, annotations, duration, linkEndpoints } =\n    params;\n  const linkThreshold = options.linkThreshold ?? LINK_THRESHOLD;\n  const minDuration = options.minDuration ?? MIN_ANNOTATION_DURATION;\n\n  const updatedAnnotations = [...annotations];\n  const annotation = annotations[annotationIndex];\n\n  if (isDraggingStart) {\n    const constrainedStart = Math.min(annotation.end - minDuration, Math.max(0, newTime));\n    const delta = constrainedStart - annotation.start;\n\n    updatedAnnotations[annotationIndex] = { ...annotation, start: constrainedStart };\n\n    if (linkEndpoints && annotationIndex > 0) {\n      const prevAnnotation = updatedAnnotations[annotationIndex - 1];\n      if (Math.abs(prevAnnotation.end - annotation.start) < linkThreshold) {\n        updatedAnnotations[annotationIndex - 1] = {\n          ...prevAnnotation,\n          end: Math.max(prevAnnotation.start + minDuration, prevAnnotation.end + delta),\n        };\n      } else if (constrainedStart <= prevAnnotation.end) {\n        updatedAnnotations[annotationIndex] = {\n          ...updatedAnnotations[annotationIndex],\n          start: prevAnnotation.end,\n        };\n      }\n    } else if (\n      !linkEndpoints &&\n      annotationIndex > 0 &&\n      constrainedStart < updatedAnnotations[annotationIndex - 1].end\n    ) {\n      updatedAnnotations[annotationIndex - 1] = {\n        ...updatedAnnotations[annotationIndex - 1],\n        end: constrainedStart,\n      };\n    }\n  } else {\n    const constrainedEnd = Math.max(annotation.start + minDuration, Math.min(newTime, duration));\n    const delta = constrainedEnd - annotation.end;\n\n    updatedAnnotations[annotationIndex] = { ...annotation, end: constrainedEnd };\n\n    if (linkEndpoints && annotationIndex < updatedAnnotations.length - 1) {\n      const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n      if (Math.abs(nextAnnotation.start - annotation.end) < linkThreshold) {\n        const newStart = nextAnnotation.start + delta;\n        updatedAnnotations[annotationIndex + 1] = {\n          ...nextAnnotation,\n          start: Math.min(nextAnnotation.end - minDuration, newStart),\n        };\n\n        let currentIndex = annotationIndex + 1;\n        while (currentIndex < updatedAnnotations.length - 1) {\n          const current = updatedAnnotations[currentIndex];\n          const next = updatedAnnotations[currentIndex + 1];\n          if (Math.abs(next.start - current.end) < linkThreshold) {\n            const nextDelta = current.end - annotations[currentIndex].end;\n            updatedAnnotations[currentIndex + 1] = {\n              ...next,\n              start: Math.min(next.end - minDuration, next.start + nextDelta),\n            };\n            currentIndex++;\n          } else {\n            break;\n          }\n        }\n      } else if (constrainedEnd >= nextAnnotation.start) {\n        updatedAnnotations[annotationIndex] = {\n          ...updatedAnnotations[annotationIndex],\n          end: nextAnnotation.start,\n        };\n      }\n    } else if (\n      !linkEndpoints &&\n      annotationIndex < updatedAnnotations.length - 1 &&\n      constrainedEnd > updatedAnnotations[annotationIndex + 1].start\n    ) {\n      const nextAnnotation = updatedAnnotations[annotationIndex + 1];\n      updatedAnnotations[annotationIndex + 1] = { ...nextAnnotation, start: constrainedEnd };\n\n      let currentIndex = annotationIndex + 1;\n      while (currentIndex < updatedAnnotations.length - 1) {\n        const current = updatedAnnotations[currentIndex];\n        const next = updatedAnnotations[currentIndex + 1];\n        if (current.end > next.start) {\n          updatedAnnotations[currentIndex + 1] = { ...next, start: current.end };\n          currentIndex++;\n        } else {\n          break;\n        }\n      }\n    }\n  }\n\n  return updatedAnnotations;\n}\n","import type { KeyBinding } from '../keyboard';\n\nexport type AnnotationShortcutAction =\n  | 'selectPrevious'\n  | 'selectNext'\n  | 'selectFirst'\n  | 'selectLast'\n  | 'clearSelection'\n  | 'moveStartEarlier'\n  | 'moveStartLater'\n  | 'moveEndEarlier'\n  | 'moveEndLater'\n  | 'playActive';\n\n/** Key remapping map — null on the consumer means \"use defaults\". */\nexport interface AnnotationShortcutMap {\n  selectPrevious?: KeyBinding;\n  selectNext?: KeyBinding;\n  selectFirst?: KeyBinding;\n  selectLast?: KeyBinding;\n  clearSelection?: KeyBinding;\n  moveStartEarlier?: KeyBinding;\n  moveStartLater?: KeyBinding;\n  moveEndEarlier?: KeyBinding;\n  moveEndLater?: KeyBinding;\n  playActive?: KeyBinding;\n}\n\n// Explicit ctrlKey/metaKey: false so browser combos (Cmd+ArrowLeft = history\n// back, Cmd+[ = back, etc.) never trigger annotation actions — same convention\n// as <daw-keyboard-shortcuts> presets.\nconst noMods = { ctrlKey: false as const, metaKey: false as const };\n\nexport const DEFAULT_ANNOTATION_SHORTCUTS: Record<AnnotationShortcutAction, KeyBinding[]> = {\n  selectPrevious: [\n    { key: 'ArrowUp', ...noMods },\n    { key: 'ArrowLeft', ...noMods },\n  ],\n  selectNext: [\n    { key: 'ArrowDown', ...noMods },\n    { key: 'ArrowRight', ...noMods },\n  ],\n  selectFirst: [{ key: 'Home', ...noMods }],\n  selectLast: [{ key: 'End', ...noMods }],\n  clearSelection: [{ key: 'Escape', ...noMods }],\n  moveStartEarlier: [{ key: '[', ...noMods }],\n  moveStartLater: [{ key: ']', ...noMods }],\n  // '{' / '}' are what event.key reports for Shift+[ / Shift+] — no explicit\n  // shiftKey needed; the key value itself encodes it.\n  moveEndEarlier: [{ key: '{', ...noMods }],\n  moveEndLater: [{ key: '}', ...noMods }],\n  playActive: [{ key: 'Enter', ...noMods }],\n};\n\nconst ALL_ACTIONS = Object.keys(DEFAULT_ANNOTATION_SHORTCUTS) as AnnotationShortcutAction[];\n\n/**\n * Flatten defaults + remap into a matchable list. A remapped action replaces\n * ALL of its default bindings with the single provided binding.\n */\nexport function resolveAnnotationShortcuts(\n  remap: AnnotationShortcutMap | null\n): Array<{ action: AnnotationShortcutAction; binding: KeyBinding }> {\n  return ALL_ACTIONS.flatMap((action) => {\n    const override = remap?.[action];\n    const bindings = override ? [override] : DEFAULT_ANNOTATION_SHORTCUTS[action];\n    return bindings.map((binding) => ({ action, binding }));\n  });\n}\n"],"mappings":"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","names":["InteractionState","ts","tpBar","tpBeat","offset","barIndexInSegment","beatInBar","newPeaks","result"]}