Karplus strong guitar thingy

Yeah it's kinda meh

Log in to post a comment.

// inspired by https://www.youtube.com/watch?v=Aktb_dmY4vk
// essentially, pass a bandpassed saw wave with vibrato and a bit of noise into a short reverb
// 
// It is possible to not do the reverb, as the diffusion it does that creates the sound
// is essentially a number of parallel delays. A chorus of saw waves with random phases
// gives the same effect.

// bit slower, original is 210
ditty.bpm = 140;
input.hit = 800; //min=100, max=1000, step=1
input.damp = 6;//min=1, max=20, step=0.1
input.tuning = 0.5; //min=0.125, max=2, step=0.125

// Delay line
// from srtuss
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) {
        let x = this.p - (offset|0) - 1;
        x %= this.n;
        if (x < 0) x += this.n;
        return this.data[x];
    }
}

// SVF filter
// https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
class Simper {
    constructor() {
        this.ic1eq = 0;
        this.ic2eq = 0;
    }
    
    tick(v0, freq, q) {
        const g = Math.tan(Math.PI * freq);
        const k = 1 / q;
        const a1 = 1 / (1 + g * (g + k));
        const a2 = g * a1;
        const a3 = g * a2;
        
        // tick
        const v3 = v0 - this.ic2eq;
        const v1 = a1 * this.ic1eq + a2 * v3;
        const v2 = this.ic2eq + a2 * this.ic1eq + a3 * v3;
        
        // update
        this.ic1eq = 2 * v1 - this.ic1eq;
        this.ic2eq = 2 * v2 - this.ic2eq;
        
        // low, band output
        return [v2, v1];
    }
}


function softclip(x) {
    return x < -1 ? -1 : x > 1 ? 1 : 1.5*(1-x*x/3)*x;
}

function varsaw(p, formant) {
    let x = p-~~p;
    return (x - 0.5) * softclip(formant*x*(1-x)) * 2;
}

const osc = synth.def(
    class {
        constructor(options) {
            // frequencies
            this.freq = midi_to_hz(options.note);
            
            // phasor
            this.time = 0;
            
            // hit filter
            this.hit = new Simper();
            
            // delay line
            this.string = new Delayline(ditty.sampleRate);
            
            // damping filter
            this.damp = new Simper();
            
            // body resonance delays
            this.b1 = new Delayline(ditty.sampleRate);
            this.b2 = new Delayline(ditty.sampleRate);
            this.b3 = new Delayline(ditty.sampleRate);
            this.b4 = new Delayline(ditty.sampleRate);
        }
        process(note, env, tick, options) {
            // phasor times
            this.time += ditty.dt;
            
            // karplus strong
            const inp = this.hit.tick(this.time < 0.01 ? Math.random() * 2 - 1 : 0, ditty.dt * input.hit, 0.707)[0];
            const tap = ditty.sampleRate / (this.freq * input.tuning);
            
            const sam = this.string.tap(tap) * env.value;
            this.string.clock(this.damp.tick(sam + inp, this.freq * input.damp * ditty.dt, 0.707)[0]);
            
            // resonance
            const b1 = this.b1.tap(0.002 * ditty.sampleRate);
            const b2 = this.b2.tap(0.003 * ditty.sampleRate);
            const b3 = this.b3.tap(0.004 * ditty.sampleRate);
            const b4 = this.b4.tap(0.012 * ditty.sampleRate);
            this.b1.clock(sam + b1 * 0.2);
            this.b2.clock(sam + b2 * 0.7);
            this.b3.clock(sam + b3 * 0.3);
            this.b4.clock(sam + b4 * 0.5);
            
            return sam + b1 + b2 + b3 + b4;
        }
    }, {
        // attack parameters
        levels: [0, 1, 1, 0.9],
        times: [0.005, 4, 1],
        env: segenv,
    }
);

// === original ===
// Forked from "Wizards & Warriors (main menu)" by romaindurand
// https://dittytoy.net/ditty/3066954356

function melodyPattern(notes, baseNote) {
    return () => {
        for(i = 0; i < notes.length; i++) {
            if (notes[i] > 0) osc.play(notes[i], { duration: 0.5, pan: 0.2 - Math.random() * 0.1 });
            sleep(0.5);
            if (notes[i] > 0) osc.play(baseNote, { duration: 0.5, pan: 0.2 - Math.random() * 0.1 });
            sleep(0.5);
        }
    };
}

function simpleMelodyPattern(notes) {
    // calculate length for it to sound nice
    let lens = new Array(notes.length).fill(0.45);
    let last = 0;
    
    // every off note adds to the on length of the last on note
    for (let i = 0; i < lens.length; i++) {
        if (notes[i] === 0) lens[last] += 0.5;
        else last = i;
    }
    
    return () => {
        for(let i = 0; i < notes.length; i++) {
            if (notes[i] > 0) osc.play(notes[i], { duration: lens[i], pan: -0.2 - Math.random() * 0.1 });
            sleep(0.5);
        }
    };
}

const melodySeq0 = () => {
    melodyPattern([d5, e5, f5, g5], a4)();
    melodyPattern([bb4, d5, g5, f5], g4)();
    melodyPattern([e5, f5], c5)();
    melodyPattern([g5, c5], g4)();
    melodyPattern([bb4, a4, f5, e5], f4)();
    
    const notes0 = [d5, e5, f5, d5];
    melodyPattern(notes0, bb4)();
    //same as previous pattern with another base note
    melodyPattern(notes0, g4)();
    
    melodyPattern([e5, cs5, e5, cs5], a4)();
    melodyPattern([a5, cs5, a5, a5], a4)();
};

const melodySeq1 = () => {
    melodyPattern([f5, d5], a4)();
    simpleMelodyPattern([f5, g5, a5, a4])();
    simpleMelodyPattern([
        a5, bb4, d5, bb5, a5, bb4, d5, bb5,
        g5, g4, c5, g4, e5, f5, g5, c5,
        g5, a4, c5, a5, f5, a4, e5, c5,
        f5, bb4, d5, bb4, d5, e5, f5, bb4,
        f5, g4, bb4, g4, d5, e5, f5, g4
    ])();
    melodyPattern([f5, cs5, e5, cs5, a5, cs5], a4)();
    melodyPattern([a5, a5], cs5)();
};

const melodySeqEnd = simpleMelodyPattern([d5, a4, e5, f5]);

const bassPattern = simpleMelodyPattern([
        d3, 0, 0, 0, 0, d3, e3, f3,
        g3, 0, 0, 0, 0, g3, a3, bb3,
        c4, 0, 0, 0, 0, bb3, a3, g3,
        f3, 0, g3, 0, a3, 0, f3, 0,
        bb3, 0, 0, 0, 0, c4, bb3, a3,
        g3, 0, 0, 0, 0, a3, bb3, g3,
        a3, 0, 0, 0, a3, 0, 0, 0,
        a3, 0, g3, 0, f3, 0, e3, 0
    ]);

loop(() => {
    melodySeq0();
    melodySeq0();
    melodySeq1();
    melodySeq1();
    melodySeqEnd();
    melodySeqEnd();
    melodySeqEnd();
    simpleMelodyPattern([d5, 0, 0, 0])();
}, { name: 'melody', amp: 0.3 });

loop(() => {
    sleep(32);
    bassPattern();
    bassPattern();
    bassPattern();
    simpleMelodyPattern([d3, 0, 0, 0, 0, 0, 0, 0])();
    sleep(4);
}, { name: 'bass', amp: 0.5 });