{"version":3,"sources":["jsdelivr-header.js","/npm/realtime-bpm-analyzer@5.0.7/src/index.ts","/npm/realtime-bpm-analyzer@5.0.7/src/core/consts.ts","/npm/realtime-bpm-analyzer@5.0.7/src/generated-processor.ts","/npm/realtime-bpm-analyzer@5.0.7/src/core/bpm-analyzer.ts","/npm/realtime-bpm-analyzer@5.0.7/src/core/utils.ts","/npm/realtime-bpm-analyzer@5.0.7/src/core/analyzer.ts","/npm/realtime-bpm-analyzer@5.0.7/src/core/realtime-bpm-analyzer.ts"],"names":[],"mappings":"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","file":"/npm/realtime-bpm-analyzer@5.0.7/dist/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/realtime-bpm-analyzer@5.0.7/dist/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","import {realtimeBpmProcessorName} from './core/consts';\nimport realtimeBpmProcessorContent from './generated-processor';\nimport {type RealTimeBpmAnalyzerParameters} from './core/types';\nimport {BpmAnalyzer} from './core/bpm-analyzer';\n\nexport * from './core/realtime-bpm-analyzer';\nexport {analyzeFullBuffer, getBiquadFilter} from './core/analyzer';\nexport * from './core/types';\nexport {BpmAnalyzer} from './core/bpm-analyzer';\n\n/**\n * Creates a real-time BPM analyzer for live audio streams.\n *\n * This function sets up a BPM analyzer that processes audio in real-time\n * to detect the beats-per-minute (tempo) of streaming audio sources like\n * microphones, audio elements, or live streams.\n *\n * @param audioContext - The Web Audio API AudioContext to use for processing\n * @param processorOptions - Optional configuration parameters for the analyzer\n * @returns A promise that resolves to a BpmAnalyzer instance (event emitter wrapping AudioWorkletNode)\n *\n * @remarks\n * The returned `BpmAnalyzer` is an event emitter that wraps an AudioWorkletNode.\n * - Use the analyzer directly for event listening: `analyzer.on('bpm', ...)`\n * - Use `.node` property for Web Audio API connections: `source.connect(analyzer.node)`\n *\n * @example\n * **Basic Usage with Audio Element**\n * ```typescript\n * const audioContext = new AudioContext();\n * const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext);\n *\n * // Connect audio graph - use .node for AudioWorkletNode connections\n * const audioElement = document.querySelector('audio');\n * const source = audioContext.createMediaElementSource(audioElement);\n * source.connect(bpmAnalyzer.node);\n * bpmAnalyzer.node.connect(audioContext.destination);\n *\n * // Listen for BPM events with full type safety\n * bpmAnalyzer.on('bpm', (data) => {\n *   console.log('Current BPM:', data.bpm[0].tempo);\n *   console.log('Confidence:', data.bpm[0].count);\n * });\n *\n * bpmAnalyzer.on('bpmStable', (data) => {\n *   console.log('Stable BPM detected:', data.bpm[0].tempo);\n * });\n * ```\n *\n * @example\n * **With Custom Configuration**\n * ```typescript\n * const audioContext = new AudioContext();\n * const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext, {\n *   continuousAnalysis: true,    // Keep analyzing after stable BPM found\n *   stabilizationTime: 20000,    // Time in ms to consider BPM stable\n *   muteTimeInIndexes: 10000,    // Audio indexes to skip between peaks\n *   debug: false                 // Enable debug logging\n * });\n * ```\n *\n * @example\n * **With Microphone**\n * ```typescript\n * const audioContext = new AudioContext();\n * const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext);\n *\n * // Connect microphone - use .node for audio connections\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * const source = audioContext.createMediaStreamSource(stream);\n * source.connect(bpmAnalyzer.node);\n * // Note: Don't connect to destination to avoid feedback\n *\n * // Handle different event types\n * bpmAnalyzer.on('bpm', (data) => {\n *   console.log('Analyzing:', data.bpm[0].tempo);\n * });\n *\n * bpmAnalyzer.on('bpmStable', (data) => {\n *   console.log('Stable BPM:', data.bpm[0].tempo);\n *   console.log('Confidence:', data.bpm[0].count);\n * });\n *\n * // One-time listener\n * bpmAnalyzer.once('bpmStable', (data) => {\n *   console.log('First stable detection!');\n * });\n * ```\n *\n * @example\n * **Controlling the Analyzer**\n * ```typescript\n * // Reset the analyzer state\n * bpmAnalyzer.reset();\n *\n * // Listen for reset events\n * bpmAnalyzer.on('analyzerReset', () => {\n *   console.log('Analyzer was reset');\n * });\n *\n * // Stop the analyzer\n * bpmAnalyzer.stop();\n *\n * // Remove listeners with native API\n * const handler = (event) => console.log(event.detail);\n * bpmAnalyzer.addEventListener('bpm', handler);\n * bpmAnalyzer.removeEventListener('bpm', handler);\n *\n * // Or use once() for automatic cleanup\n * bpmAnalyzer.once('bpmStable', (data) => console.log('First detection'));\n *\n * // Disconnect and cleanup\n * bpmAnalyzer.disconnect();\n * ```\n *\n * @remarks\n * - The analyzer uses AudioWorklet technology for efficient real-time processing\n * - Returns a BpmAnalyzer (event emitter) that wraps an AudioWorkletNode\n * - Use `.node` property to connect in Web Audio API graphs: `source.connect(analyzer.node)`\n * - Events are emitted via typed EventEmitter with full autocomplete support\n * - Event types: 'bpm' (continuous), 'bpmStable' (when stabilized), 'analyzerReset'\n * - For offline/synchronous analysis of audio files, use {@link analyzeFullBuffer} instead\n *\n * @see {@link BpmAnalyzer} for the analyzer class API\n * @see {@link analyzeFullBuffer} for offline audio file analysis\n * @see {@link RealTimeBpmAnalyzerParameters} for configuration options\n * @see {@link BpmCandidates} for the structure of BPM results\n *\n * @group Functions\n */\nexport async function createRealtimeBpmAnalyzer(audioContext: AudioContext, processorOptions?: RealTimeBpmAnalyzerParameters): Promise<BpmAnalyzer> {\n  const processorNode = await setupAudioWorkletNode(audioContext, realtimeBpmProcessorName, processorOptions);\n  await audioContext.resume();\n  return new BpmAnalyzer(processorNode);\n}\n\n/**\n * @deprecated Use {@link createRealtimeBpmAnalyzer} instead, will be removed in next major release\n * @hidden\n */\nexport const createRealTimeBpmProcessor = createRealtimeBpmAnalyzer;\n\n/**\n * Creates AudioWorkletNode for the Processor\n * @param audioContext AudioContext instance\n * @param processorName Name of the audio processor, without the extension\n * @param processorOptions RealTimeBpmAnalyzerParameters\n * @returns Recording node related components for the app.\n */\nasync function setupAudioWorkletNode(audioContext: AudioContext, processorName: string, processorOptions?: RealTimeBpmAnalyzerParameters): Promise<AudioWorkletNode> {\n  const blob = new Blob([realtimeBpmProcessorContent], {type: 'application/javascript'});\n\n  const objectUrl = URL.createObjectURL(blob);\n\n  try {\n    await audioContext.audioWorklet.addModule(objectUrl);\n\n    const audioWorkletNode = new AudioWorkletNode(audioContext, processorName, {\n      processorOptions,\n    });\n\n    return audioWorkletNode;\n  } finally {\n    // Clean up the blob URL to prevent memory leaks\n    URL.revokeObjectURL(objectUrl);\n  }\n}\n","export const realtimeBpmProcessorName = 'realtime-bpm-processor' as const;\nexport const startThreshold = 0.95;\nexport const minValidThreshold = 0.2;\nexport const minPeaks = 15;\nexport const thresholdStep = 0.05;\nexport const frequencyValue = 200;\nexport const qualityValue = 1;\n\n/**\n * Minimum BPM value for tempo normalization\n * @remarks\n * Due to amplitude threshold limitations in the detection algorithm,\n * tempos below this value are doubled to fit within the detectable range.\n */\nexport const minBpmRange = 90;\n\n/**\n * Maximum BPM value for tempo normalization\n * @remarks\n * Due to amplitude threshold limitations in the detection algorithm,\n * tempos above this value are halved to fit within the detectable range.\n */\nexport const maxBpmRange = 180;\n\n/**\n * Duration in seconds to skip after detecting a peak\n * @remarks\n * This prevents detecting the same peak multiple times during its decay phase.\n * 0.25 seconds is approximately the minimum time between distinct beats at high tempos.\n */\nexport const peakSkipDuration = 0.25;\n\n/**\n * Maximum number of interval comparisons per peak\n * @remarks\n * When analyzing intervals between peaks, we compare each peak with the next N peaks.\n * This limits computational complexity while capturing sufficient tempo information.\n */\nexport const maxIntervalComparisons = 10;\n\n/**\n * Default buffer size for chunk aggregation\n * @remarks\n * Audio data is aggregated into chunks of this size before analysis.\n * Larger values provide more context but increase latency.\n */\nexport const defaultBufferSize = 4096;\n","export default `\"use strict\";\n(() => {\n  // src/core/consts.ts\n  var realtimeBpmProcessorName = \"realtime-bpm-processor\";\n  var startThreshold = 0.95;\n  var minValidThreshold = 0.2;\n  var minPeaks = 15;\n  var thresholdStep = 0.05;\n  var minBpmRange = 90;\n  var maxBpmRange = 180;\n  var peakSkipDuration = 0.25;\n  var maxIntervalComparisons = 10;\n  var defaultBufferSize = 4096;\n\n  // src/core/utils.ts\n  async function descendingOverThresholds(onThreshold, minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {\n    let threshold = startThreshold2;\n    do {\n      threshold -= thresholdStep2;\n      const shouldExit = await onThreshold(threshold);\n      if (shouldExit) {\n        break;\n      }\n    } while (threshold > minValidThreshold2);\n  }\n  function generateThresholdMap(initialValueFactory, minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {\n    const object = {};\n    let threshold = startThreshold2;\n    do {\n      threshold -= thresholdStep2;\n      object[threshold.toString()] = initialValueFactory();\n    } while (threshold > minValidThreshold2);\n    return object;\n  }\n  function generateValidPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {\n    return generateThresholdMap(() => [], minValidThreshold2, startThreshold2, thresholdStep2);\n  }\n  function generateNextIndexPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {\n    return generateThresholdMap(() => 0, minValidThreshold2, startThreshold2, thresholdStep2);\n  }\n  function chunkAggregator(bufferSize = defaultBufferSize) {\n    let _bytesWritten = 0;\n    let buffer = new Float32Array(0);\n    function initBuffer() {\n      _bytesWritten = 0;\n      buffer = new Float32Array(0);\n    }\n    function isBufferFull() {\n      return _bytesWritten === bufferSize;\n    }\n    function flush() {\n      initBuffer();\n    }\n    return function(pcmData) {\n      if (isBufferFull()) {\n        flush();\n      }\n      const newBuffer = new Float32Array(buffer.length + pcmData.length);\n      newBuffer.set(buffer, 0);\n      newBuffer.set(pcmData, buffer.length);\n      buffer = newBuffer;\n      _bytesWritten += pcmData.length;\n      return {\n        isBufferFull: isBufferFull(),\n        buffer,\n        bufferSize\n      };\n    };\n  }\n  function computeIndexesToSkip(durationSeconds, sampleRate2) {\n    return Math.round(durationSeconds * sampleRate2);\n  }\n\n  // src/core/analyzer.ts\n  function findPeaksAtThreshold({\n    audioSampleRate,\n    data,\n    threshold,\n    offset = 0\n  }) {\n    if (threshold < 0 || threshold > 1) {\n      throw new Error(\"Invalid threshold: \" + threshold + \". Threshold must be between 0 and 1.\");\n    }\n    if (audioSampleRate <= 0) {\n      throw new Error(\"Invalid sample rate: \" + audioSampleRate + \". Sample rate must be positive.\");\n    }\n    const peaks = [];\n    const skipForwardIndexes = computeIndexesToSkip(peakSkipDuration, audioSampleRate);\n    const { length } = data;\n    for (let i = offset; i < length; i += 1) {\n      if (data[i] > threshold) {\n        peaks.push(i);\n        i += skipForwardIndexes;\n      }\n    }\n    return {\n      peaks,\n      threshold\n    };\n  }\n  async function computeBpm({\n    audioSampleRate,\n    data\n  }) {\n    const minPeaks2 = minPeaks;\n    let hasPeaks = false;\n    let foundThreshold = minValidThreshold;\n    await descendingOverThresholds(async (threshold) => {\n      if (hasPeaks) {\n        return true;\n      }\n      if (data[threshold] && data[threshold].length > minPeaks2) {\n        hasPeaks = true;\n        foundThreshold = threshold;\n      }\n      return false;\n    });\n    if (hasPeaks && foundThreshold) {\n      const intervals = identifyIntervals(data[foundThreshold]);\n      const tempos = groupByTempo({ audioSampleRate, intervalCounts: intervals });\n      const candidates = getTopCandidates(tempos);\n      const bpmCandidates = {\n        bpm: candidates,\n        threshold: foundThreshold\n      };\n      return bpmCandidates;\n    }\n    return {\n      bpm: [],\n      threshold: foundThreshold\n    };\n  }\n  function getTopCandidates(candidates, length = 5) {\n    return candidates.sort((a, b) => b.count - a.count).slice(0, length);\n  }\n  function identifyIntervals(peaks) {\n    const intervals = [];\n    for (let n = 0; n < peaks.length; n++) {\n      for (let i = 0; i < maxIntervalComparisons; i++) {\n        const peak = peaks[n];\n        const peakIndex = n + i;\n        const interval = peaks[peakIndex] - peak;\n        let foundInterval = intervals.find((intervalCount) => intervalCount.interval === interval);\n        if (foundInterval) {\n          const index = intervals.indexOf(foundInterval);\n          intervals[index] = {\n            interval: foundInterval.interval,\n            count: foundInterval.count + 1\n          };\n          foundInterval = intervals[index];\n        }\n        if (!foundInterval) {\n          const item = {\n            interval,\n            count: 1\n          };\n          intervals.push(item);\n        }\n      }\n    }\n    return intervals;\n  }\n  function groupByTempo({\n    audioSampleRate,\n    intervalCounts\n  }) {\n    const tempoCounts = [];\n    for (const intervalCount of intervalCounts) {\n      if (intervalCount.interval === 0) {\n        continue;\n      }\n      const absoluteInterval = Math.abs(intervalCount.interval);\n      let theoreticalTempo = 60 / (absoluteInterval / audioSampleRate);\n      while (theoreticalTempo < minBpmRange) {\n        theoreticalTempo *= 2;\n      }\n      while (theoreticalTempo > maxBpmRange) {\n        theoreticalTempo /= 2;\n      }\n      theoreticalTempo = Math.round(theoreticalTempo);\n      let foundTempo = tempoCounts.find((tempoCount) => tempoCount.tempo === theoreticalTempo);\n      if (foundTempo) {\n        const index = tempoCounts.indexOf(foundTempo);\n        tempoCounts[index] = {\n          tempo: foundTempo.tempo,\n          count: foundTempo.count + intervalCount.count,\n          confidence: foundTempo.confidence\n        };\n        foundTempo = tempoCounts[index];\n      }\n      if (!foundTempo) {\n        const tempo = {\n          tempo: theoreticalTempo,\n          count: intervalCount.count,\n          confidence: 0\n        };\n        tempoCounts.push(tempo);\n      }\n    }\n    return tempoCounts;\n  }\n\n  // src/core/realtime-bpm-analyzer.ts\n  var initialValue = {\n    minValidThreshold: () => minValidThreshold,\n    validPeaks: () => generateValidPeaksModel(),\n    nextIndexPeaks: () => generateNextIndexPeaksModel(),\n    skipIndexes: () => 1,\n    effectiveBufferTime: () => 0\n  };\n  var RealTimeBpmAnalyzer = class {\n    constructor(options = {}) {\n      /**\n       * Default configuration\n       */\n      this.options = {\n        continuousAnalysis: false,\n        stabilizationTime: 2e4,\n        muteTimeInIndexes: 1e4,\n        debug: false\n      };\n      /**\n       * Minimum valid threshold, below this level result would be irrelevant.\n       */\n      this.minValidThreshold = initialValue.minValidThreshold();\n      /**\n       * Contain all valid peaks\n       */\n      this.validPeaks = initialValue.validPeaks();\n      /**\n       * Next index (+10000 ...) to take care about peaks\n       */\n      this.nextIndexPeaks = initialValue.nextIndexPeaks();\n      /**\n       * Number / Position of chunks\n       */\n      this.skipIndexes = initialValue.skipIndexes();\n      this.effectiveBufferTime = initialValue.effectiveBufferTime();\n      /**\n       * Computed values\n       */\n      this.computedStabilizationTimeInSeconds = 0;\n      Object.assign(this.options, options);\n      this.updateComputedValues();\n    }\n    /**\n     * Update the computed values\n     */\n    updateComputedValues() {\n      this.computedStabilizationTimeInSeconds = this.options.stabilizationTime / 1e3;\n    }\n    /**\n     * Reset BPM computation properties to get a fresh start\n     */\n    reset() {\n      this.minValidThreshold = initialValue.minValidThreshold();\n      this.validPeaks = initialValue.validPeaks();\n      this.nextIndexPeaks = initialValue.nextIndexPeaks();\n      this.skipIndexes = initialValue.skipIndexes();\n      this.effectiveBufferTime = initialValue.effectiveBufferTime();\n    }\n    /**\n     * Remve all validPeaks between the minThreshold pass in param to optimize the weight of datas\n     * @param minThreshold - Value between 0.9 and 0.2\n     */\n    async clearValidPeaks(minThreshold) {\n      this.minValidThreshold = minThreshold;\n      await descendingOverThresholds(async (threshold) => {\n        if (threshold < minThreshold && this.validPeaks[threshold] !== void 0) {\n          delete this.validPeaks[threshold];\n          delete this.nextIndexPeaks[threshold];\n        }\n        return false;\n      });\n    }\n    /**\n     * Attach this function to an audioprocess event on a audio/video node to compute BPM / Tempo in realtime\n     * @param options - RealtimeAnalyzeChunkOptions\n     * @param options.audioSampleRate - Audio sample rate (44100)\n     * @param options.channelData - Channel data\n     * @param options.bufferSize - Buffer size (4096)\n     * @param options.postMessage - Function to post a message to the processor node\n     */\n    async analyzeChunk({ audioSampleRate, channelData, bufferSize, postMessage }) {\n      if (this.options.debug) {\n        postMessage({ type: \"analyzeChunk\", data: channelData });\n      }\n      this.effectiveBufferTime += bufferSize;\n      const currentMaxIndex = bufferSize * this.skipIndexes;\n      const currentMinIndex = currentMaxIndex - bufferSize;\n      await this.findPeaks({\n        audioSampleRate,\n        channelData,\n        bufferSize,\n        currentMinIndex,\n        currentMaxIndex,\n        postMessage\n      });\n      this.skipIndexes++;\n      const data = await computeBpm({ audioSampleRate, data: this.validPeaks });\n      const { threshold } = data;\n      postMessage({ type: \"bpm\", data });\n      if (this.minValidThreshold < threshold) {\n        postMessage({ type: \"bpmStable\", data });\n        await this.clearValidPeaks(threshold);\n      }\n      if (this.options.continuousAnalysis && this.effectiveBufferTime / audioSampleRate > this.computedStabilizationTimeInSeconds) {\n        this.reset();\n        postMessage({ type: \"analyzerReset\" });\n      }\n    }\n    /**\n     * Find the best threshold with enought peaks\n     * @param options - Options for finding peaks\n     * @param options.audioSampleRate - Sample rate\n     * @param options.channelData - Channel data\n     * @param options.bufferSize - Buffer size\n     * @param options.currentMinIndex - Current minimum index\n     * @param options.currentMaxIndex - Current maximum index\n     * @param options.postMessage - Function to post a message to the processor node\n     */\n    async findPeaks({\n      audioSampleRate,\n      channelData,\n      bufferSize,\n      currentMinIndex,\n      currentMaxIndex,\n      postMessage\n    }) {\n      await descendingOverThresholds(async (threshold) => {\n        if (this.nextIndexPeaks[threshold] >= currentMaxIndex) {\n          return false;\n        }\n        const offsetForNextPeak = this.nextIndexPeaks[threshold] % bufferSize;\n        const { peaks, threshold: atThreshold } = findPeaksAtThreshold({ audioSampleRate, data: channelData, threshold, offset: offsetForNextPeak });\n        if (peaks.length === 0) {\n          return false;\n        }\n        for (const relativeChunkPeak of peaks) {\n          const index = currentMinIndex + relativeChunkPeak;\n          this.nextIndexPeaks[atThreshold] = index + this.options.muteTimeInIndexes;\n          this.validPeaks[atThreshold].push(index);\n          if (this.options.debug) {\n            postMessage({\n              type: \"validPeak\",\n              data: {\n                threshold: atThreshold,\n                index\n              }\n            });\n          }\n        }\n        return false;\n      }, this.minValidThreshold);\n    }\n  };\n\n  // src/processor/realtime-bpm-processor.ts\n  var RealTimeBpmProcessor = class extends AudioWorkletProcessor {\n    constructor(options) {\n      super(options);\n      this.stopped = false;\n      this.aggregate = chunkAggregator();\n      this.realTimeBpmAnalyzer = new RealTimeBpmAnalyzer(options.processorOptions);\n      this.port.addEventListener(\"message\", this.onMessage.bind(this));\n      this.port.start();\n    }\n    /**\n     * Handle message event\n     * @param _event Contain event data from main process\n     */\n    onMessage(_event) {\n    }\n    /**\n     * Process function to handle chunks of data\n     * @param inputs Inputs (the data we need to process)\n     * @param _outputs Outputs (not useful for now)\n     * @param _parameters Parameters\n     * @returns Process ended successfully\n     */\n    process(inputs, _outputs, _parameters) {\n      const currentChunk = inputs[0][0];\n      if (this.stopped) {\n        return true;\n      }\n      if (!currentChunk) {\n        return true;\n      }\n      const { isBufferFull, buffer, bufferSize } = this.aggregate(currentChunk);\n      if (isBufferFull) {\n        this.realTimeBpmAnalyzer.analyzeChunk({ audioSampleRate: sampleRate, channelData: buffer, bufferSize, postMessage: (event) => {\n          this.port.postMessage(event);\n        } }).catch((error) => {\n          this.port.postMessage({\n            type: \"error\",\n            data: {\n              message: error instanceof Error ? error.message : \"Unknown error during BPM analysis\",\n              error: error instanceof Error ? error : new Error(String(error))\n            }\n          });\n        });\n      }\n      return true;\n    }\n  };\n  registerProcessor(realtimeBpmProcessorName, RealTimeBpmProcessor);\n  var realtime_bpm_processor_default = {};\n})();\n//# sourceMappingURL=realtime-bpm-processor.js.map\n`;\n","import type {BpmAnalyzerEventMap, ProcessorOutputEvent, ProcessorInputEvent} from './types';\n\n/**\n * Custom event class for BPM analyzer events with typed data\n * @internal\n */\nclass BpmAnalyzerEvent<T> extends CustomEvent<T> {\n  constructor(type: string, data: T) {\n    super(type, {detail: data});\n  }\n}\n\n/**\n * Real-time BPM analyzer with typed event listeners.\n *\n * This class wraps an AudioWorkletNode and provides a clean, type-safe API\n * for analyzing beats per minute in real-time audio streams.\n *\n * @remarks\n * This class extends native EventTarget to provide typed event listeners\n * with full TypeScript autocomplete support. It implements the AudioNode\n * interface for seamless integration into Web Audio API graphs.\n *\n * @example\n * **Basic Usage**\n * ```typescript\n * const audioContext = new AudioContext();\n * const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext);\n *\n * // Listen for BPM events with full type safety\n * bpmAnalyzer.on('bpm', (data) => {\n *   console.log('Current BPM:', data.bpm[0].tempo);\n * });\n *\n * bpmAnalyzer.on('bpmStable', (data) => {\n *   console.log('Stable BPM:', data.bpm[0].tempo);\n *   console.log('Confidence:', data.bpm[0].count);\n * });\n *\n * // Connect to audio source - use .node for audio connections\n * const source = audioContext.createMediaElementSource(audioElement);\n * source.connect(bpmAnalyzer.node);\n * bpmAnalyzer.node.connect(audioContext.destination);\n * ```\n *\n * @example\n * **One-time Listeners**\n * ```typescript\n * analyzer.once('bpmStable', (data) => {\n *   console.log('First stable detection:', data.bpm[0].tempo);\n *   // This listener will be automatically removed after firing\n * });\n * ```\n *\n * @example\n * **Removing Listeners**\n * ```typescript\n * // Store handler reference for cleanup\n * const handleBPM = (event) => {\n *   console.log('BPM:', event.detail.bpm);\n * };\n *\n * // Add listener using native EventTarget API\n * analyzer.addEventListener('bpm', handleBPM);\n *\n * // Remove with same reference\n * analyzer.removeEventListener('bpm', handleBPM);\n * ```\n *\n * @example\n * **Controlling the Analyzer**\n * ```typescript\n * // Reset the analyzer state\n * analyzer.reset();\n *\n * // Stop analysis\n * analyzer.stop();\n *\n * // Disconnect from audio graph\n * analyzer.disconnect();\n * ```\n *\n * @group Classes\n */\nexport class BpmAnalyzer extends EventTarget {\n  /**\n   * The underlying AudioWorkletNode\n   * @remarks\n   * Exposed for advanced use cases. Most users should interact with the\n   * BpmAnalyzer API instead of directly accessing the worklet node.\n   */\n  readonly node: AudioWorkletNode;\n\n  /**\n   * Creates a new BpmAnalyzer instance\n   * @param workletNode - The AudioWorkletNode to wrap\n   */\n  constructor(workletNode: AudioWorkletNode) {\n    super();\n    this.node = workletNode;\n    this.setupMessageHandler();\n  }\n\n  /**\n   * Reset the analyzer state to start fresh analysis\n   *\n   * @remarks\n   * This clears all internal state including detected peaks and intervals,\n   * allowing the analyzer to start analyzing as if it were newly created.\n   *\n   * @example\n   * ```typescript\n   * // When switching to a different audio source\n   * audioElement.src = 'new-song.mp3';\n   * analyzer.reset();\n   * ```\n   */\n  reset(): void {\n    this.sendControlMessage({type: 'reset'});\n  }\n\n  /**\n   * Stop the analyzer\n   *\n   * @remarks\n   * This stops the analysis and resets the internal state. The analyzer\n   * will no longer emit events until analysis is restarted.\n   *\n   * @example\n   * ```typescript\n   * analyzer.stop();\n   * ```\n   */\n  stop(): void {\n    this.sendControlMessage({type: 'stop'});\n  }\n\n  /**\n   * Add an event listener for a specific event type\n   *\n   * @param event - The event name to listen for\n   * @param listener - The callback function to invoke when the event is emitted\n   *\n   * @example\n   * ```typescript\n   * analyzer.on('bpm', (data) => {\n   *   console.log('Current BPM:', data.bpm[0].tempo);\n   * });\n   * ```\n   */\n  on<K extends keyof BpmAnalyzerEventMap>(\n    event: K,\n    listener: (data: BpmAnalyzerEventMap[K]) => void,\n  ): void {\n    this.addEventListener(event, ((event_: BpmAnalyzerEvent<BpmAnalyzerEventMap[K]>) => {\n      listener(event_.detail);\n    }) as EventListener);\n  }\n\n  /**\n   * Add a one-time event listener that will be removed after being called once\n   *\n   * @param event - The event name to listen for\n   * @param listener - The callback function to invoke when the event is emitted\n   *\n   * @example\n   * ```typescript\n   * analyzer.once('bpmStable', (data) => {\n   *   console.log('First stable BPM detected:', data.bpm[0].tempo);\n   * });\n   * ```\n   */\n  once<K extends keyof BpmAnalyzerEventMap>(\n    event: K,\n    listener: (data: BpmAnalyzerEventMap[K]) => void,\n  ): void {\n    const onceWrapper = (event_: BpmAnalyzerEvent<BpmAnalyzerEventMap[K]>) => {\n      listener(event_.detail);\n      this.removeEventListener(event, onceWrapper as EventListener);\n    };\n\n    this.addEventListener(event, onceWrapper as EventListener);\n  }\n\n  /**\n   * Connect the analyzer to an audio destination\n   *\n   * @param destinationNode - The destination node to connect to\n   * @param outputIndex - The output index (default: 0)\n   * @param inputIndex - The input index on the destination (default: 0)\n   * @returns The destination node for chaining\n   *\n   * @example\n   * ```typescript\n   * analyzer.connect(audioContext.destination);\n   * ```\n   */\n  connect(destinationNode: AudioNode, outputIndex?: number, inputIndex?: number): AudioNode;\n  connect(destinationParameter: AudioParam, outputIndex?: number): void;\n  connect(destination: AudioNode | AudioParam, outputIndex = 0, inputIndex = 0): AudioNode | void {\n    if (destination instanceof AudioNode) {\n      return this.node.connect(destination, outputIndex, inputIndex);\n    }\n\n    this.node.connect(destination, outputIndex);\n  }\n\n  /**\n   * Disconnect the analyzer from all destinations or a specific destination\n   *\n   * @param destinationNode - Optional destination node to disconnect from\n   * @param outputIndex - The output index (default: 0)\n   * @param inputIndex - The input index on the destination (default: 0)\n   *\n   * @example\n   * ```typescript\n   * // Disconnect from all destinations\n   * analyzer.disconnect();\n   *\n   * // Disconnect from a specific destination\n   * analyzer.disconnect(audioContext.destination);\n   * ```\n   */\n  disconnect(destinationNode?: AudioNode, outputIndex?: number, inputIndex?: number): void;\n  disconnect(destinationParameter?: AudioParam, outputIndex?: number): void;\n  disconnect(destination?: AudioNode | AudioParam | number, output?: number, input?: number): void {\n    if (destination === undefined) {\n      this.node.disconnect();\n    } else if (typeof destination === 'number') {\n      this.node.disconnect(destination);\n    } else if (destination instanceof AudioNode) {\n      if (output !== undefined && input !== undefined) {\n        this.node.disconnect(destination, output, input);\n      } else if (output === undefined) {\n        this.node.disconnect(destination);\n      } else {\n        this.node.disconnect(destination, output);\n      }\n    } else if (destination instanceof AudioParam) {\n      if (output === undefined) {\n        this.node.disconnect(destination);\n      } else {\n        this.node.disconnect(destination, output);\n      }\n    }\n  }\n\n  /**\n   * Emit an event to all registered listeners\n   */\n  private emit<K extends keyof BpmAnalyzerEventMap>(event: K, data: BpmAnalyzerEventMap[K]): void {\n    this.dispatchEvent(new BpmAnalyzerEvent(event as string, data));\n  }\n\n  /**\n   * Set up the message handler to convert MessagePort events to typed events\n   */\n  private setupMessageHandler(): void {\n    // Using onmessage is the standard pattern for MessagePort\n    this.node.port.onmessage = (event: MessageEvent<ProcessorOutputEvent>) => {\n      const eventData = event.data;\n\n      // Map processor event types to emitter event names\n      // eslint-disable-next-line default-case -- all event types are handled\n      switch (eventData.type) {\n        case 'bpm': {\n          this.emit('bpm', eventData.data);\n          break;\n        }\n\n        case 'bpmStable': {\n          this.emit('bpmStable', eventData.data);\n          break;\n        }\n\n        case 'analyzerReset': {\n          this.emit('analyzerReset', undefined as void);\n          break;\n        }\n\n        case 'analyzeChunk': {\n          this.emit('analyzeChunk', eventData.data);\n          break;\n        }\n\n        case 'validPeak': {\n          this.emit('validPeak', eventData.data);\n          break;\n        }\n\n        case 'error': {\n          this.emit('error', eventData.data);\n          break;\n        }\n      }\n    };\n  }\n\n  /**\n   * Send a control message to the processor\n   */\n  private sendControlMessage(message: ProcessorInputEvent): void {\n    this.node.port.postMessage(message);\n  }\n\n  /**\n   * Get the audio context associated with this analyzer\n   */\n  get context(): BaseAudioContext {\n    return this.node.context;\n  }\n\n  /**\n   * Get the number of inputs\n   */\n  get numberOfInputs(): number {\n    return this.node.numberOfInputs;\n  }\n\n  /**\n   * Get the number of outputs\n   */\n  get numberOfOutputs(): number {\n    return this.node.numberOfOutputs;\n  }\n\n  /**\n   * Get the channel count\n   */\n  get channelCount(): number {\n    return this.node.channelCount;\n  }\n\n  set channelCount(value: number) {\n    this.node.channelCount = value;\n  }\n\n  /**\n   * Get the channel count mode\n   */\n  get channelCountMode(): ChannelCountMode {\n    return this.node.channelCountMode;\n  }\n\n  set channelCountMode(value: ChannelCountMode) {\n    this.node.channelCountMode = value;\n  }\n\n  /**\n   * Get the channel interpretation\n   */\n  get channelInterpretation(): ChannelInterpretation {\n    return this.node.channelInterpretation;\n  }\n\n  set channelInterpretation(value: ChannelInterpretation) {\n    this.node.channelInterpretation = value;\n  }\n}\n","import * as consts from './consts';\nimport type {Peaks, ValidPeaks, NextIndexPeaks, OnThresholdFunction, AggregateData} from './types';\n\n/**\n * Loop between .9 and minValidThreshold at .2 by default, passing the threshold to the function\n * @param onThreshold Function for each iteration, you must return a boolean, true will exit the loop process\n * @param minValidThreshold minValidThreshold usualy 0.2\n * @param startThreshold startThreshold usualy 0.9\n * @param thresholdStep thresholdStep usuably 0.05\n * @returns A promise that resolves nothing\n */\nexport async function descendingOverThresholds(onThreshold: OnThresholdFunction, minValidThreshold = consts.minValidThreshold, startThreshold = consts.startThreshold, thresholdStep = consts.thresholdStep): Promise<void> {\n  let threshold = startThreshold;\n\n  do {\n    threshold -= thresholdStep;\n    const shouldExit = await onThreshold(threshold);\n    if (shouldExit) {\n      break;\n    }\n  } while (threshold > minValidThreshold);\n}\n\n/**\n * Generic helper to generate a threshold-indexed map\n * @param initialValueFactory Function that creates the initial value for each threshold key\n * @param minValidThreshold Minimum threshold (default: 0.2)\n * @param startThreshold Starting threshold (default: 0.95)\n * @param thresholdStep Step size between thresholds (default: 0.05)\n * @returns Object with threshold strings as keys and initialValue as values\n */\nfunction generateThresholdMap<T>(initialValueFactory: () => T, minValidThreshold = consts.minValidThreshold, startThreshold = consts.startThreshold, thresholdStep = consts.thresholdStep): Record<string, T> {\n  const object: Record<string, T> = {};\n  let threshold = startThreshold;\n\n  do {\n    threshold -= thresholdStep;\n    // Use toFixed to handle floating point precision issues (e.g., 0.7499999 -> \"0.75\")\n    object[threshold.toString()] = initialValueFactory();\n  } while (threshold > minValidThreshold);\n\n  return object;\n}\n\n/**\n * Generate an object with keys as thresholds and will containes validPeaks\n * @param minValidThreshold minValidThreshold usualy 0.2\n * @param startThreshold startThreshold usualy 0.9\n * @param thresholdStep thresholdStep usuably 0.05\n * @returns Collection of validPeaks by thresholds\n */\nexport function generateValidPeaksModel(minValidThreshold = consts.minValidThreshold, startThreshold = consts.startThreshold, thresholdStep = consts.thresholdStep): ValidPeaks {\n  return generateThresholdMap<Peaks>(() => [], minValidThreshold, startThreshold, thresholdStep);\n}\n\n/**\n * Generate an object with keys as thresholds and will containes NextIndexPeaks\n * @param minValidThreshold Minimum threshold to reach (we're descending from the startThreshold)\n * @param startThreshold Starting threshold\n * @param thresholdStep Usually 0.05\n * @returns Collection of NextIndexPeaks by thresholds\n */\nexport function generateNextIndexPeaksModel(minValidThreshold = consts.minValidThreshold, startThreshold = consts.startThreshold, thresholdStep = consts.thresholdStep): NextIndexPeaks {\n  return generateThresholdMap<number>(() => 0, minValidThreshold, startThreshold, thresholdStep);\n}\n\n/**\n * Creates a function that aggregates incoming PCM data into chunks.\n * @param bufferSize - Size of the buffer to aggregate (default: 4096)\n * @returns A function that accepts PCM data and aggregates it into chunks.\n */\nexport function chunkAggregator(bufferSize = consts.defaultBufferSize): (pcmData: Float32Array) => AggregateData {\n  /**\n   * Track the current buffer fill level.\n   */\n  let _bytesWritten = 0;\n\n  /**\n   * Create a buffer of fixed size.\n   */\n  let buffer: Float32Array = new Float32Array(0);\n\n  /**\n   * Initialize the buffer.\n   */\n  function initBuffer(): void {\n    _bytesWritten = 0;\n    buffer = new Float32Array(0);\n  }\n\n  /**\n   * Checks if the buffer is full.\n   * @returns True if the buffer is full, otherwise false.\n   */\n  function isBufferFull(): boolean {\n    return _bytesWritten === bufferSize;\n  }\n\n  /**\n   * Flushes the buffer.\n   */\n  function flush(): void {\n    initBuffer();\n  }\n\n  /**\n   * Aggregates incoming PCM data into chunks.\n   * @param pcmData - The PCM data to be aggregated.\n   * @returns Object containing aggregated data and buffer information.\n   */\n  return function (pcmData: Float32Array): AggregateData {\n    if (isBufferFull()) {\n      flush();\n    }\n\n    const newBuffer = new Float32Array(buffer.length + pcmData.length);\n    newBuffer.set(buffer, 0);\n    newBuffer.set(pcmData, buffer.length);\n    buffer = newBuffer;\n    _bytesWritten += pcmData.length;\n\n    return {\n      isBufferFull: isBufferFull(),\n      buffer,\n      bufferSize,\n    };\n  };\n}\n\n/**\n * Computes the number of indexes we need to skip based on sampleRate\n * @param durationSeconds Duration expressed in seconds\n * @param sampleRate Sample rate, typically 48000, 441000, etc\n * @returns The number of indexes we need to skip\n */\nexport function computeIndexesToSkip(durationSeconds: number, sampleRate: number): number {\n  return Math.round(durationSeconds * sampleRate);\n}\n","import {descendingOverThresholds} from './utils';\nimport type {\n  Peaks,\n  PeaksAndThreshold,\n  BpmCandidates,\n  Interval,\n  Tempo,\n  Threshold,\n  AnalyzerFindPeaksOptions,\n  AnalyzerGroupByTempoOptions,\n  AnalyzerComputeBpmOptions,\n  BiquadFilterOptions,\n  AnalyzerFindPeaksAtTheshold,\n} from './types';\nimport * as consts from './consts';\nimport * as utils from './utils';\n\n/**\n * Find peaks when the signal if greater than the threshold, then move 10_000 indexes (represents ~0.23s) to ignore the descending phase of the parabol\n * @param options - AnalyzerFindPeaksAtTheshold\n * @param options.audioSampleRate - Sample rate\n * @param options.data - Buffer channel data\n * @param options.threshold - Threshold for qualifying as a peak\n * @param options.offset - Position where we start to loop\n * @returns Peaks found that are greater than the threshold\n */\nexport function findPeaksAtThreshold({\n  audioSampleRate,\n  data,\n  threshold,\n  offset = 0,\n}: AnalyzerFindPeaksAtTheshold): PeaksAndThreshold {\n  if (threshold < 0 || threshold > 1) {\n    throw new Error('Invalid threshold: ' + threshold + '. Threshold must be between 0 and 1.');\n  }\n\n  if (audioSampleRate <= 0) {\n    throw new Error('Invalid sample rate: ' + audioSampleRate + '. Sample rate must be positive.');\n  }\n\n  const peaks: Peaks = [];\n  const skipForwardIndexes = utils.computeIndexesToSkip(consts.peakSkipDuration, audioSampleRate);\n\n  const {length} = data;\n\n  /**\n   * Identify peaks that are greater than the threshold, adding them to the collection\n   */\n  for (let i = offset; i < length; i += 1) {\n    if (data[i] > threshold) {\n      peaks.push(i);\n\n      /**\n       * Skip forward ~0.25s to pass this peak\n       */\n      i += skipForwardIndexes;\n    }\n  }\n\n  return {\n    peaks,\n    threshold,\n  };\n}\n\n/**\n * Find the minimum amount of peaks from top to bottom threshold, it's necessary to analyze at least 10seconds at 90bpm\n * @param options - AnalyzerFindPeaksOptions\n * @param options.audioSampleRate - Sample rate\n * @param options.channelData - Channel data\n * @returns Suffisent amount of peaks in order to continue further the process\n */\nexport async function findPeaks({\n  audioSampleRate,\n  channelData,\n}: AnalyzerFindPeaksOptions): Promise<PeaksAndThreshold> {\n  if (audioSampleRate <= 0) {\n    throw new Error(`Invalid sample rate: ${audioSampleRate}. Sample rate must be positive.`);\n  }\n\n  if (!channelData || channelData.length === 0) {\n    throw new Error('Invalid channel data: buffer is empty or undefined.');\n  }\n\n  let validPeaks: Peaks = [];\n  let validThreshold = 0;\n\n  await descendingOverThresholds(async threshold => {\n    const {peaks} = findPeaksAtThreshold({audioSampleRate, data: channelData, threshold});\n\n    /**\n     * Loop over peaks\n     */\n    if (peaks.length < consts.minPeaks) {\n      return false;\n    }\n\n    validPeaks = peaks;\n    validThreshold = threshold;\n\n    return true;\n  });\n\n  return {\n    peaks: validPeaks,\n    threshold: validThreshold,\n  };\n}\n\n/**\n * Creates a biquad lowpass filter for audio processing.\n *\n * This filter helps isolate low-frequency components (bass) from the audio signal,\n * which is useful for detecting rhythmic patterns and beats in BPM analysis.\n *\n * @param context - The AudioContext or OfflineAudioContext to create the filter in\n * @param options - Optional configuration for the filter's frequency and quality\n * @returns A configured BiquadFilterNode ready to use in an audio graph\n *\n * @example\n * ```typescript\n * const audioContext = new AudioContext();\n * const filter = getBiquadFilter(audioContext, {\n *   frequencyValue: 150,  // Hz - frequencies above this are attenuated\n *   qualityValue: 1       // Q factor - steepness of the filter\n * });\n *\n * // Use in an audio graph\n * source.connect(filter);\n * filter.connect(audioContext.destination);\n * ```\n *\n * @remarks\n * - Default frequency is optimized for detecting bass drum kicks\n * - The lowpass filter attenuates high frequencies while preserving low frequencies\n * - Higher Q values create a steeper filter curve\n *\n * @see {@link analyzeFullBuffer} which uses this filter internally\n * @see {@link BiquadFilterOptions} for configuration options\n *\n * @group Functions\n */\nexport function getBiquadFilter(context: OfflineAudioContext | AudioContext, options?: BiquadFilterOptions): BiquadFilterNode {\n  const lowpass = context.createBiquadFilter();\n\n  lowpass.type = 'lowpass';\n  lowpass.frequency.value = options?.frequencyValue ?? consts.frequencyValue;\n  lowpass.Q.value = options?.qualityValue ?? consts.qualityValue;\n\n  return lowpass;\n}\n\n/**\n * Apply to the source a biquad lowpass filter\n * @param buffer - Audio buffer\n * @param options - Optionnal BiquadFilterOptions\n * @returns A Promise that resolves an AudioBuffer instance\n */\nexport async function getOfflineLowPassSource(buffer: AudioBuffer, options?: BiquadFilterOptions): Promise<AudioBuffer> {\n  const {length, numberOfChannels, sampleRate} = buffer;\n  const offlineAudioContext = new OfflineAudioContext(numberOfChannels, length, sampleRate);\n\n  /**\n   * Create buffer source\n   */\n  const source = offlineAudioContext.createBufferSource();\n  source.buffer = buffer;\n\n  const lowpass = getBiquadFilter(offlineAudioContext, options);\n\n  /**\n   * Pipe the song into the filter, and the filter into the offline context\n   */\n  source.connect(lowpass);\n  lowpass.connect(offlineAudioContext.destination);\n\n  source.start(0);\n\n  const audioBuffer = await offlineAudioContext.startRendering();\n\n  return audioBuffer;\n}\n\n/**\n * Return the computed bpm from data\n * @param options - AnalyzerComputeBpmOptions\n * @param options.data - Contain valid peaks\n * @param options.audioSampleRate - Audio sample rate\n * @returns A Promise that resolves BPM Candidates\n */\nexport async function computeBpm({\n  audioSampleRate,\n  data,\n}: AnalyzerComputeBpmOptions): Promise<BpmCandidates> {\n  const minPeaks = consts.minPeaks;\n\n  /**\n   * Flag to fix Object.keys looping\n   */\n  let hasPeaks = false;\n  let foundThreshold = consts.minValidThreshold;\n\n  await descendingOverThresholds(async (threshold: Threshold) => {\n    if (hasPeaks) {\n      return true;\n    }\n\n    if (data[threshold] && data[threshold].length > minPeaks) {\n      hasPeaks = true;\n      foundThreshold = threshold;\n    }\n\n    return false;\n  });\n\n  if (hasPeaks && foundThreshold) {\n    const intervals = identifyIntervals(data[foundThreshold]);\n    const tempos = groupByTempo({audioSampleRate, intervalCounts: intervals});\n    const candidates = getTopCandidates(tempos);\n\n    const bpmCandidates: BpmCandidates = {\n      bpm: candidates,\n      threshold: foundThreshold,\n    };\n\n    return bpmCandidates;\n  }\n\n  return {\n    bpm: [],\n    threshold: foundThreshold,\n  };\n}\n\n/**\n * Sort results by count and return top candidate\n * @param candidates - BPMs with count\n * @param length - Amount of returned candidates (default: 5)\n * @returns Returns the 5 top candidates with highest counts\n */\nexport function getTopCandidates(candidates: Tempo[], length = 5): Tempo[] {\n  return candidates.sort((a, b) => (b.count - a.count)).slice(0, length);\n}\n\n/**\n * Gets the top candidate from the array\n * @param candidates - BPMs with counts.\n * @returns Returns the top candidate with the highest count.\n * @throws {Error} When the candidates array is empty (insufficient samples for reliable detection)\n */\nexport function getTopCandidate(candidates: Tempo[]): number {\n  if (candidates.length === 0) {\n    throw new Error('Could not find enough samples for a reliable detection.');\n  }\n\n  const [first] = candidates.sort((a, b) => (b.count - a.count));\n\n  return first.tempo;\n}\n\n/**\n * Identify intervals between bass peaks\n * @param peaks - Array of qualified bass peaks\n * @returns Return a collection of intervals between peaks\n */\nexport function identifyIntervals(peaks: Peaks): Interval[] {\n  const intervals: Interval[] = [];\n\n  for (let n = 0; n < peaks.length; n++) {\n    for (let i = 0; i < consts.maxIntervalComparisons; i++) {\n      const peak = peaks[n];\n      const peakIndex = n + i;\n      const interval = peaks[peakIndex] - peak;\n\n      /**\n       * Try and find a matching interval and increase it's count\n       */\n      let foundInterval = intervals.find((intervalCount: Interval) => intervalCount.interval === interval);\n\n      if (foundInterval) {\n        // Create new object with incremented count (immutable update)\n        const index = intervals.indexOf(foundInterval);\n        intervals[index] = {\n          interval: foundInterval.interval,\n          count: foundInterval.count + 1,\n        };\n        foundInterval = intervals[index];\n      }\n\n      /**\n       * Add the interval to the collection if it's unique\n       */\n      if (!foundInterval) {\n        const item: Interval = {\n          interval,\n          count: 1,\n        };\n        intervals.push(item);\n      }\n    }\n  }\n\n  return intervals;\n}\n\n/**\n * Figure out best possible tempo candidates\n * @param options - AnalyzerGroupByTempoOptions\n * @param options.audioSampleRate - Audio sample rate\n * @param options.intervalCounts - List of identified intervals\n * @returns Intervals grouped with similar values\n */\nexport function groupByTempo({\n  audioSampleRate,\n  intervalCounts,\n}: AnalyzerGroupByTempoOptions): Tempo[] {\n  const tempoCounts: Tempo[] = [];\n\n  for (const intervalCount of intervalCounts) {\n    /**\n     * Skip if interval is equal 0\n     */\n    if (intervalCount.interval === 0) {\n      continue;\n    }\n\n    // Use local variable to avoid mutating input parameter\n    const absoluteInterval = Math.abs(intervalCount.interval);\n\n    /**\n     * Convert an interval to tempo\n     */\n    let theoreticalTempo = (60 / (absoluteInterval / audioSampleRate));\n\n    /**\n     * Adjust the tempo to fit within the 90-180 BPM range\n     */\n    while (theoreticalTempo < consts.minBpmRange) {\n      theoreticalTempo *= 2;\n    }\n\n    while (theoreticalTempo > consts.maxBpmRange) {\n      theoreticalTempo /= 2;\n    }\n\n    /**\n     * Round to legible integer\n     */\n    theoreticalTempo = Math.round(theoreticalTempo);\n\n    /**\n     * See if another interval resolved to the same tempo\n     */\n    let foundTempo = tempoCounts.find((tempoCount: Tempo) => tempoCount.tempo === theoreticalTempo);\n\n    if (foundTempo) {\n      // Create new object with aggregated count (immutable update)\n      const index = tempoCounts.indexOf(foundTempo);\n      tempoCounts[index] = {\n        tempo: foundTempo.tempo,\n        count: foundTempo.count + intervalCount.count,\n        confidence: foundTempo.confidence,\n      };\n      foundTempo = tempoCounts[index];\n    }\n\n    /**\n     * Add a unique tempo to the collection\n     */\n    if (!foundTempo) {\n      const tempo: Tempo = {\n        tempo: theoreticalTempo,\n        count: intervalCount.count,\n        confidence: 0,\n      };\n\n      tempoCounts.push(tempo);\n    }\n  }\n\n  return tempoCounts;\n}\n\n/**\n * Analyzes a complete audio buffer to detect the BPM (tempo) synchronously.\n *\n * This function performs offline analysis of an entire audio file to detect\n * its tempo. It's ideal for analyzing pre-recorded audio files where you\n * have the complete audio data available.\n *\n * @param originalBuffer - The AudioBuffer containing the complete audio to analyze\n * @param options - Optional filter configuration to customize bass detection\n * @returns A promise that resolves to an array of tempo candidates sorted by confidence\n *\n * @example\n * **Analyze an uploaded audio file**\n * ```typescript\n * const audioContext = new AudioContext();\n *\n * // Load audio file\n * const response = await fetch('song.mp3');\n * const arrayBuffer = await response.arrayBuffer();\n * const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n *\n * // Analyze BPM\n * const tempos = await analyzeFullBuffer(audioBuffer);\n *\n * console.log('Detected BPM:', tempos[0].tempo);\n * console.log('Confidence:', tempos[0].count);\n * console.log('Top 5 candidates:', tempos);\n * ```\n *\n * @example\n * **With custom filter settings**\n * ```typescript\n * const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n *\n * const tempos = await analyzeFullBuffer(audioBuffer, {\n *   frequencyValue: 150,  // Focus on bass frequencies\n *   qualityValue: 1       // Filter steepness\n * });\n *\n * // Get the most likely BPM\n * const mostLikelyBPM = tempos[0].tempo;\n * ```\n *\n * @example\n * **Analyze from file input**\n * ```typescript\n * const fileInput = document.querySelector('input[type=\"file\"]');\n *\n * fileInput.addEventListener('change', async (e) => {\n *   const file = e.target.files[0];\n *   const arrayBuffer = await file.arrayBuffer();\n *   const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n *\n *   const tempos = await analyzeFullBuffer(audioBuffer);\n *\n *   if (tempos.length > 0) {\n *     console.log(`Detected BPM: ${tempos[0].tempo} with ${tempos[0].count} matching intervals`);\n *   }\n * });\n * ```\n *\n * @remarks\n * - This is a synchronous/offline analysis method (analyzes complete audio at once)\n * - For real-time streaming audio, use {@link createRealtimeBpmAnalyzer} instead\n * - The function returns multiple candidates sorted by confidence (count)\n * - Analysis applies a lowpass filter to isolate bass frequencies for better beat detection\n * - BPM values are normalized to the 90-180 range (doubles/halves if outside this range)\n * - Higher `count` values indicate more confidence in the detected tempo\n *\n * @see {@link createRealtimeBpmAnalyzer} for real-time streaming audio analysis\n * @see {@link getBiquadFilter} for the lowpass filter used internally\n * @see {@link Tempo} for the structure of returned tempo candidates\n * @see {@link BiquadFilterOptions} for filter configuration options\n *\n * @group Functions\n */\nexport async function analyzeFullBuffer(originalBuffer: AudioBuffer, options?: BiquadFilterOptions): Promise<Tempo[]> {\n  if (!originalBuffer || originalBuffer.length === 0) {\n    throw new Error('Invalid audio buffer: buffer is empty or undefined.');\n  }\n\n  if (originalBuffer.sampleRate <= 0) {\n    throw new Error(`Invalid sample rate: ${originalBuffer.sampleRate}. Sample rate must be positive.`);\n  }\n\n  const buffer = await getOfflineLowPassSource(originalBuffer, options);\n  const channelData = buffer.getChannelData(0);\n  const {peaks} = await findPeaks({audioSampleRate: buffer.sampleRate, channelData});\n  const intervals = identifyIntervals(peaks);\n  const tempos = groupByTempo({audioSampleRate: buffer.sampleRate, intervalCounts: intervals});\n  const topCandidates = getTopCandidates(tempos);\n\n  return topCandidates;\n}\n","import {findPeaksAtThreshold, computeBpm} from './analyzer';\nimport type {\n  RealTimeBpmAnalyzerOptions,\n  RealTimeBpmAnalyzerParameters,\n  ValidPeaks,\n  NextIndexPeaks,\n  BpmCandidates,\n  Threshold,\n  RealtimeFindPeaksOptions,\n  RealtimeAnalyzeChunkOptions,\n} from './types';\nimport {\n  generateValidPeaksModel,\n  generateNextIndexPeaksModel,\n  descendingOverThresholds,\n} from './utils';\nimport * as consts from './consts';\n\n/**\n * Initial value of key parameters of the analyzer\n */\nconst initialValue = {\n  minValidThreshold: () => consts.minValidThreshold,\n  validPeaks: () => generateValidPeaksModel(),\n  nextIndexPeaks: () => generateNextIndexPeaksModel(),\n  skipIndexes: () => 1,\n  effectiveBufferTime: () => 0,\n};\n\n/**\n * Core analyzer class for real-time BPM detection.\n *\n * This class manages the state and logic for analyzing audio chunks in real-time\n * to detect beats per minute. It's used internally by the AudioWorklet processor.\n *\n * @remarks\n * Most users should use {@link createRealtimeBpmAnalyzer} instead of instantiating\n * this class directly. This class is primarily for internal use or advanced scenarios.\n *\n * @group Classes\n */\nexport class RealTimeBpmAnalyzer {\n  /**\n   * Default configuration\n   */\n  options: RealTimeBpmAnalyzerOptions = {\n    continuousAnalysis: false,\n    stabilizationTime: 20000,\n    muteTimeInIndexes: 10000,\n    debug: false,\n  };\n\n  /**\n   * Minimum valid threshold, below this level result would be irrelevant.\n   */\n  minValidThreshold: Threshold = initialValue.minValidThreshold();\n  /**\n   * Contain all valid peaks\n   */\n  validPeaks: ValidPeaks = initialValue.validPeaks();\n  /**\n   * Next index (+10000 ...) to take care about peaks\n   */\n  nextIndexPeaks: NextIndexPeaks = initialValue.nextIndexPeaks();\n  /**\n   * Number / Position of chunks\n   */\n  skipIndexes: number = initialValue.skipIndexes();\n  effectiveBufferTime: number = initialValue.effectiveBufferTime();\n  /**\n   * Computed values\n   */\n  computedStabilizationTimeInSeconds = 0;\n\n  constructor(options: RealTimeBpmAnalyzerParameters = {}) {\n    Object.assign(this.options, options);\n    this.updateComputedValues();\n  }\n\n  /**\n   * Update the computed values\n   */\n  updateComputedValues() {\n    this.computedStabilizationTimeInSeconds = this.options.stabilizationTime / 1000;\n  }\n\n  /**\n   * Reset BPM computation properties to get a fresh start\n   */\n  reset(): void {\n    this.minValidThreshold = initialValue.minValidThreshold();\n    this.validPeaks = initialValue.validPeaks();\n    this.nextIndexPeaks = initialValue.nextIndexPeaks();\n    this.skipIndexes = initialValue.skipIndexes();\n    this.effectiveBufferTime = initialValue.effectiveBufferTime();\n  }\n\n  /**\n   * Remve all validPeaks between the minThreshold pass in param to optimize the weight of datas\n   * @param minThreshold - Value between 0.9 and 0.2\n   */\n  async clearValidPeaks(minThreshold: Threshold): Promise<void> {\n    this.minValidThreshold = minThreshold;\n\n    await descendingOverThresholds(async threshold => {\n      if (threshold < minThreshold && this.validPeaks[threshold] !== undefined) {\n        delete this.validPeaks[threshold]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n        delete this.nextIndexPeaks[threshold]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n      }\n\n      return false;\n    });\n  }\n\n  /**\n   * Attach this function to an audioprocess event on a audio/video node to compute BPM / Tempo in realtime\n   * @param options - RealtimeAnalyzeChunkOptions\n   * @param options.audioSampleRate - Audio sample rate (44100)\n   * @param options.channelData - Channel data\n   * @param options.bufferSize - Buffer size (4096)\n   * @param options.postMessage - Function to post a message to the processor node\n   */\n  async analyzeChunk({audioSampleRate, channelData, bufferSize, postMessage}: RealtimeAnalyzeChunkOptions): Promise<void> {\n    if (this.options.debug) {\n      postMessage({type: 'analyzeChunk', data: channelData});\n    }\n\n    /**\n     * We are summing up the size of each analyzed chunks in order to compute later if we reached the stabilizationTime\n     * Ex: effectiveBufferTime / audioSampleRate = timeInSeconds (1000000/44100=22s)\n     */\n    this.effectiveBufferTime += bufferSize;\n\n    /**\n     * Compute the maximum index with all previous chunks\n     */\n    const currentMaxIndex = bufferSize * this.skipIndexes;\n\n    /**\n     * Compute the minimum index with all previous chunks\n     */\n    const currentMinIndex = currentMaxIndex - bufferSize;\n\n    /**\n     * Mutate nextIndexPeaks and validPeaks if possible\n     */\n    await this.findPeaks({\n      audioSampleRate,\n      channelData,\n      bufferSize,\n      currentMinIndex,\n      currentMaxIndex,\n      postMessage,\n    });\n\n    /**\n     * Increment chunk\n     */\n    this.skipIndexes++;\n\n    const data: BpmCandidates = await computeBpm({audioSampleRate, data: this.validPeaks});\n    const {threshold} = data;\n    postMessage({type: 'bpm', data});\n\n    /**\n     * If the results found have a \"high\" threshold, the BPM is considered stable/strong\n     */\n    if (this.minValidThreshold < threshold) {\n      postMessage({type: 'bpmStable', data});\n      await this.clearValidPeaks(threshold);\n    }\n\n    /**\n     * After x time, we reinit the analyzer\n     */\n    if (this.options.continuousAnalysis && this.effectiveBufferTime / audioSampleRate > this.computedStabilizationTimeInSeconds) {\n      this.reset();\n      postMessage({type: 'analyzerReset'});\n    }\n  }\n\n  /**\n   * Find the best threshold with enought peaks\n   * @param options - Options for finding peaks\n   * @param options.audioSampleRate - Sample rate\n   * @param options.channelData - Channel data\n   * @param options.bufferSize - Buffer size\n   * @param options.currentMinIndex - Current minimum index\n   * @param options.currentMaxIndex - Current maximum index\n   * @param options.postMessage - Function to post a message to the processor node\n   */\n  async findPeaks({\n    audioSampleRate,\n    channelData,\n    bufferSize,\n    currentMinIndex,\n    currentMaxIndex,\n    postMessage,\n  }: RealtimeFindPeaksOptions): Promise<void> {\n    await descendingOverThresholds(async threshold => {\n      if (this.nextIndexPeaks[threshold] >= currentMaxIndex) {\n        return false;\n      }\n\n      /**\n       * Get the next index in the next chunk\n       */\n      const offsetForNextPeak = this.nextIndexPeaks[threshold] % bufferSize; // 0 - 4095\n\n      const {peaks, threshold: atThreshold} = findPeaksAtThreshold({audioSampleRate, data: channelData, threshold, offset: offsetForNextPeak});\n\n      /**\n       * Loop over peaks\n       */\n      if (peaks.length === 0) {\n        return false;\n      }\n\n      for (const relativeChunkPeak of peaks) {\n        const index = currentMinIndex + relativeChunkPeak;\n\n        /**\n         * Add current Index + muteTimeInIndexes (10000/44100=0.22s)\n         */\n        this.nextIndexPeaks[atThreshold] = index + this.options.muteTimeInIndexes;\n\n        /**\n         * Store valid relativeChunkPeak Indexes\n         */\n        this.validPeaks[atThreshold].push(index);\n\n        if (this.options.debug) {\n          postMessage({\n            type: 'validPeak',\n            data: {\n              threshold: atThreshold,\n              index,\n            },\n          });\n        }\n      }\n\n      return false;\n    }, this.minValidThreshold);\n  }\n}\n"]}