Page 1 of 1

Square duty cycle vs apu_mixer/square test

Posted: Tue Dec 20, 2016 7:51 pm
by Sour
(This is pretty much a follow up of the conversation in Mesen's main thread, but I figured this might be better off as a thread of its own at this point)

So I've been taking another look at Mesen's square channel code, and looking at the "apu_mixer/square" test's code..

And there's something that I just can't make sense of.
In the test, square 2's duty cycle is set to 50%.
The code writes to $4007 to reset square 2's sequencer and sets the timer to 111 (i.e clock every 224 cpu cycles)
Then, it loops and writes 127 to $4011, 160 times in a row (each 896 cycles apart), before finally writing 123 on the 161th write.

The sequence looks like this, right after the $4007 write that causes the sequencer to reset to 0 (thus outputting 0 for the next 4 square cycles)

Code: Select all

DMC Value:         127 #1          127 #2          127 #3  ... (156 more writes) ...    127 #160        123             127            [...]
Square Output:     0   0   0   0   1   1   1   1   0      ...                   ...     1   1   1   1   0   0   0   0   1   1   1   1  [...]
So odd writes (1, 3, #161) match with a "0" for the duty cycle and even writes (#2, #4, #160) match with a "1".
This means that the 161th write (value = 123) falls on a falling edge for the duty cycle (1 -> 0), which makes BOTH channels drop at the same time.
Considering the test is attempting to cancel both channels by applying the opposite change, that doesn't make sense. I would expect the exact opposite.

What am I not understanding?

Current problems I had with my code vs the wiki:
1- Writes to $4003/7 were resetting the sequencer AND the timer (instead of just the sequencer).
2- The sequencer was counting upwards instead of downwards.
(It looks like both of these might be "fixes" I took from puNES' code while attempting to make the apu_mixer tests work, but they both contradict the wiki)

After fixing these to match the wiki, I get a perfect match up (cycle-wise) between the square duty changes and the DMC writes, but the value of the square channel is always opposite of what it would need to be for it to cancel out the DMC's output.
At this point, if I invert the duty cycle so that the sequence after a $4007 write is 11110000 instead of 00001111, everything falls into place and the sound output is (to my ears) even closer to the apu_mixer/square recording than my older code.

Picture below is the (what I assume to be) NES recording that was bundled with the tests, Mesen's 0.5.3 output (bottom), and Mesen's current output if I fix both bugs, and invert the duty cycle like I said (middle).
Clearly the middle one is the closest to the recording - so what am I not getting?

Re: Square duty cycle vs apu_mixer/square test

Posted: Tue Dec 20, 2016 7:57 pm
by tepples
Inverting the duty is on the right track. Because the waveform position counter is a down counter, it counts in the order 07654321, producing the waveform 01111000.

Re: Square duty cycle vs apu_mixer/square test

Posted: Tue Dec 20, 2016 8:04 pm
by Sour
Yes, it counts 07654321, but that produces 00001111 (according to the wiki), which is what I used in the sequence above (repeated 161 times).
But then that doesn't produce noise cancellation, it doubles up the sound's volume instead. (DMC goes 127 -> 123 when square goes 1 -> 0, and then 123 -> 127 happens when square does 0 -> 1)

To get the square channel to produce the opposite of the DMC channel, I need to count backwards (07654321) and invert the duty cycle values (0<->1) so that it becomes 11110000.

Re: Square duty cycle vs apu_mixer/square test

Posted: Wed Dec 21, 2016 5:38 am
by Quietust
Sour wrote:Yes, it counts 07654321, but that produces 00001111 (according to the wiki)
The wiki is wrong, then - the hardware definitely outputs 01111000 for that duty cycle, and I'm pretty sure it's been confirmed against visual2a03 (it effectively outputs D2 of the counter).

Re: Square duty cycle vs apu_mixer/square test

Posted: Wed Dec 21, 2016 6:32 am
by Sour
Ah, now I see my mistake. Thanks for clearing this up! I always looked at the wiki's data thinking it was the internal representation of the duty cycle from indexes 0 to 7. But it does actually says that this is the actual sequence that is output, so it's actually listed as 07654321.

So I wasn't counting backwards as I originally thought.
That still leaves a 1 duty cycle gap though. Does writing to 4003/7 change the output instantly because it resets the sequencer? Or is that delayed until the next time the timer expires? I.e, when you write does it reset to index 0 and then goes to index 7 on the next time the timer is up?

Re: Square duty cycle vs apu_mixer/square test

Posted: Wed Dec 21, 2016 3:19 pm
by Sour
I took a look at Nintendulator's code, and it implies any write to $4000-4007 can instantly alter the channel's output - I'm not sure if this is actually written anywhere on the wiki though.

So the write to $4007 instantly resets the sequencer, and ends up setting the volume to 0.
The sequence is then 0 (on $4007 write) 1 (on next timer) 1 1 1 0 0 0 (like the wiki), with the first DMC write occurring at the same time as the first "1", this matches up perfectly with the DMC channel's output. The end result is an almost silent tone, all while keeping in line with everything that's written in the wiki.

Problem solved, I think. Thanks for the help!

Re: Square duty cycle vs apu_mixer/square test

Posted: Wed Dec 21, 2016 5:48 pm
by Quietust
Sour wrote:I took a look at Nintendulator's code, and it implies any write to $4000-4007 can instantly alter the channel's output - I'm not sure if this is actually written anywhere on the wiki though.
It's only for $4003 and $4007, and the APU Pulse page states that "The sequencer is restarted at the first value of the current sequence."

Re: Square duty cycle vs apu_mixer/square test

Posted: Wed Dec 21, 2016 9:22 pm
by Sour
Quietust wrote:the APU Pulse page states that "The sequencer is restarted at the first value of the current sequence."
Yea, but I always had interpreted that as "the next time the timer expires and the duty cycle moves to the next step, it'll have been reset to the start of the sequence instead".
Now that I know the actual behavior, and thinking about it from a hardware point of view (which is something I know relatively little about), it does make more sense that it would be applied immediately, though.