NSF files

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

NSF files

Post by zeroone »

The wiki does not mention how to determine when a song ends. Any suggested mechanisms to detect this?

Also, it's kind of strange that the player needs to repeatedly jump to the play address at a fixed interval as opposed to the song loop actually being written in 6502 code, especially considering that the play routine maybe designed not to return within the interval. It's easy to set the processor's PC to an address, but it sounds like I need a way to externally trigger a JSR and then some way to detect a return to that virtual JSR. Ugh.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: NSF files

Post by tepples »

zeroone wrote:The wiki does not mention how to determine when a song ends. Any suggested mechanisms to detect this?
The same way you detect in any linear bounded automaton: hash the RAM every frame, and if it's looped, you'll get a repeat. This won't work quite as precisely for music engines that use a non-integer row length, such as my own. But the most reliable way is to sit down, listen to it, and add NSF 2 annotations.
Also, it's kind of strange that the player needs to repeatedly jump to the play address at a fixed interval as opposed to the song loop actually being written in 6502 code, especially considering that the play routine maybe designed not to return within the interval.
That's because the vast majority of play routines used in games return in less than 20,000 cycles, giving the player UI a chance to read the controller and update the screen.
It's easy to set the processor's PC to an address, but it sounds like I need a way to externally trigger a JSR and then some way to detect a return to that virtual JSR.
How virtual? Just put some player code in memory that no NSF mapper uses for readable registers. Or do like a couple Atari 2600 games did: watch $01Fx accesses and bank switch that way. Both can be done in hardware.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: NSF files

Post by rainwarrior »

The usual thing to do is have something that automatically goes to the next track after a period of silence, or detecting a loop (by comparing a log of register writes).

Alternatively, use the NSFe format instead, which can have track times: http://wiki.nesdev.com/w/index.php/NSFe

(Tepples suggested "NSF2 annotations" but the NSF2 proposal is unfinished and doesn't have a specification for track times yet; also nobody's really implemented it yet or made NSF2 files.)

