Help with APU Triangle emulation bug?

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
User avatar
olivecc
Posts: 2
Joined: Mon Jul 08, 2019 4:12 pm

Help with APU Triangle emulation bug?

Post by olivecc »

Hello,

I'm making progress on writing a NES emulator, and having gotten through CPU/PPU emulation and the NROM mapper (testing with Donkey Kong), am now working on the APU. The pulse channels seem alright, but the triangle channel implementation has a bug that I can't diagnose for the life of me; I was hoping someone could shed light on a possible cause. (I've searched for similar issues on the forum with no luck, so apologies if this is a duplicate question.)

The erroneous behaviour can be seen at https://www.youtube.com/watch?v=8m0_M51mzgg, beginning at timestamp 0:24. The triangle channel should be producing something resembling this: https://www.youtube.com/watch?v=n5drmQRAaes, but instead seems to output every 'beat' (in musical terms), instead of on the beats that the music should play on.

The relevant source code can be found at https://github.com/olivecc/nos/tree/master/core. Given that the triangle channel timer can only clock the sequencer (and thus, is audible) when both length and linear counters are non-zero[1], I'd presume that one of the latter two are at fault, although I can't be sure. The relevant files are lectr.h, lictr.h (length and linear counters respectively), triangle.h, and apu.h. Feel free to request further clarification if anything's unclear.

Does anyone have suggestions for what might be causing this? Alternatively, is there another emulator with the ability to log APU state that I might be able to compare mine to?

On a side note, I'm immensely grateful to all wiki and forum contributors for documenting and explaining hardware behaviour so clearly that only now do I need to ask a question, kudos.

EDIT: For some further context, if I comment out the 'lectr.is_active()' in the line linked in [1], the output is identical, implying the length counter has no effect; if I do the same for 'lictr.is_active()', the triangle channel is never 'turned off' and is continually audible, only changing pitch at the same points as before.

[1]: https://github.com/olivecc/nos/blob/4aa ... ngle.h#L52
User avatar
Drilian
Posts: 18
Joined: Tue Jan 25, 2005 3:51 pm

Re: Help with APU Triangle emulation bug?

Post by Drilian »

So I think I found the problem! In your write_a function for linear counter, you're setting should_reload to be true. However, I have a comment in my code that says "nesdev wiki says that this sets the reload flag but that seems to be untrue!" (which, incidentally, I forgot about and meant to bring up on the wiki, I'll go do that after this).

I added the equivalent code to my emulator and got exactly the behavior you're seeing - so I think if you take that line out it'll work fine.

As to your later point:
For some further context, if I comment out the 'lectr.is_active()' in the line linked in [1], the output is identical, implying the length counter has no effect; if I do the same for 'lictr.is_active()', the triangle channel is never 'turned off' and is continually audible, only changing pitch at the same points as before.
Generally it seems that games either rely on one or the other, and either use the length counter (in which case the linear counter is set to a very large value) or they use the linear counter (and set the length counter to a large value). So what your experiment is telling you is "this is a game that used the linear counter and not the length counter" - which is a good bit of info to know when debugging, but you sort of have to know to expect that only one of them is actually really going to do the job :)


Hope that helps!


Edit: I asked about it on the APU triangle talk page (http://wiki.nesdev.com/w/index.php/Talk:APU_Triangle). Hopefully it gets sorted out :)
User avatar
olivecc
Posts: 2
Joined: Mon Jul 08, 2019 4:12 pm

Re: Help with APU Triangle emulation bug?

Post by olivecc »

Thanks a bunch! I independently came to the same conclusion by reading blargg's apu_ref.txt, which mentions nothing about that flag being set on write to $4008, only $400B; changing it solved the issue. I'm deeply grateful for you going the length of checking your own implementation to check it out though, and for explaining the length/linear counters in more detail. Much appreciated!

EDIT: I guess it's possible that somebody just miswrote/read 4008 instead of 400B on some reference doc, which was added to the wiki?
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Help with APU Triangle emulation bug?

Post by rainwarrior »

It wasn't a typo. That change was not copying from some reference doc. It was the result of a discussion that led to a hardware test about ways to silence and resume the triangle:
viewtopic.php?p=236304#p236304

However, on careful review of the test I see that I made an error. Sorry about the confusion, and thanks for pointing it out. Here's the correction:


A write to $4008 does produce an eventual reload, but not in all cases and doesn't directly affect the reload flag. Specifically I was looking at the test that showed writing $00 to $4008 can silence the triangle. What I neglected to realize is that there was another case I missed. I think this describes the issue, but I will need to verify tomorrow:

When the reload flag is set while the control (halt) flag is set, the reload flag never clears and the linear counter will be reloaded every subsequent frame sequence tick. Thus:

1. Writing $4008 while already halted and in reload state causes a reload with the new value on the next frame sequence, whether or not its high bit is set. Thus $4008 alone is enough to wake or silence the channel when already halted. (There's a note in the wiki about these two steps being done in order, and that order creates this consequence.)

2. Writing $4008 when not previously halted should not cause the value to change. (This case was missing from my test, will add a new case for this tomorrow...)
User avatar
Drilian
Posts: 18
Joined: Tue Jan 25, 2005 3:51 pm

Re: Help with APU Triangle emulation bug?

Post by Drilian »

Oh awesome, thanks for the clarification! That all makes sense and, I think, jives with how I have everything implemented. $4008 doesn't SET the reload flag but it can allow a reload to proceed if one was waiting.

Thanks again!
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Help with APU Triangle emulation bug?

Post by rainwarrior »

Yeah, I made the mistake because I was trying to advise good ways for a music engine can silence and wake-up the triangle, and writing $4008 by itself is actually a pretty convenient option for that (but only if already halted and in the reload state-- that's the part I forgot when I was making the edit, and I misread my emulator code at the same time to compound my mistake). Sorry! :S

I have updated my triangle silence test as well with a few more cases that can verify the difference, not that this is new unknown behaviour or anything, just I wouldn't have made this mistake if I'd had those cases in it. :P
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: Help with APU Triangle emulation bug?

Post by Zepper »

The expected result is a phone call style, right? :) Tune-silence-tune-silence... correct?
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Help with APU Triangle emulation bug?

Post by rainwarrior »

There's a list at the top of that source code of the sounds or silences. Most of it is 1s of tone, 1 second of silence, but not everything is. (Especially when dealing with linear counter, it only goes up to 1/2 second so I can't make a 1s tone with it.) Each line in that list describes 1 second worth of what you hear. Easiest way to compare is just to record the result on hardware and emulator and see if they match.
Post Reply