NSF export format

This document is the reference for the .nsf files SampleToNES writes: the file a console or an NSF player loads, and the song block inside it that the player's own 6502 driver reads. Read it before changing how an .nsf is written or read. The player explains why a song is stored this way and how the driver is held to it. Song compression explains the compression scheme. The layout is here.

An .nsf differs from the tracker exports beside it. A FamiTracker or Bitphase file describes a song to a program that already knows how to play one, and an .nsf carries its own player. The file has three parts: a header, the assembled driver, and the song the driver plays. The header names where the program loads and which routines the console calls.

Every constant named here has a counterpart under sampletones_player/specification/.

▍A. The file

+0                      NSF header, 128 bytes
+128                    the assembled driver, loaded at $8000
+128 + driver length    the song block, at the address the header states

The header is NSF version 1:

Offset Size Field Value
0x00 5 magic NESM\x1a
0x05 1 version 1
0x06 1 song count 1
0x07 1 first song 1
0x08 2 load address where the driver loads
0x0A 2 init address the driver's init
0x0C 2 play address the driver's play
0x0E 32 title UTF-8 text ending in a NUL
0x2E 32 artist as the title
0x4E 32 copyright as the title
0x6E 2 NTSC play period microseconds per video frame
0x70 8 bankswitch zeros
0x78 2 PAL play period 20000 microseconds
0x7A 1 region 0, NTSC
0x7B 1 expansion chips 0, none
0x7C 1 NSF2 features 0
0x7D 3 NSF2 length zeros

Each text field holds STRING_TEXT_SIZE bytes of text, cut on a character boundary.

The driver's image starts with two jumps, the entry points. init is at the load address and play is three bytes later, whatever the driver's own length. The header can therefore give both addresses without assembling anything. The song follows the code directly. That address is the one thing a build decides.

The console calls init once and then play once per video frame. The header asks for the NTSC frame period. The song's speed does not depend on the header's play period.

The program area is 32 KB, from $8000 upward, and the song block gets what the driver leaves of it. A song that outgrows that space is reported as an export failure, and no shortened file is written.

▍B. The song block

A song reaches the console as one token stream per plane. The driver decodes each stream a tick at a time against a dictionary of phrases and a timer table of pitches. Every channel has a control plane and a value plane, and a tone channel has a bend plane besides. Every offset below is a uint16 counted from the block's first byte, so the whole block plays from wherever the file loads it.

+0      header
+51     timer table
        phrase table:  count, then one offset per phrase
        phrase bodies: each a length byte, a count byte, then its values
        one token stream per plane, in plane order

B.1 The header

Offset Size Field
+0 1 ticks each play call advances by, whole part
+1 2 the same step's 16-bit fraction
+3 2 the ticks the song lasts
+5 2 the tick the song returns to, or $FFFF where it stops there
+7 2 where the timer table begins
+9 2 where the phrase table begins
+11 PLANE_COUNT×2 where each plane's stream begins, or $FFFF for an absent plane
+31 PLANE_COUNT×2 where each plane's stream is re-entered once the song comes round, or $FFFF for an absent plane

All fields are little-endian, and the header runs to SONG_HEADER_SIZE bytes.

The step lets one data set play at every rate. A reconstruction advances its envelopes at the rate it was built at, and the console calls play at the video frame rate. The step is the first rate measured against the second, held as a whole byte and a 16-bit fraction. The driver adds it to an accumulator on each call and advances the streams by the whole ticks that fall out. A song slower than the play rate stands still on the calls between its ticks, and a faster one advances several.

B.2 The timer table

The table has the timer register value each pitch sounds at: the low byte of each pitch in pitch order, then the high byte of each. One pointer reaches both halves, which the driver's lookup uses.

A plane names a pitch as its index, the distance above the lowest pitch the tuning covers, and not as a divider. A tick's divider is written as the index of the pitch it is counted from, beside a bend: the steps from that pitch's own divider (section C). Pitches beyond the divider's range share the timer they clamp to, and the lowest pitch sounding a timer represents the whole group.

