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;
};