Symphony No. 6.28 in φ minor. An ode to magic numbers.
Log in to post a comment.
// LL 2021
const systems = 5; // min=1 max=20 step=1
const perspective = 0.15; // min=0 max=1 step=0.01
const curved = 1; // min=0 max=1 step=1 (No, Yes)
Canvas.setpenopacity(1);
class Page {
constructor(x, y, w, h, systems) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.systems = [];
const margin_top = h / 8;
const margin_w = w / 10;
const margin_h = h / systems / 3;
const sx = x + margin_w;
const sw = w - margin_w * 2;
const sh = (h - margin_top - margin_h * systems) / systems;
for (var i=0; i<systems; i++) {
const sy = y + margin_top + i * (sh + margin_h);
this.systems.push(new System(sx, sy, sw, sh));
}
const ty = y + margin_top / 4;
const th = margin_top / 2;
this.title = new Title(sx, ty, sw, th);
}
draw() {
turtle.jump(this.x, this.y);
turtle.goto(this.x + this.w, this.y);
turtle.goto(this.x + this.w, this.y + this.h);
turtle.goto(this.x, this.y + this.h);
turtle.goto(this.x, this.y);
const pages = 5;
const offset_x = 4/pages, offset_y = 6/pages;
const offset_xl = (this.x < 0) ? offset_x : 0;
const offset_xr = (this.x >= 0) ? -offset_x : 0;
const offset_yl = (this.x < 0) ? offset_y : 0;
const offset_yr = (this.x >= 0) ? offset_y : 0;
for (var i=1; i<pages; i++) {
turtle.jump(this.x + offset_xl * i, this.y + this.h + offset_yl * (i-1));
turtle.goto(this.x + offset_xl * i, this.y + this.h + offset_yl * i);
turtle.goto(this.x + this.w + offset_xr * i, this.y + this.h + offset_yr * i);
turtle.goto(this.x + this.w + offset_xr * i, this.y + this.h + offset_yr * (i-1));
}
this.title.draw();
this.systems.forEach(s => s.draw());
this.systems[0].drawStart();
this.systems[this.systems.length-1].drawEnd();
}
}
class System {
constructor(x, y, w, h) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.clefs = [];
this.clefs.push(new Clef("G", x + 2, y - h/10 , h/2))
this.clefs.push(new Clef("F", x + 2, y + h/1.8, h/2))
this.notes = [];
for (var l=0; l<2; l++) {
var nx = this.x + this.h;
const ny = this.y + l * this.h * 0.7;
const ns = this.h / 20;
const ox = ns * 4;
for (var i=0; i<50; i++) {
const pitches = [];
const count = 1 + ((Math.random() * 4) | 0);
var min = 3, max = 3;
for (var c=0; c<count; c++) {
if ((nx + (pitches.length+2) * ns * 3) > (this.x + this.w)) break;
var pitch = 100;
while (Math.abs(pitch-min) > 4 || Math.abs(pitch-max) > 4) {
pitch = ((Math.random() * 11) | 0) - 2;
}
min = Math.min(min, pitch);
max = Math.min(max, pitch);
pitches.push(pitch);
}
if (pitches.length < 1) break;
//if ((nx + (pitches.length) * ns * 3) > (this.x + this.w)) break;
this.notes.push(new Note(nx, ny, ns, pitches));
nx += ox * pitches.length;
}
}
}
draw() {
const margin = this.h / 3;
const offset_y = (this.h - margin) / 9;
for (var i=0; i<5; i++) {
turtle.jump(this.x, this.y + offset_y*i);
turtle.goto(this.x + this.w, this.y + offset_y*i);
}
for (var i=0; i<5; i++) {
turtle.jump(this.x, this.y + offset_y*(i+5) + margin);
turtle.goto(this.x + this.w, this.y + offset_y*(i+5) + margin);
}
const count = 4;
const offset_x = this.w / (count-1);
for (var i=0; i<count; i++) {
turtle.jump(this.x + offset_x*i, this.y);
turtle.goto(this.x + offset_x*i, this.y + this.h);
}
this.clefs.forEach(c => c.draw());
this.notes.forEach(n => n.draw());
}
drawStart(end=false) {
const count1 = 5, count2 = 4, count3 = 2;
const offset_x = 0.125;
for (var i=0; i<count1+count2+count3; i++) {
if (i > count1 && i < count1+count2) continue;
turtle.jump(this.x + (end ? (this.w - offset_x*i) : (offset_x*i)), this.y);
turtle.goto(this.x + (end ? (this.w - offset_x*i) : (offset_x*i)), this.y + this.h);
}
}
drawEnd() { this.drawStart(true); } // Clear as mud
}
class Note {
constructor(x, y, scale, pitches) {
this.x = x; this.y = y; this.scale = scale; this.pitches = pitches;
}
draw() {
var max = Math.max(...this.pitches);
var min = Math.min(...this.pitches);
const middle = 3;
const dir = (max > middle) ? -1 : 1;
const line_y = this.y + (((dir > 0) ? (max + 4) : (max - 4)) + 0) * this.scale * dir;
const line_x = [];
for (var i=0; i<this.pitches.length; i++) {
const cx = this.x + i * this.scale * 3;
const cy = this.y + this.pitches[i] * this.scale;
turtle.jump(cx, cy);
for (var a = 0, r=0; r < this.scale; r += 0.0025, a += 0.1) {
const bx = cx + Math.cos(a) * r;
const by = cy + Math.sin(a) * r;
turtle.goto(bx, by);
}
turtle.goto(cx + this.scale * -dir, cy);
turtle.goto(cx + this.scale * -dir, line_y);
line_x.push(cx + this.scale * -dir);
}
const line_count = Math.random() * 2;
for (var i=0; i<line_count; i++) {
turtle.up();
line_x.forEach(lx => {
turtle.goto(lx, line_y - i * this.scale * dir * 0.5);
turtle.down();
});
}
}
}
class Clef {
constructor(type, x, y, h) {
this.x = x; this.y = y; this.h = h;
this.points = getPointsForClef(type);
}
draw() {
// TODO: Draw as curves
const scale = this.h / 10;
const od = 0.2;
for (var o=0; o<2; o++) {
for (var i=0; i<this.points.length-1; i++) {
const x1 = this.x + this.points[i][0] * scale + o * od;
const y1 = this.y + this.h - this.points[i][1] * scale;
const x2 = this.x + this.points[i+1][0] * scale + o * od;
const y2 = this.y + this.h - this.points[i+1][1] * scale;
turtle.jump(x1, y1);
turtle.goto(x2, y2);
}
}
}
}
function getPointsForClef(type) {
switch (type) {
case "F": return [ [5,5],[6,4],[5,3],[4,5],[5,6],[7,5],[5,2],[4,2] ];
case "G": return [ [5,5],[4,6],[5,6],[6,5],[4,4],[2,5],[5,6],[6,7],[4,8],[4,3],[4,2],[3,3],[4,4] ];
}
return [];
}
class Title {
constructor(x, y, w, h) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.generate();
}
draw() {
const size_f = this.h / 90;
const size_w = size_f * 8;
turtle.jump(this.x + this.w / 2 - this.text.length * size_w, this.y + this.h / 2);
text.print(turtle, this.text, size_f);
}
generate() {
const articles = [ "A", "The" ];
const adjectives = [ "Red", "Grand", "Little", "Wee", "Brilliant", "Velvet", "Round", "Shiny", "Black", "Gyrating", "Quiet", "Magnificent" ];
const nouns = [ "Boat", "House", "Forest", "River", "Sea", "Turtle", "Teacup", "Castle" ];
const prepositions = [ "In", "Out Of", "Through", "Over", "Underneath", "Behind", "Around" ];
var good_enough = false;
while (!good_enough) {
const words = [];
words.push(articles[(Math.random() * articles.length)|0])
words.push(adjectives[(Math.random() * adjectives.length)|0])
words.push(nouns[(Math.random() * nouns.length)|0])
if (Math.random() > 0.3) {
words.push(prepositions[(Math.random() * prepositions.length)|0])
words.push(articles[(Math.random() * articles.length)|0])
words.push(adjectives[(Math.random() * adjectives.length)|0])
words.push(nouns[(Math.random() * nouns.length)|0])
}
for (var i=0; i<words.length-1; i++) {
if (words[i] == "A" && [ "A", "E", "I", "O", "U", "Y" ].includes(words[i+1][0])) {
words[i] += 'n';
}
}
good_enough = true;
for (var i=0; i<words.length; i++) {
for (var j=i+1; j<words.length; j++) {
if (words[i] === words[j]) good_enough = false;
}
}
if (good_enough) this.text = words.join(' ');
}
}
}
class PageTurtle extends Turtle {
goto(x, y) {
const dest_x = (y === undefined) ? x[0] : x;
const dest_y = (y === undefined) ? x[1] : y;
const start_x = (this.last_x === undefined) ? 0 : this.last_x;
const start_y = (this.last_y === undefined) ? 0 : this.last_y;
const distance = /*Math.abs(x-sx);*/ Math.hypot(dest_x - start_x, dest_y - start_y);
var segments = 1;
const dd = 0.25;
while (distance / segments > dd) segments++;
for (var t=0; t<=segments; t++) {
const tx = start_x + (dest_x - start_x) * t / segments;
const ty = start_y + (dest_y - start_y) * t / segments;
super.goto(this.transform(tx, ty, t / segments));
}
this.last_x = dest_x;
this.last_y = dest_y;
}
x() { return (this.last_x === undefined) ? super.x() : this.last_x; }
y() { return (this.last_y === undefined) ? super.y() : this.last_y; }
transform(x, y) {
const op = 1 - perspective;
const px = x * (op + (y+100) / 200 * perspective);
const py = (((y + Math.sin(Math.abs(px) / -17) * 5) - 100) * op) + (100 * op);
return [px, py];
}
}
const text = new Text();
const turtle = curved ? new PageTurtle() : new Turtle();
new Page( 0, -80, 90, 160, systems).draw();
new Page(-90, -80, 90, 160, systems).draw();
////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
////////////////////////////////////////////////////////////////
function Text(){const s="br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefggmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfphnmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>cqfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrkshserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlpingkfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjdhcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wagsg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmhn<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnopmrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqporlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkcieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfehdkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_jbjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofpips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkdmepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsksmrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj[i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjcieigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhillnmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs".split(">").map(s=>[s.charCodeAt(0)-106,s.charCodeAt(1)-106,s.substr(2).split("<").map(s=>{const e=[];for(let p=0;p<s.length;p+=2)e.push(s.substr(p,2).split("").map(s=>s.charCodeAt(0)-106));return e})]);return new class{print(e,p,j=1,h=0,r=1){e.radians();let f=[e.x(),e.y()],o=e.h(),i=f;p.split("").map(p=>{const c=p.charCodeAt(0)-32;if(c<0)f=i=this.rotAdd([0,48*j],i,o);else if(c>96)f=this.rotAdd([16*j,0],i,o);else{const p=s[c],i=p[0]*j,d=p[1]*j;p[2].map(s=>{e.up(),s.map(s=>{e.goto(this.rotAdd([(s[0]-s[1]*h)*j-i,s[1]*j],f,o)),e.down()})}),f=this.rotAdd([(d-i)*r,0],f,o)}})}rotAdd(s,e,p){return[Math.cos(p)*s[0]-Math.sin(p)*s[1]+e[0],Math.cos(p)*s[1]+Math.sin(p)*s[0]+e[1]]}}}