Browse Source

Complete APU noise channel

master
Nathaniel Walizer 11 months ago
parent
commit
c71634e669
2 changed files with 126 additions and 33 deletions
  1. +113
    -19
      src/apu.c
  2. +13
    -14
      src/apu.h

+ 113
- 19
src/apu.c View File

@@ -12,16 +12,20 @@
#define apu_gain_dmc (240U)


/*
static const uint8_t length_lut [32] = {
static const uint8_t apu_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] = {
static const uint16_t noise_period_lut[16] = {
4, 8, 16, 32, 64, 96, 128, 160,
202, 254, 380, 508, 762, 1016, 2034, 4068,
};

static const uint16_t dmc_period_lut[16] = {
428, 380, 340, 320, 286, 254, 226, 214,
190, 160, 142, 128, 106, 84, 72, 54,
};
@@ -38,9 +42,12 @@ static const void nes_apu_dmc_fetch(nes_apu* apu,
channel->mask = 1;
channel->addr = (channel->addr + 1) & 0x7FFFU;
channel->length--;
if ( channel->length <= 0 &&
(channel->reg[0] & apu_DMC_Loop)) {
dmc_restart(channel);
if (channel->length <= 0) {
if (channel->reg[0] & apu_DMC_Loop) {
dmc_restart(channel);
} else if (channel->reg[0] & apu_DMC_IRQ_Enable) {
channel->interrupt = 1;
}
}
}

@@ -65,7 +72,14 @@ static void nes_apu_write_triangle(nes_apu_Channel* channel,
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

if (reg == 0) {
channel->envelope = (val & apu_Envelope_Volume);

} else if (reg == 2) {
channel->period = noise_period_lut[val & apu_Noise_Period] *
nes_clock_cpu_div;
}
}

static void nes_apu_write_dmc(nes_apu_Channel* channel,
@@ -73,7 +87,7 @@ static void nes_apu_write_dmc(nes_apu_Channel* channel,
APU_LOG("APU: DMC %d < %02x\n", reg, val);

if (reg == 0) {
channel->period = dmc_period_lut[val & 0xFU] *
channel->period = dmc_period_lut[val & apu_DMC_Period] *
nes_clock_cpu_div;
if (!(val & apu_DMC_IRQ_Enable)) {
channel->interrupt = 0;
@@ -116,7 +130,8 @@ int nes_apu_init(nes_apu* apu, int clock, int frequency,
apu->channels[apu_Channel_Noise].gain = apu_gain_noise;
apu->channels[apu_Channel_DMC].gain = apu_gain_dmc;

apu->frame_lfsr = 0x7FFFU;
apu->channels[apu_Channel_Noise].lfsr = 0x7FFFU;

apu->frame_period = clock / 240;
}

@@ -171,6 +186,15 @@ void nes_apu_write(nes_apu* apu, uint16_t addr, uint8_t val) {
int reg = (addr - nes_apu_reg_base) % nes_apu_chan_size;

nes_apu_Channel* channel = &apu->channels[chan];
if (3 == reg) {
if ( chan != apu_Channel_DMC &&
apu->status & (1 << chan)) {
channel->length = apu_length_lut[val >> 3];
channel->envelope = 15;
channel->env_period = (channel->reg[0] & apu_Envelope_Volume);
channel->env_delay = channel->env_period;
}
}
channel->write(channel, reg, val);
channel->reg[reg] = val;

@@ -220,7 +244,7 @@ static int channel_update(nes_apu_Channel* channel) {
static void nes_apu_run_dmc(nes_apu* apu,
nes_apu_Channel* channel,
int cycles) {
if (channel->length <= 0) return;
if (channel->length <= 0 || channel->period <= 0) return;

int time = apu->time;
int delta = channel_update(channel);
@@ -260,6 +284,72 @@ static void nes_apu_run_dmc(nes_apu* apu,
}
}

static inline int apu_envelope_volume(nes_apu_Channel* channel) {
return ( (channel->reg[0] & apu_Envelope_Constant) ?
(channel->reg[0] & apu_Envelope_Volume) :
(channel->envelope) );
}

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

int time = apu->time;

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;

uint16_t feedback = (1 & (channel->lfsr ^ (
channel->lfsr >> (
(channel->reg[2] & apu_Noise_Mode) ?
6 : 1
)
)
));
channel->lfsr = (feedback << 14) |
(channel->lfsr >> 1);
channel->sample = (channel->lfsr & 1) ?
apu_envelope_volume(channel) : 0;
int delta = channel_update(channel);
if (delta) {
blip_add_delta(apu->blip, time, delta);
}
}

cycles -= run;
}
}

static inline void nes_apu_clock_length(nes_apu_Channel* channel,
uint8_t halt_mask) {
if ( channel->length > 0 &&
!(channel->reg[0] & halt_mask)) {
channel->length--;
}
}

static inline void nes_apu_clock_envelope(nes_apu_Channel* channel) {
if (channel->env_delay <= 0) {
channel->env_delay = channel->env_period;
if ( channel->envelope > 0 ||
(channel->reg[0] & apu_Envelope_Halt)) {
channel->envelope = ((channel->envelope - 1) & 0xFU);
}
} else {
channel->env_delay--;
}
}

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

@@ -275,9 +365,10 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) {
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);
*/
nes_apu_run_noise(apu, &apu->channels[apu_Channel_Noise],
run);

