• const pi = Math.PI; // PI const opi = 1.0 / Math.PI; // 1/PI const tau = 2.0 * Math.PI; // 2*PI const fs = 44100.0; // Sampling frequency const fc = 440.0; // Ordinary frequency const omega = tau * fc / fs; // Angular frequency const z1 = .998; // Lowpass filter pole let mainOut = 0.0; let delayOut = 0.0; let delay = Delay(16384); function process(a) { // Loop through sample buffer for (var t = 0; t < a.length; ++t) { // Filter delay controller changes fxa.n = lowpass(fxa.val, fxa.n, z1); // Delay amplitude fxb.n = lowpass(fxb.val * 0.99 + 0.01, fxb.n, z1); // Delay feedback fxc.n = lowpass(fxc.val * 16000 + 383, fxc.n, z1); // Delay time // Filter oscillator harmonics controller changes vca.n = lowpass(vca.val.y * cca3.val, vca.n, z1); // Oscillator harmonics ctrl A vcb.n = lowpass(vcb.val.y * ccb3.val, vcb.n, z1); // Oscillator harmonics ctrl B vcc.n = lowpass(vcc.val.y * ccc3.val, vcc.n, z1); // Oscillator harmonics ctrl C // Calculate oscillator phases vca.theta += vca.step = lowpass(vca.val.x * cca2.val, vca.step, z1); // Frequency ctrl A vcb.theta += vcb.step = lowpass(vcb.val.x * ccb2.val, vcb.step, z1); // Frequency ctrl B vcc.theta += vcc.step = lowpass(vcc.val.x * ccc2.val, vcc.step, z1); // Frequency ctrl C // Example of oscillator "drift" //vca.theta *= 1.00000025; // Feedback matrix vca.fb = cca4.val * vcc.out + cca5.val * vcb.out; // Feedback A vcb.fb = ccb4.val * vca.out + ccb5.val * vcc.out; // Feedback B vcc.fb = ccc4.val * vcb.out + ccc5.val * vca.out; // Feedback C // Waveform output vca.out = square(vca.theta * omega, vca.n + vca.fb); // Out A vcb.out = sawtooth(vcb.theta * omega, vcb.n + vcb.fb); // Out B vcc.out = triangle(vcc.theta * omega, vcc.n + vcc.fb); // Out C // For a simple sine wave: // vcc.out = Math.sin(vcc.theta * omega); // Filter oscillator amplitude controller changes vca.amp = lowpass(cca1.val, vca.amp, z1); // Volume ctrl A vcb.amp = lowpass(ccb1.val, vcb.amp, z1); // Volume ctrl B vcc.amp = lowpass(ccc1.val, vcc.amp, z1); // Volume ctrl C // Main output (dry) mainOut = 0.3333 * (vca.amp * vca.out + vcb.amp * vcb.out + vcc.amp * vcc.out); // Delay output (wet) delayOut = fxa.n * delay.feedback(fxb.n).delay(fxc.n).run(mainOut); // Fill buffer at time=t a[t] = 0.5 * (mainOut + delayOut); } // Return buffer return a; } // Single LPF, suitable for controller changes function lowpass(x, n, z) { return x + (n - x) * z; } // Kirby's Bandlimited Square square = function (x, k) { const c = Math.cos(x); const v = 12.0 * k * c; const s0 = digamma(0.75 - v); const s1 = digamma(0.25 - v); const s = s0 - s1; const p = Math.cos(tau * v) * s; return p * opi - 1.0; }; // Kirby's Bandlimited Saw sawtooth = function (x, k) { return Math.sin(x) * square(x, k); }; // Kirby's Bandlimited Triangle? triangle = function (x, k) { return sawtooth(x, k) * square(x, k); } // Asymptotic expansion of the Digamma function // https://en.wikipedia.org/wiki/Digamma_function#Asymptotic_expansion // Todo: use reflection formula for values less than 1/2? digamma = function (a) { for (var b = 0; 12.0 > a; a++) { b -= 1.0 / a; } return b += Math.log(a) - .5 / a, a *= a, b - ( .08333333333333333 - ( .008333333333333333 - ( .0039682539682539 - ( .004166666666666 - 1 / ( 132 * a)) / a) / a) / a) / a; }; // Delay (via opendsp) function Delay(size) { if (!(this instanceof Delay)) return new Delay(size); size = size || 16384; this.buffer = new Float32Array(size); this.size = size; this.counter = 0; this._feedback = 0.5; this._delay = 16384; } Delay.prototype.feedback = function (n) { this._feedback = n; return this; }; Delay.prototype.delay = function (n) { this._delay = n; return this; }; Delay.prototype.run = function (inp) { var back = this.counter - this._delay; if (back < 0) back = this.size + back; var index0 = Math.floor(back); var index_1 = index0 - 1; var index1 = index0 + 1; var index2 = index0 + 2; if (index_1 < 0) index_1 = this.size - 1; if (index1 >= this.size) index1 = 0; if (index2 >= this.size) index2 = 0; var y_1 = this.buffer[index_1]; var y0 = this.buffer[index0]; var y1 = this.buffer[index1]; var y2 = this.buffer[index2]; var x = back - index0; var c0 = y0; var c1 = 0.5 * (y1 - y_1); var c2 = y_1 - 2.5 * y0 + 2.0 * y1 - 0.5 * y2; var c3 = 0.5 * (y2 - y_1) + 1.5 * (y0 - y1); var out = ((c3 * x + c2) * x + c1) * x + c0; this.buffer[this.counter] = inp + out * this._feedback; this.counter++; if (this.counter >= this.size) this.counter = 0; return out; };