Browse Source

Start implementing APU

master
Nathaniel Walizer 11 months ago
parent
commit
dcae99a332
6 changed files with 418 additions and 12 deletions
  1. +5
    -3
      Makefile
  2. +259
    -0
      src/apu.c
  3. +133
    -5
      src/apu.h
  4. +16
    -2
      src/nes.c
  5. +2
    -1
      src/nes.h
  6. +3
    -1
      src/nese.c

+ 5
- 3
Makefile View File

@@ -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)/%)


+ 259
- 0
src/apu.c View File

@@ -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_

+ 133
- 5
src/apu.h View File

@@ -2,17 +2,145 @@
#define ENES_APU_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;

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_

+ 16
- 2
src/nes.c View File

@@ -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);



+ 2
- 1
src/nes.h View File

@@ -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*);



+ 3
- 1
src/nese.c View File

@@ -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) {


Loading…
Cancel
Save