{"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/MidiToneTrack.ts","../src/SoundFontToneTrack.ts","../src/SoundFontCache.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../src/TonePlayoutAdapter.ts"],"sourcesContent":["import {\n  Volume,\n  Gain,\n  ToneAudioNode,\n  getDestination,\n  start,\n  now,\n  getTransport,\n  getContext,\n  BaseContext,\n} from 'tone';\nimport { gainToDb } from '@waveform-playlist/core';\nimport { ToneTrack, ToneTrackOptions, type ClipInfo } from './ToneTrack';\nimport { MidiToneTrack, MidiToneTrackOptions } from './MidiToneTrack';\nimport type { PlayableTrack } from './MidiToneTrack';\nimport { SoundFontToneTrack, SoundFontToneTrackOptions } from './SoundFontToneTrack';\n\nexport type EffectsFunction = (\n  masterGainNode: Volume,\n  destination: ToneAudioNode,\n  isOffline: boolean\n) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n  tracks?: ToneTrack[];\n  masterGain?: number;\n  effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n  private tracks: Map<string, PlayableTrack> = new Map();\n  private masterVolume: Volume;\n  private _masterTap: Gain;\n  private isInitialized = false;\n  private soloedTracks: Set<string> = new Set();\n  private manualMuteState: Map<string, boolean> = new Map();\n  private effectsCleanup?: () => void;\n  private onPlaybackCompleteCallback?: () => void;\n  private _completionEventId: number | null = null;\n  private _loopHandler: (() => void) | null = null;\n  private _loopEnabled = false;\n  private _loopStart = 0;\n  private _loopEnd = 0;\n  private _masterChainNode: AudioNode | null = null;\n\n  constructor(options: TonePlayoutOptions = {}) {\n    this.masterVolume = new Volume(gainToDb(options.masterGain ?? 1));\n    // Pass-through tap node for consumer connections (analyzers, recorders).\n    // Shares the same standardized-audio-context as masterVolume.\n    this._masterTap = new Gain(1);\n\n    // Serial chain: masterVolume → tap → destination.\n    // When effects are present: masterVolume → effects → tap → destination.\n    // Consumers connect analyzers, effects, recorders to the tap node.\n    if (options.effects) {\n      const cleanup = options.effects(this.masterVolume, this._masterTap, false);\n      if (cleanup) {\n        this.effectsCleanup = cleanup;\n      }\n      this._masterTap.connect(getDestination());\n    } else {\n      this.masterVolume.chain(this._masterTap, getDestination());\n    }\n\n    if (options.tracks) {\n      options.tracks.forEach((track) => {\n        this.tracks.set(track.id, track);\n        this.manualMuteState.set(track.id, track.muted);\n      });\n    }\n  }\n\n  private clearCompletionEvent(): void {\n    if (this._completionEventId !== null) {\n      try {\n        getTransport().clear(this._completionEventId);\n      } catch (err) {\n        console.warn(\n          '[waveform-playlist] Error clearing Transport completion event:' + ' ' + String(err)\n        );\n      }\n      this._completionEventId = null;\n    }\n  }\n\n  async init(): Promise<void> {\n    if (this.isInitialized) return;\n\n    await start();\n    this.isInitialized = true;\n  }\n\n  addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n    // Ensure tracks connect to master volume instead of destination\n    const optionsWithDestination = {\n      ...trackOptions,\n      destination: this.masterVolume,\n    };\n    const toneTrack = new ToneTrack(optionsWithDestination);\n    this.tracks.set(toneTrack.id, toneTrack);\n    this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n    if (trackOptions.track.soloed) {\n      this.soloedTracks.add(toneTrack.id);\n    }\n    return toneTrack;\n  }\n\n  addMidiTrack(trackOptions: MidiToneTrackOptions): MidiToneTrack {\n    const optionsWithDestination = {\n      ...trackOptions,\n      destination: this.masterVolume,\n    };\n    const midiTrack = new MidiToneTrack(optionsWithDestination);\n    this.tracks.set(midiTrack.id, midiTrack);\n    this.manualMuteState.set(midiTrack.id, trackOptions.track.muted ?? false);\n    if (trackOptions.track.soloed) {\n      this.soloedTracks.add(midiTrack.id);\n    }\n    return midiTrack;\n  }\n\n  addSoundFontTrack(trackOptions: SoundFontToneTrackOptions): SoundFontToneTrack {\n    const optionsWithDestination = {\n      ...trackOptions,\n      destination: this.masterVolume,\n    };\n    const sfTrack = new SoundFontToneTrack(optionsWithDestination);\n    this.tracks.set(sfTrack.id, sfTrack);\n    this.manualMuteState.set(sfTrack.id, trackOptions.track.muted ?? false);\n    if (trackOptions.track.soloed) {\n      this.soloedTracks.add(sfTrack.id);\n    }\n    return sfTrack;\n  }\n\n  /**\n   * Apply solo muting after all tracks have been added.\n   * Call this after adding all tracks to ensure solo logic is applied correctly.\n   */\n  applyInitialSoloState(): void {\n    this.updateSoloMuting();\n  }\n\n  removeTrack(trackId: string): void {\n    const track = this.tracks.get(trackId);\n    if (track) {\n      track.dispose();\n      this.tracks.delete(trackId);\n      this.manualMuteState.delete(trackId);\n      this.soloedTracks.delete(trackId);\n    }\n  }\n\n  getTrack(trackId: string): PlayableTrack | undefined {\n    return this.tracks.get(trackId);\n  }\n\n  getTrackIds(): string[] {\n    return [...this.tracks.keys()];\n  }\n\n  /**\n   * Replace clips on a track, preserving the track's audio graph.\n   * Only works for ToneTrack (audio clips), not MidiToneTrack.\n   */\n  replaceTrackClips(trackId: string, newClips: ClipInfo[], newStartTime?: number): boolean {\n    const track = this.tracks.get(trackId);\n    if (!track || !('replaceClips' in track)) return false;\n    (track as ToneTrack).replaceClips(newClips, newStartTime);\n    return true;\n  }\n\n  /**\n   * Start mid-clip sources for a specific track at the current Transport position.\n   * Call after adding/updating a track during active playback so clips that span\n   * the current position produce audio immediately.\n   */\n  resumeTrackMidPlayback(trackId: string): void {\n    const track = this.tracks.get(trackId);\n    if (!track) return;\n    const transport = getTransport();\n    if (transport.state !== 'started') return;\n    const context = getContext();\n    // Use audible position (transportOffset - lookAhead) so new sources\n    // overlap with remaining buffered audio for a seamless transition\n    const lookAhead = context.lookAhead ?? 0;\n    const audibleOffset = Math.max(0, transport.seconds - lookAhead);\n    track.startMidClipSources(audibleOffset, context.currentTime);\n    // Fades were last prepared at play(), before this track was added or\n    // swapped in — schedule its envelopes for the current cycle (no-op for\n    // MIDI tracks, whose prepareFades is empty).\n    track.prepareFades(context.currentTime, audibleOffset);\n  }\n\n  play(when?: number, offset?: number, duration?: number): void {\n    if (!this.isInitialized) {\n      throw new Error('[waveform-playlist] TonePlayout not initialized. Call init() first.');\n    }\n\n    const startTime = when ?? now();\n    const transport = getTransport();\n\n    this.clearCompletionEvent();\n\n    const transportOffset = offset ?? 0;\n    this.tracks.forEach((track) => {\n      track.cancelFades();\n      track.prepareFades(startTime, transportOffset);\n    });\n\n    // Schedule duration-limited stop via Transport\n    if (duration !== undefined) {\n      this._completionEventId = transport.scheduleOnce(() => {\n        this._completionEventId = null;\n        try {\n          this.onPlaybackCompleteCallback?.();\n        } catch (err) {\n          console.warn(\n            '[waveform-playlist] Error in playback completion callback:' + ' ' + String(err)\n          );\n        }\n      }, transportOffset + duration);\n    }\n\n    // Start Transport — triggers schedule() callbacks for clips at/after offset\n    try {\n      // Stop all active native sources before restarting to prevent layered audio.\n      // Native AudioBufferSourceNodes don't respond to Transport state changes.\n      if (transport.state !== 'stopped') {\n        transport.stop();\n      }\n      this.tracks.forEach((track) => track.stopAllSources());\n\n      // Set loop boundaries BEFORE enabling loop. _processTick checks\n      // `ticks >= _loopEnd` every tick; _loopEnd defaults to 0.\n      transport.loopStart = this._loopStart;\n      transport.loopEnd = this._loopEnd;\n      transport.loop = this._loopEnabled;\n\n      // Set schedule guard BEFORE transport.start(). Ghost ticks from stale\n      // Clock._lastUpdate can fire schedule callbacks at past positions;\n      // the guard suppresses callbacks for clips before the play offset\n      // (those are handled by startMidClipSources below).\n      this.tracks.forEach((track) => track.setScheduleGuardOffset(transportOffset));\n\n      if (offset !== undefined) {\n        transport.start(startTime, offset);\n      } else {\n        transport.start(startTime);\n      }\n\n      // Advance Clock._lastUpdate past the stop/start boundary.\n      //\n      // After stop/start cycles, _lastUpdate is stale (set by the previous\n      // context tick, before our stop/start). The next Clock._loop() processes\n      // ticks from [_lastUpdate, now()]. In the gap [_lastUpdate, startTime),\n      // the TickSource is still \"started\" from the PREVIOUS play cycle with\n      // its old tick offset. Those accumulated ticks can exceed _loopEnd,\n      // causing _processTick to wrap immediately to loopStart.\n      //\n      // By advancing _lastUpdate to startTime, we skip the stale range.\n      // The next Clock._loop() only processes [startTime, now()] — ticks\n      // from the current play cycle with the correct offset.\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tone.js private internal, see CLAUDE.md ghost tick fix\n      (transport as any)._clock._lastUpdate = startTime;\n\n      // Start sources for clips that span the current Transport position.\n      // Transport.schedule() only fires for clips at/after the offset;\n      // clips whose start time is before the offset need manual creation.\n      this.tracks.forEach((track) => {\n        track.startMidClipSources(transportOffset, startTime);\n      });\n    } catch (err) {\n      // Clean up scheduled events since Transport failed to start\n      this.clearCompletionEvent();\n      this.tracks.forEach((track) => track.cancelFades());\n      console.warn(\n        '[waveform-playlist] Transport.start() failed. Audio playback could not begin.' +\n          ' ' +\n          String(err)\n      );\n      throw err;\n    }\n  }\n\n  pause(): void {\n    const transport = getTransport();\n    try {\n      transport.pause();\n    } catch (err) {\n      console.warn('[waveform-playlist] Transport.pause() failed:' + ' ' + String(err));\n    }\n    // Native AudioBufferSourceNodes ignore Transport state changes —\n    // they must be explicitly stopped.\n    this.tracks.forEach((track) => track.stopAllSources());\n    this.tracks.forEach((track) => track.cancelFades());\n    this.clearCompletionEvent();\n  }\n\n  stop(): void {\n    const transport = getTransport();\n    try {\n      transport.stop();\n    } catch (err) {\n      console.warn('[waveform-playlist] Transport.stop() failed:' + ' ' + String(err));\n    }\n    // Remove loop handler before stopping sources. Prevents any deferred\n    // loop event from creating new sources via startMidClipSources() after\n    // stopAllSources() runs. Defense-in-depth — setLoop(false) should\n    // already have removed it, but stop() must be self-contained.\n    if (this._loopHandler) {\n      try {\n        transport.off('loop', this._loopHandler);\n      } catch (err) {\n        console.warn('[waveform-playlist] Error removing loop handler:' + ' ' + String(err));\n      }\n      this._loopHandler = null;\n    }\n    // Stop all native sources explicitly\n    this.tracks.forEach((track) => {\n      try {\n        track.stopAllSources();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error stopping sources for track \"${track.id}\":` + ' ' + String(err)\n        );\n      }\n    });\n    this.tracks.forEach((track) => {\n      try {\n        track.cancelFades();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error canceling fades for track \"${track.id}\":` + ' ' + String(err)\n        );\n      }\n    });\n    this.clearCompletionEvent();\n  }\n\n  setMasterGain(gain: number): void {\n    this.masterVolume.volume.value = gainToDb(gain);\n  }\n\n  /** The master output tap node. In the signal chain: masterVolume → tap → destination.\n   *  Connect analyzers/effects/recorders here — parallel or serial.\n   *  The tap's native GainNode is on the same standardized-audio-context as adapter.audioContext. */\n  get masterOutputNode(): GainNode {\n    return this._masterTap.input;\n  }\n\n  /**\n   * Native GainNode behind masterVolume.input — the master-bus junction that\n   * per-track effect chains reconnect into (pre master volume). Distinct from\n   * masterOutputNode (the post-volume tap for analyzers).\n   */\n  get masterBusInputNode(): GainNode {\n    return (this.masterVolume.input as unknown as Gain).input;\n  }\n\n  /** Insert an external chain on a track: muteGain → node (caller wires node onward). */\n  connectTrackOutput(trackId: string, node: AudioNode): void {\n    const track = this.tracks.get(trackId);\n    if (!track) {\n      throw new Error('[waveform-playlist] connectTrackOutput: unknown track \"' + trackId + '\"');\n    }\n    if (!(track instanceof ToneTrack)) {\n      throw new Error(\n        '[waveform-playlist] connectTrackOutput: per-track effects chains are not supported ' +\n          'for MIDI tracks on the Tone adapter (track \"' +\n          trackId +\n          '\")'\n      );\n    }\n    track.connectEffects(node);\n  }\n\n  /** Restore a track's direct connection. No-op for unknown or MIDI tracks. */\n  disconnectTrackOutput(trackId: string): void {\n    const track = this.tracks.get(trackId);\n    if (track instanceof ToneTrack) {\n      track.disconnectEffects();\n    }\n  }\n\n  /**\n   * Insert an external master chain after the tap: masterVolume → [closure\n   * effects] → tap → node (caller wires node.output → ctx.destination).\n   */\n  connectMasterOutput(node: AudioNode): void {\n    // Guard the disconnect (as disconnectMasterOutput does) — a consumer may\n    // have disposed the old chain node externally; the rewire must proceed.\n    try {\n      if (this._masterChainNode) {\n        this._masterTap.disconnect(this._masterChainNode);\n      } else {\n        this._masterTap.disconnect(getDestination());\n      }\n    } catch (err) {\n      console.warn('[waveform-playlist] connectMasterOutput disconnect: ' + String(err));\n    }\n    this._masterTap.connect(node);\n    this._masterChainNode = node;\n  }\n\n  /** Restore tap → destination. Safe when no master chain is connected. */\n  disconnectMasterOutput(): void {\n    if (!this._masterChainNode) return;\n    try {\n      this._masterTap.disconnect(this._masterChainNode);\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] disconnectMasterOutput: ' +\n          (err instanceof Error ? err.message : String(err))\n      );\n    }\n    this._masterTap.connect(getDestination());\n    this._masterChainNode = null;\n  }\n\n  setSolo(trackId: string, soloed: boolean): void {\n    const track = this.tracks.get(trackId);\n    if (track) {\n      track.setSolo(soloed);\n      if (soloed) {\n        this.soloedTracks.add(trackId);\n      } else {\n        this.soloedTracks.delete(trackId);\n      }\n      this.updateSoloMuting();\n    }\n  }\n\n  private updateSoloMuting(): void {\n    const hasSoloedTracks = this.soloedTracks.size > 0;\n\n    this.tracks.forEach((track, id) => {\n      if (hasSoloedTracks) {\n        if (!this.soloedTracks.has(id)) {\n          track.setMute(true);\n        } else {\n          const manuallyMuted = this.manualMuteState.get(id) ?? false;\n          track.setMute(manuallyMuted);\n        }\n      } else {\n        const manuallyMuted = this.manualMuteState.get(id) ?? false;\n        track.setMute(manuallyMuted);\n      }\n    });\n  }\n\n  setMute(trackId: string, muted: boolean): void {\n    const track = this.tracks.get(trackId);\n    if (track) {\n      this.manualMuteState.set(trackId, muted);\n      // Route through solo-aware muting: while any track is soloed, a\n      // non-soloed track must stay silent regardless of its manual mute —\n      // the manual state is remembered and applies when solo clears.\n      this.updateSoloMuting();\n    }\n  }\n\n  setLoop(enabled: boolean, loopStart: number, loopEnd: number): void {\n    // Update cached state first — play() uses these values to configure\n    // the Transport on next start, so they must reflect the caller's intent\n    // regardless of whether Transport property setting succeeds.\n    this._loopEnabled = enabled;\n    this._loopStart = loopStart;\n    this._loopEnd = loopEnd;\n\n    const transport = getTransport();\n    try {\n      // Set boundaries BEFORE enabling loop. Tone.js's _processTick checks\n      // `ticks >= _loopEnd` on every tick. If we set transport.loop = true\n      // first, a tick could fire before loopEnd is updated, seeing the stale\n      // _loopEnd value (0 from Transport default) and wrapping immediately.\n      transport.loopStart = loopStart;\n      transport.loopEnd = loopEnd;\n      transport.loop = enabled;\n    } catch (err) {\n      console.warn('[waveform-playlist] Error configuring Transport loop:' + ' ' + String(err));\n      return;\n    }\n\n    if (enabled && !this._loopHandler) {\n      this._loopHandler = () => {\n        // On loop boundary: stop old sources, re-schedule fades, start mid-clip sources.\n        // Event ordering in Transport's tick processing (Tone.js 15.x _processTick):\n        //   loopEnd → ticks reset → loopStart → loop → forEachAtTime(ticks)\n        // Our loop handler fires BEFORE schedule callbacks, so:\n        // 1. stopAllSources + cancelFades — clean slate\n        // 2. startMidClipSources — for clips spanning loopStart boundary\n        // 3. prepareFades — fresh fade envelopes\n        // Then Transport fires schedule callbacks for clips at/after loopStart.\n        const currentTime = now();\n        this.tracks.forEach((track) => {\n          try {\n            track.stopAllSources();\n            track.cancelFades();\n            track.setScheduleGuardOffset(this._loopStart);\n            track.startMidClipSources(this._loopStart, currentTime);\n            track.prepareFades(currentTime, this._loopStart);\n          } catch (err) {\n            console.warn(\n              `[waveform-playlist] Error re-scheduling track \"${track.id}\" on loop:` +\n                ' ' +\n                String(err)\n            );\n          }\n        });\n      };\n      transport.on('loop', this._loopHandler);\n    } else if (!enabled && this._loopHandler) {\n      transport.off('loop', this._loopHandler);\n      this._loopHandler = null;\n    }\n  }\n\n  getCurrentTime(): number {\n    return getTransport().seconds;\n  }\n\n  seekTo(time: number): void {\n    getTransport().seconds = time;\n  }\n\n  dispose(): void {\n    // Stop Transport and all active sources before disposing.\n    // Without this, the global Transport singleton keeps firing scheduled\n    // callbacks and native AudioBufferSourceNodes continue playing through\n    // the shared AudioContext (e.g., during Docusaurus client-side navigation).\n    try {\n      this.stop();\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] Error stopping Transport during dispose:' + ' ' + String(err)\n      );\n    }\n\n    this.tracks.forEach((track) => {\n      try {\n        track.dispose();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error disposing track \"${track.id}\":` + ' ' + String(err)\n        );\n      }\n    });\n    this.tracks.clear();\n\n    if (this.effectsCleanup) {\n      try {\n        this.effectsCleanup();\n      } catch (err) {\n        console.warn(\n          '[waveform-playlist] Error during master effects cleanup:' + ' ' + String(err)\n        );\n      }\n    }\n\n    try {\n      this._masterTap.dispose();\n    } catch (err) {\n      console.warn('[waveform-playlist] Error disposing master tap: ' + String(err));\n    }\n\n    try {\n      this.masterVolume.dispose();\n    } catch (err) {\n      console.warn('[waveform-playlist] Error disposing master volume: ' + String(err));\n    }\n  }\n\n  get context(): BaseContext {\n    return getContext();\n  }\n\n  get sampleRate(): number {\n    return getContext().sampleRate;\n  }\n\n  setOnPlaybackComplete(callback: () => void): void {\n    this.onPlaybackCompleteCallback = callback;\n  }\n}\n","import {\n  Volume,\n  Gain,\n  Panner,\n  ToneAudioNode,\n  getDestination,\n  getTransport,\n  getContext,\n} from 'tone';\nimport { Track, gainToDb, applyFadeIn, applyFadeOut, type Fade } from '@waveform-playlist/core';\nimport { getUnderlyingAudioParam } from './fades';\n\nexport type TrackEffectsFunction = (\n  graphEnd: Gain,\n  masterGainNode: ToneAudioNode,\n  isOffline: boolean\n) => void | (() => void);\n\nexport interface ClipInfo {\n  buffer: AudioBuffer;\n  startTime: number; // When this clip starts in the track timeline (seconds)\n  duration: number; // How long this clip plays (seconds)\n  offset: number; // Where to start playing within the buffer (seconds)\n  fadeIn?: Fade;\n  fadeOut?: Fade;\n  gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n  buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n  clips?: ClipInfo[]; // Modern: array of clips\n  track: Track;\n  effects?: TrackEffectsFunction;\n  destination?: ToneAudioNode;\n  /** Max channel count across clips — sets Panner channelCount. Default: 1 */\n  channelCount?: number;\n}\n\n/** Per-clip scheduling info and audio nodes */\ninterface ScheduledClip {\n  clipInfo: ClipInfo;\n  fadeGainNode: GainNode; // Native GainNode for per-clip fade envelope\n  scheduleId: number; // Transport.schedule() event ID\n}\n\nexport class ToneTrack {\n  private scheduledClips: ScheduledClip[];\n  private activeSources: Set<AudioBufferSourceNode> = new Set();\n  private volumeNode: Volume;\n  private panNode: Panner;\n  private muteGain: Gain;\n  private track: Track;\n  private effectsCleanup?: () => void;\n  private _destination: ToneAudioNode;\n  private _hasClosureEffects: boolean;\n  private _effectsChainNode: AudioNode | null = null;\n  // Guard against ghost tick schedule callbacks. After stop/start cycles with\n  // loops, stale Clock._lastUpdate causes ticks from the previous cycle to fire\n  // Transport.schedule() callbacks at past positions (e.g., time 0 clips fire\n  // when starting at offset 5s). Clips before this offset are handled by\n  // startMidClipSources(); schedule callbacks should only create sources for\n  // clips at/after this offset.\n  private _scheduleGuardOffset = 0;\n\n  constructor(options: ToneTrackOptions) {\n    this.track = options.track;\n\n    // Create shared track-level Tone.js nodes\n    this.volumeNode = new Volume(gainToDb(options.track.gain));\n    // Match channelCount to source — Tone.js Panner defaults to 1 (stereo→mono downmix).\n    this.panNode = new Panner({\n      pan: options.track.stereoPan,\n      channelCount: options.channelCount ?? 1,\n    });\n    this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n    // Chain shared Tone.js nodes: Volume → Pan → MuteGain\n    this.volumeNode.chain(this.panNode, this.muteGain);\n\n    // Connect to destination or apply effects chain\n    this._destination = options.destination || getDestination();\n    this._hasClosureEffects = Boolean(options.effects);\n    if (options.effects) {\n      const cleanup = options.effects(this.muteGain, this._destination, false);\n      if (cleanup) {\n        this.effectsCleanup = cleanup;\n      }\n    } else {\n      this.muteGain.connect(this._destination);\n    }\n\n    // Create clips array - support both legacy single buffer and modern clips array\n    const clipInfos: ClipInfo[] =\n      options.clips ||\n      (options.buffer\n        ? [\n            {\n              buffer: options.buffer,\n              startTime: 0,\n              duration: options.buffer.duration,\n              offset: 0,\n              fadeIn: options.track.fadeIn,\n              fadeOut: options.track.fadeOut,\n              gain: 1,\n            },\n          ]\n        : []);\n\n    const transport = getTransport();\n    const rawContext = getContext().rawContext as AudioContext;\n\n    // Get the native AudioNode input of the Volume for native→Tone connection.\n    // Volume.input is a Tone.js Gain<\"decibels\"> whose .input is the native GainNode.\n    // Cast through unknown since Gain<\"decibels\"> and Gain<\"gain\"> don't overlap.\n    const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n    // Schedule each clip via Transport.schedule() with native AudioBufferSourceNode\n    this.scheduledClips = clipInfos.map((clipInfo) => {\n      // Native GainNode for per-clip fade envelope — created once, reused across play cycles\n      const fadeGainNode = rawContext.createGain();\n      fadeGainNode.gain.value = clipInfo.gain;\n      fadeGainNode.connect(volumeNativeInput);\n\n      // Schedule a permanent Transport event at the clip's absolute timeline position.\n      // This callback fires on every play and every loop iteration when Transport\n      // passes this point.\n      const absTransportTime = this.track.startTime + clipInfo.startTime;\n      const scheduleId = transport.schedule((audioContextTime: number) => {\n        // Guard: ghost ticks from stale Clock._lastUpdate can fire this callback\n        // at past positions (see Tone.js #1419). Clips before the play/loop offset\n        // are already handled by startMidClipSources().\n        if (absTransportTime < this._scheduleGuardOffset) {\n          return;\n        }\n        this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n      }, absTransportTime);\n\n      return { clipInfo, fadeGainNode, scheduleId };\n    });\n  }\n\n  /**\n   * Create and start an AudioBufferSourceNode for a clip.\n   * Sources are one-shot: each play or loop iteration creates a fresh one.\n   */\n  private startClipSource(\n    clipInfo: ClipInfo,\n    fadeGainNode: GainNode,\n    audioContextTime: number,\n    bufferOffset?: number,\n    playDuration?: number\n  ): void {\n    const rawContext = getContext().rawContext as AudioContext;\n    const source = rawContext.createBufferSource();\n    source.buffer = clipInfo.buffer;\n    source.connect(fadeGainNode);\n\n    const offset = bufferOffset ?? clipInfo.offset;\n    const duration = playDuration ?? clipInfo.duration;\n\n    try {\n      source.start(audioContextTime, offset, duration);\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Failed to start source on track \"${this.id}\" ` +\n          `(time=${audioContextTime}, offset=${offset}, duration=${duration}):` +\n          ' ' +\n          String(err)\n      );\n      source.disconnect();\n      return;\n    }\n\n    this.activeSources.add(source);\n    source.onended = () => {\n      this.activeSources.delete(source);\n    };\n  }\n\n  /**\n   * Set the schedule guard offset. Schedule callbacks for clips before this\n   * offset are suppressed (already handled by startMidClipSources).\n   * Must be called before transport.start() and in the loop handler.\n   */\n  setScheduleGuardOffset(offset: number): void {\n    this._scheduleGuardOffset = offset;\n  }\n\n  /**\n   * Start sources for clips that span the given Transport position.\n   * Used for mid-playback seeking and loop boundary handling where\n   * Transport.schedule() callbacks have already passed.\n   *\n   * Uses strict < for absClipStart to avoid double-creation with\n   * schedule callbacks at exact Transport position (e.g., loopStart).\n   */\n  startMidClipSources(transportOffset: number, audioContextTime: number): void {\n    for (const { clipInfo, fadeGainNode } of this.scheduledClips) {\n      const absClipStart = this.track.startTime + clipInfo.startTime;\n      const absClipEnd = absClipStart + clipInfo.duration;\n\n      // Only handle clips that started before the transport position\n      // but haven't ended yet (i.e., the transport is \"inside\" the clip)\n      if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n        const elapsed = transportOffset - absClipStart;\n        const adjustedOffset = clipInfo.offset + elapsed;\n        const remainingDuration = clipInfo.duration - elapsed;\n        this.startClipSource(\n          clipInfo,\n          fadeGainNode,\n          audioContextTime,\n          adjustedOffset,\n          remainingDuration\n        );\n      }\n    }\n  }\n\n  /**\n   * Add a clip to this track at runtime. Creates a Transport.schedule event\n   * and fadeGainNode. If playing, starts the source mid-clip if needed.\n   */\n  addClip(clipInfo: ClipInfo): ScheduledClip {\n    const transport = getTransport();\n    const rawContext = getContext().rawContext as AudioContext;\n    const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n    const fadeGainNode = rawContext.createGain();\n    fadeGainNode.gain.value = clipInfo.gain;\n    fadeGainNode.connect(volumeNativeInput);\n\n    const absTransportTime = this.track.startTime + clipInfo.startTime;\n    const scheduleId = transport.schedule((audioContextTime: number) => {\n      if (absTransportTime < this._scheduleGuardOffset) return;\n      this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n    }, absTransportTime);\n\n    const scheduled: ScheduledClip = { clipInfo, fadeGainNode, scheduleId };\n    this.scheduledClips.push(scheduled);\n    return scheduled;\n  }\n\n  /**\n   * Remove a scheduled clip by index. Clears the Transport event and\n   * disconnects the fadeGainNode.\n   */\n  removeScheduledClip(index: number): void {\n    const scheduled = this.scheduledClips[index];\n    if (!scheduled) return;\n    const transport = getTransport();\n\n    try {\n      transport.clear(scheduled.scheduleId);\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] removeScheduledClip: clear failed on track \"' +\n          this.id +\n          '\": ' +\n          String(err)\n      );\n    }\n    try {\n      scheduled.fadeGainNode.disconnect();\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] removeScheduledClip: disconnect failed on track \"' +\n          this.id +\n          '\": ' +\n          String(err)\n      );\n    }\n\n    this.scheduledClips.splice(index, 1);\n  }\n\n  /**\n   * Replace clips on this track. Diffs old vs new by buffer + timing —\n   * unchanged clips keep their active sources playing (no audible interruption).\n   * Changed/added/removed clips are rescheduled. Disconnecting a removed clip's\n   * fadeGainNode silences its source immediately (audio path broken) without\n   * needing to explicitly stop it.\n   */\n  replaceClips(newClips: ClipInfo[], newStartTime?: number): void {\n    // When track.startTime changes, all Transport events must be rescheduled\n    // because absolute times (track.startTime + clip.startTime) shift even if\n    // relative clip.startTime values stay the same.\n    const startTimeChanged = newStartTime !== undefined && newStartTime !== this.track.startTime;\n\n    // Update track startTime if the minimum clip position changed (e.g., moveClip)\n    if (newStartTime !== undefined) {\n      this.track.startTime = newStartTime;\n    }\n    const tp = getTransport();\n\n    // Diff old vs new clips — a clip is \"unchanged\" if buffer reference and\n    // all timing properties match exactly. Skip diff if startTime changed\n    // (all clips need rescheduling for new absolute positions).\n    const kept: ScheduledClip[] = [];\n    const toAdd: ClipInfo[] = [];\n    const matched = new Set<number>(); // indices into this.scheduledClips\n\n    if (startTimeChanged) {\n      // Force full reschedule — all new clips are \"toAdd\", none kept\n      toAdd.push(...newClips);\n    } else {\n      for (const clipInfo of newClips) {\n        const idx = this.scheduledClips.findIndex(\n          (s, i) => !matched.has(i) && this._clipsEqual(s.clipInfo, clipInfo)\n        );\n        if (idx !== -1) {\n          kept.push(this.scheduledClips[idx]);\n          matched.add(idx);\n        } else {\n          toAdd.push(clipInfo);\n        }\n      }\n    }\n\n    // Remove old clips that weren't matched — disconnect fadeGainNode to\n    // silence any active source (audio path broken, no audible pop)\n    for (let i = 0; i < this.scheduledClips.length; i++) {\n      if (!matched.has(i)) {\n        const scheduled = this.scheduledClips[i];\n        try {\n          tp.clear(scheduled.scheduleId);\n        } catch (err) {\n          console.warn(\n            '[waveform-playlist] replaceClips: clear failed on track \"' +\n              this.id +\n              '\": ' +\n              String(err)\n          );\n        }\n        try {\n          scheduled.fadeGainNode.disconnect();\n        } catch (err) {\n          console.warn(\n            '[waveform-playlist] replaceClips: disconnect failed on track \"' +\n              this.id +\n              '\": ' +\n              String(err)\n          );\n        }\n      }\n    }\n\n    this.scheduledClips = kept;\n\n    // Add new/changed clips — start mid-clip source if Transport is running\n    const isPlaying = tp.state === 'started';\n    for (const clipInfo of toAdd) {\n      const scheduled = this.addClip(clipInfo);\n      if (isPlaying) {\n        const context = getContext();\n        const transportOffset = tp.seconds;\n        const audioContextTime = context.currentTime;\n        // Transport runs lookAhead ahead of what's audible. The old source was\n        // disconnected but had ~lookAhead of buffered audio still playing.\n        // Start the new source lookAhead earlier so it overlaps with the\n        // remaining buffered audio, producing a seamless transition.\n        const lookAhead = context.lookAhead ?? 0;\n        const audibleOffset = Math.max(0, transportOffset - lookAhead);\n        const absClipStart = this.track.startTime + clipInfo.startTime;\n        const absClipEnd = absClipStart + clipInfo.duration;\n        if (absClipStart < transportOffset && absClipEnd > audibleOffset) {\n          const elapsed = audibleOffset - absClipStart;\n          this.startClipSource(\n            clipInfo,\n            scheduled.fadeGainNode,\n            audioContextTime,\n            clipInfo.offset + Math.max(0, elapsed),\n            clipInfo.duration - Math.max(0, elapsed)\n          );\n          // Schedule the clip's fade envelope for this play cycle — without\n          // it the clip plays at flat gain until the next play().\n          this.scheduleFades(scheduled, audioContextTime, clipInfo.offset + Math.max(0, elapsed));\n        } else if (absClipStart >= transportOffset) {\n          // Clip starts later this cycle: its schedule callback will create\n          // the source, but fades were prepared at play() — before this clip\n          // existed. Schedule them now at the clip's future start time.\n          const delay = absClipStart - transportOffset;\n          this.scheduleFades(scheduled, audioContextTime + delay, clipInfo.offset);\n        }\n      }\n    }\n  }\n\n  /** Compare two clips by reference (buffer), timing, and fade properties */\n  private _clipsEqual(a: ClipInfo, b: ClipInfo): boolean {\n    return (\n      a.buffer === b.buffer &&\n      a.startTime === b.startTime &&\n      a.duration === b.duration &&\n      a.offset === b.offset &&\n      a.gain === b.gain &&\n      a.fadeIn?.duration === b.fadeIn?.duration &&\n      a.fadeIn?.type === b.fadeIn?.type &&\n      a.fadeOut?.duration === b.fadeOut?.duration &&\n      a.fadeOut?.type === b.fadeOut?.type\n    );\n  }\n\n  /**\n   * Stop all active AudioBufferSourceNodes and clear the set.\n   * Native AudioBufferSourceNodes ignore Transport state changes —\n   * they must be explicitly stopped.\n   */\n  stopAllSources(): void {\n    this.activeSources.forEach((source) => {\n      try {\n        source.stop();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error stopping source on track \"${this.id}\":` + ' ' + String(err)\n        );\n      }\n    });\n    this.activeSources.clear();\n  }\n\n  /**\n   * Schedule fade envelopes for a clip at the given AudioContext time.\n   * Uses native GainNode.gain (AudioParam) directly — no _param workaround needed.\n   */\n  private scheduleFades(\n    scheduled: ScheduledClip,\n    clipStartTime: number,\n    clipOffset: number = 0\n  ): void {\n    const { clipInfo, fadeGainNode } = scheduled;\n    const audioParam = fadeGainNode.gain;\n\n    // Cancel any previous automation\n    audioParam.cancelScheduledValues(0);\n\n    // Calculate how much of the clip we're skipping (for seeking)\n    const skipTime = clipOffset - clipInfo.offset;\n\n    // Apply fade in if it exists and we haven't skipped past it\n    if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n      const fadeInDuration = clipInfo.fadeIn.duration;\n\n      if (skipTime <= 0) {\n        applyFadeIn(\n          audioParam,\n          clipStartTime,\n          fadeInDuration,\n          clipInfo.fadeIn.type || 'linear',\n          0,\n          clipInfo.gain\n        );\n      } else {\n        const remainingFadeDuration = fadeInDuration - skipTime;\n        const fadeProgress = skipTime / fadeInDuration;\n        const startValue = clipInfo.gain * fadeProgress;\n        applyFadeIn(\n          audioParam,\n          clipStartTime,\n          remainingFadeDuration,\n          clipInfo.fadeIn.type || 'linear',\n          startValue,\n          clipInfo.gain\n        );\n      }\n    } else {\n      audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n    }\n\n    // Apply fade out if it exists\n    if (clipInfo.fadeOut) {\n      const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n      const fadeOutStartInClip = fadeOutStart - skipTime;\n\n      if (fadeOutStartInClip > 0) {\n        const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n        applyFadeOut(\n          audioParam,\n          absoluteFadeOutStart,\n          clipInfo.fadeOut.duration,\n          clipInfo.fadeOut.type || 'linear',\n          clipInfo.gain,\n          0\n        );\n      } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n        const elapsedFadeOut = -fadeOutStartInClip;\n        const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n        const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n        const startValue = clipInfo.gain * (1 - fadeProgress);\n        applyFadeOut(\n          audioParam,\n          clipStartTime,\n          remainingFadeDuration,\n          clipInfo.fadeOut.type || 'linear',\n          startValue,\n          0\n        );\n      }\n    }\n  }\n\n  /**\n   * Prepare fade envelopes for all clips based on Transport offset.\n   * Called before Transport.start() to schedule fades at correct AudioContext times.\n   */\n  prepareFades(when: number, transportOffset: number): void {\n    this.scheduledClips.forEach((scheduled) => {\n      const absClipStart = this.track.startTime + scheduled.clipInfo.startTime;\n      const absClipEnd = absClipStart + scheduled.clipInfo.duration;\n\n      if (transportOffset >= absClipEnd) return; // clip already finished\n\n      if (transportOffset >= absClipStart) {\n        // Mid-clip: playing now\n        const clipOffset = transportOffset - absClipStart + scheduled.clipInfo.offset;\n        this.scheduleFades(scheduled, when, clipOffset);\n      } else {\n        // Clip starts later\n        const delay = absClipStart - transportOffset;\n        this.scheduleFades(scheduled, when + delay, scheduled.clipInfo.offset);\n      }\n    });\n  }\n\n  /**\n   * Cancel all scheduled fade automation and reset to nominal gain.\n   * Called on pause/stop to prevent stale fade envelopes.\n   */\n  cancelFades(): void {\n    this.scheduledClips.forEach(({ fadeGainNode, clipInfo }) => {\n      const audioParam = fadeGainNode.gain;\n      audioParam.cancelScheduledValues(0);\n      audioParam.setValueAtTime(clipInfo.gain, 0);\n    });\n  }\n\n  setVolume(gain: number): void {\n    this.track.gain = gain;\n    this.volumeNode.volume.value = gainToDb(gain);\n  }\n\n  setPan(pan: number): void {\n    this.track.stereoPan = pan;\n    this.panNode.pan.value = pan;\n  }\n\n  setMute(muted: boolean): void {\n    this.track.muted = muted;\n    const value = muted ? 0 : 1;\n    // Use setValueAtTime on the raw AudioParam to ensure the value is applied\n    // even when the AudioContext is suspended. Setting .gain.value on the Tone.js\n    // Signal wrapper doesn't propagate to the underlying AudioParam until the\n    // context resumes, causing a brief audio glitch (e.g., all tracks audible\n    // before solo muting takes effect).\n    const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n    audioParam?.setValueAtTime(value, 0);\n    this.muteGain.gain.value = value;\n  }\n\n  setSolo(soloed: boolean): void {\n    this.track.soloed = soloed;\n  }\n\n  /**\n   * Insert an external effects chain: reroute muteGain → node instead of\n   * muteGain → destination. The caller wires the chain's output onward\n   * (dawcore's EffectsManager connects it to the master bus input).\n   * Mutually exclusive with the TrackEffectsFunction closure model.\n   */\n  connectEffects(node: AudioNode): void {\n    if (this._hasClosureEffects) {\n      throw new Error(\n        '[waveform-playlist] Track \"' +\n          this.track.id +\n          '\" was built with a TrackEffectsFunction closure — transport effects hooks and ' +\n          'closure effects are mutually exclusive.'\n      );\n    }\n    // Guard the disconnect (as disconnectEffects does) — a consumer may have\n    // disposed the old chain node externally; the rewire must proceed.\n    try {\n      if (this._effectsChainNode) {\n        this.muteGain.disconnect(this._effectsChainNode);\n      } else {\n        this.muteGain.disconnect(this._destination);\n      }\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] connectEffects disconnect on track \"' +\n          this.track.id +\n          '\": ' +\n          String(err)\n      );\n    }\n    this.muteGain.connect(node);\n    this._effectsChainNode = node;\n  }\n\n  /** Restore the direct muteGain → destination connection. Safe when nothing is connected. */\n  disconnectEffects(): void {\n    if (!this._effectsChainNode) return;\n    try {\n      this.muteGain.disconnect(this._effectsChainNode);\n    } catch (err) {\n      console.warn(\n        '[waveform-playlist] disconnectEffects: ' +\n          (err instanceof Error ? err.message : String(err))\n      );\n    }\n    this.muteGain.connect(this._destination);\n    this._effectsChainNode = null;\n  }\n\n  dispose(): void {\n    const transport = getTransport();\n\n    if (this.effectsCleanup) {\n      try {\n        this.effectsCleanup();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error during track \"${this.id}\" effects cleanup:` + ' ' + String(err)\n        );\n      }\n    }\n\n    this.stopAllSources();\n\n    // Clear Transport schedule events and disconnect native fade gain nodes\n    this.scheduledClips.forEach((scheduled, index) => {\n      try {\n        transport.clear(scheduled.scheduleId);\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error clearing schedule ${index} on track \"${this.id}\":` +\n            ' ' +\n            String(err)\n        );\n      }\n      try {\n        scheduled.fadeGainNode.disconnect();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error disconnecting fadeGain ${index} on track \"${this.id}\":` +\n            ' ' +\n            String(err)\n        );\n      }\n    });\n\n    try {\n      this.volumeNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing volumeNode on track \"${this.id}\":` + ' ' + String(err)\n      );\n    }\n    try {\n      this.panNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing panNode on track \"${this.id}\":` + ' ' + String(err)\n      );\n    }\n    try {\n      this.muteGain.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing muteGain on track \"${this.id}\":` + ' ' + String(err)\n      );\n    }\n  }\n\n  get id(): string {\n    return this.track.id;\n  }\n\n  get duration(): number {\n    // Max end across clips — the array is NOT sorted by startTime\n    // (replaceClips/addClip append), so array-last is not the latest-ending.\n    let max = 0;\n    for (const { clipInfo } of this.scheduledClips) {\n      const end = clipInfo.startTime + clipInfo.duration;\n      if (end > max) max = end;\n    }\n    return max;\n  }\n\n  get buffer(): AudioBuffer {\n    return this.scheduledClips[0]?.clipInfo.buffer;\n  }\n\n  get muted(): boolean {\n    return this.track.muted;\n  }\n\n  get startTime(): number {\n    return this.track.startTime;\n  }\n}\n","/**\n * Tone.js-specific fade helpers. Pure fade utilities (curves, applyFadeIn/\n * applyFadeOut) live in @waveform-playlist/core — import them from there\n * directly (repo rule: no cross-package re-exports).\n */\n\n/**\n * Access the underlying Web Audio AudioParam from a Tone.js Signal/Param wrapper.\n *\n * Tone.js wraps native AudioParam in its Signal class, but sometimes we need\n * direct access to the raw AudioParam for setValueAtTime/cancelScheduledValues\n * (e.g., when the AudioContext is suspended and Tone.js Signal doesn't propagate).\n *\n * This uses `_param` which is a private Tone.js 15.x internal.\n * Pin the Tone.js version carefully if upgrading.\n *\n * @param signal - A Tone.js Signal or Param wrapper (e.g., `gain.gain`)\n * @returns The underlying AudioParam, or undefined if not found\n */\nlet hasWarned = false;\n\nexport function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined {\n  const param = (signal as { _param?: AudioParam } | null | undefined)?._param;\n  if (!param && !hasWarned) {\n    hasWarned = true;\n    console.warn(\n      '[waveform-playlist] Unable to access Tone.js internal _param. ' +\n        'This likely means the Tone.js version is incompatible. ' +\n        'Mute scheduling may not work correctly.'\n    );\n  }\n  return param;\n}\n","import {\n  Volume,\n  Gain,\n  Panner,\n  PolySynth,\n  Synth,\n  MembraneSynth,\n  MetalSynth,\n  NoiseSynth,\n  Part,\n  ToneAudioNode,\n  getDestination,\n  getContext,\n} from 'tone';\nimport type { SynthOptions } from 'tone';\nimport { Track, gainToDb, type MidiNoteData } from '@waveform-playlist/core';\nimport { getUnderlyingAudioParam } from './fades';\nimport type { TrackEffectsFunction } from './ToneTrack';\n\n/**\n * Shared interface for tracks managed by TonePlayout.\n * Both ToneTrack (audio) and MidiToneTrack (MIDI) implement this,\n * allowing TonePlayout to manage them uniformly.\n */\nexport interface PlayableTrack {\n  id: string;\n  startTime: number;\n  muted: boolean;\n  duration: number;\n  stopAllSources(): void;\n  startMidClipSources(offset: number, time: number): void;\n  setScheduleGuardOffset(offset: number): void;\n  prepareFades(when: number, offset: number): void;\n  cancelFades(): void;\n  setVolume(gain: number): void;\n  setPan(pan: number): void;\n  setMute(muted: boolean): void;\n  setSolo(soloed: boolean): void;\n  dispose(): void;\n}\n\nexport interface MidiClipInfo {\n  notes: MidiNoteData[];\n  startTime: number; // When this clip starts relative to track start (seconds)\n  duration: number; // Clip duration (seconds)\n  offset: number; // Trim offset into the MIDI data (seconds)\n}\n\nexport interface MidiToneTrackOptions {\n  clips: MidiClipInfo[];\n  track: Track;\n  effects?: TrackEffectsFunction;\n  destination?: ToneAudioNode;\n  synthOptions?: Partial<SynthOptions>;\n}\n\n/**\n * Categorize GM percussion note numbers into synth types.\n * See: https://www.midi.org/specifications-old/item/gm-level-1-sound-set\n */\ntype DrumCategory = 'kick' | 'snare' | 'cymbal' | 'tom';\n\nfunction getDrumCategory(midiNote: number): DrumCategory {\n  // Bass drums\n  if (midiNote === 35 || midiNote === 36) return 'kick';\n  // Snare, side stick, clap\n  if (midiNote >= 37 && midiNote <= 40) return 'snare';\n  // Toms (low floor tom through high tom)\n  if (\n    midiNote === 41 ||\n    midiNote === 43 ||\n    midiNote === 45 ||\n    midiNote === 47 ||\n    midiNote === 48 ||\n    midiNote === 50\n  )\n    return 'tom';\n  // Hi-hats, cymbals, bells, tambourine, cowbell, etc.\n  return 'cymbal';\n}\n\n/** Per-clip scheduling info */\ninterface ScheduledMidiClip {\n  clipInfo: MidiClipInfo;\n  part: Part;\n}\n\n/**\n * MIDI track that always creates both melodic and percussion synths.\n * Per-note routing uses the `channel` field on each MidiNoteData:\n * channel 9 → percussion synths, all others → melodic PolySynth.\n * This enables flattened tracks (mixed channels) to play correctly.\n */\nexport class MidiToneTrack implements PlayableTrack {\n  private scheduledClips: ScheduledMidiClip[];\n  // Melodic synth — always created\n  private synth: PolySynth;\n  // Percussion synths — always created (PolySynth wrappers for polyphony)\n  private kickSynth: PolySynth<MembraneSynth>;\n  private snareSynth: NoiseSynth; // No pitch param, can't wrap in PolySynth\n  private cymbalSynth: PolySynth<MetalSynth>;\n  private tomSynth: PolySynth<MembraneSynth>;\n  private volumeNode: Volume;\n  private panNode: Panner;\n  private muteGain: Gain;\n  private track: Track;\n  private effectsCleanup?: () => void;\n\n  constructor(options: MidiToneTrackOptions) {\n    this.track = options.track;\n\n    // Create shared track-level Tone.js nodes (same chain as ToneTrack)\n    this.volumeNode = new Volume(gainToDb(options.track.gain));\n    this.panNode = new Panner(options.track.stereoPan);\n    this.muteGain = new Gain(options.track.muted ? 0 : 1);\n    this.volumeNode.chain(this.panNode, this.muteGain);\n\n    // Melodic: PolySynth with basic Synth voice\n    this.synth = new PolySynth(Synth, options.synthOptions);\n    this.synth.connect(this.volumeNode);\n\n    // Percussion: PolySynth wrappers for polyphonic playback\n    this.kickSynth = new PolySynth(MembraneSynth, {\n      voice: MembraneSynth,\n      options: {\n        pitchDecay: 0.05,\n        octaves: 6,\n        envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.1 },\n      },\n    } as never);\n    this.snareSynth = new NoiseSynth({\n      noise: { type: 'white' },\n      envelope: { attack: 0.001, decay: 0.15, sustain: 0, release: 0.05 },\n    });\n    this.cymbalSynth = new PolySynth(MetalSynth, {\n      voice: MetalSynth,\n      options: {\n        envelope: { attack: 0.001, decay: 0.3, release: 0.1 },\n        harmonicity: 5.1,\n        modulationIndex: 32,\n        resonance: 4000,\n        octaves: 1.5,\n      },\n    } as never);\n    this.tomSynth = new PolySynth(MembraneSynth, {\n      voice: MembraneSynth,\n      options: {\n        pitchDecay: 0.08,\n        octaves: 4,\n        envelope: { attack: 0.001, decay: 0.3, sustain: 0, release: 0.1 },\n      },\n    } as never);\n\n    this.kickSynth.connect(this.volumeNode);\n    this.snareSynth.connect(this.volumeNode);\n    this.cymbalSynth.connect(this.volumeNode);\n    this.tomSynth.connect(this.volumeNode);\n\n    // Connect to destination or apply effects chain\n    const destination = options.destination || getDestination();\n    if (options.effects) {\n      const cleanup = options.effects(this.muteGain, destination, false);\n      if (cleanup) {\n        this.effectsCleanup = cleanup;\n      }\n    } else {\n      this.muteGain.connect(destination);\n    }\n\n    // Create a Tone.Part for each clip, scheduling notes relative to Transport\n    this.scheduledClips = options.clips.map((clipInfo) => {\n      // Filter notes within the clip's visible window (after trim offset)\n      const visibleNotes = clipInfo.notes.filter((note) => {\n        const noteEnd = note.time + note.duration;\n        // Note must start before clip ends and end after clip's trim offset\n        return note.time < clipInfo.offset + clipInfo.duration && noteEnd > clipInfo.offset;\n      });\n\n      // Create Part events with absolute Transport times\n      const absClipStart = this.track.startTime + clipInfo.startTime;\n\n      const partEvents = visibleNotes.map((note) => {\n        // Adjust note timing relative to clip's trim offset\n        const adjustedTime = note.time - clipInfo.offset;\n        // Clamp to clip boundaries\n        const clampedStart = Math.max(0, adjustedTime);\n        const clampedDuration = Math.min(\n          note.duration - Math.max(0, clipInfo.offset - note.time),\n          clipInfo.duration - clampedStart\n        );\n\n        return {\n          time: absClipStart + clampedStart,\n          note: note.name,\n          midi: note.midi,\n          duration: Math.max(0, clampedDuration),\n          velocity: note.velocity,\n          channel: note.channel,\n        };\n      });\n\n      const part = new Part((time, event) => {\n        if (event.duration > 0) {\n          this.triggerNote(\n            event.midi,\n            event.note,\n            event.duration,\n            time,\n            event.velocity,\n            event.channel\n          );\n        }\n      }, partEvents);\n\n      // Part starts automatically with Transport\n      part.start(0);\n\n      return { clipInfo, part };\n    });\n  }\n\n  /**\n   * Trigger a note using the appropriate synth.\n   * Routes per-note: channel 9 → percussion synths, others → melodic PolySynth.\n   */\n  private triggerNote(\n    midiNote: number,\n    noteName: string,\n    duration: number,\n    time: number,\n    velocity: number,\n    channel?: number\n  ): void {\n    if (channel === 9) {\n      const category = getDrumCategory(midiNote);\n      switch (category) {\n        case 'kick':\n          this.kickSynth.triggerAttackRelease('C1', duration, time, velocity);\n          break;\n        case 'snare':\n          // NoiseSynth is monophonic — wrap in try-catch for rare overlaps\n          try {\n            this.snareSynth.triggerAttackRelease(duration, time, velocity);\n          } catch (err) {\n            console.warn(\n              '[waveform-playlist] Snare overlap — previous hit still decaying, skipped:' +\n                ' ' +\n                String(err)\n            );\n          }\n          break;\n        case 'tom': {\n          const tomPitches: Record<number, string> = {\n            41: 'G1',\n            43: 'A1',\n            45: 'C2',\n            47: 'D2',\n            48: 'E2',\n            50: 'G2',\n          };\n          this.tomSynth.triggerAttackRelease(\n            tomPitches[midiNote] || 'C2',\n            duration,\n            time,\n            velocity\n          );\n          break;\n        }\n        case 'cymbal':\n          // PolySynth requires a note arg; MetalSynth uses it as fundamental frequency.\n          // 'C4' provides a reasonable metallic timbre for all cymbal/hi-hat hits.\n          this.cymbalSynth.triggerAttackRelease('C4', duration, time, velocity);\n          break;\n      }\n    } else {\n      this.synth.triggerAttackRelease(noteName, duration, time, velocity);\n    }\n  }\n\n  /**\n   * No-op for MIDI — schedule guard is for AudioBufferSourceNode ghost tick prevention.\n   * Tone.Part handles its own scheduling relative to Transport.\n   */\n  setScheduleGuardOffset(_offset: number): void {\n    // No-op: Tone.Part handles scheduling internally\n  }\n\n  /**\n   * For MIDI, mid-clip sources are notes that should already be sounding.\n   * We trigger them with their remaining duration.\n   */\n  startMidClipSources(transportOffset: number, audioContextTime: number): void {\n    for (const { clipInfo } of this.scheduledClips) {\n      const absClipStart = this.track.startTime + clipInfo.startTime;\n      const absClipEnd = absClipStart + clipInfo.duration;\n\n      if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n        // Find notes that should be currently sounding\n        for (const note of clipInfo.notes) {\n          const adjustedTime = note.time - clipInfo.offset;\n          const noteAbsStart = absClipStart + Math.max(0, adjustedTime);\n          const noteAbsEnd = noteAbsStart + note.duration;\n\n          if (noteAbsStart < transportOffset && noteAbsEnd > transportOffset) {\n            const remainingDuration = noteAbsEnd - transportOffset;\n            try {\n              this.triggerNote(\n                note.midi,\n                note.name,\n                remainingDuration,\n                audioContextTime,\n                note.velocity,\n                note.channel\n              );\n            } catch (err) {\n              console.warn(\n                `[waveform-playlist] Failed to start mid-clip MIDI note \"${note.name}\" on track \"${this.id}\":` +\n                  ' ' +\n                  String(err)\n              );\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Stop all sounding notes and cancel scheduled Part events.\n   */\n  stopAllSources(): void {\n    const now = getContext().rawContext.currentTime;\n    // Per-synth guard: one failing releaseAll must not skip the others —\n    // this runs on every stop() and loop boundary, and a skipped percussion\n    // release leaves voices ringing/stuck.\n    const release = (label: string, fn: () => void): void => {\n      try {\n        fn();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error releasing ${label} synth on track \"${this.id}\": ` + String(err)\n        );\n      }\n    };\n    release('melodic', () => this.synth.releaseAll(now));\n    release('kick', () => this.kickSynth.releaseAll(now));\n    release('cymbal', () => this.cymbalSynth.releaseAll(now));\n    release('tom', () => this.tomSynth.releaseAll(now));\n    // NoiseSynth has no releaseAll — it decays naturally via short envelope\n  }\n\n  /**\n   * No-op for MIDI — MIDI uses note velocity, not gain fades.\n   */\n  prepareFades(_when: number, _offset: number): void {\n    // No-op: MIDI clips don't use fade envelopes\n  }\n\n  /**\n   * No-op for MIDI — no fade automation to cancel.\n   */\n  cancelFades(): void {\n    // No-op: MIDI clips don't use fade envelopes\n  }\n\n  setVolume(gain: number): void {\n    this.track.gain = gain;\n    this.volumeNode.volume.value = gainToDb(gain);\n  }\n\n  setPan(pan: number): void {\n    this.track.stereoPan = pan;\n    this.panNode.pan.value = pan;\n  }\n\n  setMute(muted: boolean): void {\n    this.track.muted = muted;\n    const value = muted ? 0 : 1;\n    const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n    audioParam?.setValueAtTime(value, 0);\n    this.muteGain.gain.value = value;\n  }\n\n  setSolo(soloed: boolean): void {\n    this.track.soloed = soloed;\n  }\n\n  dispose(): void {\n    if (this.effectsCleanup) {\n      try {\n        this.effectsCleanup();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error during MIDI track \"${this.id}\" effects cleanup:` +\n            ' ' +\n            String(err)\n        );\n      }\n    }\n\n    this.stopAllSources();\n\n    // Dispose Parts\n    this.scheduledClips.forEach(({ part }, index) => {\n      try {\n        part.dispose();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error disposing Part ${index} on MIDI track \"${this.id}\":` +\n            ' ' +\n            String(err)\n        );\n      }\n    });\n\n    // Dispose all synths\n    const synthsToDispose = [\n      this.synth,\n      this.kickSynth,\n      this.snareSynth,\n      this.cymbalSynth,\n      this.tomSynth,\n    ];\n    for (const s of synthsToDispose) {\n      try {\n        s?.dispose();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error disposing synth on MIDI track \"${this.id}\":` +\n            ' ' +\n            String(err)\n        );\n      }\n    }\n    try {\n      this.volumeNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing volumeNode on MIDI track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n    try {\n      this.panNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing panNode on MIDI track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n    try {\n      this.muteGain.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing muteGain on MIDI track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n  }\n\n  get id(): string {\n    return this.track.id;\n  }\n\n  get duration(): number {\n    // Max end across clips — the array is NOT sorted by startTime\n    // (replaceClips/edits append), so array-last is not the latest-ending.\n    let max = 0;\n    for (const { clipInfo } of this.scheduledClips) {\n      const end = clipInfo.startTime + clipInfo.duration;\n      if (end > max) max = end;\n    }\n    return max;\n  }\n\n  get muted(): boolean {\n    return this.track.muted;\n  }\n\n  get startTime(): number {\n    return this.track.startTime;\n  }\n}\n","import { Volume, Gain, Panner, Part, ToneAudioNode, getDestination, getContext } from 'tone';\nimport { Track, gainToDb } from '@waveform-playlist/core';\nimport { getUnderlyingAudioParam } from './fades';\nimport type { TrackEffectsFunction } from './ToneTrack';\nimport type { PlayableTrack, MidiClipInfo } from './MidiToneTrack';\nimport type { SoundFontCache } from './SoundFontCache';\n\nexport interface SoundFontToneTrackOptions {\n  clips: MidiClipInfo[];\n  track: Track;\n  soundFontCache: SoundFontCache;\n  /** GM program number (0-127) for melodic instruments */\n  programNumber?: number;\n  /** Whether this track uses percussion bank (channel 9) */\n  isPercussion?: boolean;\n  effects?: TrackEffectsFunction;\n  destination?: ToneAudioNode;\n}\n\n/** Per-clip scheduling info */\ninterface ScheduledMidiClip {\n  clipInfo: MidiClipInfo;\n  part: Part;\n}\n\n/**\n * Analytic value of the scheduled AHD envelope at `tRel` seconds after\n * note-on. Mirrors the automation emitted by triggerNote exactly, including\n * the hold-event threshold: hold <= 0.001 emits no hold event, so the decay\n * ramp spans [attack, attack + hold + decay].\n *\n * Exported for direct unit testing; NOT part of the public API — do not\n * re-export from index.ts.\n */\nexport function envelopeValueAt(\n  tRel: number,\n  attack: number,\n  hold: number,\n  decay: number,\n  peakGain: number,\n  sustainGain: number\n): number {\n  if (tRel <= 0) return 0;\n  if (tRel < attack) return peakGain * (tRel / attack);\n  const rampStart = hold > 0.001 ? attack + hold : attack;\n  const rampEnd = attack + hold + decay;\n  if (tRel <= rampStart || rampEnd <= rampStart) {\n    return tRel >= rampEnd ? sustainGain : peakGain;\n  }\n  if (tRel < rampEnd) {\n    return peakGain + (sustainGain - peakGain) * ((tRel - rampStart) / (rampEnd - rampStart));\n  }\n  return sustainGain;\n}\n\n/**\n * MIDI track that uses SoundFont samples for playback.\n *\n * Instead of PolySynth synthesis, each note triggers the correct instrument\n * sample from an SF2 file, pitch-shifted via AudioBufferSourceNode.playbackRate.\n *\n * Audio graph per note:\n *   AudioBufferSourceNode (native, one-shot, pitch-shifted)\n *     → GainNode (native, per-note velocity)\n *     → Volume.input (Tone.js, shared per-track)\n *     → Panner → muteGain → effects/destination\n */\nexport class SoundFontToneTrack implements PlayableTrack {\n  /** Rate-limit missing sample warnings — one per track instance (a page-\n   *  lifetime static would silence diagnostics for unrelated later tracks\n   *  and soundfont swaps). */\n  private _missingSampleWarned = false;\n  private scheduledClips: ScheduledMidiClip[];\n  private activeSources: Set<AudioBufferSourceNode> = new Set();\n  private soundFontCache: SoundFontCache;\n  private programNumber: number;\n  private bankNumber: number;\n  private volumeNode: Volume;\n  private panNode: Panner;\n  private muteGain: Gain;\n  private track: Track;\n  private effectsCleanup?: () => void;\n\n  constructor(options: SoundFontToneTrackOptions) {\n    this.track = options.track;\n    this.soundFontCache = options.soundFontCache;\n    this.programNumber = options.programNumber ?? 0;\n    // Bank 128 for percussion (channel 9), bank 0 for melodic\n    this.bankNumber = options.isPercussion ? 128 : 0;\n\n    // Create shared track-level Tone.js nodes (same chain as ToneTrack)\n    this.volumeNode = new Volume(gainToDb(options.track.gain));\n    this.panNode = new Panner(options.track.stereoPan);\n    this.muteGain = new Gain(options.track.muted ? 0 : 1);\n    this.volumeNode.chain(this.panNode, this.muteGain);\n\n    // Connect to destination or apply effects chain\n    const destination = options.destination || getDestination();\n    if (options.effects) {\n      const cleanup = options.effects(this.muteGain, destination, false);\n      if (cleanup) {\n        this.effectsCleanup = cleanup;\n      }\n    } else {\n      this.muteGain.connect(destination);\n    }\n\n    // Create a Tone.Part for each clip, scheduling notes relative to Transport\n    this.scheduledClips = options.clips.map((clipInfo) => {\n      // Filter notes within the clip's visible window (after trim offset)\n      const visibleNotes = clipInfo.notes.filter((note) => {\n        const noteEnd = note.time + note.duration;\n        return note.time < clipInfo.offset + clipInfo.duration && noteEnd > clipInfo.offset;\n      });\n\n      const absClipStart = this.track.startTime + clipInfo.startTime;\n\n      const partEvents = visibleNotes.map((note) => {\n        const adjustedTime = note.time - clipInfo.offset;\n        const clampedStart = Math.max(0, adjustedTime);\n        const clampedDuration = Math.min(\n          note.duration - Math.max(0, clipInfo.offset - note.time),\n          clipInfo.duration - clampedStart\n        );\n\n        return {\n          time: absClipStart + clampedStart,\n          note: note.name,\n          midi: note.midi,\n          duration: Math.max(0, clampedDuration),\n          velocity: note.velocity,\n          channel: note.channel,\n        };\n      });\n\n      const part = new Part((time, event) => {\n        if (event.duration > 0) {\n          // Contain failures: nothing in Tone.js catches a throw from a Part\n          // callback (ToneEvent._tick → Transport._processTick → Clock._loop\n          // are all unguarded) — an escape aborts every other same-tick event\n          // across ALL tracks. Same guard as startMidClipSources below.\n          try {\n            this.triggerNote(event.midi, event.duration, time, event.velocity, event.channel);\n          } catch (err) {\n            console.warn(\n              `[waveform-playlist] Failed to trigger SoundFont note on track \"${this.id}\": ` +\n                String(err)\n            );\n          }\n        }\n      }, partEvents);\n\n      part.start(0);\n\n      return { clipInfo, part };\n    });\n  }\n\n  /**\n   * Trigger a note by creating a native AudioBufferSourceNode from the SoundFont cache.\n   *\n   * Per-note routing: channel 9 → bank 128 (drums), others → bank 0 with programNumber.\n   */\n  private triggerNote(\n    midiNote: number,\n    duration: number,\n    time: number,\n    velocity: number,\n    channel?: number\n  ): void {\n    const bank = channel === 9 ? 128 : this.bankNumber;\n    const preset = channel === 9 ? 0 : this.programNumber;\n\n    const sfSample = this.soundFontCache.getAudioBuffer(midiNote, bank, preset);\n    if (!sfSample) {\n      if (!this._missingSampleWarned) {\n        console.warn(\n          `[waveform-playlist] SoundFont sample not found for MIDI note ${midiNote} (bank ${bank}, preset ${preset}) on track \"${this.id}\". ` +\n            'Subsequent missing samples on this track will be silent.'\n        );\n        this._missingSampleWarned = true;\n      }\n      return;\n    }\n\n    const rawContext = getContext().rawContext as BaseAudioContext;\n\n    // Create AudioBufferSourceNode with optional looping\n    const source = rawContext.createBufferSource();\n    source.buffer = sfSample.buffer;\n    source.playbackRate.value = sfSample.playbackRate;\n\n    // Enable sample looping if SF2 zone defines loop mode\n    if (sfSample.loopMode === 1 || sfSample.loopMode === 3) {\n      source.loop = true;\n      source.loopStart = sfSample.loopStart;\n      source.loopEnd = sfSample.loopEnd;\n    }\n\n    // For non-looping samples (percussion, one-shots), use the sample's natural\n    // buffer duration so cymbals/drums ring out fully. For looping samples\n    // (sustained instruments), use the MIDI note duration for note-off timing.\n    const sampleDuration = sfSample.buffer.duration / sfSample.playbackRate;\n    const effectiveDuration =\n      sfSample.loopMode === 0 ? Math.max(duration, sampleDuration) : duration;\n\n    // Per-note gain with SF2 volume ADSR envelope\n    const peakGain = velocity * velocity;\n    const gainNode = rawContext.createGain();\n    const { attackVolEnv, holdVolEnv, decayVolEnv, sustainVolEnv, releaseVolEnv } = sfSample;\n    const sustainGain = peakGain * sustainVolEnv;\n\n    // AHD envelope TRUNCATED at note-off. The duration is known up-front, so\n    // schedule only the envelope that actually plays. Scheduling the full AHD\n    // and stepping to sustain at note-off is a CONFIRMED click for staccato\n    // notes on long-decay patches: the decay ramp's endpoint sorts after\n    // note-off, so it never governs — the gain holds at PEAK for the whole\n    // note, then steps peak→sustain in one sample (Chromium gain-curve\n    // capture, 2026-07-09). A truncated partial ramp lies on the same decay\n    // line, so notes that outlive the decay are scheduled identically to\n    // before.\n    const noteOff = time + effectiveDuration;\n    const attackEnd = time + attackVolEnv;\n    const decayStart = time + attackVolEnv + holdVolEnv;\n    const decayEnd = decayStart + decayVolEnv;\n    const heldAtNoteOff = envelopeValueAt(\n      effectiveDuration,\n      attackVolEnv,\n      holdVolEnv,\n      decayVolEnv,\n      peakGain,\n      sustainGain\n    );\n\n    gainNode.gain.setValueAtTime(0, time);\n    if (noteOff < attackEnd) {\n      // Note-off mid-attack: partial attack ramp\n      gainNode.gain.linearRampToValueAtTime(heldAtNoteOff, noteOff);\n    } else {\n      gainNode.gain.linearRampToValueAtTime(peakGain, attackEnd);\n      if (holdVolEnv > 0.001 && decayStart < noteOff) {\n        gainNode.gain.setValueAtTime(peakGain, decayStart);\n      }\n      if (noteOff < decayEnd) {\n        // Note-off mid-hold/mid-decay: partial ramp to the analytic value\n        gainNode.gain.linearRampToValueAtTime(heldAtNoteOff, noteOff);\n      } else {\n        gainNode.gain.linearRampToValueAtTime(sustainGain, decayEnd);\n        gainNode.gain.setValueAtTime(sustainGain, noteOff);\n      }\n    }\n    // Release from wherever the envelope was at note-off\n    gainNode.gain.linearRampToValueAtTime(0, noteOff + releaseVolEnv);\n\n    // Connect: source → gainNode → Volume.input (Tone.js)\n    source.connect(gainNode);\n    gainNode.connect((this.volumeNode.input as unknown as Gain).input);\n\n    // Track active sources for stopAllSources()\n    this.activeSources.add(source);\n    source.onended = () => {\n      this.activeSources.delete(source);\n      try {\n        gainNode.disconnect();\n      } catch (err) {\n        console.warn('[waveform-playlist] GainNode already disconnected:' + ' ' + String(err));\n      }\n    };\n\n    source.start(time);\n    source.stop(time + effectiveDuration + releaseVolEnv);\n  }\n\n  /**\n   * No-op — Tone.Part handles scheduling internally, no ghost tick guard needed.\n   */\n  setScheduleGuardOffset(_offset: number): void {\n    // No-op\n  }\n\n  /**\n   * Start notes that should already be sounding at the current transport offset.\n   */\n  startMidClipSources(transportOffset: number, audioContextTime: number): void {\n    for (const { clipInfo } of this.scheduledClips) {\n      const absClipStart = this.track.startTime + clipInfo.startTime;\n      const absClipEnd = absClipStart + clipInfo.duration;\n\n      if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n        for (const note of clipInfo.notes) {\n          const adjustedTime = note.time - clipInfo.offset;\n          const noteAbsStart = absClipStart + Math.max(0, adjustedTime);\n          const noteAbsEnd = noteAbsStart + note.duration;\n\n          if (noteAbsStart < transportOffset && noteAbsEnd > transportOffset) {\n            const remainingDuration = noteAbsEnd - transportOffset;\n            try {\n              this.triggerNote(\n                note.midi,\n                remainingDuration,\n                audioContextTime,\n                note.velocity,\n                note.channel\n              );\n            } catch (err) {\n              console.warn(\n                `[waveform-playlist] Failed to start mid-clip SoundFont note on track \"${this.id}\":` +\n                  ' ' +\n                  String(err)\n              );\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Stop all active AudioBufferSourceNodes.\n   */\n  stopAllSources(): void {\n    for (const source of this.activeSources) {\n      try {\n        source.stop();\n      } catch (err) {\n        console.warn(\n          '[waveform-playlist] Error stopping AudioBufferSourceNode:' + ' ' + String(err)\n        );\n      }\n    }\n    this.activeSources.clear();\n  }\n\n  /** No-op for MIDI — MIDI uses note velocity, not gain fades. */\n  prepareFades(_when: number, _offset: number): void {}\n\n  /** No-op for MIDI — no fade automation to cancel. */\n  cancelFades(): void {}\n\n  setVolume(gain: number): void {\n    this.track.gain = gain;\n    this.volumeNode.volume.value = gainToDb(gain);\n  }\n\n  setPan(pan: number): void {\n    this.track.stereoPan = pan;\n    this.panNode.pan.value = pan;\n  }\n\n  setMute(muted: boolean): void {\n    this.track.muted = muted;\n    const value = muted ? 0 : 1;\n    const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n    audioParam?.setValueAtTime(value, 0);\n    this.muteGain.gain.value = value;\n  }\n\n  setSolo(soloed: boolean): void {\n    this.track.soloed = soloed;\n  }\n\n  dispose(): void {\n    if (this.effectsCleanup) {\n      try {\n        this.effectsCleanup();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error during SoundFont track \"${this.id}\" effects cleanup:` +\n            ' ' +\n            String(err)\n        );\n      }\n    }\n\n    this.stopAllSources();\n\n    // Dispose Parts\n    this.scheduledClips.forEach(({ part }, index) => {\n      try {\n        part.dispose();\n      } catch (err) {\n        console.warn(\n          `[waveform-playlist] Error disposing Part ${index} on SoundFont track \"${this.id}\":` +\n            ' ' +\n            String(err)\n        );\n      }\n    });\n\n    try {\n      this.volumeNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing volumeNode on SoundFont track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n    try {\n      this.panNode.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing panNode on SoundFont track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n    try {\n      this.muteGain.dispose();\n    } catch (err) {\n      console.warn(\n        `[waveform-playlist] Error disposing muteGain on SoundFont track \"${this.id}\":` +\n          ' ' +\n          String(err)\n      );\n    }\n  }\n\n  get id(): string {\n    return this.track.id;\n  }\n\n  get duration(): number {\n    // Max end across clips — the array is NOT sorted by startTime.\n    let max = 0;\n    for (const { clipInfo } of this.scheduledClips) {\n      const end = clipInfo.startTime + clipInfo.duration;\n      if (end > max) max = end;\n    }\n    return max;\n  }\n\n  get muted(): boolean {\n    return this.track.muted;\n  }\n\n  get startTime(): number {\n    return this.track.startTime;\n  }\n}\n","import { SoundFont2, GeneratorType } from 'soundfont2';\nimport type { Generator, ZoneMap } from 'soundfont2';\n\n/**\n * Result of looking up a MIDI note in the SoundFont.\n * Contains the AudioBuffer, playbackRate, loop points, and volume envelope.\n */\nexport interface SoundFontSample {\n  /** Cached AudioBuffer for this sample */\n  buffer: AudioBuffer;\n  /** Playback rate to pitch-shift from originalPitch to target note */\n  playbackRate: number;\n  /** Loop mode: 0=no loop, 1=continuous, 3=sustain loop */\n  loopMode: number;\n  /** Loop start in seconds, relative to AudioBuffer start */\n  loopStart: number;\n  /** Loop end in seconds, relative to AudioBuffer start */\n  loopEnd: number;\n  /** Volume envelope attack time in seconds */\n  attackVolEnv: number;\n  /** Volume envelope hold time in seconds */\n  holdVolEnv: number;\n  /** Volume envelope decay time in seconds */\n  decayVolEnv: number;\n  /** Volume envelope sustain level as linear gain 0-1 */\n  sustainVolEnv: number;\n  /** Volume envelope release time in seconds */\n  releaseVolEnv: number;\n}\n\n/**\n * Convert SF2 timecents to seconds.\n * SF2 formula: seconds = 2^(timecents / 1200)\n * Default -12000 timecents ≈ 0.001s (effectively instant).\n */\nexport function timecentsToSeconds(tc: number): number {\n  return Math.pow(2, tc / 1200);\n}\n\n/** Max release time to prevent extremely long tails from stale generators */\nconst MAX_RELEASE_SECONDS = 5;\n\n/**\n * Get a numeric generator value from a zone map.\n */\nexport function getGeneratorValue(\n  generators: ZoneMap<Generator>,\n  type: GeneratorType\n): number | undefined {\n  return generators[type]?.value;\n}\n\n/**\n * Convert Int16Array sample data to Float32Array.\n * SF2 samples are 16-bit signed integers; Web Audio needs Float32 [-1, 1].\n */\nexport function int16ToFloat32(samples: Int16Array): Float32Array {\n  const floats = new Float32Array(samples.length);\n  for (let i = 0; i < samples.length; i++) {\n    floats[i] = samples[i] / 32768;\n  }\n  return floats;\n}\n\n/**\n * Input parameters for playback rate calculation.\n */\nexport interface PlaybackRateParams {\n  /** Target MIDI note number (0-127) */\n  midiNote: number;\n  /** OverridingRootKey generator value, or undefined if not set */\n  overrideRootKey: number | undefined;\n  /** sample.header.originalPitch (255 means unpitched) */\n  originalPitch: number;\n  /** CoarseTune generator value in semitones (default 0) */\n  coarseTune: number;\n  /** FineTune generator value in cents (default 0) */\n  fineTune: number;\n  /** sample.header.pitchCorrection in cents (default 0) */\n  pitchCorrection: number;\n}\n\n/**\n * Calculate playback rate for a MIDI note using the SF2 generator chain.\n *\n * SF2 root key resolution priority:\n *   1. OverridingRootKey generator (per-zone, most specific)\n *   2. sample.header.originalPitch (sample header)\n *   3. MIDI note 60 (middle C fallback)\n *\n * Tuning adjustments:\n *   - CoarseTune generator (semitones, additive)\n *   - FineTune generator (cents, additive)\n *   - sample.header.pitchCorrection (cents, additive)\n */\nexport function calculatePlaybackRate(params: PlaybackRateParams): number {\n  const { midiNote, overrideRootKey, originalPitch, coarseTune, fineTune, pitchCorrection } =\n    params;\n\n  // Resolve root key: OverridingRootKey → originalPitch → 60\n  const rootKey =\n    overrideRootKey !== undefined ? overrideRootKey : originalPitch !== 255 ? originalPitch : 60;\n\n  // Total offset in semitones: target note - root key + tuning\n  const totalSemitones = midiNote - rootKey + coarseTune + (fineTune + pitchCorrection) / 100;\n\n  return Math.pow(2, totalSemitones / 12);\n}\n\n/**\n * Input parameters for loop and envelope extraction.\n */\nexport interface LoopAndEnvelopeParams {\n  /** SF2 generators zone map */\n  generators: ZoneMap<Generator>;\n  /** Sample header with loop points and sample rate */\n  header: {\n    startLoop: number;\n    endLoop: number;\n    sampleRate: number;\n  };\n}\n\n/**\n * Extract loop points and volume envelope data from per-zone generators.\n *\n * Loop points are stored as absolute indices into the SF2 sample pool.\n * We convert to AudioBuffer-relative seconds by subtracting header.start\n * and dividing by sampleRate.\n *\n * Volume envelope times are in SF2 timecents; sustain is centibels attenuation.\n */\nexport function extractLoopAndEnvelope(\n  params: LoopAndEnvelopeParams\n): Omit<SoundFontSample, 'buffer' | 'playbackRate'> {\n  const { generators, header } = params;\n\n  // --- Loop points ---\n  const loopMode = getGeneratorValue(generators, GeneratorType.SampleModes) ?? 0;\n\n  // Compute actual loop positions (header + fine/coarse generator offsets)\n  const rawLoopStart =\n    header.startLoop +\n    (getGeneratorValue(generators, GeneratorType.StartLoopAddrsOffset) ?? 0) +\n    (getGeneratorValue(generators, GeneratorType.StartLoopAddrsCoarseOffset) ?? 0) * 32768;\n  const rawLoopEnd =\n    header.endLoop +\n    (getGeneratorValue(generators, GeneratorType.EndLoopAddrsOffset) ?? 0) +\n    (getGeneratorValue(generators, GeneratorType.EndLoopAddrsCoarseOffset) ?? 0) * 32768;\n\n  // The soundfont2 library already converts startLoop/endLoop to be\n  // relative to sample.data (subtracts header.start during parsing),\n  // so we only need to divide by sampleRate to get seconds.\n  const loopStart = rawLoopStart / header.sampleRate;\n  const loopEnd = rawLoopEnd / header.sampleRate;\n\n  // --- Volume envelope ---\n  const attackVolEnv = timecentsToSeconds(\n    getGeneratorValue(generators, GeneratorType.AttackVolEnv) ?? -12000\n  );\n  const holdVolEnv = timecentsToSeconds(\n    getGeneratorValue(generators, GeneratorType.HoldVolEnv) ?? -12000\n  );\n  const decayVolEnv = timecentsToSeconds(\n    getGeneratorValue(generators, GeneratorType.DecayVolEnv) ?? -12000\n  );\n  const releaseVolEnv = Math.min(\n    timecentsToSeconds(getGeneratorValue(generators, GeneratorType.ReleaseVolEnv) ?? -12000),\n    MAX_RELEASE_SECONDS\n  );\n\n  // SustainVolEnv is centibels attenuation: 0 = full volume, 1440 = silence\n  // Convert to linear gain: 10^(-cb / 200)\n  const sustainCb = getGeneratorValue(generators, GeneratorType.SustainVolEnv) ?? 0;\n  const sustainVolEnv = Math.pow(10, -sustainCb / 200);\n\n  return {\n    loopMode,\n    loopStart,\n    loopEnd,\n    attackVolEnv,\n    holdVolEnv,\n    decayVolEnv,\n    sustainVolEnv,\n    releaseVolEnv,\n  };\n}\n\n/**\n * Caches parsed SoundFont2 data and AudioBuffers for efficient playback.\n *\n * AudioBuffers are created lazily on first access and cached by sample index.\n * Pitch calculation uses the SF2 generator chain:\n *   OverridingRootKey → sample.header.originalPitch → fallback 60\n *\n * Audio graph per note:\n *   AudioBufferSourceNode (playbackRate for pitch) → GainNode (velocity) → track chain\n */\nexport class SoundFontCache {\n  private sf2: SoundFont2 | null = null;\n  private audioBufferCache: Map<number, AudioBuffer> = new Map();\n  private context: BaseAudioContext;\n\n  /**\n   * @param context Optional AudioContext for createBuffer(). If omitted, uses\n   *   an OfflineAudioContext which doesn't require user gesture — safe to\n   *   construct before user interaction (avoids Firefox autoplay warnings).\n   */\n  constructor(context?: BaseAudioContext) {\n    // OfflineAudioContext only needs valid params; we never call startRendering().\n    // It's used solely for createBuffer() which works identically to AudioContext.\n    this.context = context ?? new OfflineAudioContext(1, 1, 44100);\n  }\n\n  /**\n   * Fetch and parse an SF2 file, resolving only once it's ready to play.\n   * Prefer this over `new SoundFontCache()` + `load()` — the returned cache\n   * is always loaded, so it can't hit the \"unloaded cache → PolySynth\n   * fallback\" path in createToneAdapter / setSoundFontCache.\n   */\n  static async fromUrl(\n    url: string,\n    options?: { context?: BaseAudioContext; signal?: AbortSignal }\n  ): Promise<SoundFontCache> {\n    const cache = new SoundFontCache(options?.context);\n    await cache.load(url, options?.signal);\n    return cache;\n  }\n\n  /**\n   * Load and parse an SF2 file from a URL.\n   */\n  async load(url: string, signal?: AbortSignal): Promise<void> {\n    const response = await fetch(url, { signal });\n    if (!response.ok) {\n      // statusText is frequently empty on HTTP/2 — always include the code.\n      throw new Error(\n        `Failed to fetch SoundFont ${url}: ${response.status}${\n          response.statusText ? ` ${response.statusText}` : ''\n        }`\n      );\n    }\n    const arrayBuffer = await response.arrayBuffer();\n    try {\n      this.sf2 = new SoundFont2(new Uint8Array(arrayBuffer));\n    } catch (err) {\n      throw new Error(\n        `Failed to parse SoundFont ${url}: ${err instanceof Error ? err.message : String(err)}`\n      );\n    }\n    // Cache keys are numeric sample indices — stale entries from a previous\n    // font would pair the OLD font's buffer with the NEW font's pitch/loop/\n    // envelope (mispitched audio). Clear only on successful parse: a failed\n    // reload keeps the old font, whose cache entries are still valid.\n    this.audioBufferCache.clear();\n  }\n\n  /**\n   * Load from an already-fetched ArrayBuffer.\n   */\n  loadFromBuffer(data: ArrayBuffer): void {\n    try {\n      this.sf2 = new SoundFont2(new Uint8Array(data));\n    } catch (err) {\n      throw new Error(\n        `Failed to parse SoundFont from buffer: ${err instanceof Error ? err.message : String(err)}`\n      );\n    }\n    // Invalidate buffers from the previous font (see load() for rationale).\n    this.audioBufferCache.clear();\n  }\n\n  get isLoaded(): boolean {\n    return this.sf2 !== null;\n  }\n\n  /**\n   * Look up a MIDI note and return the AudioBuffer + playbackRate.\n   *\n   * @param midiNote - MIDI note number (0-127)\n   * @param bankNumber - Bank number (0 for melodic, 128 for percussion/drums)\n   * @param presetNumber - GM program number (0-127)\n   * @returns SoundFontSample or null if no sample found for this note\n   */\n  getAudioBuffer(\n    midiNote: number,\n    bankNumber: number = 0,\n    presetNumber: number = 0\n  ): SoundFontSample | null {\n    if (!this.sf2) return null;\n\n    const keyData = this.sf2.getKeyData(midiNote, bankNumber, presetNumber);\n    if (!keyData) return null;\n\n    const sample = keyData.sample;\n    const sampleIndex = this.sf2.samples.indexOf(sample);\n\n    // Get or create the AudioBuffer for this sample\n    let buffer = this.audioBufferCache.get(sampleIndex);\n    if (!buffer) {\n      buffer = this.int16ToAudioBuffer(sample.data, sample.header.sampleRate);\n      this.audioBufferCache.set(sampleIndex, buffer);\n    }\n\n    // Calculate playback rate using SF2 generator chain for root key.\n    // Priority: OverridingRootKey generator → sample.header.originalPitch → 60\n    const playbackRate = calculatePlaybackRate({\n      midiNote,\n      overrideRootKey: getGeneratorValue(keyData.generators, GeneratorType.OverridingRootKey),\n      originalPitch: sample.header.originalPitch,\n      coarseTune: getGeneratorValue(keyData.generators, GeneratorType.CoarseTune) ?? 0,\n      fineTune: getGeneratorValue(keyData.generators, GeneratorType.FineTune) ?? 0,\n      pitchCorrection: sample.header.pitchCorrection ?? 0,\n    });\n\n    // Extract per-zone loop points and volume envelope from generators\n    const loopAndEnvelope = extractLoopAndEnvelope({\n      generators: keyData.generators,\n      header: keyData.sample.header,\n    });\n\n    return { buffer, playbackRate, ...loopAndEnvelope };\n  }\n\n  /**\n   * Convert Int16Array sample data to an AudioBuffer.\n   * Uses the extracted int16ToFloat32 for the conversion, then copies into an AudioBuffer.\n   */\n  private int16ToAudioBuffer(data: Int16Array, sampleRate: number): AudioBuffer {\n    const floats = int16ToFloat32(data);\n    const buffer = this.context.createBuffer(1, floats.length, sampleRate);\n    buffer.getChannelData(0).set(floats);\n    return buffer;\n  }\n\n  /**\n   * Clear all cached AudioBuffers and release the parsed SF2.\n   */\n  dispose(): void {\n    this.audioBufferCache.clear();\n    this.sf2 = null;\n  }\n}\n","/**\n * Global AudioContext Manager\n *\n * Provides a single AudioContext shared across the entire application.\n * This context is used by Tone.js for playback and by all recording/monitoring hooks.\n *\n * Supports both native AudioContext (WAM 2.0 plugin hosting, requires\n * AudioListener AudioParams — Firefox fallback to standardized-audio-context)\n * and standardized-audio-context wrapper for cross-browser compatibility.\n */\n\nimport { Context, setContext } from 'tone';\n\nlet globalToneContext: Context | null = null;\nlet _nativeMode = false;\n\nexport interface AudioContextOptions {\n  /**\n   * Create the global context around a NATIVE AudioContext instead of\n   * standardized-audio-context. Required for WAM 2.0 plugin hosting — WAM\n   * worklets subclass the native AudioWorkletNode and cannot join a\n   * standardized-audio-context graph. Falls back to the default context\n   * (with a console warning) on browsers missing AudioListener AudioParams\n   * (Firefox), where Tone.js Listener initialization would throw.\n   */\n  nativeAudioContext?: boolean;\n  /** Desired sample rate. Creates a standardized-audio-context AudioContext\n   *  at this rate, bypassing Tone.js 15.1.22's limitation. Cross-browser safe. */\n  sampleRate?: number;\n  /** Latency hint passed to the AudioContext constructor. */\n  latencyHint?: AudioContextLatencyCategory | number;\n}\n\n/**\n * Whether this browser can run Tone.js on a raw native AudioContext.\n * Firefox lacks the AudioListener AudioParams (positionX/…/upZ) that Tone's\n * Listener wraps eagerly at context initialization (Tone.js #681) —\n * standardized-audio-context polyfills them, native contexts cannot.\n */\nexport function supportsNativeContextMode(): boolean {\n  return (\n    typeof AudioContext !== 'undefined' &&\n    typeof AudioListener !== 'undefined' &&\n    'positionX' in AudioListener.prototype\n  );\n}\n\n/**\n * True when the global context wraps a native AudioContext (WAM-capable).\n */\nexport function isNativeGlobalContext(): boolean {\n  return _nativeMode && globalToneContext !== null;\n}\n\n/** Test-only: clears the module singleton. Not exported from the package index. */\nexport function _resetGlobalContextForTests(): void {\n  globalToneContext = null;\n  _nativeMode = false;\n}\n\n/**\n * Configure the global AudioContext with sample rate and latency hints.\n * Supports both native AudioContext (for WAM 2.0 hosting) and standardized-audio-context.\n *\n * Should be called BEFORE getGlobalContext(). If the context already exists\n * (e.g., from resumeGlobalAudioContext), warns and returns the existing rate.\n *\n * ```ts\n * configureGlobalContext({ sampleRate: 48000, latencyHint: 0 })\n * configureGlobalContext({ nativeAudioContext: true, sampleRate: 48000 })\n * ```\n */\nexport function configureGlobalContext(options: AudioContextOptions): number {\n  if (globalToneContext) {\n    const existingRate = (globalToneContext.rawContext as AudioContext).sampleRate;\n    if (options.nativeAudioContext && !_nativeMode) {\n      console.warn(\n        '[playout] configureGlobalContext: context already created — nativeAudioContext ' +\n          'ignored. Call configureGlobalContext before any audio operations.'\n      );\n    }\n    if (options.sampleRate !== undefined && options.sampleRate !== existingRate) {\n      console.warn(\n        '[playout] configureGlobalContext: context already created at ' +\n          existingRate +\n          ' Hz (requested ' +\n          options.sampleRate +\n          ' Hz). Call configureGlobalContext before any audio operations for sample rate control.'\n      );\n    }\n    return existingRate;\n  }\n\n  if (options.nativeAudioContext) {\n    if (supportsNativeContextMode()) {\n      const nativeCtx = new AudioContext({\n        ...(options.sampleRate !== undefined ? { sampleRate: options.sampleRate } : {}),\n        ...(options.latencyHint !== undefined ? { latencyHint: options.latencyHint } : {}),\n      });\n      // Tone's Context constructor accepts an existing context; its typing is\n      // standardized-audio-context's, so cast through the constructor params.\n      globalToneContext = new Context(\n        nativeCtx as unknown as ConstructorParameters<typeof Context>[0]\n      );\n      setContext(globalToneContext);\n      _nativeMode = true;\n      return nativeCtx.sampleRate;\n    }\n    console.warn(\n      '[playout] nativeAudioContext requested but this browser does not implement the ' +\n        'AudioListener AudioParams Tone.js needs on a native context (Firefox). Falling back ' +\n        'to the standardized-audio-context default — WAM plugin hosting is unavailable.'\n    );\n  }\n\n  // Default (standardized-audio-context) path. latencyHint IS supported by\n  // Tone's Context constructor (unlike sampleRate, which Tone 15.1.22 drops).\n  // Tone's ContextOptions types it category-only, but runtime forwards numeric\n  // seconds to the underlying AudioContext constructor unchanged — cast.\n  globalToneContext =\n    options.latencyHint !== undefined\n      ? new Context({ latencyHint: options.latencyHint as AudioContextLatencyCategory })\n      : new Context();\n  setContext(globalToneContext);\n  const actualRate = (globalToneContext.rawContext as AudioContext).sampleRate;\n  if (options.sampleRate !== undefined && options.sampleRate !== actualRate) {\n    console.warn(\n      '[playout] Requested sampleRate ' +\n        options.sampleRate +\n        ' but AudioContext is running at ' +\n        actualRate +\n        ' — pre-computed peaks at ' +\n        options.sampleRate +\n        ' Hz will fall back to worker'\n    );\n  }\n  return actualRate;\n}\n\n/**\n * Get the global Tone.js Context\n * This is the main context for cross-browser audio operations.\n * Use context.createAudioWorkletNode(), context.createMediaStreamSource(), etc.\n * @returns The Tone.js Context instance\n */\nexport function getGlobalContext(): Context {\n  if (!globalToneContext) {\n    globalToneContext = new Context();\n    setContext(globalToneContext);\n  }\n  return globalToneContext;\n}\n\n/**\n * Get or create the global AudioContext\n * Uses Tone.js Context for cross-browser compatibility\n * @returns The global AudioContext instance (rawContext from Tone.Context)\n */\nexport function getGlobalAudioContext(): AudioContext {\n  return getGlobalContext().rawContext as AudioContext;\n}\n\n/**\n * @deprecated Use getGlobalContext() instead\n * Get the Tone.js Context's rawContext typed as IAudioContext\n * @returns The rawContext cast as IAudioContext\n */\nexport function getGlobalToneContext(): Context {\n  return getGlobalContext();\n}\n\n/**\n * Resume the global AudioContext if it's suspended\n * Should be called in response to a user gesture (e.g., button click)\n * @returns Promise that resolves when context is running\n */\nexport async function resumeGlobalAudioContext(): Promise<void> {\n  const context = getGlobalContext();\n  if (context.state !== 'running') {\n    await context.resume();\n  }\n}\n\n/**\n * Get the current state of the global AudioContext\n * @returns The AudioContext state ('suspended', 'running', or 'closed')\n */\nexport function getGlobalAudioContextState(): AudioContextState {\n  return globalToneContext?.rawContext.state || 'suspended';\n}\n\n/**\n * Close the global AudioContext\n * Should only be called when the application is shutting down\n */\nexport async function closeGlobalAudioContext(): Promise<void> {\n  if (!globalToneContext) return;\n  // Skip close() when the raw context is already closed (a consumer can close\n  // it directly via getGlobalAudioContext()), but ALWAYS reset the singleton —\n  // otherwise every future getGlobalContext() returns the dead context forever.\n  if (globalToneContext.rawContext.state !== 'closed') {\n    await globalToneContext.close();\n  }\n  globalToneContext = null;\n  _nativeMode = false;\n}\n","/**\n * MediaStreamSource Manager\n *\n * Manages MediaStreamAudioSourceNode instances to ensure only one source\n * is created per MediaStream per AudioContext.\n *\n * Web Audio API constraint: You can only create one MediaStreamAudioSourceNode\n * per MediaStream per AudioContext. Multiple attempts will fail or disconnect\n * previous sources.\n *\n * This manager ensures a single source is shared across multiple consumers\n * (e.g., AnalyserNode for VU meter, AudioWorkletNode for recording).\n *\n * NOTE: With Tone.js Context, you can also use context.createMediaStreamSource()\n * directly, which handles cross-browser compatibility internally.\n */\n\nimport { getGlobalContext } from './audioContext';\n\n// Map of MediaStream -> MediaStreamAudioSourceNode\nconst streamSources = new Map<MediaStream, MediaStreamAudioSourceNode>();\n\n// Map of MediaStream -> cleanup handlers\nconst streamCleanupHandlers = new Map<MediaStream, () => void>();\n\n/**\n * Get or create a MediaStreamAudioSourceNode for the given stream\n *\n * Automatic cleanup fires when the stream's tracks end remotely (device\n * unplugged, remote peer stopped). NOTE: a LOCAL `track.stop()` does NOT\n * fire the track-level 'ended' event (per spec) — call\n * releaseMediaStreamSource() when tearing a stream down yourself.\n *\n * @param stream - The MediaStream to create a source for\n * @returns MediaStreamAudioSourceNode that can be connected to multiple nodes\n */\nexport function getMediaStreamSource(stream: MediaStream): MediaStreamAudioSourceNode {\n  // Return existing source if we have one for this stream\n  if (streamSources.has(stream)) {\n    return streamSources.get(stream)!;\n  }\n\n  // Create on the package's global playout context — Tone's raw getContext()\n  // would lazily create an orphaned default context when called before\n  // configureGlobalContext()/getGlobalContext(), stranding this source on a\n  // dead graph once the real global context is created.\n  const context = getGlobalContext();\n  const source = context.createMediaStreamSource(stream);\n  streamSources.set(stream, source);\n\n  // MediaStream itself has no 'ended'/'inactive' events ('ended' is\n  // track-level; active/inactive were removed from the spec) — hook each\n  // track and clean up once the stream reports inactive.\n  const tracks = stream.getTracks();\n\n  const onTrackEnded = () => {\n    if (!stream.active) {\n      cleanup();\n    }\n  };\n\n  const cleanup = () => {\n    source.disconnect();\n    streamSources.delete(stream);\n    streamCleanupHandlers.delete(stream);\n    tracks.forEach((track) => track.removeEventListener('ended', onTrackEnded));\n  };\n\n  streamCleanupHandlers.set(stream, cleanup);\n  tracks.forEach((track) => track.addEventListener('ended', onTrackEnded));\n\n  return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Required after a local `track.stop()` (which fires no 'ended' event);\n * remote-ended streams clean up automatically.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n  const cleanup = streamCleanupHandlers.get(stream);\n  if (cleanup) {\n    cleanup();\n  }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n  return streamSources.has(stream);\n}\n","import type { ClipTrack, Track } from '@waveform-playlist/core';\nimport {\n  clipStartTime,\n  clipEndTime,\n  clipOffsetTime,\n  clipDurationTime,\n  trackChannelCount,\n} from '@waveform-playlist/core';\nimport type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport { TonePlayout } from './TonePlayout';\nimport type { EffectsFunction } from './TonePlayout';\nimport type { ClipInfo } from './ToneTrack';\nimport type { MidiClipInfo } from './MidiToneTrack';\nimport type { SoundFontCache } from './SoundFontCache';\nimport { now } from 'tone';\nimport { getGlobalAudioContext, getGlobalContext, isNativeGlobalContext } from './audioContext';\n\nexport interface ToneAdapterOptions {\n  effects?: EffectsFunction;\n  /** When provided, MIDI clips use SoundFont sample playback instead of PolySynth */\n  soundFontCache?: SoundFontCache;\n  /** Pulses per quarter note. Defaults to 192 (Tone.js native). */\n  ppqn?: number;\n}\n\n/**\n * Effects wiring hooks consumed by dawcore's EffectsManager (structural match\n * for its EffectsTransportLike). All hooks require native-context mode —\n * effect chains carry native AudioNodes (incl. WAM worklets) that cannot join\n * a standardized-audio-context graph.\n */\nexport interface ToneEffectsTransport {\n  connectTrackOutput(trackId: string, node: AudioNode): void;\n  disconnectTrackOutput(trackId: string): void;\n  connectMasterOutput(node: AudioNode): void;\n  disconnectMasterOutput(): void;\n  readonly masterOutputNode: AudioNode;\n}\n\nexport interface ToneAdapter extends PlayoutAdapter {\n  /**\n   * Provide or swap the SoundFont after creation. Rebuilds only the MIDI\n   * tracks whose routing changes; audio tracks keep playing untouched.\n   * Pass undefined to revert MIDI tracks to PolySynth synthesis.\n   */\n  setSoundFontCache(cache: SoundFontCache | undefined): void;\n  /** Effects wiring hooks (dawcore EffectsManager). Requires native-context mode. */\n  readonly transport: ToneEffectsTransport;\n}\n\n/**\n * Capability check for Tone-specific adapter features. Structural, not\n * instanceof — any adapter implementing setSoundFontCache passes. Narrows a\n * generic PlayoutAdapter so soundfont calls typecheck without casts.\n */\nexport function isToneAdapter(adapter: PlayoutAdapter | null | undefined): adapter is ToneAdapter {\n  return typeof (adapter as Partial<ToneAdapter>)?.setSoundFontCache === 'function';\n}\n\nexport function createToneAdapter(options?: ToneAdapterOptions): ToneAdapter {\n  // Ensure the global shared context exists BEFORE creating the playout.\n  // Without this, TonePlayout's Volume is created on Tone's default context,\n  // which is replaced by getGlobalContext() later — causing cross-context errors.\n  getGlobalContext();\n\n  let _playoutGeneration = 1;\n  let playout: TonePlayout | null = new TonePlayout({ effects: options?.effects });\n  let _onPlaybackEnded: (() => void) | null = null;\n  playout.setOnPlaybackComplete(() => {\n    if (_playoutGeneration === 1) {\n      _isPlaying = false;\n      _onPlaybackEnded?.();\n    }\n  });\n  let _isPlaying = false;\n  let _loopEnabled = false;\n  let _loopStart = 0;\n  let _loopEnd = 0;\n  let _audioInitialized = false;\n  let _pendingInit: Promise<void> | null = null;\n  const _ppqn = options?.ppqn ?? 192;\n  let _bpm = 120;\n  let _soundFontCache = options?.soundFontCache;\n  // Snapshot of the adapter's tracks, kept fresh by setTracks/addTrack/\n  // updateTrack/removeTrack and the per-track control setters. Used by\n  // setSoundFontCache to rebuild MIDI tracks without an engine round-trip.\n  const _currentTracks = new Map<string, ClipTrack>();\n  // What each MIDI playout track was built with (null = PolySynth). The\n  // rebuild guard compares effective routing against this build record —\n  // not against the previous setSoundFontCache argument — because the same\n  // cache object can flip isLoaded after a late load().\n  const _midiTrackBuild = new Map<string, SoundFontCache | null>();\n\n  // A track with both audio and MIDI clips becomes TWO playout tracks:\n  // audio under track.id, MIDI under track.id + ':midi'.\n  function midiPlayoutTrackId(track: ClipTrack): string {\n    const hasAudio = track.clips.some((c) => c.audioBuffer && !c.midiNotes);\n    return hasAudio ? `${track.id}:midi` : track.id;\n  }\n\n  function addAudioTrackToPlayout(p: TonePlayout, track: ClipTrack): void {\n    const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);\n    if (audioClips.length === 0) return;\n\n    const startTime = Math.min(...audioClips.map(clipStartTime));\n    const endTime = Math.max(...audioClips.map(clipEndTime));\n\n    const trackObj: Track = {\n      id: track.id,\n      name: track.name,\n      gain: track.volume,\n      muted: track.muted,\n      soloed: track.soloed,\n      stereoPan: track.pan,\n      startTime,\n      endTime,\n    };\n\n    const clipInfos: ClipInfo[] = audioClips.map((clip) => ({\n      buffer: clip.audioBuffer!,\n      startTime: clipStartTime(clip) - startTime,\n      duration: clipDurationTime(clip),\n      offset: clipOffsetTime(clip),\n      fadeIn: clip.fadeIn,\n      fadeOut: clip.fadeOut,\n      gain: clip.gain,\n    }));\n\n    p.addTrack({\n      clips: clipInfos,\n      track: trackObj,\n      effects: track.effects,\n      channelCount: trackChannelCount(track),\n    });\n  }\n\n  function addMidiTrackToPlayout(p: TonePlayout, track: ClipTrack): void {\n    const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);\n    if (midiClips.length === 0) return;\n\n    const startTime = Math.min(...midiClips.map(clipStartTime));\n    const endTime = Math.max(...midiClips.map(clipEndTime));\n\n    const trackId = midiPlayoutTrackId(track);\n\n    const trackObj: Track = {\n      id: trackId,\n      name: track.name,\n      gain: track.volume,\n      muted: track.muted,\n      soloed: track.soloed,\n      stereoPan: track.pan,\n      startTime,\n      endTime,\n    };\n\n    const midiClipInfos: MidiClipInfo[] = midiClips.map((clip) => ({\n      notes: clip.midiNotes!,\n      startTime: clipStartTime(clip) - startTime,\n      duration: clipDurationTime(clip),\n      offset: clipOffsetTime(clip),\n    }));\n\n    if (_soundFontCache?.isLoaded) {\n      const firstClip = midiClips[0];\n      const midiChannel = firstClip.midiChannel;\n      const isPercussion = midiChannel === 9;\n      const programNumber = firstClip.midiProgram ?? 0;\n\n      p.addSoundFontTrack({\n        clips: midiClipInfos,\n        track: trackObj,\n        soundFontCache: _soundFontCache,\n        programNumber,\n        isPercussion,\n        effects: track.effects,\n      });\n      _midiTrackBuild.set(trackId, _soundFontCache);\n    } else {\n      if (_soundFontCache) {\n        console.warn(\n          `[waveform-playlist] SoundFont not loaded for track \"${track.name}\" — falling back to PolySynth.`\n        );\n      }\n      p.addMidiTrack({\n        clips: midiClipInfos,\n        track: trackObj,\n        effects: track.effects,\n      });\n      _midiTrackBuild.set(trackId, null);\n    }\n  }\n\n  // Add a single ClipTrack to the playout (shared by buildPlayout and addTrack)\n  function addTrackToPlayout(p: TonePlayout, track: ClipTrack): void {\n    addAudioTrackToPlayout(p, track);\n    addMidiTrackToPlayout(p, track);\n  }\n\n  // Recreates TonePlayout after dispose. The initial playout is created eagerly\n  // above. setTracks() uses the incremental path when playout exists.\n  function buildPlayout(tracks: ClipTrack[]): void {\n    if (playout) {\n      try {\n        playout.dispose();\n      } catch (err) {\n        console.warn('[waveform-playlist] Error disposing previous playout:' + ' ' + String(err));\n      }\n      playout = null;\n    }\n\n    _playoutGeneration++;\n    const generation = _playoutGeneration;\n\n    playout = new TonePlayout({\n      effects: options?.effects,\n    });\n\n    // If Tone.start() was already called (AudioContext resumed), carry\n    // initialization forward. Store the promise so adapter.init() can await it.\n    if (_audioInitialized) {\n      _pendingInit = playout.init().catch((err) => {\n        console.warn(\n          '[waveform-playlist] Failed to initialize playout. ' +\n            'Audio playback will require another user gesture.' +\n            ' ' +\n            String(err)\n        );\n        _audioInitialized = false;\n      });\n    }\n\n    for (const track of tracks) {\n      addTrackToPlayout(playout, track);\n    }\n    playout.applyInitialSoloState();\n    playout.setLoop(_loopEnabled, _loopStart, _loopEnd);\n\n    playout.setOnPlaybackComplete(() => {\n      if (generation === _playoutGeneration) {\n        _isPlaying = false;\n        _onPlaybackEnded?.();\n      }\n    });\n  }\n\n  function requireEffectsPlayout(): TonePlayout {\n    if (!isNativeGlobalContext()) {\n      throw new Error(\n        '[waveform-playlist] Effects chains on the Tone adapter require a native ' +\n          'AudioContext. Call configureGlobalContext({ nativeAudioContext: true }) from ' +\n          '@waveform-playlist/playout before any audio initialization.'\n      );\n    }\n    if (!playout) {\n      throw new Error('[waveform-playlist] adapter.transport accessed after dispose.');\n    }\n    return playout;\n  }\n\n  const effectsTransport: ToneEffectsTransport = {\n    connectTrackOutput: (trackId, node) =>\n      requireEffectsPlayout().connectTrackOutput(trackId, node),\n    disconnectTrackOutput: (trackId) => requireEffectsPlayout().disconnectTrackOutput(trackId),\n    connectMasterOutput: (node) => requireEffectsPlayout().connectMasterOutput(node),\n    disconnectMasterOutput: () => requireEffectsPlayout().disconnectMasterOutput(),\n    get masterOutputNode(): AudioNode {\n      return requireEffectsPlayout().masterBusInputNode;\n    },\n  };\n\n  return {\n    async init(): Promise<void> {\n      // If buildPlayout already started init (rebuild after setTracks), await it\n      if (_pendingInit) {\n        await _pendingInit;\n        _pendingInit = null;\n        return;\n      }\n      if (playout) {\n        await playout.init();\n        _audioInitialized = true;\n      }\n    },\n\n    setTracks(tracks: ClipTrack[]): void {\n      _currentTracks.clear();\n      _midiTrackBuild.clear();\n      for (const track of tracks) {\n        _currentTracks.set(track.id, track);\n      }\n\n      if (!playout) {\n        buildPlayout(tracks);\n        return;\n      }\n      // Incremental: diff by track ID — only remove/add what changed\n      const newTrackIds = new Set(tracks.map((t) => t.id));\n      const oldTrackIds = new Set(playout.getTrackIds());\n\n      // Remove tracks no longer present\n      for (const id of oldTrackIds) {\n        if (!newTrackIds.has(id)) {\n          playout.removeTrack(id);\n        }\n      }\n      // Add or replace tracks\n      for (const track of tracks) {\n        if (oldTrackIds.has(track.id)) {\n          playout.removeTrack(track.id);\n          playout.removeTrack(track.id + ':midi');\n        }\n        addTrackToPlayout(playout, track);\n      }\n      playout.applyInitialSoloState();\n      // Resume mid-clip sources if playing\n      if (_isPlaying) {\n        for (const track of tracks) {\n          playout.resumeTrackMidPlayback(track.id);\n          playout.resumeTrackMidPlayback(track.id + ':midi');\n        }\n      }\n    },\n\n    updateTrack(trackId: string, track: ClipTrack): void {\n      _currentTracks.set(trackId, track);\n\n      if (!playout) return;\n\n      // Try clip-level update — preserves track audio graph (no glitch)\n      const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);\n      if (audioClips.length > 0) {\n        const startTime = Math.min(...audioClips.map(clipStartTime));\n        const clipInfos: ClipInfo[] = audioClips.map((clip) => ({\n          buffer: clip.audioBuffer!,\n          startTime: clipStartTime(clip) - startTime,\n          duration: clipDurationTime(clip),\n          offset: clipOffsetTime(clip),\n          fadeIn: clip.fadeIn,\n          fadeOut: clip.fadeOut,\n          gain: clip.gain,\n        }));\n\n        const audioUpdated = playout.replaceTrackClips(trackId, clipInfos, startTime);\n\n        // Also update companion MIDI track if present. Re-add only the MIDI\n        // half — the audio track was updated in-place by replaceTrackClips\n        // above, and TonePlayout.addTrack has no idempotency guard (a second\n        // add leaks the old ToneTrack and duplicates its Transport events).\n        const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);\n        const midiTrackId = trackId + ':midi';\n        if (midiClips.length > 0) {\n          playout.removeTrack(midiTrackId);\n          addMidiTrackToPlayout(playout, track);\n          if (_isPlaying) {\n            playout.resumeTrackMidPlayback(midiTrackId);\n          }\n        } else {\n          // Last MIDI clip deleted — without this, the stale companion keeps\n          // its scheduled Part and the deleted MIDI plays as a ghost.\n          playout.removeTrack(midiTrackId);\n          _midiTrackBuild.delete(midiTrackId);\n        }\n\n        if (audioUpdated) {\n          playout.applyInitialSoloState();\n          return;\n        }\n      }\n\n      // Fallback: full track remove+re-add (MIDI-only or no audio clips)\n      playout.removeTrack(trackId);\n      playout.removeTrack(trackId + ':midi');\n      // Drop both build-record keys — a shape change (mixed ↔ MIDI-only)\n      // flips the MIDI playout id between trackId and trackId + ':midi',\n      // and the re-add below only records the current one.\n      _midiTrackBuild.delete(trackId);\n      _midiTrackBuild.delete(trackId + ':midi');\n      addTrackToPlayout(playout, track);\n      playout.applyInitialSoloState();\n      if (_isPlaying) {\n        playout.resumeTrackMidPlayback(trackId);\n        playout.resumeTrackMidPlayback(trackId + ':midi');\n      }\n    },\n\n    addTrack(track: ClipTrack): void {\n      _currentTracks.set(track.id, track);\n\n      if (!playout) {\n        console.warn(\n          '[waveform-playlist] adapter.addTrack() called but playout is not available ' +\n            '(adapter may have been disposed).'\n        );\n        return;\n      }\n      addTrackToPlayout(playout, track);\n      playout.applyInitialSoloState();\n    },\n\n    removeTrack(trackId: string): void {\n      _currentTracks.delete(trackId);\n      _midiTrackBuild.delete(trackId);\n      _midiTrackBuild.delete(trackId + ':midi');\n\n      if (!playout) return;\n      playout.removeTrack(trackId);\n      playout.removeTrack(trackId + ':midi');\n      playout.applyInitialSoloState();\n    },\n\n    play(startTime: number, endTime?: number): void {\n      if (!playout) {\n        console.warn(\n          '[waveform-playlist] adapter.play() called but no playout is available. ' +\n            'Tracks may not have been set, or the adapter was disposed.'\n        );\n        return;\n      }\n      const duration = endTime !== undefined ? endTime - startTime : undefined;\n      playout.play(now(), startTime, duration);\n      // Only set _isPlaying if play() didn't throw\n      // (TonePlayout.play() re-throws after cleanup on Transport failure)\n      _isPlaying = true;\n    },\n\n    pause(): void {\n      playout?.pause();\n      _isPlaying = false;\n    },\n\n    stop(): void {\n      playout?.stop();\n      _isPlaying = false;\n    },\n\n    seek(time: number): void {\n      playout?.seekTo(time);\n    },\n\n    getCurrentTime(): number {\n      return playout?.getCurrentTime() ?? 0;\n    },\n\n    isPlaying(): boolean {\n      return _isPlaying;\n    },\n\n    onPlaybackEnded(callback: (() => void) | null): void {\n      _onPlaybackEnded = callback;\n    },\n\n    setMasterVolume(volume: number): void {\n      playout?.setMasterGain(volume);\n    },\n\n    // Runtime controls must reach BOTH playout halves of a mixed audio+MIDI\n    // track (trackId + trackId:midi) — otherwise muting a mixed track leaves\n    // its MIDI playing and soloing it silences its own MIDI half. The :midi\n    // calls no-op for audio-only and MIDI-only tracks (unknown playout id),\n    // same blind-forwarding pattern as removeTrack.\n    setTrackVolume(trackId: string, volume: number): void {\n      const existing = _currentTracks.get(trackId);\n      if (existing) _currentTracks.set(trackId, { ...existing, volume });\n      playout?.getTrack(trackId)?.setVolume(volume);\n      playout?.getTrack(trackId + ':midi')?.setVolume(volume);\n    },\n\n    setTrackMute(trackId: string, muted: boolean): void {\n      const existing = _currentTracks.get(trackId);\n      if (existing) _currentTracks.set(trackId, { ...existing, muted });\n      playout?.setMute(trackId, muted);\n      playout?.setMute(trackId + ':midi', muted);\n    },\n\n    setTrackSolo(trackId: string, soloed: boolean): void {\n      const existing = _currentTracks.get(trackId);\n      if (existing) _currentTracks.set(trackId, { ...existing, soloed });\n      playout?.setSolo(trackId, soloed);\n      playout?.setSolo(trackId + ':midi', soloed);\n    },\n\n    setTrackPan(trackId: string, pan: number): void {\n      const existing = _currentTracks.get(trackId);\n      if (existing) _currentTracks.set(trackId, { ...existing, pan });\n      playout?.getTrack(trackId)?.setPan(pan);\n      playout?.getTrack(trackId + ':midi')?.setPan(pan);\n    },\n\n    setLoop(enabled: boolean, start: number, end: number): void {\n      _loopEnabled = enabled;\n      _loopStart = start;\n      _loopEnd = end;\n      playout?.setLoop(enabled, start, end);\n    },\n\n    get audioContext(): AudioContext {\n      return getGlobalAudioContext();\n    },\n\n    get lookAhead(): number {\n      // Tone.js Transport reports a position `lookAhead` ahead of what's audible.\n      // Consumers visualizing playback should subtract this to align the playhead\n      // with the listener's \"now\". Default is 0.1s on Tone Context wrappers.\n      return getGlobalContext().lookAhead ?? 0;\n    },\n\n    get ppqn(): number {\n      return _ppqn;\n    },\n\n    setTempo(bpm: number, atTick?: number): void {\n      if (atTick !== undefined) {\n        throw new Error(\n          'Multiple tempo changes not supported by TonePlayoutAdapter. ' +\n            'Use NativePlayoutAdapter from @dawcore/transport for multi-tempo support.'\n        );\n      }\n      _bpm = bpm;\n    },\n\n    setMeter(_numerator: number, _denominator: number, atTick?: number): void {\n      if (atTick !== undefined) {\n        throw new Error(\n          'Multiple meter changes not supported by TonePlayoutAdapter. ' +\n            'Use NativePlayoutAdapter from @dawcore/transport for multi-meter support.'\n        );\n      }\n      // No-op — Tone.js timeSignature is metadata-only, not used for scheduling\n    },\n\n    ticksToSeconds(tick: number): number {\n      return (tick * 60) / (_bpm * _ppqn);\n    },\n\n    secondsToTicks(seconds: number): number {\n      return (seconds * _bpm * _ppqn) / 60;\n    },\n\n    // --- Cross-context worklet support ---\n    // Tone.js wraps standardized-audio-context. Native AudioWorkletNode constructor\n    // rejects it (\"parameter 1 is not of type 'BaseAudioContext'\").\n    // These methods use Tone.js Context wrappers that handle both context types.\n    // Note: addWorkletModule is NOT needed — rawContext.audioWorklet.addModule() works\n    // identically for both native and standardized contexts. Only node/source creation differs.\n\n    createAudioWorkletNode(name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode {\n      return getGlobalContext().createAudioWorkletNode(name, options);\n    },\n\n    createMediaStreamSource(stream: MediaStream): MediaStreamAudioSourceNode {\n      return getGlobalContext().createMediaStreamSource(stream);\n    },\n\n    get masterOutputNode(): AudioNode {\n      if (!playout) {\n        throw new Error(\n          '[waveform-playlist] adapter.masterOutputNode accessed after dispose. ' +\n            'Disconnect your analyzer before disposing the adapter.'\n        );\n      }\n      return playout.masterOutputNode;\n    },\n\n    get transport(): ToneEffectsTransport {\n      return effectsTransport;\n    },\n\n    setSoundFontCache(cache: SoundFontCache | undefined): void {\n      _soundFontCache = cache;\n      if (cache && !cache.isLoaded) {\n        console.warn(\n          '[waveform-playlist] setSoundFontCache called with a SoundFontCache that is not ' +\n            'loaded — MIDI tracks remain on PolySynth. Await cache.load() and call again.'\n        );\n      }\n      if (!playout) return;\n\n      const effective = cache?.isLoaded ? cache : null;\n      let changed = false;\n\n      for (const track of _currentTracks.values()) {\n        const hasMidi = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);\n        if (!hasMidi) continue;\n\n        const midiTrackId = midiPlayoutTrackId(track);\n        if (_midiTrackBuild.get(midiTrackId) === effective) continue;\n\n        // Isolate per-track failures — a Tone.js graph error on one track\n        // must not leave the remaining tracks stuck on their old routing\n        // or escape into the caller (in React that unmounts the playlist\n        // via the error boundary).\n        try {\n          playout.removeTrack(midiTrackId);\n          addMidiTrackToPlayout(playout, track);\n          if (_isPlaying) {\n            playout.resumeTrackMidPlayback(midiTrackId);\n          }\n          changed = true;\n        } catch (err) {\n          console.warn(\n            `[waveform-playlist] SoundFont swap failed for track \"${track.name}\": ` + String(err)\n          );\n        }\n      }\n\n      if (changed) {\n        playout.applyInitialSoloState();\n      }\n    },\n\n    dispose(): void {\n      try {\n        playout?.dispose();\n      } catch (err) {\n        console.warn('[waveform-playlist] Error disposing playout: ' + String(err));\n      }\n      playout = null;\n      _isPlaying = false;\n      _onPlaybackEnded = null;\n      _currentTracks.clear();\n      _midiTrackBuild.clear();\n    },\n  };\n}\n"],"mappings":"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","names":["Volume","Gain","getDestination","getTransport","getContext","gainToDb","clipStartTime","Volume","Gain","Panner","getDestination","getContext","gainToDb","Volume","gainToDb","Panner","Gain","getDestination","now","getContext","Volume","Gain","Panner","Part","getDestination","getContext","gainToDb","Volume","gainToDb","Panner","Gain","getDestination","Part","getContext","Volume","gainToDb","Gain","getDestination","getTransport","getContext","now","now","start","options"]}