Here's a general outline of what happens in my NMI and main loop routines:
Code:
NMI:
pha
txa
pha
tya
pha
jsr PPUUpdates
jsr APUUpdates
inc VBLCount
pla
tay
pla
tax
pla
rti
Main:
jsr HandlePlayer
jsr HandleAI
jsr HandleSoundEffectCompetition
jsr HandleScrolling
jsr DrawSprites
lda VBLCount
-
cmp VBLCount
beq -
jmp Main
My main loop doesn't look exactly like that, but pretty close. The first routine does stuff based off of the players input with the controller (It also reads the controller in this loop). Since my game is a platformer, this routine moves the main character, allows him to attack, etc. depending on the state of the controller. The second routine handles all of the AI (assuming there are enemies or you are playing against the computer). Based on what decisions the player made, the AI for other objects will do what they do. For example, in my game, the player might jump, and the AI for a skeleton will see this and attempt to throw a bone or something at the character. All that is handled in this routine.
In both the player and AI handling code, the player and other objects might do something that would cause a sound effect to happen. The HandleSoundEffectCompetition routine decides which sound effect will take place, as one will dominate over the others.
The next routine will determine if/how much the screen will need to be scrolled over, and what will need to be written to the background once it is. This does NOT write to the background; it prepares data for the NMI to write to the BG, once it gives the NMI permission.
Then the next routine takes all of the coordinates of enemies, the player, and whatnot and draws them in their appropriate location in the OAM page. This does not perform an OAM transfer! That is performed in the PPUUpdates routine in the NMI routine. An OAM transfer is not performed in the NMI unless this sprite drawing process is complete, and the sprite page has been completely updated.
So basically the first two loops take care of all the game logic that happens (characters moving, enemies moving, shooting, killing, etc.), then the next loops prepare communication data (anything that will come out of a TV). The NMI will seal the deal, communicating everything to the player with sound and visuals.
The sound is handled in the NMI every frame, because there is very little game logic involved with sound handling (besides sound effects, which are prepared in the main loop). This is so no matter what, music can play at a constant frame rate. Sound effects will also be played at a constant frame rate, just the rate at which they are requested is dependent on how long the Main loop takes to execute (an integer number of hardware frames. Usually 1, but sometimes 2, hopefully never 3).
I like to think of the NMI routine as the routine that translates what the NES is trying to say to the player, and the Main routine is the routine that translates what the player has to say to the NES. Now, of course it isn't THAT simple, but mainly that's what purpose those routines serve.