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