Rubedo

With a velvet noise reverb

Log in to post a comment.

ditty.bpm = 80;

input.wet = 1.0; // min=0, max=1, step=0.01

// basic sine oscillator that somewhat sounds like a plucked String
// tuned vibrato to match the original song
const osc = synth.def(class {
    constructor(options) {
        this.phasel = Math.random();
        this.phaser = Math.random();
        this.delta = midi_to_hz(options.note) * ditty.dt;
        
        // vibrato
        this.vib = Math.random();
        this.ofs = ditty.dt * 4;
    }
    process(note, env, tick, options) {
        // phase
        this.phasel += this.delta + Math.sin(this.vib * 2 * Math.PI) * this.delta * 0.006;
        this.phasel -= this.phase|0;
        
        this.phaser += this.delta + Math.sin(this.vib * 2 * Math.PI) * this.delta * 0.006;
        this.phaser -= this.phase|0;
        
        // vibrato
        this.vib += this.ofs;
        this.vib -= this.phase|0;
        
        // fm modulator
        let fm_mod_l = Math.sin(this.phasel * 2 * Math.PI) * env.value
            + Math.sin(this.phasel * 4 * Math.PI) * env.value * env.value * 0.4;
        let fm_mod_r = Math.sin(this.phaser * 2 * Math.PI) * env.value
            + Math.sin(this.phaser * 4 * Math.PI) * env.value * env.value * 0.4;
        
        // fm carrier
        let fml = Math.sin(this.phasel * 2 * Math.PI + fm_mod_l) * env.value;
        let fmr = Math.sin(this.phaser * 2 * Math.PI + fm_mod_r) * env.value;
        
        return [fml, fmr];
    }
}, { attack: 0.001, decay: 2.0, sustain: 0.5, release: 0.5 });

// === reverb ===

// from struss
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n * 2);
    }
    
    clock(l, r) {
        this.data[this.p * 2 + 0] = l;
        this.data[this.p * 2 + 1] = l;
        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 * 2], this.data[x * 2 + 1]];
    }
}

// random biased towards 0
const biased_random = () => Math.random() ** 8;
const random_sign = () => Math.random() < 0.5 ? 1 : -1;

// velvet noise reverb
const reverb = filter.def(class {
    constructor(options) {
        // delay time
        const time = 2;
        
        // number of taps
        const taps = 256;
        
        // delay, 4 seconds total
        this.delay = new Delayline(ditty.sampleRate * time);
        
        // left taps
        this.taps_l = new Int32Array(taps)
            .fill(0)
            .map(_ => biased_random() * ditty.sampleRate * time * random_sign());
        
        // right taps
        this.taps_r = new Int32Array(taps)
            .fill(0)
            .map(_ => biased_random() * ditty.sampleRate * time * random_sign());
    }
    process(inp, options) {
        // into the Delayline
        this.delay.clock(inp[0], inp[1]);
        
        // velvet noise
        const reduce = (acc, x) => { 
            const [l, r] = this.delay.tap(Math.abs(x));
            return [l * Math.sign(x) + acc[0], r * Math.sign(x) + acc[1]] 
        };
        
        // left
        const [ll, lr] = this.taps_l.reduce(reduce, [0, 0]);
        
        // right
        const [rl, rr] = this.taps_r.reduce(reduce, [0, 0]);
        
        return [
            (ll + lr) * 0.5 * input.wet / Math.sqrt(this.taps_l.length) + (1 - input.wet) * inp[0], 
            (rr + rl) * 0.5 * input.wet / Math.sqrt(this.taps_l.length) + (1 - input.wet) * inp[1]
        ];
    }
});

// === melody ===

// play a few notes
function melodyPattern(notes) {
    for (let i = 0; i < notes.length; i++) {
        osc.play(notes[i], { duration: 0.5, amp: 0.4 + 0.6 * Math.random() });
        sleep(0.5);
    }
}

const pat0 = () => {
    for (let i = 0; i < 4; i++) melodyPattern([e3, b3, d4, a4]);
    for (let i = 0; i < 4; i++) melodyPattern([e3, b3, d4, gs4]);
    for (let i = 0; i < 4; i++) melodyPattern([e3, b3, d4, g4]);
    for (let i = 0; i < 4; i++) melodyPattern([e3, b3, d4, gs4]);
    // TODO other notes
};

loop( () => {
    pat0();
}, { name: 'Piano', amp: 0.9 }).connect(reverb.create());