From 518fec637e537c5bfb39f1a514070fbc384b2882 Mon Sep 17 00:00:00 2001 From: Arignir Date: Sun, 24 Dec 2023 04:16:10 +0100 Subject: [PATCH] Revamp the APU and implement the PSG channels 1, 2 and 4. Fix https://github.com/Arignir/Hades/issues/61 Fix https://github.com/Arignir/Hades/issues/62 Fix https://github.com/Arignir/Hades/issues/63 --- include/gba/apu.h | 114 ++++++++++++++++++--- include/gba/gba.h | 2 +- include/gba/io.h | 99 +++++++++++++++++- include/gba/scheduler.h | 5 +- source/app/dbg/cmd/io.c | 12 +-- source/app/dbg/io.c | 49 +++++++++ source/gba/apu/apu.c | 142 +++++++------------------- source/gba/apu/fifo.c | 92 +++++++++++++++++ source/gba/apu/modules.c | 146 +++++++++++++++++++++++++++ source/gba/apu/noise.c | 114 +++++++++++++++++++++ source/gba/apu/tone.c | 211 +++++++++++++++++++++++++++++++++++++++ source/gba/apu/wave.c | 57 ++++++----- source/gba/gba.c | 9 +- source/gba/memory/io.c | 109 ++++++++++++++++++-- source/gba/meson.build | 4 + source/gba/scheduler.c | 5 +- source/gba/timer.c | 2 +- 17 files changed, 998 insertions(+), 174 deletions(-) create mode 100644 source/gba/apu/fifo.c create mode 100644 source/gba/apu/modules.c create mode 100644 source/gba/apu/noise.c create mode 100644 source/gba/apu/tone.c diff --git a/include/gba/apu.h b/include/gba/apu.h index abf6b724..ec2d9257 100644 --- a/include/gba/apu.h +++ b/include/gba/apu.h @@ -11,28 +11,88 @@ #include -#define FIFO_CAPACITY 32 +#define FIFO_CAPACITY 32 // TODO: This should be dynamically set to a least 3x the value contained in `have.samples` (see `gui/sdl/audio.c`) -#define APU_RBUFFER_CAPACITY (2048 * 3) +#define APU_RBUFFER_CAPACITY (2048 * 3) enum fifo_idx { FIFO_A = 0, FIFO_B = 1, }; -struct fifo { +struct apu_fifo { int8_t data[FIFO_CAPACITY]; size_t read_idx; size_t write_idx; size_t size; }; -struct wave { +struct apu_counter { + bool enabled; + + uint32_t value; +}; + +struct apu_sweep { + uint32_t shifts; + bool direction; + uint32_t time; + + uint32_t step; + uint32_t frequency; + uint32_t shadow_frequency; +}; + +struct apu_envelope { + uint32_t step_time; + bool direction; + int32_t initial_volume; + + bool enabled; + + uint32_t step; + int32_t volume; +}; + +struct apu_tone_and_sweep { + bool enabled; + + struct apu_sweep sweep; + struct apu_counter counter; + struct apu_envelope envelope; + + uint32_t step; + event_handler_t step_handler; +}; + +struct apu_tone { + bool enabled; + + struct apu_counter counter; + struct apu_envelope envelope; + uint32_t step; - uint32_t length; event_handler_t step_handler; - event_handler_t counter_handler; +}; + +struct apu_wave { + bool enabled; + + uint32_t step; + event_handler_t step_handler; + struct apu_counter counter; +}; + +struct apu_noise { + bool enabled; + + struct apu_counter counter; + struct apu_envelope envelope; + + uint32_t lfsr; + + event_handler_t step_handler; }; struct apu_rbuffer { @@ -43,23 +103,51 @@ struct apu_rbuffer { }; struct apu { - struct fifo fifos[2]; - struct wave wave; + struct apu_fifo fifos[2]; + struct apu_tone_and_sweep tone_and_sweep; + struct apu_tone tone; + struct apu_wave wave; + struct apu_noise noise; + + uint32_t modules_step; struct { int16_t fifo[2]; - int16_t wave; + int16_t channel_1; + int16_t channel_2; + int16_t channel_3; + int16_t channel_4; } latch; }; /* gba/apu/apu.c */ -void apu_reset_fifo(struct gba *gba, enum fifo_idx fifo_idx); -void apu_fifo_write8(struct gba *gba, enum fifo_idx fifo_idx, uint8_t val); uint32_t apu_rbuffer_pop(struct apu_rbuffer *rbuffer); -void apu_on_timer_overflow(struct gba *gba, uint32_t timer_id); -void apu_sequencer(struct gba *gba, struct event_args args); void apu_resample(struct gba *gba, struct event_args args); +/* gba/apu/fifo.c */ +void apu_reset_fifo(struct gba *gba, enum fifo_idx fifo_idx); +void apu_fifo_write8(struct gba *gba, enum fifo_idx fifo_idx, uint8_t val); +void apu_fifo_timer_overflow(struct gba *gba, uint32_t timer_id); + +/* gba/apu/modules.c */ +void apu_modules_step(struct gba *gba, struct event_args args); +void apu_modules_sweep_reset(struct apu_sweep *sweep, uint32_t frequency, uint32_t shifts, bool direction, uint32_t time); +void apu_modules_counter_reset(struct apu_counter *counter, bool enabled, uint32_t value); +void apu_modules_envelope_reset(struct apu_envelope *envelope, uint32_t step_time, bool direction, uint32_t initial_volume); + +/* gba/apu/noise.c */ +void apu_noise_reset(struct gba *gba); +void apu_noise_stop(struct gba *gba); +void apu_noise_step(struct gba *gba, struct event_args args); + +/* gba/apu/tone.c */ +void apu_tone_and_sweep_reset(struct gba *gba); +void apu_tone_and_sweep_stop(struct gba *gba); +void apu_tone_and_sweep_step(struct gba *gba, struct event_args args); +void apu_tone_reset(struct gba *gba); +void apu_tone_stop(struct gba *gba); +void apu_tone_step(struct gba *gba, struct event_args args); + /* gba/apu/wave.c */ void apu_wave_reset(struct gba *gba); void apu_wave_stop(struct gba *gba); diff --git a/include/gba/gba.h b/include/gba/gba.h index ccc234ec..cb961c10 100644 --- a/include/gba/gba.h +++ b/include/gba/gba.h @@ -15,7 +15,7 @@ #define GBA_SCREEN_REAL_HEIGHT 228 #define GBA_CYCLES_PER_PIXEL 4 #define GBA_CYCLES_PER_FRAME (CYCLES_PER_PIXEL * GBA_SCREEN_REAL_WIDTH * GBA_SCREEN_REAL_HEIGHT) -#define GBA_CYCLES_PER_SECOND (16 * 1024 * 1024) +#define GBA_CYCLES_PER_SECOND ((uint64_t)(16 * 1024 * 1024)) #include "hades.h" #include "gba/channel.h" diff --git a/include/gba/io.h b/include/gba/io.h index df3bb211..e46bd4a8 100644 --- a/include/gba/io.h +++ b/include/gba/io.h @@ -153,6 +153,9 @@ enum io_regs { /* Serial Communication (2) */ IO_REG_SIOCNT = 0x04000128, IO_REG_RCNT = 0x04000134, + IO_REG_IR = 0x04000136, + IO_REG_UNKNOWN_1 = 0x04000142, + IO_REG_UNKNOWN_2 = 0x0400015A, /* Interrupts */ @@ -160,6 +163,7 @@ enum io_regs { IO_REG_IF = 0x04000202, IO_REG_WAITCNT = 0x04000204, IO_REG_IME = 0x04000208, + IO_REG_UNKNOWN_3 = 0x04000302, /* System */ @@ -487,6 +491,68 @@ struct io { uint8_t bytes[2]; } bldy; + // REG_SOUND1CNT_L + union { + struct { + uint16_t sweep_shift_number: 3; + uint16_t sweep_direction: 1; + uint16_t sweep_step_time: 3; + uint16_t : 9; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound1cnt_l; + + // REG_SOUND1CNT_H + union { + struct { + uint16_t length: 6; + uint16_t duty: 2; + uint16_t eveloppe_step_time: 3; + uint16_t envelope_direction: 1; + uint16_t envelope_initial_volume: 4; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound1cnt_h; + + // REG_SOUND1CNT_X + union { + struct { + uint16_t sample_rate: 11; + uint16_t : 3; + uint16_t use_length: 1; + uint16_t reset: 1; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound1cnt_x; + + // REG_SOUND2CNT_L + union { + struct { + uint16_t length: 6; + uint16_t duty: 2; + uint16_t eveloppe_step_time: 3; + uint16_t envelope_direction: 1; + uint16_t envelope_initial_volume: 4; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound2cnt_l; + + // REG_SOUND2CNT_H + union { + struct { + uint16_t sample_rate: 11; + uint16_t : 3; + uint16_t use_length: 1; + uint16_t reset: 1; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound2cnt_h; + // REG_SOUND3CNT_L union { struct { @@ -524,12 +590,39 @@ struct io { uint8_t bytes[2]; } sound3cnt_x; + // REG_SOUND4CNT_L + union { + struct { + uint16_t length: 6; + uint16_t : 2; + uint16_t eveloppe_step_time: 3; + uint16_t envelope_direction: 1; + uint16_t envelope_initial_volume: 4; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound4cnt_l; + + // REG_SOUND4CNT_H + union { + struct { + uint16_t frequency_ratio: 3; + uint16_t width: 1; + uint16_t frequency_shift: 4; + uint16_t : 6; + uint16_t use_length: 1; + uint16_t reset: 1; + } __packed; + uint16_t raw; + uint8_t bytes[2]; + } sound4cnt_h; + // REG_SOUNDCNT_L union { struct { - uint16_t sound_right_volume: 3; + uint16_t channel_right_volume: 3; uint16_t : 1; - uint16_t sound_left_volume: 3; + uint16_t channel_left_volume: 3; uint16_t : 1; uint16_t enable_sound_1_right: 1; uint16_t enable_sound_2_right: 1; @@ -547,7 +640,7 @@ struct io { // REG_SOUNDCNT_H union { struct { - uint16_t volume_sounds: 2; + uint16_t volume_channels: 2; uint16_t volume_fifo_a: 1; uint16_t volume_fifo_b: 1; uint16_t : 4; diff --git a/include/gba/scheduler.h b/include/gba/scheduler.h index 9838006f..05dedb35 100644 --- a/include/gba/scheduler.h +++ b/include/gba/scheduler.h @@ -19,9 +19,12 @@ enum sched_event_kind { SCHED_EVENT_PPU_HBLANK, SCHED_EVENT_TIMER_OVERFLOW, SCHED_EVENT_TIMER_STOP, - SCHED_EVENT_APU_SEQUENCER, SCHED_EVENT_APU_RESAMPLE, + SCHED_EVENT_APU_MODULES_STEP, + SCHED_EVENT_APU_TONE_AND_SWEEP_STEP, + SCHED_EVENT_APU_TONE_STEP, SCHED_EVENT_APU_WAVE_STEP, + SCHED_EVENT_APU_NOISE_STEP, SCHED_EVENT_DMA_ADD_PENDING, }; diff --git a/source/app/dbg/cmd/io.c b/source/app/dbg/cmd/io.c index 9549cac3..87e0f481 100644 --- a/source/app/dbg/cmd/io.c +++ b/source/app/dbg/cmd/io.c @@ -118,7 +118,7 @@ debugger_cmd_io_dump_reg( bitfield = ®->bitfield[i]; value = bitfield_get_range(val, bitfield->start, bitfield->end + 1); printf( - "%s%3i%s | %-30s %s\n", + "%s%4i%s | %-30s %s\n", value ? g_light_magenta : g_dark_gray, value, g_reset, @@ -144,9 +144,9 @@ debugger_cmd_io( size_t i; printf( - "|%.12s|%.10s|%.20s|%.20s|%.8s|\n" - "| %-10s | %-8s | %-18s | %-18s | %-6s |\n" - "|%.12s|%.10s|%.20s|%.20s|%.8s|\n", + "|%.12s|%.14s|%.30s|%.20s|%.8s|\n" + "| %-10s | %-12s | %-28s | %-18s | %-6s |\n" + "|%.12s|%.14s|%.30s|%.20s|%.8s|\n", "-------------------------------------", "-------------------------------------", "-------------------------------------", @@ -171,7 +171,7 @@ debugger_cmd_io( val = debugger_cmd_io_read_register(app, reg); printf( - "| 0x%08x | %s%-8s%s | %-18s | ", + "| 0x%08x | %s%-12s%s | %-28s | ", reg->address, g_light_green, mem_io_reg_name(reg->address), @@ -185,7 +185,7 @@ debugger_cmd_io( } printf( - "|%.12s|%.10s|%.20s|%.20s|%.8s|\n", + "|%.12s|%.14s|%.30s|%.20s|%.8s|\n", "-------------------------------------", "-------------------------------------", "-------------------------------------", diff --git a/source/app/dbg/io.c b/source/app/dbg/io.c index 827bfe54..c65cf9e4 100644 --- a/source/app/dbg/io.c +++ b/source/app/dbg/io.c @@ -191,6 +191,55 @@ debugger_io_init( debugger_io_new_bitfield(reg, 9, 15, "Reserved (0)", NULL); } } + + reg = debugger_io_new_register16(IO_REG_SOUND1CNT_L, "Channel 1 Sweep register", NULL); + debugger_io_new_bitfield(reg, 0, 2, "Number of sweep shift", "(n=0-7)"); + debugger_io_new_bitfield(reg, 3, 3, "Sweep Frequency Direction", "(0=Increase, 1=Decrease)"); + debugger_io_new_bitfield(reg, 4, 6, "Sweep Time; units of 7.8ms", "(0-7, min=7.8ms, max=54.7ms)"); + debugger_io_new_bitfield(reg, 7, 15, "Not used", NULL); + + reg = debugger_io_new_register16(IO_REG_SOUND1CNT_H, "Channel 1 Duty/Len/Envelope", NULL); + debugger_io_new_bitfield(reg, 0, 5, "Sound length; units of (64-n)/256s", "(0-63)"); + debugger_io_new_bitfield(reg, 6, 7, "Wave Pattern Duty", "(0-3, see below)"); + debugger_io_new_bitfield(reg, 8, 10, "Envelope Step-Time; units of n/64s", "(1-7, 0=No Envelope)"); + debugger_io_new_bitfield(reg, 11, 11, "Envelope Direction", "(0=Decrease, 1=Increase)"); + debugger_io_new_bitfield(reg, 12, 15, "Initial Volume of envelope", "(1-15, 0=No Sound)"); + + reg = debugger_io_new_register16(IO_REG_SOUND1CNT_X, "Channel 1 Frequency/Control", NULL); + debugger_io_new_bitfield(reg, 0, 10, "Frequency; 131072/(2048-n)Hz", "(0-2047)"); + debugger_io_new_bitfield(reg, 11, 13, "Not used", NULL); + debugger_io_new_bitfield(reg, 14, 14, "Length Flag", "(1=Stop output when length in NR11 expires)"); + debugger_io_new_bitfield(reg, 15, 16, "Not used", NULL); + + reg = debugger_io_new_register16(IO_REG_SOUND2CNT_L, "Channel 2 Duty/Len/Envelope", NULL); + debugger_io_new_bitfield(reg, 0, 5, "Sound length; units of (64-n)/256s", "(0-63)"); + debugger_io_new_bitfield(reg, 6, 7, "Wave Pattern Duty", "(0-3, see below)"); + debugger_io_new_bitfield(reg, 8, 10, "Envelope Step-Time; units of n/64s", "(1-7, 0=No Envelope)"); + debugger_io_new_bitfield(reg, 11, 11, "Envelope Direction", "(0=Decrease, 1=Increase)"); + debugger_io_new_bitfield(reg, 12, 15, "Initial Volume of envelope", "(1-15, 0=No Sound)"); + + reg = debugger_io_new_register16(IO_REG_SOUND2CNT_H, "Channel 2 Frequency/Control", NULL); + debugger_io_new_bitfield(reg, 0, 10, "Frequency; 131072/(2048-n)Hz", "(0-2047)"); + debugger_io_new_bitfield(reg, 11, 13, "Not used", NULL); + debugger_io_new_bitfield(reg, 14, 14, "Length Flag", "(1=Stop output when length in NR11 expires)"); + debugger_io_new_bitfield(reg, 15, 16, "Not used", NULL); + + // TODO Channel 3 + + reg = debugger_io_new_register16(IO_REG_SOUND4CNT_L, "Channel 4 Len/Envelope", NULL); + debugger_io_new_bitfield(reg, 0, 5, "Sound length; units of (64-n)/256s", "(0-63)"); + debugger_io_new_bitfield(reg, 6, 7, "Not used", NULL); + debugger_io_new_bitfield(reg, 8, 10, "Envelope Step-Time; units of n/64s", "(1-7, 0=No Envelope)"); + debugger_io_new_bitfield(reg, 11, 11, "Envelope Direction", "(0=Decrease, 1=Increase)"); + debugger_io_new_bitfield(reg, 12, 15, "Initial Volume of envelope", "(1-15, 0=No Sound)"); + + reg = debugger_io_new_register16(IO_REG_SOUND4CNT_H, "Channel 4 Frequency/Control", NULL); + debugger_io_new_bitfield(reg, 0, 2, "Dividing Ratio of Frequencies (r)", NULL); + debugger_io_new_bitfield(reg, 3, 3, "Counter Step/Width (0=15 bits, 1=7 bits)", NULL); + debugger_io_new_bitfield(reg, 4, 7, "Shift Clock Frequency (s)", NULL); + debugger_io_new_bitfield(reg, 8, 13, "Not used", NULL); + debugger_io_new_bitfield(reg, 14, 14, "Length Flag", "(1=Stop output when length in NR41 expires)"); + debugger_io_new_bitfield(reg, 15, 15, "Initial", "(1=Restart Sound)"); } } diff --git a/source/gba/apu/apu.c b/source/gba/apu/apu.c index 4d7c95b3..baccb791 100644 --- a/source/gba/apu/apu.c +++ b/source/gba/apu/apu.c @@ -7,55 +7,19 @@ ** \******************************************************************************/ -#include #include "gba/gba.h" #include "gba/apu.h" -#include "gba/scheduler.h" -void -apu_reset_fifo( - struct gba *gba, - enum fifo_idx fifo_idx -) { - memset(&gba->apu.fifos[fifo_idx], 0, sizeof(gba->apu.fifos[0])); -} - -void -apu_fifo_write8( - struct gba *gba, - enum fifo_idx fifo_idx, - uint8_t val -) { - struct fifo *fifo; - - fifo = &gba->apu.fifos[fifo_idx]; - - if (fifo->size < FIFO_CAPACITY) { - fifo->data[fifo->write_idx] = (int8_t)val; - fifo->write_idx = (fifo->write_idx + 1) % FIFO_CAPACITY; - ++fifo->size; - } -} - -static -int8_t -apu_fifo_read8( - struct gba *gba, - uint32_t fifo_idx -) { - struct fifo *fifo; - int8_t val; - - fifo = &gba->apu.fifos[fifo_idx]; - val = fifo->data[fifo->read_idx]; - - if (fifo->size > 0) { - fifo->read_idx = (fifo->read_idx + 1) % FIFO_CAPACITY; - --fifo->size; - } +/* +** Reference for the APU implementation: +** - https://belogic.com/gba/channel1.shtml +** - https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware +** - https://gbdev.io/pandocs/Audio_details.html +** - https://nightshade256.github.io/2021/03/27/gb-sound-emulation.html +*/ - return (val); -} +static int32_t fifo_volume[2] = {2, 4}; +static int32_t psg_volume[4] = {1, 2, 4, 0}; static void @@ -89,58 +53,6 @@ apu_rbuffer_pop( return (val); } -void -apu_on_timer_overflow( - struct gba *gba, - uint32_t timer_id -) { - struct io *io; - size_t fifo_idx; - - io = &gba->io; - - if (!io->soundcnt_x.master_enable) { - return; - } - - for (fifo_idx = 0; fifo_idx < 2; ++fifo_idx) { - - // We are interested only in the FIFO synchronised with our timer - if (bitfield_get(io->soundcnt_h.raw, 10 + fifo_idx * 4) != timer_id) { - continue; - } - - gba->apu.latch.fifo[fifo_idx] = apu_fifo_read8(gba, fifo_idx); - - if (gba->apu.fifos[fifo_idx].size <= 16) { - size_t dma_idx; - - for (dma_idx = 1; dma_idx <= 2; ++dma_idx) { - if (mem_dma_is_fifo(gba, dma_idx, fifo_idx)) { - mem_schedule_dma_transfers_for(gba, dma_idx, DMA_TIMING_SPECIAL); // Fifo DMA - } - } - } - } -} - -/* -** Called at a rate of 256Hz to handle the different modulation units (length, envelope and sweep) -*/ -void -apu_sequencer( - struct gba *gba, - struct event_args args __unused -) { - /* Wave - Length */ - if (gba->io.sound3cnt_l.enable && gba->io.sound3cnt_x.use_length && gba->apu.wave.length) { - --gba->apu.wave.length; - if (!gba->apu.wave.length) { - apu_wave_stop(gba); - } - } -} - /* ** This function is called at the same frequency than the real hardware the emulator is running on (probably 48000Hz). ** @@ -151,30 +63,46 @@ apu_resample( struct gba *gba, struct event_args args __unused ) { - static int32_t fifo_volume[2] = {2, 4}; - static int32_t sound_volume[4] = {1, 2, 4, 0}; int32_t sample_l; int32_t sample_r; sample_l = 0; sample_r = 0; - sample_l += (gba->apu.latch.wave * (bool)gba->io.soundcnt_l.enable_sound_3_left); - sample_r += (gba->apu.latch.wave * (bool)gba->io.soundcnt_l.enable_sound_3_right); + sample_l += (gba->apu.latch.channel_1 * (bool)gba->io.soundcnt_l.enable_sound_1_left); // [-0x80; 0x80] + sample_r += (gba->apu.latch.channel_1 * (bool)gba->io.soundcnt_l.enable_sound_1_right); + + sample_l += (gba->apu.latch.channel_2 * (bool)gba->io.soundcnt_l.enable_sound_2_left); // [-0x100; 0x100] + sample_r += (gba->apu.latch.channel_2 * (bool)gba->io.soundcnt_l.enable_sound_2_right); + + sample_l += (gba->apu.latch.channel_3 * (bool)gba->io.soundcnt_l.enable_sound_3_left); // [-0x180; 0x180] + sample_r += (gba->apu.latch.channel_3 * (bool)gba->io.soundcnt_l.enable_sound_3_right); + + sample_l += (gba->apu.latch.channel_4 * (bool)gba->io.soundcnt_l.enable_sound_4_left); // [-0x200; 0x200] + sample_r += (gba->apu.latch.channel_4 * (bool)gba->io.soundcnt_l.enable_sound_4_right); + + sample_l *= psg_volume[gba->io.soundcnt_h.volume_channels] * gba->io.soundcnt_l.channel_left_volume; // [-0x3800; 0x3800] + sample_r *= psg_volume[gba->io.soundcnt_h.volume_channels] * gba->io.soundcnt_l.channel_right_volume; // [-0x3800; 0x3800] - sample_l = sample_l * sound_volume[gba->io.soundcnt_h.volume_sounds]; - sample_r = sample_r * sound_volume[gba->io.soundcnt_h.volume_sounds]; + /* + ** Keep the range of the PSG channels within [-0x200; 0x200] even after applying the volumes. + ** This ensures the ratio PSG/Direct Sound is normal. + ** + ** max(sound_volume) * max(gba->io.soundcnt_l.channel_{left,right}_volume) = 4 * 7 = 28 + */ + sample_l /= 28; // [-0x200; 0x200] + sample_r /= 28; - sample_l += (gba->apu.latch.fifo[FIFO_A] * (bool)gba->io.soundcnt_h.enable_fifo_a_left) * fifo_volume[gba->io.soundcnt_h.volume_fifo_a]; + sample_l += (gba->apu.latch.fifo[FIFO_A] * (bool)gba->io.soundcnt_h.enable_fifo_a_left) * fifo_volume[gba->io.soundcnt_h.volume_fifo_a]; // [-0x400; 0x400] sample_r += (gba->apu.latch.fifo[FIFO_A] * (bool)gba->io.soundcnt_h.enable_fifo_a_right) * fifo_volume[gba->io.soundcnt_h.volume_fifo_a]; - sample_l += (gba->apu.latch.fifo[FIFO_B] * (bool)gba->io.soundcnt_h.enable_fifo_b_left) * fifo_volume[gba->io.soundcnt_h.volume_fifo_b]; + sample_l += (gba->apu.latch.fifo[FIFO_B] * (bool)gba->io.soundcnt_h.enable_fifo_b_left) * fifo_volume[gba->io.soundcnt_h.volume_fifo_b]; // [-0x600; 0x600] sample_r += (gba->apu.latch.fifo[FIFO_B] * (bool)gba->io.soundcnt_h.enable_fifo_b_right) * fifo_volume[gba->io.soundcnt_h.volume_fifo_b]; - sample_l += gba->io.soundbias.bias; + sample_l += gba->io.soundbias.bias; // [-0x400; 0x800] (with default bias) sample_r += gba->io.soundbias.bias; - sample_l = max(min(sample_l, 0x3FF), 0) - 0x200; + sample_l = max(min(sample_l, 0x3FF), 0) - 0x200; // [-0x200; 0x200] sample_r = max(min(sample_r, 0x3FF), 0) - 0x200; sample_l *= 32; // Otherwise we can't hear much diff --git a/source/gba/apu/fifo.c b/source/gba/apu/fifo.c new file mode 100644 index 00000000..3295fc30 --- /dev/null +++ b/source/gba/apu/fifo.c @@ -0,0 +1,92 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2023 - The Hades Authors +** +\******************************************************************************/ + +#include +#include "hades.h" +#include "gba/gba.h" + +void +apu_reset_fifo( + struct gba *gba, + enum fifo_idx fifo_idx +) { + memset(&gba->apu.fifos[fifo_idx], 0, sizeof(gba->apu.fifos[0])); +} + +void +apu_fifo_write8( + struct gba *gba, + enum fifo_idx fifo_idx, + uint8_t val +) { + struct apu_fifo *fifo; + + fifo = &gba->apu.fifos[fifo_idx]; + + if (fifo->size < FIFO_CAPACITY) { + fifo->data[fifo->write_idx] = (int8_t)val; + fifo->write_idx = (fifo->write_idx + 1) % FIFO_CAPACITY; + ++fifo->size; + } +} + +static +int8_t +apu_fifo_read8( + struct gba *gba, + uint32_t fifo_idx +) { + struct apu_fifo *fifo; + int8_t val; + + fifo = &gba->apu.fifos[fifo_idx]; + val = fifo->data[fifo->read_idx]; + + if (fifo->size > 0) { + fifo->read_idx = (fifo->read_idx + 1) % FIFO_CAPACITY; + --fifo->size; + } + + return (val); +} + +void +apu_fifo_timer_overflow( + struct gba *gba, + uint32_t timer_id +) { + struct io *io; + size_t fifo_idx; + + io = &gba->io; + + if (!io->soundcnt_x.master_enable) { + return; + } + + for (fifo_idx = 0; fifo_idx < 2; ++fifo_idx) { + + // We are interested only in the FIFO synchronised with our timer + if (bitfield_get(io->soundcnt_h.raw, 10 + fifo_idx * 4) != timer_id) { + continue; + } + + gba->apu.latch.fifo[fifo_idx] = apu_fifo_read8(gba, fifo_idx); + + if (gba->apu.fifos[fifo_idx].size <= 16) { + size_t dma_idx; + + for (dma_idx = 1; dma_idx <= 2; ++dma_idx) { + if (mem_dma_is_fifo(gba, dma_idx, fifo_idx)) { + mem_schedule_dma_transfers_for(gba, dma_idx, DMA_TIMING_SPECIAL); // Fifo DMA + } + } + } + } +} diff --git a/source/gba/apu/modules.c b/source/gba/apu/modules.c new file mode 100644 index 00000000..eb7a57d8 --- /dev/null +++ b/source/gba/apu/modules.c @@ -0,0 +1,146 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2023 - The Hades Authors +** +\******************************************************************************/ + +#include "hades.h" +#include "gba/gba.h" + +void +apu_modules_counter_reset( + struct apu_counter *counter, + bool enabled, + uint32_t value +) { + counter->enabled = enabled; + counter->value = value; +} + +static inline +bool +apu_modules_counter_step( + struct apu_counter *counter +) { + if (counter->enabled) { + counter->value -= (counter->value > 0); + return (counter->value > 0); + } + return (true); +} + +void +apu_modules_sweep_reset( + struct apu_sweep *sweep, + uint32_t frequency, + uint32_t shifts, + bool direction, + uint32_t time +) { + sweep->shifts = shifts; + sweep->direction = direction; + sweep->time = time; + + sweep->frequency = frequency; + sweep->shadow_frequency = frequency; + + sweep->step = sweep->time; +} + +static inline +bool +apu_modules_sweep_step( + struct apu_sweep *sweep +) { + if (sweep->time) { + --sweep->step; + + if (!sweep->step) { + uint32_t new_frequency; + + if (sweep->direction) { + new_frequency = sweep->shadow_frequency - (sweep->shadow_frequency >> sweep->shifts); + } else { + new_frequency = sweep->shadow_frequency + (sweep->shadow_frequency >> sweep->shifts); + } + + if (new_frequency >= 2048) { + return (false); + } else if (sweep->shifts > 0) { + sweep->frequency = new_frequency; + sweep->shadow_frequency = new_frequency; + } + + sweep->step = sweep->time; + } + } + + return (true); +} + +void +apu_modules_envelope_reset( + struct apu_envelope *envelope, + uint32_t step_time, + bool direction, + uint32_t initial_volume +) { + envelope->step_time = step_time; + envelope->direction = direction; + envelope->initial_volume = initial_volume; + + envelope->step = step_time; + envelope->volume = initial_volume; +} + +static inline +void +apu_modules_envelope_step( + struct apu_envelope *envelope +) { + if (envelope->step_time) { + --envelope->step; + + if (!envelope->step) { + envelope->step = envelope->step_time; + + envelope->volume += 2 * envelope->direction - 1; // Increment or decrement the volume based on the direction + envelope->volume = max(min(envelope->volume, 0xF), 0); // Clamp the volume to [0; 0xF] + } + } +} + +/* +** Called at a rate of 512Hz to update all the submodules that a PSG channel can rely on. +*/ +void +apu_modules_step( + struct gba *gba, + struct event_args args __unused +) { + // Tick the length counter modules at a rate of 256Hz + if ((gba->apu.modules_step % 2) == 0) { + gba->apu.tone_and_sweep.enabled &= apu_modules_counter_step(&gba->apu.tone_and_sweep.counter); + gba->apu.tone.enabled &= apu_modules_counter_step(&gba->apu.tone.counter); + gba->apu.wave.enabled &= apu_modules_counter_step(&gba->apu.wave.counter); + gba->apu.noise.enabled &= apu_modules_counter_step(&gba->apu.noise.counter); + } + + // Tick the sweep module at a rate of 128Hz + if (gba->apu.modules_step == 2 || gba->apu.modules_step == 6) { + gba->apu.tone_and_sweep.enabled &= apu_modules_sweep_step(&gba->apu.tone_and_sweep.sweep); + } + + // Tick the envelope modules at a rate of 64Hz + if (gba->apu.modules_step == 7) { + apu_modules_envelope_step(&gba->apu.tone_and_sweep.envelope); + apu_modules_envelope_step(&gba->apu.tone.envelope); + apu_modules_envelope_step(&gba->apu.noise.envelope); + } + + ++gba->apu.modules_step; + gba->apu.modules_step %= 8; +} diff --git a/source/gba/apu/noise.c b/source/gba/apu/noise.c new file mode 100644 index 00000000..5bc1e892 --- /dev/null +++ b/source/gba/apu/noise.c @@ -0,0 +1,114 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2023 - The Hades Authors +** +\******************************************************************************/ + +#include "hades.h" +#include "gba/gba.h" + +void +apu_noise_reset( + struct gba *gba +) { + uint64_t period; + + gba->io.sound4cnt_h.reset = false; + + apu_noise_stop(gba); + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound4cnt_l.envelope_direction && !gba->io.sound4cnt_l.envelope_initial_volume) { + return ; + } + + gba->apu.noise.enabled = true; + gba->apu.noise.lfsr = gba->io.sound4cnt_h.width ? 0x40 : 0x4000; + + apu_modules_envelope_reset( + &gba->apu.noise.envelope, + gba->io.sound4cnt_l.eveloppe_step_time, + gba->io.sound4cnt_l.envelope_direction, + gba->io.sound4cnt_l.envelope_initial_volume + ); + + apu_modules_counter_reset( + &gba->apu.noise.counter, + gba->io.sound4cnt_h.use_length, + gba->io.sound4cnt_h.use_length ? 64 - gba->io.sound4cnt_l.length : 0 + ); + + period = 524288; + if (gba->io.sound4cnt_h.frequency_ratio == 0) { + period *= 2; + } else { + period /= gba->io.sound4cnt_h.frequency_ratio; + } + period /= 1 << (gba->io.sound4cnt_h.frequency_shift + 1); + period = GBA_CYCLES_PER_SECOND / period; + + gba->apu.noise.step_handler = sched_add_event( + gba, + NEW_REPEAT_EVENT( + SCHED_EVENT_APU_NOISE_STEP, + gba->scheduler.cycles, // TODO: Is there a delay before the sound is started? + period + ) + ); +} + +void +apu_noise_stop( + struct gba *gba +) { + gba->io.soundcnt_x.sound_4_status = false; + gba->apu.latch.channel_4 = 0; + gba->apu.noise.lfsr = 0; + gba->apu.noise.enabled = false; + + if (gba->apu.noise.step_handler != INVALID_EVENT_HANDLE) { + sched_cancel_event(gba, gba->apu.noise.step_handler); + gba->apu.noise.step_handler = INVALID_EVENT_HANDLE; + } +} + +void +apu_noise_step( + struct gba *gba, + struct event_args args __unused +) { + bool carry; + int16_t sample; + + if (!gba->apu.noise.enabled) { + apu_noise_stop(gba); + return ; + } + + gba->io.soundcnt_x.sound_4_status = true; + + carry = gba->apu.noise.lfsr & 0b1; + + gba->apu.noise.lfsr >>= 1; + + if (carry) { + gba->apu.noise.lfsr ^= gba->io.sound4cnt_h.width ? 0x60 : 0x6000; + } + + // Center the sample around 0. + sample = 2 * carry - 1; // [-1; 1] + + // Apply counter + sample *= gba->io.sound4cnt_h.use_length ? (gba->apu.noise.counter.value > 0) : 1; // [-1; +1] + + // Apply envelope + sample *= gba->apu.noise.envelope.volume; // [-15; 15], since `volume` is `[0; 0xF]` + + // Adjust the volume to match the other PSG channels + sample *= 8; // [-120; 120] + + gba->apu.latch.channel_4 = sample; +} diff --git a/source/gba/apu/tone.c b/source/gba/apu/tone.c new file mode 100644 index 00000000..06135ebb --- /dev/null +++ b/source/gba/apu/tone.c @@ -0,0 +1,211 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +********************************************************************/ + +#include "gba/gba.h" +#include "gba/apu.h" + +/* +** Reference: +** - https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Square_Wave +*/ +static int16_t duty_lut[4][8] = { + [0] = { 1, 1, 1, 1, 1, 1, 1, -1}, // 12.5% + [1] = { -1, 1, 1, 1, 1, 1, 1, -1}, // 25% + [2] = { -1, 1, 1, 1, 1, -1, -1, -1}, // 50% + [3] = { 1, -1, -1, -1, -1, -1, -1, 1}, // 75% +}; + +/* +** This is the frequency of a whole cycle. Our `step` function is called 8 times per cycle, hence why +** we have an extra division by 8 at the end. +*/ +#define CHANNEL_FREQUENCY_AS_CYCLES(x) ((GBA_CYCLES_PER_SECOND * (2048 - (x))) / (131072 * 8)) + +void +apu_tone_and_sweep_reset( + struct gba *gba +) { + gba->io.sound1cnt_x.reset = false; + + apu_tone_and_sweep_stop(gba); + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound1cnt_h.envelope_direction && !gba->io.sound1cnt_h.envelope_initial_volume) { + return ; + } + + gba->apu.tone_and_sweep.enabled = true; + + apu_modules_envelope_reset( + &gba->apu.tone_and_sweep.envelope, + gba->io.sound1cnt_h.eveloppe_step_time, + gba->io.sound1cnt_h.envelope_direction, + gba->io.sound1cnt_h.envelope_initial_volume + ); + + apu_modules_sweep_reset( + &gba->apu.tone_and_sweep.sweep, + gba->io.sound1cnt_x.sample_rate, + gba->io.sound1cnt_l.sweep_shift_number, + gba->io.sound1cnt_l.sweep_direction, + gba->io.sound1cnt_l.sweep_step_time + ); + + apu_modules_counter_reset( + &gba->apu.tone_and_sweep.counter, + gba->io.sound1cnt_x.use_length, + gba->io.sound1cnt_x.use_length ? 64 - gba->io.sound1cnt_h.length : 0 + ); + + gba->apu.tone_and_sweep.step_handler = sched_add_event( + gba, + NEW_FIX_EVENT( + SCHED_EVENT_APU_TONE_AND_SWEEP_STEP, + gba->scheduler.cycles + CHANNEL_FREQUENCY_AS_CYCLES(gba->apu.tone_and_sweep.sweep.frequency) // TODO: Is there a delay before the sound is started? + ) + ); +} + +void +apu_tone_and_sweep_stop( + struct gba *gba +) { + gba->io.soundcnt_x.sound_1_status = false; + gba->apu.latch.channel_1 = 0; + gba->apu.tone_and_sweep.enabled = false; + + if (gba->apu.tone_and_sweep.step_handler != INVALID_EVENT_HANDLE) { + sched_cancel_event(gba, gba->apu.tone_and_sweep.step_handler); + gba->apu.tone_and_sweep.step_handler = INVALID_EVENT_HANDLE; + } +} + +void +apu_tone_and_sweep_step( + struct gba *gba, + struct event_args args __unused +) { + int16_t sample; + + if (!gba->apu.tone_and_sweep.enabled) { + apu_tone_and_sweep_stop(gba); + return ; + } + + gba->io.soundcnt_x.sound_1_status = true; + + // Fetch the value from the duty LUT. + sample = duty_lut[gba->io.sound1cnt_h.duty][gba->apu.tone_and_sweep.step]; // [-1; +1] + + // Apply counter + sample *= gba->io.sound1cnt_x.use_length ? (gba->apu.tone_and_sweep.counter.value > 0) : 1; // [-1; +1] + + // Apply envelope + sample *= gba->apu.tone_and_sweep.envelope.volume; // [-15; 15], since `volume` is `[0; 0xF]` + + // Adjust the volume to match the other PSG channels + sample *= 8; // [-120; 120] + + gba->apu.latch.channel_1 = sample; + + // Increment the step counter + ++gba->apu.tone_and_sweep.step; + gba->apu.tone_and_sweep.step %= 8; + + gba->apu.tone_and_sweep.step_handler = sched_add_event( + gba, + NEW_FIX_EVENT( + SCHED_EVENT_APU_TONE_AND_SWEEP_STEP, + gba->scheduler.cycles + CHANNEL_FREQUENCY_AS_CYCLES(gba->apu.tone_and_sweep.sweep.frequency) // TODO: Is there a delay before the sound is started? + ) + ); +} + +void +apu_tone_reset( + struct gba *gba +) { + gba->io.sound2cnt_h.reset = false; + + apu_tone_stop(gba); + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound2cnt_l.envelope_direction && !gba->io.sound2cnt_l.envelope_initial_volume) { + return ; + } + + gba->apu.tone.enabled = true; + + apu_modules_envelope_reset( + &gba->apu.tone.envelope, + gba->io.sound2cnt_l.eveloppe_step_time, + gba->io.sound2cnt_l.envelope_direction, + gba->io.sound2cnt_l.envelope_initial_volume + ); + + apu_modules_counter_reset( + &gba->apu.tone.counter, + gba->io.sound2cnt_h.use_length, + gba->io.sound2cnt_h.use_length ? 64 - gba->io.sound2cnt_l.length : 0 + ); + + gba->apu.tone.step_handler = sched_add_event( + gba, + NEW_REPEAT_EVENT( + SCHED_EVENT_APU_TONE_STEP, + gba->scheduler.cycles, + CHANNEL_FREQUENCY_AS_CYCLES(gba->io.sound2cnt_h.sample_rate) // TODO: Is there a delay before the sound is started? + ) + ); +} + +void +apu_tone_stop( + struct gba *gba +) { + gba->io.soundcnt_x.sound_2_status = false; + gba->apu.latch.channel_2 = 0; + gba->apu.tone.enabled = false; + + if (gba->apu.tone.step_handler != INVALID_EVENT_HANDLE) { + sched_cancel_event(gba, gba->apu.tone.step_handler); + gba->apu.tone.step_handler = INVALID_EVENT_HANDLE; + } +} + +void +apu_tone_step( + struct gba *gba, + struct event_args args __unused +) { + int16_t sample; + + if (!gba->apu.tone.enabled) { + apu_tone_stop(gba); + return ; + } + + gba->io.soundcnt_x.sound_2_status = true; + + // Fetch the value from the duty LUT. + sample = duty_lut[gba->io.sound2cnt_l.duty][gba->apu.tone.step]; // [-1; +1] + + // Apply counter + sample *= gba->io.sound2cnt_h.use_length ? (gba->apu.tone.counter.value > 0) : 1; // [-1; +1] + + // Apply envelope + sample *= gba->apu.tone.envelope.volume; // [-15; 15], since `volume` is `[0; 0xF]` + + // Adjust the volume to match the other PSG channels + sample *= 8; // [-120; 120] + + gba->apu.latch.channel_2 = sample; + + // Increment the step counter + ++gba->apu.tone.step; + gba->apu.tone.step %= 8; +} diff --git a/source/gba/apu/wave.c b/source/gba/apu/wave.c index 8a0186e8..496971aa 100644 --- a/source/gba/apu/wave.c +++ b/source/gba/apu/wave.c @@ -14,30 +14,28 @@ static int16_t volume_lut[4] = { 0, 4, 2, 1}; -void -apu_wave_init( - struct gba *gba -) { - gba->apu.wave.step_handler = INVALID_EVENT_HANDLE; - gba->apu.wave.counter_handler = INVALID_EVENT_HANDLE; -} +#define CHANNEL_FREQUENCY_AS_CYCLES(x) ((GBA_CYCLES_PER_SECOND / 2097152) * (2048 - (x))) void apu_wave_reset( struct gba *gba ) { - size_t period; + uint64_t period; gba->io.sound3cnt_x.reset = false; + apu_wave_stop(gba); - if (gba->io.sound3cnt_x.use_length) { - gba->apu.wave.length = 256 - gba->io.sound3cnt_h.length; - } else { - gba->apu.wave.length = 0; - } + gba->apu.wave.enabled = true; + gba->apu.wave.step = 0; + + apu_modules_counter_reset( + &gba->apu.wave.counter, + gba->io.sound3cnt_x.use_length, + gba->io.sound3cnt_x.use_length ? 256 - gba->io.sound3cnt_h.length : 0 + ); - period = GBA_CYCLES_PER_SECOND / (2097152 / (2048 - gba->io.sound3cnt_x.sample_rate)); + period = CHANNEL_FREQUENCY_AS_CYCLES(gba->io.sound3cnt_x.sample_rate); gba->apu.wave.step_handler = sched_add_event( gba, @@ -53,20 +51,15 @@ void apu_wave_stop( struct gba *gba ) { - gba->apu.latch.wave = 0; + gba->io.soundcnt_x.sound_3_status = false; + gba->apu.latch.channel_3 = 0; gba->apu.wave.step = 0; - gba->apu.wave.length = 0; - gba->io.soundcnt_x.sound_1_status = false; + gba->apu.wave.enabled = false; if (gba->apu.wave.step_handler != INVALID_EVENT_HANDLE) { sched_cancel_event(gba, gba->apu.wave.step_handler); + gba->apu.wave.step_handler = INVALID_EVENT_HANDLE; } - gba->apu.wave.step_handler = INVALID_EVENT_HANDLE; - - if (gba->apu.wave.counter_handler != INVALID_EVENT_HANDLE) { - sched_cancel_event(gba, gba->apu.wave.counter_handler); - } - gba->apu.wave.counter_handler = INVALID_EVENT_HANDLE; } /* @@ -81,12 +74,12 @@ apu_wave_step( uint8_t byte; int16_t sample; - if (!gba->io.sound3cnt_l.enable) { + if (!gba->io.sound3cnt_l.enable || !gba->apu.wave.enabled) { apu_wave_stop(gba); return ; } - gba->io.soundcnt_x.sound_1_status = true; + gba->io.soundcnt_x.sound_3_status = true; byte = gba->io.waveram[gba->io.sound3cnt_l.bank_select][gba->apu.wave.step / 2]; @@ -96,13 +89,19 @@ apu_wave_step( byte >>= 4; } - // Recenter the sample around 0. - sample = byte - 8; + // Center the sample around 0. + sample = byte - 8; // [-8; 7] + + // Apply counter + sample *= gba->io.sound3cnt_x.use_length ? (gba->apu.wave.counter.value > 0) : 1; // [-8; 7] // Apply volume - sample *= gba->io.sound3cnt_h.force_volume ? 3 : volume_lut[gba->io.sound3cnt_h.volume]; + sample *= gba->io.sound3cnt_h.force_volume ? 3 : volume_lut[gba->io.sound3cnt_h.volume]; // [-32; 28], since volume is at most 4 + + // Adjust the volume to match the other PSG channels + sample *= 4; // [-128; 112] - gba->apu.latch.wave = sample; + gba->apu.latch.channel_3 = sample; // Swap bank if we reached the end of this one and `bank_mode` is 1. ++gba->apu.wave.step; diff --git a/source/gba/gba.c b/source/gba/gba.c index 7eb13927..3e46896a 100644 --- a/source/gba/gba.c +++ b/source/gba/gba.c @@ -219,16 +219,17 @@ gba_state_reset( apu = &gba->apu; memset(apu, 0, sizeof(*apu)); - // Wave Channel + gba->apu.tone_and_sweep.step_handler = INVALID_EVENT_HANDLE; + gba->apu.tone.step_handler = INVALID_EVENT_HANDLE; gba->apu.wave.step_handler = INVALID_EVENT_HANDLE; - gba->apu.wave.counter_handler = INVALID_EVENT_HANDLE; + gba->apu.noise.step_handler = INVALID_EVENT_HANDLE; sched_add_event( gba, NEW_REPEAT_EVENT( - SCHED_EVENT_APU_SEQUENCER, + SCHED_EVENT_APU_MODULES_STEP, 0, - GBA_CYCLES_PER_SECOND / 256 + GBA_CYCLES_PER_SECOND / 512 ) ); diff --git a/source/gba/memory/io.c b/source/gba/memory/io.c index 071dbef5..52ce131c 100644 --- a/source/gba/memory/io.c +++ b/source/gba/memory/io.c @@ -196,19 +196,43 @@ mem_io_read8( case IO_REG_BLDALPHA + 1: return (io->bldalpha.bytes[1]); /* Sound */ - case IO_REG_SOUND3CNT_L: return (io->sound3cnt_l.bytes[0]); - case IO_REG_SOUND3CNT_L + 1: return (io->sound3cnt_l.bytes[1]); - case IO_REG_SOUND3CNT_H: return (io->sound3cnt_h.bytes[0]); - case IO_REG_SOUND3CNT_H + 1: return (io->sound3cnt_h.bytes[1]); - case IO_REG_SOUND3CNT_X: return (io->sound3cnt_x.bytes[0]); - case IO_REG_SOUND3CNT_X + 1: return (io->sound3cnt_x.bytes[1]); + case IO_REG_SOUND1CNT_L: return (io->sound1cnt_l.bytes[0]); + case IO_REG_SOUND1CNT_L + 1: return (io->sound1cnt_l.bytes[1]); + case IO_REG_SOUND1CNT_H: return (io->sound1cnt_h.bytes[0] & 0xC0); + case IO_REG_SOUND1CNT_H + 1: return (io->sound1cnt_h.bytes[1]); + case IO_REG_SOUND1CNT_X: return (0); + case IO_REG_SOUND1CNT_X + 1: return (io->sound1cnt_x.bytes[1] & 0x40); + case IO_REG_SOUND1CNT_X + 2: return (0); + case IO_REG_SOUND1CNT_X + 3: return (0); + case IO_REG_SOUND2CNT_L: return (io->sound2cnt_l.bytes[0] & 0xC0); + case IO_REG_SOUND2CNT_L + 1: return (io->sound2cnt_l.bytes[1]); + case IO_REG_SOUND2CNT_L + 2: return (0); + case IO_REG_SOUND2CNT_L + 3: return (0); + case IO_REG_SOUND2CNT_H: return (0); + case IO_REG_SOUND2CNT_H + 1: return (io->sound2cnt_h.bytes[1] & 0x40); + case IO_REG_SOUND2CNT_H + 2: return (0); + case IO_REG_SOUND2CNT_H + 3: return (0); + case IO_REG_SOUND3CNT_L: return (io->sound3cnt_l.bytes[0] & 0xE0); + case IO_REG_SOUND3CNT_L + 1: return (0); + case IO_REG_SOUND3CNT_H: return (0); + case IO_REG_SOUND3CNT_H + 1: return (io->sound3cnt_h.bytes[1] & 0xE0); + case IO_REG_SOUND3CNT_X: return (0); + case IO_REG_SOUND3CNT_X + 1: return (io->sound3cnt_x.bytes[1] & 0x40); case IO_REG_SOUND3CNT_X + 2: case IO_REG_SOUND3CNT_X + 3: return (0); + case IO_REG_SOUND4CNT_L: return (0); + case IO_REG_SOUND4CNT_L + 1: return (io->sound4cnt_l.bytes[1]); + case IO_REG_SOUND4CNT_L + 2: return (0); + case IO_REG_SOUND4CNT_L + 3: return (0); + case IO_REG_SOUND4CNT_H: return (io->sound4cnt_h.bytes[0]); + case IO_REG_SOUND4CNT_H + 1: return (io->sound4cnt_h.bytes[1] & 0x40); + case IO_REG_SOUND4CNT_H + 2: return (0); + case IO_REG_SOUND4CNT_H + 3: return (0); case IO_REG_SOUNDCNT_L: return (io->soundcnt_l.bytes[0]); case IO_REG_SOUNDCNT_L + 1: return (io->soundcnt_l.bytes[1]); case IO_REG_SOUNDCNT_H: return (io->soundcnt_h.bytes[0]); case IO_REG_SOUNDCNT_H + 1: return (io->soundcnt_h.bytes[1]); - case IO_REG_SOUNDCNT_X: return (io->soundcnt_x.bytes[0]); + case IO_REG_SOUNDCNT_X: return (io->soundcnt_x.bytes[0] & 0x8F); case IO_REG_SOUNDCNT_X + 1: case IO_REG_SOUNDCNT_X + 2: case IO_REG_SOUNDCNT_X + 3: return (0); @@ -306,6 +330,12 @@ mem_io_read8( case IO_REG_SIOCNT + 1: return (io->siocnt.bytes[1]); case IO_REG_RCNT: return (io->rcnt.bytes[0]); case IO_REG_RCNT + 1: return (io->rcnt.bytes[1]); + case IO_REG_IR: return (0); + case IO_REG_IR + 1: return (0); + case IO_REG_UNKNOWN_1: return (0); + case IO_REG_UNKNOWN_1 + 1: return (0); + case IO_REG_UNKNOWN_2: return (0); + case IO_REG_UNKNOWN_2 + 1: return (0); /* Interrupts */ case IO_REG_IE: return (io->int_enabled.bytes[0]); @@ -320,6 +350,8 @@ mem_io_read8( case IO_REG_IME + 1: case IO_REG_IME + 2: case IO_REG_IME + 3: return (0); + case IO_REG_UNKNOWN_3: return (0); + case IO_REG_UNKNOWN_3 + 1: return (0); /* System */ case IO_REG_POSTFLG: return (io->postflg); @@ -436,6 +468,47 @@ mem_io_write8( case IO_REG_BLDY + 1: io->bldy.bytes[1] = val; break; /* Sound */ + case IO_REG_SOUND1CNT_L: io->sound1cnt_l.bytes[0] = val & 0x7F; break; + case IO_REG_SOUND1CNT_H: io->sound1cnt_h.bytes[0] = val; break; + case IO_REG_SOUND1CNT_H + 1: { + io->sound1cnt_h.bytes[1] = val; + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound1cnt_h.envelope_direction && !gba->io.sound1cnt_h.envelope_initial_volume) { + apu_tone_and_sweep_stop(gba); + } + + break; + }; + case IO_REG_SOUND1CNT_X: io->sound1cnt_x.bytes[0] = val; break; + case IO_REG_SOUND1CNT_X + 1: { + io->sound1cnt_x.bytes[1] = val; + if (io->sound1cnt_x.reset) { + apu_tone_and_sweep_reset(gba); + } + io->sound1cnt_x.reset = false; + break; + }; + case IO_REG_SOUND2CNT_L: io->sound2cnt_l.bytes[0] = val; break; + case IO_REG_SOUND2CNT_L + 1: { + io->sound2cnt_l.bytes[1] = val; + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound2cnt_l.envelope_direction && !gba->io.sound2cnt_l.envelope_initial_volume) { + apu_tone_stop(gba); + } + + break; + }; + case IO_REG_SOUND2CNT_H: io->sound2cnt_h.bytes[0] = val; break; + case IO_REG_SOUND2CNT_H + 1: { + io->sound2cnt_h.bytes[1] = val; + if (io->sound2cnt_h.reset) { + apu_tone_reset(gba); + } + io->sound2cnt_h.reset = false; + break; + }; case IO_REG_SOUND3CNT_L: { io->sound3cnt_l.bytes[0] = val; if (!io->sound3cnt_l.enable) { @@ -454,7 +527,27 @@ mem_io_write8( io->sound3cnt_x.reset = false; break; }; - case IO_REG_SOUNDCNT_L: io->soundcnt_l.bytes[0] = val; break; + case IO_REG_SOUND4CNT_L: io->sound4cnt_l.bytes[0] = val; break; + case IO_REG_SOUND4CNT_L + 1: { + io->sound4cnt_l.bytes[1] = val; + + // Enveloppe set to decrease mode with a volume of 0 mutes the channel + if (!gba->io.sound4cnt_l.envelope_direction && !gba->io.sound4cnt_l.envelope_initial_volume) { + apu_tone_and_sweep_stop(gba); + } + + break; + }; + case IO_REG_SOUND4CNT_H: io->sound4cnt_h.bytes[0] = val; break; + case IO_REG_SOUND4CNT_H + 1: { + io->sound4cnt_h.bytes[1] = val; + if (io->sound4cnt_h.reset) { + apu_noise_reset(gba); + } + io->sound4cnt_h.reset = false; + break; + }; + case IO_REG_SOUNDCNT_L: io->soundcnt_l.bytes[0] = val & 0x77; break; case IO_REG_SOUNDCNT_L + 1: io->soundcnt_l.bytes[1] = val; break; case IO_REG_SOUNDCNT_H: io->soundcnt_h.bytes[0] = val & 0x0F; break; case IO_REG_SOUNDCNT_H + 1: { diff --git a/source/gba/meson.build b/source/gba/meson.build index e73b0b8a..9757bcc3 100644 --- a/source/gba/meson.build +++ b/source/gba/meson.build @@ -10,6 +10,10 @@ libgba = static_library( 'gba', 'apu/apu.c', + 'apu/fifo.c', + 'apu/modules.c', + 'apu/noise.c', + 'apu/tone.c', 'apu/wave.c', 'core/arm/alu.c', 'core/arm/bdt.c', diff --git a/source/gba/scheduler.c b/source/gba/scheduler.c index 91c68879..74985954 100644 --- a/source/gba/scheduler.c +++ b/source/gba/scheduler.c @@ -19,9 +19,12 @@ void (*sched_event_callbacks[])(struct gba *gba, struct event_args args) = { [SCHED_EVENT_PPU_HBLANK] = ppu_hblank, [SCHED_EVENT_TIMER_OVERFLOW] = timer_overflow, [SCHED_EVENT_TIMER_STOP] = timer_stop, - [SCHED_EVENT_APU_SEQUENCER] = apu_sequencer, + [SCHED_EVENT_APU_MODULES_STEP] = apu_modules_step, [SCHED_EVENT_APU_RESAMPLE] = apu_resample, + [SCHED_EVENT_APU_TONE_AND_SWEEP_STEP] = apu_tone_and_sweep_step, + [SCHED_EVENT_APU_TONE_STEP] = apu_tone_step, [SCHED_EVENT_APU_WAVE_STEP] = apu_wave_step, + [SCHED_EVENT_APU_NOISE_STEP] = apu_noise_step, [SCHED_EVENT_DMA_ADD_PENDING] = mem_dma_add_to_pending, }; diff --git a/source/gba/timer.c b/source/gba/timer.c index 9287e002..95a3c068 100644 --- a/source/gba/timer.c +++ b/source/gba/timer.c @@ -92,7 +92,7 @@ timer_overflow( } if (timer_idx == 0 || timer_idx == 1) { - apu_on_timer_overflow(gba, timer_idx); + apu_fifo_timer_overflow(gba, timer_idx); } if (timer_idx < 3 && gba->io.timers[timer_idx + 1].control.enable && gba->io.timers[timer_idx + 1].control.count_up) {