{"version":3,"sources":["../src/audio_resampler.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n  FlushSoxResamplerResponse,\n  NewSoxResamplerResponse,\n  PushSoxResamplerResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n  FlushSoxResamplerRequest,\n  NewSoxResamplerRequest,\n  PushSoxResamplerRequest,\n  SoxQualityRecipe,\n  SoxResamplerDataType,\n} from '@livekit/rtc-ffi-bindings';\nimport { AudioFrame } from './audio_frame.js';\nimport { FfiClient, FfiHandle } from './ffi_client.js';\n\n/**\n * Resampler quality. Higher quality settings result in better audio quality but require more\n * processing power.\n */\nexport enum AudioResamplerQuality {\n  QUICK = SoxQualityRecipe.SOXR_QUALITY_QUICK,\n  LOW = SoxQualityRecipe.SOXR_QUALITY_LOW,\n  MEDIUM = SoxQualityRecipe.SOXR_QUALITY_MEDIUM,\n  HIGH = SoxQualityRecipe.SOXR_QUALITY_HIGH,\n  VERY_HIGH = SoxQualityRecipe.SOXR_QUALITY_VERYHIGH,\n}\n\n/**\n * AudioResampler provides functionality to resample audio data from an input sample rate to\n * an output sample rate using the Sox resampling library. It supports multiple channels and\n * configurable resampling quality.\n */\nexport class AudioResampler {\n  #inputRate: number;\n  #outputRate: number;\n  #channels: number;\n  #ffiHandle: FfiHandle;\n\n  /**\n   * Initializes a new AudioResampler.\n   *\n   * @param inputRate - The sample rate of the input audio data (in Hz).\n   * @param outputRate - The desired sample rate of the output audio data (in Hz).\n   * @param channels - The number of audio channels (e.g., 1 for mono, 2 for stereo). Defaults to 1.\n   * @param quality - The quality setting for the resampler. Defaults to\n   * `AudioResamplerQuality.MEDIUM`.\n   */\n  constructor(\n    inputRate: number,\n    outputRate: number,\n    channels = 1,\n    quality = AudioResamplerQuality.MEDIUM,\n  ) {\n    this.#inputRate = inputRate;\n    this.#outputRate = outputRate;\n    this.#channels = channels;\n\n    const req = new NewSoxResamplerRequest({\n      inputRate,\n      outputRate,\n      numChannels: channels,\n      qualityRecipe: quality as number as SoxQualityRecipe,\n      inputDataType: SoxResamplerDataType.SOXR_DATATYPE_INT16I,\n      outputDataType: SoxResamplerDataType.SOXR_DATATYPE_INT16I,\n      flags: 0,\n    });\n\n    const res = FfiClient.instance.request<NewSoxResamplerResponse>({\n      message: {\n        case: 'newSoxResampler',\n        value: req,\n      },\n    });\n\n    switch (res.message.case) {\n      case 'resampler':\n        this.#ffiHandle = new FfiHandle(res.message.value.handle!.id!);\n        break;\n      case 'error':\n      default:\n        throw new Error(res.message.value);\n    }\n  }\n\n  get inputRate(): number {\n    return this.#inputRate;\n  }\n\n  get outputRate(): number {\n    return this.#outputRate;\n  }\n\n  get channels(): number {\n    return this.#channels;\n  }\n\n  /**\n   * Releases the underlying native resampler handle. Must be called when\n   * the resampler is no longer needed to avoid leaking the FD.\n   */\n  close() {\n    this.#ffiHandle.dispose();\n  }\n\n  /**\n   * Push audio data into the resampler and retrieve any available resampled data.\n   *\n   * This method accepts audio data, resamples it according to the configured input and output rates,\n   * and returns any resampled data that is available after processing the input.\n   *\n   * @param data - The audio frame to resample\n   *\n   * @returns A list of {@link AudioFrame} objects containing the resampled audio data. The list may\n   * be empty if no output data is available yet.\n   */\n  push(data: AudioFrame): AudioFrame[] {\n    const req = new PushSoxResamplerRequest({\n      resamplerHandle: this.#ffiHandle.handle,\n      dataPtr: data.protoInfo().dataPtr,\n      size: data.data.byteLength,\n    });\n\n    const res = FfiClient.instance.request<PushSoxResamplerResponse>({\n      message: {\n        case: 'pushSoxResampler',\n        value: req,\n      },\n    });\n\n    if (res.error) {\n      throw new Error(res.error);\n    }\n\n    if (!res.outputPtr) {\n      return [];\n    }\n\n    const outputData = FfiClient.instance.copyBuffer(res.outputPtr, res.size!);\n\n    return [\n      new AudioFrame(\n        new Int16Array(outputData.buffer),\n        this.#outputRate,\n        this.#channels,\n        Math.trunc(outputData.length / this.#channels / 2),\n        data.userdata,\n      ),\n    ];\n  }\n\n  /**\n   * Flush any remaining audio data through the resampler and retrieve the resampled data.\n   *\n   * @remarks\n   * This method should be called when no more input data will be provided to ensure that all\n   * internal buffers are processed and all resampled data is output.\n   */\n  flush(): AudioFrame[] {\n    const req = new FlushSoxResamplerRequest({\n      resamplerHandle: this.#ffiHandle.handle,\n    });\n\n    const res = FfiClient.instance.request<FlushSoxResamplerResponse>({\n      message: {\n        case: 'flushSoxResampler',\n        value: req,\n      },\n    });\n\n    if (res.error) {\n      throw new Error(res.error);\n    }\n\n    if (!res.outputPtr) {\n      return [];\n    }\n\n    const outputData = FfiClient.instance.copyBuffer(res.outputPtr, res.size!);\n    return [\n      new AudioFrame(\n        new Int16Array(outputData.buffer),\n        this.#outputRate,\n        this.#channels,\n        Math.trunc(outputData.length / this.#channels / 2),\n      ),\n    ];\n  }\n}\n"],"mappings":"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","names":["AudioResamplerQuality"]}