NES APU Pulse Wave Frequency

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

NES APU Pulse Wave Frequency

Post by DarylTechNES » Wed Apr 08, 2020 2:05 pm

I've been reading the APU docs on the wiki and have been writing some scratch code. I'm trying to print what one of the pulse wave freqeuencies is in at a given point when the system is running. I wrote some code that looks like this:

Code: Select all

void ExecuteCycle()
{
   // Decrease the timer every 2 CPU cycles
   if (CyclesExecuted % 2 == 0)
   {
       pulsewave_1.timer--;
       pulsewave_1.timer &= 0x7ff;
   }
   // Sample / Print Frequency at every ~40.58 CPU cycles.
   if (CyclesExecuted % 41 == 0)
   {
        if (pulsewave_1.timer >= 8)
            printf("Frequency = %d", 1789772 / ((float)16 * (pulsewave_1.timer));
   }
   DoExecuteCycle();
   CyclesExecuted += 1;
   SleepUntilTimeToRunNextCycle();
}
DoExecuteCycle() will handle the instruction which will set pulsewave_1.timer if a memory write occurs to $4002 or $4003.

I realize its a naive implementation but I'm trying to figure out if I'm on the right track. I used the formulas on the APU Pulse Wave page I've been testing with Donkey Kong and the frequency values are all over the place on the pulse wave that does't seem to remotely match what is supposed to be playing.

Does that logic seems relatively correct?
Last edited by DarylTechNES on Wed Apr 08, 2020 2:36 pm, edited 3 times in total.

lidnariq
Posts: 9403
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: NES APU Pulse Wave Frequency

Post by lidnariq » Wed Apr 08, 2020 2:24 pm

You need two values: the current divider count, and the reload value. Right now, you're never reloading it.

DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

Re: NES APU Pulse Wave Frequency

Post by DarylTechNES » Thu Apr 09, 2020 5:02 pm

Thanks for that, I went ahead and changed the code above and the sound that is coming out now sounds like the game, but very poorly because I'm struggling with the resampling part.

Ignoring volume/amplitude, I'm struggling to understand when the pulse wave is high or low.

As seen above, I'm trying to resample to 44100. I'm attempting to do that by skipping over ~40 samples to down sample from the APU which is 1789772.

What I'm trying to wrap my head around is what and how much to write to SDL_QueueAudio when its time to look at the 40th sample at the APU. Ignoring volume, I'm just trying to mess around and write a positive or negative value but I'm not sure what to write every 40th CPU clock.

Is there an algorithm or formula to look at that would lead me in the right direction?

dink
Posts: 41
Joined: Sun Jan 12, 2020 8:42 pm

Re: NES APU Pulse Wave Frequency

Post by dink » Thu Apr 09, 2020 5:27 pm

Hi Daryl,
Something simple like this might be helpful - a simple copy & update-style re-sampler (no interpolation).
I started with this, then as I progressed, moved to a cubic interpolating-style re-sampler for higher quality.
This will definitely get you where you want to be right now, though :)

If you need to know anything about this - let me know and I'll try to help or explain things as best as possible:

Code: Select all

// set these up in your init or something, make nBurnSoundLen and samples_from global (if you want)
int32_t nBurnFPS = 6000; // fps(60.00) * 100
int32_t nBurnSoundLen = (44100 * 100 + (nBurnFPS >> 1)) / nBurnFPS;
uint32_t samples_from = (uint64_t)((double)((1789772 * 100) / nBurnFPS) + 0.5);

void cnu_resample(uint16_t *in, uint16_t *out, uint32_t samples_len)
{
	// *in = mono 16bit samples
	// *out = stereo 16bit samples

	for (INT32 j = 0; j < samples_len; j++)
	{
		INT32 k = (samples_from * j) / nBurnSoundLen;

		out[0] = in[k];
		out[1] = in[k]; // if you want mono out, comment this line and change '2' in the next line to '1'
		out += 2;
	}
}
best regards,
- dink

User avatar
Quietust
Posts: 1557
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: NES APU Pulse Wave Frequency

Post by Quietust » Thu Apr 09, 2020 7:05 pm

dink wrote:
Thu Apr 09, 2020 5:27 pm
Something simple like this might be helpful - a simple copy & update-style re-sampler (no interpolation).
I started with this, then as I progressed, moved to a cubic interpolating-style re-sampler for higher quality.
This will definitely get you where you want to be right now, though :)

Code: Select all

// set these up in your init or something, make nBurnSoundLen and samples_from global (if you want)
int32_t nBurnFPS = 6000; // fps(60.00) * 100
int32_t nBurnSoundLen = (44100 * 100 + (nBurnFPS >> 1)) / nBurnFPS;
uint32_t samples_from = (uint64_t)((double)((1789772 * 100) / nBurnFPS) + 0.5);

