//
// Similar Dittytoy script
// Reference: https://Dittytoy.net/syntax
// Example user ditties: https://dittytoy.net/user/Dittytoy
//
// ------------------ Reverb Filter ------------------
class Imprint {
constructor(freq, absorption, weight=1) {
this.x = 0;
this.v = 0;
this.a = 0;
this.weight = weight;
this.freq = freq;
this.absorption = absorption;
}
sample(s) {
// Add some randomness for an airy reverb tail
this.v *= this.absorption;
this.v += s + (Math.random() - 0.5);
this.v -= this.x;
this.x += this.v * this.freq;
return this.x * this.weight;
}
}
let reverb = filter.def(class {
constructor(options) {
this.mem = [];
this.maxWeight = 0;
for (let i = 0; i < 512; i++) {
let freq = Math.random() * options.freq / 44100;
let weight = Math.random();
this.maxWeight += weight;
this.mem.push(new Imprint(freq, 1 - options.decay, weight));
}
}
process(input) {
const s = input[0];
let out = 0;
for (let imprint of this.mem) {
out += imprint.sample(s);
}
out /= this.maxWeight;
return [out, out];
}
});
// ------------------ Synth Definition ------------------
const wave = synth.def(
class {
constructor() {
this.phase = 0;
}
process(note, env, tick, options) {
// Create a simple “squirrely” saw wave
const freq = midi_to_hz(note);
// Add a tiny random detuning for a less static tone
this.phase += freq * ditty.dt + Math.random() * 0.00001;
return ((this.phase) % 2 - 1) * env.value;
}
}
);
// ------------------ BPM & Globals ------------------
ditty.bpm = 95; // set a slower groove
// ------------------ Patterns / Combos ------------------
//
// Each combo is a function that plays some notes,
// followed by a list of possible "next states" to pick from
//
let combos = [
[
() => {
// A repeating two-note motif
wave.play(c4, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(g4, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(ds4, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(as4, {attack: 0.01, release: 0.75, duration: 0.25});
sleep(0.5);
},
[1, 2] // possible transitions
],
[
() => {
// A simple ascending phrase
wave.play(c4, {attack: 0.01, release: 0.3, duration: 0.25});
sleep(0.25);
wave.play(ds4, {attack: 0.01, release: 0.3, duration: 0.25});
sleep(0.25);
wave.play(f4, {attack: 0.01, release: 0.3, duration: 0.25});
sleep(0.25);
wave.play(gs4, {attack: 0.01, release: 1, duration: 0.25});
sleep(1);
},
[0, 2, 3] // possible transitions
],
[
() => {
// Descending line
wave.play(as4, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(g4, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(f4, {attack: 0.01, release: 0.75, duration: 0.25});
sleep(0.5);
wave.play(ds4, {attack: 0.01, release: 1.5, duration: 0.25});
sleep(1);
},
[0, 1] // possible transitions
],
[
() => {
// Bass figure
wave.play(c3, {attack: 0.02, release: 0.5, duration: 0.25});
sleep(0.75);
wave.play(as2, {attack: 0.02, release: 0.75, duration: 0.25});
sleep(0.75);
wave.play(g2, {attack: 0.02, release: 0.5, duration: 0.25});
sleep(0.75);
wave.play(ds3, {attack: 0.02, release: 1, duration: 0.25});
sleep(1);
},
[1, 2, 0] // possible transitions
]
];
// ------------------ Utility to pick random next combo ------------------
function pickRandom(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// ------------------ Main Loop #1 ------------------
let state = 0;
loop(() => {
combos[state][0]();
// Move to next state from possible transitions
state = pickRandom(combos[state][1]);
}, { name: 'Melodic Loop' })
.connect(
reverb.create({freq: 2000, decay: 0.0005})
);
// ------------------ Main Loop #2 ------------------
loop(() => {
// Alternate a short phrase
wave.play(c2, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(ds2, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(0.5);
wave.play(g2, {attack: 0.01, release: 0.5, duration: 0.25});
sleep(1.0);
}, { name: 'Bass Loop' })
.connect(
reverb.create({freq: 2000, decay: 0.001})
);