RuCore.NET

NES Audio Ripping (english)





NES Music Ripping Guide
Version 1.4, May 30, 2000
—————————
Written by Chris Covell (ccovell@direct.ca), with help from Kevin Horton and
Michel Iwaniec (among others).

Contents
———
1. Introduction
2. Early Basics
3. Getting Started
i. Useful Tools
4. Spotting Music Code
i. Using a HEX Editor
ii. Using an Emulator
5. Post-Rip Cleanup
i. Testing the Ripped Data
ii. Arranging Songs
iii. Ripping/Relocating Samples.
6. NSF Guidelines
7. Conclusion
8. Appendix
i. The NESM/.NSF Format
ii. Example 6502 ASM Player



1. Introduction
—————

I have been asked to write up a guide on how to rip NES music for use in «NSF»
players, which play NES music in Kevin Horton’s NESM file format.
Although I am not an expert by any means in music ripping, I have enough
knowledge to rip the easy stuff… And to get other people started. Kevin is
the one who got me started, so I am using his ideas (and some of his e-mails)
as a resource.

2. Early Basics
—————

First of all, if you want to rip NES music, it is important to know several
things. The most crucial is that you have a knowledge of 6502 assembly
language. Get that down and memorized first. It may also be a good idea to
become familiar with the HEX opcodes for the 6502, as well as the number of
bytes taken up by each opcode. Next, learn hexadecimal notation, if you don’t
know it yet.

Secondly, study the architecture of the NES/Famicom. There are several
documents on this available on the Internet. You should become familiar with
how the NES handles interrupts, NMI, writes to various memory areas, system
vectors, and such. Become familiar especially with the sound registers
located at $4000-$4015, with the exception of $4014, which is for sprites, and
not sound.

3. Getting Started
——————

To begin ripping NES music, it’s a good idea to have both the physical NES
cartridge and a ROM file of it. Not only is this a good idea for legal
reasons, but the «real thing» is your control system. It’s where the NES game
will behave exactly as it was designed. Unfortunately, no emulator is
perfect.

i. Useful Tools
—————-

Having many emulators for your specific computer(s) is very useful. They all
behave differently with different ROMs, so knowing each emulator’s strengths
and weaknesses is good. Here is what I have going on my computers (I rip and
assemble NES music and demos on my Amiga)

Amiga: A/NES: Good for fast NTSC emulation, and somewhat more compatible.
Displays NES vectors. Good for listening to the DPCM samples
REALLLY LOUD. 🙂
CoolNESs: Better sound than A/NES.

MacOS: GrayBox: Great for viewing areas in memory while a game is running,
even areas in memory which are supposed to be «write-only».
Ideal for spotting writes to the sound registers and
especially to the DPCM registers.

PC: Nesticle: Not the most compatible, but very useful for tracing through
a game, and dumping the active NES memory to a file.
FWNES: Useful also for searching through memory, also more compatible
with games.
LoopyNES: Very accurate NES emulation, and it has NSF playing built-in.

It is also useful for whatever computer you use to have a HEX editor, a 6502
disassembler, a split/join utility like cat or join, and perhaps even a 6502
assembler. A good stereo system for listening for errors in sound, and lots
of time and patience are also useful.

If you are successful at ripping music, you might want to get a program that
searches for relative text, in order to search for the music composer’s
credits in the game ROM. This is if you don’t know the composer’s name
off-hand, and don’t want to have to play through the game just to see the
composer’s name in the ending credits. A program called Hexposure exists for
DOS, and I have made a program called ROMSearcher which is written in C++.
Binaries of it exist for DOS, Windows, Linux, and AmigaOS.

4. Spotting Music Code
———————-

Okay, we’re starting. Most NES music accesses its music-routines in a logical
way, by first JSRing to an INIT subroutine, which sets up areas in memory and
sets a song going. Next, every VBlank, the game JSRs to its PLAY subroutine,
which keeps updating the audio hardware, making music. So: INIT once to start
music, PLAY many times to play the music. Some games behave in a nonstandard
way, such as not having an INIT subroutine to speak of, etc… You will have
to deal with these yourself.

i. Using a HEX Editor
———————

Sometimes, you don’t even need an emulator to spot the music code. First,
load up the game ROM in a HEX editor, and search for a write to the NES’ sound
address space. Generally, I do a search for $8D$15$40, which is «STA $4015»
in ASM speak. Usually these writes will turn on or off the sound channels,
which is a good indicator of code which is part of the INIT routine. Now the
tough part is to trace through the code to figure out where the INIT
subroutine actually begins.

If you are using a HEX editor, you should take note of the way many NES games
are laid out in a ROM. In games that have more than 32k of game code, mappers
are used to swap in portions of code. Often, the music code stays in memory
all the time, while game code gets swapped in. Sometimes, the music code is
enmeshed in the game code, and so it will be your job to separate that.

But anyhow, let’s imagine that there is a game which is larger than 32k, and
it has its music code in memory at all times. Usually, the music code resides
in the $8000-$BFFF region, and the game code is swapped in the $C000-$FFFF
region. This means that you may have to inspect each 16k section in the game
ROM for music code. First, get rid of the NES header so that the addresses
align correctly.

If you search for that write to $4015 in a HEX editor, and a search pops up,
keep searching. It may not be the only write, and it may not even be limited
to a 16k chunk of memory. (There may be several 16k chunks of music code in a
game, like what Kevin encountered when ripping Kirby’s Adventure and Metal
Gear 2.) If a search pops up very close to a 16k offset in the ROM file, like
at $0000, $4000, $8000, $C000, $10000, $14000, etc.. then you’re quite in
luck. Chances are that the music code begins very close to the start of that
16k section. Copy out that entire 16k chunk of the ROM into another file. I
usually call that «*.mus».

