// ┌────────────────────────────────┐ // 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); }