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.phase = 0.0;
        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.phase += this.delta + Math.sin(this.vib * 2 * Math.PI) * this.delta * 0.012;
        this.phase -= this.phase|0;
        
        // vibrato
        this.vib += this.ofs;
        this.vib -= this.phase|0;
        
        // fm modulator
        let fm_mod = Math.sin(this.phase * 2 * Math.PI) * env.value
            + Math.sin(this.phase * 4 * Math.PI) * env.value * env.value * 0.4;
        
        // fm carrier
        let fm = Math.sin(this.phase * 2 * Math.PI + fm_mod) * env.value;
        
        return fm;
    }
}, { attack: 0.0, decay: 2.0, sustain: 0.0, release: 0.0 });

// === reverb ===

// from struss
class Delayline {
    constructor(n) {
        this.n = ~~n;
        this.p = 0;
        this.lastOut = 0;
        this.data = new Float32Array(n);
    }
    
    clock(input) {
        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];
    }
}

// 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) {
        // down to mono
        const mono = (inp[0] + inp[1]) * 0.5;
        
        // into the Delayline
        this.delay.clock(mono);
        
        // velvet noise
        const reduce = (acc, x) => this.delay.tap(Math.abs(x)) * Math.sign(x) + acc;
        
        // left
        const l = this.taps_l.reduce(reduce, 0) / Math.sqrt(this.taps_l.length);
        
        // right
        const r = this.taps_r.reduce(reduce, 0) / Math.sqrt(this.taps_r.length);
        
        return [l * input.wet + (1 - input.wet) * inp[0], r * input.wet + (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());