Shepard scale

An ever-rising, ever-accelerating scale.

Log in to post a comment.

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