The table comes from the tuning the exported work was built at, computed by the function the reconstruction's own generators render from.

B.3 The dictionary

count               1 byte, how many phrases the table holds
offsets             one uint16 per phrase, in id order
bodies              each phrase: a length byte, a count byte, then its values

A phrase is a run of values a plane plays, stored at the pitch it was found at. Its position in the table is its id. The ids that fit inside a token's opcode are the cheap ones, so the phrases a song relies on most are listed first.

A phrase states the count its tokens play it at most often. The count byte holds that count less one, the way a token states one. A token playing the phrase at that count names the phrase alone and spends a byte fewer.

A song's phrases come from two places: the samples it plays, each offering the planes it writes, and a search over whatever those leave uncovered. Both are weighed the same way. A phrase keeps its entry when it saves the streams more bytes than the entry costs.

B.4 The token streams

Each plane is a byte sequence of tokens. The opcode's top two bits name the kind and the low six carry its operand:

00cccccc                 hold the value the plane reached, for c+1 symbols
01nnnnnn b0..bn          the n+1 symbols that follow, one each
10dppppp cccccccc        phrase p, for c+1 symbols
11dppppp cccccccc tt     phrase p, for c+1 symbols, every value plus tt

p == $1F is an escape: the phrase's id is the byte that follows. That reaches every id in the table while the low ids stay a byte cheaper. The shift tt is added within the byte and wraps. That is one addition on the 6502, and the encoder does the same addition.

The d bit says the count is the phrase's own. A token carrying it names the phrase and no count, and plays the count the table states for that phrase. The count byte cccccccc is then absent. That bit is what leaves five bits for an id, so a phrase opcode names ids up to $1F outright where a hold or a literal counts to $3F.

A token's count is a duration, and it may run past the phrase. Past its last value the plane holds that value, so a note whose envelope has finished keeps sounding. A count shorter than the body cuts the note off. One entry therefore serves every length a figure is played at and, with the shift, every pitch.

A token counts symbols. On a plane whose byte carries a repeat count (§C) one symbol covers as many ticks as it states, so a token's reach in ticks is the ticks its symbols carry between them. On every other plane a symbol is a tick.

B.5 Where a song comes round

A song that repeats re-enters its streams partway through. The tick it returns to therefore begins a symbol, and a token, on every plane, and that token names its values outright instead of relying on the value the plane had reached. A bend plane is re-entered at the value its channel's flags have reached by that tick (section C), which begins a token of its own. Coming round then means pointing each plane at the byte the header names and clearing what it was playing.

The tick a song returns to is the export's choice: its first tick, the first tick of an order frame, or none. With none, the header has $FFFF and the song stops at its end.

▍C. What the planes hold

The planes are written in this order, and each group belongs to one channel:

Plane Carries Reaches
pulse 1 control duty cycle and volume, under a repeat count $4000
pulse 1 value pitch index, and a flag for a bent tick $4002, $4003
pulse 1 bend divider offset of each flagged tick $4002, $4003
pulse 2 control duty cycle and volume, under a repeat count $4004
pulse 2 value pitch index, and a flag for a bent tick $4006, $4007
pulse 2 bend divider offset of each flagged tick $4006, $4007
triangle value pitch index, a flag for a bent tick, and the index that rests $4008, $400A, $400B
triangle bend divider offset of each flagged tick $400A, $400B
noise control volume, under a repeat count $400C
noise value period and mode, around a repeat count $400E

Splitting a channel's registers apart gives each plane something to repeat: a volume envelope and a pitch line are separate series that turn over at their own rates.

Every channel's planes are in every block. A channel an export leaves out has its silent values from the first tick to the last, which a hold covers in a few bytes. The driver therefore reads the same layout whichever channels a song sounds.

