Apu Sweep Unit reset

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
User avatar
Anes
Posts: 702
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Apu Sweep Unit reset

Post by Anes »

Blarrg says:
When the sweep unit is clocked, the divider is *first* clocked and then if
there was a write to the sweep register since the last sweep clock, the divider
is reset.
Well here is my "clock sweep unit" code and i don't know how to implement that part:

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;
	if (psweep->period)
		psweep->period--;
	if (psweep->period == 0)
	{
		if (psweep->enable && psweep->shift && psweep->dac)
			ptimer->timer_reload = ptimer->result;
		psweep->period = psweep->reload;
	}
}
The "period" is clocked, but when do i have to "reset"?
ANes
notyourdad
Posts: 16
Joined: Sun Mar 12, 2023 5:49 am

Re: Apu Sweep Unit reset

Post by notyourdad »

I may not have enough context to fully understand your question, but I believe the terminology of "reset" means "reload" with respect to the APU sweep document. Without more context, I think that blargg's quote is clarifying that writes to the sweep register are not applied immediately. Rather, they are applied after the divider is clocked. The half frame tick of the APU Frame Counter clocks the sweep unit.

From your code snippet, nearly all of the sweep unit logic appears to be present, but I don't see any check for whether the frame counter is generating a half frame tick. Perhaps you only call this function from other code when that tick occurs. If that's the case, I see one likely issue with APU sweep muting:
Two conditions cause the sweep unit to mute the channel:
  1. If the current period is less than 8, the sweep unit mutes the channel.
  2. If at any time the target period is greater than $7FF, the sweep unit mutes the channel.
The calculated target period can mute the channel at any time, such as when the pulse unit reloads the length counter (presumably the ptimer->timer_reload value in your code) not just when the half frame tick occurs. Unless you choose to factor things differently, I think your function will end up needing another parameter to provide the state of the half frame tick. Then the logic would go something like:

Code: Select all

void ClockSweep(PSWEEP psweep, PTIMER ptimer, PFRAME_COUNTER pcounter, 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;
	
	if (pcounter->half_tick) {
		if (psweep->period)
			psweep->period--;
		else {
			if (psweep->enable && psweep->shift && psweep->dac)
				ptimer->timer_reload = ptimer->result;
			psweep->period = psweep->reload;
		}
	}
}
User avatar
Anes
Posts: 702
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Re: Apu Sweep Unit reset

Post by Anes »

Ok i got it.
Im using "old text info" for doing things.

What do you mean with "half frame"?
ANes
notyourdad
Posts: 16
Joined: Sun Mar 12, 2023 5:49 am

Re: Apu Sweep Unit reset

Post by notyourdad »

Anes wrote: Thu Mar 16, 2023 11:24 am What do you mean with "half frame"?
I'm assuming you're familiar with the APU Frame Counter. It generates the clock ticks that drive several systems in the APU. Approximately four ticks are generated per PPU frame (the timing is not actually the same, but close). The 1st and 3rd ticks are the "quarter frame" ticks. They clock the various channels' envelopes and the triangle channel's "linear counter." The 2nd and 4th ticks are the "half frame" ticks. They also clock the envelopes and triangle channels linear counter, as well as the various channels' length counters, and the pulse channels' sweep units.

The handy tables in the APU Frame Counter document explain this setup visually and also show the differences between the two modes that the frame counter can operate in.
User avatar
Anes
Posts: 702
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Re: Apu Sweep Unit reset

Post by Anes »

I'm assuming you're familiar with the APU Frame Counter
Yes, i know what half frame is. I just want it to know what was your mening in what you said.
So, im doing it well.
I just wanted to know:
He says: "there is a reset flag that reloads/reset the counter when its on".
Maybe i should do something like:

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;

	if (psweep->period)
		psweep->period--;
	else if (psweep->reset)
	{
		psweep->period = psweep->reload;
		psweep->reset = 0;
	}
	if (psweep->period == 0)
	{
		if (psweep->enable && psweep->shift && psweep->dac)
			ptimer->timer_reload = ptimer->result;
		psweep->period = psweep->reload;
	}
}
He says that the divider/counter is first clocked and THEN if reset flag is ON the counter is reloaded.
I just wanted to know if my code was ok.
ANes
notyourdad
Posts: 16
Joined: Sun Mar 12, 2023 5:49 am

Re: Apu Sweep Unit reset

Post by notyourdad »

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:
  1. 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.
  2. 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!).
User avatar
Anes
Posts: 702
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Re: Apu Sweep Unit reset

Post by Anes »

Thanks very much for the code. Now i belive i understand what about the "reset" thing on the sweep unit.
ANes
Post Reply