// 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);