// ┌────────────────────────────────┐
//          2024 nabr ^;.;^
//    dittytoy.net/ditty/3e2ac07cd2
// └────────────────────────────────┘


class Song
{
    period = 0;

    constructor()
    {
        this.revInst = new v1Rev();
        this.revBass = new v1Rev();
    };

    stwidth(xin, kw)
    {
        const tmp = 0.5 * kw;
        const nL = tmp * (xin[1] - xin[0]);
        const nR = (1.0 - tmp) * (xin[1] + xin[0]);
        return [nR + nL, nR - nL]
    };

    fio(fadeout, fadein, scenetime) { return smoothstep(fadeout, fadein, scenetime)};

    decimator(x, n) //from musicdsp.org
    {
        let qn = 4; // pow( 2.0, 2 );
        return lerp(x, floor(x * qn + .5) / qn, .5);
    };

    drm(t, ch)
    {
        let kk = 0.0, sn = 0.0, x = 0.0;
        let tt = t * 3., ft = mod(tt, 1.);
        let ut = int(tt) % 12;

        // kick
        kk = .316 * sin(471. * ft * exp(-10. * ft)) + .707 * sin(311. * ft * exp(-2. * ft));
        kk *= elem(1., 0., 0., 1., 0., 0., 1., 0., 1., 1., 0., 0.)[ut];
        kk *= atan2(1 - ft, 10. * ft); //fun

        // snare
        x = t * 5623.123456;
        sn = mod(x * mod(t + x, 1.), 1.);
        sn += .5 * sin(6.283185 * t * 170.) + sin(6.283185 * t * 300.) * exp(-10. * ft);
        sn *= elem(0., 0., 1., 0., 0., 1., 0., 1., 0., 0., 1., 1.)[ut];

        let isrev = 0;
        let att = 60.;
        let sna = .51;
        if (ch === isrev) {
            att = 10.;               // opens env. to let more snare noise pass
            kk = this.decimator(kk); // a bit more fur
            sna = .23;
        }

        sn *= (max(0, (1 - ft), min(1, 20. * ft)) / max(1, att * ft));

        return (kk + sna * sn);

    };

    bass(t, ch)
    {
        let o = 0.0, x = 0.0;
        let ft = (mod(.5 * t, 4.) < 3 ? .5 * t : 2. * t);
        let y = sin(tau * 1.51 * t);
        let p = tau * 200. * elem((mod(2. * t, 4.) > 2 ? .544 : .614), 0.368, .411, (mod(2. * t, 4.) > 2 ? .737 : 0.548))[int(.5 * t) % 4];
        x = (ch == 0) ? (y * cos(.5 * p * t) + sin(4. * p * t)) : (y * cos(2. * p * t) + sin(4. * p * t));
        o = .7 * x + cos(2. * exp(-.72 * x));
        o *= min(1., 100. * fract(ft)) * max(0., 1. - fract(ft));
        return .32 * o;
    };

    pipes(t)
    {
        let y = 0., ph = 0., mph = 0., env = 0.;
        ph = (elem(.5, 1)[int(1. * t) % 2] * elem(220, 330)[int(6. * t) % 2]);
        mph = elem(0.375, .5 * 0.899, 0.75)[int(3. * t) % 3];
        env = fract(6. * t) * exp(1. + log(1. - fract(6. * t)));
        y = mod(.1 * env + 1.5 * ph * t / mph, 1);
        y = (abs(cos(tau * y) - .5) * pow(env, 1.));

        return y;
    };

    //main instrument 
    inst(t)
    {
        let ylw = 0., yhg = 0., env = 0, a = 0., b = 0.;
        a = fract(6. * t)
        b = (1. - a);
        env = min(1., 50. * fract(6. * t)) * (1. - fract(6. * t));

        let ph = elem(110, 165, 110, 165, 110, 165, 220, 330, 220, 330, 220, 330)[int(6. * t) % 12];
        ph /= (mod(t, 3.) < 1.0 ? (elem(0.375, 0.89, 0.75)[int(3. * t) % 3]) : (elem(0.25, 0.375, 1, 0.5)[int(3. * t) % 4]));

        ylw = sin(mod(.2 * env + ph * t, 1) * tau + lerp(-1., 1, sin(.3 * floor(t)) * .5 + .5));
        yhg = (sin(3. * ylw - .5) / elem(.7, .4)[int(t) % 2] * ylw * ylw);
        ylw *= copysign(min(a, b) / max(a, b), 1.);
        yhg *= abs(cos(3. * yhg) - (.7 + .755 * cos(t))) * env;

        return tanh(ylw + yhg * (.4 + .4 * sin(t)));
    };