So you now have what is most likely the music code for a particular game. You
may want to search through that file for the likely starting point of the INIT
routine, or disassemble the chunk of code (with ORIGIN $8000 or whatever) and
trace through it for the INIT and even PLAY routines, if you wish. That may
be enough, but if not, it might be a good idea to search through the game code
of the ROM for calls to the INIT or PLAY routines. Generally, the PLAY
routine is called in the middle of a game’s NMI code. The vector for the NMI
routine is located at $FFFA-$FFFB in the NES memory, and is generally the 6th
and 5th to the last bytes before a 16k offset in game code. This NMI routine
should point to some place in the $C000-$FFFF region of memory, but some games
do point to weird places, like $0700, or $6000, or even somewhere after $8000.

When you have found what you think is the NMI vector, go to the location in
the file addressed by the vector. If the potential NMI code starts out with
stuff like «PHA, TXA, PHA, TYA, PHA, etc..» it is probably the beginning of
the NMI code. Now, somewhere in the NMI code, the game should JSR to a
subroutine which is the PLAY code. Look for a JSR to $8000, or $80xx. It is
pretty tell-tale when a game which is greater than 32k jumps to a subroutine
which is beyond a 16k boundary. You know that you have found the PLAY code
when the subroutine in this memory region which the NMI code calls does a
bunch of saves to $4000-$4013, or calls other subroutines which save to
$4000-$4013.

Next, to find the INIT code, well, that is more difficult. Use what you found
when you did the search for «STA $4015» to help you. Try to trace backwards
through the music code for possible entry-points into an INIT routine.
Usually, they will directly follow an RTS instruction, or opcode $60.
Sometimes when I’m stumped, I just try out all the points in the music code
that follow an RTS. You can also trace through the game code for calls to
this elusive INIT routine; the calls for it might be in the NMI routine, or
even in the RESET routine, pointed to by $FFFC-$FFFD in the NES’ memory, which
is just after the NMI vector. Trace, trace, trace; that’s all you can do.

Let’s just say that the best-case scenario is many Capcom and Sunsoft games.
They have their music code at $8000-$BFFF, their INIT routine is at $8003, and
the PLAY routine is at $8000. Couldn’t be simpler. Of course, there are many
exceptions. Using an emulator can help out with those…

ii. Using an Emulator
———————

Here is what Kevin has to say about using an emulator:

*BEGIN QUOTE*

I have used the following method to rip stuff very effectively:
(assuming NESticle here)

1) start up the victim, earm, game.

2) pause it. [pause emulation; don’t just press «start»]

3) go into debug and hit «VBI» [it’s «Vint» in Nesticle]

4) keep hitting control-T to trace the code until you see a tell-tale write.

5) go back into debug and hit «save RAM»

Now, you’ve got a conveniently extracted chunk of code in the correct space in
memory and everything. Lop off the lower 32K and keep the upper 32K. This
should be a complete rip of the music. Now you’ve got a much smaller chunk
of code to peruse thru. Also remember to write down the address that was
called to the sound area. (note this may take a few goes… don’t be afraid to
re-run the code to find it) This will only tell you the play address of the
tune. However you can sort of deduce what the init address will be. Note
that some tunes do not have an init address!

For these, the tune# is stored in a memory location and the player code checks
to see if it’s changed, and if so it will load up the proper tune, etc.
Others may need to have you store the tune # into a memory location and then
call an init routine.

I’ve seen all three and they are about equally common.

*END QUOTE*

Remember that in NESticle you can view the messages in a scrolling window.
Use this for easier viewing of all the instructions while you trace. Also
note that after an RTI in the NMI code, it is a LONG time to wait until the
next NMI, so trigger it yourself.

If you don’t have a PC or NESticle, you can still use an emulator perhaps to
dump the active RAM, to trace opcodes, to trap writes to $4000-$4015, or to
view the NMI, RESET, and IRQ/BRK vectors.

5. Post-Rip Cleanup
——————-

After you have extracted a chunk of memory which you believe contains music
code, and you have a good idea of the LOAD, INIT, and PLAY addresses, you
should test it out to see if it is a good rip. This stage is often annoying,
because the correct selection of INIT and PLAY addresses can mean the
difference between NES music and silence. And silence does not help you in
finding out what exactly is wrong.

i. Testing the Ripped Data
—————————

There are currently two ways of playing back music ripped from NES games: in
an NES ROM, or in a .NSF file. In the appendix at the bottom of this
document, you will find assembly source code for the NES player, and a
description of the .NSF header format.

To use your rip in the NES file, you need to assemble the included source code
into a 6502 code stub, and place the ripped music data where it is supposed to
be in the NES ROM. First off, the source code is formatted for use with the
cross-platform cross-assembler «DASM» (but it can be easily converted to other
styles), and it has some constants which you must set up for the 6502 to JSR
to the right locations. Be sure to define the INIT address, the PLAY address,
and the maximum number of songs (defaults to $FF for now) to be played. This
last constant starts at 1, not 0. So, if there are 5 songs in the music data,
this constant reads 5. The start song is also definable, but you will usually
want to keep this at 0 (see ii. for the reason why).

Setting up the ORIGIN of the player code is important. With most rips with a
load address of $8000 and a filesize of 16384 ($4000) bytes, the origin of the
player code is $C000. If you have less data in the music code, or more data
due to an initialization table or samples (see ii. and iii.) you will need to
adjust the origin to compensate for this loss or addition of data. The key is
to make sure that the music code and assembled player code add up to 32768
bytes.

After you assemble the player sourcecode, you will need to make a standard
iNES header (Mapper 0, 2 PRG segments, 0 CHR segments), and append to it the
ripped music data, and then the binary of the assembled player code. Load up
the NES ROM into an emulator, and try out the music. Press «start» to advance
the songs. If the NES crashes upon playing a certain song (and not at the
beginning of the ROM), it usually means that it is trying to play an invalid
song (it is pointing to a non-existent song). In the assembler code, change
the starting song constant to see if there are more valid songs after that
invalid song.

