From 65ce84c8a57956ac685256b50d496cf305eac66e Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Fri, 28 May 2021 19:22:49 +0200 Subject: Cleaup some leftover code and comments --- src/apu.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ src/common.h | 5 ++ src/filesystem.c | 2 +- src/main.c | 46 ++-------- src/uxn/devices/apu.c | 242 -------------------------------------------------- 5 files changed, 251 insertions(+), 282 deletions(-) create mode 100644 src/apu.c delete mode 100644 src/uxn/devices/apu.c (limited to 'src') diff --git a/src/apu.c b/src/apu.c new file mode 100644 index 0000000..8dd4ce5 --- /dev/null +++ b/src/apu.c @@ -0,0 +1,238 @@ +// +// REG_TM0D frequency buffer size +// | | | +// V V V +// +// Timer = 62610 = 65536 - (16777216 / 5734), buf = 96 +// Timer = 63940 = 65536 - (16777216 / 10512), buf = 176 +// Timer = 64282 = 65536 - (16777216 / 13379), buf = 224 +// Timer = 64612 = 65536 - (16777216 / 18157), buf = 304 +// Timer = 64738 = 65536 - (16777216 / 21024), buf = 352 +// Timer = 64909 = 65536 - (16777216 / 26758), buf = 448 +// Timer = 65004 = 65536 - (16777216 / 31536), buf = 528 +// Timer = 65073 = 65536 - (16777216 / 36314), buf = 608 +// Timer = 65118 = 65536 - (16777216 / 40137), buf = 672 +// Timer = 65137 = 65536 - (16777216 / 42048), buf = 704 +// +// Source: https://deku.gbadev.org/program/sound1.html +#define AUDIO_FREQ 18157 +#define AUDIO_BUF_LEN 304 +#define AUDIO_TIMER 64612 + +typedef struct Audio { + s8 mix_buffer[AUDIO_BUF_LEN * 2]; + s8 *current_buffer; + u8 active_buffer; +} Audio; + +typedef struct AudioChannel { + // Pointer to the raw data in the ROM. + u8 *data; + // Current position in the data (20.12 fixed-point). + u32 pos; + // Increment (20.12 fixed-point). + u32 inc; + // Volume (0-64, 1.6 fixed-point). + u32 vol; + // Sound length (20.12 fixed-point). + u32 length; + // Length of looped portion (20.12 fixed-point, 0 to disable looping). + 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 maximum duration of + // 4 seconds. Each component can last up to 1 second. + u8 filter[240]; + // Current position in the filter (0-60). + u8 filter_pos; + // Duration of the filter. + u8 filter_len; +} AudioChannel; + +// 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 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, + 236, 250, 264, 280, 297, 315, 333, 353, + 374, 396, 420, 445, 472, 500, 529, 561, + 594, 630, 667, 707, 749, 793, 841, 891, + 944, 1000, 1059, 1122, 1189, 1260, 1335, 1414, + 1498, 1587, 1682, 1782, 1888, 2000, 2119, 2245, + 2379, 2520, 2670, 2829, 2997, 3175, 3364, 3564, + 3776, 4001, 4239, 4491, 4758, 5041, 5341, 5658, + 5995, 6351, 6729, 7129, 7553, 8002, 8478, 8982, + 9517, 10083, 10682, 11317, 11990, 12703, 13459, 14259, + 15107, 16005, 16957, 17965, 19034, 20166, 21365, 22635, + 23981, 25407, 26918, 28519, 30215, 32011, 33915, 35931, + 38068, 40332, 42730, 45271, 47963, 50815, 53837, 57038, +}; + +IWRAM_CODE +void +build_adsr(AudioChannel *chan, u16 adsr) { + chan->filter_pos = 0; + if (adsr == chan->adsr) { + return; + } + + u8 a = (adsr >> 12); + u8 d = (adsr >> 8) & 0xF; + u8 s = (adsr >> 4) & 0xF; + u8 r = (adsr >> 0) & 0xF; + + // Initialize the filter array. + dma_fill(chan->filter, 0, sizeof(chan->filter), 3); + u8 k = 0; + + // Attack. + u32 interval = FP_DIV(1 << 6, (4 * a) << 6, 6); + for (u32 i = 0; i < 4 * a; ++i) { + chan->filter[k++] = FP_LERP(0, chan->vol, i * interval, 6); + } + // Decay. + interval = FP_DIV(1 << 6, (4 * d) << 6, 6); + for (u32 i = 0; i < 4 * d; ++i) { + chan->filter[k++] = FP_LERP(chan->vol, chan->vol / 2, i * interval, 6); + } + // Sustain. + for (u32 i = 0; i < 4 * s; ++i) { + chan->filter[k++] = chan->vol / 2; + } + // Release. + interval = FP_DIV(1 << 6, (4 * r) << 6, 6); + for (u32 i = 0; i < 4 * r; ++i) { + chan->filter[k++] = FP_LERP(chan->vol / 2, 0, i * interval, 6); + } + + // Setup the channel vars. + chan->adsr = adsr; + chan->filter_len = k; +} + +static Audio audio; + +#define POLYPHONY 4 +static AudioChannel channels[POLYPHONY]; + +void +init_sound(void) { + // Initialize audio buffers/channels. + audio = (Audio){0}; + for (size_t i = 0; i < POLYPHONY; ++i) { + channels[i] = (AudioChannel){0}; + } + + // Enable the sound chip. + SOUND_STATUS = SOUND_ENABLE; + + // Set max volume, left-right sound, fifo reset and use timer 0 for + // DirectSound A. + SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A + | SOUND_DSOUND_LEFT_A + | SOUND_DSOUND_RIGHT_A + | SOUND_DSOUND_RESET_A; + + // The timer depends on the buffer length. + TIMER_DATA_0 = AUDIO_TIMER; + TIMER_CTRL_0 = TIMER_CTRL_ENABLE; +} + +void +sound_vsync() { + if(audio.active_buffer == 1) { + // Start playing and set the backbuffer. + dma_transfer_copy(SOUND_FIFO_A, audio.mix_buffer, 1, 1, DMA_DST_FIXED + | DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE); + audio.current_buffer = audio.mix_buffer + AUDIO_BUF_LEN; + audio.active_buffer = 0; + } else { + // Flip front/backbuffer. + audio.current_buffer = audio.mix_buffer; + audio.active_buffer = 1; + } +} + +IWRAM_CODE +void +update_channel(AudioChannel *c, u8 *data, u16 length, u8 pitch, u16 adsr, + u8 vol, bool loop) { + c->pos = 0; + c->length = length << 12; + c->data = data; + c->vol = vol; + c->pitch = pitch; + + if (loop) { + c->loop_length = c->length; + } else { + c->loop_length = 0; + } + + u32 sampling_rate = length; + if (length > 256) { + sampling_rate = 44100; + } + c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5; + + build_adsr(c, adsr); +} + +IWRAM_CODE +void +sound_mix(void) { + // Clear mix_buffer. + u16 mix_buffer[AUDIO_BUF_LEN]; + dma_fill(mix_buffer, 0, sizeof(mix_buffer), 3); + + // Mix channels into the temporary buffer. + for (size_t j = 0; j < POLYPHONY; ++j) { + AudioChannel *ch = &channels[j]; + // Check if channel is active. + 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; + } + } + + for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { + // Remember we are using fixed point values. + mix_buffer[i] += (0x80 ^ (s8)ch->data[ch->pos >> 12]) * vol; + ch->pos += ch->inc; + + if (ch->pos >= ch->length) { + // If looping is not active disable the channel. + if (ch->loop_length == 0) { + ch->data = NULL; + break; + } + + // Loop the sample. + while (ch->pos >= ch->length) { + ch->pos -= ch->loop_length; + } + } + } + } + + // Downsample and copy to the playing buffer (Vectorized). + u64 *mix_ptr = mix_buffer; + u32 *buf_ptr = audio.current_buffer; + for (size_t i = 0; i < AUDIO_BUF_LEN / 4; i++) { + u64 mix = mix_ptr[i]; + buf_ptr[i] = ((mix >> 8) & 0xFF) + | ((mix >> 16) & 0xFF00) + | ((mix >> 24) & 0xFF0000) + | ((mix >> 32) & 0xFF000000); + } +} diff --git a/src/common.h b/src/common.h index 048708b..0fe2752 100644 --- a/src/common.h +++ b/src/common.h @@ -722,6 +722,11 @@ wait_vsync(void) { #define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X)) #define LEN(ARR) (sizeof(ARR) / sizeof((ARR)[0])) +// Fixed-point arithmetic for (i.P) numbers. +#define FP_MUL(A,B,P) (((A) * (B)) >> (P)) +#define FP_DIV(A,B,P) (((A) << (P)) / (B)) +#define FP_LERP(Y0,Y1,X,P) ((Y0) + FP_MUL((X), ((Y1) - (Y0)), P)) + // // Memory section macros for devkitARM. // diff --git a/src/filesystem.c b/src/filesystem.c index b60dbde..8aaa51c 100644 --- a/src/filesystem.c +++ b/src/filesystem.c @@ -114,7 +114,7 @@ fs_init(void) { } // Initialize filesystem. - memset(&filesystem, 0, sizeof(FileSystem)); + dma_fill(&filesystem, 0, sizeof(FileSystem), 3); filesystem.initialized = FS_INIT_PATTERN; for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { filesystem.files[i].first_block = FS_NULL; diff --git a/src/main.c b/src/main.c index aee68f8..35bc0cf 100644 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,7 @@ WITH REGARD TO THIS SOFTWARE. #include "uxn/uxn.c" #include "uxn/devices/ppu.h" #include "uxn/devices/ppu.c" -#include "uxn/devices/apu.c" +#include "apu.c" #include "text.h" @@ -124,44 +124,12 @@ audio_talk(Device *d, u8 b0, u8 w) { } } else if(b0 == 0xf) { u16 length = mempeek16(d->dat, 0xa); - - // Disable the channel before updating. - c->data = NULL; - - // Data. - c->pos = 0; - c->length = length; - c->length <<= 12; // fixed point. - c->data = &d->mem[mempeek16(d->dat, 0xc)]; - - // Volume (Mono only for now). - c->vol = MAX(d->dat[0xe] >> 4, d->dat[0xe] & 0xf); // 0-64 - c->vol *= 4 / 3; - - // Looping. - if (!(d->dat[0xf] & 0x80)) { - c->loop_length = c->length; - } else { - c->loop_length = 0; - } - - // Pitch - c->pitch = d->dat[0xf] & 0x7f; - - // Initialization. - u32 sampling_rate = length; - if (length > 256) { - sampling_rate = 44100; - } - c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5; - + u8 *data = &d->mem[mempeek16(d->dat, 0xc)]; + u8 pitch = d->dat[0xf] & 0x7f; u16 adsr = mempeek16(d->dat, 0x8); - build_adsr(c, adsr); - txt_position(0, 0); - txt_printf("note: %d \n", c->pitch); - txt_printf("inc: %lu \n", c->inc); - txt_printf("length: %lu \n", c->length >> 12); - txt_printf("chan: %lu \n", d - devaudio); + u32 vol = MAX(d->dat[0xe] >> 4, d->dat[0xe] & 0xf) * 4 / 3; + bool loop = !(d->dat[0xf] & 0x80); + update_channel(c, data, length, pitch, adsr, vol, loop); } } @@ -412,7 +380,7 @@ int main(void) { // irs_set(IRQ_TIMER_1, irs_stop_sample); // Initialize VM. - memset(&u, 0, sizeof(u)); + dma_fill(&u, 0, sizeof(u), 3); u.ram.dat = umem; init_uxn(&u); diff --git a/src/uxn/devices/apu.c b/src/uxn/devices/apu.c deleted file mode 100644 index 47151a6..0000000 --- a/src/uxn/devices/apu.c +++ /dev/null @@ -1,242 +0,0 @@ -// 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 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, - 236, 250, 264, 280, 297, 315, 333, 353, - 374, 396, 420, 445, 472, 500, 529, 561, - 594, 630, 667, 707, 749, 793, 841, 891, - 944, 1000, 1059, 1122, 1189, 1260, 1335, 1414, - 1498, 1587, 1682, 1782, 1888, 2000, 2119, 2245, - 2379, 2520, 2670, 2829, 2997, 3175, 3364, 3564, - 3776, 4001, 4239, 4491, 4758, 5041, 5341, 5658, - 5995, 6351, 6729, 7129, 7553, 8002, 8478, 8982, - 9517, 10083, 10682, 11317, 11990, 12703, 13459, 14259, - 15107, 16005, 16957, 17965, 19034, 20166, 21365, 22635, - 23981, 25407, 26918, 28519, 30215, 32011, 33915, 35931, - 38068, 40332, 42730, 45271, 47963, 50815, 53837, 57038, -}; - -// -// REG_TM0D frequency buffer size -// | | | -// V V V -// -// Timer = 62610 = 65536 - (16777216 / 5734), buf = 96 -// Timer = 63940 = 65536 - (16777216 / 10512), buf = 176 -// Timer = 64282 = 65536 - (16777216 / 13379), buf = 224 -// Timer = 64612 = 65536 - (16777216 / 18157), buf = 304 -// Timer = 64738 = 65536 - (16777216 / 21024), buf = 352 -// Timer = 64909 = 65536 - (16777216 / 26758), buf = 448 -// Timer = 65004 = 65536 - (16777216 / 31536), buf = 528 -// Timer = 65073 = 65536 - (16777216 / 36314), buf = 608 -// Timer = 65118 = 65536 - (16777216 / 40137), buf = 672 -// Timer = 65137 = 65536 - (16777216 / 42048), buf = 704 -// -// Source: https://deku.gbadev.org/program/sound1.html -#define AUDIO_FREQ 18157 -#define AUDIO_BUF_LEN 304 -#define AUDIO_TIMER 64612 - -typedef struct Audio { - s8 mix_buffer[AUDIO_BUF_LEN * 2]; - s8 *current_buffer; - u8 active_buffer; -} Audio; - -typedef struct AudioChannel { - // Pointer to the raw data in the ROM. - s8 *data; - // Current position in the data (20.12 fixed-point). - u32 pos; - // Increment (20.12 fixed-point). - u32 inc; - // Volume (0-64, 1.6 fixed-point). - u32 vol; - // Sound length (20.12 fixed-point). - u32 length; - // Length of looped portion (20.12 fixed-point, 0 to disable looping). - 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 -static AudioChannel channels[POLYPHONY]; - -void -init_sound(void) { - // Initialize audio buffers/channels. - audio = (Audio){0}; - for (size_t i = 0; i < POLYPHONY; ++i) { - channels[i] = (AudioChannel){0}; - } - - // Enable the sound chip. - SOUND_STATUS = SOUND_ENABLE; - - // Set max volume, left-right sound, fifo reset and use timer 0 for - // DirectSound A. - SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A - | SOUND_DSOUND_LEFT_A - | SOUND_DSOUND_RIGHT_A - | SOUND_DSOUND_RESET_A; - - TIMER_DATA_0 = AUDIO_TIMER; - TIMER_CTRL_0 = TIMER_CTRL_ENABLE; -} - -void sound_vsync() { - // buffer 1 just got over - if(audio.active_buffer == 1) { - // Start playing buffer 0 - dma_transfer_copy(SOUND_FIFO_A, audio.mix_buffer, 1, 1, - DMA_DST_FIXED | DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE); - - // Set the current buffer pointer to the start of buffer 1 - audio.current_buffer = audio.mix_buffer + AUDIO_BUF_LEN; - audio.active_buffer = 0; - } else { - // buffer 0 just got over - // DMA points to buffer 1 already, so don't bother stopping and resetting it - // Set the current buffer pointer to the start of buffer 0 - audio.current_buffer = audio.mix_buffer; - audio.active_buffer = 1; - } -} - -IWRAM_CODE -void sound_mix() { - // Initialize and clear mix_buffer. - s16 mix_buffer[AUDIO_BUF_LEN]; - u32 fill = 0; - dma_fill(mix_buffer, fill, sizeof(mix_buffer), 3); - - // Mix channels into the temporary buffer. - for (size_t j = 0; j < POLYPHONY; ++j) { - AudioChannel *ch = &channels[j]; - // Check if channel is active. - 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 ^ ch->data[ch->pos >> 12]) * vol; - ch->pos += ch->inc; - - if (ch->pos >= ch->length) { - // If looping is not active disable the channel. - if (ch->loop_length == 0) { - ch->data = NULL; - break; - } - - // Loop the sample. - while (ch->pos >= ch->length) { - ch->pos -= ch->loop_length; - } - } - } - } else { - // Sample still have room to go, no need to check for looping or - // end of sample. - for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { - mix_buffer[i] += (0x80 ^ ch->data[ch->pos>>12]) * vol; - ch->pos += ch->inc; - } - } - } - - // Downsample and copy to the playing buffer (Vectorized). - u64 *mix_ptr = mix_buffer; - u32 *buf_ptr = audio.current_buffer; - for (size_t i = 0; i < AUDIO_BUF_LEN / 4; i++) { - u64 mix = mix_ptr[i]; - buf_ptr[i] = (mix >> 8) & 0xFF - | (mix >> 16) & 0xFF00 - | (mix >> 24) & 0xFF0000 - | (mix >> 32) & 0xFF000000; - } -} -- cgit v1.2.1