diff --git a/Makefile b/Makefile index 840f356..c51c00f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ CC = gcc LD = $(CC) -#PFLAGS = -pg -PFLAGS += -O3 +PFLAGS = -g +#PFLAGS += -O3 #PFLAGS += -DDEBUG_MAPPER #PFLAGS += -DDEBUG_RENDER #PFLAGS += -DDEBUG_PPU -DDEBUG_VRAM -DDEBUG_OAM +PFLAGS += -DDEBUG_APU #PFLAGS += -DE6502_DEBUG CFLAGS = $(PFLAGS) -Wall -Werror -Wshadow -Wunused -I.. LDFLAGS = $(PFLAGS) @@ -23,11 +24,12 @@ 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 MAPDIR = src/map MAP_SRCS_1 = nrom.c mmc1.c cnrom.c mmc3.c -EXT_SRCS_1 = e6502/e6502.c e6502/opcodes.c +EXT_SRCS_1 = e6502/e6502.c e6502/opcodes.c blip-buf/blip_buf.c SRCS_1 = $(SRC_SRCS_1:%=$(SRCDIR)/%) diff --git a/src/apu.c b/src/apu.c new file mode 100644 index 0000000..bd34c17 --- /dev/null +++ b/src/apu.c @@ -0,0 +1,259 @@ +#ifndef APU_H_ +#define APU_H_ + +#include "apu.h" +#include "blip-buf/blip_buf.h" + +/* +static const uint8_t length_lut [32] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E, +}; +*/ + +static const uint16_t dmc_period_lut [16] = { + 428, 380, 340, 320, 286, 254, 226, 214, + 190, 160, 142, 128, 106, 84, 72, 54, +}; + +static void nes_apu_write_square(nes_apu_Channel* channel, + int reg, uint8_t val) { + APU_LOG("APU: Square %d < %02x\n", reg, val); + // TODO +} + +static void nes_apu_write_triangle(nes_apu_Channel* channel, + int reg, uint8_t val) { + APU_LOG("APU: Triangle %d < %02x\n", reg, val); + // TODO +} + +static void nes_apu_write_noise(nes_apu_Channel* channel, + int reg, uint8_t val) { + APU_LOG("APU: Noise %d < %02x\n", reg, val); + // TODO +} + +static void nes_apu_write_dmc(nes_apu_Channel* channel, + int reg, uint8_t val) { + APU_LOG("APU: DMC %d < %02x\n", reg, val); + + if (reg == 0) { + channel->period = dmc_period_lut[val & 0xFU]; + if (!(val & apu_DMC_IRQ_Enable)) { + channel->interrupt = 0; + } + + } else if (reg == 1) { + channel->sample = (val & 0x7FU); + + } else if (reg == 3) { + channel->length = val; + } +} + +int nes_apu_init(nes_apu* apu, int clock, int frequency, + uint8_t(*mem_read)(void*, uint16_t), + void* arg_mem) { + int ret = 0; + + // 20 ms buffer + apu->blip = blip_new(frequency / 50); + + if (NULL == apu->blip) { + APU_ERR("APU: Failed to create resampler\n"); + ret = -1; + + } else { + blip_set_rates(apu->blip, clock, frequency); + + nes_apu_reset(apu); + + apu->mem_read = mem_read; + apu->arg_mem = arg_mem; + + apu->channels[apu_Channel_Square_0].write = nes_apu_write_square; + apu->channels[apu_Channel_Square_1].write = nes_apu_write_square; + apu->channels[apu_Channel_Triangle].write = nes_apu_write_triangle; + apu->channels[apu_Channel_Noise].write = nes_apu_write_noise; + apu->channels[apu_Channel_DMC].write = nes_apu_write_dmc; + + apu->frame_lfsr = 0x7FFFU; + apu->frame_period = clock / 240; + } + + return ret; +} + +void nes_apu_done(nes_apu* apu) { + blip_delete(apu->blip); +} + +void nes_apu_reset(nes_apu* apu) { + apu->status = 0; + + for (int chan = 0; chan < nes_apu_chan_count; ++chan) { + apu->channels[chan].length = 0; + } + + apu->channels[apu_Channel_Triangle].sample = 15; + apu->channels[apu_Channel_DMC].reg[1] &= 1; + apu->channels[apu_Channel_DMC].sample = 0; +} + +uint8_t nes_apu_read(nes_apu* apu, uint16_t addr) { + uint8_t val = 0; + + if (addr == nes_apu_reg_status) { + for (int chan = 0; chan < nes_apu_chan_count; ++chan) { + if (apu->channels[chan].length > 0) { + val |= (1 << chan); + } + } + + if (apu->channels[apu_Channel_DMC].interrupt) { + val |= apu_Status_DMC_Int; + } + + if (apu->status & apu_Status_Frame_Int) { + val |= apu_Status_Frame_Int; + apu->status &= ~apu_Status_Frame_Int; + } + + APU_LOG("APU: Status %02x\n", val); + } + + return val; +} + +void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) { + if (addr < nes_apu_reg_base + + (nes_apu_chan_count * nes_apu_chan_size)) { + int chan = (addr - nes_apu_reg_base) / nes_apu_chan_size; + int reg = (addr - nes_apu_reg_base) % nes_apu_chan_size; + + nes_apu_Channel* channel = &apu->channels[chan]; + channel->write(channel, reg, val); + channel->reg[reg] = val; + + } else if (addr == nes_apu_reg_status) { + APU_LOG("APU: Enable %02x\n", val); + + for (int chan = 0; chan < nes_apu_chan_count; ++chan) { + if (!((val >> chan) & 1)) { + apu->channels[chan].length = 0; + } + val >>= 1; + } + + if ( (val & (1 << apu_Channel_DMC)) && + !(apu->status & (1 << apu_Channel_DMC))) { + // TODO: Start DMC + } + if (!(val & (1 << apu_Channel_DMC))) { + apu->channels[apu_Channel_DMC].interrupt = 0; + } + + apu->status &= ~((1 << nes_apu_chan_count) - 1); + apu->status |= (val & ((1 << nes_apu_chan_count) - 1)); + + } else if (addr == nes_apu_reg_frame) { + APU_LOG("APU: Frame %02x\n", val); + + apu->frame_reg = val & + (apu_Frame_Mode | apu_Frame_Inhibit); + + apu->frame = 0; + apu->frame_delay = 0; + + if (val & apu_Frame_Inhibit) { + apu->status &= ~apu_Status_Frame_Int; + } + } +} + +nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { +/* + nes_apu_run_dmc(&apu->channels[apu_Channel_DMC], + apu->blip, cycles); +*/ + while (cycles > 0) { + int run = cycles; + if (run > apu->frame_delay) { + run = apu->frame_delay; + } +/* + nes_apu_run_square(&apu->channels[apu_Channel_Square_0], + apu->blip, run); + nes_apu_run_square(&apu->channels[apu_Channel_Square_1], + apu->blip, run); + nes_apu_run_triangle(&apu->channels[apu_Channel_Triangle], + apu->blip, run); + nes_apu_run_noise(&apu->channels[apu_Channel_Noise], + apu->blip, run); +*/ + apu->frame_delay -= run; + + if (apu->frame_delay <= 0) { + int end = 0; + int clock = 1; + int half_clock = 0; + + apu->frame_delay += apu->frame_period; + + if (1 == apu->frame) { + half_clock = 1; + } else if (0 == apu->frame && 0 == apu->frame_reg) { + apu->status |= apu_Status_Frame_Int; + } + + if (apu->frame_reg & apu_Frame_Mode) { + if (3 == apu->frame) { + clock = 0; + } else if (4 == apu->frame) { + half_clock = 1; + end = 1; + } + } else { + if (3 == apu->frame) { + half_clock = 1; + end = 1; + } + } + + if (half_clock) { +/* + nes_apu_clock_length(&apu->channels[apu_Channel_Square_0], 32); + nes_apu_clock_length(&apu->channels[apu_Channel_Square_1], 32); + nes_apu_clock_length(&apu->channels[apu_Channel_Triangle], 128); + nes_apu_clock_length(&apu->channels[apu_Channel_Noise], 32); + + nes_apu_clock_sweep(&apu->channels[apu_Channel_Square_0], -1); + nes_apu_clock_sweep(&apu->channels[apu_Channel_Square_1], 0); +*/ + } + + if (clock) { +/* + nes_apu_clock_envelope(&apu->channels[apu_Channel_Square_0]); + nes_apu_clock_envelope(&apu->channels[apu_Channel_Square_1]); + nes_apu_clock_linear(&apu->channels[apu_Channel_Triangle]); + nes_apu_clock_envelope(&apu->channels[apu_Channel_Noise]); +*/ + } + + if (end) apu->frame = 0; + else apu->frame++; + } + + cycles -= run; + } + + return ( (apu->status & apu_Status_Frame_Int) || + apu->channels[apu_Channel_DMC].interrupt ) ? + apu_Result_IRQ : apu_Result_Running; +} + +#endif // APU_H_ diff --git a/src/apu.h b/src/apu.h index e1a5970..07c5f22 100644 --- a/src/apu.h +++ b/src/apu.h @@ -2,17 +2,145 @@ #define ENES_APU_H_ #include +#include -// TODO: Stubbed +#define DBG_LOG(...) printf(__VA_ARGS__) +#define ERR_LOG(...) DBG_LOG(__VA_ARGS__) -typedef struct { +#ifdef DEBUG_APU +#define APU_LOG DBG_LOG +#else +#define APU_LOG(...) +#endif +#define APU_ERR ERR_LOG + + +#define nes_apu_frame_period = 7450; + +typedef enum { + apu_Status_Square_0 = 0b00000001, + apu_Status_Square_1 = 0b00000010, + apu_Status_Triangle = 0b00000100, + apu_Status_Noise = 0b00001000, + apu_Status_DMC = 0b00010000, + apu_Status_Frame_Int = 0b01000000, + apu_Status_DMC_Int = 0b10000000, +} nes_apu_Status; + +typedef enum { + apu_Frame_Inhibit = 0b01000000, + apu_Frame_Mode = 0b10000000, +} nes_apu_Frame; + +typedef enum { + apu_Channel_Square_0 = 0, + apu_Channel_Square_1 = 1, + apu_Channel_Triangle = 2, + apu_Channel_Noise = 3, + apu_Channel_DMC = 4, +} nes_apu_Channel_Index; + +// Applies to Square and Noise +typedef enum { + apu_Square_Volume = 0b00001111, + apu_Square_Constant = 0b00010000, + apu_Square_Halt = 0b00100000, + apu_Square_Duty = 0b11000000, +} nes_apu_Envelope_Reg_0_Mask; + +typedef enum { + apu_Square_Shift = 0b00000111, + apu_Square_Negate = 0b00001000, + apu_Square_Period = 0b01110000, + apu_Square_Enable = 0b10000000, +} nes_apu_Square_Reg_1_Mask; + +typedef enum { + apu_Triangle_Count = 0b01111111, + apu_Triangle_Halt = 0b10000000, +} nes_apu_Triangle_Reg_0_Mask; + +typedef enum { + apu_Noise_Period = 0b00001111, + apu_Noise_Loop = 0b10000000, +} nes_apu_Noise_Reg_2_Mask; + +typedef enum { + apu_Noise_Length = 0b11111000, +} nes_apu_Noise_Reg_3_Mask; +typedef enum { + apu_DMC_Period = 0b00001111, + apu_DMC_Loop = 0b01000000, + apu_DMC_IRQ_Enable = 0b10000000, +} nes_apu_DMC_Reg_0_Mask; + +static inline int nes_apu_osc_timer(uint8_t reg[4]) { + return ((unsigned)reg[2] | (((unsigned)reg[3] & 7U) << 8)); +} + +static inline int nes_apu_osc_length(uint8_t reg[4]) { + return (reg[3] >> 3); +} + +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 + + union { + // Square/Triangle + struct { + int phase; + int sweep; + int envelope; + }; + // DMC + struct { + uint8_t interrupt; + uint8_t mask; + uint16_t addr; + int period; + }; + }; +} nes_apu_Channel; + +typedef struct { + nes_apu_Channel channels[5]; + uint8_t (*mem_read)(void*, uint16_t); + void* arg_mem; + struct blip_t* blip; + nes_apu_Status status; + uint8_t frame_reg; + uint16_t frame_lfsr; + int frame; + int frame_delay; + int frame_period; } nes_apu; -static inline uint8_t nes_apu_read(nes_apu*, uint16_t addr) { return 0; } -static inline void nes_apu_write(nes_apu*, uint16_t addr, uint8_t) {} -static inline void nes_apu_reset(nes_apu*) {} +typedef enum { + apu_Result_Running = 0U, + apu_Result_IRQ, +} nes_apu_Result; + +#define nes_apu_reg_base (0x4000U) +#define nes_apu_chan_count (5U) +#define nes_apu_chan_size (4U) +#define nes_apu_reg_status (0x4015U) +#define nes_apu_reg_frame (0x4017U) + +int nes_apu_init(nes_apu*, int clock, int frequency, + uint8_t(*)(void*, uint16_t), void*); +void nes_apu_done(nes_apu*); +void nes_apu_reset(nes_apu*); + +uint8_t nes_apu_read(nes_apu*, uint16_t addr); +void nes_apu_write(nes_apu*, uint16_t addr, uint8_t val); + +nes_apu_Result nes_apu_run(nes_apu*, int cycles); #endif // ENES_APU_H_ diff --git a/src/nes.c b/src/nes.c index 555717e..c39cda4 100644 --- a/src/nes.c +++ b/src/nes.c @@ -63,11 +63,18 @@ static void nes_irq(void* sys, int active) { e6502_set_irq(&((nes*)sys)->cpu, active); } -int nes_init(nes* sys) { +int nes_init(nes* sys, int audio_freq) { e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read, (e6502_Write*)nes_mem_write, sys); nes_map_set_irq(sys->cart.mapper, nes_irq, sys); - return nes_ppu_init(&sys->ppu, &sys->cart); + nes_ppu_init(&sys->ppu, &sys->cart); + return nes_apu_init( + &sys->apu, + nes_clock_master_num / nes_clock_master_den, + audio_freq, + (uint8_t(*)(void*, uint16_t))nes_mem_read, + sys + ); } void nes_reset(nes* sys) { @@ -83,6 +90,13 @@ nes_ppu_Result nes_step(nes* sys, int* run) { if (cpu_run > 0) { int master_cycles = cpu_run * nes_clock_cpu_div; + + nes_apu_Result aresult = nes_apu_run(&sys->apu, + master_cycles); + + // TODO: Does this conflict with MMC3? + e6502_set_irq(&sys->cpu, aresult == apu_Result_IRQ); + int ppu_cycles = master_cycles / nes_clock_ppu_div; result = nes_ppu_run(&sys->ppu, ppu_cycles); diff --git a/src/nes.h b/src/nes.h index 5f13176..0b16945 100644 --- a/src/nes.h +++ b/src/nes.h @@ -13,6 +13,7 @@ #define nes_clock_master_den (44U) #define nes_clock_cpu_div (12U) #define nes_clock_ppu_div (4U) +#define nes_clock_apu_div (24U) #define nes_chr_page_size (0x1000U) #define nes_vram_page_size (0x0400U) @@ -55,7 +56,7 @@ uint8_t nes_mem_read(nes*, uint16_t addr); void nes_mem_write(nes*, uint16_t addr, uint8_t); -int nes_init(nes*); +int nes_init(nes*, int audio_freq); void nes_reset(nes*); diff --git a/src/nese.c b/src/nese.c index 70f7b0d..80f1ca4 100644 --- a/src/nese.c +++ b/src/nese.c @@ -7,6 +7,8 @@ #include "input.h" +#define audio_freq (44100U) + /* void e6502_print_registers(const e6502_Registers* regs, FILE* file) { @@ -81,7 +83,7 @@ int main(int argc, char* argv[]) { } if (status == 0) { - status = nes_init(&sys); + status = nes_init(&sys, audio_freq); } if (status == 0) {