auroralcircuits

#LoFiVibes #Retrowave #PunctumContra #OriginalMusic

Log in to post a comment.

// synth chords
function chd(rt, nt, sw) {
  const chrr = [chord(f4, chords['7sus4']), 
                chord(ab3, chords['M7']), 
                chord(c4, chords['m7']), 
                chord(db3, chords['M7'])];
  let y = 0.;
  const ft = .5 * rt;
  const ev = env_adc(fract(ft), .4, .64);
  const crr = chrr.ring(ft);
  for (let i = 0; i < crr.length; i++) {
    const x = max(.6, fract(ft)) - cos(.7 * sin(sw * rt) + midi_to_hz(crr.ring(i)) * tau * rt);
    y += sin(3 * fract(.5 + x)) + cos(mod(x, tau));
  }
  y *= .3 * ev;
  return y;
}

// strings
const instrument = { vio0: 0, vio1: 1, vio2: 2 };
function strings(rt, mt, type) {
  const melscl = scale(f3, scales['melodic_minor_desc'], 2);
  let y = 0;
  switch (type) {
    case instrument.vio0: {
      const ft = mt * rt;
      y = (cos(1.57 * mod(midi_to_hz(melscl.ring(ft)) * rt * tau, tau)) 
            + sin(sin(2 * rt) * 3.14 * mod(midi_to_hz(melscl.ring(ft)) * rt * tau, PI / 2))) 
            * pow(min(1., 10. * fract(ft)) * (1. - fract(ft)), 2.);
    };break;
    case instrument.vio1: {
      const ft = mt * rt;
      y = sin(sin(8 * rt) * 2. + 0.523 * mod((2 * midi_to_hz(melscl.ring(ft)) * rt * tau), PI)) 
            * env_adc(fract(ft), .22, .345);
    };break;
    case instrument.vio2: {
      const ft = mt * rt;
      y = (sin(2 + 0.785 * mod(midi_to_hz(melscl.ring(ft)) * rt * tau, PI)) 
            + cos(sin(3 * rt) * 2. + 0.628 * mod(midi_to_hz(melscl.ring(ft)) * rt *tau, tau))) 
            * env_adc(fract(ft), .124, .467);
    };break;
  }
  return y;
}

//################# InstumentGroup
const InstGr = class {
  constructor() {}
  process(time) {
    let inst0 = 0 //violine
      , inst1 = 0 //cello
      , inst2 = 0 //cello
      , inst3 = 0 //staccato
      , inst4 = 0; //chords

    inst4 = chd(.15 + time, time, 24) * .7 + chd(.05 + .5025 * time, .5 * time, 12);
    
    // phaseoffset * [...scalestart] * time   
    inst3 = strings(1.0025 * [1, 4, 3].ring([1, 2].ring(time) * time) + .5 * time, [4, 6].ring(.125 * time),instrument.vio2);
    inst3 += .9 * strings(.50005 * [7, 9, 5].ring(.5 * time) + .5 * time, [4, 6].ring(.125 * time), instrument.vio2);
    
    inst0 = strings(.50005 * [5, 4, 7].ring(time) + time, [6, 4].ring(.5 * time), instrument.vio1);
   
    let htime = time * .5; // mod(time * .5, 32);
   
    inst1 = strings(1.0025 * [1, 8, 3].ring(htime) + mod(-.5 * htime, 3) + htime, 4, instrument.vio0) 
            * lerp(1.,.83, mod(.25 * htime, 1))
    inst2 = strings(1.00005 * [3, 5, 1].ring(htime) + mod(-htime, 2) + .5 * htime, 4, instrument.vio0) 
            * lerp(.77,1., mod(.25 * htime, 1));
    
    inst0 *= .25 * gainEnv(mod(time, 32), 9, 30, 3);
    inst1 *= .17 * gainEnv(time, 18, 60, 1);
    inst1 += (time > 24 ? (.19 * strings(time, 4, instrument.vio0) * gainEnv(mod(time, 14), 7, 2, 2)) : 0);
    inst2 *= .22 * min(1, time/22);
    inst3 *= .17 * min(1, time/5);
    inst4 *= .7;
    
    return { inst0Out: inst0, inst1Out: inst1, inst2Out: inst2, inst3Out: inst3, inst4Out: inst4 };
  }
}

