Extended Nintendo Sound Format proposed NSFE File format ------------------------------------ Introduction: i) Why make a new format? ===================================== It does seem kind of silly to revise a format that not only already works well, but also has several apps supporting it. So my rationale for developing this format may be confusing. But I felt that in order for the features I had in mind, the existing NSF format just didn't do the job well enough. The previous format didn't allow a whole lot of room for expansion. In fact... according to the specifications, there were exactly 4 bytes available for future expansion. And since there was no indication as to the end of the music code aside from an EOF, appending data to the end of the file (and pointing to it via the 4 available bytes) would cause extra data to be loaded into the memory banks of NSF players. There was the option of increasing the version number of the NSF file, and then adding more room for expansion. However, that would demand all NSF players to update -every time- new features are added. As long as we're forcing a player update... we might as well do it only once... and make future expansion easier. Hence, this new format. Conversion between the two formats is a snap. You can easily convert all your NSFs to NSFEs and begin adding timers/playlists to your NSFs. And just as easily drop the added info and convert back to a regular NSF. ii) Differences between NSFE and NSF ===================================== Aside from the increased capability of future expansion, NSFE provides several options for the user that makes NSFs flow more like other game music files (SPC, PSF, etc). The most notable are: - Optional timers for songs - Optional Fade-out time - Able to arrange the tracks in a playlist.. allowing you to omit tracks and play tracks in the order of your choosing. - Labels per track, makes it so you don't have to remember your fav track on the NSF by number. Format Specifications: 1) Type definitions and keywords ==================================== Throughout the doc I'll use a few keywords/symbols defined here: 0x#### = Numbers preceeded by '0x' are represented in hexadecimal BYTE = a single byte (8 bits in size), representing unsigned data UINT = an unsigned integer.. 4 bytes in size, stored low byte first: 08 06 02 FF = 0xFF020608 int = a signed integer.. 4 bytes in size, stored low byte first WORD = an unsigned integer.. 2 bytes in size, stored low byte first string = a string of text, terminated with a NULL character (zero), typically, strings shouldn't be any longer than 32 chars, but there's technically no limit (though exceedingly long strings will probably be truncated by players). 'TEXT' = ASCII representation of a UINT. First character is stored as the first byte: 'data' = 64 61 74 61 = 0x61746164 d a t a a t a d This is a little backwards from standard C++ notation, where 'data' would really equal 0x64617461... but when written to a file with the low byte first, it would appear backwards thus the reason for the reversal. 2) The General File Format ====================================== The header to NSFE files is very small.. just a single UINT: 'NSFE' Following that 4 BYTEs is a series of data chunks. The file must contain at the very minimum the following chunks: 'INFO' - must be before 'DATA' chunk 'DATA' 'NEND' - must be the very last chunk There may be extra chunks included... but these are the bare minimum that must be in every file. The extra chunks may or may not be required to be placed before/after certain chunks; it depends on the chunk. See the specific chunk formats for details. Once the 'NEND' chunk is reached... the entire NSFE file has been read. No chunks should appear after the 'NEND' chunk. You may notice that NSFEs lack a version number. This is because the chunk system makes a version number obsolete. 3) The Chunk format ===================================== Data in NSFEs are stored in a chunk format, similar to WAV, PNG, and many other common files. Each chunk contains the following data: UINT size of chunk in bytes (does not include this value or the chunk ID) UINT chunk identifier ... chunk data (size is determined by the value given above) The data is interpreted in different ways depeding upon the chunk ID. The chunk IDs also follow a certain naming convension: If the first byte of the id is between 0x41 and 0x5A (in ASCII: A to Z), then the chunk is MANDITORY for NSF playback. If the program comes across a chunk it doesn't recognize, and the first byte is within that range. Program should not attempt to play the NSF. However, if the value is anything else, the chunk is optional and can be skipped if unsupported. Examples: 'test' - optional 'TEST' - manditory 'tEST' - optional 'Test' - manditory If a chunk size is larger than expected, extra bytes can be ignored. This allows for future expansion by adding values to specific chunks without having to create a new chunk. If a chunk size is smaller than expected, default values can be used to fill unfilled variables. However, there are a few varialbes that are manditory for playback. See the next section for details. 4) Specific Chunk Formats ======================================= As of right now... there are 9 chunk types defined. Each chunk is identified by its 4-byte ID: Must be in every NSFE file: 'INFO' 'DATA' - must come after 'INFO' 'NEND' - must be the very last chunk May or may not be in every NSFE file: 'plst' 'time' - must come after 'INFO' 'fade' - must come after 'INFO' 'tlbl' - must come after 'INFO' 'auth' 'BANK' Aside from the restraints placed on specific chunks... the chunks can be listed in any order. As of right now... you cannot have multiple occurances of the same type of chunk. Future chunks may arise that can be listed mutiple times, however. Now for the nitty-gritty on each chunk: ---- 'INFO' chunk ---- This chunk MUST BE AT LEAST 8 bytes in size... enough to supply data up to the "Number of Tracks". The rest of the data can be assumed if it's missing. WORD Load Address WORD Init Address WORD Play Address BYTE PAL/NTSC BYTE Extra sound chip support BYTE Number of Tracks BYTE Initial Track WORD Speed (in 1/1000000th sec ticks) Load Address = the area in ROM, in which to load the NSF data. Should be between 0x8000 - 0xFFFF Init Address = address of the code that's run upon initialization of each track Play Address = address of the code that's called to play each track PAL/NTSC = Bitwise representation of whether the NSF is PAL or NTSC if bit 0 is set -> NSF is PAL if bit 0 is clear -> NSF is NTSC if bit 1 is set -> Ignore bit 0, NSF is a dual PAL/NTSC bits 2-7 -> Unknown. Should be zero to allow for future expansion. Escentially... this bit specifies what should be loaded into the X Register upon loading the song. Extra Sound Chip support = Bitwise representation of additional sound chips if bit 0 is set -> NSF uses VRCVI if bit 1 is set -> NSF uses VRCVII if bit 2 is set -> NSF uses FDS Sound if bit 3 is set -> NSF uses MMC5 audio bits 4-7 -> Not used... should be 0 to allow for expansion See nsfspec.txt for more details. Number of Tracks = The number of tracks contained in the NSF. Must be at least 1. (Assume 1 if data doesn't exist) Initial Track = The first track to play upon playing the NSF. Unlike the NSF format, this number is zero-based. So 0 = 1st track, 1 = 2nd track, etc. (Assume 0 if data doesn't exist) Speed for NTSC = The speed of playback for NTSC. See nsfspec.txt for details. (Assume 0x411A). The same value is shared for both NTSC and PAL versions (there were 2 different entries in the NSF format but one entry was always ignored.) Remember... if the data chunk was larger than expected... just ignore the rest. More data may be added at a later date. ---- 'DATA' chunk ---- This chunk contains the 6502 music code. It should be loaded into ROM at the address specified by the Load Address in the 'INFO' chunk. ---- 'NEND' chunk ---- This chunk signals the end of the file. It should have a size of 0. ---- 'plst' chunk ---- The plst (playlist) chunk specifies which order to play the tracks.. and what tracks to play. Each byte in the chunk is the zero-based index of the song to play. Once that song completes (or is skipped), the program should start the next song on the list. When all the songs on the list have been played... the program should stop (thus, songs can be omitted from playback if they're not on the list. So it may be a good idea for the user to be able to turn off the playlist option instead of having to alter their list all the time). If a playlist exists... the Initial Track value specified in the 'INFO' chunk is ignored. ---- 'time' chunk ---- The time chunk specifies the length of the song to be played. It's stored as a list of ints. Each int is the number of milliseconds for the song to play. If the value given is less than 0, the song should play indefinatly. The first int in the list is the value for the first track in the NSF -not- the first track in the playlist. If not all tracks are included in the chunk, they are assumed to have a time less than 0 (however, a program may choose to have the user select a default play time in that case... or in the case of an absense of a time chunk). ---- 'fade' chunk ---- Very similar to the 'time' chunk, the 'fade' chunk is the length of the fade-out associated with each track. The fade comes AFTER the play time... so if the play time is 30 seconds, and the fade time is 1 second... the true length of the music played will be 31 seconds. Data is stored just like the 'time' chunk: a list of ints for each track, representing the length of the fade in milliseconds. ---- 'tlbl' chunk ---- The tlbl (track label) chunk contains a series of null-terminated strings, which are used as the names of each individual track. If this chunk is absent, or if not all tracks are represented, they are assumed to have no label.. or some sort of generic lable (like "Track 05"), however the program wants to interpret it. ---- 'auth' chunk ---- The auth (author) chunk contains a few strings that holds information about the entire NSF in general (instead of by each specific track): string Name of the Game string Name of the Artist string Name of the Copywrite holder string Name of the NSF ripper Remember that not all 4 strings may be included... the chunk might stop after the 3rd string. ---- 'BANK' chunk ---- The BANK chunk contains bankswitching init values. For more details, see nsfspec.txt. This chunk (if exists) should be 8 bytes in size. If less, it can be assumed that the bytes are 0. Likewise, if the BANK chunk is absent, it's assumed that all 8 bankswitching values are 0. 5) Expanding the file format ======================================= If people are ever willing to add other stuff, like interpolation, subbing square/triangle channels with other samples, etc... extra chunks can be added at will. Just follow the format given in section 3 to allow for backwards compatibility with NSFE players. 6) Credits! ======================================= Kevin Horton - for developing the NSF format (on which this format was based) All the people who went to all the trouble of reverse engineering the 2A03 so that NSFs were possible. Disch - for the quick typeup of this format ^_^, who greatly hopes it will one day catch on.