Monogon

💻🎶

Log in to post a comment.

// srtuss, 2024
// procedural music

ditty.bpm = 140;
// athibaul's reverb filter with added modulation
class Delayline { constructor(n) { this.n = n; this.p = 0; this.data = new Float32Array(n); } get end() { return this.data[this.p]; } tap(offset) { var x = this.p + offset; x = ~~x; x %= this.n; return this.data[x]; } sample(offset) { if(offset < 0) offset = 0; var p = offset; var pi = ~~p; return lerp(this.tap(pi), this.tap(pi+1), p-pi); } clock(input) { var end = this.data[this.p]; this.data[this.p] = input; if(++this.p >= this.n) { this.p = 0; } return end; } }  const ISQRT2 = Math.sqrt(0.5); const SQRT2 = Math.sqrt(2); const SQRT8 = Math.sqrt(8); const ISQRT8 = 1/SQRT8;  const fibodelays = [1467,1691,1932,2138,2286,2567,3141,3897]; const fiboshort = [465, 537, 617, 698, 742, 802, 963, 1215]; let meandelay = fibodelays[3] * ditty.dt;
const reverb = filter.def(class { constructor(options) { this.outgain = 0.3; this.predl = [new Delayline(2), new Delayline(2)]; this.dls = []; this.s0 = new Float32Array(8); for(let i=0; i<8; i++) { this.dls.push(new Delayline(fibodelays[i])); this.dls[i].i = i; this.s0[i] = 0; } this.a0 = clamp01(2*Math.PI*options.cutoff*ditty.dt);  this.dls_nr = []; for(let i=0; i<4; i++) { this.dls_nr.push(new Delayline(fiboshort[i*2])); } } process(inpt, options) {  let s0 = this.predl[0].clock(inpt[0]), s1 = this.predl[1].clock(inpt[1]); for(let i=0; i<4; i++) { let u0 = 0.6*s0 + 0.8*s1, u1 = 0.8*s0 - 0.6*s1; let u1p = this.dls_nr[i].end; this.dls_nr[i].clock(u1); s0 = u0; s1 = u1p; } let v0 = (s0 + s1); let v1 = (s0 - s1);     let rt60 = options.rt60; let loopgain = 10 ** (-3*meandelay / rt60) * ISQRT8; let higain = 10 ** (-3*meandelay / options.rtHi) * ISQRT8; let mod = 10; let v = this.dls.map( (dl) => dl.sample((1 + Math.sin(ditty.tick * (3 + dl.i * 2))) * mod));   let w = [v[0]+v[4], v[1]+v[5], v[2]+v[6], v[3]+v[7], v[0]-v[4], v[1]-v[5], v[2]-v[6], v[3]-v[7]]; let x = [w[0]+w[2], w[1]+w[3], w[0]-w[2], w[1]-w[3], w[4]+w[6], w[5]+w[7], w[4]-w[6], w[5]-w[7]]; let y = [x[0]+x[1], x[0]-x[1], x[2]+x[3], x[2]-x[3], x[4]+x[5], x[4]-x[5], x[6]+x[7], x[6]-x[7]]; y[0] += v0; y[2] += v1; for(let i=0; i<8; i++) { let hipass = y[i] - this.s0[i]; let lopass = this.s0[i]; this.dls[i].clock(lopass * loopgain + hipass * higain); this.s0[i] += this.a0 * hipass; }  return [lerp(inpt[0], v[0], options.mix), lerp(inpt[1], v[2], options.mix)]; } }, {mix:1, rt60:10, cutoff:20000, rtHi:0.9});

// useful waveforms...
const TAU = Math.PI * 2;
const wave0 = p => Math.sin(p * TAU);
const wave1 = p => Math.cos(clamp((p+10) % 1, 0, .5) * TAU * 2) - .5;
const sy2 = synth.def((p,e,t,o) => wave1(p+(o.phase||0)) * e.value);
const sy = synth.def((p,e,t,o) => wave0(wave1(p+(o.phase||0)) * (.8+Math.sin(ditty.time)*.2)) * e.value);
const rand = () => Math.random()*2-1;
const wrap01 = p => p < 0 ? p % 1 + 1 : p % 1;
function softclip(x) {
    return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x;
}
function varsaw(p, formant) {
    let x = wrap01(p);
    return (x - 0.5) * softclip(formant*x*(1-x));
}
let saw = synth.def((p,e,t,o) => varsaw(p+(o.pha||0), ditty.sampleRate / midi_to_hz(o.note)) * e.value);

