| @@ -1,10 +1,11 @@ | |||||
| CC = gcc | CC = gcc | ||||
| LD = $(CC) | LD = $(CC) | ||||
| #PFLAGS = -pg | |||||
| PFLAGS += -O3 | |||||
| PFLAGS = -g | |||||
| #PFLAGS += -O3 | |||||
| #PFLAGS += -DDEBUG_MAPPER | #PFLAGS += -DDEBUG_MAPPER | ||||
| #PFLAGS += -DDEBUG_RENDER | #PFLAGS += -DDEBUG_RENDER | ||||
| #PFLAGS += -DDEBUG_PPU -DDEBUG_VRAM -DDEBUG_OAM | #PFLAGS += -DDEBUG_PPU -DDEBUG_VRAM -DDEBUG_OAM | ||||
| PFLAGS += -DDEBUG_APU | |||||
| #PFLAGS += -DE6502_DEBUG | #PFLAGS += -DE6502_DEBUG | ||||
| CFLAGS = $(PFLAGS) -Wall -Werror -Wshadow -Wunused -I.. | CFLAGS = $(PFLAGS) -Wall -Werror -Wshadow -Wunused -I.. | ||||
| LDFLAGS = $(PFLAGS) | 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 += nes.c ppu.c input.c | ||||
| SRC_SRCS_1 += cart.c mapper.c | SRC_SRCS_1 += cart.c mapper.c | ||||
| SRC_SRCS_1 += sdl_render.c sdl_input.c | SRC_SRCS_1 += sdl_render.c sdl_input.c | ||||
| SRC_SRCS_1 += apu.c | |||||
| MAPDIR = src/map | MAPDIR = src/map | ||||
| MAP_SRCS_1 = nrom.c mmc1.c cnrom.c mmc3.c | 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)/%) | SRCS_1 = $(SRC_SRCS_1:%=$(SRCDIR)/%) | ||||
| @@ -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_ | |||||
| @@ -2,17 +2,145 @@ | |||||
| #define ENES_APU_H_ | #define ENES_APU_H_ | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include <stdio.h> | |||||
| // 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; | } 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_ | #endif // ENES_APU_H_ | ||||
| @@ -63,11 +63,18 @@ static void nes_irq(void* sys, int active) { | |||||
| e6502_set_irq(&((nes*)sys)->cpu, 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_init(&sys->cpu, (e6502_Read*)nes_mem_read, | ||||
| (e6502_Write*)nes_mem_write, sys); | (e6502_Write*)nes_mem_write, sys); | ||||
| nes_map_set_irq(sys->cart.mapper, nes_irq, 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) { | void nes_reset(nes* sys) { | ||||
| @@ -83,6 +90,13 @@ nes_ppu_Result nes_step(nes* sys, int* run) { | |||||
| if (cpu_run > 0) { | if (cpu_run > 0) { | ||||
| int master_cycles = cpu_run * nes_clock_cpu_div; | 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; | int ppu_cycles = master_cycles / nes_clock_ppu_div; | ||||
| result = nes_ppu_run(&sys->ppu, ppu_cycles); | result = nes_ppu_run(&sys->ppu, ppu_cycles); | ||||
| @@ -13,6 +13,7 @@ | |||||
| #define nes_clock_master_den (44U) | #define nes_clock_master_den (44U) | ||||
| #define nes_clock_cpu_div (12U) | #define nes_clock_cpu_div (12U) | ||||
| #define nes_clock_ppu_div (4U) | #define nes_clock_ppu_div (4U) | ||||
| #define nes_clock_apu_div (24U) | |||||
| #define nes_chr_page_size (0x1000U) | #define nes_chr_page_size (0x1000U) | ||||
| #define nes_vram_page_size (0x0400U) | #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); | 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*); | void nes_reset(nes*); | ||||
| @@ -7,6 +7,8 @@ | |||||
| #include "input.h" | #include "input.h" | ||||
| #define audio_freq (44100U) | |||||
| /* | /* | ||||
| void e6502_print_registers(const e6502_Registers* regs, | void e6502_print_registers(const e6502_Registers* regs, | ||||
| FILE* file) { | FILE* file) { | ||||
| @@ -81,7 +83,7 @@ int main(int argc, char* argv[]) { | |||||
| } | } | ||||
| if (status == 0) { | if (status == 0) { | ||||
| status = nes_init(&sys); | |||||
| status = nes_init(&sys, audio_freq); | |||||
| } | } | ||||
| if (status == 0) { | if (status == 0) { | ||||