Dittytoy

Over the past few months, I have spent some free time developing Dittytoy: an online platform that allows you to create music using a minimalistic javascript API.

dittytoy.net/
dittytoy.net/syntax#envelopes

#text #font #dittytoy #adsr

Log in to post a comment.

// Forked from "Hello, world" by reinder
// https://turtletoy.net/turtle/1713ddbe99

// Print "Hello, world". Created by Reinder Nijhoff 2019 - @reindernijhoff
// The MIT License
//
// https://turtletoy.net/turtle/1713ddbe99
//

const turtle = new Turtle(-25,-5);
const text = new Text();

const attack  = 0.2; // min=0, max=2, step=0.01
const decay   = 0.2; // min=0, max=2, step=0.01
const sustain = 0.5;   // min=0, max=2, step=0.01
const release = 0.5; // min=0, max=2, step=0.01

const attack_level = 1; // min=0, max=1, step=0.01
const decay_level = 0.45; // min=0, max=1, step=0.01
const sustain_level = 0.75; // min=0, max=1, step=0.01

const scale = 105; // min=1, max=200, step=0.1
const yScale = 0.4; // min=0, max=1, step=0.01
const xOffset = -88; // min=-100, max=100, step=1
const yOffset = 22; // min=-100, max=100, step=1
const fontScale = .15; // min=0, max=.2, step=0.01


const t = p => [ p[0]*scale + xOffset, p[1]*scale + yOffset ];

function line(s, ...e) {
    turtle.jump(t(s));
    e.forEach( _ => turtle.goto(t(_)));
}

function dashed_line(s, ...e) {
    const d = new Dither(turtle, (_, l) => l % 3 > 1 ? 1 : 0);
    turtle.jump(t(s));
    e.forEach( _ => d.goto(t(_)));
}

function y(_) {
    return -0.05-_*yScale;
}

function drawSegment(s, e, str) {
    line(add(s,[0.03,0]), add(e,[-0.03,0]));

    turtle.jump(t(s));
    turtle.goto(t(add(s, [ 0.03, 0.015])));
    turtle.goto(t(add(s, [ 0.03,-0.015])));
    turtle.goto(t(s));
    
    turtle.jump(t(e));
    turtle.goto(t(add(e, [-0.03, 0.015])));
    turtle.goto(t(add(e, [-0.03,-0.015])));
    turtle.goto(t(e));
    
    turtle.jump(-500,-500);
    text.print(turtle, str, fontScale);
    const w = 500 + turtle.x();
    turtle.jump(t([ (s[0]+e[0])*.5 - w/(2*scale) , 0.05]) );
    text.print(turtle, str, fontScale);
}

function drawHeight(x, h, str) {
    const width = attack + decay + sustain + release;
    
    dashed_line([x, y(0)], [x, y(h)], [width, y(h)]);
    
    turtle.jump(t([width + 0.025, y(h)]));
    text.print(turtle, str, fontScale);
}


let x = 0;
drawSegment( [x, 0], [x + attack, 0], "attack" );

x += attack;
drawHeight(x, attack_level, "attack_level" );
drawSegment( [x, 0], [x + decay, 0], "decay" );

x += decay;
drawHeight(x, decay_level, "decay_level" );
drawSegment( [x, 0], [x + sustain, 0], "sustain" );

x += sustain;
drawHeight(x, sustain_level, "sustain_level" );
drawSegment( [x, 0], [x + release, 0], "release" );

line( [0, y(0)], 
      [attack, y(attack_level)],
      [attack + decay, y(decay_level)],
      [attack + decay + sustain, y(sustain_level)],
      [attack + decay + sustain + release, y(0)],
     );

function add(a,b) { return [a[0]+b[0],a[1]+b[1]]; }
function sub(a,b) { return [a[0]-b[0],a[1]-b[1]]; }
function dist_sqr(a,b) { return (a[0]-b[0])**2+(a[1]-b[1])**2; }
function dist(a,b) { return Math.sqrt(dist_sqr(a,b)); }
function lerp(a,b,t) { return [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t]; }

function Dither(turtle, densFunc) {
    class Dither {
        constructor(turtle, densFunc) {
            this.turtle = turtle;
            this.densFunc = densFunc;
            this.dist = 0;
        }
        goto(dest) {
            const p = this.turtle.pos();
            const d = dist(p, dest);
            const startDist = this.dist, steps = Math.max(d*5, 1);
            for (let i=0; i<=steps; i++) {
                const c = lerp(p, dest, i/steps);
                if (this.densFunc(c, startDist + d * i/steps) <= 0) {
                    this.turtle.up();
                } else {
                    this.turtle.down();
                }
                this.turtle.goto(c);
            }
            this.dist += d;
        }
    }
    return new Dither(turtle, densFunc);
}

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
////////////////////////////////////////////////////////////////

function Text() {
    class Text {
        print (t, str, scale = 1, italic = 0, kerning = 1) {
            t.radians();
            let pos = [t.x(), t.y()], h = t.h(), o = pos;
            str.split('').map(c => {
                const i = c.charCodeAt(0) - 32;
                if (i < 0 ) {
                    pos = o = this.rotAdd([0, 48*scale], o, h);
                } else if (i > 96 ) {
                    pos = this.rotAdd([16*scale, 0], o, h);
                } else {
                    const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2];
                    paths.map( p => {
                        t.up();
                    	p.map( s=> {
                        	t.goto(this.rotAdd([(s[0]-s[1]*italic)*scale - lt, s[1]*scale], pos, h));
                        	t.down();
                        });
                    });
                    pos = this.rotAdd([(rt - lt)*kerning, 0], pos, h);
                }
            });
        }
        rotAdd (a, b, h) {
            return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], 
                    Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];
        }
    }
    
const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefg'+
'gmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfph'+
'nmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>c'+
'qfZh\\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^jfmfogphqkqmppnrk'+
'shserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlping'+
'kfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjd'+
'hcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wag'+
'sg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmh'+
'n<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_oapcqfqkpnop'+
'mrjscs>`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_daccbfbkcnd'+
'pfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqpo'+
'rlshsercp>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>fnkc'+
'ieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfeh'+
'dkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_j'+
'bjs<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<eihfjemeofp'+
'ips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkd'+
'mepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsks'+
'mrpo<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`kbjci'+
'eigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhill'+
'nmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map(
    r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret 
    = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0)
    -106));} return ret; })]; });

    return new Text();
}