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());