At this point, we know where on the cartridge the tapes for Snake Rattle 'n' Roll songs are located. Let us look now at the format of this data. At a high level, a track's tape is a list of messages, terminating with an END OF TRACK message, which is encoded with a single byte of value $00.
A simple song would just be a list of NOTE messages and REST messages. Note messages are encoded starting with a byte in the range [$81, $BC]. The byte $81 corresponds to pitch C2, increasing up to byte $BC, which is the pitch B6 (giving a five octave range). Rest messages are encoded starting with byte value $80.
Both note and rest messages are usually followed by an additional byte, giving the length of the note or rest in terms of 'updates' (I say 'usually' here because there is an exception, noted below). The audio module is temporally synchronized with the frame rate of the NES, 60 Hz1. In each frame, only two of the four channels are updated: the pulse channels are updated on even numbered frames, while the triangle and noise channels are updated on odd numbered frames. This means that a note or rest whose length is given as $1E (30) would usually play for an entire second. (Again, I say 'usually' because there is another exception, noted belower.)
The first exception listed above concerns the encoding of a note or rest message. In a song, it is not uncommon to have a long run of notes or rests which share a common length. In these cases, it would be wasteful to encode each note with two bytes for its pitch and length. The audio module has a SET LENGTH OVERRIDE message for this use case. The set length override message is encoded with two bytes: the first has value $06, and the second byte specifies the shared length of the following notes. While the length override is set, the audio module will use its value whenever it encounters a note or rest message, instead of attempting to read a second byte to get the length. The length override can be cleared by a set note length override message specifying a length of zero. For convenience, there is also a CLEAR LENGTH OVERRIDE message, encoded with byte value $07, which does the same thing but does not require a second byte.
In addition to the note length override, there is a second compression technique available for tape data. It is common in NES-era music to have repeated sections of music. The tape data format has a control message, START LOOP, which instructs the audio module to play another section of the tape one or more times. This loop section is terminated by the STOP LOOP control message, encoded with byte $05.
There are actually two different ways that the START LOOP message can be encoded. The long format is four bytes: byte value $04, the number of loop iterations, the low address byte, and the high address byte of the start of the loop section. The short format is three bytes: a byte value in the range [$17, $7F], the low address byte and the high address byte of the start of the loop section. In the short format, the number of loop iterations is the value of the initial byte, minus $16.2
Loops are very common in the Snake Rattle 'n' Roll songs. Most of the track tapes are little more than a dozen or so loop messages. One cautionary note to developers, though: you are doubtlessly used to being able to nest subroutine calls to an arbitrary depth. The audio module does not support this. It has exactly one nesting level, and for a given update call it is either playing the 'main' section of the music or a 'loop' section. You cannot call a loop within a loop. Aside from this, any message that is valid in the main section of a tape is valid in a loop section of a tape.
The second exception listed above concerns the tempo of a song. The normal mode in the audio module for a track is thirty updates per second, but it is also possible for a song to specify a lower update rate by skipping occasional updates. The details of how the audio module decides which updates to skip will wait until we dive into the source code, but for know, it is enough to know that you can specify a number x in the range [$0, $7F] and for every $80 update calls, only x are actually performed. The skipped updates are evenly spaced among the $80 update calls; at least, as evenly as they can be up to the precision of integer division. In Snake Rattle 'n' Roll, only two tempo values are used, besides maximum tempo of $80: $68, which keeps 81.25% of updates, and $6A, which keeps 82.8125% of updates. These tempos are stored in a list at address $D621, according to the following table:
Song # | Song Name | $D621 | Tempo |
---|---|---|---|
0 | Level 1 | $80 | Full |
1 | Levels 5 and 8 | $68 | 81.25% |
2 | Levels 6, 9 and 10 | $6A | 82.8125% |
3 | Level 2 | $6A | 82.8125% |
4 | Level 3 | $83 | Full |
5 | Levels 4 and 7 | $6A | 82.8125% |
6 | Time Out | $80 | Full |
7 | Game Over | $80 | Full |
8 | Main Titles and End Credits | $80 | Full |
This strategy for slowing down the tempo of the song is a bit strange at first glance: at random, some of the notes with a given length may be stretched to be one note longer, because one of their update calls were switched. However, because the maximum update rate is 30 Hz, you don't actually notice that notes which should share a common length are not the same length. The effect is also less pronounced because the skipped updates are at uneven intervals.
To summarize: we now recognize the following messages encoded in a tape data stream:
Message | Encoding |
---|---|
END OF TRACK | [$00] |
START LOOP (long format) | [$04] [loop count] [address low] [address high] |
END OF LOOP | [$05] |
SET LENGTH OVERRIDE | [$06] [length] |
CLEAR LENGTH OVERRIDE | [$07] |
START LOOP (short format) | [$17,$7F] [address low] [address high] |
REST | [$80] [length?] |
NOTE | [$81,$BC] [length?] |
There are no messages with an initial byte above $BC; these would be interpreted by the audio moduel as a note message, but it would overrun the list on the cartridge containing the pitch periods and would play with seemingly random pitches. That just leaves eighteen other messages, starting with bytes in the range [$01,$16]. We will examine these messages in the next post.
- Technically, it is actually 60.1 Hz, but what's a tenth of a Hertz between friends?
- I am not actually sure why there were two encodings for the start loop message. I suspect that the long form was invented first, and that the short form was added later when the developers had to squeeze a few more bytes of space out of the cartridge. This makes me wonder why they didn't just remove the long format (and its machine instructions) entirely. There is one song still in Snake Rattle 'n' Roll that uses the long format, looping $50 times. This is still within the loop count that the short format can encode.
No comments:
Post a Comment