As for how to call PLAY at a regular interval, you need to create some sort of timer either externally or from existing stuff. You could call PLAY from an NMI handler (doesn't support variable speeds, but pretty much all NSFs are at the NMI rate anyway). You could use the APU frame counter instead of the NMI (may fail on NSFs that use the frame counter; a lot of Nintendo NSFs do this, like Zelda). You could build a timer with an IRQ and call PLAY from an IRQ handler. You could build a timer with a state that can be polled in a loop and call PLAY from there (PowerPak does this). If it's an emulator, you have a lot of options (e.g. treating time outside the PLAY routine as "halted" you can save CPU by not having to emulate a wait loop).
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

Thanks rainwarrior and tepples. Your suggestions were very helpful.

Most of the tracks in NSF files loop—it is game music after all. Consequentially, many of the NSF players that I experimented with were not designed to detect the end of the songs, requiring the user to advance the track manually instead. But, as you guys described, detecting a period of silence is probably the only practical solution unless it's an NSFe file containing track durations.

Triggering a JSR for the INIT and PLAY routines from outside of code was not that difficult, but I did encounter one unexpected thing. Below is the beginning of the PLAY routine of the Super Mario Bros. NSF file:

Code: Select all

F2D0:AD 70 07  LDA $0770 = #$01
F2D3:D0 04     BNE $F2D9
F2D5:8D 15 40  STA $4015 = #$FF
F2D8:60        RTS -----------------------------------------
The code assumes that the Z flag is initially cleared. However, the INIT routine actually leaves the Z flag set. I solved this by clearing all flags prior to triggering JSR except for the I flag (the JSR is a surrogate for NMI). I'm not sure if this is the proper solution.

Interestingly, FCEUX actually injects a bit of code to loop the PLAY routine:

Code: Select all

00:8000:8D F4 3F  STA $3FF4 = #$00
00:8003:A2 FF     LDX #$FF
00:8005:9A        TXS
00:8006:AD F0 3F  LDA $3FF0 = #$00
00:8009:F0 09     BEQ $8014
00:800B:AD F1 3F  LDA $3FF1 = #$00
00:800E:AE F3 3F  LDX $3FF3 = #$00
00:8011:20 00 00  JSR $0000
00:8014:A9 00     LDA #$00
00:8016:AA        TAX
00:8017:A8        TAY
00:8018:20 00 00  JSR $0000
00:801B:8D F5 EF  STA $EFF5 = #$FF
00:801E:90 FE     BCC $801E
00:8020:8D F3 3F  STA $3FF3 = #$00
00:8023:18        CLC
00:8024:90 FE     BCC $8024
It's unfortunate that the NSF spec did not require something like that already in place. Starting and stopping tracks could have been controlled by treating certain memory locations as registers.

Is it important to respect the playback rate in the NSF file? If the file contains ripped music, why would it need to be played back at a non-NTSC/PAL rate? NSFe didn't even bother to support custom playback rates.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: NSF files

Post by rainwarrior »

NSFe deliberately removed the playback rate feature because the original spec had suggested 60.0hz instead of the real NES rate of 60.1Hz. As such, basically every NTSC NSF rip inaccurately specifies 60.0Hz. Disch thought it would be better to remove the field entirely than obey the existing field that was normally slightly inaccurate.

I've never seen a game rip that required a custom playback rate. Many NSF players don't even support this feature. (I think I saw one game rip with a 30hz rate once, but I think it should have been ripped with a small piece of code to just run every second frame at 60hz instead.) More recently people making new NSF music have been using it for musical effects, as well as other things like enabling multiple expansions at once, etc. Whether you want to support that kind of stuff is your own decision.

The PLAY routine should not rely on the setup of A,X,Y or flags. Anything that does so is an invalid rip, and the ripper should have addressed this with some code modification.

However, I don't know what you mean about SMB. The first instruction in the PLAY routine is LDA $0770, which sets the Z flag. It's not relying on the prior state of Z at all?

I'm also unclear what you mean about FCEUX interjecting code? If I use the debugger to step in, execution begins at INIT, or PLAY, and there's never an opportunity to see any code run that's not from the NSF itself. What are you describing with that code block? Where did it come from?
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

rainwarrior wrote:NSFe deliberately removed the playback rate feature because the original spec had suggested 60.0hz instead of the real NES rate of 60.1Hz. As such, basically every NTSC NSF rip inaccurately specifies 60.0Hz. Disch thought it would be better to remove the field entirely than obey the existing field that was normally slightly inaccurate.

I've never seen a game rip that required a custom playback rate. Many NSF players don't even support this feature. (I think I saw one game rip with a 30hz rate once, but I think it should have been ripped with a small piece of code to just run every second frame at 60hz instead.) More recently people making new NSF music have been using it for musical effects, as well as other things like enabling multiple expansions at once, etc. Whether you want to support that kind of stuff is your own decision.
Thanks. I guess I'll make this an optional feature that defaults to the actual NTSC/PAL rates unless the user demands otherwise.
zeroone wrote:However, I don't know what you mean about SMB. The first instruction in the PLAY routine is LDA $0770, which sets the Z flag. It's not relying on the prior state of Z at all?
That's a good point. I forgot that LDA does that. I'll review my issues with SMB.
zeroone wrote:I'm also unclear what you mean about FCEUX interjecting code? If I use the debugger to step in, execution begins at INIT, or PLAY, and there's never an opportunity to see any code run that's not from the NSF itself. What are you describing with that code block? Where did it come from?
The source code of nsf.cpp found in the fceux-2.2.2.src package contains a comment at the top and a block of hex:

Code: Select all

//mbg 7/31/06 todo - no reason this couldnt be assembled on the fly from actual asm source code. thatd be less obscure.
//here it is disassembled, for reference
/*
00:8000:8D F4 3F  STA $3FF4 = #$00
00:8003:A2 FF     LDX #$FF
00:8005:9A        TXS
00:8006:AD F0 3F  LDA $3FF0 = #$00
00:8009:F0 09     BEQ $8014
00:800B:AD F1 3F  LDA $3FF1 = #$00
00:800E:AE F3 3F  LDX $3FF3 = #$00
00:8011:20 00 00  JSR $0000
00:8014:A9 00     LDA #$00
00:8016:AA        TAX
00:8017:A8        TAY
00:8018:20 00 00  JSR $0000
00:801B:8D F5 EF  STA $EFF5 = #$FF
00:801E:90 FE     BCC $801E
00:8020:8D F3 3F  STA $3FF3 = #$00
00:8023:18        CLC
00:8024:90 FE     BCC $8024
*/
static uint8 NSFROM[0x30+6]=
{
	/* 0x00 - NMI */
	0x8D,0xF4,0x3F,       /* Stop play routine NMIs. */
	0xA2,0xFF,0x9A,       /* Initialize the stack pointer. */
	0xAD,0xF0,0x3F,       /* See if we need to init. */
	0xF0,0x09,            /* If 0, go to play routine playing. */

	0xAD,0xF1,0x3F,       /* Confirm and load A      */
	0xAE,0xF3,0x3F,       /* Load X with PAL/NTSC byte */

	0x20,0x00,0x00,       /* JSR to init routine     */

	0xA9,0x00,
	0xAA,
	0xA8,
	0x20,0x00,0x00,       /* JSR to play routine  */
	0x8D,0xF5,0x3F,        /* Start play routine NMIs. */
	0x90,0xFE,             /* Loopie time. */

	/* 0x20 */
	0x8D,0xF3,0x3F,        /* Init init NMIs */
	0x18,
	0x90,0xFE        /* Loopie time. */
};
I didn't study the source very much. I just happened to notice that at the top and I assumed that is what they did.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: NSF files

Post by tepples »

zeroone wrote:I guess I'll make this an optional feature that defaults to the actual NTSC/PAL rates unless the user demands otherwise.
Better yet, make it automatic: default to the standard rate (16639 μs for NTSC or 19997 μs for PAL) if it's within 1% of the rate in the file.
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

tepples wrote:Better yet, make it automatic: default to the standard rate (16639 μs for NTSC or 19997 μs for PAL) if it's within 1% of the rate in the file.
That sounds like a good option. Thanks.
User avatar
rainwarrior
Posts: 8734
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: NSF files

Post by rainwarrior »

Interesting. I'm curious how that NSFROM code is actually applied in FCEUX, since it's function is completely hidden from the debugger somehow, if it's being used. In NSFPlay, there is just code that externally controls the CPU instead; I figured most emulating players would do the same.

Hardware players do have to do it with code, of course. There is source code for PowerPak's NSF player available, and I had to create something similar myself for the 2A03 Puritans album cartridge. The TNS-HFC series of NSF playing cartridges do not support the speed parameter (they appear to be NMI-driven), though they also don't support bankswitching the high bank (i.e. the vectors are written there at load, there is no circuitry to replace them on the fly like with the PowerPak).

There isn't really a problem running NSFs at the 60.0Hz rate; all the game rips sound fine this way, just the tempo is off in a musically insignificant way. The only way I've ever seen this come up is when someone is trying to synchronize an NSF with a video. You'll be 1 second out of synch after 10 minutes. Though, usually the problem goes the other way, i.e. the nonstandard NES rate goes out of synch with non-NES video, in which case the user probably needs the NSF to play at 60.0 Hz anyway (or possibly even the standard NTSC 59.94 Hz).

Like, it's one of those things that's technically very slightly inaccurate, but doesn't normally have any significant consequences.

My own feeling is that automatically "correcting" this is not really the way to go. In the places where it's going to make a difference the user really needs complete and explicit control over the synchronization. In the majority case it won't matter at all, and in the minority where it does matter an automatic override will make it difficult for the user to diagnose and correct their problem.
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

rainwarrior wrote:However, I don't know what you mean about SMB. The first instruction in the PLAY routine is LDA $0770, which sets the Z flag. It's not relying on the prior state of Z at all?
Further testing confirmed that I was mistaken about the Z flag requirement. But, it seems reasonable to set the I flag before the JSR to make it act more like NMI.
rainwarrior wrote:My own feeling is that automatically "correcting" this is not really the way to go. In the places where it's going to make a difference the user really needs complete and explicit control over the synchronization. In the majority case it won't matter at all, and in the minority where it does matter an automatic override will make it difficult for the user to diagnose and correct their problem.
Good points. Food for thought.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: NSF files

Post by Memblers »

rainwarrior wrote:(I think I saw one game rip with a 30hz rate once, but I think it should have been ripped with a small piece of code to just run every second frame at 60hz instead.)
That game rip might have been 720°, I remember running into that when I made my NSF player.

And if anyone ever wondered, I'm the idiot that's responsible for most of the NSF rips that had the playback rate MSB/LSB swapped. Boy, was I surprised when I first tried an NSF player that actually used that value.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: NSF files

Post by Dwedit »

Many homebrew NSFs override the playback rate.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

Dwedit wrote:Many homebrew NSFs override the playback rate.
Per the discussion on another thread, the constants used in the elliptic filter for sample decimation were derived based on the CPU frequencies for NTSC and PAL. And, unfortunately, those constants were derived using Octave as opposed to within the code at runtime. It might be possible to generate a table of constants for a wide range of frequencies and interpolate for an approximation of the constants required for the specified frequency. This will require a bit of research.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: NSF files

Post by lidnariq »

NSF playback rate ≠ Sample rate...
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: NSF files

Post by zeroone »

lidnariq wrote:NSF playback rate ≠ Sample rate...
True. But, my emulator is sampling at the CPU frequency. And, the playback rate modifies that frequency. It would have to compensate by decimating or interpolating back to the standard NTSC/PAL frequency before filtering.
Post Reply