Kick and Shotgun

wip

Log in to post a comment.

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