// === Instruments ===
function softsaw(p, formant) {
const x = p-~~p;
// polyblep
const f = Math.abs(formant);
const s = Math.max(0, Math.abs((f + 1) * (2*x - 1)) - f);
// saw - polyblep
return 2 * x - 1 - s*s * Math.sign(x - 0.5);
}
// from my Piano ditty, https://dittytoy.net/ditty/bc17a034bf
// modified slightly
const osc = class {
constructor(options) {
// The value of the note argument of the play call is retrievable via options.note.
this.phases = new Float32Array(8).map(_ => Math.random());
this.damping = new Float32Array(8).map(_ => Math.random());
// frequency
this.freq = midi_to_hz(options.note) * ditty.dt;
// phasor
this.time = Math.random();
}
process(note, env, tick, options) {
// phasor times
this.time += ditty.dt * 3;
this.time -= this.time|0;
let phase = Math.sin(this.time * 2 * Math.PI);
// total
let l = 0.0;
let r = 0.0;
// all saw waves
for (let i = 0; i < this.phases.length; i++) {
// sample wave
const v = softsaw(
this.phases[i],
this.damping[i] * (options.soft_decay * env.value + options.soft_base)
) * env.value;
// update phase
this.phases[i] += this.freq * (options.detune||1) + phase * ditty.dt * (options.vibrato||0);
this.phases[i] -= this.phases[i]|0;
// stereo
if (i < this.phases.length / 2) l += v;
else r += v;
}
return [l * 0.707, r * 0.707];
}
};
const piano = synth.def(
osc, {
soft_decay: 15,
soft_base: 0,
detune: 0.5,
levels: [0, 1, 0, 0],
times: [0.001, 6, 1],
env: segenv,
}
);
const cello = synth.def(
osc, {
soft_decay: 10,
soft_base: 10,
vibrato: 1.5,
levels: [0, 1, 0.8, 0],
times: [0.1, 0.2, 0.2],
env: segenv,
}
);
// === Song utils ===
// play several notes at the same time, for a given duration
function playMany(instr, duration, ...notes) {
for (const note of notes) {
// note can be multiple in parallel
for (let i = 1; i < note.length; i++) instr.play(note[i], { duration: duration * note[0] });
// duration is the first item in the sequence
sleep(duration * note[0]);
}
}
// play one note at a time
function playOne(instr, duration, ...notes) {
for (const note of notes) {
// just one
if (note > 0) instr.play(note, { duration: duration });
// no altered duration here
sleep(duration);
}
}
// === Song ===
ditty.bpm = 133;
// opening loop
function lead1(instr) {
for (let i = 0; i < 4; i++) {
playMany(instr, 1/4, [1, c5, g4], [1, c5, g4], [1, g4], [1, g4]);
playMany(instr, 1/4, [1, d5, as4], [1, d5, as4], [1, g4], [1, g4]);
playMany(instr, 1/4, [1, ds5, as4], [1, ds5, as4], [1, g4], [1, d5, as4, g4]);
playMany(instr, 1/4, [1, d5, as4], [1, g4], [1, c5, g4], [1, c5, g4]);
}
}
function bass1(instr) {
for (let i = 0; i < 2; i++) {
playMany(instr, 8, [1, g3, c3, g2]);
}
}
// melody 1 loop
function lead2(instr) {
playOne(instr, 1/4, d5, ds5, f5, ds5);
playOne(instr, 3/2, d5);
playOne(instr, 1/2, g4, c5, ds5);
playOne(instr, 3/2, d5);
playOne(instr, 1/2, d5);
playOne(instr, 1/1, g5);
playOne(instr, 1/1, d5);
playOne(instr, 2/1, d5);
playOne(instr, 1/2, d5, ds5, f5, g5);
playOne(instr, 3/2, f5);
playOne(instr, 1/4, g5, f5);
playOne(instr, 2/1, c5);
// part 2
playOne(instr, 5/2, ds5);
playOne(instr, 1/4, c5, d5, ds5, g5, c6, ds6);
playOne(instr, 2/1, d6);
playOne(instr, 1/1, a5, d6);
playOne(instr, 3/1, c6);
playOne(instr, 1/4, c6, d6, c6, a5);
playOne(instr, 4/1, b5);
}
function bass2(instr) {
playMany(instr, 4, [1, c4, g3, c3]);
playMany(instr, 4, [1, b3, g3, d3, b2]);
playMany(instr, 4, [1, as3, f3, d3, as2]);
playMany(instr, 4, [1, a3, f3, c3, a2]);
// part 2
playMany(instr, 4, [1, gs3, ds3, c3, gs2]);
playMany(instr, 4, [1, d4, a3, fs3, d3]);
playMany(instr, 4, [1, c4, g3, d3, g2]);
playMany(instr, 4, [1, b3, g3, d3, b2]);
}
// melody 2 loop
function lead3(instr) {
playOne(instr, 5/2, g5);
playOne(instr, 1/2, ds5, f5, g5);
playOne(instr, 2/1, f5, d5);
playOne(instr, 3/1, f5);
playOne(instr, 1/4, f5, g5, f5, d5);
playOne(instr, 4/1, e5);
// part 2
playOne(instr, 5/2, g5);
playOne(instr, 1/2, ds5, f5, g5);
playOne(instr, 2/1, f5);
playOne(instr, 1/1, d5, as4);
playOne(instr, 4/1, as4, a4);
}
// melody 3 loop
function lead4(instr) {
playOne(instr, 5/2, g5);
playOne(instr, 1/2, ds5, f5, g5);
playOne(instr, 2/1, f5, d5);
playOne(instr, 3/1, f5);
playOne(instr, 1/4, f5, g5, f5, d5);
playOne(instr, 5/2, e5);
playOne(instr, 1/2, e5);
playOne(instr, 1/2, g5, c6);
// part 2
playOne(instr, 5/2, ds6);
playOne(instr, 1/2, ds6, f6, g6);
playOne(instr, 2/1, f6);
playOne(instr, 1/1, d6);
playOne(instr, 1/1, as5);
playOne(instr, 3/1, as5);
sleep(1/4);
playOne(instr, 1/4, c6, as5, g5);
playOne(instr, 4/1, a5);
}
function bass4(instr) {
// same as melody 2
bass3(instr);
}
function bass3(instr) {
playMany(instr, 4, [1, c4, gs3, ds3]);
playMany(instr, 4, [1, d4, c4, as3, f3]);
playMany(instr, 4, [1, f4, c4, g3, c3]);
playMany(instr, 4, [1, e4, c4, g3, e3]);
// part 2
playMany(instr, 4, [1, c4, gs3, ds3]);
playMany(instr, 4, [1, d4, c4, as3, f3]);
playMany(instr, 4, [1, c4, as3, f3, as2]);
playMany(instr, 4, [1, c4, a3, f3, c3]);
}
loop(() => {
lead1(piano);
lead2(piano);
lead2(piano);
lead3(piano);
lead4(piano);
}, { name: 'lead', amp: 0.5 });
loop(() => {
bass1(cello);
bass2(cello);
bass2(cello);
bass3(cello);
bass4(cello);
}, { name: 'bass', amp: 0.5 });