ditty.bpm = 140; const { sin, cos, exp, pow, floor, tanh } = Math; const PI = Math.PI; const TAU = 2.0 * PI; const U32_MAX = 0xffffffff; const exp2 = ( x ) => pow( 2.0, x ); const arraySerial = ( n ) => [ ...Array( n ) ].map( ( _, i ) => i ); /** * A RNG. * @param {number} seed Put an i32 seed number. `1` would work fine */ function xorshift( seed ) { seed = seed ^ ( seed << 13 ); seed = seed ^ ( seed >>> 17 ); seed = seed ^ ( seed << 5 ); return seed; } /** * A kick drum tried to replicate the Syntakt BD HARD */ const kick = synth.def( ( phase, env, tick, options ) => { const t = tick_to_second( tick ); const freq = midi_to_hz( options.note ); const pitchEnvAmp = options.pitchEnvAmp ?? 3.0; const pitchEnvDecay = options.pitchEnvDecay ?? 0.03; const bodyGain = options.bodyGain ?? 1.5; const snapGain = options.snapGain ?? 1.5; const bodyAmp = options.bodyAmp ?? 0.5; const snapAmp = options.snapAmp ?? 0.3; const impulseAmp = options.impulseAmp ?? 0.2; let sum = 0.0; // body let pt = phase; pt -= pitchEnvAmp * freq * exp( -t / pitchEnvDecay ) * pitchEnvDecay; sum += bodyAmp * tanh( bodyGain * sin( TAU * pt ) ); // impulse const impulse = exp( -500.0 * t ); sum += impulseAmp * impulse; // snap sum += snapAmp * tanh( snapGain * sin( TAU * 3.0 * impulse ) ); return sum * env.value; } ); /** * A bunch of random sinewaves, which sounds like TR-808 hihat. * * Ref: https://dittytoy.net/ditty/59300f01a0 */ const shotgun = synth.def( class { constructor( options ) { const bullets = options.bullets ?? 64; const spread = options.spread ?? 2.0; const snap = options.snap ?? 0.0; let seed = options.seed ?? 1; this.states = arraySerial( 64 ).map( () => { seed = xorshift( seed ); const dice = seed / U32_MAX + 0.5; const freq = midi_to_hz( options.note ); let omega = exp2( spread * dice ); omega = lerp( omega, floor( omega + 0.5 ), snap ); omega *= freq * TAU; return { c: 2.0 * cos( omega * ditty.dt ), b1: 0.0, b2: sin( -omega * ditty.dt ), }; } ); } process( phase, env, tick, options ) { let sum = 0.0; this.states.map( ( state ) => { const b = state.b1 * state.c - state.b2; state.b2 = state.b1; state.b1 = b; sum += b; } ); const amp = 2.0 / this.states.length; return amp * sum * env.value; } } ); loop( () => { kick.play( c2, { attack: 0.0, release: 0.25, duration: 0.1, pitchEnvAmp: 3.0, pitchEnvDecay: 0.03, } ); sleep( 1.0 ); }, { name: 'Kick' } ); loop( () => { arraySerial( 16 ).map( ( i ) => { shotgun.play( c8, { attack: 0.001, release: exp2( 2.0 * ( ( i * 0.642 ) % 1 ) - 5.0 ), seed: 5, spread: 2.0, } ); sleep( 0.25 ); } ); }, { name: 'Hihat', amp: 0.7 } ); loop( () => { shotgun.play( d8, { attack: 0.001, release: 0.5, seed: 5, spread: 3.0, } ); sleep( 0.5 ); }, { name: 'Ride', amp: 0.5 } );