The other way to test out the ripped music data is to prepend to the data a
NESM/.NSF header, and try it out in NEZAmp or other NSF players. The NSF
header format is described in the appendix at the bottom of this document.
The constants like LOAD, INIT, and PLAY address, etc, are self-explanatory.
When you have successfully ripped a song and have made a working NSF file, be
sure to fill in the relevant Title, Composer, and Copyright fields. Don’t be
too lazy or impatient to credit the composer and companies that made that
great music! Keep reading! Don’t be lazy like some of the lazy rippers out
there! Lazy lazy lazy lazy lazy lazy! I can’t stress this enough! 🙂

ii. Arranging Songs
——————-

In many NES games, the arrangement of the music and sound effects may be
helter-skelter. Sometimes, a game may have its «ending» music before its
level music, or it might have a section of music, then sound effects, then
more music. This makes for inconvenient listening. What the ripper can do is
rearrange the ordering of the «songs» so that the songs are arranged in a more
logical fashion. It’s easier than you think!

All that one has to do is make up a table (or array or index) containing a
«proper» ordering of songs in the music file. Let’s say for example that when
you play the music of a certain game, the order of songs encountered in the
music-player (either in the NES ROM or the .NSF file) goes like this: Title,
Ending, Level 2, Level 1, Level 3. You obviously want to rearrange the songs
so that the Level music is in the correct order, and that the Ending music is
at the end. In the NES ROM of the assembled sourcecode, you can see a pair of
numbers on-screen which tell you the current song number being played. (This
corresponds to the number which is loaded into the 6502’s accumulator right
before JSRing to the INIT subroutine.) The way the hypothetical ROM is right
now, song 0 is the Title music, song 1 the Ending music, song 2 the Level 2
music, and so on. What I usually do is go through each song, and write down
the song number and what part in the game each song is from, like I did above.
I then make an ordered list of songs. In the case of the example above, I
would write down $00,$03,$02,$04,$01. There, we have the 5 songs, all in the
order that you want. Now we have to modify the music data to reflect this.

First off, locate at least 8 or so empty bytes in the music data. These are
regions which you know are unused, and are filled with 0s or FFs, or whatever.
Just make sure that these are UNUSED bytes. If you can’t find any, then you
will just have to append the relocation data at the end of the music data
file, and adapt the Origin of the NES player code (if you are using it) to
reflect those changes. The 8 or so bytes are going to be used up with some
instructions designed to load the accumulator with a number from that ordered
list of songs that you made. But first, you need to check the first
instructions at the beginning of the INIT code (which you discovered in step
4), to see if they are relocatable. Usually, the first bytes immediately at
the INIT address are something to the effect of $4C$zz$zz, in other words, JMP
$zzzz. This is really easy to modify. Simply change in a HEX editor the
address in the JMP instruction to the beginning of the location where you
found some free bytes. Make sure you memorize or jot down the original
address JMPed to in that instruction. Also make sure you take into account
the origin of the music code. For example, whatever offset you have in the
music code file, you must add $8000 to it (or whatever your Origin/LOAD
address) is.

Next, you should look for some more empty and unused bytes to store your
relocation table. You will need as many free bytes as there are songs in your
list of music. In the example above, I had 5 songs, so I will need 5 unused
bytes. I will then type in the list of songs into these bytes:
$00,$03,$02,$04,$01. Now, memorize or write down the location of the
beginning of this table (being sure to add the origin to this number as well.)

Now that you know the address of the relocation table, you should lay down
some HEX code back at those 8 or so unused bytes:

$AA,$BD,$yy,$xx,$4C,$zz,$zz, which translates to:

TAX
LDA $xxyy,X
JMP $zzzz

(where $xxyy is the address of your relocation table, the area where your
ordered list of music is stored; and zzzz is the original address which was
originally at the beginning of the INIT routine).

What this will do is: The NSF player/NES ROM player will JSR to the same old
INIT subroutine with the starting song stored in the accumulator, but as soon
as it gets there, it will be redirected through a JMP instruction to your
relocation code. Your relocation code takes the value stored in the
accumulator and uses that to get values from the lookup-table that you made.
Once it has this value, it will JMP back to the REAL beginning of the INIT
code.

And voila! You have made a simple relocation table! Try it out to see if it
works.

Sometimes, at the beginning of the INIT routine in a game, the code is
something complicated, like $C9,$FD,$D0,$03…. or in ASM terms,

