Simple demo of an AudioWorklet loaded via webpack4 and worklet-loader within a standard vuecli3 project. It will play either a preconfigured ogg file or use the local microphone. The gain of each channel (L and R) can be adjusted by the worklet.
NOTE: This will only work in browsers that support AudioWorklet, notably, Chrome and Opera.
vue.config.js
...
configureWebpack: {
module: {
rules: [
{
test: /Worklet.js/, <---- change this to match your filename conventions
loader: 'worklet-loader',
options: {
name: 'js/[hash].worklet.js'
}
}
]
}
}
...
App.vue - Creating Worklet processor (GainWorklet) and a Worklet Node (AudioWorkletNode)
import GainWorklet from './worklet/GainWorklet'
...
try {
await context.audioWorklet.addModule(GainWorklet)
gainWorkletNode = new AudioWorkletNode(context, 'gain-worklet')
} catch (error) {
// ...
}
The GainWorklet processor simply adjusts the gain of two input channels via two k-rate AudioParams.
GainWorklet.js
...
process(inputs, outputs, parameters) {
const input = inputs[0]
const output = outputs[0]
for (let channel = 0; channel < input.length; ++channel) {
const inputChannel = input[channel]
const outputChannel = output[channel]
// parameters contains our audioParams for each channel
let gain = parameters[`gainChannel_${channel}`]
for (let i = 0; i < inputChannel.length; ++i) outputChannel[i] = inputChannel[i] * gain[0]
}
return true
}
...
Adjusting the audio params is done in the main thread via the worklet parameters.
let gain = await this.gainWorkletNode.parameters.get('gainChannel_0')
gain.setValueAtTime(this.leftGain, this.audioContext.currentTime)
...
let gain = await this.gainWorkletNode.parameters.get('gainChannel_1')
gain.setValueAtTime(this.rightGain, this.audioContext.currentTime)
The Audio Graph you hear is simply SourceNode -> GainWorkletNode -> DestinationNode.
source.buffer = decodedAudioData
// Connect the source buffer node to the worklet
source.connect(gainWorkletNode)
// Connect the worklet to the destination output
gainWorkletNode.connect(context.destination)
The output of the GainWorklet is also routed to a ChannelSplitterNode to visualize each channel. Each channel splitter output is connected to an analyser node.
/ Create a splitter for the visualization
const splitter = context.createChannelSplitter(source.channelCount)
// Connect the worklet to the splitter
gainWorkletNode.connect(splitter)
// Add visualization that shows the gain for each channel
this.analysers = new Map()
for (let i = 0; i < source.channelCount; i++) {
let analyser = context.createAnalyser()
analyser.fftSize = 128
analyser.minDecibels = -70
analyser.maxDecibels = -25
analyser.smoothingTimeConstant = 0.8
this.analysers.set(i, analyser)
splitter.connect(analyser, i, 0)
}
// Start the animations
this.updateLevels()
The animation loop pulls the levels from the analysers and sends it to the meters. Normally, this would be drawn directly into a canvas and not use vue's bindings. This is just an example.
updateLevels() {
for (let [key, analyser] of this.analysers) {
let buffer = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(buffer)
let maxVal = 0
for (let i = 0; i < analyser.frequencyBinCount; i++) {
maxVal = Math.max(maxVal, buffer[i])
}
if (key === 0) {
this.rightChannelLevel = maxVal
} else {
this.leftChannelLevel = maxVal
}
}
this.animationLoopId = requestAnimationFrame(this.updateLevels)
},
- worklet-loader
- AudioWorklet Spec
- AudioWorklet Intro
- Web Audio Visualizations
- Web Audio API
- Drum Break Sample
yarn install
yarn run serve
yarn run build