From bc7f470714032c9e97798a5e532517c3d01adeef Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Thu, 27 May 2021 16:46:36 +0200 Subject: Add ADSR filter --- src/main.c | 16 +++++++---- src/uxn/devices/apu.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/main.c b/src/main.c index cbf781d..aee68f8 100644 --- a/src/main.c +++ b/src/main.c @@ -155,10 +155,13 @@ audio_talk(Device *d, u8 b0, u8 w) { } c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5; + u16 adsr = mempeek16(d->dat, 0x8); + build_adsr(c, adsr); txt_position(0, 0); txt_printf("note: %d \n", c->pitch); - txt_printf("inc: %ld \n", c->inc); - txt_printf("length: %ld \n", c->length >> 12); + txt_printf("inc: %lu \n", c->inc); + txt_printf("length: %lu \n", c->length >> 12); + txt_printf("chan: %lu \n", d - devaudio); } } @@ -423,21 +426,24 @@ int main(void) { int frame_counter = 0; evaluxn(&u, 0x0100); u32 flip_cycles = 0; + u32 eval_cycles = 0; + u32 input_cycles = 0; + u32 mix_cycles = 0; while(true) { bios_vblank_wait(); profile_start(); handle_input(&u); - u32 input_cycles = profile_stop(); + input_cycles = MAX(profile_stop(), input_cycles); profile_start(); evaluxn(&u, mempeek16(devscreen->dat, 0)); - u32 eval_cycles = profile_stop(); + eval_cycles = MAX(profile_stop(), eval_cycles); txt_position(0, 8); profile_start(); flip_cycles = profile_stop(); frame_counter++; profile_start(); sound_mix(); - u32 mix_cycles = profile_stop(); + mix_cycles = MAX(profile_stop(), mix_cycles); txt_position(0, 15); txt_printf("INPUT: %lu \n", input_cycles); diff --git a/src/uxn/devices/apu.c b/src/uxn/devices/apu.c index 4447e12..d968840 100644 --- a/src/uxn/devices/apu.c +++ b/src/uxn/devices/apu.c @@ -1,7 +1,7 @@ // Calculated as ((261.6 / 18157) << 17) for C4. If multiplied by sampling rate // we will have a u32 (15.17) fixed-point number. This should be enough to // accurately portray samples up to 75300 Hz. -static u32 pitch_table[120] = { +static u16 pitch_table[120] = { 59, 62, 66, 70, 74, 78, 83, 88, 93, 99, 105, 111, 118, 125, 132, 140, 148, 157, 166, 176, 187, 198, 210, 222, @@ -61,8 +61,75 @@ typedef struct AudioChannel { u32 loop_length; // Pitch encoded as a MIDI note. u8 pitch; + // Keeping track of the original adsr values. + u16 adsr; + // The filter is built with the ADSR and has a duration of 60 ticks. Since + // weare synced at 60FPS it should be pretty straightforward. + u8 filter[241]; + // Current position in the filter (0-60). + u8 filter_pos; + // Duration of the filter. + u8 filter_len; } AudioChannel; +// NOTE: fixed-point (1.6) precision for volume adjustments. +typedef u32 fp32; + +inline +fp32 +fp_mul(fp32 a, fp32 b) { + return (a * b) >> 6; +} + +inline +fp32 +fp_div(fp32 a, fp32 b) { + return (a << 6) / b; +} + +inline +fp32 +fp_lerp(fp32 y0, fp32 y1, fp32 x) { + return y0 + fp_mul(x, (y1 - y0)); +} + + +IWRAM_CODE +void +build_adsr(AudioChannel *chan, u16 adsr) { + // u8 a = (adsr >> 12); + u8 a = (adsr >> 12); + u8 d = (adsr >> 8) & 0xF; + u8 s = (adsr >> 4) & 0xF; + u8 r = (adsr >> 0) & 0xF; + + // Initialize the filter array. + memset(chan->filter, 0, sizeof(chan->filter)); + u8 k = 0; + + // Attack. + for (u32 i = 0; i < 4 * a; ++i) { + chan->filter[k++] = fp_lerp(0, chan->vol, fp_div(i << 6, (4 * a) << 6)); + } + // Decay. + for (u32 i = 0; i < 4 * d; ++i) { + chan->filter[k++] = fp_lerp(chan->vol, chan->vol / 2, fp_div(i << 6, (4 * d) << 6)); + } + // Sustain. + for (u32 i = 0; i < 4 * s; ++i) { + chan->filter[k++] = chan->vol / 2; + } + // Release. + for (u32 i = 0; i < 4 * r; ++i) { + chan->filter[k++] = fp_lerp(chan->vol / 2, 0, fp_div(i << 6, (4 * r) << 6)); + } + + // Setup the channel vars. + chan->adsr = adsr; + chan->filter_pos = 0; + chan->filter_len = k; +} + static Audio audio; #define POLYPHONY 4 @@ -123,12 +190,20 @@ void sound_mix() { if (ch->data == NULL || ch->pitch >= 108) { continue; } + + u32 vol = ch->vol; + if (ch->adsr != 0) { + vol = ch->filter[ch->filter_pos++]; + if (ch->filter_pos == ch->filter_len) { + continue; + } + } if (ch->pos + ch->inc * AUDIO_BUF_LEN >= ch->length) { // Sample is going to finish, need to consider this for looping or // stopping. for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { // Remember we are using fixed point values. - mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos >> 12]) * ch->vol; + mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos >> 12]) * vol; ch->pos += ch->inc; if (ch->pos >= ch->length) { -- cgit v1.2.1