// ################ MIX
const MIX = class {
  constructor() {
    this.period = 0;
    this.cmain = new InstGr();
    this.crev = [
    new Rev_Mn(), new Rev_Mn(), new Rev_Mn(),new Rev_Mn(),
    new Rev_Mn(), new Rev_Mn(),
    new Rev_Mn(), new Rev_Mn(), new Rev_Mn(), new Rev_Mn()];
    this.fltr = new FilterBank();
    this.swidth = [new FilterBank(),new FilterBank() ];
    this.eq3band = [new Band3Eq(), new Band3Eq()];
  }
  process() {
    let inst0 = 0//violine
       ,inst1 = 0//cello
       ,inst2 = 0//cello
       ,inst3 = 0//staccato
       ,inst4 = 0;//chords
    let rev_sum = [0, 0], bas_sum = [0,0];
    
    this.period += 1;
    let time = this.period / ditty.sampleRate;
    
    const cmain = this.cmain.process(time);
    inst0 = this.fltr.svf_opt2(cmain.inst0Out, 460, 1., -12., 2, 0);
    inst1 = this.fltr.svf_opt2(cmain.inst1Out, 2300, 1, -7., 8, 1);
    inst2 = this.fltr.svf_opt2(cmain.inst2Out, 1800, 1, -10., 8, 2);
    inst3 = this.fltr.svf_opt2(cmain.inst3Out, 1600, 1, -12., 8, 3);
    inst4 = cmain.inst4Out;
    inst4 = lerp(inst4, this.fltr.svf_opt2(inst4, 4200, .7, 3, 1, 4), .7);
    
    let drysum =  tanh(2 *  ( .7 * inst0 + .3 * inst3 + .4 * inst4 + ( .5 * (inst1 + inst2)))) ;
        drysum = this.fltr.svf_opt2(drysum, 40, .7, -6., 2, 5);
        
    let stx = min(1.1, (.75 + smoothstep(0, 16, mod(floor(time), 16.)) ));
    
    // difference +/- 6 will induce a slight panning, which helps with frequency separation
    rev_sum[0] =  this.crev[0].process(inst0, 0, 25, 0.46);
    rev_sum[1] =  this.crev[1].process(inst0, 8, 25, 0.47);
    bas_sum[0] =  this.crev[2].process(inst1, 0, 46, 0.69);
    bas_sum[1] =  this.crev[3].process(inst1, 8, 46, 0.63);
    bas_sum[0] += this.crev[4].process(inst2, 0, 50, 0.42);
    bas_sum[1] += this.crev[5].process(inst2, 8, 50, 0.39);
    rev_sum[0] += this.crev[6].process(inst3, 0, 30, 0.46);
    rev_sum[1] += this.crev[7].process(inst3, 8, 30, 0.41);
    rev_sum = this.swidth[0].stwidth(rev_sum,  1.1);
    rev_sum[0] += this.crev[8].process(inst4, 0, 32, 0.56);
    rev_sum[1] += this.crev[9].process(inst4, 8, 32, 0.61);

    bas_sum = this.swidth[1].stwidth(bas_sum, .83);
    
    rev_sum[0] += bas_sum[0] + drysum;
    rev_sum[1] += bas_sum[1] + drysum;
    
    // master out  
    const veq = {lowG:8, midG:-2, hghG:5,lowMidFq:260,midHghFq:2800,masterG:3};
    rev_sum[0] = this.eq3band[0].process(rev_sum[0], veq );
    rev_sum[1] = this.eq3band[1].process(rev_sum[1], veq );
    
    return rev_sum;
  }
}
// ################ REVERB
const Rev_Mn = class {
  constructor() {
    this.zn = Array.from({length: 8}, () => Array(2639).fill(0));
    this.dcBlockState = Array.from({length: 17}, () => [0, 0]);
    this.period = 0;
  }
  process(rv_xin, offs, rtail, scl) {
    this.period += 1;
    let y = [0, 0, 0, 0, 0, 0, 0, 0];
    let rv = 0,itter = 0;
    const mat0 = [1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1];
    const mat1 = [-1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1];
    const mat2 = [1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];
    let dt = [1637, 1277, 1607, 2639, 1823, 751, 1823, 2591];
        dt = dt.map((n, idx) => (scl * dt[idx]) | 0);
    for (let i = 0; i < 8; i++) {
      const it8 = i % 8;
      const pdt = this.period % dt[it8];
      const g3 = exp(-(1 * rtail * 0.022675736) / (i + 1));
      for (let it = 0; it < 2; it++) {
        const g4 = exp(-((1.41421 / rtail) * 0.0022675736) * (itter + 1));
        y[it8] = this.dcblock((this.zn[it8][pdt] * mat0[itter % 16]), g4, itter);
        itter++;
      }
      this.zn[it8][pdt] = g3 * y[it8] + mat2[i % 8 + offs] * rv_xin;
      rv += mat1[i % 8 + offs] * y[it8];
    }
    rv = this.dcblock(rv, .997, 16);
    return rv * 0.25;
  }
  dcblock(xin, at, instnc) {
    let state = this.dcBlockState[instnc];
    let y0 = xin - state[0] + at * state[1];
    state[0] = xin;
    state[1] = y0;
    return y0;
  }
};
//################ FILTERBANK
class FilterBank {
  constructor() {
    this.svf_opt2State = Array.from({length: 8}, () => [0, 0]);
    this.dcBlockState = Array.from({length: 8}, () => [0, 0]);
    //this.sfv_type = {low: 0,band: 1,high: 2,notch: 3,peak: 4,all: 5,bell: 6,lsf: 7,hsf: 8};
  }
  // http://www.cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
  svf_opt2(v0, cutoff, Q, bellgaindB, type, instance) {
    let m = [0, 0, 0];
    let a1 = 0, a2 = 0, a3 = 0;
    let v1 = 0, v2 = 0, v3 = 0;
    let ic1eq = this.svf_opt2State[instance][0]
      , ic2eq = this.svf_opt2State[instance][1];
    const A = Math.pow(10.0, bellgaindB / 40.0);
    let g = tan(PI * cutoff / 44100);
    if (type == 7) g /= sqrt(A) // low shelf
    else if (type == 8) g *= sqrt(A) // high shelf
    let k = 1 / (type == 6 ? (A * Q) : Q); //bell A*Q
    const filter = [[0, 0, 1], [0, 1, 0], [1, -k, -1], [1, -k, 0], [1, -k, -2], 
    [1, -2 * k, 0], [1, k * (A * A - 1), 0], [1, k * (A - 1), (A * A - 1)], [A * A, k * (1 - A) * A, (1 - A * A)]];
    a1 = 1 / (1 + g * (g + k));
    a2 = g * a1;
    a3 = g * a2;
    m = filter[type];
    v3 = v0 - ic2eq;
    v1 = a1 * ic1eq + a2 * v3;
    v2 = ic2eq + a2 * ic1eq + a3 * v3;
    ic1eq = 2 * v1 - ic1eq;
    ic2eq = 2 * v2 - ic2eq;
    this.svf_opt2State[instance][0] = ic1eq;
    this.svf_opt2State[instance][1] = ic2eq;
    return m[0] * v0 + m[1] * v1 + m[2] * v2;
  }
  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]
  }
  dcblock(xin, at, instnc) {
    let state = this.dcBlockState[instnc];
    let y0 = xin - state[0] + at * state[1];
    state[0] = xin;
    state[1] = y0;
    return y0;
  }
};


