In NSF, how to start track from loop point without delay?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
In NSF, how to start track from loop point without delay?
by on (#70237)
Hi. I'm the creator of the game Super Mario Bros. Crossover. It's a flash game that recreates Super Mario Bros and allows you to play as other characters. In the last release, I incorporated an NSF player (Game Music Emu) into the game so that I could have a lot of music with a small file size.

However, the main problem I have with NSF is that I cannot change the start time of the track. If I use the seek function, the game comes to a complete hault while it seeks to the point in the song I need. The way I understand it, it's not possible to move to a different point in a song without emulating the entire song up to that point. Is this true? Is there any way around it?

The solution I have come up with is to create custom NSF files that would have additional tracks which are duplicates of the original song but that start from the time I need them to start at. I have come here asking if this is possible and how difficult it would be to do.

I wouldn't need a lot of these custom NSF's... at least not right now. The main ones I need are one song in Mega Man 3 and one song in Blaster Master. I need to be able to start the songs from the loop point instead of the beginning of the track. Any info you guys can give would be greatly appreciated. Thanks!

by on (#70239)
Welcome, exploding rabbit! Super Mario Bros. Crossover is an awesome flash game.

Since NSF files are basically roms, and NSF players are basically emulators, I think the answer for faster seeking is savestates. You'll need to create savestates for each point you want to start the music from, but that requries the NSF player to support save states.

Another way is to asm-hack the NSF so it starts with a particular initial state (RAM, sound, and CPU registers), so any NSF player will work, whether or not it has savestate support. It's like embedding a savestate into an NSF file.

by on (#70244)
Example of a hacked NSF file (Mega Man 3, plays the ending music as the first track)
Download

To make the RAM dump, I'm using the emulator FCEUX, hitting Pause and Frame Advance to get to the right frame, then in the Hex Editor (Debug -> Hex Editor), I'm dumping the RAM (File -> Dump to File -> Ram).

I made an NSF shim that includes the original NSF, but overrides the Init address, then I load the copy the RAM dump into the NES's memory as the replacement Init code. Miraculously, that's enough to get it working.

by on (#70245)
Hmm... I don't think Game Music Emu supports save states, so we'll have to use the second method. So when I dump the RAM from FCEUX, do I go to a part in the game that has the music playing at the exact part I need it to start at? I think usually when I go into frame advance mode, the sound stops playing so it would be hard to tell.
Dwedit wrote:
I made an NSF shim that includes the original NSF, but overrides the Init address, then I load the copy the RAM dump into the NES's memory as the replacement Init code. Miraculously, that's enough to get it working.

I don't understand this part at all. Could you explain in a little more detail?

Also, if I do this, I'll basically be including two copies of the same NSF file, which uses up a lot of memory. Well, it's not a lot, but is there any way to have the hacked nsf file only have the song I need or does it have to be everything?

Specifically what I'm trying to do right now is have the music from the first level of Blaster Master start from the loop point instead of the 7 second intro. You can use that as an example to explain it if you want. Thanks so much!

by on (#70249)
Blaster Master

This one was harder, because it doesn't automatically write to all the sound registers given just a RAM state. Had to step through the code to figure out which bytes to change so it would write some of the sound registers, then had to add some ASM code to write to the rest.

by on (#70250)
It might be worth changing the init address, to skip over the init routine added during the rip (and check all tracks 0-255). It's seemed kinda common to have separate tracks with and without intros, etc. But this type of stuff might be removed in the NSF rip as a duplicate.

Save states in an NSF, that's a pretty cool hack.

For an NSF optimizer, there actually was one but I can't find any trace of it online. It was by TNSe (who made NESten), called NSFTool. A google search turns up a much simpler NSF editor. You would set the time length, it would emulate it for that time, so you could zero-out any memory that wasn't used. When I made my SNES NSF player, this feature saved lots of memory and a huge deal of loading time. Too bad it seems to have vanished, or maybe it was never on a website (from 2001 or so).

by on (#70254)
Dwedit wrote:

Wow, thanks!!! This is EXACTLY what I needed. And it's all in one file too! The other song that causes the most trouble in the game is the first track in the Mega Man 3 NSF because the intro is so long. Can you make me a hacked Mega Man 3 NSF that has an additional track of that song starting at the loop point? Thanks again!

In the future there's going to be a lot of music in the game, and it'd be great to have versions of each track with the intro and without. Is this something that is difficult to learn or should I just come here and request custom NSFs? Also, I might switch to the NSFe file format later because I'll want players to be able to choose which songs to put in their own levels and I'd like to have the track name listed in the game. Will we have to remake the custom files if I switch? Will the process be different if it is NSFe?

You guys are awesome. I should have come here earlier.

by on (#70256)
The other way to optimize an NSF file is to make a tiny NES wrapper, and use FCEUX's Code/Data logger to create a stripped rom, then turn it back into an NSF file. Not only will all the other music data be missing, but much of the music code will be too.

by on (#70260)
Dwedit wrote:
The other way to optimize an NSF file is to make a tiny NES wrapper, and use FCEUX's Code/Data logger to create a stripped rom, then turn it back into an NSF file. Not only will all the other music data be missing, but much of the music code will be too.

Great idea. Is there a tool that runs an NSF, logs it in such a manner, and zeroes out any unread ROM locations? If not, how hard would it be to make one, say by adding logging to Game_Music_Emu?

And how hard would it be to make an automated tool that uses such logging to make a cue sheet approximating the playing time for each track? A couple minutes after no new addresses have been read, you can assume the track has either ended or started to repeat.

by on (#70261)
Mega Man 3
This one plays the main part of the title song. I decided to start it AFTER one loop instead of immediately as the title song plays so it's cleaner.

I also included an example of a stripped NSF file, it's about 2k in size when zipped. It's missing maybe 15% of the music code, and has none of the music data except for the title loop.
When looking at filesizes, the only size that matters is how big it is after being GZIP-ed.

by on (#70274)
Thanks again! That's exactly what I needed. Why does the file size only matter after being gzipped?

by on (#70275)
Because if you care about file size, using gzip compression as a first step to reduction is cheap and easy. Why waste effort achieving what gzip can at very little cost?

by on (#70278)
blargg wrote:
Why waste effort achieving what gzip can at very little cost?

PowerPak can't gunzip.

by on (#70280)
blargg wrote:
Because if you care about file size, using gzip compression as a first step to reduction is cheap and easy. Why waste effort achieving what gzip can at very little cost?

Funny seeing you here... just kidding. I didn't think Game Music Emu could play zipped files.

Also, I noticed in the Blaster Master NSF that Dwedit made, it has more sounds than were in the original NSF. I even found some sounds that weren't used in the game. It's very helpful to have all the sounds from the whole game in an NSF because I sometimes play sound effects through NSF too. For example, in the Legend of Zelda NSF, it doesn't have the sound effect it plays when you find a secret. I try to use NSF to play sounds as much as possible because there's no quality loss and no addition to the filesize.

by on (#70281)
Hm, well if I can get ahold of TNSE sometime, maybe I can get his permission to upload NSFTool. I'm sure I still have it.

Exploding Rabbit: if you PM me your email address, I could send that app to you, if you think it would help. I forgot to mention in my other post too, that Super Mario Crossover is the best SMB hack / Flash game ever. :D

Yeah lots of extra tracks, sound effects, get removed by the ripper. If they didn't re-order it, the track order would be insane (lots of blank tracks inbetween, especially). When I was ripping NSFs I tried to keep the sound effects in it as much as possible, and even re-ordered those, but I'm sure other rippers aimed to remove them (since they just wanted the music I guess). Sometimes the sound effect routine is separate from the music routine, too.

by on (#70282)
If you want to unlock songs from the NSF, not only do you change the Number of Tracks byte, you also need to NOP out the code that selects track numbers from a table.

You'll often see these soon after the Init code, something like

TAX
LDA SomeTable,X

Nop it out, and you can use the real track numbers rather than how the NSF creator decided to order the tracks.

by on (#70283)
I'm still learning how to use a hex editor and that sort of thing. I know how to change the number of tracks but I don't know what it means to NOP something. Where can I find more information about this "Init code" I've been hearing so much about?

by on (#70285)
NOP is a 6502 opcode. In a hex editor, in the code you type in one or more bytes that are EA. EA is the hexadecimal number for NOP.

Look at the NSF spec for details on how to find the three addresses that you want to know and those are LOAD, INIT and PLAY.

LOAD is the start of the program or music driver.

INIT is the entry point to code where memory and data blocks are allocated and initialized.

PLAY is the main core of the music driver and it is updated often. Usually updated everytime the NMI is called in a game.

You can find the NSF spec at Kevin Horton's website http://www.kevtris.org/ .

by on (#70286)
From the NSF Specifications

NSF files have a header.
Code:
0008    2   WORD    (lo/hi) load address of data (8000-FFFF)
000a    2   WORD    (lo/hi) init address of data (8000-FFFF)
000c    2   WORD    (lo/hi) play address of data (8000-FFFF)


The "Load address" specifies where the first byte goes. It is designed so that NSF creators can save space by eliminating unused portions before the music code or data. But in reality, whenever it's not something like 8000 or C000, it's just plain evil. It means you need to do a lot of math to figure out what address is where in the file.
The Init Address is what the game calls to select a tune to play, so the play code can be called later.
The Play Address is what the game calls every frame to update the music.

Addresses are Least Significant Byte first, so an address of 801C would be 1C 80 in a hex editor.

Usually, the Load address is 8000, so if you want to find a file address that corresponds to a NES memory address (like 8123), you subtract 8000, and add 80 for the NSF header, so you'd get 01A3 in the file.

So let's say you want to kill the track selection code that was put onto an NSF rip. Let's say it's blaster master...
Load address is 8000
Init address is 8013
Play address is 8077

Load it up in FCEUX, set a breakpoint for 8013...
we see this...
Code:
00:8013:4C A0 BF  JMP $BFA0

so it's jumping somewhere else (JMP instruction is like a GOTO)
Then we see this
Code:
03:BFA0:AA        TAX
03:BFA1:BD B0 BF  LDA $BFB0,X @ $BFB0 = #$54
03:BFA4:85 E9     STA $00E9 = #$00
03:BFA6:0A        ASL
03:BFA7:4C 16 80  JMP $8016

This is the code that reads from the track remapping table.
TAX means Transfer A to X, it's like "X = A".
LDA $xxxx,X means A = what's what memory address xxxx + X.
There's also a STA $E9 in there for some reason, we'll leave that alone.
Then there's an ASL instruction, which multiplies A by 2.
Then it jumps back to 8016 (which is the address of the instruction right after that first jump)

So if you want to get rid of the relevant code, you would change the three bytes at BFA1 from BD B0 BF to EA EA EA. EA is the NOP instruction, which does nothing.

NES Memory address BFA1 is located at 4021 in the NSF file.

Note that after you take out the track remapping, the tracks may be in a strange order, some tracks are songs, some tracks are sound effects, etc.

by on (#70293)
Thanks guys that is very, very helpful. I'll be sure to start playing around with it when I have some extra time.