Rainy Night

Close your eyes and listen to the rain...

Log in to post a comment.

// ========== Audio EQ filters ========== see https://dittytoy.net/ditty/b0737467eb
const cos = Math.cos, sin = Math.sin, PI = Math.PI, sqrt = Math.sqrt;
// Second-order section in Transposed Direct Form II
// https://ccrma.stanford.edu/~jos/filters/Transposed_Direct_Forms.html
class TDF2 {
    constructor(a1,a2,b0,b1,b2) {
        this.set_coefs(a1,a2,b0,b1,b2);
        this.s1 = 0; this.s2 = 0;
    }
    set_coefs(a1,a2,b0,b1,b2){
        this.a1 = a1; this.a2 = a2; this.b0 = b0; this.b1 = b1; this.b2 = b2;
    }
    process(xn) {
        var yn  = this.s1                + this.b0 * xn;
        this.s1 = this.s2 - this.a1 * yn + this.b1 * xn;
        this.s2 =         - this.a2 * yn + this.b2 * xn;
        return yn;
    }
}
// Biquad filter coefficients from the Audio EQ Cookbook
// https://www.w3.org/TR/audio-eq-cookbook/
function calc_biquad_coefs(opt) {
    var w0 = 2*PI*opt.f0*ditty.dt; var cw0 = cos(w0); var alpha = sin(w0)/(2*opt.Q);
    if(opt.type == "LPF") { // Low pass filter
        var b0 =  (1 - cw0)/2, b1 =   1 - cw0, b2 =  (1 - cw0)/2, a0 =   1 + alpha, a1 =  -2*cw0, a2 =   1 - alpha;
    } else if (opt.type == "HPF") { // High pass filter
        var b0 =  (1 + cw0)/2, b1 =  -1 - cw0, b2 =  (1 + cw0)/2, a0 =   1 + alpha, a1 =  -2*cw0, a2 =   1 - alpha;
    } else if(opt.type == "resonance") { // Band pass filter (constant skirt gain, peak gain = Q)
        var b0 =  opt.Q*alpha, b1 =  0, b2 = -opt.Q*alpha, a0 =   1 + alpha, a1 =  -2*cw0, a2 =   1 - alpha;
    } else if(opt.type == "BPF") { // Band pass filter (constant 0 dB peak gain)
        var b0 = alpha, b1 =  0, b2 = -alpha, a0 =   1 + alpha, a1 =  -2*cw0, a2 = 1 - alpha;
    } else if(opt.type == "notch") { // Notch filter (removes frequency f0)
        var b0 = 1, b1 = -2*cw0, b2 = 1, a0 =   1 + alpha, a1 =  -2*cw0, a2 = 1 - alpha;
    } else if(opt.type == "APF") { // All pass filter (constant gain, phase shift around f0)
        var b0 = 1 - alpha, b1 = -2*cw0, b2 = 1 + alpha, a0 =   1 + alpha, a1 =  -2*cw0, a2 = 1 - alpha;
    } else if(opt.type == "peak") { // Peaking EQ (requires dBgain)
        var A = 10**(opt.dBgain/40);
        var b0 = 1 + alpha*A, b1 = -2*cw0, b2 = 1 - alpha*A, a0 =   1 + alpha/A, a1 =  -2*cw0, a2 = 1 - alpha/A;
    } else if(opt.type == "lowShelf") { // Low shelf (requires dBgain)
        var A = 10**(opt.dBgain/40);
        var b0 = A*( (A+1) - (A-1)*cw0 + 2*sqrt(A)*alpha ), b1 =  2*A*( (A-1) - (A+1)*cw0), b2 = A*( (A+1) - (A-1)*cw0 - 2*sqrt(A)*alpha ),
            a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha,   a1 =   -2*( (A-1) + (A+1)*cw0), a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha;
    } else if(opt.type == "highShelf") { // High shelf (requires dBgain)
        var A = 10**(opt.dBgain/40);
        var b0 =    A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ),
            b1 = -2*A*( (A-1) + (A+1)*cos(w0)                   ),
            b2 =    A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ),
            a0 =        (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha,
            a1 =    2*( (A-1) - (A+1)*cos(w0)                   ),
            a2 =        (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha;
    } else {
        debug.error("Unknown filter type");
    }
    return [a1/a0, a2/a0, b0/a0, b1/a0, b2/a0];
}



// =================== echo filter by srtuss =================== 
// https://dittytoy.net/ditty/cef878f9e1
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    process(input) {
        this.lastOut = this.data[this.p];
        this.data[this.p] = input;
        if(++this.p >= this.n)
            this.p = 0;
        return this.lastOut;
    }
    tap(offset) {
        var x = this.p - offset - 1;
        x %= this.n;
        if(x < 0)
            x += this.n;
        return this.data[x];
    }
}
const echo = filter.def(class {
    constructor(opt) {
        this.lastOut = [0, 0];
        var division = opt.division || 3/4;
        var pan = clamp01((opt.pan || 0)*.5+.5);
        var sidetime = (opt.sidetime || 0) / ditty.dt;
        var time = 60 * division / ditty.bpm;
        this.fb = clamp(opt.feedback || 0, -1, 1);
        this.kl = 1-pan;
        this.kr = pan;
        this.wet = opt.wet || .5;
        this.stereo = isFinite(opt.stereo) ? opt.stereo : 1;
        var n = ~~(time / ditty.dt);
        this.delay = [new Delayline(n), new Delayline(n)];
        this.dside = new Delayline(~~sidetime);
    }
    process(inv, opt) {
        this.dside.process(inv[0]);
        var l = this.dside.lastOut * this.kl;
        var r = inv[1] * this.kr;
        var nextl = l + this.delay[1].lastOut * this.fb;
        var nextr = r + this.delay[0].lastOut * this.fb;
        this.lastOut[0] = inv[0] + this.delay[0].lastOut * this.wet;
        this.lastOut[1] = inv[1] + this.delay[1].lastOut * this.wet;
        this.delay[0].process(nextl);
        this.delay[1].process(nextr);
        if(this.stereo != 1) {
            var m = (this.lastOut[0] + this.lastOut[1])*.5;
            var s = (this.lastOut[0] - this.lastOut[1])*.5;
            s *= this.stereo;
            this.lastOut[0] = m+s;
            this.lastOut[1] = m-s;
        }
        return this.lastOut;
    }
}, {sidetime: .01, division: 1/2, pan: .5, wet: .5, feedback: .6, stereo: 2});


