#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 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 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, }; 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) { if (channel->reg[0] & apu_DMC_Loop) { dmc_restart(channel); } else if (channel->reg[0] & apu_DMC_IRQ_Enable) { channel->interrupt = 1; } } } static const void nes_apu_dmc_start(nes_apu* apu, nes_apu_Channel* channel) { dmc_restart(channel); nes_apu_dmc_fetch(apu, channel); } static inline int apu_channel_raw_timer_value( nes_apu_Channel *channel) { return ( channel->reg[2] | (((uint16_t)channel->reg[3] & 0x7U) << 8)); } static inline void apu_channel_update_timer( nes_apu_Channel *channel) { channel->period = apu_channel_raw_timer_value(channel) * nes_clock_cpu_div; } static void nes_apu_write_square(nes_apu_Channel* channel, int reg, uint8_t val) { APU_LOG("APU: Square %d < %02x\n", reg, val); if (0 == reg) { channel->env_period = (val & apu_Envelope_Volume); } else if (1 == reg) { channel->sweep_period = ( ((val & apu_Square_Period) >> 4) ); channel->flags |= apu_Channel_Reload; } else if (2 <= reg) { channel->reg[reg] = val; channel->period = ( (apu_channel_raw_timer_value(channel) /*+ 1*/) * 2 ) * nes_clock_cpu_div; if (3 == reg) { // channel->flags |= apu_Channel_Reload; channel->delay = 0; // Envelope and step are already restarted } } } static void nes_apu_write_triangle(nes_apu_Channel* channel, int reg, uint8_t val) { APU_LOG("APU: Triangle %d < %02x\n", reg, val); if (2 <= reg) { channel->reg[reg] = val; apu_channel_update_timer(channel); if (3 == reg) channel->flags |= apu_Channel_Reload; } } static void nes_apu_write_noise(nes_apu_Channel* channel, int reg, uint8_t val) { APU_LOG("APU: Noise %d < %02x\n", reg, val); if (reg == 0) { channel->env_period = (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, int reg, uint8_t val) { APU_LOG("APU: DMC %d < %02x\n", reg, val); if (reg == 0) { channel->period = dmc_period_lut[val & apu_DMC_Period] * nes_clock_cpu_div; if (!(val & apu_DMC_IRQ_Enable)) { channel->interrupt = 0; } } else if (reg == 1) { channel->sample = (val & 0x7FU); } } 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->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->channels[apu_Channel_Noise].lfsr = 0x7FFFU; apu->frame_period = (clock / 240) + (nes_clock_cpu_div / 2); apu->frame_delay = apu->frame_period; } 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].step = 31; apu->channels[apu_Channel_DMC].reg[1] &= 1; apu->channels[apu_Channel_DMC].sample = 0; apu->channels[apu_Channel_DMC].mask = 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 @ %d\n", val, apu->frame_time_elapsed); } return val; } 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--; APU_LOG("APU: Clock Length %p -> %d\n", channel, channel->length); } } static inline void nes_apu_clock_sweep(nes_apu_Channel* channel, int adjust) { int decrement = 1; if (channel->flags & apu_Channel_Reload) { channel->flags &= ~apu_Channel_Reload; decrement = 0; } if (channel->sweep_delay == 0) { decrement = 0; if (channel->period < 8 * nes_clock_cpu_div) { channel->period = 0; channel->length = 0; } else if ( (channel->reg[1] & apu_Square_Enable) && 0 != (channel->reg[1] & apu_Square_Shift)) { int delta = ( apu_channel_raw_timer_value(channel) >> (channel->reg[1] & apu_Square_Shift) ); if (channel->reg[1] & apu_Square_Negate) { delta = adjust - delta; } channel->period += delta * nes_clock_apu_div; if ( channel < 0 || channel->period > 0x7FFU * nes_clock_cpu_div) { channel->period = 0; channel->length = 0; } } } if (decrement) { channel->sweep_delay--; } else { channel->sweep_delay = channel->sweep_period; } } static inline void nes_apu_clock_envelope(nes_apu_Channel* channel) { if (channel->flags & apu_Channel_Start) { channel->flags &= ~apu_Channel_Start; channel->envelope = 15; channel->env_delay = channel->env_period; } else 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--; } } static inline void nes_apu_clock_linear(nes_apu_Channel* channel) { if (channel->flags & apu_Channel_Reload) { channel->counter = (channel->reg[0] & apu_Triangle_Count); } else if (channel->counter > 0) { channel->counter--; } if (!(channel->reg[0] & apu_Triangle_Halt)) { channel->flags &= ~apu_Channel_Reload; } } static void nes_apu_clock_quarter_frame(nes_apu* apu) { nes_apu_clock_envelope(&apu->channels[apu_Channel_Square_0]); nes_apu_clock_envelope(&apu->channels[apu_Channel_Square_1]); nes_apu_clock_envelope(&apu->channels[apu_Channel_Noise]); nes_apu_clock_linear(&apu->channels[apu_Channel_Triangle]); } static void nes_apu_clock_half_frame(nes_apu* apu) { 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); } 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]; if (3 == reg) { if ( chan != apu_Channel_DMC && (apu->status & (1 << chan))) { channel->length = apu_length_lut[val >> 3]; channel->step = (uint8_t)-1; channel->flags |= apu_Channel_Start; } } channel->write(channel, reg, val); channel->reg[reg] = val; } else if (addr == nes_apu_reg_status) { APU_LOG("APU: Enable %02x\n", val); // Clear this now in case nes_apu_dmc_start resets it apu->channels[apu_Channel_DMC].interrupt = 0; for (int chan = 0; chan < nes_apu_chan_count; ++chan) { if (!(val & (1 << chan))) { apu->channels[chan].length = 0; } } if (!(val & (1 << apu_Channel_DMC))) { apu->channels[apu_Channel_DMC].mask = 0; } else if ( (val & (1 << apu_Channel_DMC)) && apu->channels[apu_Channel_DMC].length <= 0) { nes_apu_dmc_start(apu, &apu->channels[apu_Channel_DMC]); } 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_time_elapsed = 0; apu->frame_delay = apu->frame_period; if (val & apu_Frame_Mode) { nes_apu_clock_quarter_frame(apu); nes_apu_clock_half_frame(apu); } if (val & apu_Frame_Inhibit) { apu->status &= ~apu_Status_Frame_Int; } } } 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 || channel->period <= 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; } } 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 uint8_t triangle_steps[32] = { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, }; static void nes_apu_run_triangle(nes_apu* apu, nes_apu_Channel* channel, int cycles) { if ( channel->length <= 0 || channel->period <= 0 || channel->counter <= 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; channel->step = ((channel->step + 1) & 31U); channel->sample = triangle_steps[channel->step]; int delta = channel_update(channel); if (delta) { blip_add_delta(apu->blip, time, delta); } } cycles -= run; } } static const uint8_t square_sequence[4] = { 0b00000010, 0b00000110, 0b00011110, 0b11111001, }; static void nes_apu_run_square(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; channel->step = ((channel->step + 1) & 7U); channel->sample = (( square_sequence[channel->reg[0] >> 6] & (1 << channel->step) ) ? apu_envelope_volume(channel) : 0); int delta = channel_update(channel); if (delta) { blip_add_delta(apu->blip, time, delta); } } cycles -= run; } } nes_apu_Result nes_apu_run(nes_apu* apu, int cycles) { nes_apu_run_dmc(apu, &apu->channels[apu_Channel_DMC], cycles); while (cycles > 0) { int run = cycles; if (run > apu->frame_delay) { run = apu->frame_delay; } apu->frame_time_elapsed += run; nes_apu_run_square( apu, &apu->channels[apu_Channel_Square_0], run ); nes_apu_run_square( apu, &apu->channels[apu_Channel_Square_1], run ); nes_apu_run_triangle( apu, &apu->channels[apu_Channel_Triangle], run ); nes_apu_run_noise( apu, &apu->channels[apu_Channel_Noise], run ); apu->frame_delay -= run; if (apu->frame_delay <= 0) { APU_LOG("APU: End of quarter frame: %d\n", apu->frame_time_elapsed); int end = 0; int quarter_frame = 1; int half_frame = 0; apu->frame_delay += apu->frame_period; if (1 == apu->frame) { half_frame = 1; } if (apu->frame_reg & apu_Frame_Mode) { if (3 == apu->frame) { quarter_frame = 0; } else if (4 <= apu->frame) { half_frame = 1; end = 1; } } else { if (3 <= apu->frame) { half_frame = 1; end = 1; } } if (half_frame) { nes_apu_clock_half_frame(apu); } if (quarter_frame) { nes_apu_clock_quarter_frame(apu); } if (end) { if (0 == apu->frame_reg) { apu->status |= apu_Status_Frame_Int; APU_LOG("APU: Frame Interrupt @ %d\n", apu->frame_time_elapsed); } apu->frame = 0; apu->frame_time_elapsed = 0; } else { apu->frame++; } } cycles -= run; apu->time += run; } return ( (apu->status & apu_Status_Frame_Int) || apu->channels[apu_Channel_DMC].interrupt ) ? apu_Result_IRQ : apu_Result_Running; } #endif // APU_H_