"use strict";
(() => {
    var __async = (__this, __arguments, generator) => {
        return new Promise((resolve, reject) => {
            var fulfilled = (value) => {
                try {
                    step(generator.next(value));
                } catch (e) {
                    reject(e);
                }
            };
            var rejected = (value) => {
                try {
                    step(generator.throw(value));
                } catch (e) {
                    reject(e);
                }
            };
            var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
            step((generator = generator.apply(__this, __arguments)).next());
        });
    };

    // src/consts.ts
    var realtimeBpmProcessorName = "realtime-bpm-processor";
    var startThreshold = 0.95;
    var minValidThreshold = 0.3;
    var minPeaks = 15;
    var thresholdStep = 0.05;
    var skipForwardIndexes = 1e4;

    // src/utils.ts
    function descendingOverThresholds(_0) {
        return __async(this, arguments, function* (onThreshold, minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
            let threshold = startThreshold2;
            do {
                threshold -= thresholdStep2;
                const shouldExit = yield onThreshold(threshold);
                if (shouldExit) {
                    break;
                }
            } while (threshold > minValidThreshold2);
        });
    }
    function generateValidPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
        const object = {};
        let threshold = startThreshold2;
        do {
            threshold -= thresholdStep2;
            object[threshold.toString()] = [];
        } while (threshold > minValidThreshold2);
        return object;
    }
    function generateNextIndexPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
        const object = {};
        let threshold = startThreshold2;
        do {
            threshold -= thresholdStep2;
            object[threshold.toString()] = 0;
        } while (threshold > minValidThreshold2);
        return object;
    }
    function chunckAggregator() {
        const bufferSize = 4096;
        let _bytesWritten = 0;
        let buffer = new Float32Array(0);
        function initBuffer() {
            _bytesWritten = 0;
            buffer = new Float32Array(0);
        }
        function isBufferFull() {
            return _bytesWritten === bufferSize;
        }
        function flush() {
            initBuffer();
        }
        return function (pcmData) {
            if (isBufferFull()) {
                flush();
            }
            const newBuffer = new Float32Array(buffer.length + pcmData.length);
            newBuffer.set(buffer, 0);
            newBuffer.set(pcmData, buffer.length);
            buffer = newBuffer;
            _bytesWritten += pcmData.length;
            return {
                isBufferFull: isBufferFull(),
                buffer,
                bufferSize
            };
        };
    }

    // src/analyzer.ts
    function findPeaksAtThreshold(data, threshold, offset = 0, skipForwardIndexes2 = skipForwardIndexes) {
        const peaks = [];
        const { length } = data;
        for (let i = offset; i < length; i += 1) {
            if (data[i] > threshold) {
                peaks.push(i);
                i += skipForwardIndexes2;
            }
        }
        return {
            peaks,
            threshold
        };
    }
    function computeBpm(_0, _1) {
        return __async(this, arguments, function* (data, audioSampleRate, minPeaks2 = minPeaks) {
            let hasPeaks = false;
            let foundThreshold = minValidThreshold;
            yield descendingOverThresholds((threshold) => __async(this, null, function* () {
                if (hasPeaks) {
                    return true;
                }
                if (data[threshold].length > minPeaks2) {
                    hasPeaks = true;
                    foundThreshold = threshold;
                }
                return false;
            }));
            if (hasPeaks && foundThreshold) {
                const intervals = identifyIntervals(data[foundThreshold]);
                const tempos = groupByTempo(audioSampleRate, intervals);
                const candidates = getTopCandidates(tempos);
                const bpmCandidates = {
                    bpm: candidates,
                    threshold: foundThreshold
                };
                return bpmCandidates;
            }
            return {
                bpm: [],
                threshold: foundThreshold
            };
        });
    }
    function getTopCandidates(candidates, length = 5) {
        return candidates.sort((a, b) => b.count - a.count).splice(0, length);
    }
    function identifyIntervals(peaks) {
        const intervals = [];
        for (let n = 0; n < peaks.length; n++) {
            for (let i = 0; i < 10; i++) {
                const peak = peaks[n];
                const peakIndex = n + i;
                const interval = peaks[peakIndex] - peak;
                const foundInterval = intervals.some((intervalCount) => {
                    if (intervalCount.interval === interval) {
                        intervalCount.count += 1;
                        return intervalCount.count;
                    }
                    return false;
                });
                if (!foundInterval) {
                    const item = {
                        interval,
                        count: 1
                    };
                    intervals.push(item);
                }
            }
        }
        return intervals;
    }
    function groupByTempo(audioSampleRate, intervalCounts) {
        const tempoCounts = [];
        for (const intervalCount of intervalCounts) {
            if (intervalCount.interval === 0) {
                continue;
            }
            intervalCount.interval = Math.abs(intervalCount.interval);
            let theoreticalTempo = 60 / (intervalCount.interval / audioSampleRate);
            while (theoreticalTempo < 90) {
                theoreticalTempo *= 2;
            }
            while (theoreticalTempo > 180) {
                theoreticalTempo /= 2;
            }
            theoreticalTempo = Math.round(theoreticalTempo);
            const foundTempo = tempoCounts.some((tempoCount) => {
                if (tempoCount.tempo === theoreticalTempo) {
                    tempoCount.count += intervalCount.count;
                    return tempoCount.count;
                }
                return false;
            });
            if (!foundTempo) {
                const tempo = {
                    tempo: theoreticalTempo,
                    count: intervalCount.count,
                    confidence: 0
                };
                tempoCounts.push(tempo);
            }
        }
        return tempoCounts;
    }

    // src/realtime-bpm-analyzer.ts
    var initialValue = {
        minValidThreshold: () => minValidThreshold,
        timeoutStabilization: () => 0,
        validPeaks: () => generateValidPeaksModel(),
        nextIndexPeaks: () => generateNextIndexPeaksModel(),
        skipIndexes: () => 1
    };
    var RealTimeBpmAnalyzer = class {
        constructor(config = {}) {
            this.options = {
                continuousAnalysis: false,
                computeBpmDelay: 1e4,
                stabilizationTime: 2e4,
                muteTimeInIndexes: 1e4
            };
            this.minValidThreshold = initialValue.minValidThreshold();
            this.timeoutStabilization = initialValue.timeoutStabilization();
            this.validPeaks = initialValue.validPeaks();
            this.nextIndexPeaks = initialValue.nextIndexPeaks();
            this.skipIndexes = initialValue.skipIndexes();
            Object.assign(this.options, config);
        }
        setAsyncConfiguration(parameters) {
            Object.assign(this.options, parameters);
        }
        reset() {
            this.minValidThreshold = initialValue.minValidThreshold();
            this.timeoutStabilization = initialValue.timeoutStabilization();
            this.validPeaks = initialValue.validPeaks();
            this.nextIndexPeaks = initialValue.nextIndexPeaks();
            this.skipIndexes = initialValue.skipIndexes();
        }
        clearValidPeaks(minThreshold) {
            return __async(this, null, function* () {
                console.log(`[clearValidPeaks] function: under ${minThreshold}, this.minValidThreshold has been setted to that threshold.`);
                this.minValidThreshold = Number.parseFloat(minThreshold.toFixed(2));
                yield descendingOverThresholds((threshold) => __async(this, null, function* () {
                    if (threshold < minThreshold) {
                        delete this.validPeaks[threshold];
                        delete this.nextIndexPeaks[threshold];
                    }
                    return false;
                }));
            });
        }
        analyzeChunck(channelData, audioSampleRate, bufferSize, postMessage) {
            return __async(this, null, function* () {
                const currentMaxIndex = bufferSize * this.skipIndexes;
                const currentMinIndex = currentMaxIndex - bufferSize;
                yield this.findPeaks(channelData, bufferSize, currentMinIndex, currentMaxIndex);
                this.skipIndexes++;
                const result = yield computeBpm(this.validPeaks, audioSampleRate);
                const { threshold } = result;
                postMessage({ message: "BPM", result });
                if (this.minValidThreshold < threshold) {
                    postMessage({ message: "BPM_STABLE", result });
                    yield this.clearValidPeaks(threshold);
                }
                if (this.options.continuousAnalysis) {
                    clearTimeout(this.timeoutStabilization);
                    this.timeoutStabilization = window.setTimeout(() => {
                        console.log("[timeoutStabilization] setTimeout: Fired !");
                        this.options.computeBpmDelay = 0;
                        this.reset();
                    }, this.options.stabilizationTime);
                }
            });
        }
        findPeaks(channelData, bufferSize, currentMinIndex, currentMaxIndex) {
            return __async(this, null, function* () {
                yield descendingOverThresholds((threshold) => __async(this, null, function* () {
                    if (this.nextIndexPeaks[threshold] >= currentMaxIndex) {
                        return false;
                    }
                    const offsetForNextPeak = this.nextIndexPeaks[threshold] % bufferSize;
                    const { peaks, threshold: atThreshold } = findPeaksAtThreshold(channelData, threshold, offsetForNextPeak);
                    if (peaks.length === 0) {
                        return false;
                    }
                    for (const relativeChunkPeak of peaks) {
                        this.nextIndexPeaks[atThreshold] = currentMinIndex + relativeChunkPeak + this.options.muteTimeInIndexes;
                        this.validPeaks[atThreshold].push(currentMinIndex + relativeChunkPeak);
                    }
                    return false;
                }), this.minValidThreshold);
            });
        }
    };

    // processor/realtime-bpm-processor.ts
    var RealTimeBpmProcessor = class extends AudioWorkletProcessor {
        constructor() {
            super();
            this.realTimeBpmAnalyzer = new RealTimeBpmAnalyzer();
            this.aggregate = chunckAggregator();
            this.port.addEventListener("message", this.onMessage.bind(this));
            this.port.start();
        }
        onMessage(event) {
            if (event.data.message === "ASYNC_CONFIGURATION") {
                this.realTimeBpmAnalyzer.setAsyncConfiguration(event.data.parameters);
            }
        }
        process(inputs, _outputs, _parameters) {
            const currentChunk = inputs[0][0];
            if (!currentChunk) {
                return true;
            }
            const { isBufferFull, buffer, bufferSize } = this.aggregate(currentChunk);
            if (isBufferFull) {
                this.realTimeBpmAnalyzer.analyzeChunck(buffer, sampleRate, bufferSize, (event) => {
                    this.port.postMessage(event);
                }).catch((error) => {
                    console.error(error);
                });
            }
            return true;
        }
    };
    registerProcessor(realtimeBpmProcessorName, RealTimeBpmProcessor);
    var realtime_bpm_processor_default = {};
})();
//# sourceMappingURL=realtime-bpm-processor.js.map