void cnu_resample(uint16_t *in, uint16_t *out, uint32_t samples_len)
{
	// *in = mono 16bit samples
	// *out = stereo 16bit samples

	for (INT32 j = 0; j < samples_len; j++)
	{
		INT32 k = (samples_from * j) / nBurnSoundLen;

		out[0] = in[k];
		out[1] = in[k]; // if you want mono out, comment this line and change '2' in the next line to '1'
		out += 2;
	}
}
That should definitely work (in fact, it's almost exactly what Nintendulator used in its infancy), though I can personally guarantee that the Noise channel will sound wrong at high frequencies, especially if it's using the 93-step mode (e.g. in the game Solstice).

If you don't want to go all the way to cubic interpolation, a simple linear interpolation (where you take the 40-41 input samples and average them together to get the output sample) ought to be sufficient to get decent-sounding noise.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

Re: NES APU Pulse Wave Frequency

Post by DarylTechNES » Sat Apr 11, 2020 12:57 pm

Thanks for that code, I haven't had a chance to try it yet since I've been trying to track down what appears to be a bug in my timer logic.

For the pulse wave's timer, I'm trying to make sure I understand the behavior...

The timer counts down from t to 0, if t < 8 there is no sound. Once it reaches 0 it goes back to t.

1. If there is a write to 4002/4006 (timer low) or 4003/4007 (timer high), does that start the countdown, even though its technically not "atomic" (two writes are required to set a full timer value).
2. The timer gets decremented every 2nd cpu cycle. When it reaches zero and needs to get decremented again, it goes back to the current low/high values in 4002/4006 and 4003/4007?
3. If there is a write to timer or or timer high, in the middle of a countdown does that affect it? OR does that only affect what the value rolls back to once t reaches zero?

User avatar
Quietust
Posts: 1557
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: NES APU Pulse Wave Frequency

Post by Quietust » Sat Apr 11, 2020 7:11 pm

DarylTechNES wrote:
Sat Apr 11, 2020 12:57 pm
1. If there is a write to 4002/4006 (timer low) or 4003/4007 (timer high), does that start the countdown, even though its technically not "atomic" (two writes are required to set a full timer value).
The countdown is constantly running in the background, whether the channel is playing anything or not. If you write the low bits, wait a while, then write the high bits, you may indeed cause one loop iteration to use a bogus value, but considering the fact that the timer has to count down 8 times just to play a single wave, you're unlikely to actually notice it unless you're specifically listening for it.
DarylTechNES wrote:
Sat Apr 11, 2020 12:57 pm
2. The timer gets decremented every 2nd cpu cycle. When it reaches zero and needs to get decremented again, it goes back to the current low/high values in 4002/4006 and 4003/4007?
Yes.
DarylTechNES wrote:
Sat Apr 11, 2020 12:57 pm
3. If there is a write to timer or or timer high, in the middle of a countdown does that affect it? OR does that only affect what the value rolls back to once t reaches zero?
Writes to those registers only affect the "reload" value, so the current countdown will not be affected.

However, writes to $4003/$4007 will cause the duty cycle phase to immediately reset to the beginning, which can result in undesired noise if used improperly (e.g. trying to modulate between frequencies that cross an 8-bit boundary).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

Re: NES APU Pulse Wave Frequency

Post by DarylTechNES » Fri Apr 17, 2020 10:31 am

If I'm understanding the nesdev wiki, the frequency of the pulse wave at any given point in time can be calculated by:

frequency = 1789772 / (16 * (t+ 1))

where t is the current timer value.

The channel is silenced if t is < 8.

Looking at the math, as t counts down to 0, the frequency values in that formula get very very high though. For example, I'm messing around with Donkey Kong and the first pulse wave (which is supposed to play the lower sounding part of the menu song). I'm stepping cycle-by-cycle in the emulator to inspect the frequency values. When I look at the frequencies that formula outputs when the pulse wave channel is enabled, I'm getting increasingly high frequencies like 2237hz when t is approaching 0 (t = 49), which is expected with that formula from the wiki.

Why don't we ever hear these higher frequencies?

lidnariq
Posts: 9403
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: NES APU Pulse Wave Frequency

Post by lidnariq » Fri Apr 17, 2020 10:49 am

Because you're still confused in the same way when you started this thread. The value that the program writes to the registers is not the instant counter value, but is instead the reload value.

DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

Re: NES APU Pulse Wave Frequency

Post by DarylTechNES » Fri Apr 17, 2020 12:13 pm

lidnariq wrote:
Fri Apr 17, 2020 10:49 am
Because you're still confused in the same way when you started this thread. The value that the program writes to the registers is not the instant counter value, but is instead the reload value.
In my previous post, t never gets modified by the program until t reaches 0. After t reaches 0, the emulator looks at $4002 and $4003 to determine what the new value of t should be. The program cannot modify the countdown.

By "instant counter value" you are referring to a value that started at t and gets decremented by the emulator until it reaches 0.

By "reload value" you are referring to the value that t started at. This is equivalent to:
1. The value of the "instant counter value" after the "instant counter value" hits 0.
and
2. The $4002 and $4003 for Timer Low/Timer High.

I'm misunderstanding this but I'm not sure where.

lidnariq
Posts: 9403
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: NES APU Pulse Wave Frequency

Post by lidnariq » Fri Apr 17, 2020 12:47 pm

DarylTechNES wrote:
Fri Apr 17, 2020 12:13 pm
In my previous post, t never gets modified by the program until t reaches 0. After t reaches 0, the emulator looks at $4002 and $4003 to determine what the new value of t should be. The program cannot modify the countdown.
In the formula you quoted, the "t" in the equation is the reload value, not the current contents of the counter.

DarylTechNES
Posts: 10
Joined: Sun May 10, 2015 5:01 pm

Re: NES APU Pulse Wave Frequency

Post by DarylTechNES » Fri Apr 17, 2020 12:57 pm

lidnariq wrote:
Fri Apr 17, 2020 12:47 pm
In the formula you quoted, the "t" in the equation is the reload value, not the current contents of the counter.
YES. Thank you, that was my problem. Now this totally makes sense and the audio sounds better.

Thanks again.

Post Reply