Music sheet

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]]}}}