Arpeggios

Harping 🎸

Changing parameters might take a while until you hear it.

Reverb from Karplus synth with reverb by @srtuss

Log in to post a comment.

//
// RTFM You can find the DittyToy API Reference here: https://dittytoy.net/syntax
//

// reverb from https://dittytoy.net/ditty/827b1b3e63
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    clock(input) {
        this.lastOut = 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];
    }
}

function allpass(delayline, x, k) {
    var delayin = x - delayline.lastOut * k;
    var y = delayline.lastOut + k * delayin;
    delayline.clock(delayin);
    return y;
}

// Simple allpass reverberator, based on this article:
// http://www.spinsemi.com/knowledge_base/effects.html
const reverb = filter.def(class {
    constructor(options) {
        this.lastReturn = 0;
        this.krt = .7;
        this.delaylines = [];
        // Create several delay lines with random lengths
        for(var i = 0; i < 12; ++i) {
            this.delaylines.push(new Delayline(10 + Math.floor(Math.random() * 5000)));
        }
    }
    process(input, options) {
        var inv = input[0] + input[1];
        var v = this.lastReturn;
        // Let the signal pass through the loop of delay lines. Inject input signal at multiple locations.
        v = allpass(this.delaylines[0], v + inv, .5);
        v = allpass(this.delaylines[1], v, .5);
        this.delaylines[2].clock(v);
        v = this.delaylines[2].lastOut * this.krt;
        v = allpass(this.delaylines[3], v + inv, .5);
        v = allpass(this.delaylines[4], v, .5);
        this.delaylines[5].clock(v);
        v = this.delaylines[5].lastOut * this.krt;
        v = allpass(this.delaylines[6], v + inv, .5);
        v = allpass(this.delaylines[7], v, .5);
        this.delaylines[8].clock(v);
        v = this.delaylines[8].lastOut * this.krt;
        v = allpass(this.delaylines[9], v + inv, .5);
        v = allpass(this.delaylines[10], v, .5);
        this.delaylines[11].clock(v);
        v = this.delaylines[11].lastOut * this.krt;
        this.lastReturn = v;
        // Tap the delay lines at randomized locations and accumulate the output signal.
        var ret = [0, 0];
        ret[0] += this.delaylines[2].tap(111);
        ret[1] += this.delaylines[2].tap(2250);
        ret[0] += this.delaylines[5].tap(311);
        ret[1] += this.delaylines[5].tap(1150);
        ret[0] += this.delaylines[8].tap(511);
        ret[1] += this.delaylines[8].tap(50);
        ret[0] += this.delaylines[11].tap(4411);
        ret[1] += this.delaylines[11].tap(540);
        // Mix wet + dry signal.
        ret[0] = ret[0] * .1 + input[0];
        ret[1] = ret[1] * .1 + input[1];
        // Slight stereo widening:
        var m = (ret[0] + ret[1]) * .5;
        var s = (ret[1] - ret[0]) * .5;
        ret[0] = m + s * 1.5;
        ret[1] = m - s * 1.5;
        return ret;
    }
});


// source: https://dittytoy.net/ditty/6f30b0885d
function tri(t) { return -1 + 4*Math.abs(t%1 - 0.5); }
const varsaw = synth.def(
    (phase, env, tick) => { let x = (phase%1); return (x-0.5) * clamp01(15*x*(1-x)*(1 + 0.1*tri(tick))) * env.value; },
    {attack:0.02, release:0.25, decay:0.5, sustain:0.15, duration: 1.5}
);


input.type = 1; // min=0, max=1, step=1 (Linear, Mirror)
input.sequenceLength = 16; // min=4, max=16, step=4
input.sequence = 1; // min=0, max=4, step=1 (+1, +2 -1, +3 -1 -1, +2 0 +2 -1 -2 0, custom)
input.stepSize = 2; // min=1, max=2, step=1 


const scaleNotesMinor = scale(f2, scales['minor'], 64);
const scaleNotesHarmonic = scale(f2, scales['harmonic_minor'], 64);
const scaleNotesMajor = scale(f2, scales['major'], 64);

const scalesToChoose = new Array(12).fill(scaleNotesMajor);
scalesToChoose.push(scaleNotesHarmonic);
scalesToChoose.push(scaleNotesMinor);

const ease = t => t * t * (3.0 - 2.0 * t);

let stepSequence1 = [2,-1];
let stepSequence2 = [3,-1,-1];
let stepSequence3 = [2,0,2,-1,-2,0];
let stepSequenceCustom = [3,-2,-1,1]; // write your own custom arpeggio here

let stepSequences=[stepSequence1,stepSequence2,stepSequence3,stepSequenceCustom];

let step = 0;
loop( () => {
    let scaleNotes = scalesToChoose.choose();
    let size = input.sequenceLength;
    let sequence = new Array(size).fill(0).map((_,idx) => idx*input.stepSize);
    if (input.sequence >= 1) {
        let stepSequence = stepSequences[input.sequence-1];
        let tmp = [];
        let acc=0;
        for(let i=0;i<sequence.length;i++) {
            acc+=stepSequence[i%stepSequence.length];
            tmp.push(sequence[acc]);
        }
        sequence = tmp;
    }
    
    if (input.type == 1) {
        sequence = sequence.mirror()
    }
    
    let stage = [0,0,0,1,2,3,4,5,6,7].choose();
    for (let i=0; i<sequence.length; i++) {
        if (scaleNotes[stage + (sequence[i]-1)]) varsaw.play( scaleNotes[stage + (sequence[i]-1)] , {amp: 0.5 + 0.5*ease(1-i/sequence.length), pan: Math.sin(1-i/sequence.length*3) * 0.5 });
        sleep( 0.25 );
    }
}, { name: 'arpeggio' }).connect(reverb.create());