Skip to content

Instantly share code, notes, and snippets.

@hattorix
Last active June 13, 2023 05:30
Show Gist options
  • Save hattorix/f95352a780443ff1fc29441d9a490bf8 to your computer and use it in GitHub Desktop.
Save hattorix/f95352a780443ff1fc29441d9a490bf8 to your computer and use it in GitHub Desktop.
AudioWorklet で音量取得
https://g200kg.github.io/web-audio-api-ja/ のを TypeScript 化と少し機能を整理
export default class VUMeterNode extends AudioWorkletNode {
private _updateIntervalInMS: number
private _volume: number
constructor(context: BaseAudioContext, updateIntervalInMS: number = null) {
super(context, "vumeter", {
numberOfInputs: 1,
numberOfOutputs: 0,
channelCount: 1,
processorOptions: {
updateIntervalInMS: updateIntervalInMS || 16.67,
},
})
// States in AudioWorkletNode
this._updateIntervalInMS = updateIntervalInMS
this._volume = 0
// Handles updated values from AudioWorkletProcessor
this.port.onmessage = (event) => {
if (event.data.volume) {
this._volume = event.data.volume
}
}
this.port.start()
}
get updateInterval(): number {
return this._updateIntervalInMS
}
set updateInterval(updateIntervalInMS: number) {
this._updateIntervalInMS = updateIntervalInMS
this.port.postMessage({ updateIntervalInMS: updateIntervalInMS })
}
get volume(): number {
// Float の値を dB に変換します
return 20 * Math.log10(this._volume)
}
}
const SMOOTHING_FACTOR = 0.9
class VUMeterProcessor extends AudioWorkletProcessor {
private _volume: number
private _updateIntervalInMS: number
private _nextUpdateFrame: number
constructor(options) {
super()
this._volume = 0
this._updateIntervalInMS = options.processorOptions.updateIntervalInMS
this._nextUpdateFrame = this._updateIntervalInMS
this.port.onmessage = (event) => {
if (event.data.updateIntervalInMS) {
this._updateIntervalInMS = event.data.updateIntervalInMS
}
}
}
get intervalInFrames(): number {
return (this._updateIntervalInMS / 1000) * sampleRate
}
process(inputs: Float32Array[][]): boolean {
const input = inputs[0]
if (input.length > 0) {
const samples = input[0]
// 入力値の RMS レベルを取得
let rms = rmsLevel(samples)
// スムージング
this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR)
// 指定された更新間隔で音量をメインスレッドに通知
this._nextUpdateFrame -= samples.length
if (this._nextUpdateFrame < 0) {
this._nextUpdateFrame += this.intervalInFrames
this.port.postMessage({ volume: this._volume })
}
}
return true
}
}
function peakLevel(input: Float32Array): number {
let maxVal = 0.0
for (let i = 0; i < input.length; i++) {
const sm = Math.abs(input[i])
if (maxVal < sm) {
maxVal = sm
}
}
return maxVal
}
function rmsLevel(input: Float32Array): number {
let sum = 0.0
for (let i = 0; i < input.length; i += 1) {
sum += input[i] ** 2
}
return Math.sqrt(sum / input.length)
}
registerProcessor("vumeter", VUMeterProcessor)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment