From d217db42a480030437c3bc706454aac1f250251b Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Sat, 4 Jan 2025 01:41:28 -0800 Subject: [PATCH] Add SDL audio; complete DMC channel --- Makefile | 2 +- src/apu.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++------ src/apu.h | 10 +++-- src/nese.c | 13 +++++++ 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index e835e30..36270fa 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ LDLIBS_1 = -lSDL2 SRC_SRCS_1 = nese.c ines.c SRC_SRCS_1 += nes.c ppu.c input.c SRC_SRCS_1 += cart.c mapper.c -SRC_SRCS_1 += sdl_render.c sdl_input.c SRC_SRCS_1 += apu.c +SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c MAPDIR = src/map MAP_SRCS_1 = nrom.c mmc1.c uxrom.c cnrom.c mmc3.c diff --git a/src/apu.c b/src/apu.c index bd34c17..2e17f28 100644 --- a/src/apu.c +++ b/src/apu.c @@ -1,9 +1,17 @@ #ifndef APU_H_ #define APU_H_ +#include "nes.h" #include "apu.h" #include "blip-buf/blip_buf.h" + +#define apu_gain_square (563U) +#define apu_gain_triangle (723U) +#define apu_gain_noise (482U) +#define apu_gain_dmc (240U) + + /* static const uint8_t length_lut [32] = { 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, @@ -18,6 +26,30 @@ static const uint16_t dmc_period_lut [16] = { 190, 160, 142, 128, 106, 84, 72, 54, }; +static const void dmc_restart(nes_apu_Channel* channel) { + channel->length = (channel->reg[3] * 16) + 1; + channel->addr = 0x4000U | ((uint16_t)channel->reg[2] << 6); +} + +static const void nes_apu_dmc_fetch(nes_apu* apu, + nes_apu_Channel* channel) { + channel->data = apu->mem_read(apu->arg_mem, + 0x8000U | channel->addr); + channel->mask = 1; + channel->addr = (channel->addr + 1) & 0x7FFFU; + channel->length--; + if ( channel->length <= 0 && + (channel->reg[0] & apu_DMC_Loop)) { + dmc_restart(channel); + } +} + +static const void nes_apu_dmc_start(nes_apu* apu, + nes_apu_Channel* channel) { + dmc_restart(channel); + nes_apu_dmc_fetch(apu, channel); +} + static void nes_apu_write_square(nes_apu_Channel* channel, int reg, uint8_t val) { APU_LOG("APU: Square %d < %02x\n", reg, val); @@ -41,16 +73,14 @@ static void nes_apu_write_dmc(nes_apu_Channel* channel, APU_LOG("APU: DMC %d < %02x\n", reg, val); if (reg == 0) { - channel->period = dmc_period_lut[val & 0xFU]; + channel->period = dmc_period_lut[val & 0xFU] * + nes_clock_cpu_div; if (!(val & apu_DMC_IRQ_Enable)) { channel->interrupt = 0; } } else if (reg == 1) { channel->sample = (val & 0x7FU); - - } else if (reg == 3) { - channel->length = val; } } @@ -60,7 +90,7 @@ int nes_apu_init(nes_apu* apu, int clock, int frequency, int ret = 0; // 20 ms buffer - apu->blip = blip_new(frequency / 50); + apu->blip = blip_new(frequency / 20); if (NULL == apu->blip) { APU_ERR("APU: Failed to create resampler\n"); @@ -80,6 +110,12 @@ int nes_apu_init(nes_apu* apu, int clock, int frequency, apu->channels[apu_Channel_Noise].write = nes_apu_write_noise; apu->channels[apu_Channel_DMC].write = nes_apu_write_dmc; + apu->channels[apu_Channel_Square_0].gain = apu_gain_square; + apu->channels[apu_Channel_Square_1].gain = apu_gain_square; + apu->channels[apu_Channel_Triangle].gain = apu_gain_triangle; + apu->channels[apu_Channel_Noise].gain = apu_gain_noise; + apu->channels[apu_Channel_DMC].gain = apu_gain_dmc; + apu->frame_lfsr = 0x7FFFU; apu->frame_period = clock / 240; } @@ -142,15 +178,15 @@ void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) { APU_LOG("APU: Enable %02x\n", val); for (int chan = 0; chan < nes_apu_chan_count; ++chan) { - if (!((val >> chan) & 1)) { + if (!(val & (1 << chan))) { apu->channels[chan].length = 0; } - val >>= 1; } if ( (val & (1 << apu_Channel_DMC)) && !(apu->status & (1 << apu_Channel_DMC))) { - // TODO: Start DMC + nes_apu_dmc_start(apu, + &apu->channels[apu_Channel_DMC]); } if (!(val & (1 << apu_Channel_DMC))) { apu->channels[apu_Channel_DMC].interrupt = 0; @@ -174,11 +210,59 @@ void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) { } } +static int channel_update(nes_apu_Channel* channel) { + int output = (channel->gain * channel->sample); + int delta = output - channel->output; + channel->output = output; + return delta; +} + +static void nes_apu_run_dmc(nes_apu* apu, + nes_apu_Channel* channel, + int cycles) { + if (channel->length <= 0) return; + + int time = apu->time; + int delta = channel_update(channel); + if (delta) { + blip_add_delta(apu->blip, time, delta); + } + + while (cycles > 0) { + int run = cycles; + if (run > channel->delay) { + run = channel->delay; + } + + channel->delay -= run; + time += run; + + if (channel->delay <= 0) { + channel->delay = channel->period; + + delta = (channel->mask & channel->data) ? 2 : -2; + int sample = channel->sample + delta; + if ((unsigned)sample <= 0x7FU) { + channel->sample = sample; + delta = channel_update(channel); + if (delta) { + blip_add_delta(apu->blip, time, delta); + } + } + + channel->mask <<= 1; + if (!channel->mask) { + nes_apu_dmc_fetch(apu, channel); + } + } + + cycles -= run; + } +} + nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { -/* - nes_apu_run_dmc(&apu->channels[apu_Channel_DMC], - apu->blip, cycles); -*/ + nes_apu_run_dmc(apu, &apu->channels[apu_Channel_DMC], cycles); + while (cycles > 0) { int run = cycles; if (run > apu->frame_delay) { @@ -249,6 +333,7 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { } cycles -= run; + apu->time += run; } return ( (apu->status & apu_Status_Frame_Int) || diff --git a/src/apu.h b/src/apu.h index 07c5f22..10c612f 100644 --- a/src/apu.h +++ b/src/apu.h @@ -87,9 +87,11 @@ static inline int nes_apu_osc_length(uint8_t reg[4]) { typedef struct nes_apu_Channel_t { uint8_t reg[4]; void(*write)(struct nes_apu_Channel_t*, int reg, uint8_t); - int length; // Active counter - int next_delta; // APU cycles until next state change - int sample; // Current sample + int gain; + int length; // Active counter + int delay; // Cycles until next state change + int sample; // Current sample + int output; // Last output (sample * gain) union { // Square/Triangle @@ -102,6 +104,7 @@ typedef struct nes_apu_Channel_t { struct { uint8_t interrupt; uint8_t mask; + uint8_t data; uint16_t addr; int period; }; @@ -119,6 +122,7 @@ typedef struct { int frame; int frame_delay; int frame_period; + int time; } nes_apu; typedef enum { diff --git a/src/nese.c b/src/nese.c index 80f1ca4..4195969 100644 --- a/src/nese.c +++ b/src/nese.c @@ -5,6 +5,7 @@ #include "nes.h" #include "render.h" #include "input.h" +#include "audio.h" #define audio_freq (44100U) @@ -60,6 +61,8 @@ extern nes_Renderer sdl_renderer; extern nes_Input_Reader sdl_input; +extern nes_Audio_Stream sdl_audio; + static nes sys = {0}; @@ -82,6 +85,11 @@ int main(int argc, char* argv[]) { status = nes_input_init(input); } + nes_Audio_Stream* audio = &sdl_audio; + if (status == 0) { + status = nes_audio_init(audio, audio_freq); + } + if (status == 0) { status = nes_init(&sys, audio_freq); } @@ -133,6 +141,11 @@ int main(int argc, char* argv[]) { // Update button states every rendered frame status = nes_input_update(input, &sys.input); + + if (status >= 0) { + // Update audio, too + status = nes_audio_fill(audio, &sys.apu); + } } } else if (result == ppu_Result_Halt) { status = -1;