// // 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}) );