Browse Source

Add SDL audio; complete DMC channel

master
Nathaniel Walizer 11 months ago
parent
commit
d217db42a4
4 changed files with 118 additions and 16 deletions
  1. +1
    -1
      Makefile
  2. +97
    -12
      src/apu.c
  3. +7
    -3
      src/apu.h
  4. +13
    -0
      src/nese.c

+ 1
- 1
Makefile View File

@@ -23,8 +23,8 @@ LDLIBS_1 = -lSDL2
SRC_SRCS_1 = nese.c ines.c 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 += apu.c SRC_SRCS_1 += apu.c
SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c


MAPDIR = src/map MAPDIR = src/map
MAP_SRCS_1 = nrom.c mmc1.c uxrom.c cnrom.c mmc3.c MAP_SRCS_1 = nrom.c mmc1.c uxrom.c cnrom.c mmc3.c


+ 97
- 12
src/apu.c View File

@@ -1,9 +1,17 @@
#ifndef APU_H_ #ifndef APU_H_
#define APU_H_ #define APU_H_


#include "nes.h"
#include "apu.h" #include "apu.h"
#include "blip-buf/blip_buf.h" #include "blip-buf/blip_buf.h"



#define apu_gain_square (563U)
#define apu_gain_triangle (723U)
#define apu_gain_noise (482U)
#define apu_gain_dmc (240U)


/* /*
static const uint8_t length_lut [32] = { static const uint8_t length_lut [32] = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
@@ -18,6 +26,30 @@ static const uint16_t dmc_period_lut [16] = {
190, 160, 142, 128, 106, 84, 72, 54, 190, 160, 142, 128, 106, 84, 72, 54,
}; };


static const void dmc_restart(nes_apu_Channel* channel) {
channel->length = (channel->reg[3] * 16) + 1;
channel->addr = 0x4000U | ((uint16_t)channel->reg[2] << 6);
}

static const void nes_apu_dmc_fetch(nes_apu* apu,
nes_apu_Channel* channel) {
channel->data = apu->mem_read(apu->arg_mem,
0x8000U | channel->addr);
channel->mask = 1;
channel->addr = (channel->addr + 1) & 0x7FFFU;
channel->length--;
if ( channel->length <= 0 &&
(channel->reg[0] & apu_DMC_Loop)) {
dmc_restart(channel);
}
}

static const void nes_apu_dmc_start(nes_apu* apu,
nes_apu_Channel* channel) {
dmc_restart(channel);
nes_apu_dmc_fetch(apu, channel);
}

static void nes_apu_write_square(nes_apu_Channel* channel, static void nes_apu_write_square(nes_apu_Channel* channel,
int reg, uint8_t val) { int reg, uint8_t val) {
APU_LOG("APU: Square %d < %02x\n", reg, val); APU_LOG("APU: Square %d < %02x\n", reg, val);
@@ -41,16 +73,14 @@ static void nes_apu_write_dmc(nes_apu_Channel* channel,
APU_LOG("APU: DMC %d < %02x\n", reg, val); APU_LOG("APU: DMC %d < %02x\n", reg, val);


if (reg == 0) { if (reg == 0) {
channel->period = dmc_period_lut[val & 0xFU];
channel->period = dmc_period_lut[val & 0xFU] *
nes_clock_cpu_div;
if (!(val & apu_DMC_IRQ_Enable)) { if (!(val & apu_DMC_IRQ_Enable)) {
channel->interrupt = 0; channel->interrupt = 0;
} }


} else if (reg == 1) { } else if (reg == 1) {
channel->sample = (val & 0x7FU); channel->sample = (val & 0x7FU);

} else if (reg == 3) {
channel->length = val;
} }
} }


@@ -60,7 +90,7 @@ int nes_apu_init(nes_apu* apu, int clock, int frequency,
int ret = 0; int ret = 0;


// 20 ms buffer // 20 ms buffer
apu->blip = blip_new(frequency / 50);
apu->blip = blip_new(frequency / 20);


if (NULL == apu->blip) { if (NULL == apu->blip) {
APU_ERR("APU: Failed to create resampler\n"); APU_ERR("APU: Failed to create resampler\n");
@@ -80,6 +110,12 @@ int nes_apu_init(nes_apu* apu, int clock, int frequency,
apu->channels[apu_Channel_Noise].write = nes_apu_write_noise; apu->channels[apu_Channel_Noise].write = nes_apu_write_noise;
apu->channels[apu_Channel_DMC].write = nes_apu_write_dmc; apu->channels[apu_Channel_DMC].write = nes_apu_write_dmc;


apu->channels[apu_Channel_Square_0].gain = apu_gain_square;
apu->channels[apu_Channel_Square_1].gain = apu_gain_square;
apu->channels[apu_Channel_Triangle].gain = apu_gain_triangle;
apu->channels[apu_Channel_Noise].gain = apu_gain_noise;
apu->channels[apu_Channel_DMC].gain = apu_gain_dmc;

apu->frame_lfsr = 0x7FFFU; apu->frame_lfsr = 0x7FFFU;
apu->frame_period = clock / 240; apu->frame_period = clock / 240;
} }
@@ -142,15 +178,15 @@ void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) {
APU_LOG("APU: Enable %02x\n", val); APU_LOG("APU: Enable %02x\n", val);


for (int chan = 0; chan < nes_apu_chan_count; ++chan) { for (int chan = 0; chan < nes_apu_chan_count; ++chan) {
if (!((val >> chan) & 1)) {
if (!(val & (1 << chan))) {
apu->channels[chan].length = 0; apu->channels[chan].length = 0;
} }
val >>= 1;
} }


if ( (val & (1 << apu_Channel_DMC)) && if ( (val & (1 << apu_Channel_DMC)) &&
!(apu->status & (1 << apu_Channel_DMC))) { !(apu->status & (1 << apu_Channel_DMC))) {
// TODO: Start DMC
nes_apu_dmc_start(apu,
&apu->channels[apu_Channel_DMC]);
} }
if (!(val & (1 << apu_Channel_DMC))) { if (!(val & (1 << apu_Channel_DMC))) {
apu->channels[apu_Channel_DMC].interrupt = 0; apu->channels[apu_Channel_DMC].interrupt = 0;
@@ -174,11 +210,59 @@ void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) {
} }
} }


static int channel_update(nes_apu_Channel* channel) {
int output = (channel->gain * channel->sample);
int delta = output - channel->output;
channel->output = output;
return delta;
}

static void nes_apu_run_dmc(nes_apu* apu,
nes_apu_Channel* channel,
int cycles) {
if (channel->length <= 0) return;

int time = apu->time;
int delta = channel_update(channel);
if (delta) {
blip_add_delta(apu->blip, time, delta);
}

while (cycles > 0) {
int run = cycles;
if (run > channel->delay) {
run = channel->delay;
}

channel->delay -= run;
time += run;

if (channel->delay <= 0) {
channel->delay = channel->period;

delta = (channel->mask & channel->data) ? 2 : -2;
int sample = channel->sample + delta;
if ((unsigned)sample <= 0x7FU) {
channel->sample = sample;
delta = channel_update(channel);
if (delta) {
blip_add_delta(apu->blip, time, delta);
}
}

channel->mask <<= 1;
if (!channel->mask) {
nes_apu_dmc_fetch(apu, channel);
}
}

cycles -= run;
}
}

nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) {
/*
nes_apu_run_dmc(&apu->channels[apu_Channel_DMC],
apu->blip, cycles);
*/
nes_apu_run_dmc(apu, &apu->channels[apu_Channel_DMC], cycles);