// filters...
let comb = filter.def(class {
    constructor() {
        this.d = new Delayline(100);
        this.d2 = new Delayline(100);
    }
    process(inp) {
        let po = (Math.sin(ditty.tick * 0.2) * .5 + .5) * 80;
        let u = inp[0] - this.d.sample(po) * .3;
        let v = inp[1] - this.d2.sample(po) * .3;
        this.d.clock(u);
        this.d2.clock(v);
        return [u, v];
    }
});
class SVF {
    constructor(opt)
    {
        this.lp=this.bp=this.hp=this.ic1eq=this.ic2eq=0;
    }
    process(v0, fc, q)
    {
        let s = this;
        fc = clamp(fc, 0, .499);
        let g = Math.tan(Math.PI * fc);
        let k = 1 / q;
        s.a1 = 1 / (1 + g * (g + k));
        s.a2 = g * s.a1;
        s.a3 = g * s.a2;
        let v1, v2, v3;
        v3 = v0 - s.ic2eq;
        v1 = s.a1 * s.ic1eq + s.a2 * v3;
        v2 = s.ic2eq + s.a2 * s.ic1eq + s.a3 * v3;
        s.ic1eq = 2 * v1 - s.ic1eq;
        s.ic2eq = 2 * v2 - s.ic2eq;
        s.lp = v2;
        s.bp = v1;
        s.hp = v0 - k * v1 - v2;
        return s;
    }
}
class SVF24 {
    constructor() {
        this.a = new SVF();
        this.b = new SVF();
    }
    process(v0, mode, fc, q) {
        return this.b.process(this.a.process(v0, fc, q)[mode], fc, q)[mode];
    }
}

let eq = filter.def(class {
    constructor() {
        this.l = new SVF24();
        this.r = new SVF24();
    }
    process(inp, opt) {
        return [this.l.process(inp[0], 'hp', opt.fc, 1),
         this.r.process(inp[1], 'hp', opt.fc, 1)];
    }
})

// song arrangement...
let soff = 4*56*0+4*40*0;
function songc(start, end) {
    let x = Math.max((ditty.tick+soff) / 4 - start, 0);
    if(x >= end - start)
        x = 0;
    return x;
}
function songr(start, end) {
    let x = Math.max((ditty.tick + soff) / 4 - start, 0) / (end-start);
    if(x >= 1)
        x = 0;
    return x;
}
function song(start, end) {
    let x = (ditty.tick + soff) / 4 - start;
    return x >= 0 && x < end - start;
}

let scal = scale(a4, scales['minor'], 6);

loop( (lc) => {
    let chords = [[0, 2, 4, 6, 3, 2, 7]];
    var tp = -5 + ((Math.ceil(songc(23.99, 48)/2) * 7) % 12);
    let rt = 0;//(lc*3) % 7;
    chords.forEach(chord => {
        // bass
        for(let i = 0; i < 3; ++i) {
            let x = (i&1?1:-1);
            if(song(12, 48)+song(56, 64))
                sy2.play(scal[rt + chord[0]] + tp - 36 + i * .06, {duration: 7, attack: .1, release: 1, amp: .2, phase:Math.random(), pan: x*.2});
        }
        // chip-arp
        if(lc % 4 == 0 && lc)
            saw.play((x,y)=>scal[rt + chord[(~~(x*16))%7]] + tp, {duration: 1, release: 1.5, amp: .2});
        // lead
        if(song(40, 52)) {
            let x = (ditty.tick+soff)>>3;
            let xx = (x*(x+1)*3)%7;
            let bend = [12,-12].choose();
            sy2.play((x,y)=>scal[rt + xx] + tp + Math.sin(x*15)*clamp(x-.4,0,.5) + clamp(1-x*2,0,1)*bend, {attack: .2, duration: 8, amp: .2});
        }
        let div = 32;
        for(let i = 0; i < div; ++i) {
            // chord pulses
            if(song(48, 64)) {
                if([1,1,1,1,0,0,1,1][i%8]) {
                    for(let j = 0; j < 4; j+=2) {
                        sine.play(scal[(rt + chord[j%chord.length])] + tp + 24, {attack: .003, release: .04, amp: .3});
                    }
                }
            }
            // synth bass
            if(song(8, 48))
                sy.play(scal[rt + chord[0]] + tp - 24 + ((0xA4 >> (i%8)) & 1) * 12, {duration: 0, attack: 0.0001, release: .1, amp: .5, pan: Math.random() - .5});
            let n = scal[rt + chord[i % chord.length] + ((i >> 3)&1) * 0] + tp;
            // synth arp
            if(song(0, 72))
                sy2.play(n, {duration: 0, attack: 0.0001, release: .1, amp: .5, pan: rand()*.5});
            sleep(8/div);
        }
    });
}, { name: 'synth', amp: 1.3 })
    .connect(reverb.create({mix: .4}))
    .connect(eq.create({fc: ()=>20 * 2**(songr(14, 16)*.7*10)*ditty.dt}))
    .connect(comb.create());

let hihat = synth.def((p, e, t, o) => rand() * e.value, {release: .02, amp: .15});

loop( (lc) => {
    for(let i = 0; i < 4; ++i) {
        if(song(16, 40)+song(56, 64))
            hihat.play(c4, {release: .01 + Math.random() * .03, pan: rand()*.5});
        sleep(.25);
    }
}, { name: 'hat', amp: 1.3 });

let kick = synth.def((p, e, t, o) => wave0(p+.2) * e.value, {release: .4});

loop( (lc) => {
    if(song(16, 40)+song(56, 64))
        kick.play(e1);
    sleep(1);
}, { name: 'kick', amp: 1.3 });