    process()
    {

        let oinst = [0, 0], obass = [0, 0], otsum = [0, 0];

        this.period += 1;
        let t = int(this.period) / ditty.sampleRate;

        let dplucked = .7 * this.inst(t) + (mod(t, 22.) > [19, 14][int(t / 5) % 2] ? .5 * this.pipes(t) : 0);
        let dbass = (.7 * this.bass(t, 1) + .3 * this.bass(t, 0));

        //
        oinst = this.revInst.process(dplucked, int(this.period), 0.82, 0.95);
        oinst = [0, 0].map((v, id) => lerp(dplucked, oinst[id], .3));
        oinst = this.stwidth(oinst, 0.72);

        //
        obass = this.revBass.process(dbass + .4 * this.drm(t, 0), int(this.period), 0.7, 0.767);
        obass = [0, 0].map((v, id) => lerp(dbass, obass[id], .4));
        obass = this.stwidth(obass, 0.55);

        let fdt = mod(t, 116.);
        return [0, 0].map((v, id) => this.fio(57., 58., fdt) * this.fio(116., 115., fdt) * (obass[id] + this.drm(t, 1)) + oinst[id]);
    };

};

class v1Rev
{
    z1 = Array(8).fill().map(() => Array(6859).fill(0));
    dt = [2048, 2187, 3125, 2401, 1331, 2197, 4913, 6859];
    lpf_fltrState1RC = [0, 0];
    dump_fltrState = 0;

    constructor() { };

    damp(xin, a, state) 
    {
        let q = tau * exp(a * 10) / 44100;
        q = q / (1 + q);
        let yot = q * xin + (1 - q) * state;
        state = yot;
        return yot;
    };

    lpf_1rc(xin) //from musicdsp.org
    {
        let state = this.lpf_fltrState1RC;
        let c = 0.2002674; //pow(0.5, (128-0.71*128) / 16.0);
        let r = 0.3120826; //pow(0.5, (0.12*24+24) / 16.0);
        state[0] = (1 - r * c) * state[0] - c * state[1] + c * xin;
        state[1] = (1 - r * c) * state[1] + c * state[0];
        let tmp = state[1];
        state[0] = (1 - r * c) * state[0] - c * state[1] + c * xin;
        state[1] = (1 - r * c) * state[1] + c * state[0];
        return (tmp + state[1]) * 0.5;
    }
    process(sgnl_in, samp, damping_amount, feedback) 
    {
        let rv_ot = [0,0];
        let x = Array(8).fill(0), y = Array(8).fill(0);

        //diffuser
        sgnl_in = this.lpf_1rc(sgnl_in);

        const tanksz = 8;
        for (let i = 0; i < tanksz; i++) {
            const g = exp(-2. * ditty.dt * this.dt[i]); // second gain damping

            y[i] = this.z1[i][samp % this.dt[i]];

            for (let k = 0; k < tanksz; k++) {
                let beatingtremolo = exp(1. / (1 + k % 7));

                x[i] =
                    this.damp(sgnl_in * beatingtremolo, damping_amount, this.dump_fltrState)
                    * [[1, 1, 1, 1, 1, 1, 1, 1],
                    [1, -1, 1, -1, 1, -1, 1, -1],
                    [1, 1, -1, -1, 1, 1, -1, -1],
                    [1, -1, -1, 1, 1, -1, -1, 1],
                    [1, 1, 1, 1, -1, -1, -1, -1],
                    [1, -1, 1, -1, -1, 1, -1, 1],
                    [1, 1, -1, -1, -1, -1, 1, 1],
                    [1, -1, -1, 1, -1, 1, 1, -1]][i][k] * g;
            }

            this.z1[i][samp % this.dt[i]] = g * x[i] + y[i] * feedback;
            y[i] *= (-g) * 0.353553;

            //out
            rv_ot[0] += y[i] * elem(1, 1, -1, -1, 1, -1)[i % 6];
            rv_ot[1] += y[i] * elem(-1, 1, -1, 1, 1, 1)[i % 6];
        }

        return rv_ot;
    };
};

synth.def(Song).play(0, { duration: 500 });

// stuff i frequently use
const { abs, acos, asin, atan, atan2, ceil, clz32, cos, exp, floor, imul, fround, log, max, min, pow, random, round, sin, sqrt, tan, log10, log2, log1p, expm1, cosh, sinh, tanh, acosh, asinh, atanh, hypot, trunc, sign, cbrt,/*E,*/LOG2E, LOG10E, LN2, LN10, PI, SQRT2, SQRT1_2 } = Math;
const int = (x) => x | 0;
const tau = PI * 2.0;
const hpi = PI * .5;
const fract = (x) => (x - floor(x));
const mod = (x, y) => (x - floor(x / y) * y);
const exp2 = (x) => exp(x * LN2);
const dbg = (x) => debug.warn(' ', x);
const elem = (...x) => [...x];
const copysign = (x, y) => sign(x) === sign(y) ? x : -x;
const smoothstep = (e0, e1, value) => { const x = max(0, min(1, (value - e0) / (e1 - e0))); return x * x * (3 - 2 * x); }