Cello but without the reverb trick, instead multiple saw waves.
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.tuning = 0.5; //min=0.125, max=2, step=0.125
input.adsr_attack = 0.05; //min=0.01, max=1, step=0.01
input.adsr_decay = 0.05; //min=0.01, max=4, step=0.01
input.adsr_sustain = 0.8; //min=0, max=1, step=0.01
input.adsr_release = 0.05; //min=0.01, max=4, step=0.01
input.fold = 0.2; //min=0.1, max=5, step=0.01
input.soft_base = 40; //min=0, max=50, step=1
input.soft_random = 20; //min=0, max=50, step=1
input.vibrato_amount = 3.5; //min=0, max=5, step=0.01
input.vibrato_freq = 32; //min=0, max=60, step=0.01
input.loss_freq = 500; //min=100, max=8000, step=1
input.loss_q = 1.3;//min=0.7, max=4, step=0.1
input.loss_wet = 0.5; //min=0, max=1, step=0.01
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;
}
/// envelope
class Env {
constructor(sr) {
this.dt = 1 / sr;
this.v = 0;
this.decaying = false;
}
tick(note_on, attack, decay, sustain, release) {
if (note_on && this.v < 1.0 && !this.decaying) {
this.v += this.dt / Math.max(attack, this.dt);
this.v = Math.min(this.v, 1.0);
return this.v;
}
if (note_on) {
this.decaying = true;
this.v += (sustain - this.v)
* (1 - Math.exp(-Math.PI * 2 * this.dt / Math.max(decay, this.dt)));
return this.v;
}
if (!note_on) {
this.decaying = true;
this.v -= this.v
* (1 - Math.exp(-Math.PI * 2 * this.dt / Math.max(release, this.dt)));
return this.v;
}
}
}
// SVF filter
// https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
class Svf {
constructor(sr) {
this.dt = 1 / sr;
this.ic1eq = 0;
this.ic2eq = 0;
}
tick(v0, freq, q) {
const g = Math.tan(Math.PI * freq * this.dt);
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;
// bandpass output
return v1;
}
}
const osc = synth.def(
class {
constructor(options) {
// envelope
this.adsr = new Env(ditty.sampleRate);
this.time = 0;
// The value of the note argument of the play call is retrievable via options.note.
this.phases = new Float32Array(16).map(_ => Math.random());
this.damping = new Float32Array(16).map(_ => Math.random());
// frequencies
this.freq = midi_to_hz(options.note) * ditty.dt;
this.vibrato = Math.random();
// filters
this.bpl = new Svf(ditty.sampleRate);
this.bpr = new Svf(ditty.sampleRate);
}
process(note, env, tick, options) {
// envelope
this.time += ditty.dt;
const val = this.adsr.tick(
this.time < tick_to_second(options.duration),
input.adsr_attack,
input.adsr_decay,
input.adsr_sustain,
input.adsr_release
);
// vibrato
this.vibrato += ditty.dt * input.vibrato_freq;
// 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 = varsaw(this.phases[i], input.soft_base - this.damping[i] * input.soft_random) * val;
// update phase
const dt = this.freq + Math.sin(this.vibrato) * input.vibrato_amount * ditty.dt;
this.phases[i] += dt * input.tuning;
// stereo
if (i < this.phases.length / 2) l += v;
else r += v;
}
// folding
l = Math.sin(l * input.fold) / input.fold;
r = Math.sin(r * input.fold) / input.fold;
// filter
l = this.bpl.tick(l, input.loss_freq, input.loss_q) * input.loss_wet + (1 - input.loss_wet) * l;
r = this.bpr.tick(r, input.loss_freq, input.loss_q) * input.loss_wet + (1 - input.loss_wet) * r;
return [l * 0.707, r * 0.707];
}
}, {
// attack parameters
attack: 0.01,
decay: 2.05,
sustain: 0.0,
release: 0.05,
//env: adsr,
}
);
// === 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++) {
osc.play(notes[i], { duration: 0.5, pan: 0.2 - Math.random() * 0.1 });
sleep(0.5);
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++) {
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 });