TWID #12: Exploring Web Audio
Synthesizing sounds in the browser with Faust und WebAssembly.
I have always been fascinated by the possibilities of synthesizing sounds directly in the browser, especially for interactive pieces. So, this week, I spent some time exploring ways to achieve this.
How to synthesize sound in the browser
When synthesizing audio in the browser, you will end up dealing with the WebAudio API which comes with a set of nodes to create oscillators, filters, and other components needed to produce sound.
However, the capabilities are somewhat limited, but there is a way around that using Audio Worklets and WebAssembly.
Audio Worklets allow you to supply custom audio processing scripts that execute in a separate thread to provide very low latency audio processing.
WebAssembly lets you run machine-level code in the browser. In our context, it can execute the code from the worklets, enabling high performance.
It's almost impossible to produce WebAssembly code by yourself, so there is usually a compiler involved that generates this code for you. RNBO is a tool that lets you compile patches from Max to WebAssembly and other targets. Faust is similar, but it's free and very flexible.
Using Faust
Faust is a programming language that lets you compile your code to multiple targets, including C++, WebAssembly, Rust, and others. Making it versatile and compatible with different platforms.
The best thing is, it has an online IDE, where you can write and compile the code directly in the browser.
Building a simple synth with faust
There are some good learning resources on the Faust website that helped me a lot to build something like this.
This is a simple synth with an oscillator that goes through a filter into a VCA that's controlled with an envelope. The oscillator is just a pulse wave with PWM and receives notes that are quantized to a particular scale. The filter is a resonant low-pass filter, and the envelope can be triggered automatically with a certain speed to create interesting sounds.
import("stdfaust.lib");
// Setup the osciallator
oscGroup(x) = vgroup("[0] Oscillaor", x);
freq = oscGroup(hslider("[0] Frequency", 440, 40, 2000, 0.01));
pwm = oscGroup(hslider("[1] PulseWidth", 2, 0, 10, 0.01));
quantizer = freq : qu.quantize(440, qu.ionian);
pw = os.lf_sawpos(pwm);
osc = os.pulsetrain(quantizer, pw);
// Setup the filter
filterGroup(x) = vgroup("[1] Filter", x);
cut = filterGroup(hslider("[2] Cutoff", 500, 50, 10000, 0.01));
res = filterGroup(hslider("[3] Resonance", 5, 1, 30, 0.1));
filter = fi.resonlp(cut, res, 1);
// Setup the envelope
envelopeGroup(x) = vgroup("[3] Envelope", x);
attack = envelopeGroup(hslider("Attack", 0.1, 0.01, 4, 0.01));
release = envelopeGroup(hslider("Release", 1, 0.01, 4, 0.01));
env = en.ar(attack / 10, release / 10, trigger);
// Set up the gate
gateGroup(x) = vgroup("[5] Gate", x);
mode = gateGroup(checkbox("Cycle"));
gate = gateGroup(button("Gate"));
speed = gateGroup(hslider("Speed", 2, 1, 40, 1));
repeat = ba.pulse(48000 / speed);
trigger = select2((mode == 1), gate, repeat);
// Setup the vca
gain = hslider("[6] Gain", 0.2, 0, 1, 0.01);
vca = env * gain;
process = osc : filter * vca <: _,_;You can run this code and it should produce some sounds.
Conclusion
In my opinion, Faust is a nice tool to build sound pieces since it is very modular in its ways of working and lets you compile things to all kinds of platforms.
Other Things I did this week
Animations
Drawing
That’s it for this week. Hope you liked it.






