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