Files

241 lines
7.4 KiB
JavaScript
Raw Permalink Normal View History

2021-09-13 09:34:08 +02:00
// 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,
AUDIO_SAMPLERATE = 44100, // Samples per second
AUDIO_TAB_SIZE = 4096,
AUDIO_TAB_MASK = AUDIO_TAB_SIZE-1,
AUDIO_TAB = new Float32Array(AUDIO_TAB_SIZE*4), // 4 oscilators
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
}
},
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();
},
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;
},
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,
osc1_offset = osc1_waveform * AUDIO_TAB_SIZE,
osc2_offset = osc2_waveform * AUDIO_TAB_SIZE,
fx_pan_freq = Math.pow(2, fx_pan_freq_p - 8) / row_len,
lfo_freq = Math.pow(2, lfo_freq_p - 8) / row_len,
c1 = 0,
c2 = 0,
q = fx_resonance / 255,
low = 0,
band = 0,
high = 0,
buf_length = buf_l.length,
num_samples = attack + sustain + release - 1,
osc1_freq =
Math.pow(1.059463094, (note + (osc1_oct - 8) * 12 + osc1_det) - 128)
* 0.00390625 * (1 + 0.0008 * osc1_detune),
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) {
let
// Buffer positions
k = j + write_pos,
// LFO
lfor = AUDIO_TAB[osc_lfo_offset + ((k * lfo_freq * AUDIO_TAB_SIZE) & AUDIO_TAB_MASK)] * lfo_amt / 512 + 0.5,
sample = 0,
filter_f = fx_freq,
temp_f,
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;
}
},
audio_create_song = (row_len, pattern_len, song_len, tracks) => {
let num_samples = AUDIO_SAMPLERATE * song_len,
mix_buf_l = new Float32Array(num_samples),
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);
},
audio_create_sound = (note, instrument, row_len = 5605) => {
let delay_shift = (instrument[20/*fx_delay_time*/] * row_len) >> 1,
delay_amount = instrument[21/*fx_delay_amt*/] / 255,
num_samples =
instrument[13/*env_attack*/] +
instrument[14/*env_sustain*/] +
instrument[15/*env_release*/] +
delay_shift * 32 * delay_amount,
buf_l = new Float32Array(num_samples),
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);
},
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;
}
};