CMP #$FD
BNE ($INIT+#$04) + #$03 ;Remember that BNEs are relative, not absolute!

This might trip you up, but you can always put this code after your relocation
routine. In place of this code at the location of the INIT routine, put this
instead:

$4C,$ww,$vv,$EA, which is:

JMP $vvww ;location of relocation code.
NOP ;to fill in the gap.

Next, at the location for your relocation code, put in:

$AA,$BD,$yy,$xx,$C9,$FD,$D0,$03,$4C,($INIT+#$04),$4C,($INIT+#$04)+#$03, which
is:

TAX
LDA $xxyy,X
CMP #$FD
BNE (PC+1) + 3 ;Branch 3 bytes over (after PC increment)
JMP ($INIT+#$04) ;Whatever was skipped over back at INIT, in other words.
JMP ($INIT+#$04) + 3

(This code might look messy. I’ll give an example. Mega Man 2 is like this.
At its INIT routine at $8003, it has this code:

$C9,$FC,$D0,$03,$4C,$29,$81,$C9,… which in ASM is this:

CMP #$FC
BNE $800A
JMP $8129….

I replace it with a JMP to my redirection code, and at my redirection code is
this:

$AA,$BD,$yy,$xx,$C9,$FC,$D0,$03,$4C,$07,$80,$4C,$0A,$80, which in ASM is this:

TAX
LDA $xxyy,X ;Get value from lookup-table.
CMP #$FC
BNE (PC+1) + 3
JMP $8007 ;This is the location directly after the BNE #$03…
JMP $800A ;This is the location it would have jumped to.

This may seem like a lot of hard work, but it is an efficient way of
relocating code and still maintaining the same logic structure of the original
music data. Better still, the INIT and PLAY routine locations remain the same
as they were in the unmodified version of the music data, which is important
for consistency’s sake. I realize that this may sound confusing in
description, but believe me, it is simple in practice.

The NSF format was originally designed by Kevin Horton in order to save the
music from games into a compact and useful standard similar to the PSID file
format. With this in mind, please don’t leave songs and samples unarranged
in the .NSF file. The recommended practice is to order the songs and NOT to
include the sound effects in the NSF. Please, sound effects are so generic
and it is incredibly annoying to sift through 70 sounds that go like (in
Kevin’s words): «boing, boooing, pop, poop, poooop, pfft, pffft, pfff, pshhht,
ff, ffff, ffff, zap, zzap, zzzzap» in order to get to some more music. Having
said that, if you _must_ include SFX in the .NSF file, put them way at the back,
behind all the music. You don’t need to build a huge relocation table just to
accommodate the SFX. You can just put in some code (for example, if there are
15 songs and the rest are SFX) that checks the accumulator upon entry into the
INIT routine, and if it is <15, you look it up in a relocation table. If the
contents of the accumulator are >=15, then you either pass it through to the
INIT routine (if the SFX start at 15), or add whatever value to the accumulator
that gets you to the start of the SFX. So, I’m just helping out, but in no way
condoning the inclusion of SFX into an .NSF rip.

iii. Ripping/Relocating Samples.
———————————

Now we come to a part that might be overlooked by hasty rippers. When you rip
the music data from a game, oftentimes you might find that the DPCM samples
have disappeared in the rip. If you rip the music from a game which you KNOW
has samples in it, you will have to relocate the sample data located in the
game code ($C000-$FFFF) and change the pointers to it in order to be able to
hear the samples again in the ripped file. This method only covers DPCM
samples and not relocation of the «RAW» format of samples. The latter is a
little more difficult, and not something in which I have experience.

First off, play a game on a real NES, and check to see if you hear any
samples, such as voices, percussive instruments, etc… If it does, you will
have to rip it. If you can’t verify it this way, do a search through the
music code of the ripped game for writes to $4010-$4013, and also somehow
setting the upper nybble of $4015 to 1. Many games store the data to write to
$4010-$4013 in a table, so it is your task to find this table. GrayBox on the
Mac helps out immensely, because you can monitor $4010-$4015, and glean the
data written to those locations. For example, let’s say that a game writes to
$4010-$4013 this: $0E,$00,$C0,$23. You can then search through the music code
for a table containing these bytes in series, among other similar bytes.
Usually, you’ll find a large table with these bytes among them, which points
to samples in different locations, but have only their playback frequencies
($4010) which differ.

If you are just using NESticle or similar, your job is a little more
difficult. Many games have the location of the sample table stored in the
zero-page somewhere, so it is not a direct access to it in the ROM. It is
somewhat time-consuming, but you can find the location of a sample table by
disassembling the music-code, and then looking for the code where it loads a
value from someplace, and then saves it at «$4010,Y». Sometimes, the
zero-page address is accessed here. From that address, you can do a dump of
memory at that zero-page address for a clue as to where in the music code the
table is.

Or, in NESticle (or a tracing emulator), you can trace through the NMI routine
for the code that initiates a sample. This obviously doesn’t happen every
VBlank, so you might have to be creative in the timing of your traces. When
you reach a point in the trace where you know beforehand (by disassembling and
looking for writes to $4010-$4013) that the game is about to load data and
write it to the sample addresses, slow down. Look for the first instruction
that goes something like «LDA ($8D),Y». Then when it goes «STA $4010,Y» or
something similar, view the registers if you can. Write down what is in the
accumulator. Repeat these steps for the writes to $4011, $4012, and $4013.
You now should have 4 bytes which make up part of the sample table of that
game. Now do a search for these 4 bytes in the music code using a HEX editor.
It should find the sample table for you!

Batman, for instance, has a table that looks like this:

$0F,$00,$C0,$0D
$0F,$00,$C4,$1F
$0F,$00,$CC,$1F
$0E,$00,$CC,$1F
$0C,$00,$CC,$1F

This means that there are 5 different sample sounds used in the game: One at
(($C0 * #$40) + $C000), which has a sample length of (($0D * #$10) + 1)
(bytes), being played at a frequency of «$0F»; another sample at (($C4 * #$40)
+ $C000), which has a sample length of (($1F * #$10) + 1), being played at a
frequency of «$0F»; and another at (($CC * #$40) + $C000), which has a sample
length of (($1F * #$10) + 1), being played at 3 different frequencies: $0F,
$0E, and $0C. In a table then, you have samples:

Sample Location Length
————————————
1 $F000 209 (#$D1) bytes
2 $F100 497 (#$1F1) bytes
3 $F300 497 (#$1F1) bytes
4 $F300 497 (#$1F1) bytes
5 $F300 497 (#$1F1) bytes

The frequency doesn’t matter; just make sure you are able to count the number
and length of all of the samples used in the game. Most games store their
sample data at or near the end of their game code, at around $E000-$FFFF.
Once you have taken note of all the samples used (either by studying the table
in the music code of a game, or by taking note of all the different
combinations of things written to $4010-$4013, save the total sequential
sample data pointed to by the game into a different file, maybe called
«game.smp». In Batman, for instance, I would rip out the data from $F000 (in
the NES’ memory space) to $F4F1 (at least) and save it into a file. Maybe
making the total sample length a multiple of 64 bytes (but still longer than
the used sample length) would be a good idea.

Now that you have the samples correctly stored in another file, you need to
tack it on to the end of the music code that you ripped. But, you need to
make sure that the sample data fits into the address space of $C000-$FFFF in
the NES, AND that the sample data is aligned at a 64-byte boundary. If you
just rip out the music code, and the total length of the code is a full 16384
bytes, then the sample data appended to the music code will show up exactly at
$C000. If the music code that you ripped does not go all the way to $BFFF in
the NES’ memory space, then you will need to pad the rest of the music code to
make up for the shortage. SAMPLES NEED TO FIT SOMEWHERE IN $C000-$FFFF!!

After you have chosen a good location for the sample data, you will need to
modify the instances of all those samples in the sample table to reflect your
putting the samples at the beginning of $C000 instead of where it was at the
end of memory. For instance in Batman, the sample which was at $F000 is now
at $C000. So you will need to change the «sample address» byte (which will go
into $4012) in the sample table to $00, instead of the $C0 which it once was.
Clever hexadecimal mathematicians will realize that you now have to do the
same to the rest of the instances in the sample table: you have to subtract
$C0 from the other sample pointers as well. A finished sample table (in
Batman, for example) should look like the one on the right:

$0F,$00,$C0,$0D <- this $0F,$00,$00,$0D
$0F,$00,$C4,$1F goes to $0F,$00,$04,$1F
$0F,$00,$CC,$1F this -> $0F,$00,$0C,$1F
$0E,$00,$CC,$1F —> $0E,$00,$0C,$1F
$0C,$00,$CC,$1F —> $0C,$00,$0C,$1F

It is a little tricky to grasp, but if you just study the meaning of the
registers from $4010 — $4015, you should understand it. I can provide other
example files for better illustrative purposes.

So, to recap… Hunt for and rip out the sample data. Stick the sample data
somewhere which will go into $C000-$FFFF of the NES’ memory at a 64-byte
offset. Change the bytes in the sample table which point to the location of
the sample data to point to the new location of the samples.

Finally, if you are using the ASM NES player to make the ripped music code
into an NES ROM, make sure you: Make a valid iNES header, append the music
code, append the sample data, assemble the ASM file (MAKING SURE TO CHANGE THE
ORIGIN OF THE PLAYER CODE TO REFLECT THE NEW PRESENCE OF THE SAMPLE DATA
STARTING AT THE $C000 REGION!), and append the assembled binary file. You
should have a working, sampling 🙂 NES audio ROM.

For .NSF files, just make sure you make a valid .NSF header, append the music
code, and then append the sample data (making sure the sample data will get
loaded in to somwehere in $C000-$FFFF!)

6. NSF Guidelines (RULES!!!)
—————————-

Now that NES music ripping has become popular and is done by many people, it
has become clear that a set of standards should be adhered to in ripping and
creating NESM (.NSF) files. I’m not just being Mr. Gestapo here; it is
important for the sake of ease-of-use, for the benefit of the end user, and
for legal purposes (maybe). Here are some things which you should seriously
think about after and while ripping NES music:

1) Don’t put your name or «handle» in the «Artist/Composer» field, or anywhere
else in the NSF header, for that matter. If you’re looking for fame in
ripping, you won’t get it here. This field is for the name of the original
composer of the music only. Don’t think this is harsh; the HVSC has the
same rule. The basic premise is that the music is more important than the
individuals who rip it. Fortunately, some NSF collections give credit to
whoever ripped the music, but it is done outside the actual file.

2) Don’t be a lazy arse and not credit the composer or game company. Don’t
just put <?> in the composer or copyright fields when you know that you
could spend 5 minutes to find out, by looking inside of the game ROM, and
just by looking at the title screen for the company and copyright date.
This is becoming a very prevalent problem, with many individuals ignoring
crediting the composers and companies that made the game. Don’t know the
company and copyright date of the game whose music you just ripped?
TAKE A LOOK ON THE BLOODY TITLE SCREEN!! JEEZ!

3) Try to reduce the NSF file size by as much as possible before releasing it.
Don’t just dump the NES’ 64k of RAM, slap a header on top, and send it out
there. Try to figure out what the upper and lower boundaries of the music
code are in the file, and chop off anything else. This is important for
people who have computers with filesystems that access the harddrive in
large blocks. An NSF file that is 32k + the header will, unfortunately,
take up much more space because it is just a tad over the 32k blocksize.
(I know that people with this problem are in the minority now, but it is
at least nice to be considerate.) This is also another problem that is
becoming prevalent: lazy arses that just tack the NESM header to 32k worth
of ROM. Listen, most games out there that have memory mappers that switch
16k of ROM at a time generally have their music code limited to 16k of ROM!
It is further inexcusable if you rip music whose code starts at $E000 or
somewhere similar, and your NSF file is 32k. Optimize that sucker, or the
world will frown upon you.

4) Don’t pass off a bad rip as a good one. If you ripped some music and a
song messes up, or you know that there are songs missing from the rip that
you heard while playing the game, then say so. Preferrably in the «Name»
field of the NSF file. This might look ugly, but it lets the user know
that the file is incomplete, and it lets other rippers know to try to rip
(or correct) a complete, working version of the music to replace that file.
(When I ripped MegaMan 3, it turned out to be a bad rip, so I stuck «BAD
RIP!!» in the file’s name. Somebody soon after corrected the rip.)

5) Don’t make a rip that’s identical to a prior one just so that you can get
your name in lights. If you improve on the rip in a way, by arranging the
songs or something, then that’s OK. But be considerate of other people.
Before you rip music from a game, make sure it doesn’t already exist in
the current NSF collections, either under the same or similar name, or under
its Japanese name. Don’t know the Japanese name? Take a listen to all the
Japanese NSFs from the game company that made the music to the game that
you want to rip. At the very least, you get to listen to some great music.

I don’t think adding sound effects to a rip constitutes a new rip. The
standard that people are trying to adopt is one that includes the music,
in a proper order. Sound effects are really not needed, unless they are
completely awesome, or something. (I’m sure we’re all bored of the bog-
standard jumping and sword-slashing sounds by now.)

6) Arrange the songs in the file, if you can (see section 5 of this guide, or
ask me how to.) This makes it much more pleasant for the user to listen
to all the songs in a sequential, even chronological order (as they appear
in the game proper.) Just don’t force the user to have to wade through
invalid song numbers or random sound effects just to listen to a song he/she
likes. All the information for which songs to play in what order should
ideally be stored in just one file.

7) Study the NESM/.NSF file format specification carefully. Especially take
note of how NTSC and PAL timings work. The NTSC speed in the file is to
be written literally as #$1A,#$41. It is in the «Intel» format, which,
although considered clumsy by many, is the standard in THIS particular file
format and should be acknowledged. Also, if you rip the music from a PAL
game, make sure you either provide for PAL/NTSC switching, or set the PAL
bit and PAL speed in the file. NSF players should then (HOPEFULLY, guys!)
recognize the intended playback speed of the music.

8) Don’t invent your own data format to put in the .NSF header. If you discover
a new mapper that has a built-in Theremin, or something, let Kevin Horton
(khorton@iquest.net) know about it, and he’ll include it somehow into the NSF
spec. Kevin has now taken into account bank switching for the FDS’ RAM area,
so rippers of FDS music should make their rips conform to what he outlined in
the NSF spec.

7. Conclusion
————-

This is just a guide to the basics of NES music ripping. I’m sure I have not
covered some things here which are also necessary for ripping some games, but
again, my knowledge of this matter is definitely not extensive. This document
has been eclipsed by other ripping documents, but until they get translated from
Japanese, I’m afraid I’ll have to stick with this. 🙁 This document
is rather large, and I have written a whole lot. Don’t be confused or
bewildered initially by all the damn writing. Get to know ASM and the NES
internals first; the terminology that I use will then become a little more
understandable.

I must give a big THANK YOU to Kevin Horton, who invented the .NSF header
format, and started the ball rolling on NES music. He still does the
difficult rips; I have done just some easy ones. Thanks also to Michel
Iwaniec for making some great NSF players. I look forward to the day when we
will have a collection that rivals the HVSC (no kidding!)

If anybody wants to start a PSID/NSF-like music format for GameBoy, Turbo/PCE
Master System/Game Gear, etc… you have my full support! You can see from
this document in theory how it should be done, and it would be great to listen
to the fabulous music of other formats too. All it takes is a little knowledge
of the specific hardware and processor of that system, and a will to devise a
good file standard for that type of music.

If anybody has any questions about NES music ripping that isn’t covered
(adequately) in this document, feel free to mail me at ccovell@direct.ca.
Also, if you spot any glaring errors or omissions, let me know. You might
also want to take the time to check out my webpage at
http://mypage.direct.ca/c/ccovell, for some fun videogame-related
stuff, and art and poetry, and much more! Bye for now and good luck
ripping!!!

8. Appendix
————

i. The NESM/.NSF Format
————————

The NESM format is a header format invented by Kevin Horton and designed to
enable music players to play back music code ripped from NES and Famicom
games, using a standardized interface. The format is called NESM, but the
files themselves have the .NSF suffix, which stands for «NES Sound Format».
Around the Internet, most people are already beginning to refer to these files
as just «NSFs». I believe that is the name that will stick. In this
document, I refer to files in this format as such.

NES Music Format Spec
———————

By: Kevin Horton khorton@iquest.net

NOTE:
——

Since I last updated this spec, it seems some Japanese rippers have
added some stuff to this spec without asking/telling me what they
added. I’ve had to gronk out this extra information so Sunsoft, Namco,
and FDS tunes currently released MAY NOT CONFORM TO THIS SPEC. I will
do my best to fix the non-conforming tunes.

Remember that I am very willing to add stuff and update this spec. If
you find a new sound chip or other change let me know and I will get back
with you. E-mail to the above address.

V1.50 — 05/28/2000 Updated FDS, added Sunsoft and Namco chips
V1.32 — 11/27/1999 Added MMC5 register locations
V1.30 — 11/14/1999 Added MMC5 audio bit, added some register info
V1.20 — 09/12/1999 VRC and FDS prelim sound info added
V1.00 — 05/11/1999 First official NSF specification file

This file encompasses a way to transfer NES music data in a small, easy to
use format.

The basic idea is one rips the music/sound code from an NES game and prepends
a small header to the data.

A program of some form (6502/sound emulator) then takes the data and loads
it into the proper place into the 6502’s address space, then inits and plays
the tune.

Here’s an overview of the header:

offset # of bytes Function
—————————-

0000 5 STRING «NESM»,01Ah ; denotes an NES sound format file
0005 1 BYTE Version number (currently 01h)
0006 1 BYTE Total songs (1=1 song, 2=2 songs, etc)
0007 1 BYTE Starting song (1= 1st song, 2=2nd song, etc)
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)
000e 32 STRING The name of the song, null terminated
002e 32 STRING The artist, if known, null terminated
004e 32 STRING The Copyright holder, null terminated
006e 2 WORD (lo/hi) speed, in 1/1000000th sec ticks, NTSC (see text)
0070 8 BYTE Bankswitch Init Values (see text, and FDS section)
0078 2 WORD (lo/hi) speed, in 1/1000000th sec ticks, PAL (see text)
007a 1 BYTE PAL/NTSC bits:
bit 0: if clear, this is an NTSC tune
bit 0: if set, this is a PAL tune
bit 1: if set, this is a dual PAL/NTSC tune
bits 2-7: not used. they *must* be 0
007b 1 BYTE Extra Sound Chip Support
bit 0: if set, this song uses VRCVI
bit 1: if set, this song uses VRCVII
bit 2: if set, this song uses FDS Sound
bit 3: if set, this song uses MMC5 audio
bit 4: if set, this song uses Namco 106
bit 5: if set, this song uses Sunsoft FME-07
bits 6,7: future expansion: they *must* be 0
007c 4 —- 4 extra bytes for expansion (must be 00h)
0080 nnn —- The music program/data follows

This may look somewhat familiar; if so that’s because this is somewhat
sorta of based on the PSID file format for C64 music/sound.

Loading a tune into RAM
————————

If offsets 0070h to 0077h have 00h in them, then bankswitching is *not*
used. If one or more bytes are something other than 00h then bankswitching
is used. If bankswitching is used then the load address is still used,
but you now use (ADDRESS AND 0FFFh) to determine where on the first bank
to load the data.

Each bank is 4K in size, and that means there are 8 of them for the
entire 08000h-0ffffh range in the 6502’s address space. You determine where
in memory the data goes by setting bytes 070h thru 077h in the file.
These determine the inital bank values that will be used, and hence where
the data will be loaded into the address space.

Here’s an example:

METROID.NSF will be used for the following explaination.

The file is set up like so: (starting at 070h in the file)

0070: 05 05 05 05 05 05 05 05 — 00 00 00 00 00 00 00 00
0080: … music data goes here…

Since 0070h-0077h are something other than 00h, then we know that this
tune uses bankswitching. The load address for the data is specified as
08000h. We take this AND 0fffh and get 0000h, so we will load data in
at byte 0 of bank 0, since data is loaded into the banks sequentially
starting from bank 0 up until the ROM is fully loaded.

Metroid has 6 4K banks in it, numbered 0 through 5. The 6502’s address
space has 8 4K bankswitchable blocks on it, starting at 08000h-08fffh,
09000h-09fffh, 0a000h-0afffh … 0f000h-0ffffh. Each one of these is 4K in
size, and the current bank is controlled by writes to 05ff8h thru 05fffh,
one byte per bank. So, 05ff8h controls the 08000h-08fffh range, 05ff9h
controls the 09000h-09fffh range, etc. up to 05fffh which controls the
0f000h-0ffffh range. When the song is loaded into RAM, it is loaded into
the banks and not the 6502’s address space. Once this is done, then the
bank control registers are written to set up the inital bank values.
To do this, the value at 0070h in the file is written to 05ff8h, 0071h
is written to 05ff9h, etc. all the way to 0077h is written to 05fffh.
This is only done once, when the song is loaded. It is not done after the
song is loaded between each tune init so make sure that the rip takes this
into account (the rip usually will).

If the tune was not bankswitched, then it is simply loaded in at the
specified load address, until EOF

Initalizing a tune
——————

This is pretty simple. Load the desired song # into the accumulator,
minus 1 and set the X register to specify PAL (X=1) or NTSC (X=0).
If this is a single standard tune (i.e. PAL *or* NTSC but not both)
then the X register contents should not matter. Once the song # and
optional PAL/NTSC standard are loaded, simply call the INIT address.
Once init is done, it should perform an RTS.

Playing a tune
—————

Once the tune has been initalized, it can now be played. To do this,
simply call the play address several times a second. How many times
per second is determined by offsets 006eh and 006fh in the file.
These bytes denote the speed of playback in 1/1000000ths of a second.
For the «usual» 60Hz playback rate, set this to 411ah.

To generate a differing playback rate, use this formula:

1000000
PBRATE= ———
speed

Where PBRATE is the value you stick into 006e/006fh in the file, and
speed is the desired speed in hertz.

«Proper» way to load the tune
——————————

1) If the tune is bankswitched, go to #3.

2) Load the data into the 6502’s address space starting at the specified
load address. Go to #4.

3) Load the data into a RAM area, starting at (start_address AND 0fffh).
Load the inital bank values into the bank select registers.

4) Tune load is done.

«Proper» way to init a tune
—————————

1) Clear all RAM at 0000h-07ffh.

2) Init the sound registers by writing 00h to 04000-04013h.

3) Set volume register 04015h to 00fh.

4) Set the accumulator and X registers for the desired song.

5) Call the music init routine.

«Proper» way to play a tune
—————————

1) Call the play address of the music at periodic intervals determined
by the speed words. Which word to use is determined by which mode
you are in- PAL or NTSC.

Sound Chip Support
——————

Byte 007bh of the file stores the sound chip flags. If a particular flag
is set, those sound registers should be enabled. If the flag is clear,
then those registers should be disabled.

* VRCVI Uses registers 9000-9002, A000-A002, and B000-B002, write only.

Caveats: 1) The above registers are *write only* and must not disrupt music
code that happens to be stored there.

