Tiny tracker with a cello instrument.
Melody is not too great.
Log in to post a comment.
// // You can find the Dittytoy API Reference here: https://Dittytoy.net/syntax // Example ditties can be found here: https://dittytoy.net/user/Dittytoy // // Most of your ditty will run 44100 times per second using javascript in the browser. // Make sure you optimize your ditty to work well on as many devices as possible. To do that, try to limit // the number of simultaneously active synths: make sure they don't last longer than necessary, or you can // hear them, and spread them over different loops (each loop runs in a separate worker). // ditty.bpm = 120; const start = 0; const notelen = 0.15; const melody = [ // open 0, ds4, 12, 1, 15, fs4, 4, 1, 20, cs4, 4, 1, 25, gs3, 13, 1, 50, ds4, 14, 1, 65, fs4, 4, 1, 70, cs4, 4, 1, 75, gs3, 13, 1, 100, ds4, 13, 1, 115, fs4, 4, 1, 120, cs4, 5, 1, 125, gs3, 13, 1, 150, ds4, 12, 1, 165, fs4, 5, 1, 170, cs4, 4, 1, 175, gs3, 16, 1, // main 200, as4, 5, 1, 205, ds5, 5, 1, 210, as5, 14, 1, 225, gs5, 5, 1, 230, f5, 15, 1, 250, cs5, 5, 1, 255, ds5, 4, 1, 260, fs5, 18, 1, 280, f5, 5, 1, 285, as4, 10, 1, 300, as4, 1, 1, 305, ds5, 1, 1, 310, as5, 1, 1, 325, gs5, 1, 1, 330, f5, 1, 1, 350, cs5, 1, 1, 355, ds5, 1, 1, 360, fs5, 1, 1, 380, f5, 1, 1, 385, as4, 1, 1 ]; input.tuning = 0.875; //min=0.125, max=2, step=0.125 input.tuning = 0.75; //min=0.125, max=2, step=0.125 input.vibrato_amount = 1.5; //min=0, max=5, step=0.01 input.vibrato_freq = 15; //min=0, max=60, step=0.01 input.loss_freq = 900; //min=100, max=8000, step=1 input.loss_q = 1.3;//min=0.1, max=10, step=0.1 input.loss_wet = 1; //min=0, max=1, step=0.01 input.body_length = 0.06; //min=0, max=1, step=0.01 input.body_diffuse = 0.02; //min=0, max=1, step=0.01 input.body_feedback = 0.6; //min=0, max=1, step=0.01 input.body_damping = 5200; //min=10, max=10000, step=1 input.body_wet = 1.0; //min=0, max=1, step=0.01 function softclip(x) { return x < -1 ? -1 : x > 1 ? 1 : 1.5*(1-x*x/3)*x; } function varsaw(p, formant) { let x = p-~~p; return (x - 0.5) * softclip(formant*x*(1-x)) * 2; } const osc = synth.def( class { constructor(options) { // The value of the note argument of the play call is retrievable via options.note. this.phase = 0.0; this.vibrato = 0.0; // filter state this.ic1eq = 0.0; this.ic2eq = 0.0; } process(note, env, tick, options) { // saw wave this.vibrato += ditty.dt * input.vibrato_freq; this.phase += (midi_to_hz(note) + Math.sin(this.vibrato) * input.vibrato_amount) * ditty.dt * input.tuning; const saw = varsaw(this.phase, 50); const noise = Math.random(); const v0 = (saw + 0.2 * noise) * env.value; // SVF filter // https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf const g = Math.tan(Math.PI * input.loss_freq * ditty.dt); const k = 1 / input.loss_q; const a1 = 1 / (1 + g * (g + k)); const a2 = g * a1; const a3 = g * a2; // tick const v3 = v0 - this.ic2eq; const v1 = a1 * this.ic1eq + a2 * v3; const v2 = this.ic2eq + a2 * this.ic1eq + a3 * v3; // update this.ic1eq = 2 * v1 - this.ic1eq; this.ic2eq = 2 * v2 - this.ic2eq; // bandpass out return v1 * input.loss_wet + (1 - input.loss_wet) * v0; } }, { // attack parameters attack: 0.02, decay: 0.0, sustain: 1.0, release: 0.1, env: adsr, } ); // from struss class Delayline { constructor(n) { this.n = ~~n; this.p = 0; this.lastOut = 0; this.data = new Float32Array(n); } clock(input) { this.lastOut = this.data[this.p]; this.data[this.p] = input; if (++this.p >= this.n) this.p = 0; } tap(offset) { let x = this.p - (offset|0) - 1; x %= this.n; if (x < 0) x += this.n; return this.data[x]; } } function newdelay(count) { return new Array(count) .fill(null) .map(_ => new Delayline(ditty.sampleRate * 0.2)) } function newlens(count, max) { return new Array(count) .fill(1) .map(_ => Math.random() * max * 0.2); } function hada8([c0, c1, c2, c3, c4, c5, c6, c7]) { // shuffle [c0, c1, c2, c3, c4, c5, c6, c7] = [c3, -c7, c1, -c4, -c5, c6, c0, c2]; // 8x8 hadamard matrix return [ (c0 + c1 + c2 + c3 + c4 + c5 + c6 + c7) * Math.sqrt(1 / 8), (c0 - c1 + c2 - c3 + c4 - c5 + c6 - c7) * Math.sqrt(1 / 8), (c0 + c1 - c2 - c3 + c4 + c5 - c6 - c7) * Math.sqrt(1 / 8), (c0 - c1 - c2 + c3 + c4 - c5 - c6 + c7) * Math.sqrt(1 / 8), (c0 + c1 + c2 + c3 - c4 - c5 - c6 - c7) * Math.sqrt(1 / 8), (c0 - c1 + c2 - c3 - c4 + c5 - c6 + c7) * Math.sqrt(1 / 8), (c0 + c1 - c2 - c3 - c4 - c5 + c6 + c7) * Math.sqrt(1 / 8), (c0 - c1 - c2 + c3 - c4 + c5 + c6 - c7) * Math.sqrt(1 / 8), ]; } function house8([c0, c1, c2, c3, c4, c5, c6, c7]) { const sum = c0 + c1 + c2 + c3 + c4 + c5 + c6 + c7; return [c0, c1, c2, c3, c4, c5, c6, c7].map(x => x - sum * 0.25); } // geraint luff reverberator // https://signalsmith-audio.co.uk/writing/2021/lets-write-a-reverb/ class Reverb { constructor(n) { // diffusors this.diff1 = newdelay(8); this.diff2 = newdelay(8); this.diff3 = newdelay(8); this.diff4 = newdelay(8); // length of diffusors this.lens1 = newlens(8, ditty.sampleRate); this.lens2 = newlens(8, ditty.sampleRate); this.lens3 = newlens(8, ditty.sampleRate); this.lens4 = newlens(8, ditty.sampleRate); // reverb loop this.reverb = newdelay(8); this.rvlens = newlens(8, ditty.sampleRate); this.rvlp = new Array(8).fill(0); } tick(inp) { // split into channels const channels = new Array(8).fill(inp); // diffuser 1 const diff1 = hada8(this.diff1.map((d, i) => { d.clock(channels[i]); return d.tap(this.lens1[i] * input.body_diffuse); })); // diffuser 2 const diff2 = hada8(this.diff2.map((d, i) => { d.clock(diff1[i]); return d.tap(this.lens2[i] * input.body_diffuse); })); // diffuser 3 const diff3 = hada8(this.diff3.map((d, i) => { d.clock(diff2[i]); return d.tap(this.lens3[i] * input.body_diffuse); })); // diffuser 4 const diff4 = hada8(this.diff4.map((d, i) => { d.clock(diff3[i]); return d.tap(this.lens4[i] * input.body_diffuse); })); // reverb loop const fb = house8(this.reverb.map((d, i) => { const tap = d.tap(this.rvlens[i] * input.body_length); return tap * input.body_feedback + diff4[i]; })); // filter const damped = fb.map((x, i) => { // one pole this.rvlp[i] += (x - this.rvlp[i]) * (1 - Math.exp(-input.body_damping * ditty.dt * Math.PI * 2)); return this.rvlp[i]; }); // feedback this.reverb.forEach((d, i) => d.clock(damped[i])); // and out again return fb.reduce((a, d) => a + d, 0) * Math.sqrt(1 / channels.length); } } // Simple allpass reverberator, based on this article: // http://www.spinsemi.com/knowledge_base/effects.html const reverb = filter.def(class { constructor(options) { this.l = new Reverb(1); this.r = new Reverb(1); } process(inp, options) { const [l, r] = inp; return [ this.l.tick(l) * input.body_wet + l * (1 - input.body_wet), this.r.tick(r) * input.body_wet + r * (1 - input.body_wet) ]; } }); loop( () => { let time = 0; for (let i = 0; i < melody.length; i += 4) { const sta = melody[i + 0]; const not = melody[i + 1]; const len = melody[i + 2]; const amp = melody[i + 3]; if (sta >= start) { sleep((sta - time) * notelen); osc.play(not, { duration: len * notelen }); } time = sta; } sleep(5); }, { name: 'Cello' }).connect(reverb.create());