input.direction = 0; // min=0, max=1, step=1 (Up, Down)
input.synth = 0; // min=0, max=1, step=1 (Chime, Hum)
let n_loops = 15; // Reduce this if you computer overloads
let nmin = -24*2;
let nmax = 100;
// Sine with reccurrence formula
// see https://dittytoy.net/ditty/59300f01a0
const recurrentSine = synth.def(class {
constructor(options) {
const freq = midi_to_hz(options.note);
const omega = 2 * Math.PI * freq;
const gamma = 7/options.duration; // --> 60 dB decay in "duration"
const T = ditty.dt;
this.c1 = 2 * Math.exp(-gamma * T) * Math.cos(omega * T);
this.c2 = Math.exp(-2 * gamma * T);
// Initial conditions:
// s[-1] = exp(γT) sin(-ωT),
// s[0] = 0,
this.s_nm1 = Math.exp(gamma*T) * Math.sin(-omega*T);
this.s_n = 0;
}
process(phase,env,tick,options) {
const s_np1 = this.c1 * this.s_n - this.c2 * this.s_nm1;
this.s_nm1 = this.s_n;
this.s_n = s_np1;
return s_np1;
}
}, {env:one, duration:3, amp:0.1});
function softclip(x) {
return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x;
}
function varsaw(p, formant) {
let x = p-~~p;
return (x - 0.5) * softclip(formant*x*(1-x));
}
const hum = synth.def( (p,e) => varsaw(p, 5) * e.value);
function playScale(n0) {
let n = n0;
loop( () => {
let nm4 = ((n%4)+4)%4;
let nn = a4 + 2 * (Math.floor(n/4) + nm4);
let fn = midi_to_hz(nn);
let dn = 2**(-n/24);
let an = Math.min(dn**2, 0.3) * Math.exp(-0.5*(3-nm4));
if(input.synth == 0) {
recurrentSine.play(nn, {duration:2*dn, amp:an});
} else {
hum.play(nn, {amp:an, attack:dn*0.8, release:dn*0.2,curve:-2});
}
if(input.direction == 0) {
// Rising
if(n >= nmax) {
n = nmin;
} else {
n = n + 1;
}
} else {
// Falling
if(n <= nmin) {
n = nmax;
} else {
n = n - 1;
}
}
sleep(dn);
}, {pan:Math.random()*2-1});
}
for(let i=1; i>0; i-=1.00001/n_loops) {
let n = nmin - 24*Math.log(i)/Math.log(2);
n = ~~n;
playScale(n);
}