2) Major caveat: The A0 and A1 lines are flipped on a few games!!
If you rip the music and it sounds all funny, flip around
the xxx1 and xxx2 register pairs. (i.e. 9001 and 9002) 9000
and 9003 can be left untouched. I decided to do this since it
would make things easier all around, and this means you only
will have to change the music code in a very few places (6).
Esper2 and Madara will need this change, while Castlevania 3j
will not for instance.

3) See my VRCVI.TXT doc for a complete register description.

* VRCVII Uses registers 9010 and 9030, write only.

Caveats: 1) Same caveat as #1, above.

2) See my VRCVII.TXT doc for a complete register description.

* FDS Sound uses registers from 4040 through 4092.

Caveats: 1) 6000-FFFF is assumed to be RAM, since 6000-DFFF is RAM on the
FDS. E000-FFFF is usually not included in FDS games because
it is the BIOS ROM. However, it can be used on FDS rips to help
the ripper (for modified play/init addresses).

2) Bankswitching operates slightly different on FDS tunes.
5FF6 and 5FF7 control the banks 6000-6FFF and 7000-7FFF
respectively. NSF header offsets 76h and 77h correspond to
*both* 6000-7FFF *AND* E000-FFFF. Keep this in mind!

*MMC5 Sound Uses registers 5000-5015, write only.

