From 142762346cf8735b7b90dc09506cfa9521be7aae Mon Sep 17 00:00:00 2001 From: Julian Ostarek Date: Mon, 27 Oct 2025 15:37:16 -0400 Subject: [PATCH] Merge standards --- README.md | 2 - build.js | 3 +- docs/examples/nodom.html | 10 ++- docs/index.html | 4 +- docs/web-audio-peak-meter-4.1.0.min.js | 2 +- docs/web-audio-peak-meter-4.1.0.min.js.map | 2 +- examples/nodom.js | 17 +++++- src/config.ts | 2 - src/index.ts | 71 +++++++++++++--------- src/peak-sample-processor.ts | 16 ----- src/true-peak-processor.ts | 19 ++++-- 11 files changed, 79 insertions(+), 69 deletions(-) delete mode 100644 src/peak-sample-processor.ts diff --git a/README.md b/README.md index afaf2a8..3ddb8f3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ Then, at the bottom of your `` tag, add the script tag for these meters. I console.log(`Current volume: ${volume.toFixed(2)}`); }, { - audioMeterStandard: "peak-sample", // 10 times per second peakCallbackDelay: 1000 / 10, // Optional, map dB values to a normalized range @@ -49,7 +48,6 @@ Then, at the bottom of your `` tag, add the script tag for these meters. I The following options options are supported (the third parameter of the constructor) -- audioMeterStandard (string): Can be either `peak-sample`, or `true-peak` (default: `peak-sample`) - peakHoldDuration (number): the number, in milliseconds, to hold the peak value before resetting (default: `0`, meaning never reset) ## Frequently encountered problems diff --git a/build.js b/build.js index d3acb64..cd257e6 100644 --- a/build.js +++ b/build.js @@ -18,7 +18,7 @@ async function buildWorklet(fileName) { input: `./src/${fileName}-processor.ts`, plugins: [ typescriptPlugin({ - include: [`./src/${fileName}*.ts`, './src/global.d.ts'], + include: ['./src/true-peak*.ts', './src/peak-sample*.ts', './src/global.d.ts'], exclude: ['node_modules', 'lib', '**/*.test.ts'], lib: ['es2016', 'DOM'], sourceMap: false, @@ -161,7 +161,6 @@ async function buildDocs() { } async function buildAll() { - await buildWorklet('peak-sample'); await buildWorklet('true-peak'); await buildMain(); await buildDocs(); diff --git a/docs/examples/nodom.html b/docs/examples/nodom.html index e8fc612..e2dae1f 100644 --- a/docs/examples/nodom.html +++ b/docs/examples/nodom.html @@ -88,7 +88,6 @@

Javascript code

const meterInstance = new webAudioPeakMeter.WebAudioPeakMeter(sourceNode, (volume) => { console.log('Volume callback:', volume); }, { - audioMeterStandard: 'peak-sample', peakCallbackDelay: 'immediate', peakHoldDuration: 5000, peakCallbackNormalizeVolume: { @@ -107,9 +106,9 @@

Javascript code

getPeaks.addEventListener('click', () => { const peaks = meterInstance.getPeaks(); - currentFloat.innerText = displayFloatArray(peaks.current); + currentFloat.innerText = displayFloatArray(peaks.current.map(({ volume }) => volume)); currentDB.innerText = displayFloatArray(peaks.currentDB); - maxesFloat.innerText = displayFloatArray(peaks.maxes); + maxesFloat.innerText = displayFloatArray(peaks.maxes.map(({ volume }) => volume)); maxesDB.innerText = displayFloatArray(peaks.maxesDB); }); @@ -152,7 +151,6 @@

Javascript code

const meterInstance = new webAudioPeakMeter.WebAudioPeakMeter(sourceNode, (volume) => { console.log('Volume callback:', volume); }, { - audioMeterStandard: 'peak-sample', peakCallbackDelay: 'immediate', peakHoldDuration: 5000, peakCallbackNormalizeVolume: { @@ -171,9 +169,9 @@

Javascript code

getPeaks.addEventListener('click', () => { const peaks = meterInstance.getPeaks(); - currentFloat.innerText = displayFloatArray(peaks.current); + currentFloat.innerText = displayFloatArray(peaks.current.map(({ volume }) => volume)); currentDB.innerText = displayFloatArray(peaks.currentDB); - maxesFloat.innerText = displayFloatArray(peaks.maxes); + maxesFloat.innerText = displayFloatArray(peaks.maxes.map(({ volume }) => volume)); maxesDB.innerText = displayFloatArray(peaks.maxesDB); }); diff --git a/docs/index.html b/docs/index.html index 610d5d3..c3b64c0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -28,9 +28,8 @@

Usage (basic)

console.log(`Current volume: ${volume.toFixed(2)}`); }, { - audioMeterStandard: "peak-sample", // 10 times per second - peakCallbackDelay: 1000 / 10, + peakCallbackDelay: 1000 / 10, // Optional, map dB values to a normalized range peakCallbackNormalizeVolume: { dbRangeMin: -48, @@ -45,7 +44,6 @@

Usage (basic)

Options

The following options options are supported (the third parameter of the constructor)

Frequently encountered problems

diff --git a/docs/web-audio-peak-meter-4.1.0.min.js b/docs/web-audio-peak-meter-4.1.0.min.js index 45f2eb7..f07180f 100644 --- a/docs/web-audio-peak-meter-4.1.0.min.js +++ b/docs/web-audio-peak-meter-4.1.0.min.js @@ -1,2 +1,2 @@ -var webAudioPeakMeter=function(e){"use strict";const t={audioMeterStandard:"peak-sample",peakHoldDuration:0,peakCallbackDelay:"immediate"};function s(e){return 20*(t=10,s=e,Math.log(s)/Math.log(t));var t,s}return e.WebAudioPeakMeter=class{constructor(e,s,a={}){this.srcNode=e,this.peakCallback=s,this.config=Object.assign(Object.assign({},t),a),this.channelCount=e.channelCount,this.tempPeaks=new Array(this.channelCount).fill(0),this.heldPeaks=new Array(this.channelCount).fill(0),this.peakHoldIntervals=new Array(this.channelCount).fill(void 0),this.initNode()}async initNode(){if("closed"===this.srcNode.context.state)return void console.warn("WebAudioPeakMeter: The AudioContext is closed, cannot initialize the node.");const{audioMeterStandard:e}=this.config;try{this.node=new AudioWorkletNode(this.srcNode.context,`${e}-processor`,{parameterData:{}})}catch(t){const s=new Blob(["true-peak"===e?'function s(s,t,e){const o=[];for(let r=0;r8e4?2:4,this.lpfCoefficients=function(s,t){const e=[],o=1/(4*t),r=1-Math.ceil(s/2),i=Math.floor(s/2);for(let n=r;n<=i;n++){const r=.54+.46*Math.cos(2*Math.PI*n/s);let i=0;i=0==n?2*o:Math.sin(2*Math.PI*o*n)/(Math.PI*n),i=r*i*t,e.push(i)}return e}(this.numCoefficients,this.upsampleFactor),this.lpfBuffers=[],this.port.postMessage({type:"message",message:`true peak inited? ${this.sampleRate}`}),this.processCount=0}process(t){const e=t[0];if(e.length>this.lpfBuffers.length)for(let s=1;s<=e.length;s+=1)s>this.lpfBuffers.length&&this.lpfBuffers.push(new Array(this.numCoefficients).fill(0));const o=function(t,e,o,r){return t.map(((t,i)=>{const n=e[i];let h=0;for(let e=0;eh&&(h=t)}}return h}))}(e,this.lpfBuffers,this.lpfCoefficients,this.upsampleFactor);return this.port.postMessage({type:"peaks",peaks:o}),this.processCount%100==0&&this.port.postMessage({type:"message",message:this.lpfBuffers}),this.processCount+=1,!0}}try{registerProcessor("true-peak-processor",t)}catch(s){console.info("Failed to register true-peak-processor. This probably means it was already registered.")}\n':'class e extends AudioWorkletProcessor{process(e){const s=function(e){return e.map((e=>{let s=0;for(let r=0;rs&&(s=t)}return s}))}(e[0]);return this.port.postMessage({type:"peaks",peaks:s}),!0}}try{registerProcessor("peak-sample-processor",e)}catch(e){console.info("Failed to register peak-sample-processor. This probably means it was already registered.")}\n'],{type:"application/javascript"}),a=URL.createObjectURL(s);await this.srcNode.context.audioWorklet.addModule(a),this.node=new AudioWorkletNode(this.srcNode.context,`${e}-processor`,{parameterData:{}})}if(this.node.port.onmessage=e=>this.handleNodePortMessage(e),this.srcNode.connect(this.node).connect(this.srcNode.context.destination),"requestAnimationFrame"===this.config.peakCallbackDelay&&this.peakCallback){const e=()=>{var t;null===(t=this.peakCallback)||void 0===t||t.call(null,this.getNormalizedCurrentPeakVolume()),this.animationRequestId=requestAnimationFrame(e)};this.animationRequestId=requestAnimationFrame(e)}"number"==typeof this.config.peakCallbackDelay&&this.peakCallback&&(this.intervalId=window.setInterval((()=>{var e;null===(e=this.peakCallback)||void 0===e||e.call(null,this.getNormalizedCurrentPeakVolume())}),this.config.peakCallbackDelay))}handleNodePortMessage(e){if("message"===e.data.type&&console.log(e.data.message),"peaks"===e.data.type){const{peaks:t}=e.data;for(let e=0;ee?this.tempPeaks[e]=t[e]:this.tempPeaks[e]=0;t.lengththis.heldPeaks[e]&&(this.heldPeaks[e]=t[e],this.peakHoldIntervals[e]&&clearInterval(this.peakHoldIntervals[e]),this.config.peakHoldDuration&&(this.peakHoldIntervals[e]=window.setInterval((()=>this.clearPeak(e)),this.config.peakHoldDuration)));"immediate"===this.config.peakCallbackDelay&&this.peakCallback&&this.peakCallback(this.getNormalizedCurrentPeakVolume())}}clearPeak(e){this.heldPeaks[e]=this.tempPeaks[e]}clearPeaks(){for(let e=0;e{console.error("WebAudioPeakMeter: Failed to initialize the AudioWorkletNode.",e)}))}async initNode(){if("closed"!==this.srcNode.context.state){try{this.node=new AudioWorkletNode(this.srcNode.context,"true-peak-processor",{parameterData:{}})}catch(e){const t=new Blob(['function t(t,s,e){const r=[];for(let o=0;o8e4?2:4,this.lpfCoefficients=function(t,s){const e=[],r=1/(4*s),o=1-Math.ceil(t/2),i=Math.floor(t/2);for(let n=o;n<=i;n++){const o=.54+.46*Math.cos(2*Math.PI*n/t);let i=0;i=0==n?2*r:Math.sin(2*Math.PI*r*n)/(Math.PI*n),i=o*i*s,e.push(i)}return e}(this.numCoefficients,this.upsampleFactor),this.lpfBuffers=[],this.port.postMessage({type:"message",message:`true peak inited? ${this.sampleRate}`}),this.processCount=0}process(s){const e=s[0];if(e.length>this.lpfBuffers.length)for(let t=1;t<=e.length;t+=1)t>this.lpfBuffers.length&&this.lpfBuffers.push(new Array(this.numCoefficients).fill(0));const r=function(s,e,r,o){return s.map(((s,i)=>{const n=e[i];let a=0;for(let e=0;ea&&(a=s)}}return a}))}(e,this.lpfBuffers,this.lpfCoefficients,this.upsampleFactor),o=function(t){return t.map((t=>{let s=0;for(let e=0;es&&(s=r)}return s}))}(e);return this.port.postMessage({type:"peaks",volumes:r,amplitudes:o}),this.processCount%100==0&&this.port.postMessage({type:"message",message:this.lpfBuffers}),this.processCount+=1,!0}}try{registerProcessor("true-peak-processor",s)}catch(t){console.info("Failed to register true-peak-processor. This probably means it was already registered.")}\n'],{type:"application/javascript"}),s=URL.createObjectURL(t);await this.srcNode.context.audioWorklet.addModule(s),this.node=new AudioWorkletNode(this.srcNode.context,"true-peak-processor",{parameterData:{}})}if(this.node.port.onmessage=e=>this.handleNodePortMessage(e),this.srcNode.connect(this.node).connect(this.srcNode.context.destination),"requestAnimationFrame"===this.config.peakCallbackDelay&&this.peakCallback){const e=()=>{var t;null===(t=this.peakCallback)||void 0===t||t.call(null,this.getNormalizedCurrentPeakVolume()),this.animationRequestId=requestAnimationFrame(e)};this.animationRequestId=requestAnimationFrame(e)}"number"==typeof this.config.peakCallbackDelay&&this.peakCallback&&(this.intervalId=window.setInterval((()=>{var e;null===(e=this.peakCallback)||void 0===e||e.call(null,this.getNormalizedCurrentPeakVolume())}),this.config.peakCallbackDelay))}else console.warn("WebAudioPeakMeter: The AudioContext is closed, cannot initialize the node.")}handleNodePortMessage(e){if("message"===e.data.type&&console.log(e.data.message),"peaks"===e.data.type){const{volumes:t,amplitudes:s}=e.data;for(let e=0;ee?this.tempPeaks[e]={volume:t[e],amplitude:s[e]}:this.tempPeaks[e]={volume:-1/0,amplitude:0};t.lengththis.heldPeaks[e].volume||s[e]>this.heldPeaks[e].amplitude)&&(this.heldPeaks[e]={volume:Math.max(this.heldPeaks[e].volume,t[e]),amplitude:Math.max(this.heldPeaks[e].amplitude,s[e])},this.peakHoldIntervals[e]&&clearInterval(this.peakHoldIntervals[e]),this.config.peakHoldDuration&&(this.peakHoldIntervals[e]=window.setInterval((()=>this.clearPeak(e)),this.config.peakHoldDuration)));"immediate"===this.config.peakCallbackDelay&&this.peakCallback&&this.peakCallback(this.getNormalizedCurrentPeakVolume())}}clearPeak(e){this.heldPeaks[e]=this.tempPeaks[e]}clearPeaks(){for(let e=0;es(e))),maxesDB:this.heldPeaks.map((({volume:e})=>s(e)))}}getNormalizedCurrentPeakVolume(){let{volume:e,amplitude:t}=this.tempPeaks.reduce(((e,t)=>({volume:Math.max(e.volume,s(t.volume)),amplitude:Math.max(e.amplitude,t.amplitude)})),{volume:-1/0,amplitude:0});if(this.config.peakCallbackNormalizeVolume){const{dbRangeMin:t,dbRangeMax:s,normalizedRangeMin:a,normalizedRangeMax:i}=this.config.peakCallbackNormalizeVolume,o=i-a,l=s-t;e=Math.max(t,Math.min(s,e)),e=a+(e-t)*o/l}return{volume:e,amplitude:t}}cleanup(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=void 0),this.animationRequestId&&(cancelAnimationFrame(this.animationRequestId),this.animationRequestId=void 0);for(let e=0;e { } }); -const meterInstance = new webAudioPeakMeter.WebAudioPeakMeter(sourceNode); +const meterInstance = new webAudioPeakMeter.WebAudioPeakMeter(sourceNode, (volume) => { + console.log('Volume callback:', volume); +}, { + peakCallbackDelay: 'immediate', + peakHoldDuration: 5000, + peakCallbackNormalizeVolume: { + dbRangeMin: -48, + dbRangeMax: 0, + normalizedRangeMin: 0, + normalizedRangeMax: 100, + }, +}); clearPeaks.addEventListener('click', () => { meterInstance.clearPeaks(); @@ -42,8 +53,8 @@ const displayFloatArray = (arr) => arr.map((val) => val.toFixed(2)).join(', '); getPeaks.addEventListener('click', () => { const peaks = meterInstance.getPeaks(); - currentFloat.innerText = displayFloatArray(peaks.current); + currentFloat.innerText = displayFloatArray(peaks.current.map(({ volume }) => volume)); currentDB.innerText = displayFloatArray(peaks.currentDB); - maxesFloat.innerText = displayFloatArray(peaks.maxes); + maxesFloat.innerText = displayFloatArray(peaks.maxes.map(({ volume }) => volume)); maxesDB.innerText = displayFloatArray(peaks.maxesDB); }); diff --git a/src/config.ts b/src/config.ts index b9a5d87..1ba15b4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,4 @@ export interface PeakMeterConfig { - audioMeterStandard: string; peakHoldDuration: number; /** * When providing a callback, determine how often it should be called. @@ -20,7 +19,6 @@ export interface PeakMeterConfig { } export const defaultConfig: PeakMeterConfig = { - audioMeterStandard: 'peak-sample', peakHoldDuration: 0, peakCallbackDelay: 'immediate', }; diff --git a/src/index.ts b/src/index.ts index bd7f1c2..96190d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,19 @@ import { PeakMeterConfig, defaultConfig } from './config'; import { dbFromFloat } from './utils'; -import peakSampleProcessor from './peak-sample-processor.txt'; import truePeakProcessor from './true-peak-processor.txt'; +import { TruePeakProcessorMessage } from './true-peak-processor'; + +export type Peak = { + volume: number; + amplitude: number; +} export class WebAudioPeakMeter { channelCount: number; node?: AudioWorkletNode; config: PeakMeterConfig; - tempPeaks: Array; - heldPeaks: Array; + tempPeaks: Array; + heldPeaks: Array; peakHoldIntervals: Array; animationRequestId?: number; intervalId?: number; @@ -23,15 +28,17 @@ export class WebAudioPeakMeter { * peakCallbackNormalizeVolume.normalizedRangeMax). */ - private peakCallback?: (peakVolume: number) => void, + private peakCallback?: (peak: Peak) => void, options: Partial = {}, ) { this.config = Object.assign({ ...defaultConfig }, options); this.channelCount = srcNode.channelCount; - this.tempPeaks = new Array(this.channelCount).fill(0.0); - this.heldPeaks = new Array(this.channelCount).fill(0.0); - this.peakHoldIntervals = new Array(this.channelCount).fill(undefined); - this.initNode(); + this.tempPeaks = new Array(this.channelCount).fill({ volume: -Infinity, amplitude: 0.0 } satisfies Peak); + this.heldPeaks = new Array(this.channelCount).fill({ volume: -Infinity, amplitude: 0.0 } satisfies Peak); + this.peakHoldIntervals = new Array(this.channelCount).fill(undefined); + this.initNode().catch((err) => { + console.error('WebAudioPeakMeter: Failed to initialize the AudioWorkletNode.', err); + }); } private async initNode() { @@ -40,18 +47,15 @@ export class WebAudioPeakMeter { return; } - const { audioMeterStandard } = this.config; try { - this.node = new AudioWorkletNode(this.srcNode.context, `${audioMeterStandard}-processor`, { + this.node = new AudioWorkletNode(this.srcNode.context, `true-peak-processor`, { parameterData: {}, }); } catch (err) { - const workletString = - audioMeterStandard === 'true-peak' ? truePeakProcessor : peakSampleProcessor; - const blob = new Blob([workletString], { type: 'application/javascript' }); + const blob = new Blob([truePeakProcessor], { type: 'application/javascript' }); const objectURL = URL.createObjectURL(blob); await this.srcNode.context.audioWorklet.addModule(objectURL); - this.node = new AudioWorkletNode(this.srcNode.context, `${audioMeterStandard}-processor`, { + this.node = new AudioWorkletNode(this.srcNode.context, `true-peak-processor`, { parameterData: {}, }); } @@ -73,25 +77,28 @@ export class WebAudioPeakMeter { } } - private handleNodePortMessage(ev: MessageEvent) { + private handleNodePortMessage(ev: MessageEvent) { if (ev.data.type === 'message') { console.log(ev.data.message); } if (ev.data.type === 'peaks') { - const { peaks } = ev.data; + const { volumes, amplitudes } = ev.data; for (let i = 0; i < this.tempPeaks.length; i += 1) { - if (peaks.length > i) { - this.tempPeaks[i] = peaks[i]; + if (volumes.length > i) { + this.tempPeaks[i] = { volume: volumes[i], amplitude: amplitudes[i] }; } else { - this.tempPeaks[i] = 0.0; + this.tempPeaks[i] = { volume: -Infinity, amplitude: 0.0 }; } } - if (peaks.length < this.channelCount) { - this.tempPeaks.fill(0.0, peaks.length); + if (volumes.length < this.channelCount) { + this.tempPeaks.fill({ + volume: -Infinity, + amplitude: 0.0 + }, volumes.length); } - for (let i = 0; i < peaks.length; i += 1) { - if (peaks[i] > this.heldPeaks[i]) { - this.heldPeaks[i] = peaks[i]; + for (let i = 0; i < volumes.length; i += 1) { + if (volumes[i] > this.heldPeaks[i].volume || amplitudes[i] > this.heldPeaks[i].amplitude) { + this.heldPeaks[i] = { volume: Math.max(this.heldPeaks[i].volume, volumes[i]), amplitude: Math.max(this.heldPeaks[i].amplitude, amplitudes[i]) }; // Re-start the interval so that the new peak is held for 1 full duration. if (this.peakHoldIntervals[i]) { @@ -125,13 +132,19 @@ export class WebAudioPeakMeter { return { current: this.tempPeaks, maxes: this.heldPeaks, - currentDB: this.tempPeaks.map(dbFromFloat), - maxesDB: this.heldPeaks.map(dbFromFloat), + currentDB: this.tempPeaks.map(({ volume }) => dbFromFloat(volume)), + maxesDB: this.heldPeaks.map(({ volume }) => dbFromFloat(volume)), }; } - public getNormalizedCurrentPeakVolume() { - let volume = dbFromFloat(Math.max(...this.heldPeaks)); + public getNormalizedCurrentPeakVolume(): Peak { + // eslint-disable-next-line prefer-const + let { volume, amplitude } = this.tempPeaks.reduce((acc, peak) => { + return { + volume: Math.max(acc.volume, dbFromFloat(peak.volume)), + amplitude: Math.max(acc.amplitude, peak.amplitude), + }; + }, { volume: -Infinity, amplitude: 0.0 }); if (this.config.peakCallbackNormalizeVolume) { const { dbRangeMin, dbRangeMax, normalizedRangeMin, normalizedRangeMax } = this.config.peakCallbackNormalizeVolume; @@ -140,7 +153,7 @@ export class WebAudioPeakMeter { volume = Math.max(dbRangeMin, Math.min(dbRangeMax, volume)); volume = normalizedRangeMin + ((volume - dbRangeMin) * normalizedRange) / dbRange; // Linear mapping to normalized range } - return volume; + return { volume, amplitude }; } cleanup() { diff --git a/src/peak-sample-processor.ts b/src/peak-sample-processor.ts deleted file mode 100644 index 90915c1..0000000 --- a/src/peak-sample-processor.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { peakValues } from './peak-sample'; - -class PeakSampleProcessor extends AudioWorkletProcessor { - process(inputs:Float32Array[][]) { - const input = inputs[0]; - const maxes = peakValues(input); - this.port.postMessage({type: 'peaks', peaks: maxes}); - return true; - } -} - -try { - registerProcessor('peak-sample-processor', PeakSampleProcessor); -} catch (err) { - console.info('Failed to register peak-sample-processor. This probably means it was already registered.'); -} diff --git a/src/true-peak-processor.ts b/src/true-peak-processor.ts index 595167f..8c295fa 100644 --- a/src/true-peak-processor.ts +++ b/src/true-peak-processor.ts @@ -1,5 +1,15 @@ +import { peakValues } from './peak-sample'; import { calculateLPFCoefficients, truePeakValues } from './true-peak'; +export type TruePeakProcessorMessage = { + type: "message"; + message: number[][]; +} | { + type: "peaks"; + volumes: number[]; + amplitudes: number[]; +} + class TruePeakProcessor extends AudioWorkletProcessor { sampleRate: number; numCoefficients: number; @@ -26,14 +36,15 @@ class TruePeakProcessor extends AudioWorkletProcessor { if (input.length > this.lpfBuffers.length) { for (let i = 1; i <= input.length; i += 1) { if (i > this.lpfBuffers.length) { - this.lpfBuffers.push(new Array(this.numCoefficients).fill(0)); + this.lpfBuffers.push(new Array(this.numCoefficients).fill(0)); } } } - const maxes = truePeakValues(input, this.lpfBuffers, this.lpfCoefficients, this.upsampleFactor); - this.port.postMessage({type: 'peaks', peaks: maxes}); + const volumes = truePeakValues(input, this.lpfBuffers, this.lpfCoefficients, this.upsampleFactor); + const amplitudes = peakValues(input); + this.port.postMessage({type: 'peaks', volumes, amplitudes} satisfies TruePeakProcessorMessage); if (this.processCount % 100 === 0) { - this.port.postMessage({type: 'message', message: this.lpfBuffers}); + this.port.postMessage({type: 'message', message: this.lpfBuffers} satisfies TruePeakProcessorMessage); } this.processCount += 1; return true;