Modified to use a cello-like instrument
Original : Wizards & Warriors (main menu)" by romaindurand (Wizards & Warriors (main menu))
Composed by David Wise for the NES game Wizards & Warriors.
Transcription available here : flat.io/score/5672cc…567-wizards-warriors
Log in to post a comment.
// inspired by https://www.youtube.com/watch?v=Aktb_dmY4vk // essentially, pass a bandpassed saw wave with vibrato and a bit of noise into a short reverb // bit slower, original is 210 ditty.bpm = 140; input.tuning = 0.5; //min=0.125, max=2, step=0.125 input.vibrato_amount = 3.5; //min=0, max=5, step=0.01 input.vibrato_freq = 32; //min=0, max=60, step=0.01 input.loss_freq = 500; //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.6; //min=0, max=1, step=0.01 input.body_diffuse = 0.8; //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 = Math.random(); this.vibrato = Math.random(); // 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 v0 = saw * 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.05, decay: 0.05, sustain: 0.7, 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.01)) } function newlens(count, max) { return new Array(count) .fill(1) .map(_ => Math.random() * max * 0.01); } 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) ]; } }); // === original === // Forked from "Wizards & Warriors (main menu)" by romaindurand // https://dittytoy.net/ditty/3066954356 function melodyPattern(notes, baseNote) { return () => { for(i = 0; i < notes.length; i++) { osc.play(notes[i], { duration: 0.5, pan: 0.2 - Math.random() * 0.1 }); sleep(0.5); osc.play(baseNote, { duration: 0.5, pan: 0.2 - Math.random() * 0.1 }); sleep(0.5); } }; } function simpleMelodyPattern(notes) { // calculate length for it to sound nice let lens = new Array(notes.length).fill(0.45); let last = 0; // every off note adds to the on length of the last on note for (let i = 0; i < lens.length; i++) { if (notes[i] === 0) lens[last] += 0.5; else last = i; } return () => { for(let i = 0; i < notes.length; i++) { osc.play(notes[i], { duration: lens[i], pan: -0.2 - Math.random() * 0.1 }); osc.play(notes[i] + 12, { duration: lens[i], pan: -0.2 - Math.random() * 0.1 }); sleep(0.5); } }; } const melodySeq0 = () => { melodyPattern([d5, e5, f5, g5], a4)(); melodyPattern([bb4, d5, g5, f5], g4)(); melodyPattern([e5, f5], c5)(); melodyPattern([g5, c5], g4)(); melodyPattern([bb4, a4, f5, e5], f4)(); const notes0 = [d5, e5, f5, d5]; melodyPattern(notes0, bb4)(); //same as previous pattern with another base note melodyPattern(notes0, g4)(); melodyPattern([e5, cs5, e5, cs5], a4)(); melodyPattern([a5, cs5, a5, a5], a4)(); }; const melodySeq1 = () => { melodyPattern([f5, d5], a4)(); simpleMelodyPattern([f5, g5, a5, a4])(); simpleMelodyPattern([ a5, bb4, d5, bb5, a5, bb4, d5, bb5, g5, g4, c5, g4, e5, f5, g5, c5, g5, a4, c5, a5, f5, a4, e5, c5, f5, bb4, d5, bb4, d5, e5, f5, bb4, f5, g4, bb4, g4, d5, e5, f5, g4 ])(); melodyPattern([f5, cs5, e5, cs5, a5, cs5], a4)(); melodyPattern([a5, a5], cs5)(); }; const melodySeqEnd = simpleMelodyPattern([d5, a4, e5, f5]); const bassPattern = simpleMelodyPattern([ d3, 0, 0, 0, 0, d3, e3, f3, g3, 0, 0, 0, 0, g3, a3, bb3, c4, 0, 0, 0, 0, bb3, a3, g3, f3, 0, g3, 0, a3, 0, f3, 0, bb3, 0, 0, 0, 0, c4, bb3, a3, g3, 0, 0, 0, 0, a3, bb3, g3, a3, 0, 0, 0, a3, 0, 0, 0, a3, 0, g3, 0, f3, 0, e3, 0 ]); loop(() => { melodySeq0(); melodySeq0(); melodySeq1(); melodySeq1(); melodySeqEnd(); melodySeqEnd(); melodySeqEnd(); simpleMelodyPattern([d5, 0, 0, 0])(); }, { name: 'melody', amp: 0.5 }).connect(reverb.create()); loop(() => { sleep(32); bassPattern(); bassPattern(); bassPattern(); simpleMelodyPattern([d3, 0, 0, 0, 0, 0, 0, 0])(); sleep(4); }, { name: 'bass', amp: 0.7 }).connect(reverb.create());