// =================================================
// ================== RAINY NIGHT ==================
// =================================================
//
// Synth code by athibaul
// https://dittytoy.net/ditty/ea22c668ca

const rand = (a,b) => a + (b-a) * Math.random();
const xrand = (a,b) => a * (b/a)**Math.random();


const aaSaw = (p, dp) => (2 * p - 1) * clamp01(p * (1-p)/dp); // Basic anti-aliasing
//const aaSaw = (p, dp) => (2 * p - 1); // without anti-aliasing
const bpfSaw = synth.def(class {
    constructor(opt) {
        this.freq = midi_to_hz(opt.note);
        var coefs = calc_biquad_coefs({type:"resonance", f0:opt.cf, Q:opt.Q});
        this.filt = new TDF2(...coefs);
        this.p = opt.p0 || 0;
        this.dp = ditty.dt * this.freq;
        this.shimt = 0;
    }
    process(note, env, tick, opt) {
        this.shimt -= ditty.dt * opt.shimFreq;
        if(this.shimt <= 0) {
            var freq = this.freq * (1 + 0.06*rand(-1,1)*opt.shimAmp);
            this.dp = ditty.dt * freq;
            this.shimt = 1;
        }
        this.p += this.dp; this.p -= ~~this.p;
        return this.filt.process(aaSaw(this.p, this.dp)) * env.value * 0.05;
    }
}, {attack:0.01, release:0.3, cf:2500, Q:3, shimFreq:10, shimAmp:0});


/*
loop((i) => {
    // Rhythm
    sleep(1);
    bpfSaw.play(hz_to_midi(2), {cf:60, Q:5, env:one}); // bass drum
    bpfSaw.play(hz_to_midi(2), {cf:8000, Q:1, env:one}); // BD click
    bpfSaw.play(hz_to_midi(8), {cf:1000, Q:15, env:one, amp:0.2});
    bpfSaw.play(hz_to_midi(2), {cf:2500, Q:30, env:one, amp:0.2, p0:-1/2});
    bpfSaw.play(hz_to_midi(1), {cf:1800, Q:10, env:one, amp:0.15, p0:-3/8});
    bpfSaw.play(hz_to_midi(4), {cf:4623, Q:15, env:one, amp:0.15});
    sleep(1);
    bpfSaw.play(hz_to_midi(1), {cf:210, Q:5, env:one}); // snare
    sleep(1000);
}, {amp:3});
*/

loop((i) => {
    sleep(4);
    bpfSaw.play(hz_to_midi(1/16), {cf:38.3, Q:200, env:one, amp:xrand(1,2)});
    bpfSaw.play(hz_to_midi(1/16), {cf:55.2, Q:40, env:one, amp:xrand(1,2)});
    sleep(1000);
}, {name:"bass drum"})
.connect(echo.create());

loop( (i) => {
    var nn = 60 + ~~((12/5) * ~~(15*Math.random()));
    var freq = xrand(0.5,6);
    bpfSaw.play(hz_to_midi(freq), {cf:midi_to_hz(nn), attack:5, release:5, Q:xrand(10,700), amp:0.5, pan:rand(-1,1),
        shimFreq:xrand(0.01,1)*freq, shimAmp:6
    });
    sleep(1.5);
}, {name:"drops"})
.connect(echo.create());


loop( (i) => {
    var nn = 48 + ~~((12/5) * ~~(15*Math.random()));
    bpfSaw.play(nn, {attack:0.2, release:xrand(0.5,5), cf:xrand(500,3000), Q:xrand(2,10), pan:rand(-0.5,0.5)});
    sleep(xrand(0.5,15));
}, {name:"voice", amp:0.5})
.connect(echo.create());

const bnn = [36,29,36,38,31,33];
const chordsnn = [[48,55,62,69,76],[53,57,60,65,67,69,72], [48,55,60,64,66,67,71,74], 
                  [50,53,62,64,65,67,69], [53,57,60,64,65,68,72], [53,55,57,60,61,62]];

loop( (i) => {
    for(nn of bnn) {
        bpfSaw.play(nn, {attack:5, duration:32, release:5, cf:60, Q:0.1, amp:xrand(3,6), shimAmp:0.05});
        bpfSaw.play(nn+12.05, {attack:5, duration:32, release:5, cf:100, Q:0.1, amp:xrand(3,6), shimAmp:0.05});
        sleep(32);
    }
}, {name:"bass", amp:0.8})
.connect(echo.create());

loop( (i) => {
    for(chord of chordsnn) {
        for(nn of chord) {
            for(j of [1,2,3]) {
                bpfSaw.play(nn, {attack:rand(2,8), duration:32, release:rand(2,8), cf:xrand(1500,3000), Q:xrand(5,10), amp:xrand(0.2,0.5),
                    shimFreq:xrand(2,20), shimAmp:0.1, pan:rand(-1,1)
                });
            }
        }
        sleep(32);
    }
}, {name:"pad", amp:0.3})
.connect(echo.create());