|
|
|
@@ -1,9 +1,17 @@ |
|
|
|
#ifndef APU_H_ |
|
|
|
#define APU_H_ |
|
|
|
|
|
|
|
#include "nes.h" |
|
|
|
#include "apu.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] = { |
|
|
|
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, |
|
|
|
}; |
|
|
|
|
|
|
|
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, |
|
|
|
int reg, uint8_t 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); |
|
|
|
|
|
|
|
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)) { |
|
|
|
channel->interrupt = 0; |
|
|
|
} |
|
|
|
|
|
|
|
} else if (reg == 1) { |
|
|
|
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; |
|
|
|
|
|
|
|
// 20 ms buffer |
|
|
|
apu->blip = blip_new(frequency / 50); |
|
|
|
apu->blip = blip_new(frequency / 20); |
|
|
|
|
|
|
|
if (NULL == apu->blip) { |
|
|
|
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_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_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); |
|
|
|
|
|
|
|
for (int chan = 0; chan < nes_apu_chan_count; ++chan) { |
|
|
|
if (!((val >> chan) & 1)) { |
|
|
|
if (!(val & (1 << chan))) { |
|
|
|
apu->channels[chan].length = 0; |
|
|
|
} |
|
|
|
val >>= 1; |
|
|
|
} |
|
|
|
|
|
|
|
if ( (val & (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))) { |
|
|
|
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_run_dmc(&apu->channels[apu_Channel_DMC], |
|
|
|
apu->blip, cycles); |
|
|
|
*/ |
|
|
|
nes_apu_run_dmc(apu, &apu->channels[apu_Channel_DMC], cycles); |
|
|
|
|
|
|
|
while (cycles > 0) { |
|
|
|
int run = cycles; |
|
|
|
if (run > apu->frame_delay) { |
|
|
|
@@ -249,6 +333,7 @@ nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { |
|
|
|
} |
|
|
|
|
|
|
|
cycles -= run; |
|
|
|
apu->time += run; |
|
|
|
} |
|
|
|
|
|
|
|
return ( (apu->status & apu_Status_Frame_Int) || |
|
|
|
|