{
  "version": "2.1.1",
  "license": "MPL-2.0",
  "sources": [
    {
      "name": "../../core/src/AbstractSamplePipe.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport type { SampleBuffer } from './SampleBuffer.js';\n\nexport interface AbstractSamplePipeOptions<\n  TInputBuffer extends SampleBuffer,\n  TOutputBuffer extends SampleBuffer,\n> {\n  /** If true, initializes input and output buffers. */\n  createBuffers?: boolean;\n  /** Factory used to create input buffer instances. */\n  inputBufferFactory?: () => TInputBuffer;\n  /** Factory used to create output buffer instances. */\n  outputBufferFactory?: () => TOutputBuffer;\n}\n\n/**\n * Abstract base class for sample processing pipes.\n *\n * @remarks\n * Manages input and output buffers for audio sample processing chains. Subclasses should implement\n * specific processing logic for audio transformation or analysis. This class is not intended to be used\n * directly, but as a base for concrete audio processing stages.\n *\n * @typeParam TInputBuffer - Concrete input buffer type (defaults to the generic `SampleBuffer` contract).\n * @typeParam TOutputBuffer - Concrete output buffer type (defaults to `TInputBuffer` so input/output share the same buffer type unless a subclass opts into different types).\n */\nexport default class AbstractSamplePipe<\n  TInputBuffer extends SampleBuffer = SampleBuffer,\n  TOutputBuffer extends SampleBuffer = TInputBuffer,\n> {\n  /**\n   * Input buffer for audio samples.\n   */\n  protected _inputBuffer: TInputBuffer | null;\n\n  /**\n   * Output buffer for processed audio samples.\n   */\n  protected _outputBuffer: TOutputBuffer | null;\n\n  /**\n   * Constructs an AbstractSamplePipe.\n   * @param options Constructor options.\n   *\n   * @remarks\n   * When `createBuffers` is true, both factories are required so subclasses can\n   * control exact buffer implementations without unsafe casting.\n   */\n  constructor({\n    createBuffers = false,\n    inputBufferFactory,\n    outputBufferFactory,\n  }: AbstractSamplePipeOptions<TInputBuffer, TOutputBuffer> = {}) {\n    if (createBuffers) {\n      if (!inputBufferFactory || !outputBufferFactory) {\n        throw new Error(\n          'buffer factories are required when createBuffers is true',\n        );\n      }\n      this._inputBuffer = inputBufferFactory();\n      this._outputBuffer = outputBufferFactory();\n    } else {\n      this._inputBuffer = null;\n      this._outputBuffer = null;\n    }\n  }\n\n  /**\n   * Gets the input buffer.\n   * @returns The current input buffer instance, or null if not set.\n   */\n  get inputBuffer(): TInputBuffer | null {\n    return this._inputBuffer;\n  }\n\n  /**\n   * Sets the input buffer.\n   * @param inputBuffer The new input buffer instance, or null to unset.\n   */\n  set inputBuffer(inputBuffer: TInputBuffer | null) {\n    this._inputBuffer = inputBuffer;\n  }\n\n  /**\n   * Gets the output buffer.\n   * @returns The current output buffer instance, or null if not set.\n   */\n  get outputBuffer(): TOutputBuffer | null {\n    return this._outputBuffer;\n  }\n\n  /**\n   * Sets the output buffer.\n   * @param outputBuffer The new output buffer instance, or null to unset.\n   */\n  set outputBuffer(outputBuffer: TOutputBuffer | null) {\n    this._outputBuffer = outputBuffer;\n  }\n\n  /**\n   * Clears both input and output buffers.\n   *\n   * @remarks\n   * Resets the state of both input and output buffers, if present, by calling their `clear()` methods.\n   */\n  clear(): void {\n    this._inputBuffer?.clear();\n    this._outputBuffer?.clear();\n  }\n}\n"
    },
    {
      "name": "../../core/src/CircularSampleBuffer.ts",
      "content": "import type { SampleBuffer } from './SampleBuffer.js';\n\nconst SAMPLES_PER_FRAME = 2;\n\n/**\n * Circular frame buffer for interleaved stereo audio samples.\n *\n * @remarks\n * Implements a ring buffer for stereo audio, where each frame consists of two contiguous float values (left, right).\n * Maintains a movable read cursor and appends at the logical end. Capacity grows automatically as needed while preserving frame order.\n * Used for efficient, low-latency audio processing where buffer wraparound and dynamic resizing are required.\n */\nexport default class CircularSampleBuffer implements SampleBuffer {\n  private _buffer: Float32Array;\n  private _capacityFrames: number;\n  private _readFrame: number;\n  private _frameCount: number;\n\n  /**\n   * @param capacityFrames Initial frame capacity before automatic growth.\n   */\n  constructor(capacityFrames = 2048) {\n    const normalizedCapacity = Math.max(1, Math.floor(capacityFrames));\n    this._capacityFrames = normalizedCapacity;\n    this._buffer = new Float32Array(normalizedCapacity * SAMPLES_PER_FRAME);\n    this._readFrame = 0;\n    this._frameCount = 0;\n  }\n\n  /**\n   * Allocated capacity expressed in frames.\n   * @returns The number of frames the buffer can currently hold without resizing.\n   */\n  get capacityFrames(): number {\n    return this._capacityFrames;\n  }\n\n  /**\n   * Number of buffered frames currently readable.\n   * @returns The number of frames available for reading.\n   */\n  get frameCount(): number {\n    return this._frameCount;\n  }\n\n  /**\n   * Clears the buffer without shrinking allocated capacity.\n   *\n   * @remarks\n   * Resets the read cursor and frame count, but does not deallocate the underlying storage.\n   */\n  clear(): void {\n    this._readFrame = 0;\n    this._frameCount = 0;\n  }\n\n  /**\n   * Ensures the internal storage can hold at least `minCapacityFrames`.\n   *\n   * @param minCapacityFrames Minimum frame capacity required.\n   * @remarks\n   * Grows the buffer if needed, preserving all readable frames in order.\n   */\n  ensureCapacity(minCapacityFrames: number): void {\n    const normalizedMinCapacityFrames = Math.max(\n      0,\n      Math.floor(minCapacityFrames),\n    );\n    if (normalizedMinCapacityFrames <= this._capacityFrames) {\n      return;\n    }\n\n    const nextCapacity = Math.max(\n      normalizedMinCapacityFrames,\n      this._capacityFrames * 2,\n      this._capacityFrames + 1024,\n    );\n\n    const nextBuffer = new Float32Array(nextCapacity * SAMPLES_PER_FRAME);\n\n    for (let frame = 0; frame < this._frameCount; frame += 1) {\n      const sourceFrame = (this._readFrame + frame) % this._capacityFrames;\n      const sourceIndex = sourceFrame * SAMPLES_PER_FRAME;\n      const destIndex = frame * SAMPLES_PER_FRAME;\n      nextBuffer[destIndex] = this._buffer[sourceIndex];\n      nextBuffer[destIndex + 1] = this._buffer[sourceIndex + 1];\n    }\n\n    this._buffer = nextBuffer;\n    this._capacityFrames = nextCapacity;\n    this._readFrame = 0;\n  }\n\n  /**\n   * Appends source frames to the end of the ring.\n   *\n   * @param source Interleaved stereo source samples.\n   * @param sourceFrameOffset Source offset in frames.\n   * @param frameCount Number of frames to append; defaults to all complete remaining frames.\n   * @remarks\n   * Automatically grows the buffer if needed. Only complete frames are appended.\n   */\n  pushSamples(\n    source: Float32Array,\n    sourceFrameOffset = 0,\n    frameCount = 0,\n  ): void {\n    const normalizedSourceFrameOffset = Math.max(\n      0,\n      Math.floor(sourceFrameOffset),\n    );\n    const sourceStartSample = normalizedSourceFrameOffset * SAMPLES_PER_FRAME;\n    const availableFrames = Math.max(\n      0,\n      Math.floor((source.length - sourceStartSample) / SAMPLES_PER_FRAME),\n    );\n    const requestedFrames = frameCount > 0 ? Math.floor(frameCount) : 0;\n    const framesToWrite =\n      requestedFrames > 0\n        ? Math.min(requestedFrames, availableFrames)\n        : availableFrames;\n\n    if (framesToWrite <= 0) {\n      return;\n    }\n\n    this.ensureCapacity(this._frameCount + framesToWrite);\n\n    const writeFrame =\n      (this._readFrame + this._frameCount) % this._capacityFrames;\n\n    for (let frame = 0; frame < framesToWrite; frame += 1) {\n      const sourceIndex = sourceStartSample + frame * SAMPLES_PER_FRAME;\n      const destFrame = (writeFrame + frame) % this._capacityFrames;\n      const destIndex = destFrame * SAMPLES_PER_FRAME;\n      this._buffer[destIndex] = source[sourceIndex];\n      this._buffer[destIndex + 1] = source[sourceIndex + 1];\n    }\n\n    this._frameCount += framesToWrite;\n  }\n\n  /**\n   * Contract alias for `pushSamples`.\n   *\n   * @param source Interleaved stereo source samples.\n   * @param sourceFrameOffset Source offset in frames.\n   * @param frameCount Number of frames to append.\n   */\n  putSamples(\n    source: Float32Array,\n    sourceFrameOffset = 0,\n    frameCount = 0,\n  ): void {\n    this.pushSamples(source, sourceFrameOffset, frameCount);\n  }\n\n  /**\n   * Extracts frames from the ring into `target`.\n   *\n   * @param target Destination array for interleaved stereo samples.\n   * @param sourceFrameOffset Read offset in frames.\n   * @param frameCount Number of frames requested.\n   * @param consume When true, consumed frames are dropped from the front.\n   * @returns Number of frames copied.\n   * @remarks\n   * If `consume` is true, the extracted frames are removed from the buffer.\n   */\n  extract(\n    target: Float32Array,\n    sourceFrameOffset = 0,\n    frameCount = 0,\n    consume = false,\n  ): number {\n    const normalizedSourceFrameOffset = Math.max(\n      0,\n      Math.floor(sourceFrameOffset),\n    );\n    const requestedFrames = frameCount > 0 ? Math.floor(frameCount) : 0;\n    const framesAvailable = Math.max(\n      0,\n      this._frameCount - normalizedSourceFrameOffset,\n    );\n    const framesToRead =\n      requestedFrames > 0\n        ? Math.min(requestedFrames, framesAvailable)\n        : framesAvailable;\n\n    if (framesToRead <= 0) {\n      return 0;\n    }\n\n    for (let frame = 0; frame < framesToRead; frame += 1) {\n      const sourceFrame =\n        (this._readFrame + normalizedSourceFrameOffset + frame) %\n        this._capacityFrames;\n      const sourceIndex = sourceFrame * SAMPLES_PER_FRAME;\n      const targetIndex = frame * SAMPLES_PER_FRAME;\n      target[targetIndex] = this._buffer[sourceIndex];\n      target[targetIndex + 1] = this._buffer[sourceIndex + 1];\n    }\n\n    if (consume) {\n      const framesToDrop = normalizedSourceFrameOffset + framesToRead;\n      this.dropFrames(framesToDrop);\n    }\n\n    return framesToRead;\n  }\n\n  /**\n   * Reads a single sample value by logical sample index.\n   *\n   * @param sampleIndex Logical sample index relative to the readable head.\n   * @returns Sample value, or `0` when the index falls outside readable data.\n   * @remarks\n   * Used for random access to individual samples within the readable region.\n   */\n  readSample(sampleIndex: number): number {\n    const normalizedSampleIndex = Math.max(0, Math.floor(sampleIndex));\n    const frameOffset = Math.floor(normalizedSampleIndex / SAMPLES_PER_FRAME);\n    if (frameOffset >= this._frameCount) {\n      return 0;\n    }\n\n    const channelOffset = normalizedSampleIndex % SAMPLES_PER_FRAME;\n    const sourceFrame = (this._readFrame + frameOffset) % this._capacityFrames;\n    const sourceIndex = sourceFrame * SAMPLES_PER_FRAME + channelOffset;\n    return this._buffer[sourceIndex] ?? 0;\n  }\n\n  /**\n   * Drops frames from the front of the ring.\n   *\n   * @param frameCount Maximum number of frames to remove.\n   * @returns Number of frames removed.\n   * @remarks\n   * Advances the read cursor and reduces the frame count. If all frames are dropped, resets the read cursor.\n   */\n  dropFrames(frameCount: number): number {\n    const normalizedFrameCount = Math.max(0, Math.floor(frameCount));\n    const framesToDrop = Math.max(\n      0,\n      Math.min(normalizedFrameCount, this._frameCount),\n    );\n    if (framesToDrop === 0) {\n      return 0;\n    }\n\n    this._readFrame = (this._readFrame + framesToDrop) % this._capacityFrames;\n    this._frameCount -= framesToDrop;\n\n    if (this._frameCount === 0) {\n      this._readFrame = 0;\n    }\n\n    return framesToDrop;\n  }\n\n  /**\n   * Contract alias for `dropFrames`.\n   *\n   * @param frameCount Number of frames to consume.\n   */\n  receive(frameCount = this._frameCount): void {\n    this.dropFrames(frameCount);\n  }\n}\n"
    },
    {
      "name": "../../core/src/FifoSampleBuffer.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/**\n * Number of bytes per sample (Float32).\n */\nconst BYTES_PER_SAMPLE = 4;\n\n/**\n * Number of samples per audio frame (stereo).\n */\nconst SAMPLES_PER_FRAME = 2;\n\n/**\n * Number of bytes per audio frame.\n */\nconst BYTES_PER_FRAME = BYTES_PER_SAMPLE * SAMPLES_PER_FRAME;\n\n/**\n * Default maximum number of frames for buffer allocation.\n */\nconst DEFAULT_MAX_FRAMES = 131072;\n\n/**\n * Resizable interleaved sample buffer for audio processing.\n *\n * @remarks\n * Stores stereo audio samples in a contiguous Float32Array and provides methods for efficient buffer management and sample transfer.\n * Uses ES2024 ArrayBuffer for zero-allocation growth. Suitable for scenarios where buffer size may need to grow dynamically during audio processing.\n */\nexport default class FifoSampleBuffer {\n  /**\n   * Backing ArrayBuffer for sample storage.\n   * @remarks\n   * Underlying memory for the buffer, which may be resized as needed.\n   */\n  private _buffer: ArrayBuffer;\n\n  /**\n   * Float32Array view of the buffer.\n   * @remarks\n   * Provides direct access to the sample data for reading and writing.\n   */\n  private _vector: Float32Array;\n\n  /**\n   * Current read position (frame index).\n   * @remarks\n   * Indicates the logical start of readable data within the buffer.\n   */\n  private _position: number;\n\n  /**\n   * Number of frames currently stored.\n   * @remarks\n   * Represents the number of complete stereo frames available for reading.\n   */\n  private _frameCount: number;\n\n  /**\n   * Creates a new FifoSampleBuffer.\n   * @param maxFrames Maximum number of frames for buffer allocation.\n   */\n  constructor(maxFrames = DEFAULT_MAX_FRAMES) {\n    this._buffer = new ArrayBuffer(0, {\n      maxByteLength: maxFrames * BYTES_PER_FRAME,\n    });\n    this._vector = new Float32Array(this._buffer);\n    this._position = 0;\n    this._frameCount = 0;\n  }\n\n  /**\n   * Returns the Float32Array view of the buffer.\n   * @returns The Float32Array containing the sample data.\n   */\n  get vector(): Float32Array {\n    return this._vector;\n  }\n\n  /**\n   * Returns the current read position (frame index).\n   * @returns The current frame index for reading.\n   */\n  get position(): number {\n    return this._position;\n  }\n\n  /**\n   * Returns the start sample index for reading.\n   * @returns The sample index corresponding to the start of readable data.\n   */\n  get startIndex(): number {\n    return this._position * 2;\n  }\n\n  /**\n   * Returns the number of frames currently stored.\n   * @returns The number of complete frames available for reading.\n   */\n  get frameCount(): number {\n    return this._frameCount;\n  }\n\n  /**\n   * Returns the end sample index for reading.\n   * @returns The sample index corresponding to the end of readable data.\n   */\n  get endIndex(): number {\n    return (this._position + this._frameCount) * 2;\n  }\n\n  /**\n   * Clears the buffer and resets position and frame count.\n   * @remarks\n   * Fills the buffer with zeros and resets all internal state.\n   */\n  clear(): void {\n    this._vector.fill(0);\n    this._position = 0;\n    this._frameCount = 0;\n  }\n\n  /**\n   * Adds empty frames to the buffer.\n   * @param numFrames Number of frames to add.\n   */\n  put(numFrames: number): void {\n    this._frameCount += numFrames;\n  }\n\n  /**\n   * Adds samples to the buffer from a Float32Array.\n   * @param samples Source samples (interleaved stereo).\n   * @param position Start frame index in source.\n   * @param numFrames Number of frames to copy (default: all available).\n   * @remarks\n   * Automatically grows the buffer if needed. Only complete frames are appended.\n   */\n  putSamples(samples: Float32Array, position = 0, numFrames = 0): void {\n    const sourceOffset = position * 2;\n    if (!(numFrames >= 0) || numFrames === 0) {\n      numFrames = (samples.length - sourceOffset) / 2;\n    }\n    const numSamples = numFrames * 2;\n\n    this.ensureCapacity(numFrames + this._frameCount);\n\n    const destOffset = this.endIndex;\n    this._vector.set(\n      samples.subarray(sourceOffset, sourceOffset + numSamples),\n      destOffset,\n    );\n\n    this._frameCount += numFrames;\n  }\n\n  /**\n   * Adds samples from another FifoSampleBuffer.\n   * @param buffer Source buffer.\n   * @param position Start frame index in source buffer.\n   * @param numFrames Number of frames to copy (default: all available).\n   */\n  putBuffer(buffer: FifoSampleBuffer, position = 0, numFrames = 0): void {\n    if (!(numFrames >= 0) || numFrames === 0) {\n      numFrames = buffer.frameCount - position;\n    }\n    this.putSamples(buffer.vector, buffer.position + position, numFrames);\n  }\n\n  /**\n   * Advances the read position and reduces frame count.\n   * @param numFrames Number of frames to receive (default: all available).\n   * @remarks\n   * Consumed frames are no longer available for reading.\n   */\n  receive(numFrames?: number): void {\n    if (\n      numFrames === undefined ||\n      !(numFrames >= 0) ||\n      numFrames > this._frameCount\n    ) {\n      numFrames = this._frameCount;\n    }\n    this._frameCount -= numFrames;\n    this._position += numFrames;\n  }\n\n  /**\n   * Copies and receives samples into an output array.\n   * @param output Destination Float32Array.\n   * @param numFrames Number of frames to copy and receive.\n   * @remarks\n   * Advances the read position after copying.\n   */\n  receiveSamples(output: Float32Array, numFrames = 0): void {\n    const numSamples = numFrames * 2;\n    const sourceOffset = this.startIndex;\n    output.set(this._vector.subarray(sourceOffset, sourceOffset + numSamples));\n    this.receive(numFrames);\n  }\n\n  /**\n   * Extracts samples into an output array without advancing position.\n   * @param output Destination Float32Array.\n   * @param position Start frame index in buffer.\n   * @param numFrames Number of frames to extract.\n   */\n  extract(output: Float32Array, position = 0, numFrames = 0): void {\n    const sourceOffset = this.startIndex + position * 2;\n    const numSamples = numFrames * 2;\n    output.set(this._vector.subarray(sourceOffset, sourceOffset + numSamples));\n  }\n\n  /**\n   * Ensures the buffer has capacity for at least numFrames.\n   * @param numFrames Minimum number of frames required.\n   * @remarks\n   * Grows the buffer if needed, preserving all readable frames in order.\n   */\n  ensureCapacity(numFrames = 0): void {\n    const minLength = Math.floor(numFrames * SAMPLES_PER_FRAME);\n    if (this._vector.length < minLength) {\n      const newByteLength = minLength * BYTES_PER_SAMPLE;\n      if (newByteLength <= this._buffer.maxByteLength) {\n        this.rewind();\n        this._buffer.resize(newByteLength);\n        this._vector = new Float32Array(this._buffer);\n      } else {\n        const newMaxBytes = newByteLength * 2;\n        const newBuffer = new ArrayBuffer(newByteLength, {\n          maxByteLength: newMaxBytes,\n        });\n        const newVector = new Float32Array(newBuffer);\n        newVector.set(this._vector.subarray(this.startIndex, this.endIndex));\n        this._buffer = newBuffer;\n        this._vector = newVector;\n        this._position = 0;\n      }\n    } else {\n      this.rewind();\n    }\n  }\n\n  /**\n   * Ensures buffer has capacity for additional frames.\n   * @param numFrames Number of additional frames required.\n   */\n  ensureAdditionalCapacity(numFrames = 0): void {\n    this.ensureCapacity(this._frameCount + numFrames);\n  }\n\n  /**\n   * Moves all unread samples to the start of the buffer.\n   * @remarks\n   * Compacts the buffer so that all unread samples are at the beginning, freeing space for new data.\n   */\n  rewind(): void {\n    if (this._position > 0) {\n      this._vector.set(this._vector.subarray(this.startIndex, this.endIndex));\n      this._position = 0;\n    }\n  }\n}\n"
    },
    {
      "name": "../../core/src/SampleBufferAdapter.ts",
      "content": "import CircularSampleBuffer from './CircularSampleBuffer.js';\nimport FifoSampleBuffer from './FifoSampleBuffer.js';\nimport type { SampleBuffer } from './SampleBuffer.js';\n\n/**\n * Read/consume adapter used by processors that need consistent extraction\n * semantics regardless of concrete input buffer implementation.\n */\nexport interface SampleBufferAdapter {\n  /** Number of frames currently available through the adapter view. */\n  readonly frameCount: number;\n\n  /** Clears all adapter-local state and temporary storage. */\n  clear(): void;\n\n  /**\n   * Synchronizes adapter state from the latest input buffer contents.\n   *\n   * @param inputBuffer Source buffer for adapter reads.\n   */\n  syncFromInputBuffer(inputBuffer: SampleBuffer): void;\n\n  /**\n   * Copies frames from adapter storage into `target`.\n   *\n   * @param target Target interleaved stereo array.\n   * @param sourceFrameOffset Source offset in frames.\n   * @param frameCount Maximum number of frames to copy.\n   * @returns Number of frames copied.\n   */\n  extract(\n    target: Float32Array,\n    sourceFrameOffset: number,\n    frameCount: number,\n  ): number;\n\n  /**\n   * Consumes frames previously exposed through the adapter.\n   *\n   * @param frameCount Number of frames to consume.\n   */\n  receive(frameCount: number): void;\n}\n\n/** Factory for creating adapter instances. */\nexport type SampleBufferAdapterFactory = () => SampleBufferAdapter;\n\nclass FifoSampleBufferAdapter implements SampleBufferAdapter {\n  private inputBuffer: SampleBuffer | null;\n\n  constructor() {\n    this.inputBuffer = null;\n  }\n\n  get frameCount(): number {\n    return this.inputBuffer?.frameCount ?? 0;\n  }\n\n  clear(): void {\n    this.inputBuffer = null;\n  }\n\n  syncFromInputBuffer(inputBuffer: SampleBuffer): void {\n    this.inputBuffer = inputBuffer;\n  }\n\n  extract(\n    target: Float32Array,\n    sourceFrameOffset: number,\n    frameCount: number,\n  ): number {\n    const buffer = this.inputBuffer;\n    if (buffer === null) {\n      return 0;\n    }\n\n    const availableFrames = Math.max(0, buffer.frameCount - sourceFrameOffset);\n    const framesToExtract = Math.max(0, Math.min(frameCount, availableFrames));\n    if (framesToExtract === 0) {\n      return 0;\n    }\n\n    buffer.extract(target, sourceFrameOffset, framesToExtract);\n    return framesToExtract;\n  }\n\n  receive(frameCount: number): void {\n    this.inputBuffer?.receive(frameCount);\n  }\n}\n\nclass CircularSampleBufferAdapter implements SampleBufferAdapter {\n  private readonly circularBuffer: CircularSampleBuffer;\n  private scratch: Float32Array;\n\n  constructor() {\n    this.circularBuffer = new CircularSampleBuffer();\n    this.scratch = new Float32Array(0);\n  }\n\n  get frameCount(): number {\n    return this.circularBuffer.frameCount;\n  }\n\n  clear(): void {\n    this.circularBuffer.clear();\n  }\n\n  syncFromInputBuffer(inputBuffer: SampleBuffer): void {\n    if (inputBuffer instanceof FifoSampleBuffer) {\n      const frames = inputBuffer.frameCount;\n      if (frames === 0) {\n        return;\n      }\n\n      this.circularBuffer.pushSamples(\n        inputBuffer.vector,\n        inputBuffer.position,\n        frames,\n      );\n      inputBuffer.receive(frames);\n      return;\n    }\n\n    const frames = inputBuffer.frameCount;\n    if (frames === 0) {\n      return;\n    }\n\n    const sampleCount = frames * 2;\n    if (this.scratch.length < sampleCount) {\n      this.scratch = new Float32Array(sampleCount);\n    }\n\n    inputBuffer.extract(this.scratch, 0, frames);\n    this.circularBuffer.pushSamples(this.scratch, 0, frames);\n    inputBuffer.receive(frames);\n  }\n\n  extract(\n    target: Float32Array,\n    sourceFrameOffset: number,\n    frameCount: number,\n  ): number {\n    return this.circularBuffer.extract(\n      target,\n      sourceFrameOffset,\n      frameCount,\n      false,\n    );\n  }\n\n  receive(frameCount: number): void {\n    this.circularBuffer.dropFrames(frameCount);\n  }\n}\n\n/** Creates an adapter that reads directly from any `SampleBuffer` contract. */\nexport const createFifoSampleBufferAdapter: SampleBufferAdapterFactory = () =>\n  new FifoSampleBufferAdapter();\n\n/**\n * Creates an adapter that stages source frames in a circular buffer for\n * efficient repeated reads.\n */\nexport const createCircularSampleBufferAdapter: SampleBufferAdapterFactory =\n  () => new CircularSampleBufferAdapter();\n"
    },
    {
      "name": "../../interpolation-strategy-lanczos/.dist/index.js",
      "content": "const LANCZOS_DEFAULT_PARAMS = {\n    zeroCrossings: 4,\n    normalize: false,\n};\nfunction normalizeLanczosParams(params, defaults) {\n    const merged = {\n        ...defaults,\n        ...(params ?? {}),\n    };\n    const zeroCrossings = Math.max(2, Math.min(8, Math.round(Number(merged['zeroCrossings'] ?? defaults['zeroCrossings'] ?? 4))));\n    const normalize = Boolean(merged['normalize']);\n    return { zeroCrossings, normalize };\n}\nfunction applyLanczosParams(state, params) {\n    if (typeof state !== 'object' || state === null) {\n        return;\n    }\n    const record = state;\n    record.params = {\n        zeroCrossings: Math.max(2, Math.round(Number(params['zeroCrossings'] ?? 4))),\n        normalize: Boolean(params['normalize']),\n    };\n}\nfunction readFrameSample(src, srcOffset, numFrames, frameIndex, channel, state) {\n    if (frameIndex < 0) {\n        return channel === 0 ? state.prevSampleL : state.prevSampleR;\n    }\n    if (frameIndex >= numFrames) {\n        const edgeIndex = srcOffset + 2 * (numFrames - 1) + channel;\n        return src[edgeIndex];\n    }\n    return src[srcOffset + 2 * frameIndex + channel];\n}\nfunction normalizedSinc(x) {\n    if (x === 0) {\n        return 1;\n    }\n    const value = Math.PI * x;\n    return Math.sin(value) / value;\n}\nfunction lanczosWeight(distance, radius) {\n    const absDistance = Math.abs(distance);\n    if (absDistance >= radius) {\n        return 0;\n    }\n    return normalizedSinc(distance) * normalizedSinc(distance / radius);\n}\nexport const lanczosKernel = (src, srcOffset, numFrames, position, channel, state) => {\n    const kernelState = state;\n    const radius = kernelState.params.zeroCrossings;\n    const normalize = Boolean(kernelState.params.normalize);\n    const center = Math.floor(position);\n    const start = center - (radius - 1);\n    const end = center + radius;\n    let numerator = 0;\n    let denominator = 0;\n    for (let sampleIndex = start; sampleIndex <= end; sampleIndex += 1) {\n        const distance = position - sampleIndex;\n        const weight = lanczosWeight(distance, radius);\n        numerator +=\n            readFrameSample(src, srcOffset, numFrames, sampleIndex, channel, kernelState) * weight;\n        denominator += weight;\n    }\n    if (Math.abs(denominator) < 1e-12) {\n        return readFrameSample(src, srcOffset, numFrames, Math.round(position), channel, kernelState);\n    }\n    return normalize ? numerator / denominator : numerator / (denominator || 1);\n};\nlanczosKernel.createState = () => ({\n    prevSampleL: 0,\n    prevSampleR: 0,\n    params: { ...LANCZOS_DEFAULT_PARAMS },\n});\n/** Default Lanczos strategy registration payload. */\nexport const lanczosStrategy = {\n    id: 'lanczos',\n    baseStrategy: 'linear',\n    kernel: lanczosKernel,\n    defaultParams: LANCZOS_DEFAULT_PARAMS,\n    normalizeParams: normalizeLanczosParams,\n    applyParams: applyLanczosParams,\n};\n/** Registers the Lanczos strategy in a compatible registry. */\nexport function registerLanczosStrategy(registry) {\n    registry.registerInterpolationStrategy(lanczosStrategy);\n}\n//# sourceMappingURL=index.js.map"
    },
    {
      "name": "../../core/src/interpolationStrategyRegistry.ts",
      "content": "import { lanczosStrategy } from '@soundtouchjs/interpolation-strategy-lanczos';\n\n/** Built-in interpolation strategy ids understood by the core pipeline. */\nexport type BuiltInInterpolationStrategy = 'linear' | 'lanczos';\n\n/**\n * Kernel contract for interpolation plugins.\n *\n * @remarks\n * Kernels receive interleaved stereo input and may read from `state` for\n * continuity across processing blocks.\n */\nexport interface InterpolationKernel {\n  (\n    src: Float32Array,\n    srcOffset: number,\n    numFrames: number,\n    position: number,\n    channel: 0 | 1,\n    state: unknown,\n  ): number;\n  /** Optional factory for per-instance mutable kernel state. */\n  createState?: () => unknown;\n}\n\nexport type InterpolationStrategyParams = Record<string, number | boolean>;\n\nexport type RateTransposerInterpolationStrategyId = string;\n\nexport interface RateTransposerInterpolationStrategyDescriptor {\n  readonly id: RateTransposerInterpolationStrategyId;\n  readonly params?: Partial<InterpolationStrategyParams>;\n}\n\nexport type RateTransposerInterpolationStrategyOption =\n  | RateTransposerInterpolationStrategyId\n  | RateTransposerInterpolationStrategyDescriptor;\n\nexport interface InterpolationStrategyRegistration {\n  /** Unique strategy id used by `SoundTouch` and `RateTransposer` options. */\n  readonly id: RateTransposerInterpolationStrategyId;\n  /** Base strategy id used when no custom kernel is provided. */\n  readonly baseStrategy?: BuiltInInterpolationStrategy;\n  /** Optional plugin kernel implementation. */\n  readonly kernel?: InterpolationKernel;\n  /** Default params merged with runtime overrides. */\n  readonly defaultParams?: InterpolationStrategyParams;\n  /** Optional params normalizer/validator. */\n  readonly normalizeParams?: (\n    params: Partial<InterpolationStrategyParams> | undefined,\n    defaults: InterpolationStrategyParams,\n  ) => InterpolationStrategyParams;\n  /** Optional hook used to apply params to kernel state. */\n  readonly applyParams?: (\n    state: unknown,\n    params: InterpolationStrategyParams,\n  ) => void;\n}\n\nexport interface ResolvedInterpolationStrategyRuntime {\n  readonly id: RateTransposerInterpolationStrategyId;\n  readonly kernel: InterpolationKernel;\n  readonly params: InterpolationStrategyParams;\n  readonly applyParams?: (\n    state: unknown,\n    params: InterpolationStrategyParams,\n  ) => void;\n}\n\ninterface RegisteredInterpolationStrategy {\n  readonly id: RateTransposerInterpolationStrategyId;\n  readonly baseStrategy: BuiltInInterpolationStrategy;\n  readonly builtIn: boolean;\n  readonly kernel?: InterpolationKernel;\n  readonly defaultParams: InterpolationStrategyParams;\n  readonly normalizeParams?: (\n    params: Partial<InterpolationStrategyParams> | undefined,\n    defaults: InterpolationStrategyParams,\n  ) => InterpolationStrategyParams;\n  readonly applyParams?: (\n    state: unknown,\n    params: InterpolationStrategyParams,\n  ) => void;\n}\n\nconst strategyRegistry = new Map<\n  RateTransposerInterpolationStrategyId,\n  RegisteredInterpolationStrategy\n>();\n\nlet activeStrategyId: RateTransposerInterpolationStrategyId = 'lanczos';\n\nfunction readStrategySelection(\n  strategy?: RateTransposerInterpolationStrategyOption,\n): RateTransposerInterpolationStrategyDescriptor {\n  if (typeof strategy === 'string') {\n    return { id: strategy };\n  }\n\n  if (strategy !== undefined) {\n    return strategy;\n  }\n\n  return { id: activeStrategyId };\n}\n\nfunction readStrategyId(\n  strategy?: RateTransposerInterpolationStrategyOption,\n): RateTransposerInterpolationStrategyId {\n  return readStrategySelection(strategy).id;\n}\n\nfunction requireRegisteredStrategy(\n  strategyId: RateTransposerInterpolationStrategyId,\n): RegisteredInterpolationStrategy {\n  const registered = strategyRegistry.get(strategyId);\n  if (registered !== undefined) {\n    return registered;\n  }\n\n  throw new Error(\n    `Unknown interpolation strategy id \"${strategyId}\". Register it before use.`,\n  );\n}\n\nexport function registerInterpolationStrategy(\n  registration: InterpolationStrategyRegistration,\n): void {\n  const baseStrategy = registration.baseStrategy ?? 'lanczos';\n  strategyRegistry.set(registration.id, {\n    id: registration.id,\n    baseStrategy,\n    builtIn: false,\n    kernel: registration.kernel,\n    defaultParams: { ...(registration.defaultParams ?? {}) },\n    normalizeParams: registration.normalizeParams,\n    applyParams: registration.applyParams,\n  });\n}\n\nfunction registerBuiltInInterpolationStrategy(\n  registration: InterpolationStrategyRegistration,\n): void {\n  const baseStrategy = registration.baseStrategy ?? 'lanczos';\n  strategyRegistry.set(registration.id, {\n    id: registration.id,\n    baseStrategy,\n    builtIn: true,\n    kernel: registration.kernel,\n    defaultParams: { ...(registration.defaultParams ?? {}) },\n    normalizeParams: registration.normalizeParams,\n    applyParams: registration.applyParams,\n  });\n}\n\nfunction resolveKernelRegistration(\n  registration: RegisteredInterpolationStrategy,\n  visited: Set<string> = new Set(),\n): RegisteredInterpolationStrategy {\n  if (registration.kernel !== undefined) {\n    return registration;\n  }\n\n  if (visited.has(registration.id)) {\n    throw new Error(\n      `Interpolation strategy resolution cycle detected at \"${registration.id}\".`,\n    );\n  }\n\n  visited.add(registration.id);\n  const next = requireRegisteredStrategy(registration.baseStrategy);\n  return resolveKernelRegistration(next, visited);\n}\n\nfunction normalizeParams(\n  registration: RegisteredInterpolationStrategy,\n  params: Partial<InterpolationStrategyParams> | undefined,\n): InterpolationStrategyParams {\n  const defaults = registration.defaultParams;\n  if (registration.normalizeParams !== undefined) {\n    return registration.normalizeParams(params, defaults);\n  }\n\n  const normalized: InterpolationStrategyParams = { ...defaults };\n  if (params !== undefined) {\n    for (const [key, value] of Object.entries(params)) {\n      if (value !== undefined) {\n        normalized[key] = value;\n      }\n    }\n  }\n\n  return normalized;\n}\n\nexport function unregisterInterpolationStrategy(\n  strategyId: RateTransposerInterpolationStrategyId,\n): boolean {\n  const existing = strategyRegistry.get(strategyId);\n  if (existing === undefined || existing.builtIn) {\n    return false;\n  }\n\n  const deleted = strategyRegistry.delete(strategyId);\n  if (deleted && activeStrategyId === strategyId) {\n    activeStrategyId = 'lanczos';\n  }\n\n  return deleted;\n}\n\n/** Returns true when a strategy id is currently registered. */\nexport function hasInterpolationStrategy(\n  strategyId: RateTransposerInterpolationStrategyId,\n): boolean {\n  return strategyRegistry.has(strategyId);\n}\n\n/** Returns all registered strategy ids sorted lexicographically. */\nexport function listInterpolationStrategies(): readonly RateTransposerInterpolationStrategyId[] {\n  return Array.from(strategyRegistry.keys()).sort();\n}\n\n/** Returns the process-wide active strategy id used as implicit default. */\nexport function getActiveInterpolationStrategyId(): RateTransposerInterpolationStrategyId {\n  return activeStrategyId;\n}\n\n/** Sets the process-wide active strategy id. */\nexport function setActiveInterpolationStrategy(\n  strategy: RateTransposerInterpolationStrategyOption,\n): void {\n  const strategyId = readStrategyId(strategy);\n  requireRegisteredStrategy(strategyId);\n  activeStrategyId = strategyId;\n}\n\n/**\n * Resolves a user-provided option to a validated strategy id.\n *\n * @throws Error when the strategy id is unknown.\n */\nexport function normalizeInterpolationStrategyId(\n  strategy?: RateTransposerInterpolationStrategyOption,\n): RateTransposerInterpolationStrategyId {\n  const strategyId = readStrategyId(strategy);\n  requireRegisteredStrategy(strategyId);\n  return strategyId;\n}\n\n/**\n * Resolves a strategy to either a built-in base id or a plugin kernel.\n *\n * @throws Error when the strategy id is unknown.\n */\nexport function resolveInterpolationStrategy(\n  strategy?: RateTransposerInterpolationStrategyOption,\n): BuiltInInterpolationStrategy | InterpolationKernel {\n  const strategyId = readStrategyId(strategy);\n  const registered = requireRegisteredStrategy(strategyId);\n  if ('kernel' in registered && registered.kernel) {\n    return registered.kernel;\n  }\n  return registered.baseStrategy;\n}\n\n/**\n * Resolves runtime strategy state (kernel + normalized params + applier hook).\n */\nexport function resolveInterpolationStrategyRuntime(\n  strategy?: RateTransposerInterpolationStrategyOption,\n): ResolvedInterpolationStrategyRuntime {\n  const selection = readStrategySelection(strategy);\n  const registered = requireRegisteredStrategy(selection.id);\n  const kernelRegistration = resolveKernelRegistration(registered);\n  const kernel = kernelRegistration.kernel;\n  if (kernel === undefined) {\n    throw new Error(\n      `Interpolation strategy \"${selection.id}\" did not resolve to a kernel.`,\n    );\n  }\n\n  const params = normalizeParams(registered, selection.params);\n\n  return {\n    id: registered.id,\n    kernel,\n    params,\n    applyParams: registered.applyParams ?? kernelRegistration.applyParams,\n  };\n}\n\nregisterBuiltInInterpolationStrategy({\n  ...lanczosStrategy,\n  baseStrategy: 'lanczos',\n});\n"
    },
    {
      "name": "../../core/src/RateTransposer.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport AbstractSamplePipe from './AbstractSamplePipe.js';\nimport CircularSampleBuffer from './CircularSampleBuffer.js';\nimport {\n  createCircularSampleBufferAdapter,\n  type SampleBufferAdapter,\n  type SampleBufferAdapterFactory,\n} from './SampleBufferAdapter.js';\nimport {\n  resolveInterpolationStrategyRuntime,\n  type InterpolationStrategyParams,\n  type RateTransposerInterpolationStrategyId,\n  type RateTransposerInterpolationStrategyOption,\n  type InterpolationKernel,\n} from './interpolationStrategyRegistry.js';\nimport type { SampleBuffer } from './SampleBuffer.js';\n\nexport type RateTransposerInterpolationStrategy =\n  RateTransposerInterpolationStrategyOption;\n\nexport interface RateTransposerConstructorOptions {\n  /** Whether to allocate internal buffers. */\n  createBuffers?: boolean;\n  /** Factory for creating adapters that normalize input reads. */\n  sampleBufferAdapterFactory?: SampleBufferAdapterFactory;\n  /** Factory for creating chain input/output buffers. */\n  sampleBufferFactory?: () => SampleBuffer;\n  /** Interpolation strategy used for resampling. */\n  interpolationStrategy?: RateTransposerInterpolationStrategy;\n}\n\n/**\n * Sample rate transposer for pitch and tempo manipulation.\n *\n * @remarks\n * Used internally by SoundTouch for rate-based processing. Applies interpolation strategies to resample audio at different rates, supporting real-time pitch and tempo changes.\n */\nexport default class RateTransposer extends AbstractSamplePipe<\n  SampleBuffer,\n  SampleBuffer\n> {\n  /**\n   * Current rate factor for transposition.\n   */\n  private _rate: number;\n\n  /**\n   * Source position (in frames) for the next output sample, relative to the\n   * current processing block where 0 is the first frame and -1 is prevSample.\n   */\n  private fractionalPosition: number;\n\n  /**\n   * Previous left channel sample for interpolation.\n   */\n  private previousLeftSample: number;\n\n  /**\n   * Previous right channel sample for interpolation.\n   */\n  private previousRightSample: number;\n\n  /** Scratch space used for extracted input samples. */\n  private inputScratch: Float32Array;\n\n  /** Scratch space used for generated output samples. */\n  private outputScratch: Float32Array;\n\n  /** Factory used when cloning or initializing adapter strategy. */\n  private readonly sampleBufferAdapterFactory: SampleBufferAdapterFactory;\n\n  /** Factory used to construct input/output chain buffers. */\n  private readonly sampleBufferFactory: () => SampleBuffer;\n\n  /** Adapter that normalizes reads from the bound input buffer. */\n  private readonly inputAdapter: SampleBufferAdapter;\n\n  /** Selected interpolation strategy for transposition. */\n  private interpolationStrategy: RateTransposerInterpolationStrategyId;\n\n  /** Resolved kernel used by this transposer instance. */\n  private resolvedInterpolationKernel: InterpolationKernel;\n\n  /** Optional per-instance state for plugin kernels. */\n  private kernelState: unknown;\n\n  /** Normalized params for the selected interpolation strategy. */\n  private interpolationStrategyParams: InterpolationStrategyParams;\n\n  /** Optional params application hook from the strategy registration. */\n  private applyKernelParams:\n    | ((state: unknown, params: InterpolationStrategyParams) => void)\n    | undefined;\n\n\n  /**\n   * Creates a RateTransposer instance.\n   * @param options Constructor options.\n   * @remarks\n   * Accepts factories for buffer and adapter creation, and allows specifying the interpolation strategy.\n   */\n  constructor({\n    createBuffers = false,\n    sampleBufferAdapterFactory = createCircularSampleBufferAdapter,\n    sampleBufferFactory = () => new CircularSampleBuffer(),\n    interpolationStrategy,\n  }: RateTransposerConstructorOptions = {}) {\n    super({\n      createBuffers,\n      inputBufferFactory: sampleBufferFactory,\n      outputBufferFactory: sampleBufferFactory,\n    });\n    this.fractionalPosition = -1;\n    this.previousLeftSample = 0;\n    this.previousRightSample = 0;\n    this._rate = 1;\n    this.inputScratch = new Float32Array(0);\n    this.outputScratch = new Float32Array(0);\n    this.sampleBufferAdapterFactory = sampleBufferAdapterFactory;\n    this.sampleBufferFactory = sampleBufferFactory;\n    this.inputAdapter = sampleBufferAdapterFactory();\n    this.interpolationStrategy = 'lanczos';\n    this.resolvedInterpolationKernel = () => 0;\n    this.kernelState = undefined;\n    this.interpolationStrategyParams = {};\n    this.applyKernelParams = undefined;\n    this.setInterpolationStrategy(interpolationStrategy ?? 'lanczos');\n  }\n\n  /**\n   * Sets the rate factor for transposition.\n   * @param rate Rate factor.\n   */\n  set rate(rate: number) {\n    this._rate = rate;\n  }\n\n  /**\n   * Active interpolation strategy.\n   * @returns The current interpolation strategy identifier.\n   */\n  get strategy(): RateTransposerInterpolationStrategyId {\n    return this.interpolationStrategy;\n  }\n\n  /**\n   * Active interpolation strategy params.\n   * @returns The current interpolation strategy parameters.\n   */\n  get strategyParams(): Readonly<InterpolationStrategyParams> {\n    return { ...this.interpolationStrategyParams };\n  }\n\n  /**\n   * Switches interpolation strategy at runtime.\n   * @param strategy The new interpolation strategy to use.\n   */\n  setInterpolationStrategy(\n    strategy: RateTransposerInterpolationStrategy,\n  ): void {\n    const resolved = resolveInterpolationStrategyRuntime(strategy);\n    this.interpolationStrategy = resolved.id;\n    this.resolvedInterpolationKernel = resolved.kernel;\n    this.interpolationStrategyParams = { ...resolved.params };\n    this.applyKernelParams = resolved.applyParams;\n\n    if (\n      'createState' in this.resolvedInterpolationKernel &&\n      typeof this.resolvedInterpolationKernel.createState === 'function'\n    ) {\n      this.kernelState = this.resolvedInterpolationKernel.createState();\n    } else {\n      this.kernelState = undefined;\n    }\n\n    if (this.applyKernelParams !== undefined) {\n      this.applyKernelParams(\n        this.kernelState,\n        this.interpolationStrategyParams,\n      );\n    }\n\n    this.reset();\n  }\n\n  /**\n   * Applies a partial params update to the current interpolation strategy.\n   * @param params Partial set of parameters to update.\n   */\n  setInterpolationStrategyParams(\n    params: Partial<InterpolationStrategyParams>,\n  ): void {\n    const nextParams: InterpolationStrategyParams = {\n      ...this.interpolationStrategyParams,\n    };\n    for (const [key, value] of Object.entries(params)) {\n      if (value !== undefined) {\n        nextParams[key] = value;\n      }\n    }\n    this.interpolationStrategyParams = nextParams;\n\n    if (this.applyKernelParams !== undefined) {\n      this.applyKernelParams(\n        this.kernelState,\n        this.interpolationStrategyParams,\n      );\n    }\n  }\n\n  /**\n   * Resets internal state for interpolation.\n   * @remarks\n   * Clears previous sample values and resets the fractional position for output generation.\n   */\n  private reset(): void {\n    this.fractionalPosition = -1;\n    this.previousLeftSample = 0;\n    this.previousRightSample = 0;\n  }\n\n  /**\n   * Clears buffers and resets internal state.\n   * @remarks\n   * Calls clear on all internal buffers and resets interpolation state.\n   */\n  override clear(): void {\n    super.clear();\n    this.inputAdapter.clear();\n    this.reset();\n  }\n\n  /**\n   * Creates a clone of this RateTransposer with the same rate.\n   * @returns Cloned RateTransposer instance.\n   */\n  clone(): RateTransposer {\n    const result = new RateTransposer({\n      createBuffers: false,\n      sampleBufferAdapterFactory: this.sampleBufferAdapterFactory,\n      sampleBufferFactory: this.sampleBufferFactory,\n      interpolationStrategy: {\n        id: this.interpolationStrategy,\n        params: this.interpolationStrategyParams,\n      },\n    });\n    result.rate = this._rate;\n    return result;\n  }\n\n  /**\n   * Processes input buffer and writes transposed samples to output buffer.\n   * @remarks\n   * Reads frames from the input buffer, applies rate transposition, and writes to the output buffer.\n   */\n  process(): void {\n    if (this._inputBuffer === null || this._outputBuffer === null) {\n      return;\n    }\n\n    this.inputAdapter.syncFromInputBuffer(this._inputBuffer);\n    const numFrames = this.inputAdapter.frameCount;\n    if (numFrames === 0) {\n      return;\n    }\n\n    const numFramesOutput = this.transpose(numFrames);\n    this.inputAdapter.receive(numFrames);\n\n    if (numFramesOutput > 0) {\n      this._outputBuffer!.putSamples(this.outputScratch, 0, numFramesOutput);\n    }\n  }\n\n  /**\n   * Ensures temporary scratch arrays are large enough for the current frame request and estimated output size.\n   *\n   * @param numInputFrames Number of input frames that will be processed.\n   * @remarks\n   * Allocates or resizes scratch arrays as needed for efficient processing.\n   */\n  private ensureScratchCapacity(numInputFrames: number): void {\n    const inputSamples = numInputFrames * 2;\n    if (this.inputScratch.length < inputSamples) {\n      this.inputScratch = new Float32Array(inputSamples);\n    }\n\n    const estimatedOutputFrames = Math.ceil(numInputFrames / this._rate) + 2;\n    const outputSamples = Math.max(0, estimatedOutputFrames) * 2;\n    if (this.outputScratch.length < outputSamples) {\n      this.outputScratch = new Float32Array(outputSamples);\n    }\n  }\n\n  /**\n   * Transposes input samples by the current rate.\n   * @param numFrames Number of input frames to transpose.\n   * @returns Number of output frames written.\n   * @remarks\n   * Applies the selected interpolation kernel to generate output samples at the new rate.\n   */\n  transpose(numFrames = 0): number {\n    if (this._inputBuffer !== null) {\n      this.inputAdapter.syncFromInputBuffer(this._inputBuffer);\n      if (numFrames === 0) {\n        numFrames = this.inputAdapter.frameCount;\n      }\n    }\n\n    if (numFrames === 0) {\n      return 0;\n    }\n\n    this.ensureScratchCapacity(numFrames);\n\n    const src = this.inputScratch;\n    const extractedFrames = this.inputAdapter.extract(src, 0, numFrames);\n    if (extractedFrames === 0) {\n      return 0;\n    }\n    numFrames = extractedFrames;\n\n    return this.transposePluginKernel(numFrames);\n  }\n\n  /**\n   * Handles transposition using a plugin kernel.\n   * @remarks\n   * Invokes the selected interpolation kernel for each output sample.\n   */\n  private transposePluginKernel(numFrames: number): number {\n    const src = this.inputScratch;\n    const dest = this.outputScratch;\n    const srcOffset = 0;\n    const destOffset = 0;\n    const kernel = this.resolvedInterpolationKernel;\n    const state = this.kernelState;\n    const stateRecord = this.getKernelStateRecord(state);\n\n    if (stateRecord !== undefined) {\n      stateRecord.prevSampleL = this.previousLeftSample;\n      stateRecord.prevSampleR = this.previousRightSample;\n    }\n\n    let i = 0;\n    let position = this.fractionalPosition;\n    const maxPosition = numFrames - 1;\n\n    while (position <= maxPosition) {\n      dest[destOffset + 2 * i] = kernel(\n        src,\n        srcOffset,\n        numFrames,\n        position,\n        0,\n        state,\n      );\n      dest[destOffset + 2 * i + 1] = kernel(\n        src,\n        srcOffset,\n        numFrames,\n        position,\n        1,\n        state,\n      );\n      i = i + 1;\n      position += this._rate;\n    }\n\n    this.fractionalPosition = position - numFrames;\n\n    this.previousLeftSample = src[srcOffset + 2 * numFrames - 2];\n    this.previousRightSample = src[srcOffset + 2 * numFrames - 1];\n\n    if (stateRecord !== undefined) {\n      stateRecord.prevSampleL = this.previousLeftSample;\n      stateRecord.prevSampleR = this.previousRightSample;\n    }\n\n    return i;\n  }\n\n  /**\n   * Returns the kernel state record if available.\n   * @param state The kernel state object.\n   * @returns The state record with previous sample values, or undefined if not present.\n   */\n  private getKernelStateRecord(\n    state: unknown,\n  ): { prevSampleL: number; prevSampleR: number } | undefined {\n    if (typeof state !== 'object' || state === null) {\n      return undefined;\n    }\n\n    const record = state as Record<string, unknown>;\n    const prevSampleL = record['prevSampleL'];\n    const prevSampleR = record['prevSampleR'];\n    if (typeof prevSampleL === 'number' && typeof prevSampleR === 'number') {\n      return record as { prevSampleL: number; prevSampleR: number };\n    }\n\n    return undefined;\n  }\n}\n"
    },
    {
      "name": "../../core/src/Stretch.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport AbstractSamplePipe from './AbstractSamplePipe.js';\nimport CircularSampleBuffer from './CircularSampleBuffer.js';\nimport FifoSampleBuffer from './FifoSampleBuffer.js';\nimport type { SampleBuffer } from './SampleBuffer.js';\nimport type { StretchPipe } from './StretchPipe.js';\n\n/**\n * Read adapter used by `Stretch` so input access is decoupled from concrete\n * buffer implementations.\n */\ninterface StretchReadBufferAdapter {\n  /** Number of readable frames currently exposed by the adapter. */\n  readonly frameCount: number;\n  /** Sample-index origin used by callers that operate on absolute offsets. */\n  readonly startIndex: number;\n\n  /**\n   * Binds the adapter to a new source buffer.\n   * @param buffer Source buffer to read from.\n   */\n  setBuffer(buffer: SampleBuffer): void;\n\n  /**\n   * Reads a single sample at an absolute sample index.\n   * @param sampleIndex Absolute sample index.\n   */\n  readSample(sampleIndex: number): number;\n\n  /**\n   * Returns a contiguous sample range.\n   * @param start Inclusive start sample index.\n   * @param end Exclusive end sample index.\n   */\n  readSubarray(start: number, end: number): Float32Array;\n\n  /**\n   * Consumes frames from the adapter source.\n   * @param numFrames Number of frames to consume.\n   */\n  receive(numFrames: number): void;\n\n  /**\n   * Extracts and consumes frames into `output`.\n   * @param output Destination sample array.\n   * @param numFrames Number of frames to extract.\n   */\n  receiveSamples(output: Float32Array, numFrames: number): void;\n}\n\n/**\n * Write adapter used by `Stretch` so output appends are abstracted over the\n * target buffer implementation.\n */\ninterface StretchWriteBufferAdapter {\n  /**\n   * Binds adapter writes to the provided output buffer.\n   * @param buffer Destination buffer.\n   */\n  setOutputBuffer(buffer: SampleBuffer): void;\n\n  /**\n   * Appends raw interleaved stereo frames.\n   * @param samples Interleaved stereo source samples.\n   * @param numFrames Number of frames to append.\n   */\n  appendSamples(samples: Float32Array, numFrames: number): void;\n\n  /**\n   * Copies frames from a read adapter into the bound output.\n   * @param source Source adapter.\n   * @param position Source frame offset.\n   * @param numFrames Number of frames to copy.\n   */\n  putFrom(\n    source: StretchReadBufferAdapter,\n    position: number,\n    numFrames: number,\n  ): void;\n}\n\n/** Factory for stretch input adapters. */\ntype StretchInputBufferAdapterFactory = () => StretchReadBufferAdapter;\n\n/**\n * WSOLA timing parameters that control the time-stretching algorithm.\n *\n * @remarks\n * All time-based fields are in milliseconds. Pass `0` for `sequenceMs` or\n * `seekWindowMs` to let the algorithm auto-compute values based on tempo.\n * Omit a field to leave it unchanged.\n *\n * @example\n * stretch.setStretchParameters({ overlapMs: 12, quickSeek: false });\n */\nexport interface StretchParameters {\n  /**\n   * Length of the processing sequence window in milliseconds.\n   * `0` switches to automatic calculation (50–125 ms range based on tempo).\n   */\n  sequenceMs?: number;\n\n  /**\n   * Length of the seek window in milliseconds.\n   * `0` switches to automatic calculation (15–25 ms range based on tempo).\n   */\n  seekWindowMs?: number;\n\n  /**\n   * Overlap crossfade length in milliseconds.\n   * Must be greater than `0`. Values less than `1 ms` (after sample-rate conversion) are clamped to 16 samples.\n   */\n  overlapMs?: number;\n\n  /**\n   * Whether to use the fast multi-pass seek algorithm.\n   * `true` (default) uses a quick scan; `false` performs an exhaustive search for better quality at lower tempos.\n   */\n  quickSeek?: boolean;\n}\n\nexport interface StretchConstructorOptions {\n  /** Initial processing sample rate in Hz.\n   *\n   * @defaultValue 44100\n   */\n  sampleRate?: number;\n  /** Whether to allocate internal input/output buffers. */\n  createBuffers?: boolean;\n  /** Factory for creating stretch input adapters. */\n  inputBufferAdapterFactory?: StretchInputBufferAdapterFactory;\n  /** Factory for creating chain input/output buffers. */\n  sampleBufferFactory?: () => SampleBuffer;\n}\n\n/**\n * Read adapter optimized for FIFO-backed buffers with a generic fallback path.\n */\nclass FifoStretchBufferAdapter implements StretchReadBufferAdapter {\n  private buffer: FifoSampleBuffer | null;\n  private readonly fallbackBuffer: FifoSampleBuffer;\n  private fallbackScratch: Float32Array;\n\n  constructor() {\n    this.buffer = null;\n    this.fallbackBuffer = new FifoSampleBuffer();\n    this.fallbackScratch = new Float32Array(0);\n  }\n\n  /**\n   * @param buffer Source buffer to expose through FIFO-style reads.\n   */\n  setBuffer(buffer: SampleBuffer): void {\n    if (buffer instanceof FifoSampleBuffer) {\n      this.buffer = buffer;\n      return;\n    }\n\n    const frameCount = buffer.frameCount;\n    if (frameCount > 0) {\n      const sampleCount = frameCount * 2;\n      if (this.fallbackScratch.length < sampleCount) {\n        this.fallbackScratch = new Float32Array(sampleCount);\n      }\n      buffer.extract(this.fallbackScratch, 0, frameCount);\n      this.fallbackBuffer.clear();\n      this.fallbackBuffer.putSamples(this.fallbackScratch, 0, frameCount);\n      buffer.receive(frameCount);\n    } else {\n      this.fallbackBuffer.clear();\n    }\n\n    this.buffer = this.fallbackBuffer;\n  }\n\n  /**\n   * Returns the currently bound FIFO buffer.\n   * @throws Error when `setBuffer` has not been called yet.\n   */\n  private getBoundBuffer(): FifoSampleBuffer {\n    if (this.buffer === null) {\n      throw new Error('buffer is not set');\n    }\n    return this.buffer;\n  }\n\n  get frameCount(): number {\n    return this.getBoundBuffer().frameCount;\n  }\n\n  get startIndex(): number {\n    return this.getBoundBuffer().startIndex;\n  }\n\n  readSample(sampleIndex: number): number {\n    const boundBuffer = this.getBoundBuffer();\n    const start = boundBuffer.startIndex;\n    const end = start + boundBuffer.frameCount * 2;\n    if (sampleIndex < start || sampleIndex >= end) {\n      return 0;\n    }\n    return boundBuffer.vector[sampleIndex];\n  }\n\n  readSubarray(start: number, end: number): Float32Array {\n    return this.getBoundBuffer().vector.subarray(start, end);\n  }\n\n  receive(numFrames: number): void {\n    this.getBoundBuffer().receive(numFrames);\n  }\n\n  receiveSamples(output: Float32Array, numFrames: number): void {\n    this.getBoundBuffer().receiveSamples(output, numFrames);\n  }\n}\n\nclass GenericStretchWriteBufferAdapter implements StretchWriteBufferAdapter {\n  private buffer: SampleBuffer | null;\n\n  constructor() {\n    this.buffer = null;\n  }\n\n  setOutputBuffer(buffer: SampleBuffer): void {\n    this.buffer = buffer;\n  }\n\n  /**\n   * Returns the currently bound output buffer.\n   * @throws Error when `setOutputBuffer` has not been called.\n   */\n  private getBoundBuffer(): SampleBuffer {\n    if (this.buffer === null) {\n      throw new Error('output buffer is not set');\n    }\n    return this.buffer;\n  }\n\n  appendSamples(samples: Float32Array, numFrames: number): void {\n    this.getBoundBuffer().putSamples(samples, 0, numFrames);\n  }\n\n  putFrom(\n    source: StretchReadBufferAdapter,\n    position: number,\n    numFrames: number,\n  ): void {\n    const sourceStart = source.startIndex + position * 2;\n    const sourceEnd = sourceStart + numFrames * 2;\n    const chunk = source.readSubarray(sourceStart, sourceEnd);\n    this.getBoundBuffer().putSamples(chunk, 0, numFrames);\n  }\n}\n\nclass CircularStretchInputBufferAdapter implements StretchReadBufferAdapter {\n  private readonly circularBuffer: CircularSampleBuffer;\n  private rangeScratch: Float32Array;\n\n  constructor() {\n    this.circularBuffer = new CircularSampleBuffer();\n    this.rangeScratch = new Float32Array(0);\n  }\n\n  /**\n   * Binds a source buffer and stages its readable frames into the internal\n   * circular storage.\n   *\n   * @param buffer Source buffer to import.\n   */\n  setBuffer(buffer: SampleBuffer): void {\n    if (buffer instanceof FifoSampleBuffer) {\n      const frames = buffer.frameCount;\n      if (frames > 0) {\n        this.circularBuffer.pushSamples(buffer.vector, buffer.position, frames);\n        buffer.receive(frames);\n      }\n      return;\n    }\n\n    const frames = buffer.frameCount;\n    if (frames > 0) {\n      const sampleCount = frames * 2;\n      if (this.rangeScratch.length < sampleCount) {\n        this.rangeScratch = new Float32Array(sampleCount);\n      }\n      buffer.extract(this.rangeScratch, 0, frames);\n      this.circularBuffer.pushSamples(this.rangeScratch, 0, frames);\n      buffer.receive(frames);\n    }\n  }\n\n  get frameCount(): number {\n    return this.circularBuffer.frameCount;\n  }\n\n  get startIndex(): number {\n    return 0;\n  }\n\n  readSample(sampleIndex: number): number {\n    return this.circularBuffer.readSample(sampleIndex);\n  }\n\n  /**\n   * Returns a contiguous range from circular storage, padding trailing values\n   * with zeros when the requested range extends past available data.\n   */\n  readSubarray(start: number, end: number): Float32Array {\n    const normalizedStart = Math.max(0, Math.floor(start));\n    const normalizedEnd = Math.max(normalizedStart, Math.floor(end));\n    const requestedSamples = normalizedEnd - normalizedStart;\n    const requestedFrames = Math.floor(requestedSamples / 2);\n\n    if (requestedFrames <= 0) {\n      return this.rangeScratch.subarray(0, 0);\n    }\n\n    const needed = requestedFrames * 2;\n    if (this.rangeScratch.length < needed) {\n      this.rangeScratch = new Float32Array(needed);\n    }\n\n    const sourceFrameOffset = Math.floor(normalizedStart / 2);\n    const readFrames = this.circularBuffer.extract(\n      this.rangeScratch,\n      sourceFrameOffset,\n      requestedFrames,\n      false,\n    );\n\n    const readSamples = readFrames * 2;\n    if (readSamples < needed) {\n      this.rangeScratch.fill(0, readSamples, needed);\n    }\n\n    return this.rangeScratch.subarray(0, needed);\n  }\n\n  receive(numFrames: number): void {\n    this.circularBuffer.dropFrames(numFrames);\n  }\n\n  receiveSamples(output: Float32Array, numFrames: number): void {\n    this.circularBuffer.extract(output, 0, numFrames, true);\n  }\n}\n\n/**\n * Creates a stretch input adapter that reads from FIFO-compatible buffers.\n */\nexport const createFifoStretchInputBufferAdapter: StretchInputBufferAdapterFactory =\n  () => new FifoStretchBufferAdapter();\n\n/**\n * Creates a stretch input adapter backed by `CircularSampleBuffer`.\n */\nexport const createCircularStretchInputBufferAdapter: StretchInputBufferAdapterFactory =\n  () => new CircularStretchInputBufferAdapter();\nconst USE_AUTO_SEQUENCE_LEN = 0;\nconst DEFAULT_SEQUENCE_MS = USE_AUTO_SEQUENCE_LEN;\n\nconst USE_AUTO_SEEKWINDOW_LEN = 0;\nconst DEFAULT_SEEKWINDOW_MS = USE_AUTO_SEEKWINDOW_LEN;\n\nconst DEFAULT_OVERLAP_MS = 8;\n\nconst AUTOSEQ_TEMPO_LOW = 0.25;\nconst AUTOSEQ_TEMPO_TOP = 4.0;\n\nconst AUTOSEQ_AT_MIN = 125.0;\nconst AUTOSEQ_AT_MAX = 50.0;\nconst AUTOSEQ_K =\n  (AUTOSEQ_AT_MAX - AUTOSEQ_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW);\nconst AUTOSEQ_C = AUTOSEQ_AT_MIN - AUTOSEQ_K * AUTOSEQ_TEMPO_LOW;\n\nconst AUTOSEEK_AT_MIN = 25.0;\nconst AUTOSEEK_AT_MAX = 15.0;\nconst AUTOSEEK_K =\n  (AUTOSEEK_AT_MAX - AUTOSEEK_AT_MIN) / (AUTOSEQ_TEMPO_TOP - AUTOSEQ_TEMPO_LOW);\nconst AUTOSEEK_C = AUTOSEEK_AT_MIN - AUTOSEEK_K * AUTOSEQ_TEMPO_LOW;\nconst NORMALIZED_CORRELATION_EPSILON = 1e-12;\nconst QUICK_SEEK_FALLBACK_THRESHOLD = 256;\nconst QUICK_SEEK_MIN_VALID_CANDIDATES = 8;\n\n/**\n * Time-stretch processor for tempo adjustment without affecting pitch.\n * Used internally by SoundTouch for time-stretching audio.\n */\nexport default class Stretch\n  extends AbstractSamplePipe<SampleBuffer, SampleBuffer>\n  implements StretchPipe\n{\n  private readonly inputBufferAdapterFactory: StretchInputBufferAdapterFactory;\n  private readonly sampleBufferFactory: () => SampleBuffer;\n  private readonly inputBufferAdapter: StretchReadBufferAdapter;\n  private readonly outputBufferAdapter: StretchWriteBufferAdapter;\n  private overlapScratch: Float32Array;\n\n  private _quickSeek: boolean;\n  private midBufferDirty: boolean;\n  midBuffer: Float32Array | null;\n  private refMidBuffer!: Float32Array;\n  private refMidBufferEnergy: number;\n  overlapLength: number;\n  private autoSeqSetting: boolean;\n  private autoSeekSetting: boolean;\n  _tempo: number;\n  private sampleRate!: number;\n  private _overlapMs!: number;\n  sequenceMs!: number;\n  seekWindowMs!: number;\n  private seekWindowLength!: number;\n  private seekLength!: number;\n  private nominalSkip!: number;\n  private skipFract!: number;\n  sampleReq!: number;\n\n  /**\n   * Creates a Stretch instance.\n   * @param options Constructor options.\n   */\n  constructor({\n    sampleRate = 44100,\n    createBuffers = false,\n    inputBufferAdapterFactory = createFifoStretchInputBufferAdapter,\n    sampleBufferFactory = () => new FifoSampleBuffer(),\n  }: StretchConstructorOptions = {}) {\n    super({\n      createBuffers,\n      inputBufferFactory: sampleBufferFactory,\n      outputBufferFactory: sampleBufferFactory,\n    });\n    this.inputBufferAdapterFactory = inputBufferAdapterFactory;\n    this.sampleBufferFactory = sampleBufferFactory;\n    this.inputBufferAdapter = inputBufferAdapterFactory();\n    this.outputBufferAdapter = new GenericStretchWriteBufferAdapter();\n    this.overlapScratch = new Float32Array(0);\n    this._quickSeek = true;\n    this.midBufferDirty = true;\n\n    this.midBuffer = null;\n    this.refMidBufferEnergy = 0;\n    this.overlapLength = 0;\n\n    this.autoSeqSetting = true;\n    this.autoSeekSetting = true;\n\n    this._tempo = 1;\n    this.setParameters(\n      sampleRate,\n      DEFAULT_SEQUENCE_MS,\n      DEFAULT_SEEKWINDOW_MS,\n      DEFAULT_OVERLAP_MS,\n    );\n  }\n\n  override clear(): void {\n    super.clear();\n    this.clearMidBuffer();\n  }\n\n  clearMidBuffer(): void {\n    this.midBufferDirty = true;\n\n    if (this.midBuffer) {\n      this.midBuffer.fill(0);\n    }\n\n    if (this.refMidBuffer) {\n      this.refMidBuffer.fill(0);\n    }\n\n    this.skipFract = 0;\n  }\n\n  setParameters(\n    sampleRate: number,\n    sequenceMs: number,\n    seekWindowMs: number,\n    overlapMs: number,\n  ): void {\n    if (sampleRate > 0) {\n      this.sampleRate = sampleRate;\n    }\n\n    if (overlapMs > 0) {\n      this._overlapMs = overlapMs;\n    }\n\n    if (sequenceMs > 0) {\n      this.sequenceMs = sequenceMs;\n      this.autoSeqSetting = false;\n    } else {\n      this.autoSeqSetting = true;\n    }\n\n    if (seekWindowMs > 0) {\n      this.seekWindowMs = seekWindowMs;\n      this.autoSeekSetting = false;\n    } else {\n      this.autoSeekSetting = true;\n    }\n\n    this.calculateSequenceParameters();\n    this.calculateOverlapLength(this._overlapMs);\n    this.updateTempoDerivedState();\n  }\n\n  set tempo(newTempo: number) {\n    this._tempo = newTempo;\n    this.updateTempoDerivedState();\n  }\n\n  get tempo(): number {\n    return this._tempo;\n  }\n\n  get inputChunkSize(): number {\n    return this.sampleReq;\n  }\n\n  get outputChunkSize(): number {\n    return (\n      this.overlapLength +\n      Math.max(0, this.seekWindowLength - 2 * this.overlapLength)\n    );\n  }\n\n  calculateOverlapLength(overlapInMsec = 0): void {\n    let newOvl = (this.sampleRate * overlapInMsec) / 1000;\n    newOvl = newOvl < 16 ? 16 : newOvl;\n\n    // must be divisible by 8\n    newOvl -= newOvl % 8;\n\n    if (newOvl === this.overlapLength && this.midBuffer !== null) {\n      return;\n    }\n\n    this.overlapLength = newOvl;\n    const needed = this.overlapLength * 2;\n\n    if (!this.refMidBuffer || this.refMidBuffer.length < needed) {\n      this.refMidBuffer = new Float32Array(needed);\n    }\n    if (!this.midBuffer || this.midBuffer.length < needed) {\n      this.midBuffer = new Float32Array(needed);\n    }\n  }\n\n  private checkLimits(x: number, mi: number, ma: number): number {\n    return x < mi ? mi : x > ma ? ma : x;\n  }\n\n  private calculateSequenceParameters(): void {\n    if (this.autoSeqSetting) {\n      let seq = AUTOSEQ_C + AUTOSEQ_K * this._tempo;\n      seq = this.checkLimits(seq, AUTOSEQ_AT_MAX, AUTOSEQ_AT_MIN);\n      this.sequenceMs = Math.floor(seq + 0.5);\n    }\n\n    if (this.autoSeekSetting) {\n      let seek = AUTOSEEK_C + AUTOSEEK_K * this._tempo;\n      seek = this.checkLimits(seek, AUTOSEEK_AT_MAX, AUTOSEEK_AT_MIN);\n      this.seekWindowMs = Math.floor(seek + 0.5);\n    }\n\n    this.seekWindowLength = Math.floor(\n      (this.sampleRate * this.sequenceMs) / 1000,\n    );\n    this.seekLength = Math.floor((this.sampleRate * this.seekWindowMs) / 1000);\n    this.normalizeWindowInvariants();\n  }\n\n  private normalizeWindowInvariants(): void {\n    this.seekLength = Math.max(1, this.seekLength);\n    this.seekWindowLength = Math.max(this.seekWindowLength, this.overlapLength);\n  }\n\n  private updateTempoDerivedState(): void {\n    this.calculateSequenceParameters();\n    this.nominalSkip =\n      this._tempo * (this.seekWindowLength - this.overlapLength);\n    this.skipFract = 0;\n    const intskip = Math.floor(this.nominalSkip + 0.5);\n    this.sampleReq =\n      Math.max(intskip + this.overlapLength, this.seekWindowLength) +\n      this.seekLength;\n  }\n\n  /**\n   * Whether the fast multi-pass seek algorithm is active.\n   * @returns `true` if quick seek is enabled (default); `false` for exhaustive search.\n   */\n  get quickSeek(): boolean {\n    return this._quickSeek;\n  }\n\n  set quickSeek(enable: boolean) {\n    this._quickSeek = enable;\n  }\n\n  /**\n   * Current overlap crossfade length in milliseconds.\n   * @returns The overlap period used at the current sample rate.\n   */\n  get overlapMs(): number {\n    return this._overlapMs;\n  }\n\n  /**\n   * Sets the overlap crossfade length and recalculates derived parameters.\n   * @param ms Overlap period in milliseconds (must be > 0).\n   */\n  set overlapMs(ms: number) {\n    if (ms > 0) {\n      this._overlapMs = ms;\n      this.calculateOverlapLength(this._overlapMs);\n      this.calculateSequenceParameters();\n      this.updateTempoDerivedState();\n    }\n  }\n\n  /**\n   * Applies a partial set of WSOLA timing parameters.\n   *\n   * @remarks\n   * Only the provided fields are updated; omitted fields remain unchanged.\n   * Pass `sequenceMs: 0` or `seekWindowMs: 0` to switch that dimension back to auto-calculation.\n   *\n   * @param params Partial set of WSOLA timing parameters to apply.\n   *\n   * @example\n   * stretch.setStretchParameters({ overlapMs: 12, quickSeek: false });\n   */\n  setStretchParameters(params: StretchParameters): void {\n    if (params.quickSeek !== undefined) {\n      this._quickSeek = params.quickSeek;\n    }\n\n    let needsRecalc = false;\n\n    if (params.sequenceMs !== undefined) {\n      if (params.sequenceMs > 0) {\n        this.sequenceMs = params.sequenceMs;\n        this.autoSeqSetting = false;\n      } else {\n        this.autoSeqSetting = true;\n      }\n      needsRecalc = true;\n    }\n\n    if (params.seekWindowMs !== undefined) {\n      if (params.seekWindowMs > 0) {\n        this.seekWindowMs = params.seekWindowMs;\n        this.autoSeekSetting = false;\n      } else {\n        this.autoSeekSetting = true;\n      }\n      needsRecalc = true;\n    }\n\n    if (params.overlapMs !== undefined && params.overlapMs > 0) {\n      this._overlapMs = params.overlapMs;\n      this.calculateOverlapLength(this._overlapMs);\n      needsRecalc = true;\n    }\n\n    if (needsRecalc) {\n      this.calculateSequenceParameters();\n      this.updateTempoDerivedState();\n    }\n  }\n\n  clone(): Stretch {\n    const result = new Stretch({\n      createBuffers: false,\n      inputBufferAdapterFactory: this.inputBufferAdapterFactory,\n      sampleBufferFactory: this.sampleBufferFactory,\n    });\n    result.tempo = this._tempo;\n    result.setParameters(\n      this.sampleRate,\n      this.sequenceMs,\n      this.seekWindowMs,\n      this._overlapMs,\n    );\n    return result;\n  }\n\n  seekBestOverlapPosition(inputBuffer?: StretchReadBufferAdapter): number {\n    const resolvedInputBuffer = inputBuffer ?? this.getInputBufferAdapter();\n    if (!this._quickSeek || this.seekLength <= QUICK_SEEK_FALLBACK_THRESHOLD) {\n      return this.seekBestOverlapPositionStereo(resolvedInputBuffer);\n    }\n    return this.seekBestOverlapPositionStereoQuick(resolvedInputBuffer);\n  }\n\n  private seekBestOverlapPositionStereo(\n    inputBuffer: StretchReadBufferAdapter,\n  ): number {\n    let bestOffset: number;\n    let bestCorrelation: number;\n    let correlation: number;\n\n    this.preCalculateCorrelationReferenceStereo();\n\n    bestOffset = 0;\n    bestCorrelation = -Infinity;\n\n    for (let i = 0; i < this.seekLength; i++) {\n      correlation = this.calculateCrossCorrelationStereo(\n        2 * i,\n        this.refMidBuffer,\n        inputBuffer,\n      );\n\n      if (correlation > bestCorrelation) {\n        bestCorrelation = correlation;\n        bestOffset = i;\n      }\n    }\n\n    return bestOffset;\n  }\n\n  private seekBestOverlapPositionStereoQuick(\n    inputBuffer: StretchReadBufferAdapter,\n  ): number {\n    let bestOffset: number;\n    let bestCorrelation: number;\n    let correlation: number;\n    let correlationOffset: number;\n    let tempOffset: number;\n    let evaluatedCandidates: number;\n\n    this.preCalculateCorrelationReferenceStereo();\n\n    bestCorrelation = this.calculateCrossCorrelationStereo(\n      0,\n      this.refMidBuffer,\n      inputBuffer,\n    );\n    evaluatedCandidates = 1;\n    bestOffset = 0;\n    correlationOffset = 0;\n\n    for (let scanCount = 0; scanCount < 4; scanCount++) {\n      let previousTempOffset = Number.MIN_SAFE_INTEGER;\n      const scanOffsets = this.getQuickScanOffsets(scanCount);\n      for (const scanOffset of scanOffsets) {\n        tempOffset = correlationOffset + scanOffset;\n        if (tempOffset === previousTempOffset) {\n          continue;\n        }\n        previousTempOffset = tempOffset;\n        if (tempOffset < 0) {\n          continue;\n        }\n        if (tempOffset >= this.seekLength) {\n          continue;\n        }\n\n        correlation = this.calculateCrossCorrelationStereo(\n          2 * tempOffset,\n          this.refMidBuffer,\n          inputBuffer,\n        );\n        evaluatedCandidates++;\n\n        if (correlation > bestCorrelation) {\n          bestCorrelation = correlation;\n          bestOffset = tempOffset;\n        }\n      }\n      correlationOffset = bestOffset;\n    }\n\n    if (evaluatedCandidates < QUICK_SEEK_MIN_VALID_CANDIDATES) {\n      return this.seekBestOverlapPositionStereo(inputBuffer);\n    }\n\n    return bestOffset;\n  }\n\n  private getQuickScanOffsets(stage: number): readonly number[] {\n    const maxOffset = Math.max(1, this.seekLength - 1);\n    if (stage === 0) {\n      return this.generateFractionalScanOffsets(maxOffset, 2, 1, 14, 24);\n    }\n\n    if (stage === 1) {\n      return this.generateSymmetricScanOffsets(maxOffset, 0.2);\n    }\n\n    if (stage === 2) {\n      return this.generateSymmetricScanOffsets(maxOffset, 0.06);\n    }\n\n    return this.generateSymmetricScanOffsets(maxOffset, 0.015);\n  }\n\n  private generateFractionalScanOffsets(\n    maxOffset: number,\n    startNumerator: number,\n    stepNumerator: number,\n    denominator: number,\n    steps: number,\n  ): readonly number[] {\n    const offsets: number[] = [];\n    const seen = new Set<number>();\n    const safeDenominator = Math.max(1, denominator);\n    const safeSteps = Math.max(1, steps);\n\n    for (let i = 0; i < safeSteps; i++) {\n      const numerator = startNumerator + i * stepNumerator;\n      const value = Math.round((maxOffset * numerator) / safeDenominator);\n      if (value <= 0 || value >= this.seekLength || seen.has(value)) {\n        continue;\n      }\n      seen.add(value);\n      offsets.push(value);\n    }\n\n    return offsets;\n  }\n\n  private generateSymmetricScanOffsets(\n    maxOffset: number,\n    spanRatio: number,\n  ): readonly number[] {\n    const span = Math.max(1, Math.round(maxOffset * spanRatio));\n    const scales = [1, 0.75, 0.5, 0.25];\n    const negative: number[] = [];\n    const positive: number[] = [];\n    const seen = new Set<number>();\n\n    for (const scale of scales) {\n      const magnitude = Math.max(1, Math.round(span * scale));\n      const neg = -magnitude;\n      const pos = magnitude;\n      if (!seen.has(neg)) {\n        seen.add(neg);\n        negative.push(neg);\n      }\n      if (!seen.has(pos)) {\n        seen.add(pos);\n        positive.push(pos);\n      }\n    }\n\n    return negative.concat(positive);\n  }\n\n  private preCalculateCorrelationReferenceStereo(): void {\n    let energy = 0;\n    for (let i = 0; i < this.overlapLength; i++) {\n      const temp = i * (this.overlapLength - i);\n      const ctx = i * 2;\n      const left = this.midBuffer![ctx] * temp;\n      const right = this.midBuffer![ctx + 1] * temp;\n      this.refMidBuffer[ctx] = left;\n      this.refMidBuffer[ctx + 1] = right;\n      energy += left * left + right * right;\n    }\n    this.refMidBufferEnergy = energy;\n  }\n\n  private calculateCrossCorrelationStereo(\n    mixingPos: number,\n    compare: Float32Array,\n    inputBuffer: StretchReadBufferAdapter,\n  ): number {\n    mixingPos += inputBuffer.startIndex;\n\n    let dot = 0;\n    let sourceEnergy = 0;\n    const calcLength = 2 * this.overlapLength;\n    const source = inputBuffer.readSubarray(mixingPos, mixingPos + calcLength);\n\n    for (let i = 0; i < calcLength; i += 2) {\n      const sourceLeft = i < source.length ? source[i] : 0;\n      const sourceRight = i + 1 < source.length ? source[i + 1] : 0;\n      const compareLeft = compare[i];\n      const compareRight = compare[i + 1];\n      dot += sourceLeft * compareLeft + sourceRight * compareRight;\n      sourceEnergy += sourceLeft * sourceLeft + sourceRight * sourceRight;\n    }\n\n    if (\n      sourceEnergy <= NORMALIZED_CORRELATION_EPSILON ||\n      this.refMidBufferEnergy <= NORMALIZED_CORRELATION_EPSILON\n    ) {\n      return -1;\n    }\n\n    return dot / Math.sqrt(sourceEnergy * this.refMidBufferEnergy);\n  }\n\n  private overlapStereo(\n    inputPosition: number,\n    inputBuffer: StretchReadBufferAdapter,\n    outputBuffer: StretchWriteBufferAdapter,\n  ): void {\n    inputPosition += inputBuffer.startIndex;\n    const overlapSamples = this.overlapLength * 2;\n    if (this.overlapScratch.length < overlapSamples) {\n      this.overlapScratch = new Float32Array(overlapSamples);\n    }\n    const output = this.overlapScratch;\n    const input = inputBuffer.readSubarray(\n      inputPosition,\n      inputPosition + overlapSamples,\n    );\n\n    const frameScale = 1 / this.overlapLength;\n\n    for (let i = 0; i < this.overlapLength; i++) {\n      const tempFrame = (this.overlapLength - i) * frameScale;\n      const fi = i * frameScale;\n      const ctx = 2 * i;\n      const inputLeft = ctx < input.length ? input[ctx] : 0;\n      const inputRight = ctx + 1 < input.length ? input[ctx + 1] : 0;\n      output[ctx] = inputLeft * fi + this.midBuffer![ctx] * tempFrame;\n      output[ctx + 1] = inputRight * fi + this.midBuffer![ctx + 1] * tempFrame;\n    }\n\n    outputBuffer.appendSamples(output, this.overlapLength);\n  }\n\n  process(): void {\n    const inputBuffer = this.getInputBufferAdapter();\n    const outputBuffer = this.getOutputBufferAdapter();\n\n    if (!this.bootstrapMidBuffer(inputBuffer)) {\n      return;\n    }\n\n    while (inputBuffer.frameCount >= this.sampleReq) {\n      this.processOneWindow(inputBuffer, outputBuffer);\n    }\n  }\n\n  private bootstrapMidBuffer(inputBuffer: StretchReadBufferAdapter): boolean {\n    if (!this.midBufferDirty) {\n      return true;\n    }\n    if (inputBuffer.frameCount < this.overlapLength) {\n      return false;\n    }\n    const needed = this.overlapLength * 2;\n    if (!this.midBuffer || this.midBuffer.length < needed) {\n      this.midBuffer = new Float32Array(needed);\n    }\n    inputBuffer.receiveSamples(this.midBuffer, this.overlapLength);\n    this.midBufferDirty = false;\n    return true;\n  }\n\n  private processOneWindow(\n    inputBuffer: StretchReadBufferAdapter,\n    outputBuffer: StretchWriteBufferAdapter,\n  ): void {\n    const offset = this.seekBestOverlapPosition(inputBuffer);\n    this.overlapStereo(2 * Math.floor(offset), inputBuffer, outputBuffer);\n\n    const middleFrames = this.seekWindowLength - 2 * this.overlapLength;\n    if (middleFrames > 0) {\n      outputBuffer.putFrom(\n        inputBuffer,\n        offset + this.overlapLength,\n        middleFrames,\n      );\n    }\n\n    this.captureOverlapHistory(offset, inputBuffer);\n    this.advanceInputByNominalSkip(inputBuffer);\n  }\n\n  private captureOverlapHistory(\n    offset: number,\n    inputBuffer: StretchReadBufferAdapter,\n  ): void {\n    const start =\n      inputBuffer.startIndex +\n      2 * (offset + this.seekWindowLength - this.overlapLength);\n    this.midBuffer!.set(\n      inputBuffer.readSubarray(start, start + 2 * this.overlapLength),\n    );\n  }\n\n  private advanceInputByNominalSkip(\n    inputBuffer: StretchReadBufferAdapter,\n  ): void {\n    this.skipFract += this.nominalSkip;\n    const overlapSkip = Math.floor(this.skipFract);\n    this.skipFract -= overlapSkip;\n    inputBuffer.receive(overlapSkip);\n  }\n\n  private getInputBufferAdapter(): StretchReadBufferAdapter {\n    if (this._inputBuffer === null) {\n      throw new Error('inputBuffer is not set');\n    }\n    this.inputBufferAdapter.setBuffer(this._inputBuffer);\n    return this.inputBufferAdapter;\n  }\n\n  private getOutputBufferAdapter(): StretchWriteBufferAdapter {\n    if (this._outputBuffer === null) {\n      throw new Error('outputBuffer is not set');\n    }\n    this.outputBufferAdapter.setOutputBuffer(this._outputBuffer);\n    return this.outputBufferAdapter;\n  }\n}\n"
    },
    {
      "name": "../../core/src/testFloatEqual.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/**\n * Tests whether two floating-point numbers differ beyond a fixed epsilon.\n *\n * @param a First number to compare.\n * @param b Second number to compare.\n * @returns True when the numbers differ by more than epsilon, false otherwise.\n * @remarks\n * Uses a fixed epsilon threshold to determine significant difference.\n */\nexport default function isFloatDifferent(a: number, b: number): boolean {\n  return (a > b ? a - b : b - a) > 1e-10;\n}\n"
    },
    {
      "name": "../../core/src/SoundTouch.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport RateTransposer from './RateTransposer.js';\nimport type { RateTransposerInterpolationStrategy } from './RateTransposer.js';\nimport type {\n  InterpolationStrategyParams,\n  RateTransposerInterpolationStrategyId,\n} from './interpolationStrategyRegistry.js';\nimport {\n  createCircularStretchInputBufferAdapter,\n  createFifoStretchInputBufferAdapter,\n  default as Stretch,\n} from './Stretch.js';\nimport type { StretchParameters } from './Stretch.js';\nimport type { StretchFactory, StretchPipe } from './StretchPipe.js';\nimport CircularSampleBuffer from './CircularSampleBuffer.js';\nimport FifoSampleBuffer from './FifoSampleBuffer.js';\nimport {\n  createCircularSampleBufferAdapter,\n  createFifoSampleBufferAdapter,\n} from './SampleBufferAdapter.js';\nimport type {\n  SampleBuffer,\n  SampleBufferFactory,\n  SampleBufferType,\n} from './SampleBuffer.js';\nimport isFloatDifferent from './testFloatEqual.js';\n\n/**\n * Configuration options for constructing a `SoundTouch` processor.\n *\n * @remarks\n * Allows customization of sample rate, buffer strategy, and interpolation strategy for the SoundTouch engine.\n */\nexport interface SoundTouchOptions {\n  /** Processing sample rate in Hz.\n   *\n   * @defaultValue 44100\n   */\n  sampleRate?: number;\n\n  /**\n   * Internal sample buffer strategy.\n   *\n   * @defaultValue 'circular'\n   */\n  sampleBufferType?: SampleBufferType;\n\n  /**\n   * Custom factory for creating all chain buffers.\n   *\n   * @remarks\n   * When provided, this takes precedence over `sampleBufferType` for buffer\n   * instantiation.\n   */\n  sampleBufferFactory?: SampleBufferFactory;\n\n  /**\n   * Interpolation strategy used by the rate transposer stage.\n   *\n   * @defaultValue 'linear'\n   */\n  interpolationStrategy?: RateTransposerInterpolationStrategy;\n\n  /**\n   * Optional factory for creating a custom time-stretch stage.\n   *\n   * @remarks\n   * When provided, `SoundTouch` calls this function instead of constructing\n   * the default WSOLA `Stretch` instance. Use this to substitute a phase\n   * vocoder or any other `StretchPipe`-compatible implementation.\n   *\n   * @example\n   * import { createPhaseVocoderFactory } from '@soundtouchjs/stretch-phase-vocoder';\n   * const st = new SoundTouch({ stretchFactory: createPhaseVocoderFactory() });\n   */\n  stretchFactory?: StretchFactory;\n}\n\n/**\n * Main processing engine for pitch shifting and time-stretching.\n *\n * @remarks\n * Chains a `RateTransposer` and `Stretch` stage to deliver real-time pitch manipulation\n * without affecting playback tempo. Set `pitch`, `pitchOctaves`, or `pitchSemitones` to\n * control the output. The internal `_rate` and `_tempo` pipeline values are derived\n * automatically from `virtualPitch`.\n */\nexport default class SoundTouch {\n  transposer: RateTransposer;\n  stretch: StretchPipe;\n\n  private _sampleRate: number;\n\n  private _sampleBufferType: SampleBufferType;\n  private _sampleBufferFactory: SampleBufferFactory;\n  private _interpolationStrategy: RateTransposerInterpolationStrategyId;\n\n  private _inputBuffer: SampleBuffer;\n  private _intermediateBuffer: SampleBuffer;\n  private _outputBuffer: SampleBuffer;\n\n  private _rate: number;\n  private _tempo: number;\n\n  /** Current pitch multiplier. Updated by the `pitch`, `pitchOctaves`, and `pitchSemitones` setters. */\n  virtualPitch: number;\n\n  /**\n   * Creates a new SoundTouch processor instance.\n   * @param options Construction options for sample rate, buffer strategy, and factories.\n   */\n  constructor(options: SoundTouchOptions = {}) {\n    this._sampleBufferType = options.sampleBufferType ?? 'circular';\n    this._sampleBufferFactory =\n      options.sampleBufferFactory ??\n      (this._sampleBufferType === 'fifo'\n        ? () => new FifoSampleBuffer()\n        : () => new CircularSampleBuffer());\n    this.transposer = new RateTransposer({\n      createBuffers: false,\n      sampleBufferAdapterFactory:\n        this._sampleBufferType === 'circular'\n          ? createCircularSampleBufferAdapter\n          : createFifoSampleBufferAdapter,\n      sampleBufferFactory: this._sampleBufferFactory,\n      interpolationStrategy: options.interpolationStrategy,\n    });\n    this._interpolationStrategy = this.transposer.strategy;\n    this._sampleRate = options.sampleRate ?? 44100;\n    if (options.stretchFactory) {\n      this.stretch = options.stretchFactory(this._sampleRate, {\n        sampleBufferFactory: this._sampleBufferFactory,\n        sampleBufferType: this._sampleBufferType,\n      });\n    } else {\n      this.stretch = new Stretch({\n        sampleRate: this._sampleRate,\n        createBuffers: false,\n        inputBufferAdapterFactory:\n          this._sampleBufferType === 'circular'\n            ? createCircularStretchInputBufferAdapter\n            : createFifoStretchInputBufferAdapter,\n        sampleBufferFactory: this._sampleBufferFactory,\n      });\n    }\n    this.stretch.setParameters(this._sampleRate, 0, 0, 0);\n\n    this._inputBuffer = this._sampleBufferFactory();\n    this._intermediateBuffer = this._sampleBufferFactory();\n    this._outputBuffer = this._sampleBufferFactory();\n\n    this._rate = 0;\n    this._tempo = 0;\n    this.virtualPitch = 1.0;\n\n    this.calculateEffectiveRateAndTempo();\n  }\n\n  /**\n   * Clears both processing stages and their internal buffers.\n   * @remarks\n   * Resets the state of the transposer and stretch stages, including all internal buffers.\n   */\n  clear(): void {\n    this.transposer.clear();\n    this.stretch.clear();\n  }\n\n  /**\n   * Creates an independent copy with equivalent runtime configuration.\n   */\n  clone(): SoundTouch {\n    const result = new SoundTouch({\n      sampleRate: this._sampleRate,\n      sampleBufferType: this._sampleBufferType,\n      sampleBufferFactory: this._sampleBufferFactory,\n      interpolationStrategy: {\n        id: this._interpolationStrategy,\n        params: this.transposer.strategyParams,\n      },\n    });\n    result.pitch = this.virtualPitch;\n    return result;\n  }\n\n  /**\n   * Active interpolation strategy id used by the transposer stage.\n   * @returns The current interpolation strategy identifier.\n   */\n  get interpolationStrategy(): RateTransposerInterpolationStrategyId {\n    return this._interpolationStrategy;\n  }\n\n  /**\n   * Active interpolation strategy params used by the transposer stage.\n   * @returns The current interpolation strategy parameters.\n   */\n  get interpolationStrategyParams(): Readonly<InterpolationStrategyParams> {\n    return this.transposer.strategyParams;\n  }\n\n  /**\n   * Switches interpolation strategy at runtime.\n   * @param strategy The new interpolation strategy to use.\n   */\n  setInterpolationStrategy(\n    strategy: RateTransposerInterpolationStrategy,\n  ): void {\n    this.transposer.setInterpolationStrategy(strategy);\n    this._interpolationStrategy = this.transposer.strategy;\n  }\n\n  /**\n   * Applies a partial runtime params update to the current strategy.\n   * @param params Partial set of parameters to update.\n   */\n  setInterpolationStrategyParams(\n    params: Partial<InterpolationStrategyParams>,\n  ): void {\n    this.transposer.setInterpolationStrategyParams(params);\n  }\n\n  /**\n   * Applies a partial set of WSOLA timing parameters to the stretch stage.\n   *\n   * @remarks\n   * Delegates directly to {@link Stretch.setStretchParameters}. Only the provided\n   * fields are updated; omitted fields remain unchanged. Pass `sequenceMs: 0` or\n   * `seekWindowMs: 0` to switch that dimension back to auto-calculation.\n   *\n   * @param params Partial set of WSOLA timing parameters to apply.\n   *\n   * @example\n   * st.setStretchParameters({ overlapMs: 12, quickSeek: false });\n   */\n  setStretchParameters(params: StretchParameters): void {\n    this.stretch.setStretchParameters(params);\n  }\n\n  /**\n   * Sets the pitch multiplier and recomputes the derived pipeline rate and tempo.\n   *\n   * @remarks\n   * Internally sets `_rate = pitch` and `_tempo = 1 / pitch`, rewiring the\n   * Transposer→Stretch stage order when pitch > 1.\n   */\n  set pitch(pitch: number) {\n    this.virtualPitch = pitch;\n    this.calculateEffectiveRateAndTempo();\n  }\n\n  /**\n   * Sets pitch by octave offset.\n   */\n  set pitchOctaves(pitchOctaves: number) {\n    this.pitch = Math.exp(0.69314718056 * pitchOctaves);\n    this.calculateEffectiveRateAndTempo();\n  }\n\n  /**\n   * Sets pitch by semitone offset.\n   */\n  set pitchSemitones(pitchSemitones: number) {\n    this.pitchOctaves = pitchSemitones / 12.0;\n  }\n\n  /**\n   * Input buffer for upstream interleaved stereo frames.\n   * @returns The input buffer for writing audio frames.\n   */\n  get inputBuffer(): SampleBuffer {\n    return this._inputBuffer;\n  }\n\n  /**\n   * Output buffer that downstream consumers read from.\n   * @returns The output buffer for reading processed audio frames.\n   */\n  get outputBuffer(): SampleBuffer {\n    return this._outputBuffer;\n  }\n\n  /**\n   * Recomputes the effective pipeline rate/tempo from `virtualPitch` and rewires stage order when needed.\n   *\n   * @remarks\n   * `_rate` is set to `virtualPitch`; `_tempo` to `1 / virtualPitch`. When `_rate > 1` the\n   * Stretch stage feeds the Transposer; otherwise the order is reversed.\n   */\n  calculateEffectiveRateAndTempo(): void {\n    const previousTempo = this._tempo;\n    const previousRate = this._rate;\n\n    this._tempo = 1.0 / this.virtualPitch;\n    this._rate = this.virtualPitch;\n\n    if (isFloatDifferent(this._tempo, previousTempo)) {\n      this.stretch.tempo = this._tempo;\n    }\n    if (isFloatDifferent(this._rate, previousRate)) {\n      this.transposer.rate = this._rate;\n    }\n\n    if (this._rate > 1.0) {\n      if (this._outputBuffer !== this.transposer.outputBuffer) {\n        this.stretch.inputBuffer = this._inputBuffer;\n        this.stretch.outputBuffer = this._intermediateBuffer;\n\n        this.transposer.inputBuffer = this._intermediateBuffer;\n        this.transposer.outputBuffer = this._outputBuffer;\n      }\n    } else {\n      if (this._outputBuffer !== this.stretch.outputBuffer) {\n        this.transposer.inputBuffer = this._inputBuffer;\n        this.transposer.outputBuffer = this._intermediateBuffer;\n\n        this.stretch.inputBuffer = this._intermediateBuffer;\n        this.stretch.outputBuffer = this._outputBuffer;\n      }\n    }\n  }\n\n  /**\n   * Runs one processing step through the currently selected stage order.\n   * @remarks\n   * Processes available frames through the pipeline, updating output buffers.\n   */\n  process(): void {\n    if (this._rate > 1.0) {\n      this.stretch.process();\n      this.transposer.process();\n    } else {\n      this.transposer.process();\n      this.stretch.process();\n    }\n  }\n}\n"
    },
    {
      "name": "../../worklet-base/src/SoundTouchProcessorBase.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport { SoundTouch, resolveInterpolationStrategy } from '@soundtouchjs/core';\nimport type {\n  InterpolationStrategyParams,\n  RateTransposerInterpolationStrategy,\n  SoundTouchOptions,\n  StretchParameters,\n} from '@soundtouchjs/core';\nimport type { ProcessCoreResult, ProcessorMessage } from './types.js';\n\n/**\n * Abstract base class for all SoundTouchJS AudioWorklet processor implementations.\n *\n * @remarks\n * Centralises shared state (pipe, sample buffers, runtime-update queue), the\n * `applyPendingRuntimeUpdates` helper, and the core DSP pipeline in\n * `processCore`. Subclasses must implement `process` and `onProcessComplete`.\n *\n * The default `process` implementation calls `applyPendingRuntimeUpdates`,\n * `processCore`, and then `onProcessComplete`. Subclasses that need to\n * interleave additional logic (e.g. LPC analysis) can override `beforePipeProcess`\n * and/or `extractSamples` instead of overriding `process` entirely.\n */\nexport abstract class SoundTouchProcessorBase extends AudioWorkletProcessor {\n  /** The SoundTouch DSP pipeline instance. */\n  protected _pipe: SoundTouch;\n  /** Interleaved (L, R, L, R, …) input staging buffer. */\n  protected _samples: Float32Array;\n  /** Interleaved output staging buffer populated by `extractSamples`. */\n  protected _outputSamples: Float32Array;\n  /** Cumulative count of render blocks where the output buffer ran short. */\n  protected _underrunCount = 0;\n  /** Total render blocks processed since construction. */\n  protected _blockCount = 0;\n\n  private _pendingInterpolationStrategy: RateTransposerInterpolationStrategy | null =\n    null;\n  private _pendingInterpolationStrategyParams: Partial<InterpolationStrategyParams> | null =\n    null;\n  private _pendingStretchParameters: StretchParameters | null = null;\n\n  /** Label used in console messages (e.g. `'[SoundTouchProcessor]'`). */\n  protected readonly processorLabel: string;\n\n  /**\n   * Validates and resolves an interpolation strategy id, falling back to\n   * `'lanczos'` if the id is unrecognised.\n   *\n   * @remarks\n   * Call this as a static expression inside the `super()` argument list of a\n   * subclass constructor so that strategy resolution happens before pipe creation.\n   *\n   * @param strategy - The strategy id provided by the caller.\n   * @param processorLabel - Label included in the fallback console message.\n   * @returns The original `strategy` if valid, or `'lanczos'` as a fallback.\n   */\n  public static resolveStrategy(\n    strategy: RateTransposerInterpolationStrategy | undefined,\n    processorLabel: string,\n  ): RateTransposerInterpolationStrategy | undefined {\n    try {\n      if (strategy) {\n        resolveInterpolationStrategy(strategy);\n      }\n      return strategy;\n    } catch {\n      console.info(\n        `${processorLabel} Unknown interpolation strategy id:`,\n        strategy,\n        '— falling back to lanczos.',\n      );\n      return 'lanczos';\n    }\n  }\n\n  /**\n   * @param processorLabel - Label string used in diagnostic messages.\n   * @param pipeOptions - Options forwarded to the `SoundTouch` constructor. The\n   *   `sampleRate` global must be available in the AudioWorklet scope.\n   */\n  constructor(processorLabel: string, pipeOptions: SoundTouchOptions) {\n    super();\n    this.processorLabel = processorLabel;\n    this._pipe = new SoundTouch(pipeOptions);\n    this._samples = new Float32Array(128 * 2);\n    this._outputSamples = new Float32Array(128 * 2);\n\n    const port = this.port;\n    if (port !== undefined) {\n      port.onmessage = (event: MessageEvent<ProcessorMessage>) => {\n        const message = event.data;\n        if (message.type === 'set-interpolation-strategy') {\n          this._pendingInterpolationStrategy = message.strategy;\n          return;\n        }\n        if (message.type === 'set-interpolation-strategy-params') {\n          this._pendingInterpolationStrategyParams = message.params;\n          return;\n        }\n        if (message.type === 'set-stretch-parameters') {\n          this._pendingStretchParameters = message.params;\n        }\n      };\n    }\n  }\n\n  /**\n   * Flushes any pending interpolation-strategy or stretch-parameter change\n   * that arrived via `port.onmessage` since the last render block.\n   *\n   * @remarks\n   * Call this at the top of `process` before touching `_pipe`.\n   */\n  protected applyPendingRuntimeUpdates(): void {\n    if (this._pendingInterpolationStrategy !== null) {\n      try {\n        this._pipe.setInterpolationStrategy(\n          this._pendingInterpolationStrategy,\n        );\n      } catch {\n        console.info(\n          `${this.processorLabel} Failed to switch interpolation strategy:`,\n          this._pendingInterpolationStrategy,\n        );\n      }\n      this._pendingInterpolationStrategy = null;\n    }\n\n    if (this._pendingInterpolationStrategyParams !== null) {\n      try {\n        this._pipe.setInterpolationStrategyParams(\n          this._pendingInterpolationStrategyParams,\n        );\n      } catch {\n        console.info(\n          `${this.processorLabel} Failed to update interpolation strategy params.`,\n        );\n      }\n      this._pendingInterpolationStrategyParams = null;\n    }\n\n    if (this._pendingStretchParameters !== null) {\n      try {\n        this._pipe.setStretchParameters(this._pendingStretchParameters);\n      } catch {\n        console.info(\n          `${this.processorLabel} Failed to update stretch parameters.`,\n        );\n      }\n      this._pendingStretchParameters = null;\n    }\n  }\n\n  /**\n   * Optional hook called after input routing and buffer resize but **before**\n   * the SoundTouch pipe processes the block.\n   *\n   * @remarks\n   * Override in subclasses that need to inspect or transform the raw input\n   * before it enters the DSP pipeline (e.g. computing LPC coefficients).\n   *\n   * @param _leftInput - Left-channel input for this render block.\n   * @param _rightInput - Right-channel input (same as left for mono sources).\n   * @param _frameCount - Number of frames in this block.\n   * @param _parameters - AudioParam k-rate values for this render block.\n   */\n  protected beforePipeProcess(\n    _leftInput: Float32Array,\n    _rightInput: Float32Array,\n    _frameCount: number,\n    _parameters: Record<string, Float32Array>,\n  ): void {\n    return;\n  }\n\n  /**\n   * Extracts rendered frames from the output buffer, writes them to\n   * `leftOutput`/`rightOutput`, zero-fills any gap, and returns RMS/peak metrics.\n   *\n   * @remarks\n   * Override in subclasses that apply post-extraction transforms (e.g. formant\n   * correction). Overrides are responsible for the full extraction, write-back,\n   * silence fill, and returning `{ outputRms, outputPeak }`.\n   *\n   * @param leftOutput - Destination view for the left channel.\n   * @param rightOutput - Destination view for the right channel.\n   * @param frameCount - Total frames expected in this block.\n   * @param toExtract - Frames available to extract (≤ frameCount).\n   * @param _parameters - AudioParam k-rate values (available for overrides).\n   * @returns RMS and peak of the extracted block.\n   */\n  protected extractSamples(\n    leftOutput: Float32Array,\n    rightOutput: Float32Array,\n    frameCount: number,\n    toExtract: number,\n    _parameters: Record<string, Float32Array>,\n  ): { outputRms: number; outputPeak: number } {\n    let outputRms = 0;\n    let outputPeak = 0;\n\n    if (toExtract > 0) {\n      const extracted = this._outputSamples;\n      this._pipe.outputBuffer.extract(extracted, 0, toExtract);\n      this._pipe.outputBuffer.receive(toExtract);\n      let sumSq = 0;\n      let peak = 0;\n      for (let i = 0; i < toExtract; i++) {\n        const l = extracted[i * 2];\n        const r = extracted[i * 2 + 1];\n        leftOutput[i] = Number.isFinite(l) ? l : 0;\n        rightOutput[i] = Number.isFinite(r) ? r : 0;\n        sumSq += l * l + r * r;\n        peak = Math.max(peak, Math.abs(l), Math.abs(r));\n      }\n      outputRms = Math.sqrt(sumSq / (toExtract * 2));\n      outputPeak = peak;\n    }\n\n    for (let i = toExtract; i < frameCount; i++) {\n      leftOutput[i] = 0;\n      rightOutput[i] = 0;\n    }\n\n    return { outputRms, outputPeak };\n  }\n\n  /**\n   * Runs the full DSP pipeline for one render block and returns metrics.\n   *\n   * @remarks\n   * Handles input routing, buffer resize, `beforePipeProcess`, pitch\n   * calculation, sample interleaving, pipe feed/process, counter updates,\n   * and `extractSamples`. Returns `null` when the input is empty or\n   * the output has not been allocated, keeping the processor alive.\n   *\n   * @param inputs - AudioWorklet input buses.\n   * @param outputs - AudioWorklet output buses.\n   * @param parameters - k-rate AudioParam values.\n   * @returns Render-block result, or `null` if inputs are not ready.\n   */\n  protected processCore(\n    inputs: Float32Array[][],\n    outputs: Float32Array[][],\n    parameters: Record<string, Float32Array>,\n  ): ProcessCoreResult | null {\n    const input = inputs[0];\n    const output = outputs[0];\n\n    if (!input || !input.length || !output[0] || !output[0].length) {\n      return null;\n    }\n\n    const leftInput = input[0];\n    // Mono input: duplicate the single channel to both sides of the stereo pipeline.\n    const rightInput = input.length > 1 ? input[1] : input[0];\n    const leftOutput = output[0];\n    // Mono output (outputChannelCount: 1): both channels write to the same array.\n    const rightOutput = output.length > 1 ? output[1] : output[0];\n    const frameCount = leftInput.length;\n\n    if (this._samples.length < frameCount * 2) {\n      this._samples = new Float32Array(frameCount * 2);\n      this._outputSamples = new Float32Array(frameCount * 2);\n    }\n\n    this.beforePipeProcess(leftInput, rightInput, frameCount, parameters);\n\n    const pitch = parameters['pitch'][0];\n    const pitchSemitones = parameters['pitchSemitones'][0];\n    const playbackRate = parameters['playbackRate'][0];\n\n    this._pipe.pitch =\n      (pitch * Math.pow(2, pitchSemitones / 12)) / playbackRate;\n\n    const samples = this._samples;\n    for (let i = 0; i < frameCount; i++) {\n      samples[i * 2] = leftInput[i];\n      samples[i * 2 + 1] = rightInput[i];\n    }\n\n    this._pipe.inputBuffer.putSamples(samples, 0, frameCount);\n    this._pipe.process();\n\n    const outputBuffer = this._pipe.outputBuffer;\n    const available = outputBuffer.frameCount;\n    const toExtract = Math.min(available, frameCount);\n\n    this._blockCount++;\n    if (available < frameCount) {\n      this._underrunCount++;\n    }\n\n    const { outputRms, outputPeak } = this.extractSamples(\n      leftOutput,\n      rightOutput,\n      frameCount,\n      toExtract,\n      parameters,\n    );\n\n    return {\n      frameCount,\n      toExtract,\n      available,\n      leftInput,\n      rightInput,\n      leftOutput,\n      rightOutput,\n      outputRms,\n      outputPeak,\n    };\n  }\n\n  /**\n   * Called after a successful `processCore` invocation with the render-block result.\n   *\n   * @remarks\n   * Implement this to post processor-specific metrics to the main thread.\n   * The default `process` implementation calls this after `processCore`.\n   *\n   * @param result - Data from the completed render block.\n   */\n  protected abstract onProcessComplete(result: ProcessCoreResult): void;\n\n  /**\n   * AudioWorkletProcessor render callback. Keeps the processor alive by always returning `true`.\n   *\n   * @remarks\n   * The default implementation calls `applyPendingRuntimeUpdates`, `processCore`,\n   * and `onProcessComplete`. Override only when the execution order must differ\n   * (e.g. pre-pipe analysis steps not covered by `beforePipeProcess`).\n   *\n   * @param inputs - AudioWorklet input buses.\n   * @param outputs - AudioWorklet output buses.\n   * @param parameters - k-rate AudioParam values.\n   * @returns Always `true` to keep the processor alive.\n   */\n  process(\n    inputs: Float32Array[][],\n    outputs: Float32Array[][],\n    parameters: Record<string, Float32Array>,\n  ): boolean {\n    this.applyPendingRuntimeUpdates();\n    const result = this.processCore(inputs, outputs, parameters);\n    if (result !== null) {\n      this.onProcessComplete(result);\n    }\n    return true;\n  }\n}\n"
    },
    {
      "name": "../../worklet-base/src/types.ts",
      "content": "import type {\n  InterpolationStrategyParams,\n  RateTransposerInterpolationStrategy,\n  StretchParameters,\n} from '@soundtouchjs/core';\n\n/** AudioParam descriptor shape expected by the worklet runtime. */\nexport interface ParameterDescriptor {\n  name: string;\n  defaultValue: number;\n  minValue: number;\n  maxValue: number;\n  automationRate: 'k-rate' | 'a-rate';\n}\n\n/**\n * Standard pitch, pitchSemitones, and playbackRate AudioParam descriptors\n * shared by all SoundTouchJS worklet processors.\n */\nexport const STANDARD_PARAMETER_DESCRIPTORS: ParameterDescriptor[] = [\n  {\n    name: 'pitch',\n    defaultValue: 1.0,\n    minValue: 0.1,\n    maxValue: 8.0,\n    automationRate: 'k-rate',\n  },\n  {\n    name: 'pitchSemitones',\n    defaultValue: 0,\n    minValue: -24,\n    maxValue: 24,\n    automationRate: 'k-rate',\n  },\n  {\n    name: 'playbackRate',\n    defaultValue: 1.0,\n    minValue: 0.1,\n    maxValue: 8.0,\n    automationRate: 'k-rate',\n  },\n];\n\nexport interface SetInterpolationStrategyMessage {\n  type: 'set-interpolation-strategy';\n  strategy: RateTransposerInterpolationStrategy;\n}\n\nexport interface SetInterpolationStrategyParamsMessage {\n  type: 'set-interpolation-strategy-params';\n  params: Partial<InterpolationStrategyParams>;\n}\n\nexport interface SetStretchParametersMessage {\n  type: 'set-stretch-parameters';\n  params: StretchParameters;\n}\n\nexport type ProcessorMessage =\n  | SetInterpolationStrategyMessage\n  | SetInterpolationStrategyParamsMessage\n  | SetStretchParametersMessage;\n\n/**\n * Data returned by {@link SoundTouchProcessorBase.processCore} after a render block.\n *\n * @remarks\n * Contains routing arrays (for post-extraction overrides), counters, and the\n * RMS/peak values computed during extraction. Subclasses that override\n * `extractSamples` may return `outputRms: 0, outputPeak: 0` when they do not\n * compute those metrics.\n */\nexport interface ProcessCoreResult {\n  /** Number of frames in this render block. */\n  frameCount: number;\n  /** Number of frames extracted from the output buffer (≤ frameCount). */\n  toExtract: number;\n  /** Frames available in the output buffer before extraction. */\n  available: number;\n  /** Left-channel input view for this render block. */\n  leftInput: Float32Array;\n  /** Right-channel input view (same as leftInput for mono sources). */\n  rightInput: Float32Array;\n  /** Left-channel output view written during extraction. */\n  leftOutput: Float32Array;\n  /** Right-channel output view written during extraction. */\n  rightOutput: Float32Array;\n  /** RMS of the extracted block (both channels, 0 if not computed). */\n  outputRms: number;\n  /** Peak amplitude of the extracted block (0 if not computed). */\n  outputPeak: number;\n}\n"
    },
    {
      "name": "../src/constants.ts",
      "content": "/**\n * Registered processor identifier used by `AudioWorkletNode`.\n *\n * @remarks\n * This constant is used to identify the SoundTouch processor module when registering and constructing nodes.\n */\nexport const PROCESSOR_NAME = 'soundtouch-processor';\n\n/**\n * Default internal buffer strategy used when callers do not provide one.\n *\n * @remarks\n * Determines the default buffer implementation for the SoundTouch processing pipeline.\n */\nexport const DEFAULT_SAMPLE_BUFFER_TYPE = 'circular';\n"
    },
    {
      "name": "../src/processor.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport {\n  SoundTouchProcessorBase,\n  STANDARD_PARAMETER_DESCRIPTORS,\n} from '@soundtouchjs/worklet-base';\nimport { DEFAULT_SAMPLE_BUFFER_TYPE } from './constants.js';\nimport type { ProcessCoreResult } from '@soundtouchjs/worklet-base';\nimport type {\n  RateTransposerInterpolationStrategy,\n  SampleBufferType,\n} from '@soundtouchjs/core';\n\nconst PROCESSOR_NAME = 'soundtouch-processor';\n\n/**\n * Constructor options passed by `SoundTouchNode` on initialization.\n *\n * @remarks\n * Used to configure the processor's internal buffer strategy and interpolation strategy.\n */\ninterface ProcessorConstructorOptions {\n  processorOptions?: {\n    /** Preferred internal buffer strategy for the SoundTouch pipeline. */\n    sampleBufferType?: SampleBufferType;\n    /** Interpolation strategy for rate transposition. */\n    interpolationStrategy?: RateTransposerInterpolationStrategy;\n  };\n}\n\n/** Metrics snapshot sent from the processor to the main thread every 100 render blocks. */\ninterface MetricsMessage {\n  type: 'metrics';\n  /** Frames available in the output buffer at the time of the last render block. */\n  framesBuffered: number;\n  /** Cumulative count of render blocks where the output buffer had fewer frames than requested. */\n  underrunCount: number;\n  /** Total render blocks processed since the processor was created. */\n  blockCount: number;\n  /** RMS of output block (last 128 frames, both channels averaged) */\n  outputRms: number;\n  /** Peak of output block (last 128 frames, both channels) */\n  outputPeak: number;\n}\n\n/**\n * Audio render-thread processor that applies SoundTouch transformations to stereo blocks.\n *\n * @remarks\n * Receives audio from the main thread, applies pitch, tempo, and rate transformations,\n * and outputs processed stereo audio. Handles runtime strategy switching via messages.\n */\nclass SoundTouchProcessor extends SoundTouchProcessorBase {\n  /** Static AudioParam metadata consumed by the browser. */\n  static get parameterDescriptors() {\n    return STANDARD_PARAMETER_DESCRIPTORS;\n  }\n\n  /**\n   * @param options Worklet constructor options provided by the main thread.\n   *\n   * @remarks\n   * Unknown interpolation strategy ids are logged and coerced to `lanczos`\n   * so render-thread startup remains resilient.\n   */\n  constructor(options?: ProcessorConstructorOptions) {\n    super('[SoundTouchProcessor]', {\n      sampleRate,\n      sampleBufferType:\n        options?.processorOptions?.sampleBufferType ??\n        DEFAULT_SAMPLE_BUFFER_TYPE,\n      interpolationStrategy: SoundTouchProcessorBase.resolveStrategy(\n        options?.processorOptions?.interpolationStrategy,\n        '[SoundTouchProcessor]',\n      ),\n    });\n  }\n\n  protected onProcessComplete(result: ProcessCoreResult): void {\n    if (this._blockCount % 100 === 0) {\n      this.port.postMessage({\n        type: 'metrics',\n        framesBuffered: result.available,\n        underrunCount: this._underrunCount,\n        blockCount: this._blockCount,\n        outputRms: result.outputRms,\n        outputPeak: result.outputPeak,\n      } satisfies MetricsMessage);\n    }\n  }\n}\n\nregisterProcessor(PROCESSOR_NAME, SoundTouchProcessor);\n"
    }
  ],
  "upstreamCommit": "8d19a91524c578f9ac414c7e7db2a5a1a605ff12",
  "wrapperSources": [
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/src/SoundTouchNode.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport type {\n  InterpolationStrategyParams,\n  RateTransposerInterpolationStrategy,\n  SampleBufferType,\n  StretchParameters,\n} from '@soundtouchjs/core';\nexport type { StretchParameters } from '@soundtouchjs/core';\nimport { DEFAULT_SAMPLE_BUFFER_TYPE, PROCESSOR_NAME } from './constants.js';\n\n/**\n * Snapshot of processor performance metrics received from the render thread.\n *\n * @remarks\n * Emitted every 100 render blocks via the `metrics` CustomEvent on `SoundTouchNode`.\n * Access the latest snapshot via `SoundTouchNode.metrics`.\n */\nexport interface ProcessorMetrics {\n  /** Frames available in the output buffer at the last render block. */\n  framesBuffered: number;\n  /** Cumulative render blocks where the output buffer had fewer frames than requested. */\n  underrunCount: number;\n  /** Total render blocks processed since the processor was created. */\n  blockCount: number;\n  /** RMS of output block (last 128 frames, both channels averaged) */\n  outputRms: number;\n  /** Peak of output block (last 128 frames, both channels) */\n  outputPeak: number;\n  /** `performance.now()` timestamp recorded on the main thread when the metrics arrived. */\n  timestamp: DOMHighResTimeStamp;\n}\n\n/**\n * Construction options for `SoundTouchNode`.\n *\n * @remarks\n * Used to configure the AudioWorkletNode and its processor, including buffer strategy and interpolation strategy.\n */\nexport interface SoundTouchNodeOptions {\n  /**\n   * Optional processor URL retained for caller-side convenience.\n   *\n   * @remarks\n   * Module registration is still performed through `SoundTouchNode.register`.\n   */\n  processorUrl?: string | URL;\n\n  /**\n   * Internal sample buffer strategy used inside the render-thread processor.\n   */\n  sampleBufferType?: SampleBufferType;\n\n  /**\n   * Interpolation strategy used by the internal rate transposer.\n   */\n  interpolationStrategy?: RateTransposerInterpolationStrategy;\n}\n\n/**\n * Construction options for SoundTouchNode constructor.\n * Extends SoundTouchNodeOptions with required context.\n */\nexport interface SoundTouchNodeConstructorOptions extends SoundTouchNodeOptions {\n  /**\n   * The AudioContext or OfflineAudioContext.\n   * @remarks\n   * Required for node construction; determines the audio graph context.\n   */\n  context: BaseAudioContext;\n\n  /**\n   * Number of output channels. Defaults to `2` (stereo).\n   *\n   * @remarks\n   * Set to `1` when connecting to a mono destination or a downstream\n   * node that only accepts a single channel. The processor always\n   * processes interleaved stereo internally; setting this to `1` tells\n   * the Web Audio graph to mix down to mono on the output side.\n   *\n   * Note: mono input is always supported regardless of this setting —\n   * the processor duplicates a single input channel to both sides of\n   * the stereo pipeline automatically.\n   */\n  outputChannelCount?: 1 | 2;\n}\n\n/**\n * Main-thread AudioWorkletNode wrapper for SoundTouch audio processing.\n *\n * @remarks\n * Provides AudioParam accessors for `pitch`, `pitchSemitones`, and\n * `playbackRate`. Communicates with the render-thread processor for real-time\n * audio transformation.\n *\n * @example\n * const source = audioCtx.createBufferSource();\n * source.connect(stNode);\n *\n * const stNode = new SoundTouchNode({ context: audioCtx });\n * stNode.pitch.value = 1.2;\n * source.playbackRate.value = 0.8;\n * stNode.playbackRate.value = 0.8;\n * stNode.pitchSemitones.value = -3;\n */\nexport class SoundTouchNode extends AudioWorkletNode {\n  /**\n   * The registered processor name for this node type.\n   */\n  static readonly processorName = PROCESSOR_NAME;\n\n  /**\n   * Registers the SoundTouch processor module with the given AudioContext.\n   *\n   * @remarks\n   * Must be called before creating SoundTouchNode instances. Loads the processor script into the AudioWorklet.\n   *\n   * @param context - The AudioContext or OfflineAudioContext\n   * @param processorUrl - URL or path to the processor script\n   */\n  static async register(\n    context: BaseAudioContext,\n    processorUrl: string | URL,\n  ): Promise<void> {\n    await context.audioWorklet.addModule(processorUrl);\n  }\n\n  /**\n   * Registers an interpolation strategy installer module in AudioWorkletGlobalScope.\n   *\n   * @remarks\n   * The module should call core registration APIs during evaluation.\n   * Loads a strategy plugin for use in the render-thread processor.\n   */\n  static async registerStrategyModule(\n    context: BaseAudioContext,\n    strategyModuleUrl: string | URL,\n  ): Promise<void> {\n    await context.audioWorklet.addModule(strategyModuleUrl);\n  }\n\n  private _lastMetrics: ProcessorMetrics | null = null;\n\n  /**\n   * Creates a SoundTouchNode instance.\n   * @param options - Node and processor configuration.\n   */\n  constructor({\n    context,\n    sampleBufferType,\n    interpolationStrategy,\n    outputChannelCount,\n  }: SoundTouchNodeConstructorOptions) {\n    super(context, PROCESSOR_NAME, {\n      numberOfInputs: 1,\n      numberOfOutputs: 1,\n      outputChannelCount: [outputChannelCount ?? 2],\n      processorOptions: {\n        sampleBufferType: sampleBufferType ?? DEFAULT_SAMPLE_BUFFER_TYPE,\n        interpolationStrategy,\n      },\n    });\n\n    this.port.onmessage = (event: MessageEvent) => {\n      const message = event.data;\n      if (message?.type === 'metrics') {\n        const metrics: ProcessorMetrics = {\n          framesBuffered: message.framesBuffered,\n          underrunCount: message.underrunCount,\n          blockCount: message.blockCount,\n          outputRms: message.outputRms,\n          outputPeak: message.outputPeak,\n          timestamp: performance.now(),\n        };\n        this._lastMetrics = metrics;\n        this.dispatchEvent(new CustomEvent('metrics', { detail: metrics }));\n      }\n    };\n  }\n\n  /**\n   * Returns the most recent processor metrics snapshot, or `null` if no metrics have been received yet.\n   *\n   * @remarks\n   * Updated every 100 render blocks by the processor. Also dispatched as a `metrics` CustomEvent.\n   *\n   * @example\n   * stNode.addEventListener('metrics', (e) => {\n   *   console.log((e as CustomEvent<ProcessorMetrics>).detail.underrunCount);\n   * });\n   */\n  get metrics(): ProcessorMetrics | null {\n    return this._lastMetrics;\n  }\n\n  /**\n   * Pitch multiplier AudioParam (1.0 = original pitch).\n   * @returns The AudioParam controlling pitch.\n   */\n  get pitch(): AudioParam {\n    return this.parameters.get('pitch')!;\n  }\n\n  /**\n   * Pitch shift in semitones AudioParam (integer steps for musical key changes).\n   * @returns The AudioParam controlling pitch in semitones.\n   */\n  get pitchSemitones(): AudioParam {\n    return this.parameters.get('pitchSemitones')!;\n  }\n\n  /**\n   * Playback rate AudioParam. Set this to the same value as the source node's\n   * playbackRate so the processor can compensate pitch for tempo changes.\n   * @returns The AudioParam controlling playback rate.\n   */\n  get playbackRate(): AudioParam {\n    return this.parameters.get('playbackRate')!;\n  }\n\n  /**\n   * Switches interpolation strategy at runtime in the render-thread processor.\n   * @param strategy The new interpolation strategy to use.\n   */\n  setInterpolationStrategy(\n    strategy: RateTransposerInterpolationStrategy,\n  ): void {\n    this.port.postMessage({\n      type: 'set-interpolation-strategy',\n      strategy,\n    });\n  }\n\n  /**\n   * Applies a partial params update to the active interpolation strategy.\n   * @param params Partial set of parameters to update.\n   */\n  setInterpolationStrategyParams(\n    params: Partial<InterpolationStrategyParams>,\n  ): void {\n    this.port.postMessage({\n      type: 'set-interpolation-strategy-params',\n      params,\n    });\n  }\n\n  /**\n   * Applies a partial set of WSOLA timing parameters to the render-thread processor.\n   *\n   * @remarks\n   * The update is queued and applied at the next render-block boundary. Only the\n   * provided fields are updated; omitted fields remain unchanged. Pass `sequenceMs: 0`\n   * or `seekWindowMs: 0` to switch that dimension back to auto-calculation.\n   *\n   * @param params Partial WSOLA timing parameters to apply.\n   *\n   * @example\n   * stNode.setStretchParameters({ overlapMs: 12, quickSeek: false });\n   */\n  setStretchParameters(params: StretchParameters): void {\n    this.port.postMessage({\n      type: 'set-stretch-parameters',\n      params,\n    });\n  }\n}\n"
    },
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/src/constants.ts",
      "content": "/**\n * Registered processor identifier used by `AudioWorkletNode`.\n *\n * @remarks\n * This constant is used to identify the SoundTouch processor module when registering and constructing nodes.\n */\nexport const PROCESSOR_NAME = 'soundtouch-processor';\n\n/**\n * Default internal buffer strategy used when callers do not provide one.\n *\n * @remarks\n * Determines the default buffer implementation for the SoundTouch processing pipeline.\n */\nexport const DEFAULT_SAMPLE_BUFFER_TYPE = 'circular';\n"
    },
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/src/index.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Olli Parviainen\n * Copyright (c) Ryan Berdeen\n * Copyright (c) Jakub Fiala\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n/**\n * Main entry point for the SoundTouch audio-worklet package.\n *\n * @remarks\n * Re-exports the SoundTouchNode class, its options types, and the processor name constant for use in host applications.\n */\nexport { SoundTouchNode } from './SoundTouchNode.js';\nexport type {\n  ProcessorMetrics,\n  SoundTouchNodeConstructorOptions,\n  SoundTouchNodeOptions,\n  StretchParameters,\n} from './SoundTouchNode.js';\nexport { PROCESSOR_NAME } from './constants.js';\nexport { processOffline } from './processOffline.js';\nexport type { ProcessOfflineOptions } from './processOffline.js';\n"
    },
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/src/processOffline.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport type {\n  RateTransposerInterpolationStrategy,\n  SampleBufferType,\n  StretchParameters,\n} from '@soundtouchjs/core';\nimport { SoundTouchNode } from './SoundTouchNode.js';\n\n/**\n * Options for `processOffline`.\n *\n * @remarks\n * All audio transform parameters are optional and default to their neutral values\n * (`pitch: 1.0`, `pitchSemitones: 0`, `playbackRate: 1.0`).\n */\nexport interface ProcessOfflineOptions {\n  /**\n   * The source audio buffer to process.\n   */\n  input: AudioBuffer;\n\n  /**\n   * URL or path to the SoundTouch processor script, passed to `SoundTouchNode.register`.\n   */\n  processorUrl: string | URL;\n\n  /**\n   * Pitch multiplier (1.0 = original pitch).\n   * @defaultValue 1.0\n   */\n  pitch?: number;\n\n  /**\n   * Pitch shift in semitones, combined with `pitch`.\n   * @defaultValue 0\n   */\n  pitchSemitones?: number;\n\n  /**\n   * Playback rate multiplier. Output length is scaled by `1 / playbackRate`.\n   * @defaultValue 1.0\n   */\n  playbackRate?: number;\n\n  /**\n   * Interpolation strategy to use in the rate transposer.\n   */\n  interpolationStrategy?: RateTransposerInterpolationStrategy;\n\n  /**\n   * WSOLA timing parameters to apply to the stretch stage.\n   */\n  stretchParameters?: StretchParameters;\n\n  /**\n   * Internal sample buffer strategy.\n   */\n  sampleBufferType?: SampleBufferType;\n}\n\n/**\n * Renders audio through SoundTouch processing in an `OfflineAudioContext`.\n *\n * @remarks\n * Creates an `OfflineAudioContext`, registers the processor module, builds the\n * audio graph, applies all transform parameters, and returns the rendered\n * `AudioBuffer`. The output length is estimated as\n * `ceil(input.length / playbackRate)` to account for tempo changes.\n *\n * @param options Processing options including the input buffer and processor URL.\n * @returns A Promise that resolves to the processed `AudioBuffer`.\n *\n * @example\n * ```ts\n * import { processOffline } from '@soundtouchjs/audio-worklet';\n *\n * const processed = await processOffline({\n *   input: audioBuffer,\n *   processorUrl: '/soundtouch-processor.js',\n *   pitchSemitones: -3,\n *   playbackRate: 1.2,\n * });\n * ```\n */\nexport async function processOffline(\n  options: ProcessOfflineOptions,\n): Promise<AudioBuffer> {\n  const {\n    input,\n    processorUrl,\n    pitch = 1.0,\n    pitchSemitones = 0,\n    playbackRate = 1.0,\n    interpolationStrategy,\n    stretchParameters,\n    sampleBufferType,\n  } = options;\n\n  const outputLength = Math.ceil(input.length / playbackRate);\n\n  const offlineCtx = new OfflineAudioContext(\n    input.numberOfChannels,\n    outputLength,\n    input.sampleRate,\n  );\n\n  await SoundTouchNode.register(offlineCtx, processorUrl);\n\n  const stNode = new SoundTouchNode({\n    context: offlineCtx,\n    interpolationStrategy,\n    sampleBufferType,\n  });\n\n  stNode.pitch.value = pitch;\n  stNode.pitchSemitones.value = pitchSemitones;\n  stNode.playbackRate.value = playbackRate;\n\n  if (stretchParameters) {\n    stNode.setStretchParameters(stretchParameters);\n  }\n\n  stNode.connect(offlineCtx.destination);\n\n  const source = offlineCtx.createBufferSource();\n  source.buffer = input;\n  source.playbackRate.value = playbackRate;\n  source.connect(stNode);\n  source.start(0);\n\n  return offlineCtx.startRendering();\n}\n"
    },
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/src/processor.ts",
      "content": "/*\n * SoundTouch JS audio processing library\n * Copyright (c) Steve 'Cutter' Blades\n *\n * Licensed under the Mozilla Public License, v. 2.0.\n * You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\nimport {\n  SoundTouchProcessorBase,\n  STANDARD_PARAMETER_DESCRIPTORS,\n} from '@soundtouchjs/worklet-base';\nimport { DEFAULT_SAMPLE_BUFFER_TYPE } from './constants.js';\nimport type { ProcessCoreResult } from '@soundtouchjs/worklet-base';\nimport type {\n  RateTransposerInterpolationStrategy,\n  SampleBufferType,\n} from '@soundtouchjs/core';\n\nconst PROCESSOR_NAME = 'soundtouch-processor';\n\n/**\n * Constructor options passed by `SoundTouchNode` on initialization.\n *\n * @remarks\n * Used to configure the processor's internal buffer strategy and interpolation strategy.\n */\ninterface ProcessorConstructorOptions {\n  processorOptions?: {\n    /** Preferred internal buffer strategy for the SoundTouch pipeline. */\n    sampleBufferType?: SampleBufferType;\n    /** Interpolation strategy for rate transposition. */\n    interpolationStrategy?: RateTransposerInterpolationStrategy;\n  };\n}\n\n/** Metrics snapshot sent from the processor to the main thread every 100 render blocks. */\ninterface MetricsMessage {\n  type: 'metrics';\n  /** Frames available in the output buffer at the time of the last render block. */\n  framesBuffered: number;\n  /** Cumulative count of render blocks where the output buffer had fewer frames than requested. */\n  underrunCount: number;\n  /** Total render blocks processed since the processor was created. */\n  blockCount: number;\n  /** RMS of output block (last 128 frames, both channels averaged) */\n  outputRms: number;\n  /** Peak of output block (last 128 frames, both channels) */\n  outputPeak: number;\n}\n\n/**\n * Audio render-thread processor that applies SoundTouch transformations to stereo blocks.\n *\n * @remarks\n * Receives audio from the main thread, applies pitch, tempo, and rate transformations,\n * and outputs processed stereo audio. Handles runtime strategy switching via messages.\n */\nclass SoundTouchProcessor extends SoundTouchProcessorBase {\n  /** Static AudioParam metadata consumed by the browser. */\n  static get parameterDescriptors() {\n    return STANDARD_PARAMETER_DESCRIPTORS;\n  }\n\n  /**\n   * @param options Worklet constructor options provided by the main thread.\n   *\n   * @remarks\n   * Unknown interpolation strategy ids are logged and coerced to `lanczos`\n   * so render-thread startup remains resilient.\n   */\n  constructor(options?: ProcessorConstructorOptions) {\n    super('[SoundTouchProcessor]', {\n      sampleRate,\n      sampleBufferType:\n        options?.processorOptions?.sampleBufferType ??\n        DEFAULT_SAMPLE_BUFFER_TYPE,\n      interpolationStrategy: SoundTouchProcessorBase.resolveStrategy(\n        options?.processorOptions?.interpolationStrategy,\n        '[SoundTouchProcessor]',\n      ),\n    });\n  }\n\n  protected onProcessComplete(result: ProcessCoreResult): void {\n    if (this._blockCount % 100 === 0) {\n      this.port.postMessage({\n        type: 'metrics',\n        framesBuffered: result.available,\n        underrunCount: this._underrunCount,\n        blockCount: this._blockCount,\n        outputRms: result.outputRms,\n        outputPeak: result.outputPeak,\n      } satisfies MetricsMessage);\n    }\n  }\n}\n\nregisterProcessor(PROCESSOR_NAME, SoundTouchProcessor);\n"
    },
    {
      "name": "cutterbl-SoundTouchJS-8d19a91/packages/audio-worklet/package.json",
      "content": "{\n  \"name\": \"@soundtouchjs/audio-worklet\",\n  \"version\": \"2.1.0\",\n  \"description\": \"AudioWorklet implementation of the SoundTouchJS library\",\n  \"license\": \"MPL-2.0\",\n  \"licenseFile\": \"LICENSE\",\n  \"type\": \"module\",\n  \"sideEffects\": false,\n  \"exports\": {\n    \"./package.json\": \"./package.json\",\n    \".\": {\n      \"types\": \"./.dist/index.d.ts\",\n      \"default\": \"./.dist/index.js\"\n    },\n    \"./processor\": \"./.dist/soundtouch-processor.js\"\n  },\n  \"main\": \"./.dist/index.js\",\n  \"types\": \"./.dist/index.d.ts\",\n  \"files\": [\n    \".dist\",\n    \"docs\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"keywords\": [\n    \"audio\",\n    \"WebAudio\",\n    \"AudioWorklet\",\n    \"TypeScript\",\n    \"pitch\",\n    \"tempo\",\n    \"soundtouch\"\n  ],\n  \"author\": \"Steve 'Cutter' Blades\",\n  \"contributors\": [\n    \"Steve 'Cutter' Blades <web.admin@cutterscrossing.com> (https://cutterscrossing.com/)\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/cutterbl/SoundTouchJS.git\",\n    \"directory\": \"packages/audio-worklet\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/cutterbl/SoundTouchJS/issues\"\n  },\n  \"homepage\": \"https://github.com/cutterbl/SoundTouchJS\",\n  \"dependencies\": {\n    \"@soundtouchjs/interpolation-strategy-lanczos\": \"workspace:*\",\n    \"@soundtouchjs/core\": \"workspace:*\",\n    \"@soundtouchjs/worklet-base\": \"workspace:*\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
    }
  ]
}