Caveats: 1) Same as #1, above.

2) Generating a proper doc file. Be patient.

*Namco 106 Sound Uses registers 4800 and F800.

Caveats: 1) No other information available. Seems to be the only addition.

*Sunsoft FME-07 Sound uses unknown registers somewhere around Cxxx

Caveats: 1) No other information available.

Caveats
——-

1) The starting song number and maximum song numbers start counting at
1, while the init address of the tune starts counting at 0. To
«fix», simply pass the desired song number minus 1 to the init
routine.

2) The NTSC speed word is used *only* for NTSC tunes, or dual PAL/NTSC tunes.
The PAL speed word is used *only* for PAL tunes, or dual PAL/NTSC tunes.

3) The length of the text in the name, artist, and copyright fields must
be 31 characters or less! There has to be at least a single NULL byte
(00h) after the text, between fields.

4) If a field is not known (name, artist, copyright) then the field must
contain the string «<?>» (without quotes).

That’s it!

ii. Example 6502 ASM Player
—————————

This is the sourcecode for a 6502 player which will play ripped NES music. It
was coded by Kevin Horton, and I converted it to DASM’s format and added a
graphical display and definable constants for INIT, PLAY, etc… You can
assemble it in the cross-platform cross-assembler DASM, using the option -f3,
and by joining an iNES header, the ripped music code, and the assembled binary
together. It should in its present form, with header, ripped music, and
binary, form a .NES file which is 32784 bytes large. Press «start» to advance
songs.

