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