Sound update timing problems

Discuss NSF files, FamiTracker, MML tools, or anything else related to NES music.

Moderator: Moderators

tepples
Posts: 22278
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples » Fri Jan 28, 2011 3:26 pm

Shiru wrote:That's it, code located in other place. Drawback is that it makes code more difficult to mantain when you have a lot of t - look here for one part of a single thing, look there for other part, try to find related code in thousands of lines in few files, etc.
An NMI handler doesn't have to be in a separate file. It can be right below the subroutine that prepares the data that the NMI handler copies to VRAM. They can be next to each other in source code even if they have to be in separate banks because of how ca65 segments work.
If you have switching handlers or pointers, there is not only some code to switch them (not much), but also need to switch them safely. Otherwise you can have wrong handler for a moment, or even non-existant handler address (updated LSB, NMI happened, crash). It could be source of weird bugs.
Good idea. I added an (untested) example of ISR-switching code to the wiki article. It reserves three bytes of RAM, changing the first to RTI while the address is half written.

User avatar
tokumaru
Posts: 11991
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Fri Jan 28, 2011 3:59 pm

Shiru wrote:If you have switching handlers or pointers, there is not only some code to switch them (not much), but also need to switch them safely. Otherwise you can have wrong handler for a moment, or even non-existant handler address (updated LSB, NMI happened, crash).
Personally, to solve this problem I just wait for VBlank before modifying the pointer, that way I know for sure that the process will not be interrupted. Another solution in my particular case is to temporally clear bit 7 of the high byte of the pointer (that will make the NMI know that there's no custom handler defined, so none will be called) and then set the lower byte followed by the high byte. I guess I prefer to wait for VBlank though. I have also used tepples' solution in other programs.

Tom
Posts: 67
Joined: Wed Apr 06, 2005 5:36 am
Location: Massachusetts

Post by Tom » Fri Jan 28, 2011 7:00 pm

Shiru wrote:It basically describes that I've meant under words 'a general VRAM update system'. Is there actual games that uses this method?
GemVenture uses a similar system, as do my other works in progress. Wraith also used such a system, but it was much cruder. My understanding is that BattleToads also used something like it, although I've never looked myself so I could be wrong.

IMHO, the tricky part is making sure the sound code is robust to timing conflicts, like if your 'start sound effect' routine get interrupted by an NMI. But it was discussed earlier and I think tokumaru came up with a pretty good solution: http://nesdev.com/bbs/viewtopic.php?p=70941#70941

Shiru
Posts: 1161
Joined: Sat Jan 23, 2010 11:41 pm

Post by Shiru » Sat Jan 29, 2011 1:06 am

tepples wrote:An NMI handler doesn't have to be in a separate file. It can be right below the subroutine that prepares the data that the NMI handler copies to VRAM. They can be next to each other in source code even if they have to be in separate banks because of how ca65 segments work.
Thanks, I know how assemblers work. If you have just one place in project where you need to update VRAM, there is nothing to discuss. I'm talking about complex projects with many such places.
Tom wrote:IMHO, the tricky part is making sure the sound code is robust to timing conflicts, like if your 'start sound effect' routine get interrupted by an NMI.
This exact problem could be avoided easily. Every sound effect channel could have a flag that shows it is 'active' (you probably have one to tell the player to not process the channel when there is no effect). Initially it reset, you also reset it when a sound effect ends and starts (i.e. in the very beginning of 'play effect' routine). You only set it when you have initialized the effect, so all the variables are set already. Player will not process a half-initialized effect then.

User avatar
MetalSlime
Posts: 186
Joined: Tue Aug 19, 2008 11:01 pm
Location: Japan

Post by MetalSlime » Sat Jan 29, 2011 4:32 am

Shiru wrote:Anyway, I got idea that you prefer NMI way. I would like to hear what others use and what commercial games used (if someone examined them) - to get idea what is common, what is not, etc.
I use the NMI way. I have a generalized VRAM update buffer that gets filled with updates in the game logic. When NMI occurs my NMI routine dumps the buffer to VRAM. My sound driver is called in the NMI after the VRAM updates are finished.

The only commercial game I have ever examined in any depth is The Guardian Legend, which I think is a pretty complex game. They use the NMI approach too. Here is an outline of The Guardian Legend's NMI routine:

Code: Select all

  ;push regs onto stack
  ;turn off bg/sprite rendering
  ;set NMI flag (tells the main program that NMI has run)
  ;sprite DMA
  ;write #$1F to $4015
  ;unload generalized drawing buffer to VRAM - there is some flag checking and branching depending on game mode
  ;update palettes
  ;do some Sprite 0 stuff
  ;set scroll
  ;turn bg/sprite rendering on
  ;bankswitch to sound bank
  ;run sound engine driver
  ;bankswitch back
  ;do some random number noodling
  ;pull regs off stack
  ;rti

tepples
Posts: 22278
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples » Sat Jan 29, 2011 7:56 am

Shiru wrote:If you have just one place in project where you need to update VRAM, there is nothing to discuss. I'm talking about complex projects with many such places.
If you have multiple "engines", such as one for one menu, one for another menu, and one for the game, you can put each NMI handler next to each respective engine's buffer filler.
Every sound effect channel could have a flag that shows it is 'active' (you probably have one to tell the player to not process the channel when there is no effect).
In my engine, for example, this flag represents the number of frames left in the current effect: if 0 then no effect is playing.
MetalSlime wrote:;write #$1F to $4015
Enable DMC playback every frame? Does that game (ab)use DMC as a rough scanline timer so that sprite 0 waiting can take place in an IRQ handler?

User avatar
clueless
Posts: 498
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless » Sat Jan 29, 2011 8:49 am

MetalSlime wrote:Here is an outline of The Guardian Legend's NMI routine:

Code: Select all

  ;push regs onto stack
  ;turn off bg/sprite rendering
  ;set NMI flag (tells the main program that NMI has run)
  ;sprite DMA
  ;write #$1F to $4015
  ;unload generalized drawing buffer to VRAM - there is some flag checking and branching depending on game mode
  ;update palettes
  ;do some Sprite 0 stuff
  ;set scroll
  ;turn bg/sprite rendering on
  ;bankswitch to sound bank
  ;run sound engine driver
  ;bankswitch back
  ;do some random number noodling
  ;pull regs off stack
  ;rti
What is the point in turning off the PPU?

Is the game guarding against overrunning the real vblank during updates, or is there another reason?

User avatar
tokumaru
Posts: 11991
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Sat Jan 29, 2011 8:18 pm

tepples wrote:If you have multiple "engines", such as one for one menu, one for another menu, and one for the game, you can put each NMI handler next to each respective engine's buffer filler.
Exactly. The same way you can have a single loop with the VBlank waiting and PPU updates at the end, you can separate the PPU updates into their own routine, but still keep them in the same file, at the exact same position. There doesn't need to be a difference in code size or organization between techniques.
Enable DMC playback every frame? Does that game (ab)use DMC as a rough scanline timer so that sprite 0 waiting can take place in an IRQ handler?
I think this is the case, yes.
clueless wrote:What is the point in turning off the PPU?
I'm wondering the same thing. AFAIK, VRAM can be accessed freely during VBlank without the need to turn rendering off. Unless the updates go past the end of VBlank, like you said.

User avatar
Dwedit
Posts: 4408
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit » Sat Jan 29, 2011 10:12 pm

You turn the screen off, and you get to use the Prerender line for vram updates. Otherwise you can't.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!

User avatar
TmEE
Posts: 777
Joined: Wed Feb 13, 2008 9:10 am
Location: Estonia, Rapla city (50 and 60Hz compatible :P)
Contact:

Post by TmEE » Mon Jan 31, 2011 2:31 am

One solution to the sound updates problem would happen by making a "delayed" system :

Code: Select all

InterruptHandler:
*Update Sound registers
*Do VRAM magic
*Manage sound stuff and prepare sound registers for update on next frame
Register updates should take very little time, and you should have plenty of time left for doing VRAM updates and things like that.

Shiru
Posts: 1161
Joined: Sat Jan 23, 2010 11:41 pm

Post by Shiru » Mon Jan 31, 2011 4:43 am

Don't really get how it could help. Usually there is no real need to have register update before VRAM magic (on NTSC NES at least), because the VRAM magic could only take relatively short time, jitter not going to be noticeable. The problem is when you have VRAM update in main 'thread', not in NMI handler, and the code could lag (take more than one frame) - the lag is the reason why you could need to have VRAM update outside of the NMI handler, and the reason why you need to have sound update in the interrupt (NMI or other).

If you have VRAM update in NMI, there is no problem with sound update, you just need different project design and more complex code.

Anyway, judging by what people have said in this thread, the NMI way is the correct one. Seems that other way I've thought of as the most promising one - using frame counter interrupt - will not work. It almost works, but not good enough - it misses updates, so music lags sometimes, rarely and at random places, but too noticeable to be acceptable.

Post Reply