| @@ -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)) | |||
| @@ -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++; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,105 @@ | |||
| #ifndef NESE_APU_H_ | |||
| #define NESE_APU_H_ | |||
| #include <stdint.h> | |||
| #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_ | |||
| @@ -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; | |||
| @@ -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 | | |||
| @@ -3,6 +3,7 @@ | |||
| #include <stdint.h> | |||
| #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; | |||
| @@ -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) { | |||
| @@ -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*); | |||
| @@ -6,7 +6,7 @@ | |||
| int nese_start(nes* sys, void* plat) { | |||
| nes_init(sys); | |||
| nes_init(sys, plat); | |||
| nes_reset(sys); | |||
| @@ -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); | |||