A plane counts its repeats in the bits its register ignores. A pulse control byte holds a duty cycle and a volume around two bits the hardware wants set. A noise control byte holds a volume under the same two. A noise period byte holds a mode bit above three the register reads nothing from. Those spare bits carry the ticks the value repeats for, so a run costs one byte however long it lasts. The register byte is the symbol masked and ored with the bits the hardware fixes, and counting one repeat off subtracts COUNT_STEP. Every other plane spends its whole byte and reads a symbol a tick. The division follows from the plane, so the block states none of it.

A plane standing at the value it is seeded to throughout is absent. The driver seeds every plane before its first token: to the bits its register fixes where it has any, and to the index that rests on the triangle's value plane. A plane that never leaves that value takes no stream, and both its header entries are ABSENT_STREAM ($FFFF). A tone channel that never bends spends nothing on its bend plane, a pulse or noise channel that never sounds spends nothing on its control plane, and a song without a triangle spends nothing on its value plane.

The triangle names its silence in the pitch it plays. The channel sounds at one level, so a tick states whether it sounds through the index its value plane names. The index standing above every pitch the table covers silences the linear counter, and the divider stays where the channel last sounded. That is a whole plane the block does without.

The noise channel has no bend plane. It selects one of sixteen fixed periods, so there is no finer grid for a bend to reach, and its block leaves the plane out.

A timer's high half reaches the register only where it differs from the last one written. Storing it restarts a pulse waveform and reloads the triangle's counter. A channel holding one pitch across a rest therefore keeps its phase running, as a rendered channel does.

A value plane names a pitch, not a divider. A note is written as its distance above the lowest one the song reaches. That lets TRANSPOSED_PHRASE move a whole phrase by adding to it, and a divider offset added to an index would mean nothing. A tone channel's bend plane carries the offset: signed bytes in two's complement, added to the divider the value plane's note resolves to.

A bend plane has a value only where a note bends. A value byte's low seven bits index the pitch table. Its top bit, BEND_FLAG, says the tick reads its offset from the bend plane, and an unflagged tick sounds its pitch's own divider. The bend plane has one value per flagged tick, in order, and the driver advances it on those ticks alone. The encoder flags each note from its first bent tick to its last, so a vibrato passing through zero keeps the value plane still. A channel that never bends has an empty bend plane, which is an absent one. The flag sits above every index, so a transposed phrase keeps it.

A bent frame sounds the divider its note's own divider is moved to. The value plane names the frame's own note, and the bend plane holds the steps from that note's divider. A row transposes a note and keeps its bend's steps, so a bent figure has the same bend bytes at every pitch it is played at, and one dictionary entry serves it. A bend past the signed byte is counted from the pitch lying nearest the divider, and a divider halfway between two pitches goes to the higher one. Those steps stay inside half the widest gap between neighboring pitches, 57 at the default tuning, so every divider the register holds reaches the planes.

The driver sign-extends that byte and adds it across both halves of the timer. That addition, and the repeat a symbol counts down, are the whole of the arithmetic it performs on a song's behalf. Python does everything that makes the sum land in range: bent_timer keeps the divider within [MIN_TIMER, MAX_TIMER], the rule the generators render a bent frame by. The timer's high half therefore never exceeds three bits, never reaches the length-counter field beside them, and never collides with the $FF the driver marks an unwritten shadow by.

▍D. Limits

Limit Value
Program area 32 KB from $8000, less the driver
Song length as many ticks as the streams fit in
Offsets within the block uint16
Symbols one hold covers 1–64 (MAX_HOLD_TICKS)
Symbols one literal covers 1–64 (MAX_LITERAL_BYTES)
Symbols one phrase token covers 1–256 (MAX_PHRASE_TICKS)
Ticks one symbol covers 1 to the plane's own count, 16 at the widest
Values one phrase holds up to 255 (MAX_PHRASE_LENGTH)
Phrases one dictionary holds up to 255 (MAX_PHRASE_IDS)
Phrase ids a token's opcode names up to 31 (CHEAP_PHRASE_IDS)

A song that reaches past the space behind the driver, or past what an offset field can hold, raises SongTooLargeError naming the part that overflowed. A project that offers more phrases than a dictionary holds keeps the ones that spare the streams most, and says so.