;*BEGIN ASM FILE*

int_en EQU #$0100
sng_ctr EQU #$0101
pv_btn EQU #$0102

INIT_ADD EQU #$8003 ;INIT Address
PLAY_ADD EQU #$8000 ;PLAY Address
START_SONG EQU #$00 ;Starting Song
MAX_SONG EQU #$FF ;Maximum Number of Songs

PROCESSOR 6502

;code

ORG $C000 ;Origin of player code

.start sei
cld
ldx #$ff
txs

.w_vbi lda $2002
bpl .w_vbi

lda #$0
tax

.ci_lp sta $0000,X
sta $0100,X
sta $0200,X
sta $0300,X
sta $0400,X
sta $0500,X
sta $0600,X
sta $0700,X
inx
bne .ci_lp ;clear RAM

lda #$80
sta $2000
lda #$00
sta $2001 ;set up PPU for interrupts, disable screen

lda #$00
sta $2006
sta $2006
tax
.PATCLR sta $2007 ;Clear char 0 in ppu.
inx
cpx #$10
bne .PATCLR

lda #$01
sta $2006
lda #$00
sta $2006 ;Point to $0100 in PPU

ldx #$00
ldy #$00
.PATLoad lda .PAT,X
sta $2007
inx
iny
cpy #$08 ;Save 8 bytes.
bne .PATLoad
ldy #$00
sty $2007 ;Save blank gfx.
sty $2007
sty $2007
sty $2007
sty $2007
sty $2007
sty $2007
sty $2007
cpx #$80
bne .PATLoad