while (cycles > 0) { while (cycles > 0) {
int run = cycles; int run = cycles;
if (run > apu->frame_delay) { if (run > apu->frame_delay) {
@@ -249,6 +333,7 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) {
} }


cycles -= run; cycles -= run;
apu->time += run;
} }


return ( (apu->status & apu_Status_Frame_Int) || return ( (apu->status & apu_Status_Frame_Int) ||


+ 7
- 3
src/apu.h View File

@@ -87,9 +87,11 @@ static inline int nes_apu_osc_length(uint8_t reg[4]) {
typedef struct nes_apu_Channel_t { typedef struct nes_apu_Channel_t {
uint8_t reg[4]; uint8_t reg[4];
void(*write)(struct nes_apu_Channel_t*, int reg, uint8_t); 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
int gain;
int length; // Active counter
int delay; // Cycles until next state change
int sample; // Current sample
int output; // Last output (sample * gain)


union { union {
// Square/Triangle // Square/Triangle
@@ -102,6 +104,7 @@ typedef struct nes_apu_Channel_t {
struct { struct {
uint8_t interrupt; uint8_t interrupt;
uint8_t mask; uint8_t mask;
uint8_t data;
uint16_t addr; uint16_t addr;
int period; int period;
}; };
@@ -119,6 +122,7 @@ typedef struct {
int frame; int frame;
int frame_delay; int frame_delay;
int frame_period; int frame_period;
int time;
} nes_apu; } nes_apu;


typedef enum { typedef enum {


+ 13
- 0
src/nese.c View File

@@ -5,6 +5,7 @@
#include "nes.h" #include "nes.h"
#include "render.h" #include "render.h"
#include "input.h" #include "input.h"
#include "audio.h"




#define audio_freq (44100U) #define audio_freq (44100U)
@@ -60,6 +61,8 @@ extern nes_Renderer sdl_renderer;


extern nes_Input_Reader sdl_input; extern nes_Input_Reader sdl_input;


extern nes_Audio_Stream sdl_audio;



static nes sys = {0}; static nes sys = {0};


@@ -82,6 +85,11 @@ int main(int argc, char* argv[]) {
status = nes_input_init(input); status = nes_input_init(input);
} }


nes_Audio_Stream* audio = &sdl_audio;
if (status == 0) {
status = nes_audio_init(audio, audio_freq);
}

if (status == 0) { if (status == 0) {
status = nes_init(&sys, audio_freq); status = nes_init(&sys, audio_freq);
} }
@@ -133,6 +141,11 @@ int main(int argc, char* argv[]) {


// Update button states every rendered frame // Update button states every rendered frame
status = nes_input_update(input, &sys.input); status = nes_input_update(input, &sys.input);

if (status >= 0) {
// Update audio, too
status = nes_audio_fill(audio, &sys.apu);
}
} }
} else if (result == ppu_Result_Halt) { } else if (result == ppu_Result_Halt) {
status = -1; status = -1;


Loading…
Cancel
Save