diff --git a/Makefile b/Makefile index bcaf68f..aa7b8e9 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ MAPDIR = $(SRCDIR)/map NESE_SRC_SRCS = f6502.c f6502_opcodes.c NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c -NESE_SRC_SRCS += ppu.c +NESE_SRC_SRCS += ppu.c apu.c NESE_SRC_SRCS += $(OS)/port.c NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*.c)) diff --git a/src/apu.c b/src/apu.c new file mode 100644 index 0000000..9e47746 --- /dev/null +++ b/src/apu.c @@ -0,0 +1,91 @@ +#include "memory.h" + + +void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq) { + // TODO +} + +void nes_apu_hsync(nes_APU* apu, nes_Memory* core_mem) { + nes_APU_Memory* mem = &core_mem->apu; + + for (int i = 0; i < core_mem->apu.n_events; ++i) { + int reg = core_mem->apu.events[i].reg; + int val = core_mem->apu.events[i].val; + + switch (reg) { + // TODO: 0x00 - 0x13 + // TODO: 0x15 + case 0x17: // Frame Counter + apu->frame_reg = val & ( apu_Frame_Mode | + apu_Frame_Inhibit); + apu->frame = 0; + apu->frame_delay = 0; + + if (val & apu_Frame_Mode) { + // TODO + /* + nes_apu_clock_quarter_frame(apu); + nes_apu_clock_half_frame(apu); + */ + } + + if (val & apu_Frame_Inhibit) { + mem->status &= ~apu_Status_Frame_Int; + } + + break; + + } + } + + core_mem->apu.n_events = 0; + + // Frame advance + + apu->frame_delay += nes_apu_hsync_ticks; + + if (apu->frame_delay >= nes_apu_quarter_frame_ticks) { + apu->frame_delay -= nes_apu_quarter_frame_ticks; + + int end = 0; + int quarter_frame = 1; + int half_frame = 0; + + if (1 == apu->frame) { + half_frame = 1; + } + + if (apu->frame_reg & apu_Frame_Mode) { + if (3 == apu->frame) { + quarter_frame = 0; + } else if (4 <= apu->frame) { + half_frame = 1; + end = 1; + } + } else { + if (3 <= apu->frame) { + half_frame = 1; + end = 1; + } + } + + if (half_frame) { + // TODO + //nes_apu_clock_half_frame(apu); + } + + if (quarter_frame) { + // TODO + //nes_apu_clock_quarter_frame(apu); + } + + if (end) { + if (0 == apu->frame_reg) { + mem->status |= apu_Status_Frame_Int; + } + apu->frame = 0; + } else { + apu->frame++; + } + } +} diff --git a/src/apu.h b/src/apu.h new file mode 100644 index 0000000..65fdd96 --- /dev/null +++ b/src/apu.h @@ -0,0 +1,105 @@ +#ifndef NESE_APU_H_ +#define NESE_APU_H_ + +#include + + +#define nes_apu_hsync_ticks (4U) +#define nes_apu_quarter_frame_ticks (262U) + + +typedef struct { + uint8_t reg; + uint8_t val; +} nes_APU_Event; + +#define APU_MAX_EVENTS (64U) + +typedef enum __attribute__ ((__packed__)) { + 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 struct { + nes_APU_Event events[APU_MAX_EVENTS]; + uint8_t n_events; + nes_apu_Status status; +} nes_APU_Memory; + + +typedef enum __attribute__ ((__packed__)) { + 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_Envelope_Volume = 0b00001111, + apu_Envelope_Constant = 0b00010000, + apu_Envelope_Halt = 0b00100000, + apu_Envelope_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_Mode = 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; + +typedef enum { + apu_Channel_Reload = 0b00000001, + apu_Channel_Start = 0b00000010, +} nes_apu_Channel_Flag; + +typedef struct { + nes_apu_Frame frame_reg; + int frame; + int frame_delay; + + uint8_t reg[20]; + + // TODO: Channel data +} nes_APU; + +struct nes_Memory; + +void nes_apu_init(nes_APU* apu, nes_APU_Memory* mem, int freq); +void nes_apu_hsync(nes_APU*, struct nes_Memory*); + + +#endif // NESE_APU_H_ diff --git a/src/f6502.c b/src/f6502.c index aecc487..1c9538d 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -90,15 +90,21 @@ static inline uint8_t f6502_read(nes_Memory* mem, case 0x4000: switch (addr & 0x1FU) { + case 0x15: + return mem->apu.status; + case 0x16: case 0x17: - { nes_Gamepad* gamepad = &mem->input.gamepads[addr & 1]; return ((addr >> 8) & nes_controller_bus_mask) | - read_gamepad_bit(gamepad); - } break; + read_gamepad_bit( + &mem->input.gamepads[addr & 1] + ); default: - // TODO: APU Reg + if (mem->mapper.read_apu) { + return mem->mapper.read_apu(&mem->mapper, mem, + addr & 0x1FU); + } } break; @@ -246,8 +252,40 @@ static inline int f6502_write(nes_Memory* mem, break; case 0x4000: - // TODO: APU switch (addr & 0x1FU) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x15: + case 0x17: + if (mem->apu.n_events >= APU_MAX_EVENTS) { + LOGE("APU event buffer full"); + } else { + mem->apu.events[mem->apu.n_events++] = + (nes_APU_Event){ + .reg = addr & 0x1FU, + .val = val, + }; + } + break; + case 0x14: // OAM DMA { uint8_t* src = NULL; // OAM DMA @@ -285,6 +323,12 @@ static inline int f6502_write(nes_Memory* mem, mem->input.gamepads[1].shift = 0; } break; + + default: + if (mem->mapper.write_apu) { + ret = mem->mapper.write_apu(&mem->mapper, mem, + addr, val); + } } break; diff --git a/src/linux/port.c b/src/linux/port.c index 98b7139..c2a48f9 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -15,7 +15,8 @@ #include "log.h" -/* OS-specific file operations +/* + * OS-specific file operations * Memory mapping specifically needs to be ported for each OS */ @@ -42,7 +43,8 @@ static int nese_file_size(FILE* file) { } -/* Platform-specific allocation +/* + * Platform-specific allocation * GPU and CPU refer to emulator memory spaces * Note: GPU (and possibly CPU) regions may need * to be placed into internal ram for performance @@ -61,7 +63,8 @@ void* nese_alloc(int size) { } -/* SDL-specific init and entry point +/* + * SDL-specific init and entry point * This should be maximally reusable across platforms */ @@ -84,6 +87,14 @@ typedef struct { } platform_data; +/* Input */ + +int nese_update_input(void* plat_data, nes_Input* input) { + // Gamepad states are already updated in nese_frame_ready() + return 0; +} + + static const int sdl_alt_start_key = SDLK_RETURN; static const int sdl_keycodes[nes_controller_num_buttons] = { @@ -138,6 +149,9 @@ static int process_events(nes* sys) { return status; } + +/* Time / Video */ + static SDL_Color nes_palette[64] = { {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6}, {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00}, @@ -278,11 +292,18 @@ int nese_frame_ready(void* plat_data) { return status; } -int nese_update_input(void* plat_data, nes_Input* input) { - // Gamepad states are already updated in nese_frame_ready() - return 0; + +/* Audio */ + +int nese_get_audio_frequency(void*) { + return 44100; } +// TODO: Audio functions + + +/* Platform Data */ + static int plat_init(platform_data* plat) { int status = SDL_Init( SDL_INIT_EVENTS | diff --git a/src/memory.h b/src/memory.h index b23bb9e..c905191 100644 --- a/src/memory.h +++ b/src/memory.h @@ -3,6 +3,7 @@ #include +#include "apu.h" #include "input.h" #include "mapper.h" #include "ppu.h" @@ -33,6 +34,7 @@ struct nes_Memory { nes_PPU_Memory ppu; nes_Mapper mapper; nes_Input input; + nes_APU_Memory apu; #endif }; typedef struct nes_Memory nes_Memory; diff --git a/src/nes.c b/src/nes.c index 2053452..153b35e 100644 --- a/src/nes.c +++ b/src/nes.c @@ -7,10 +7,11 @@ #include "log.h" -void nes_init(nes* sys) { +void nes_init(nes* sys, void* plat) { f6502_init(&sys->core); nes_ppu_init(&sys->ppu, &sys->core.memory.ppu); - // TODO: Init APU + nes_apu_init(&sys->apu, &sys->core.memory.apu, + nese_get_audio_frequency(plat)); } void nes_reset(nes* sys) { @@ -47,7 +48,7 @@ static int nes_vsync(nes* sys, void* plat) { static int nes_hsync(nes* sys, void* plat) { int status = 0; - // TODO: APU sync + nes_apu_hsync(&sys->apu, &sys->core.memory); if (sys->ppu.scanline < nes_ppu_postrender_line) { if (sys->ppu.scanline < nes_ppu_visible_line) { diff --git a/src/nes.h b/src/nes.h index 17bd9c2..e4114d9 100644 --- a/src/nes.h +++ b/src/nes.h @@ -11,10 +11,10 @@ typedef struct { const ines_Header* cart_header; f6502_Core core; nes_PPU ppu; - // TODO: APU + nes_APU apu; } nes; -void nes_init(nes*); +void nes_init(nes*, void*); void nes_reset(nes*); void nes_done(nes*); int nes_loop(nes*, void*); diff --git a/src/nese.c b/src/nese.c index ff4d2e3..fa45112 100644 --- a/src/nese.c +++ b/src/nese.c @@ -6,7 +6,7 @@ int nese_start(nes* sys, void* plat) { - nes_init(sys); + nes_init(sys, plat); nes_reset(sys); diff --git a/src/port.h b/src/port.h index 4d91896..122ef28 100644 --- a/src/port.h +++ b/src/port.h @@ -10,6 +10,7 @@ int nese_frame_start(void*, uint8_t background); int nese_line_ready(void*, uint8_t* buffer, int line); int nese_frame_ready(void*); int nese_update_input(void*, nes_Input*); +int nese_get_audio_frequency(void*); void* nese_alloc_gpu(int); void* nese_alloc_cpu(int);