├── README.md └── worklet.js /README.md: -------------------------------------------------------------------------------- 1 | # better-oscillator 2 | 3 | Improved oscillator for the WebAudio API using the new Audio Worklet API. It is currently only usable in Chrome. 4 | 5 | Exposes the following parameters: 6 | * `frequency`: 7 | * Min: 1 8 | * Max: Infinity 9 | * Default: 440 10 | * Description: Frequency at which the oscillator will vibrate at. 11 | * `wave`: 12 | * Min: 0 13 | * Max: 4 14 | * Default: 3 15 | * Description: Sets the waveform of the oscillator. If the number is not an integer, it will be rounded down. 16 | * 0 - triangle 17 | * 1 - pulse 18 | * 2 - sawtooth 19 | * 3 - sine 20 | * 4 - noise 21 | * `phase`: 22 | * Min: 0 23 | * Max: Infinity 24 | * Default: 0 25 | * Description: Controls the phase offset of the waveform. 26 | * `sync`: 27 | * Min: 0 28 | * Max: Infinity 29 | * Default: 0 30 | * Description: Sets the frequency that the oscillator will [hard sync](https://en.wikipedia.org/wiki/Oscillator_sync#Hard_Sync) to. 31 | * `duty`: 32 | * Min: 0 33 | * Max: 1 34 | * Default: 0.5 35 | * Description: Controls the duty cycle of the current oscillator **ONLY** if it is a pulsewave. 36 | -------------------------------------------------------------------------------- /worklet.js: -------------------------------------------------------------------------------- 1 | const saw = v => v - Math.floor(v); 2 | 3 | class BetterOscillatorProcessor extends AudioWorkletProcessor { 4 | constructor() { 5 | super(); 6 | this.phase = 0; 7 | this.sync_phase = 0; 8 | this.prev_sync_phase = 0 9 | } 10 | static get parameterDescriptors() { 11 | return [ 12 | { 13 | name: "phase", 14 | defaultValue: 0, 15 | max: 1, 16 | min: 0 17 | }, 18 | { 19 | name: "duty", 20 | defaultValue: 0.5, 21 | min: 0, 22 | max: 1 23 | }, 24 | { 25 | name: "frequency", 26 | defaultValue: 440, 27 | min: Number.EPSILON 28 | }, 29 | { 30 | name: "wave", 31 | defaultValue: 3, 32 | min: 0, 33 | max: 3 34 | }, 35 | { 36 | name: "sync", 37 | defaultValue: 0, 38 | min: 0, 39 | } 40 | ]; 41 | } 42 | process(input, outputs, params) { 43 | for (let z = 0; z < outputs.length; z++) { 44 | const out = outputs[z][0]; 45 | const outlen = out.length; 46 | const freq = params.frequency.length === 1; 47 | const phase = params.phase.length === 1; 48 | const wave = params.wave.length === 1; 49 | const duty = params.duty.length === 1; 50 | const sync = params.sync.length === 1 51 | const inp = input[0][0].length === 0; 52 | let back = 0 53 | for (let x = 0; x < outlen; x++) { 54 | this.sync_phase = (this.prev_sync_phase) % (params.sync[sync ? 0 : x]/sampleRate) 55 | if (params.sync[sync ? 0 : x] !== 0 && this.prev_sync_phase >= (params.sync[sync ? 0 : x]/sampleRate)) { 56 | this.phase = 0 57 | back = x 58 | 59 | } 60 | this.prev_sync_phase = this.sync_phase 61 | const main = (params.frequency[freq ? 0 : x] * (x-back)) / sampleRate; 62 | // noise 63 | if (params.wave[wave ? 0 : x] >= 4) { 64 | out[x] = (Math.random() * 2) - 1 65 | } else if (params.wave[wave ? 0 : x] >= 3) { 66 | // sine wave made using bulit-in Math.sin 67 | out[x] = Math.sin( 68 | (main + this.phase + params.phase[phase ? 0 : x]) * 2 * Math.PI 69 | ); 70 | // sawtooth wave using linear piecewise floor 71 | } else if (params.wave[wave ? 0 : x] >= 2) { 72 | out[x] = 2 * saw(main + this.phase + params.phase[phase ? 0 : x]) - 1; 73 | // pulse wave using difference of phase shifted saws and variable DC threshold 74 | } else if (params.wave[wave ? 0 : x] >= 1) { 75 | const temp = main + this.phase + params.phase[phase ? 0 : x]; 76 | out[x] = (saw(temp) - saw(temp + params.duty[duty ? 0 : x])) > 0 ? 1 : -1 77 | // triangle wave using absolute value of amplitude shifted sawtooth wave 78 | } else if (params.wave[wave ? 0 : x] >= 0) { 79 | out[x] = 80 | 4 * 81 | Math.abs( 82 | saw(main + this.phase + params.phase[phase ? 0 : x]) - 1 / 2 83 | ) - 1; 84 | } 85 | this.prev_sync_phase += 1 / sampleRate; 86 | } 87 | this.phase += 88 | (params.frequency[freq ? 0 : outlen - 1] * outlen) / sampleRate; 89 | this.phase %= sampleRate; 90 | return true; 91 | } 92 | } 93 | } 94 | 95 | registerProcessor("better-oscillator", BetterOscillatorProcessor); 96 | --------------------------------------------------------------------------------