/Oscillator

WebAPI Oscillator recreated using AudioWorkletProcessor

Primary LanguageJavaScriptGNU General Public License v3.0GPL-3.0

Oscillator recreated using AudioWorkletProcessor

Build as an extension of OscillatorNode which by default doesn't allow user to manipulate all its attributes. By using AudioWorkletProcessor one can use any of the AudioParam methods to change oscillator type or any other exposed parameter.

This Oscillator implements (in addition to the original node):

Exposed parameters

{
    name: "signalType",
    description: "Types of waveform. 
        0: triangle, 
        1: pulse, 
        2: sawtooth, 
        3: sine, 
        4: white noise, 
        5: pink noise, 
        6: brown noise",
    defaultValue: 1,
    minValue: 0,
    maxValue: 6,
},
{
    name: "frequency",
    description: "Number of waveform cycles per second [Hz]",
    defaultValue: 400,
    minValue: 0,
    maxValue: 0.5 * sampleRate,
},
{
    name: "phaseOffset",
    description: "Phase offset of the waveform",
    defaultValue: 0,
    minValue: 0,
    maxValue: 1,
},
{
    name: "vibrato",
    description: "Amount of pulsating change of pitch",
    defaultValue: 0,
    minValue: 0,
    maxValue: 1,
},
{
    name: "duty",
    description: "Duty cycle of the pulse wave. Not working with other waves",
    defaultValue: 0.5,
    minValue: 0,
    maxValue: 1,
},
{
    name: "sync",
    description: "Frequency to hard sync to in Hz",
    defaultValue: 0,
    minValue: 0,
},
{
    name: "amplitude",
    description: "Amplitude of generated waveform",
    defaultValue: 1,
    minValue: 0,
},

Hence this Oscillator expands the processor by adding a static parameterDescriptors getter all listed parameters can be accessed via parameters property of AudioWorkletNode like so:

const oscillator = new AudioWorkletNode(audioContext, "oscillator");
const type = oscillator.parameters.get("signalType");

Example

Let's create sound similar to acceleration and gear shifting in classical Amiga game like Crazy Cars 3.

Try it online! 🏎️💨

try {
    audioContext = new AudioContext();
} catch (e) {
    alert("The Web Audio API is not supported in this browser.");
}

audioContext.audioWorklet.addModule("./oscillator.js").then(() => {
    const oscillator = new AudioWorkletNode(audioContext, "oscillator");
    oscillator.connect(audioContext.destination);

    const type = oscillator.parameters.get("signalType");
    const sync = oscillator.parameters.get("sync");
    const duty = oscillator.parameters.get("duty");
    const vibrato = oscillator.parameters.get("vibrato");
    const amplitude = oscillator.parameters.get("amplitude");
    const phaseOffset = oscillator.parameters.get("phaseOffset");

    const time = audioContext.currentTime;

    // changing type from default (pulse), to sawtooth and then to triangle
    type.setValueAtTime(2, time + 0.5);
    type.setValueAtTime(0, time + 3);

    sync.linearRampToValueAtTime(440, time + 1);
    sync.linearRampToValueAtTime(0, time + 4);

    duty.linearRampToValueAtTime(1, time + 2);
    duty.linearRampToValueAtTime(0.2, time + 4);

    vibrato.exponentialRampToValueAtTime(0.8, time + 2);
    vibrato.linearRampToValueAtTime(0, time + 3);

    amplitude.setValueAtTime(0, time);
    amplitude.linearRampToValueAtTime(0.8, time + 2);
    amplitude.setValueAtTime(0.4, time + 2.5);
    amplitude.linearRampToValueAtTime(0.2, time + 4);

    phaseOffset.linearRampToValueAtTime(0.01, time + 1);
    phaseOffset.linearRampToValueAtTime(0, time + 1.5);

    setTimeout(() => audioContext.close(), 4000);
});

Technology & limitation

Currently AudioWorkletProcessor works fine in all major browers.

Thanks to Flarp for creating inital idea.