text: // Gutted for js13k and modified to use Float32 buffers directly
  // ~ Dominic Szablewski, phoboslab.org, Sep 2018
  
  // Almost re-written for for jsk13 2019. Oscilators now use a lookup table
  // instead of calling functions. This and various other changes result in a
  // ~10x performance increase and smaller file size.
  // ~ Dominic Szablewski, phoboslab.org, Sep 2019
  
  // Again updated for js13k 2021. Song and sound definitions are now just arrays
  // instead of objects.
  
  //
  // Sonant-X
  //
  // Copyr (c) 2014 Nicolas Vanhoren
  //
  // Sonant-X is a fork of js-sonant by Marcus Geelnard and Jake Taylor. It is
  // still published using the same license (zlib license, see below).
  //
  // Copyr (c) 2011 Marcus Geelnard
  // Copyr (c) 2008-2009 Jake Taylor
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //	claim that you wrote the original software. If you use this software
  //	in a product, an acknowledgment in the product documentation would be
  //	appreciated but is not required.
  //
  // 2. Altered source versions must be plainly marked as such, and must not be
  //	misrepresented as being the original software.
  //
  // 3. This notice may not be removed or altered from any source
  //	distribution.
  
  let audio_ctx;
  let AUDIO_SAMPLERATE = 44100; // Samples per second
  let AUDIO_TAB_SIZE = 4096;
  let AUDIO_TAB_MASK = AUDIO_TAB_SIZE - 1;
  let AUDIO_TAB = new Float32Array(AUDIO_TAB_SIZE * 4); // 4 oscilators
  
  let audio_init = () => {
    // This function needs to be called in response to a user action, as it
    // tries to activate the audio context.
    audio_ctx = new AudioContext();
    audio_ctx.resume();
  
    // Generate the lookup tables
    for (let i = 0; i < AUDIO_TAB_SIZE; i++) {
      AUDIO_TAB[i] = Math.sin((i * 6.283184) / AUDIO_TAB_SIZE); // sin
      AUDIO_TAB[i + AUDIO_TAB_SIZE] = AUDIO_TAB[i] < 0 ? -1 : 1; // square
      AUDIO_TAB[i + AUDIO_TAB_SIZE * 2] = i / AUDIO_TAB_SIZE - 0.5; // saw
      AUDIO_TAB[i + AUDIO_TAB_SIZE * 3] = i < AUDIO_TAB_SIZE / 2 ? i / (AUDIO_TAB_SIZE / 4) - 1 : 3 - i / (AUDIO_TAB_SIZE / 4); // tri
    }
  };
  
  let audio_play = (buffer, volume = 1, loop = 0, pan = 0) => {
    let gain = audio_ctx.createGain(),
      source = audio_ctx.createBufferSource(),
      panner = audio_ctx.createStereoPanner();
    gain.gain.value = volume;
    gain.connect(audio_ctx.destination);
    panner.connect(gain);
    panner.pan.value = pan;
    source.buffer = buffer;
    source.loop = loop;
    source.connect(panner);
    source.start();
  };
  
  let audio_get_ctx_buffer = (buf_l, buf_r) => {
    let buffer = audio_ctx.createBuffer(2, buf_l.length, AUDIO_SAMPLERATE);
    buffer.getChannelData(0).set(buf_l);
    buffer.getChannelData(1).set(buf_r);
    return buffer;
  };
  
  let audio_generate_sound = (
    row_len,
    note,
    buf_l,
    buf_r,
    write_pos,
    // Instrument properties
    osc1_oct,
    osc1_det,
    osc1_detune,
    osc1_xenv,
    osc1_vol,
    osc1_waveform,
    osc2_oct,
    osc2_det,
    osc2_detune,
    osc2_xenv,
    osc2_vol,
    osc2_waveform,
    noise_fader,
    attack,
    sustain,
    release,
    master,
    fx_filter,
    fx_freq,
    fx_resonance,
    fx_delay_time,
    fx_delay_amt,
    fx_pan_freq_p,
    fx_pan_amt,
    lfo_osc1_freq,
    lfo_fx_freq,
    lfo_freq_p,
    lfo_amt,
    lfo_waveform
  ) => {
    let osc_lfo_offset = lfo_waveform * AUDIO_TAB_SIZE;
    let osc1_offset = osc1_waveform * AUDIO_TAB_SIZE;
    let osc2_offset = osc2_waveform * AUDIO_TAB_SIZE;
    let fx_pan_freq = Math.pow(2, fx_pan_freq_p - 8) / row_len;
    let lfo_freq = Math.pow(2, lfo_freq_p - 8) / row_len;
    let c1 = 0;
    let c2 = 0;
    let q = fx_resonance / 255;
    let low = 0;
    let band = 0;
    let high = 0;
    let buf_length = buf_l.length;
    let num_samples = attack + sustain + release - 1;
    let osc1_freq = Math.pow(1.059463094, note + (osc1_oct - 8) * 12 + osc1_det - 128) * 0.00390625 * (1 + 0.0008 * osc1_detune);
    let osc2_freq = Math.pow(1.059463094, note + (osc2_oct - 8) * 12 + osc2_det - 128) * 0.00390625 * (1 + 0.0008 * osc2_detune);
  
    for (let j = num_samples; j >= 0; --j) {
      // Buffer positions
      let k = j + write_pos;
      // LFO
      let lfor = (AUDIO_TAB[osc_lfo_offset + ((k * lfo_freq * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * lfo_amt) / 512 + 0.5;
      let sample = 0;
      let filter_f = fx_freq;
      let temp_f;
      let envelope = 1;
  
      // Envelope
      if (j < attack) {
        envelope = j / attack;
      } else if (j >= attack + sustain) {
        envelope -= (j - attack - sustain) / release;
      }
  
      // Oscillator 1
      temp_f = osc1_freq;
      if (lfo_osc1_freq) {
        temp_f *= lfor;
      }
      if (osc1_xenv) {
        temp_f *= envelope * envelope;
      }
      c1 += temp_f;
      sample += AUDIO_TAB[osc1_offset + ((c1 * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * osc1_vol;
  
      // Oscillator 2
      temp_f = osc2_freq;
      if (osc2_xenv) {
        temp_f *= envelope * envelope;
      }
      c2 += temp_f;
      sample += AUDIO_TAB[osc2_offset + ((c2 * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * osc2_vol;
  
      // Noise oscillator
      if (noise_fader) {
        sample += (2 * Math.random() - 1) * noise_fader * envelope;
      }
  
      sample *= envelope / 255;
  
      // State variable filter
      if (lfo_fx_freq) {
        filter_f *= lfor;
      }
  
      filter_f = 1.5 * AUDIO_TAB[(((filter_f * 0.5) / AUDIO_SAMPLERATE) * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK];
      low += filter_f * band;
      high = q * (sample - band) - low;
      band += filter_f * high;
      sample = [sample, high, low, band, low + high][fx_filter];
  
      // Panning & master volume
      temp_f = (AUDIO_TAB[(k * fx_pan_freq * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK] * fx_pan_amt) / 512 + 0.5;
      sample *= 0.00476 * master; // 39 / 8192 = 0.00476
  
      buf_l[k] += sample * (1 - temp_f);
      buf_r[k] += sample * temp_f;
    }
  };
  
  let audio_create_song = (row_len, pattern_len, song_len, tracks) => {
    let num_samples = AUDIO_SAMPLERATE * song_len;
    let mix_buf_l = new Float32Array(num_samples);
    let mix_buf_r = new Float32Array(num_samples);
  
    for (let track of tracks) {
      let buf_l = new Float32Array(num_samples),
        buf_r = new Float32Array(num_samples),
        write_pos = 0,
        delay_shift = (track[0 /*instrument*/][20 /*fx_delay_time*/] * row_len) >> 1,
        delay_amount = track[0 /*instrument*/][21 /*fx_delay_amt*/] / 255;
  
      for (let p = 0; p < pattern_len; p++) {
        for (let row = 0; row < 32; row++) {
          let note = track[2 /*notes*/][track[1 /*pattern*/][p] - 1]?.[row];
          if (note) {
            audio_generate_sound(row_len, note, buf_l, buf_r, write_pos, ...track[0 /*instrument*/]);
          }
          write_pos += row_len;
        }
      }
  
      audio_apply_delay(delay_shift, delay_amount, buf_l, buf_r);
      for (let b = 0; b < num_samples; b++) {
        mix_buf_l[b] += buf_l[b];
        mix_buf_r[b] += buf_r[b];
      }
    }
    return audio_get_ctx_buffer(mix_buf_l, mix_buf_r);
  };
  
  let audio_create_sound = (note, instrument, row_len = 5605) => {
    let delay_shift = (instrument[20 /*fx_delay_time*/] * row_len) >> 1;
    let delay_amount = instrument[21 /*fx_delay_amt*/] / 255;
    let num_samples = instrument[13 /*env_attack*/] + instrument[14 /*env_sustain*/] + instrument[15 /*env_release*/] + delay_shift * 32 * delay_amount;
    let buf_l = new Float32Array(num_samples);
    let buf_r = new Float32Array(num_samples);
    audio_generate_sound(row_len, note, buf_l, buf_r, 0, ...instrument);
    audio_apply_delay(delay_shift, delay_amount, buf_l, buf_r);
    return audio_get_ctx_buffer(buf_l, buf_r);
  };
  
  let audio_apply_delay = (shift, amount, buf_l, buf_r) => {
    for (let i = 0; i < buf_l.length - shift; i++) {
      buf_l[i + shift] += buf_r[i] * amount;
      buf_r[i + shift] += buf_l[i] * amount;
    }
  };
  