From the "Updating the period" section on the
APU Sweep document, the updates that should happen at the half frame clock tick go like this:
When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen:
- If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is set to the target period.
- If the divider's counter is zero or the reload flag is true: The divider counter is set to P and the reload flag is cleared. Otherwise, the divider counter is decremented.
The "reload flag" described here is the same as the "reset flag" from blargg's notes. This flag is set when registers $4001 or $4005 (for pulse channel 1 and pulse channel 2, respectively) are written into. I see that the previous code snippet I wrote did not include anything about the "reset flag." Your new code snippet calls this psweep->reset, so using the same variable names, I think the logic would need to go like this:
Code: Select all
void ClockSweep(PSWEEP psweep, PTIMER ptimer, int add)
{
ptimer->result = ptimer->timer_reload >> psweep->shift;
if (psweep->negate)
ptimer->result = ptimer->timer_reload - ptimer->result + add;
else
ptimer->result = ptimer->timer_reload + ptimer->result;
if (ptimer->timer_reload < 8 || ptimer->result > 0x7FF)
psweep->dac = 0;
else
psweep->dac = 1;
}
void ClockSweepHalfFrame(PSWEEP psweep, PTIMER)
{
if (psweep->enable && psweep->shift && psweep->dac)
ptimer->timer_reload = ptimer->result;
if (psweep->period == 0 || psweep->reset) {
psweep->period = psweep->reload;
psweep->reset = 0;
} else {
psweep->period--;
}
}
void ClockPulseChannel(/* other... */, PFRAME_COUNTER pframe_counter, PSWEEP psweep, PTIMER ptimer, int pulse_add)
{
// ... Update some pulse channel stuff
if (pframe_counter->half_tick) {
ClockSweepHalfFrame(psweep, ptimer);
}
ClockSweep(psweep, ptimer, pulse_add);
// ... Update other pulse channel stuff
}
Here, I've tried to clarify that the updates need to happen at different times by splitting the logic into two functions. ClockSweep should be called on every APU tick so that it has a chance to mute the pulse channel any time that ptimer->timer_reload might have been changed. ClockSweepHalfFrame should be called only on APU ticks when the frame counter's half frame tick has occurred.
I've also shown how I'd expect these would be called from a function called ClockPulseChannel that updates the pulse channel. I'm assuming the existence of a frame counter object that should be updated before all of the channel clock update functions are called. Here, the pframe_counter->half_tick would be true when a half frame tick just occurred. Note that ClockSweepHalfFrame is called first, because it can update ptimer->timer_reload. Afterwards, ClockSweep runs so that it has a chance to mute the pulse channel.
Obviously, your pulse clock update function might be named differently, take different arguments, handle the frame counter differently, etc., but I believe this now captures the operations in the sweep unit update (hopefully not missing other details!).