apu->frame_delay -= run;

if (apu->frame_delay <= 0) {
@@ -307,11 +398,12 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) {

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_length(&apu->channels[apu_Channel_Square_0], apu_Envelope_Halt);
nes_apu_clock_length(&apu->channels[apu_Channel_Square_1], apu_Envelope_Halt);
nes_apu_clock_length(&apu->channels[apu_Channel_Triangle], apu_Triangle_Halt);
*/
nes_apu_clock_length(&apu->channels[apu_Channel_Noise], apu_Envelope_Halt);
/*
nes_apu_clock_sweep(&apu->channels[apu_Channel_Square_0], -1);
nes_apu_clock_sweep(&apu->channels[apu_Channel_Square_1], 0);
*/
@@ -321,8 +413,10 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) {
/*
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]);
/*
nes_apu_clock_linear(&apu->channels[apu_Channel_Triangle]);
*/
}



+ 13
- 14
src/apu.h View File

@@ -43,10 +43,10 @@ typedef enum {

// Applies to Square and Noise
typedef enum {
apu_Square_Volume = 0b00001111,
apu_Square_Constant = 0b00010000,
apu_Square_Halt = 0b00100000,
apu_Square_Duty = 0b11000000,
apu_Envelope_Volume = 0b00001111,
apu_Envelope_Constant = 0b00010000,
apu_Envelope_Halt = 0b00100000,
apu_Envelope_Duty = 0b11000000,
} nes_apu_Envelope_Reg_0_Mask;

typedef enum {
@@ -63,7 +63,7 @@ typedef enum {

typedef enum {
apu_Noise_Period = 0b00001111,
apu_Noise_Loop = 0b10000000,
apu_Noise_Mode = 0b10000000,
} nes_apu_Noise_Reg_2_Mask;

typedef enum {
@@ -76,14 +76,10 @@ typedef enum {
apu_DMC_IRQ_Enable = 0b10000000,
} nes_apu_DMC_Reg_0_Mask;

static inline int nes_apu_osc_timer(uint8_t reg[4]) {
static inline int nes_apu_channel_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);
@@ -93,12 +89,17 @@ typedef struct nes_apu_Channel_t {
int sample; // Current sample
int output; // Last output (sample * gain)

uint16_t period; // Noise, DMC

union {
// Square/Triangle
// Square/Triangle/Noise
struct {
int phase;
int sweep;
int envelope;
uint8_t envelope;
uint8_t env_period;
uint8_t env_delay;
uint16_t lfsr;
};
// DMC
struct {
@@ -106,7 +107,6 @@ typedef struct nes_apu_Channel_t {
uint8_t mask;
uint8_t data;
uint16_t addr;
int period;
};
};
} nes_apu_Channel;
@@ -118,7 +118,6 @@ typedef struct {
struct blip_t* blip;
nes_apu_Status status;
uint8_t frame_reg;
uint16_t frame_lfsr;
int frame;
int frame_delay;
int frame_period;


Loading…
Cancel
Save