audiobookshelf/client/players/smart-speed/SilenceCompressorProcessor.js

122 lines
3.3 KiB
JavaScript

class SilenceCompressorProcessor extends AudioWorkletProcessor {
constructor() {
super()
this.regions = []
this.ratio = 1.0
this.totalCompressedMs = 0
this.rampDurationSec = 0.005 // 5ms
this.port.onmessage = (event) => {
const msg = event.data
if (msg.type === 'set-regions') {
this.regions = msg.regions.filter(r => (r.end - r.start) >= 200)
} else if (msg.type === 'set-ratio') {
this.ratio = msg.value
}
}
}
getActiveRegion(timeMs) {
for (const r of this.regions) {
if (timeMs >= r.start && timeMs <= r.end) return r
}
return null
}
calculateRampGain(timeMs, region) {
const rampMs = this.rampDurationSec * 1000
// Entry ramp (0 -> 1)
if (timeMs - region.start < rampMs) {
return (timeMs - region.start) / rampMs
}
// Exit ramp (1 -> 0)
if (region.end - timeMs < rampMs) {
return (region.end - timeMs) / rampMs
}
return 1.0
}
process(inputs, outputs, parameters) {
const input = inputs[0]
const output = outputs[0]
if (!input || !input.length || !output || !output.length) return true
const numChannels = input.length
const numFrames = input[0].length
const sampleRateC = typeof sampleRate !== 'undefined' ? sampleRate : 48000
// Use currentTime if available, otherwise fallback to 0 (for tests)
const currentTimeSec = typeof currentTime !== 'undefined' ? currentTime : 0
let outputIndex = 0
let inputIndex = 0
let savedSecThisBlock = 0
while (inputIndex < numFrames) {
const sampleTimeSec = currentTimeSec + (inputIndex / sampleRateC)
const sampleTimeMs = sampleTimeSec * 1000
const region = this.getActiveRegion(sampleTimeMs)
let step = 1.0
let rampGain = 1.0
if (region && this.ratio > 1.0) {
step = this.ratio
rampGain = this.calculateRampGain(sampleTimeMs, region)
}
// If taking this step exceeds the input buffer, we must stop
if (inputIndex >= numFrames) break
const intIndex = Math.floor(inputIndex)
const frac = inputIndex - intIndex
for (let c = 0; c < numChannels; c++) {
const inChannel = input[c]
const outChannel = output[c]
let sample = inChannel[intIndex]
if (frac > 0 && intIndex + 1 < numFrames) {
sample = sample + frac * (inChannel[intIndex + 1] - sample)
}
if (outputIndex < numFrames) {
outChannel[outputIndex] = sample * rampGain
}
}
inputIndex += step
outputIndex += 1
if (step > 1.0) {
savedSecThisBlock += (step - 1.0) / sampleRateC
}
}
// Fill the rest of the output buffer with 0s if we compressed
for (let c = 0; c < numChannels; c++) {
for (let i = outputIndex; i < numFrames; i++) {
output[c][i] = 0
}
}
if (savedSecThisBlock > 0) {
this.totalCompressedMs += savedSecThisBlock * 1000
this.port.postMessage({ type: 'time-saved', ms: this.totalCompressedMs })
}
return true
}
}
if (typeof registerProcessor !== 'undefined') {
registerProcessor('silence-compressor', SilenceCompressorProcessor)
}
if (typeof module !== 'undefined') {
module.exports = SilenceCompressorProcessor
}