Karplus without the waveguide
Repeat noise and then filter it, doesn't sound too great
Log in to post a comment.
// bit slower, original is 210
ditty.bpm = 140;
input.filter = 1600; //min=10, max=10000, step=1
// float hash
// Copyright (c)2014 David Hoskins
// see https://www.shadertoy.com/view/4djSRW
const hash11 = p => {
p = p * 0.1031 - Math.floor(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return p - Math.floor(p);
};
// SVF filter
// https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
class Simper {
constructor() {
this.ic1eq = 0;
this.ic2eq = 0;
}
tick(v0, freq, q) {
const g = Math.tan(Math.PI * freq);
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;
// low, band output
return [v2, v1];
}
}
const osc = synth.def(
class {
constructor(options) {
// sample index
this.idx = 0;
// filter state
this.fl = new Simper();
this.fr = new Simper();
}
process(note, env, tick, options) {
// increment index
this.idx += 2;
// wrap around if it's too big
if (this.idx > 4 * ditty.sampleRate / midi_to_hz(note)) this.idx = 0;
// random number gen
const rng_l = hash11(this.idx + 0) * 8 - 4;
const rng_r = hash11(this.idx + 1) * 8 - 4;
// lowpass filter
const l = this.fl.tick(rng_l, input.filter * ditty.dt * env.value, 0.707)[0];
const r = this.fr.tick(rng_r, input.filter * ditty.dt * env.value, 0.707)[0];
// final value
return [env.value * l, env.value * r];
}
}, {
// attack parameters
levels: [0, 1, 0, 0],
times: [0.005, 4, 1],
env: segenv,
}
);
// === 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++) {
if (notes[i] > 0) osc.play(notes[i], { duration: 0.5, pan: 0.2 - Math.random() * 0.1 });
sleep(0.5);
if (baseNote > 0) 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++) {
if (notes[i] > 0) 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 });