//Easy peasy equalizer, not the proper way to do it. 
//I've just gotten used to how it sounds during composition.

// ################ Band3EQ
class Band3Eq {
  constructor() {
    this.lp1 = 0;
    this.hp1 = 0;
  }
  process(xin, veq) {
    const { lowG, midG, hghG, lowMidFq, midHghFq, masterG } = veq;
    const adb = 8.656170245;
    // Compute filter coefficients
    const xLP = exp(-2.0 * PI * lowMidFq / 44100.0);
    const a0LP = 1.0 - xLP;
    const b1LP = (-xLP);
    const xHP = exp(-2.0 * PI * midHghFq / 44100.0);
    const a0HP = 1.0 - xHP;
    const b1HP = (-xHP);
    // Low-pass filter calculation
    const lpOut = a0LP * xin - b1LP * this.lp1;
    this.lp1 = lpOut;
    // High-Pass filter calculation
    let hpOut = xin - (a0HP * xin - b1HP * this.hp1);
    this.hp1 = hpOut;
    // Decompose the signal into frequency bands and apply volume levels
    const lowBand = lpOut * exp(lowG / adb);
    const midBand = (xin - lpOut - hpOut) *  exp(midG / adb);
    const highBand = hpOut * exp(hghG / adb);
    // Sum the frequency bands and apply the master volume level
    return (lowBand + midBand + highBand) * exp(masterG / adb);
    }
}


// ++++++++++++++++++++++++++++++++++
ditty.bpm = 1.01047; // repeat at ~48 sec
loop( () => {
    const playme = synth.def(MIX, { name: 'synth', amp: 1, duration:1 });
    playme.play();
}, { sync:1, name: 'my first loop' });
// ++++++++++++++++++++++++++++++++++


const smoothstep = (e0, e1, value) => {
  const x = max(0, min(1, (value - e0) / (e1 - e0)));
  return x * x * (3 - 2 * x);
};
function gainEnv(time, fadeIn, sustain, fadeOut) {
  const totalDuration = fadeIn + sustain + fadeOut;
  // Fade-in
  if (time < fadeIn) {
    return smoothstep(0, fadeIn, time);
  }
  // Sustain
  if (time >= fadeIn && time <= fadeIn + sustain) {
    return 1;
  }
  // Fade-out
  if (time > fadeIn + sustain) {
    const fadeOutStartTime = fadeIn + sustain;
    const fadeOutEndTime = totalDuration;
    return 1 - smoothstep(fadeOutStartTime, fadeOutEndTime, time);
  }
  return 0;
}
const {sqrt,PI,floor,sin,abs,max,min,exp,cos,tanh,pow,tan} = Math;
const env_d = (t, d) => exp(-t * 5.436563 / d);
const env_ad = (t, a, d) => min(t / max(1e-5, a), env_d((t - a), d));
const env_adc = (t, a, d) => clamp(min(t / max(1e-5, a), env_d((t - a), d)), 0, 1);
const tau = (PI * 2.0);
const fract = (x) => (x - floor(x));
const mod = (x, y) => (x - floor(x / y) * y);
const msat = (x, a) => 0.5 * (abs(x + a) - abs(x - a));
const softkn = (x, kn) => x / (kn * abs(x) + 1.0);
const softkna = (x, kn, ad) => x / (kn * msat(x, ad) + 1.0);
const dbg = (x) => debug.warn(' ', x);