{"version":3,"sources":["../src/audio_mixer.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { AsyncQueue } from './async_queue.js';\nimport { AudioFrame } from './audio_frame.js';\nimport { log } from './log.js';\n\n// Re-export AsyncQueue for backward compatibility\nexport { AsyncQueue } from './async_queue.js';\n\n// Define types for async iteration (since lib: es2015 doesn't include them)\ntype AudioStream = {\n  [Symbol.asyncIterator](): {\n    next(): Promise<IteratorResult<AudioFrame>>;\n  };\n};\n\ninterface Contribution {\n  stream: AudioStream;\n  data: Int16Array;\n  buffer: Int16Array;\n  hadData: boolean;\n  exhausted: boolean;\n}\n\nexport interface AudioMixerOptions {\n  /**\n   * The size of the audio block (in samples) for mixing.\n   * If not provided, defaults to sampleRate / 10 (100ms).\n   */\n  blocksize?: number;\n\n  /**\n   * The maximum wait time in milliseconds for each stream to provide\n   * audio data before timing out. Defaults to 100 ms.\n   */\n  streamTimeoutMs?: number;\n\n  /**\n   * The maximum number of mixed frames to store in the output queue.\n   * Defaults to 100.\n   */\n  capacity?: number;\n}\n\n/**\n * AudioMixer combines multiple async audio streams into a single output stream.\n *\n * The mixer accepts multiple async audio streams and mixes them into a single output stream.\n * Each output frame is generated with a fixed chunk size determined by the blocksize (in samples).\n * If blocksize is not provided (or 0), it defaults to 100ms.\n *\n * Each input stream is processed in parallel, accumulating audio data until at least one chunk\n * of samples is available. If an input stream does not provide data within the specified timeout,\n * a warning is logged. The mixer can be closed immediately\n * (dropping unconsumed frames) or allowed to flush remaining data using endInput().\n *\n * @example\n * ```typescript\n * const mixer = new AudioMixer(48000, 2);\n * mixer.addStream(stream1);\n * mixer.addStream(stream2);\n *\n * for await (const frame of mixer) {\n *   // Process mixed audio frame\n * }\n * ```\n */\nexport class AudioMixer {\n  private streams: Set<AudioStream>;\n  private buffers: Map<AudioStream, Int16Array>;\n  private streamIterators: Map<AudioStream, { next(): Promise<IteratorResult<AudioFrame>> }>;\n  private sampleRate: number;\n  private numChannels: number;\n  private chunkSize: number;\n  private streamTimeoutMs: number;\n  private queue: AsyncQueue<AudioFrame>;\n  private streamSignal: AsyncQueue<void>; // Signals when streams are added\n  private ending: boolean;\n  private mixerTask?: Promise<void>;\n  private closed: boolean;\n\n  /**\n   * Initialize the AudioMixer.\n   *\n   * @param sampleRate - The audio sample rate in Hz.\n   * @param numChannels - The number of audio channels.\n   * @param options - Optional configuration for the mixer.\n   */\n  constructor(sampleRate: number, numChannels: number, options: AudioMixerOptions = {}) {\n    this.streams = new Set();\n    this.buffers = new Map();\n    this.streamIterators = new Map();\n    this.sampleRate = sampleRate;\n    this.numChannels = numChannels;\n    this.chunkSize =\n      options.blocksize && options.blocksize > 0 ? options.blocksize : Math.floor(sampleRate / 10);\n    this.streamTimeoutMs = options.streamTimeoutMs ?? 100;\n    this.queue = new AsyncQueue<AudioFrame>(options.capacity ?? 100);\n    this.streamSignal = new AsyncQueue<void>(1); // there should only be one mixer\n    this.ending = false;\n    this.closed = false;\n\n    // Start the mixer task\n    this.mixerTask = this.mixer();\n  }\n\n  /**\n   * Add an audio stream to the mixer.\n   *\n   * The stream is added to the internal set of streams and an empty buffer is initialized for it,\n   * if not already present.\n   *\n   * @param stream - An async iterable that produces AudioFrame objects.\n   * @throws Error if the mixer has been closed.\n   */\n  addStream(stream: AudioStream): void {\n    if (this.ending) {\n      throw new Error('Cannot add stream after mixer has been closed');\n    }\n\n    this.streams.add(stream);\n    if (!this.buffers.has(stream)) {\n      this.buffers.set(stream, new Int16Array(0));\n    }\n\n    // Signal that a stream was added (non-blocking)\n    this.streamSignal.put(undefined).catch(() => {\n      // Ignore errors if signal queue is closed\n    });\n  }\n\n  /**\n   * Remove an audio stream from the mixer.\n   *\n   * This method removes the specified stream and its associated buffer from the mixer.\n   *\n   * @param stream - The audio stream to remove.\n   */\n  removeStream(stream: AudioStream): void {\n    this.streams.delete(stream);\n    this.buffers.delete(stream);\n    this.streamIterators.delete(stream);\n  }\n\n  /**\n   * Returns an async iterator for the mixed audio frames.\n   */\n  [Symbol.asyncIterator]() {\n    return {\n      next: async (): Promise<IteratorResult<AudioFrame>> => {\n        const frame = await this.getNextFrame();\n        if (frame === null) {\n          return { done: true, value: undefined };\n        }\n        return { done: false, value: frame };\n      },\n    };\n  }\n\n  /**\n   * Immediately stop mixing and close the mixer.\n   *\n   * This stops the mixing task, and any unconsumed output in the queue may be dropped.\n   */\n  async aclose(): Promise<void> {\n    if (this.closed) {\n      return;\n    }\n    this.closed = true;\n    this.ending = true;\n\n    // Close both queues to wake up any waiting operations\n    this.streamSignal.close();\n    this.queue.close();\n\n    await this.mixerTask;\n  }\n\n  /**\n   * Signal that no more streams will be added.\n   *\n   * This method marks the mixer as closed so that it flushes any remaining buffered output before ending.\n   * Note that existing streams will still be processed until exhausted.\n   */\n  endInput(): void {\n    this.ending = true;\n  }\n\n  private async getNextFrame(): Promise<AudioFrame | null> {\n    while (true) {\n      // Try to get an item from the queue (non-blocking)\n      const frame = this.queue.get();\n\n      if (frame !== undefined) {\n        return frame;\n      }\n\n      // Check if mixer is closed or ending\n      if (this.queue.closed || (this.ending && this.streams.size === 0)) {\n        return null;\n      }\n\n      // Queue is empty but mixer is still running - wait for an item to be added\n      await this.queue.waitForItem();\n    }\n  }\n\n  private async mixer(): Promise<void> {\n    // Main mixing loop that continuously processes streams and produces output frames\n    while (true) {\n      // If we're in ending mode and there are no more streams, exit\n      if (this.ending && this.streams.size === 0) {\n        break;\n      }\n\n      if (this.streams.size === 0) {\n        // Wait for a stream to be added (signal queue will have an item)\n        await this.streamSignal.waitForItem();\n        // Consume the signal\n        this.streamSignal.get();\n        continue;\n      }\n\n      // Process all streams in parallel\n      const streamArray = Array.from(this.streams);\n      const promises = streamArray.map((stream) => this.getContribution(stream));\n      const results = await Promise.all(\n        promises.map((p) =>\n          p\n            .then((value) => ({ status: 'fulfilled' as const, value }))\n            .catch((reason) => ({ status: 'rejected' as const, reason })),\n        ),\n      );\n\n      const contributions: Int16Array[] = [];\n      let anyData = false;\n      const removals: AudioStream[] = [];\n\n      for (const result of results) {\n        if (result.status !== 'fulfilled') {\n          log.warn({ reason: result.reason }, 'AudioMixer: Stream contribution failed');\n          continue;\n        }\n\n        const contrib = result.value;\n        contributions.push(contrib.data);\n        this.buffers.set(contrib.stream, contrib.buffer);\n\n        if (contrib.hadData) {\n          anyData = true;\n        }\n\n        // Mark exhausted streams with no remaining buffer for removal\n        if (contrib.exhausted && contrib.buffer.length === 0) {\n          removals.push(contrib.stream);\n        }\n      }\n\n      // Remove exhausted streams\n      for (const stream of removals) {\n        this.removeStream(stream);\n      }\n\n      // If all streams are exhausted, end the mixer automatically.\n      // This keeps `for await...of` consumers from hanging indefinitely when inputs complete.\n      if (!this.ending && removals.length > 0 && this.streams.size === 0) {\n        this.ending = true;\n      }\n\n      if (!anyData) {\n        // No data available from any stream, wait briefly before trying again\n        await this.sleep(1);\n        continue;\n      }\n\n      // Mix the audio data\n      const mixed = this.mixAudio(contributions);\n      const frame = new AudioFrame(mixed, this.sampleRate, this.numChannels, this.chunkSize);\n\n      if (this.closed) {\n        break;\n      }\n\n      try {\n        // Add mixed frame to output queue\n        await this.queue.put(frame);\n      } catch {\n        // Queue closed while trying to add frame\n        break;\n      }\n    }\n\n    // Close the queue to signal end of stream\n    this.queue.close();\n  }\n\n  private async getContribution(stream: AudioStream): Promise<Contribution> {\n    let buf = this.buffers.get(stream) ?? new Int16Array(0);\n    const initialBufferLength = buf.length;\n    let exhausted = false;\n    let receivedDataInThisCall = false;\n\n    // Get or create iterator for this stream\n    let iterator = this.streamIterators.get(stream);\n    if (!iterator) {\n      iterator = stream[Symbol.asyncIterator]();\n      this.streamIterators.set(stream, iterator);\n    }\n\n    // Accumulate data until we have at least chunkSize samples\n    while (buf.length < this.chunkSize * this.numChannels && !exhausted && !this.closed) {\n      try {\n        const result = await Promise.race([iterator.next(), this.timeout(this.streamTimeoutMs)]);\n\n        if (result === 'timeout') {\n          console.warn(`AudioMixer: stream timeout after ${this.streamTimeoutMs}ms`);\n          break;\n        }\n\n        if (result.done) {\n          exhausted = true;\n          break;\n        }\n\n        const frame = result.value;\n        const newData = frame.data;\n\n        // Mark that we received data in this call\n        receivedDataInThisCall = true;\n\n        // Concatenate buffers\n        if (buf.length === 0) {\n          buf = newData;\n        } else {\n          const combined = new Int16Array(buf.length + newData.length);\n          combined.set(buf);\n          combined.set(newData, buf.length);\n          buf = combined;\n        }\n      } catch (error) {\n        console.error(`AudioMixer: Error reading from stream:`, error);\n        exhausted = true;\n        break;\n      }\n    }\n\n    // Extract contribution and update buffer\n    let contrib: Int16Array;\n    const samplesNeeded = this.chunkSize * this.numChannels;\n\n    if (buf.length >= samplesNeeded) {\n      // Extract the needed samples and keep the remainder in the buffer\n      contrib = buf.subarray(0, samplesNeeded);\n      buf = buf.subarray(samplesNeeded);\n    } else {\n      // Pad with zeros if we don't have enough data\n      const padded = new Int16Array(samplesNeeded);\n      padded.set(buf);\n      contrib = padded;\n      buf = new Int16Array(0);\n    }\n\n    // hadData means: we had data at start OR we received data during this call OR we have data remaining\n    const hadData = initialBufferLength > 0 || receivedDataInThisCall || buf.length > 0;\n\n    return {\n      stream,\n      data: contrib,\n      buffer: buf,\n      hadData,\n      exhausted,\n    };\n  }\n\n  private mixAudio(contributions: Int16Array[]): Int16Array {\n    if (contributions.length === 0) {\n      return new Int16Array(this.chunkSize * this.numChannels);\n    }\n\n    const length = this.chunkSize * this.numChannels;\n    // Use a wider accumulator to avoid int16 overflow while summing.\n    const acc = new Int32Array(length);\n\n    // Sum all contributions\n    for (const contrib of contributions) {\n      for (let i = 0; i < length; i++) {\n        const val = contrib[i];\n        if (val !== undefined) {\n          acc[i] = (acc[i] ?? 0) + val;\n        }\n      }\n    }\n\n    // Clip to Int16 range\n    const mixed = new Int16Array(length);\n    for (let i = 0; i < length; i++) {\n      const val = acc[i] ?? 0;\n      if (val > 32767) {\n        mixed[i] = 32767;\n      } else if (val < -32768) {\n        mixed[i] = -32768;\n      } else {\n        mixed[i] = val;\n      }\n    }\n\n    return mixed;\n  }\n\n  private sleep(ms: number): Promise<void> {\n    return new Promise((resolve) => setTimeout(resolve, ms));\n  }\n\n  private timeout(ms: number): Promise<'timeout'> {\n    return new Promise((resolve) => setTimeout(() => resolve('timeout'), ms));\n  }\n}\n"],"mappings":"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","names":["import_async_queue"]}