NSF2 is an extension of the NSF file format, first publicly implemented in 2019, with the following additions:
- Backward compatibility with the original NSF format where the new features are optional.
- Extensible metadata through incorporation of the NSFe format, of both optional and mandatory varieties.
- A programmable timer IRQ, as well as explicit access to the existing DMC and Frame Counter IRQ devices.
- An alternative non-returning INIT paradigm for playback.
- All strings contained in NSF2 should be encoded in UTF-8 format.
This header is the same as the original NSF header, but with the following additions:
offset # of bytes Function ---------------------------- $005 1 BYTE Version number, equal to 2 $07C 1 BYTE NSF2 feature flags bits 0-3: reserved, must be 0 bit 4: if set, this NSF may use the IRQ support features bit 5: if set, the non-returning INIT playback feature will be used bit 6: if set, the PLAY subroutine will not be used bit 7: if set, the appended NSFe metadata may contain a mandatory chunk required for playback $07D 3 BYTES 24-bit length of the NSF program data, allowing metadata to follow the data
If a non-zero value appears in $7D-7F, NSFe metadata may be appended to the file immediately following the end of the data.
Non-mandatory metadata is available even in the original version 1 NSF by using these bytes to indicate a data length, but older players will assume that this metadata is part of the program ROM. Since NSF playback is mostly deterministic, it's reasonable to ensure that misinterpreted appended data isn't used by the NSF and won't harm playback. For this reason if the metadata is optional information (e.g. track names), and no other NSF2 features are required, it may be preferable to leave the version as 1.
If the file version is 1, any flags in byte $7C should be ignored.
Using this bit implies a vector overlay provided by the player at $FFFA-$FFFF. The NMI and Reset vectors at $FFFA and $FFFC are reserved for the player implementation (see: #Non-Returning INIT) but the IRQ vector at $FFFE-$FFFF will be provided as RAM. This is an overlay on top of any underlying NSF data, so the NSF program must explicitly write a vector to $FFFE before enabling interrupts.
The first time INIT is called, the IRQ inhibit flag will be set (SEI), and the IRQ timer device will be set to inactive. The NSF program will be responsible for enabling interrupts (CLI). The NSF player will never change the IRQ flag directly, though if used in combination with the non-returning INIT feature the PLAY routine will have an implied SEI via being called from an NMI (see below).
A cycle counting timer device will be supplied with three readable and writable registers:
$401B R/W - Low 8 bits of counter reload. $401C R/W - High 8 bits of counter reload. $401D W - Activate with bit 0 set, deactivate with bit 0 clear. $401D R - Acknowledges IRQ. Bit 7 returns IRQ flag before clearing it. Bit 0 returns active status.
If active, every cycle the counter will be decremented. When the counter goes below 0 it will enable its IRQ flag, and be reloaded with the value given at $401B/C.
If inactive it will instead reload the counter on every cycle.
The IRQ line will be asserted whenever the IRQ flag is set as the counter underflows, until it is acknowledged by reading $401D.
Bit 7 read from $401D might be used to distinguish between different IRQ sources, if this is required.
The period of this timer with a reload value of N will repeat every N+1 cycles. A reload value of 0 means it will repeat every 1 cycle.
The automatic reload allows an IRQ to repeat at a dependable interval of cycles, minimizing jitter.
This bit changes the method of playback to a paradigm where INIT is allowed to run indefinitely, and PLAY will interrupt it as an NMI:
- The INIT routine will be called twice.
- The first call of INIT will be as NSF, but additionally the Y register will contain $80. This call must return.
- NMI is be enabled.
- A second call of INIT, with the same 'A' and 'X' as the first call, but Y will now contain $81. This call does not have to return.
- PLAY will be called by an NMI wrapper, interrupting the still running INIT function.
The NMI wrapper is implementation-defined, and part of the player. PLAY will not be called directly by the NMI vector, and must end with an RTS, not an RTI.
The NMI wrapper will be responsible for saving and restoring A,X,Y. When PLAY is called, there is an implied SEI by the enclosing NMI, which should be considered if also using the IRQ feature. The NMI wrapper should disable and re-enable the NMI signal to prevent re-entry if PLAY runs long.
After PLAY returns, the NMI wrapper may also want to do additional things like check user input or update its UI. An ideal NSF2 player should keep this wrapper as minimal as possible, but for wider compatibility the NSF program should not rely on any specific timing for this.
As with the IRQ feature, this also requires a vector memory overlay at $FFFA-FFFF. This replaces any data from the NSF program at that location, and the specific vectors for $FFFA (NMI) and $FFFC (Reset) are reserved to be provided by the player.
The second INIT allowed to return, in which case the player should fall back to its own infinite loop. (INIT will not be called a third time.)
A Y value other than $80 or $81 can be interpreted as a player that doesn't support this feature, which may be used to create a fallback for compatibility. No existing pre-NSF2 players are known to have used these values for Y on INIT.
If this bit is set, the PLAY routine will never be called. This is mostly intended for use with the non-returning INIT feature, allowing it to continue uninterrupted.
Hardware player implementations may reserve the need to still execute brief NMI interruptions during non-returning INIT, even if suppressed, to allow its player program to remain responsive to user interaction. Ideally, however, the non-returning INIT should not be interrupted at all if PLAY is disabled.
This bit does not by itself imply NMI-driven PLAY, even though the non-returning INIT does.
Optional metadata may be appended immediately following the data. It appears at a file offset of the 24-bit data length at $7D-7F (little-endian) plus $80 for the length of the NSF header.
It is identical to the format described at NSFe, but with the following differences:
- There is no 'NSFE' fourCC at the start of the NSF2 metadata. The first byte begins the first contained chunk header.
- Chunks that are already part of the NSF format must not be included. 'INFO', 'DATA', 'BANK' and 'NSF2' chunks should not be used.
The 'RATE' and 'regn' chunks, while partially redundant, may be included to provide additional Dendy region playback information. The fields in the NSF header can supply a backward compatible fallback for this case if the metadata is not parsed.
If bit 7 of header byte $7C is set, parsing the metadata becomes mandatory. This means that the appended metadata contains a mandatory chunk, indicated by a fourCC of capital letters, that must be parsed and understood by the player to be able to play back the file.
The mandatory bit is intended for cases where extra information may be needed for correct emulation. Examples:
- A 'VRC7' chunk can be used to substitute VRC7 with the related but not compatible YM2413 chip.
- A chip with embedded sample data could be provided in an appropriate chunk.
This mandatory indication allows future expansion to the format without having to redefine the NSF header.
The following implementations of NSF2 exist:
- NSFPlay 2.4 beta 9 (2019-3-1)
Though the ideal NSF2 player will not be encumbered by these problems, the are a few suggestions for NSF programs for increased compatibility with potential hardware players that might not be able to provide all features:
- For non-returning INIT use a standard PLAY rate specified in the header. Hardware players may not be able to adjust their NMI timing.
- If using IRQ or non-returning INIT, do not bankswitch $F000. This allows the vector overlay to be applied directly to the NSF data on load instead of requiring an extra decoder.
- Some NSF players already have some IRQ support. Placing the IRQ vector at $FFFE as well as writing to it can be compatible with both NSF1+IRQ and NSF2.
- The older NSF1 had no specification for string encoding. UTF-8 is the standard for NSF2, but for backward compatibility only ASCII should be used for the fields in the header. An NSFe 'auth' chunk can be used to override the header's title/author/copyright with UTF-8 strings.
The vector overlay is not implied by the header version 2, it will only be used if either the IRQ or non-returning INIT features are specified in byte $7C.
- nes-audio-tests - Test NSF files for NSF2 features
- nsfe_to_nsf2.py - Tool for converting NSFe files to NSF + Metadata
- nsf2_strip.py - Tool for stripping NSFe metadata from NSF
- 2018 forum discussion - rainwarrior's 2018 NSF2 proposal
- NSF 2.0 forum discussion - Kevtris' 2010 NSF2 proposal