APU Pulse Sweep Behavior Question

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
gravelstudios
Posts: 159
Joined: Mon Mar 13, 2017 5:21 pm
Contact:

APU Pulse Sweep Behavior Question

Post by gravelstudios »

I'm using the sweep for several sound effects. Sometimes, it sounds like the sweep is skipping the first pitch and going directly to the second pitch. It seems to be happening right after playing a different sound effect. I thought maybe the data from the previous sound was affecting the next one, so I tried loading all zeros into the pulse channel before loading the next set of data, but that didn't seem to help. Then I thought that maybe I need to reset the frame counter, so I tried loading $FF into $4017 on every frame, and also right before loading the data into the pulse registers and neither of those worked. I thought maybe the order mattered, so I tried loading the data in reverse (starting at $4003 and going backward to $4000), but I got the same thing. I'm really at a loss. I'm not even sure if I'm doing a good job explaining what my problem is. it's like, if I use the sweep to make an arpeggio sort of sound, sometimes it starts on the second pitch rather than the first one. Does anybody know what I'm talking about?
Drag
Posts: 1615
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by Drag »

This happens on actual hardware and is the result of a fun timing quirk. :P

I'm out of practice, but I believe this happens when you start a new note at the same time that the sweep unit was about to adjust the pitch of the old note.

This page on the wiki (under "updating the period") seems to explain why: when you write to the sweep register (like you would when starting a new sound effect), it doesn't immediately reset the sweep timer, it only sets the "reload" flag, which would normally reset the sweep timer and allow the initial pitch to play, except when the timer's current value is already at 0, in which case the pitch adjustment happens just like normal as the timer is being reset.
User avatar
gravelstudios
Posts: 159
Joined: Mon Mar 13, 2017 5:21 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by gravelstudios »

So how do you reset the Sweep to prevent it? like I said, I tried writing $FF to $4017 right beforehand, and also every frame, neither helped. I also tried writing 0 to $4015, then re-enabling the channels right beforehand, and that didn't help either. I also tried writing the data to the audio registers twice (just grasping at straws), which didn't fix it. Is there a specific order I should be writing data to the audio registers for the pulse channels?
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: APU Pulse Sweep Behavior Question

Post by tepples »

Did you try starting the note without sweep (write $08 to $4001 or $4005) and then enabling sweep one or two frames later?
Drag
Posts: 1615
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by Drag »

Unfortunately, the only thing that can reset the sweep counter is that reload flag, which reloads the counter on the next clock of the sweep unit. The sweep unit is clocked by the frame counter, and we know that writing a 1 to $4017.7 will immediately trigger a clock to everything connected to the frame counter (including the sweep units).

Try this:
sweep, #$FF -> $4017, pitch.

The first write sets the reload flag, the second write forces the sweep units to clock (and reload) and also resets the frame counter so we know the sweep units will not naturally clock themselves again for another 14913 CPU cycles (no unintentional double-clock here in other words), and then the third write is the pitch, which occurs safely after we know the sweep counter cannot be about to clock at zero (unless you intentionally set the sweep speed to 0, and even then, the pitch adjustment cannot happen for another 14913 CPU cycles because we reset the frame counter with the 4017 write).

Be aware that writing to $4017 multiple times will clock the volume envelopes, length counters, sweep units, etc multiple times, so to keep the APU's timings consistent, I recommend figuring out how to write your sound driver such that it always writes to $4017 exactly once per call.
User avatar
gravelstudios
Posts: 159
Joined: Mon Mar 13, 2017 5:21 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by gravelstudios »

Try this:
sweep, #$FF -> $4017, pitch.
Thanks Drag, I think I'm on the right track now. in my routine that loads sound effects, as a quick and dirty test, I inserted a write to $4017 in between updating the sweep and the pitch:

Code: Select all

	LDA [sFXPointerLo], Y
	STA $4000, X
	INY
	LDA [sFXPointerLo], Y
	STA $4001, X
	LDA #$FF
	STA $4017
	INY
	LDA [sFXPointerLo], Y
	STA $4002, X
	INY
	LDA [sFXPointerLo], Y
	STA $4003, X
This solved the problem. My new problem is that this routine is called by anything that wants to load a sound effect, any number of times per frame. so to keep the APU running at an even pace, I'll have to buffer sound effect data and load all audio registers at once, making one write to $4017 in between loading the sweeps and pitch data for the pulse channels. Thanks again for your help.
User avatar
gravelstudios
Posts: 159
Joined: Mon Mar 13, 2017 5:21 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by gravelstudios »

I finished my solution. basically, I buffer sound effects as various objects try to initiate them, then in my audio engine I loop through each audio channel once to process all the music or sound effect data and then load the first two bytes of each channel (the second of which doesn't really do anything on triangle or noise, but including them makes the loops easier to write). After that, I write $FF to $4017 (since my audio engine is called once per frame, this is very consistent now). After that, I loop through all the audio channels again and load the last two bytes (I have logic that checks and only loads the 4th byte if necessary to avoid refreshing the channel when I don't want it to), and also reload $4015 to begin DMC playback if that is necessary.

I'm happy with my solution, but I'm still curious how other people have approached this. Do you reload $4017 each frame? and if so, when do you do it?
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: APU Pulse Sweep Behavior Question

Post by rainwarrior »

Doing all of the register writes in one relatively quick pass is a good idea.

Writing $4017 once per frame is also very common, I think many of Nintendo's games write $FF to it once per frame. In my own engine I wrote $C0 to it once at the start of the APU register update pass each frame.
Drag
Posts: 1615
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Re: APU Pulse Sweep Behavior Question

Post by Drag »

In the sound engine I wrote, I write $FF (or $C0, I forget) to $4017 on each call, because I use it to silence the triangle channel:
#$00 -> $4008 ;Set linear counter to 0
#$07 -> $400B ;Set reload flag of linear counter, set pitch really low to avoid pops
#$FF -> $4017 ;Immediately clock linear counter, causing the linear counter to reload to 0, silencing triangle channel
(Note: Since I last worked on my music engine, it's been discovered that the $400B write isn't necessary if you use a different strategy for writing to $4008.)

The writes to $4008 and $400B are skipped if the triangle channel doesn't need to be silenced, but the $4017 write always happens regardless, for consistency. I used to just quickly flip the triangle bit in $4015, but needed to switch to this current method once I added DMC support.
My engine doesn't currently use the sweep channels, but it would be easy to add the square channel sweep/pitch writes to this logic as well.
Post Reply