lda #$3F
sta $2006
lda #$00
sta $2006
lda #$0E
sta $2007
lda #$30
sta $2007 ;Set up Palette

lda #$00
sta sng_ctr

lda #%00001110
sta $2001

lda #START_SONG ;Start Song
sta sng_ctr
jsr INIT_ADD ;init tune
LDA #$01
sta int_en

.k_loop jsr .r_btn
and #$10
beq .k_loop

inc sng_ctr
LDA #MAX_SONG ;Max Song.
cmp sng_ctr
bne .no_scr
lda #$0
sta sng_ctr

.no_scr lda #$0
sta int_en
lda sng_ctr
jsr INIT_ADD
lda #$01
sta int_en
jmp .k_loop ;check button, if pressed inc song # and re-init

.interrupt pha
txa
pha
tya
pha

lda #$20
sta $2006
sta $2006 ;Point to Name Table

lda sng_ctr
lsr
lsr
lsr
lsr
ora #$10 ;Point to right chr.
sta $2007
lda sng_ctr
and #$0F
ora #$10
sta $2007

lda #$00
sta $2006
sta $2006
sta $2005
sta $2005

lda int_en
beq .no_ints
jsr PLAY_ADD ;play tune

.no_ints pla
tay
pla
tax
pla
rti

.r_btn ldy #$08 ;read keypad
ldx #$01
stx $4016
dex
stx $4016

.r_bit lda $4016
ROR
txa
ROL
tax
dey
bne .r_bit

cmp pv_btn
beq .no_chg
sta pv_btn
rts

.no_chg lda #$0
rts

.PAT dc.b #$38,#$4C,#$C6,#$C6,#$C6,#$64,#$38,#$00
dc.b #$18,#$38,#$18,#$18,#$18,#$18,#$7E,#$00
dc.b #$7C,#$C6,#$0E,#$3C,#$78,#$E0,#$FE,#$00
dc.b #$7E,#$0C,#$18,#$3C,#$06,#$C6,#$7C,#$00
dc.b #$1C,#$3C,#$6C,#$CC,#$FE,#$0C,#$0C,#$00
dc.b #$FC,#$C0,#$FC,#$06,#$06,#$C6,#$7C,#$00
dc.b #$3C,#$60,#$C0,#$FC,#$C6,#$C6,#$7C,#$00
dc.b #$FE,#$C6,#$0C,#$18,#$30,#$30,#$30,#$00
dc.b #$7C,#$C6,#$C6,#$7C,#$C6,#$C6,#$7C,#$00
dc.b #$7C,#$C6,#$C6,#$7E,#$06,#$0C,#$78,#$00
dc.b #$38,#$6C,#$C6,#$C6,#$FE,#$C6,#$C6,#$00
dc.b #$FC,#$C6,#$C6,#$FC,#$C6,#$C6,#$FC,#$00
dc.b #$3C,#$66,#$C0,#$C0,#$C0,#$66,#$3C,#$00
dc.b #$F8,#$CC,#$C6,#$C6,#$C6,#$CC,#$F8,#$00
dc.b #$FE,#$C0,#$C0,#$FC,#$C0,#$C0,#$FE,#$00
dc.b #$FE,#$C0,#$C0,#$FC,#$C0,#$C0,#$C0,#$00

;fill empty space

ORG $FFFA,0

;vectors

dc.w .interrupt
dc.w .start
dc.w .interrupt

;*END ASM FILE*

Chris Covell (ccovell@direct.ca)
http://mypage.direct.ca/c/ccovell/
Solar Wars Homepage!



Поделись статьей с друзьями


183 просмотров



Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: