8-step duty cycle (attn: blargg)

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
8-step duty cycle (attn: blargg)
by on (#3070)
Blargg's APU doc mentions that the square programmable timer clocks the duty cycle every OTHER clock (effectively dividing the frequency by 2). And that the duty cycle takes 8 steps rather than 16.

This reminds me of BT's section on the noise channel. It have higher frequency values for the noise lookup table but said that the RNG unit is updated every OTHER clock.

Anyway -- I've since been thinking about it... and does this make ANY difference at all when emulating? I mean it's good to know the exact hardware behavior... but doing a 16-step duty cycle would have the exact same effect as a divided by 2 8-step duty cycle, wouldn't it?

As brought up in that savestate thread you said we should save the duty cycle as 0-7 -- but then wouldn't we have to save ANOTHER byte (or just a bit) to signal whether this is the first or second clock in the 2-step divider? Couldn't we just say that the low bit of the duty cycle counter is the step of the divider?

I don't know how much sense I'm making (it's pretty late and I'm tired) -- but my question is simple: Does emulating a 16-bit duty cycle have ANY effect on the generated wave? Or does it yeild the exact same result as emulating a divided-by-2 8-step duty cycle. Because I can't see how it would be different (and it sure is loads simpler to do 16-steps).

by on (#3074)
As I understand, the main difference is that updating the period while the channel is playing will result in slightly different behaviour between the two methods.

by on (#3077)
Maybe I'll get an award for the most rewrites of this post.

My APU reference is incorrect regarding the timer with divde-by-two on the output. I was thinking of the Wiki where it has been corrected. Apologies for that and any other remaining errors.

Yes, a 16-step duty sequence is equivalent to using an 8-step duty sequence with a divide-by-two on its input. And yes, it would be a mess to try to explicitly code the divide-by-two and save this state. I thought the question was whether the divide-by-two was before or after the timer, which does make a difference. If before, the timer is only reloaded 8 times per wave, otherwise it's reloaded 16 times per wave.

I've written several tests to verify that the divide-by-two is before the timer (link to test ROM below). That should be just as simple to implement as having it after (reload timer with raw * 2 + 2, use 8-step duty sequence).

In the simplest test, I ran the square with $4002 = 8 (i.e. highest pitch). Then I executed

Code:
lda #255
sta $4002
pha       ; 21 delay
pla
pha
pla
pha
pla
lda #8
sta $4002


which would cause the timer to be reloaded one time with whatever 255 gives. Looking in a sound editor, the flat section in the middle of the high pitch was 512 clocks (divider before timer) rather than 256 clocks (divider after timer).

In a more complex test I run a cycle-timed loop that changes $4003 momentarily (for 4 clocks) every fourth timer reload. This produces two different results depending on where the divider is. I verified both by putting the divider before and after in an emulator. The NES matches with the divider before. Here's the NES ROM, main asm code, and recordings for before and after:

square_timer_div2.zip

by on (#3079)
Well, I didn't get the idea... >_< but the result in my emulator was the sound played in "correct.wav". However, there are "clicks" during the playback; minor, but audible. I use 8 steps - if I switch to 16 steps, then I hear "after.wav".

by on (#3082)
Quote:
Well, I didn't get the idea


Same here :). I rewrote the loop last night (almost three hours on this topic, ugh) and put some diagrams in the asm source, but they still don't make it completely clear.

Your emulator probably works right. Changing the timer to the other way and verifying the test failed was a good idea to do.

The test plays three tones. The first one's pitch depends on how the timer is implemented, the second with the pitch it should be, and the third the pitch it would be if the divider is put after the timer. There will probably sometimes be a slight click between the tones (I actually wanted to re-run it on a NES until it did click, so I didn't make it seem as if it should be seamless).

by on (#3083)
Nice test, blargg. I forgot to say this... ^_^;;

by on (#3085)
blargg wrote:
The test plays three tones. [...] There will probably sometimes be a slight click between the tones (I actually wanted to re-run it on a NES until it did click, so I didn't make it seem as if it should be seamless).

Or you could insert clicks manually with CPU$4011. Do you plan on making an updated version of the test case?

by on (#3095)
As a reminder, the square duty handling isn't something that is detectable from the 6502 CPU, so instead of having a nice pass/fail result code printed on screen, the test has to cause a large audible difference when there is a problem. Every test of this sort requires a unique strategy for magnifying differences in behavior, and the pass/fail result will of course be different. I need to better document what is relevant to the test result, and what can be ignored.

I'm working on a set of kick-ass validation ROMs to verify both the documented operation of the APU and emulator implementations of it. The few tests I've released are just a preview of what's to come. I'm thrilled to be working on these because they are so useful for validating an emulator, especially when you make big changes to it. I hope to get around to writing tests for the CPU, memory, and PPU subtleties too. I love being able to shine a spotlight into obscure corners of software so it can be improved.

Disch's recent distinction between APU behavior that affects long-term emulation accuracy (things that code running on the CPU can detect) and that which only affects audio output was useful in helping me focus on finishing the ROMs that validate CPU-detectable APU errors. It's been very difficult figuring out a strategy of what to test, how to test it, how to package the tests, etc.

I'm sure everyone will enjoy implementing the new complex APU frame counter behavior once I get it documented and finish the test ROMs (not!). I haven't even implemented it yet.