class PcmProcessor extends AudioWorkletProcessor {
    constructor(options) {
        super()
        this.frameSize = options.processorOptions.frameSize || 960 // default 20ms
        this.threshold = 0.01
        this.buffer = new Float32Array(this.frameSize)
        this.offset = 0
        this.continuationFrames = 0
        /** Last completed frame that was not posted; prepended once on speech onset. */
        this.lastHeldFrame = null
        /** Whether the previous completed frame was posted. */
        this.prevEmitted = false
    }

    process(inputs) {
        const input = inputs[0]?.[0]
        if (!input) return true

        let i = 0
        while (i < input.length) {
            const remaining = this.frameSize - this.offset
            const toCopy = Math.min(remaining, input.length - i)

            this.buffer.set(input.subarray(i, i + toCopy), this.offset)
            this.offset += toCopy
            i += toCopy

            if (this.offset === this.frameSize) {
                // does the frame have any samples above the detection level? if so, send the frame and start a countdown of subsequent frames to send to avoid weird clipping
                let aboveThreshold = false
                for (let j = 0; j < this.offset; j++) {
                    if (Math.abs(this.buffer[j]) > this.threshold) {
                        aboveThreshold = true
                        this.continuationFrames = 15
                        break
                    }
                }
                let shouldSend = false
                if (aboveThreshold) {
                    shouldSend = true
                } else if (this.continuationFrames > 0) {
                    this.continuationFrames--
                    shouldSend = true
                }

                if (shouldSend) {
                    if (aboveThreshold && !this.prevEmitted && this.lastHeldFrame) {
                        this.port.postMessage(new Float32Array(this.lastHeldFrame))
                        this.lastHeldFrame = null
                    }
                    this.port.postMessage(new Float32Array(this.buffer))
                    this.prevEmitted = true
                } else {
                    this.continuationFrames = 0
                    this.lastHeldFrame = new Float32Array(this.buffer)
                    this.prevEmitted = false
                }
                this.offset = 0
            }
        }
        return true
    }
}
registerProcessor('pcm-processor', PcmProcessor)
