From e6235422f3746683e679abd353d9385835c8187f Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Wed, 26 May 2021 15:08:23 +0200 Subject: [WIP] Prototype of audio mixer following deku tutorial --- src/main.c | 41 ++++--- src/uxn/devices/apu.c | 289 +++++++++++++++++++++++++++----------------------- 2 files changed, 181 insertions(+), 149 deletions(-) diff --git a/src/main.c b/src/main.c index a6fb761..6e7d3e5 100644 --- a/src/main.c +++ b/src/main.c @@ -184,7 +184,7 @@ init_uxn(Uxn *u) { initppu(&ppu, 30, 20, 0); // Enable sound. - SOUND_STATUS = SOUND_ENABLE; + init_sound(); // Copy rom to VM. memcpy(u->ram.dat + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); @@ -388,8 +388,8 @@ int main(void) { // Register interrupts. irq_init(); - irs_set(IRQ_VBLANK, irs_stub); - irs_set(IRQ_TIMER_1, irs_stop_sample); + irs_set(IRQ_VBLANK, sound_vsync); + // irs_set(IRQ_TIMER_1, irs_stop_sample); // Initialize VM. memset(&u, 0, sizeof(u)); @@ -400,34 +400,43 @@ int main(void) { txt_init(1, TEXT_LAYER); txt_position(0,0); - // init_sound(&apu.chan_0); + init_sound(); // reset_sound(&apu.chan_0); + // txt_printf("DING\n"); + // sound_mix(); // Main loop. int frame_counter = 0; - evaluxn(&u, 0x0100); - u32 flip_cycles = 0; + // evaluxn(&u, 0x0100); + // u32 flip_cycles = 0; while(true) { bios_vblank_wait(); - profile_start(); - handle_input(&u); - u32 input_cycles = profile_stop(); - profile_start(); - evaluxn(&u, mempeek16(devscreen->dat, 0)); - u32 eval_cycles = profile_stop(); - txt_position(0, 8); + // profile_start(); + // handle_input(&u); + // u32 input_cycles = profile_stop(); + // profile_start(); + // evaluxn(&u, mempeek16(devscreen->dat, 0)); + // u32 eval_cycles = profile_stop(); + // txt_position(0, 8); // txt_printf("INPUT: %lu \n", input_cycles); // txt_printf("EVAL: %lu \n", eval_cycles); // txt_printf("FLIP: %lu \n", flip_cycles); - profile_start(); - flipbuf(&ppu); - flip_cycles = profile_stop(); + // profile_start(); + // flip_cycles = profile_stop(); frame_counter++; // if (frame_counter == 120) { // init_sound(&apu.chan_0); // reset_sound(&apu.chan_0); // frame_counter = 0; // } + profile_start(); + sound_mix(); + u32 mix_cycles = profile_stop(); + + txt_position(0, 18); + txt_printf("MIX CYCLES: %lu \n", mix_cycles); + txt_printf("FRAME: %lu \n", frame_counter); + flipbuf(&ppu); } return 0; diff --git a/src/uxn/devices/apu.c b/src/uxn/devices/apu.c index b2ccc18..ba9fe88 100644 --- a/src/uxn/devices/apu.c +++ b/src/uxn/devices/apu.c @@ -4,6 +4,7 @@ #define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025) #define ADSR_STEP (SAMPLE_FREQUENCY / 0xf) +// FIXME: We have two structs for now, needs fixing. typedef struct AudioChannel { u8 *samples; u16 n_samples; @@ -31,127 +32,22 @@ static APU apu; static u16 pitch_table[] = { - 12173, - 11490, - 10845, - 10237, - 9662, - 9120, - 8608, - 8125, - 7669, - 7238, - 6832, - 6448, - 6086, - 5745, - 5422, - 5118, - 4831, - 4560, - 4304, - 4062, - 3834, - 3619, - 3416, - 3224, - 3043, - 2872, - 2711, - 2559, - 2415, - 2280, - 2152, - 2031, - 1917, - 1809, - 1708, - 1612, - 1521, - 1436, - 1355, - 1279, - 1207, - 1140, - 1076, - 1015, - 958, - 904, - 854, - 806, - 760, - 718, - 677, - 639, - 603, - 570, - 538, - 507, - 479, - 452, - 427, - 403, - 380, - 359, - 338, - 319, - 301, - 285, - 269, - 253, - 239, - 226, - 213, - 201, - 190, - 179, - 169, - 159, - 150, - 142, - 134, - 126, - 119, - 113, - 106, - 100, - 95, - 89, - 84, - 79, - 75, - 71, - 67, - 63, - 59, - 56, - 53, - 50, - 47, - 44, - 42, - 39, - 37, - 35, - 33, - 31, - 29, - 28, - 26, - 25, - 23, - 22, - 21, - 19, - 18, - 17, - 16, - 15, - 14, - 14, - 13, - 12, - 0, + 12173, 11490, 10845, 10237, 9662, 9120, 8608, 8125, + 7669, 7238, 6832, 6448, 6086, 5745, 5422, 5118, + 4831, 4560, 4304, 4062, 3834, 3619, 3416, 3224, + 3043, 2872, 2711, 2559, 2415, 2280, 2152, 2031, + 1917, 1809, 1708, 1612, 1521, 1436, 1355, 1279, + 1207, 1140, 1076, 1015, 958, 904, 854, 806, + 760, 718, 677, 639, 603, 570, 538, 507, + 479, 452, 427, 403, 380, 359, 338, 319, + 301, 285, 269, 253, 239, 226, 213, 201, + 190, 179, 169, 159, 150, 142, 134, 126, + 119, 113, 106, 100, 95, 89, 84, 79, + 75, 71, 67, 63, 59, 56, 53, 50, + 47, 44, 42, 39, 37, 35, 33, 31, + 29, 28, 26, 25, 23, 22, 21, 19, + 18, 17, 16, 15, 14, 14, 13, 12, + 2, }; void @@ -188,23 +84,150 @@ reset_sound(AudioChannel *chan) { TIMER_CTRL_0 = TIMER_CTRL_ENABLE; } -u8 square_wave[] = { - 0x00 + 0x80, 0xFF + 0x80 +s8 square_wave[] = { + (s8)0x00 + (s8)0x80, (s8)0xFF + (s8)0x80 }; + #include "text.h" + +// +// 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 Channel { + // 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; +} Channel; + +static Audio audio; + +#define POLYPHONY 4 +static Channel channels[POLYPHONY]; + void -init_sound(AudioChannel *chan) { - // chan->samples = &square_wave; - // chan->n_samples = 2; - // chan->samples = &samples; - // chan->n_samples = LEN(samples); - // chan->pitch++; - // chan->pitch = chan->pitch == LEN(pitch_table) - 1 ? 0 : chan->pitch + 1; - // chan->loop = true; - // chan->sampling_freq = pitch_table[chan->pitch]; - // txt_printf("RATE: %lu", pitch_table[chan->pitch]) +init_sound(void) { + // Initialize audio buffers/channels. + audio = (Audio){0}; + for (size_t i = 0; i < POLYPHONY; ++i) { + channels[i] = (Channel){0}; + } + + // DEBUG: testing channel 0 with square wave + channels[0].data = samples; + channels[0].inc = (8363 << 12) / AUDIO_FREQ; + channels[0].length = (LEN(samples) - 1) << 12; + channels[0].pos = 0; + channels[0].vol = 64; + channels[0].loop_length = channels[0].length; + + // 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; + + // TODO: No pitch selection yet. + 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; + } +} + +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) { + Channel *chan = &channels[j]; + // Check if channel is active. + if (chan->data == NULL) { + continue; + } + for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { + // Remember we are using fixed point values. + mix_buffer[i] += chan->data[chan->pos >> 12] * chan->vol; + chan->pos += chan->inc; + + if (chan->pos >= chan->length) { + // If looping is not active disable the channel. + if (chan->loop_length == 0) { + chan->data = NULL; + break; + } + + // Loop the sample. + while (chan->pos >= chan->length) { + chan->pos -= chan->loop_length; + } + } + } + } + + // Downsample and copy to the playing buffer. + for (size_t i = 0; i < AUDIO_BUF_LEN; ++i) { + // >> 8 to divide off the volume, >> 2 to divide by 4 channels to + // prevent overflow. + audio.current_buffer[i] = mix_buffer[i] >> 8; + } } void -- cgit v1.2.1