Some scores initially arranged for a pair of Arduino-controlled stepper motors.
Log in to post a comment.
// To play another score, change this number and recompile: const SCORE_NUMBER = 2; // <---- input.portamento = 0.25; /** * The scores here are encoded in the following format (one char per data field, the shift is just to make characters printable) : * . tempo * . number of subdivisions of the beat+31 * . number of instruments+31 * . For each instrument part * . For each note * . note+64, or 'Space' if silence * . duration+63 * . `!` at the end of the current part */ // These score come from an old project of mine, in which I was using an Arduino to control stepper motors // and make them play music by varying their rotation speed. // I have a script to convert MusicXML files to scores, so it's fairly easy to create such a score using notation software. const scores = [ "d#!9A@A@A @;@BC<ACACA @;@BC9A@A@A @;@BC<ACACA @;@BC9@<@EC9@<@ECGBH@G@H@G@C@@C@A9A<@>@@C A@A9A<@>@;C A9@<@EC9@<@ECGBH@G@H@G@C@@C@A9A<@>@@C@A!-A0A0A/A2C0A4A4A/A2C-A0A0A/A2C0A4A4A/A2C-A4A4A/A6C0A7A7A/A6C0A5A5A0A7C0A5A5A/A4C-A4A4A @/@6C0A7A7A @/@6C-A5A5A4A;C!", "P#!H@C@>@G@C@>@E@C@>@G@C@>@E@C@>@C@H@C@>@G@C@>@E@C@>@G@C@>@E@C@>@C@H@C@>@G@C@>@E@C@>@G@C@>@E@C@>@C@G@C@>@E@C@>@E@C@>@E@C@>@E@C@>@C@!+O-O0O+O!", "x#!@CHAGAEADAEC@CAC8A;A>AAA@A>A<C;A<A9C<CAA@AEACAHCGAEACAAA@AA@C@<C;C<G<C@CHAGAEADAEC@CAC8A;A>AAA@A>A<C;A<A9C<CAA@AEACAHCGAEACAAA@AA@C@<C;C<G<CLC<ALAJAHAG@H@JACGHC9AHAGAEAD@E@GA@G@ABADAEAGAHAJAGADAMALAJAH@G@EAGCDCEGECLC<ALAJAHAG@H@JACGHC9AHAGAEAD@E@GA@G@ABADAEAGAHAJAGADAMALAJAH@G@EAGCDCEGEC!-G/C0G2C4G(C-C(C-C-C+C)C(G/C0C(C+C$C0A/A-A,A-G/C0G2C4G(C-C(C-C-C+C)C(G/C0C(C+C0C+C$C0C4C5C7C7A2A/A+A-C+C)C(A*A,A-A/A0A2C/C,C(C4C8C9C2C4C-C-A(A-A/A0C4C5C7C7A2A/A+A-C+C)C(A*A,A-A/A0A2C/C,C(C4C8C9C2C4C-C(C-C!", "B/! AEAGAIA@AIAGAEALAEAGAIA@AIAGAEAJAGAIAJABAEADABADAGAIAJA@AJAIAGAIAEAGAIA@ADABA@ABAEAGAIABALAKAIAKAGAIAKABAEA@ABA@ADAEAGA=AGAEADAEABADAEA=A@A?A=A?ABADAEA;AEADABADA@ABADA;A>A=A;A=A@ABADA=AGAFADAFABADAFA=A@A>A=A;A>A@ABA8AEADABAAA;A=A>A8A>A=A;A9ABADAEA=A@A?A=A<A?A@ABA<AEADABAKAHAIAKADANALAKALAIAKALADAGAFADAFAIAJALABALAJAIAJAGAIAJABAEACABACAFAGAIA@ACABA@A>AGAIAJABAEADABADAGAIAJA@AJAIAGAIAEAGAIA@ACABA@ABA>A@ABA8ABA@A>A=A9A;A=A4A7A6A4A6A9A;A=A6A@A?A=A?A;A=A?A6A9A7A6A4A7A9A;A4A>A=A;A:A4A6A7A1A7A6A4A2A;A=A>A6A9A8A6A5A8A9A;A5A>A=A;ADAAABADA=AGAEADAEABADAEA=A@A?A=A?ABACAEA;AEACABACA@ABACA;A>A=A;A=A@ABACA9ADABA@ABA>A@ABA9ABA@A>AEA>A@ABA9ABA@A>ACA@ABACA;A>A=A;A=A@ABACA9ACABA@ABA>A@ABA9A=A;A9A;A>A@ABA;AEADA@ADA@ABADA;A>A=A;A9A=A>A@A6A@A>A=A>A;A=A>A6A9A8A6A8A;A=A>A4A>A=A;A=A9A;A=A4A=A;A9A@A=A>A@A9ACABA@ABA>A@ABA9ABA@A>AEABADAEA?AHAGAEADA@ABADA;A>A=A;A@AEAGAIABALAJAIAJADAEAGA@AJAIAGAIABADAEA?AHAGAEADA>A@AAA;A>A<A;A<AEAGAHADAGAEADAEAMALAJAHAGAEADAEAHAGAEANAKALANAHAEAGAHABA?A@ABA;A=A?A@ABADAEAGAHADAEA@AAA>AGA>A@A<AEA<A>A;ADA;A9G<G?GBGEGE@D@B@@@?@=@;@=@?@@@B@D@E@G@H@G@E@D@B@@@?@=@;@9@8C;C>CACDCGCJCMC C9C<C?CBCECHCKCNC C;G@GEG;G@GDG=G@GEG G O!-C C9C C1C C9C C/C C9C C4C C8C C-C C9C C-C C9C C-C C3C C,C C4C C*C C4C C/C C3C C(C C1C C(C C4C C(C C.C C&C C/C C%C C1C C%C C1C C%C C1C C%C C1C C%C C1C C*C C.C C#C C/C C#C C.C C#C C/C C(C C,C C-C C-C C-C C-C C-C C-C C-C C-C C-C C3C C+C C1C C*C C.C C/C C2C C*C C/C C2C C5C C*C C6C C/C C3C C(C C4C C-C C1C C&C C2C C*C C2C C(C C2C C-C C1C C&C C2C C&C C2C C&C C,C C%C C-C C#C C-C C(C C,C C-C C-C C%C C4C C&C C2C C*C C9C C G4C C1C C-C C(_/C C,C C-C C*C C(_ A/A0A2A,A/A-A,A-A0A2A4A/A2A0A/A(_0A-A/A0A(A+A)A(A'O(G'_ C4C2C,C0C-C/C2C(_(G-G0G3G6G G O O(C/C4C C(C2C4C C-C1C4C C G O!", "s#!FEHGAAHEJEM@K@J@F@FEHEAC<E:AF@F@ AF@F@ AFEHGAAHEJEM@K@J@F@FEHEACAC GA@C@F@C@JA @JA @HC AA@C@F@C@HA @HA @FBE@C@ @A@C@F@C@FCHAEBC@A@ @ AAAHCFB @F@F@ AA@C@F@C@JA @JA @HC AA@C@F@C@MCE@ @FBE@C@ @A@C@F@C@FCHAEBC@A@ @ AAAHCFB @ G!<@ @<@<@ @9@7@ @5@ @5@ @ A<@A@>@ @>@9@ @9@7@ B7@7@ @9@7A<@ @<@9@ @9@7@ @5@ @5@ @ A<@<@>@ @>@>@ @9@7@ @A@A@5@ @A@A@>@:@<@ @<@<@ @9@7@ @5@ @5@ @ A<@A@>@ @>@9@ @9@7@ B7@7@ @9@7A<@ @<@9@ @9@7@ @5@ @5@ @ A<@<@>@ @>@>@ @9@7@ @ A5@ @:@7@ @:@<@ @<@<@ @9@7@ @5@ @5A A<@A@>@ @>@9@ @9@7@ B7@7@ @9@7A<@ @<@9@ @9@7@ @5@ @5AA@ @<@<@5E7AA@A@5@ @:@:@ @:@<@ @<@<@ @9@7@ @5@ @5A A<@A@>@ @>@9@ @9@7@ B7@7@ @9@7A<@ @<@9@ @9@7@ @5@ @5AA@ @<@<@5@ @5@5@ A7A G!", "x#! @3@6@9A=A@A<@?@B@DC?@=@=@=@ @=@ @=@=@=@=@=@ @=@ @=@=@=@=@=@ @=@ @=@=@=@=@=@ @=@ @=@;@;@;@;@ @;@ @;@;@;@;@;@ @;@ @;@<@<@<@<@ @<@ @<@<@<@<@<@ @<@ @<@ @8@=@?@@B8@=@?@@E @9@=@?@@B9@=@?@@E @6@;@=@?B6@;@=@?E @=@?A<K @8@=@?@@B8@=@?@@E @9@=@?@@B9@=@?@@E @6@;@=@?B6@;@=@?E @@@B@@@?K A8B@B?B=B8A;C;@=@;@9@9G6B?B=B;B9A A8C9@;@9@8@8G A8B@B?B=B8A;C;@=@;@9F4A6E=A;E9A8O A@B?B@B?B8A;C;@9@8@9@9G A?B=B?B;B9A8C9@;@9@8@8G A1@3@4@8B1@3@4@8@8C A1@3@4@9B1@3@4@9B4A6E1A/E-A,O O O O O@@?@@G=@?@@@B@@@?@@@?@@MB@@@BG?@@@B@D@E@G@D@E@DM1A4@3B4A1A4@3B4A-A4@3B4A-A4@3B4A/A6@5B6A/A6@5B6A9@;@9@8@8K! @'F'@,F1G1C @,@1@/@-G-C @-@,@-@/O,E$A&A(A*A-A,G/A(E(G+A%E#G*A/E @%@'A$K%A A(A,A1A,A(A%A-A A(A-A-A A(A-A%A A(A,A1A,A(A%A,A A'A,A,A*A(A'A1A A1A1A1A A1A1A-A A-A-A-A A-A-A/A A/A/A/A A/A/A,A A0A3A8A6A4A3A1A A1A1A1A A1A1A-A A-A-A-A A-A-A*E*A*E'A'O%A A(A,A1A,A(A%A-A A(A-A-A A(A-A%A A(A,A1A,A(A%A,A A'A,A,A*A(A'A%A A(A,A1A,A(A%A-A A(A-A-A A(A-A*E*A*E'A'O4@3@4G1@3@4@6@4@3@4@3@4M3@1@3G/@1@3@4@3@1@3@1@0M O O O O%G%G-G-G#G#G*A(A'K!", ]; const score = scores[SCORE_NUMBER]; const srand = () => Math.random()*2-1; function receiveScore(score) { let res = {tempo:0, ticksPerBeat:0, nInstr:0}; let scoreParts = []; let partNotes = []; let curInstr = 0; for(let char of score) { let v = char.charCodeAt(0) & 0xff; if (res.tempo == 0) { res.tempo = v; debug.log("Tempo : ", res.tempo); } else if (res.ticksPerBeat == 0) { res.ticksPerBeat = v-31; debug.log("Ticks per beat : ", res.ticksPerBeat); } else if (res.nInstr == 0) { res.nInstr = v-31; debug.log("Number of voices : ", res.nInstr); } else if (v==33) { // End of score scoreParts.push(partNotes); partNotes = []; curInstr++; //if(curInstr>=res.nInstr) {break;} } else { partNotes.push(v); } } res.scoreParts = scoreParts; return res; } const res = receiveScore(score); function softclip(x) { return x<-1?-1:x>1?1:1.5*(1-x*x/3)*x; } const varsquare = (p, formant) => {let x = 4*(p-~~p); return softclip(formant * (x < 2 ? 1-x : -3+x));} const sqsyn = synth.def(class { constructor(options) { this.p = 0; this.freq = 0; this.amp = 0; } process(nn,env,tick,options) { this.a0 = clamp01(ditty.dt / (input.portamento * 0.03 + 0.00001)); this.freq += this.a0 * (midi_to_hz(options.nn) - this.freq); this.amp += this.a0 * (options.gate - this.amp); this.p += this.freq * ditty.dt; let formant = options.cutoff / (2*this.freq); return varsquare(this.p, formant) * this.amp; } }, {env: one, nn: c4, gate: 1, amp:0.1, cutoff: 8000}); function playScore(res) { ditty.bpm = res.tempo; let sleept = 1 / res.ticksPerBeat; for(let partNotes of res.scoreParts) { loop( () => { let totaldur = sleept * partNotes.reduce( (acc,cur,j) => acc + ((j%2) ? (cur-63) : 0), 0); debug.log("totaldur", totaldur); let mySynth = sqsyn.play(60, {duration: totaldur}); for(let i=0; i<partNotes.length; i+=2) { let note = partNotes[i]; let duration = partNotes[i+1]-63; mySynth.options.nn = note - 64 + 69; mySynth.options.gate = (note == 32) ? 0 : 1; sleep(sleept * duration); } mySynth.options.gate = 0; // Ideally I would like to free this synth now (without having to manually compute how long it has run for) }).connect(echo.create()); } } ////////////////////////////////////////////////////////////////////////// /// echo by srtuss // https://dittytoy.net/ditty/24373308b4 class Delayline { constructor(n) { this.n = ~~n; this.p = 0; this.lastOutput = 0; this.data = new Float32Array(n); } clock(input) { this.lastOutput = this.data[this.p]; this.data[this.p] = input; if(++this.p >= this.n) { this.p = 0; } } tap(offset) { var x = this.p - offset - 1; x %= this.n; if(x < 0) { x += this.n; } return this.data[x]; } } input.echo = .15; const echo = filter.def(class { constructor(options) { this.lastOutput = [0, 0]; var time = 60 / ditty.bpm; //time *= 3 / 4; time /= 2; var n = Math.floor(time / ditty.dt); this.delay = [new Delayline(n), new Delayline(n)]; this.dside = new Delayline(500); this.kfbl = .5; this.kfbr = .7; } process(inv, options) { this.dside.clock(inv[0]); var new0 = (this.dside.lastOutput + this.delay[1].lastOutput) * this.kfbl; var new1 = (inv[1] + this.delay[0].lastOutput) * this.kfbr; this.lastOutput[0] = inv[0] + this.delay[0].lastOutput * input.echo; this.lastOutput[1] = inv[1] + this.delay[1].lastOutput * input.echo; this.delay[0].clock(new0); this.delay[1].clock(new1); var m = (this.lastOutput[0] + this.lastOutput[1])*.5; var s = (this.lastOutput[0] - this.lastOutput[1])*.5; s *= 2; return [m+s, m-s]; } }); //////////////////////////////////////////////////////////////////////// playScore(res);