diff --git a/src/apu.c b/src/apu.c index 72f8c6f..d0691f3 100644 --- a/src/apu.c +++ b/src/apu.c @@ -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]); */ } diff --git a/src/apu.h b/src/apu.h index 10c612f..a970c8e 100644 --- a/src/apu.h +++ b/src/apu.h @@ -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;