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