Wednesday, January 4, 2017

D&D Stat Roles

If you have played any incarnation of D&D, you are no doubt familiar with the ritual of generating the vital statistics defining your character:

  • take four D6s, roll them, throw away the lowest one, and sum up the remaining three
  • repeat the above five more times, giving six numbers
  • assign these numbers among strength, fortitude, dexterity, intelligence, wisdom, and charisma

In this post, we will examine the question: what are the odds of a given stat number being rolled?

The answer is trivial to produce for a competent programmer, and is as follows:

Stat rollOdds
3 1 in 1296
4 4 in 1296
5 10 in 1296
6 21 in 1296
7 38 in 1296
8 62 in 1296
9 91 in 1296
10122 in 1296
11148 in 1296
12167 in 1296
13172 in 1296
14160 in 1296
15131 in 1296
16 94 in 1296
17 54 in 1296
18 21 in 1296

This isn't a very satisfying answer, though. Why is the table the shape that it is? Compare the table above to the following table, which is the odds of 3D6 summing to a given number, with no throwing away of any dice:

Stat rollOdds
3 6 in 1296
4 18 in 1296
5 36 in 1296
6 60 in 1296
7 90 in 1296
8126 in 1296
9150 in 1296
10162 in 1296
11162 in 1296
12150 in 1296
13126 in 1296
14 90 in 1296
15 60 in 1296
16 36 in 1296
17 18 in 1296
18 6 in 1296

The distribution for 4D6 with a dropped die is more complex because the final sum depends critically on which die is the lowest (and thus thrown away), but the odds that a given die is the lowest is dependent on the value rolled on the other three dice.

To be specific: say you rolled the first die, and it came up a 1. In this case, you would know that this was the lowest roll, and could consider the odds of the final sum assuming that that die was thrown away. If the first die rolled a 6, you would know that this was NOT the lowest roll (at the worst it was tied), and could keep it, and odds of the final sum could be the odds of 3D6 were rolled, and one was thrown away, and you added six to that total. If, however, the first die was any other number, you really don't know very much. You have to roll the others to see.

To make headway on our problem, we need to separate these dependencies into a set of disjoint, indepenent possibilities. We do so by observing two facts:

  • For any 4D6 roll, exactly one is true: the first die is lowest, the second die is lowest, the third die is lowest, or the fourth die is lowest
  • For any 4D6 roll, exactly one is true: the lowest die is a 1, the lowest die is a 2, the lowest die is a 3, …, or the lowest die is a 6

One more fact is required, to handle the case where the lowest number is rolled more than once (for example the roll {3,1,1,6}). In such a case, the lowest die is the first one with the given lowest roll.

Convince yourself that every 4D6 roll will fall into one of these 24 buckets:

Lowest rollLowest die
1st2nd3rd4th
1
2
3
4
5
6

Let us begin to fill in the table:

  • What are the odds that the lowest roll is a 6?
    • Clearly, this will only happen if all four dice roll 6, and this can only occur once.
  • What are the odds that the lowest roll is a 5?
    • This occurs if all four dice roll 5 or 6, which can occur 2^4 = 16 times. However, we must subtract one, since we have double-counted the case where all four dice roll 6.
  • What are the odds that the lowest roll is a 4?
    • By a similar argument, this will be 3^4 - 2^4 = 65
We continue this line of reasoning, and are able to conclude that:
Lowest rollLowest dieRow sum
1st2nd3rd4th
1671
2369
3175
465
515
61

We don't yet know what is in each bucket, but we can sum the rows of the buckets.

Now, consider the case where the lowest roll is a 1:

  • What are the odds that it is the first die that rolls 1?
    • To do this, the first die must roll 1, and then the other three dice rolls can be anything. They thus fit the pattern [1][*][*][*] (where * denotes 'any'). This has 1*6*6*6=216 occurrences.
  • What are the odds that it is the second die that rolls 1?
    • Recall the rule that the lowest die is the first with a given value. For the second die to be the lowest, and have rolled 1, the first die cannot roll 1. We thus have the pattern [2+][1][*][*], which has 5*1*6*6=180 occurrences.
  • What are the odds that it is the third die that rolls 1?
    • By a similar argument, rolls of this type fit the pattern [2+][2+][1][*], which has 5*5*1*6=150 occurrences.
  • What are the odds that it is the fourth die that rolls 1?
    • By a similar argument, rolls of this type fit the pattern [2+][2+][2+][1], which has 5*5*5*1=125 occurrences.

As a check of our reasoning, observe that 216+180+150+125=671. We can thus fill in the table as follows:

Lowest rollLowest dieRow sum
1st2nd3rd4th
1 [1][*][*][*] (216) [2+][1][*][*] (180) [2+][2+][1][*] (150)[2+][2+][2+][1] (125)671
2[2][2+][2+][2+] (125)[3+][2][2+][2+] (100)[3+][3+][2][2+] (80)[3+][3+][3+][2] (64)369
3[3][3+][3+][3+] (64)[4+][3][3+][3+] (48)[4+][4+][3][3+] (36)[4+][4+][4+][3] (27)175
4[4][4+][4+][4+] (27)[5+][4][4+][4+] (18)[5+][5+][4][4+] (12)[5+][5+][5+][4] (8) 65
5[5][5+][5+][5+] (8) [6][5][5+][5+] (4) [6][6][5][5+] (2) [6][6][6][5] (1) 15
6 [6][6][6][6] (1) 1

Note that the tiebreaking rule results in three of the buckets being empty.

"So what?" I hear you now ask. "You have taken one number and split it into the sum of twenty-one different numbers. How does this help?" I respond by first noting that the identity we have discovered is not at all obvious, and can be admired in its own right:

6*6*6 + 6*6*5 + 6*5*5 + 5*5*5 + 5*5*5 + 5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 6*6*6*6
        6*6*5 + 6*5*5 + 5*5*5 + 5*5*5 + 5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 6*6*6*5
                6*5*5 + 5*5*5 + 5*5*5 + 5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 6*6*5*5
                        5*5*5 + 5*5*5 + 5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 6*5*5*5
                                5*5*5 + 5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 5*5*5*5
                                        5*5*4 + 5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 5*5*5*4
                                                5*4*4 + 4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 5*5*4*4
                                                        4*4*4 + 4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 5*4*4*4
                                                                4*4*4 + 4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 4*4*4*4
                                                                        4*4*3 + 4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 4*4*4*3
                                                                                4*3*3 + 3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 4*4*3*3
                                                                                        3*3*3 + 3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 4*3*3*3
                                                                                                3*3*3 + 3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 3*3*3*3
                                                                                                        3*3*2 + 3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 3*3*3*2
                                                                                                                3*2*2 + 2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 3*3*2*2
                                                                                                                        2*2*2 + 2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 3*2*2*2
                                                                                                                                2*2*2 + 2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 2*2*2*2
                                                                                                                                        2*2*1 + 2*1*1 + 1*1*1 + 1*1*1 = 2*2*2*1
                                                                                                                                                2*1*1 + 1*1*1 + 1*1*1 = 2*2*1*1
                                                                                                                                                        1*1*1 + 1*1*1 = 2*1*1*1
                                                                                                                                                                1*1*1 = 1*1*1*1

The key point of the table, though, is that in all cases we have identified (and thus are able to discard) the lowest die. We can compute the odds of a given number being rolled directly from the remaining three dice.

Let us compute that table:

Lowest
value
Lowest
die
34 5 6 7 8 9 10 11 12 13 14 15161718
1 1st 13 610152125 27 27 25 21 15 10 6 3 1
1 2nd 1 3 6101520 23 24 23 20 15 10 6 3 1
1 3rd 1 3 61015 19 21 21 19 15 10 6 3 1
1 4th 1 3 610 15 18 19 18 15 10 6 3 1
2 1st 1 3 610 15 18 19 18 15 10 6 3 1
2 2nd 1 3 6 10 14 16 16 14 10 6 3 1
2 3rd 1 3 6 10 13 14 13 10 6 3 1
2 4th 1 3 6 10 12 12 10 6 3 1
3 1st 1 3 6 10 12 12 10 6 3 1
3 2nd 1 3 6 9 10 9 6 3 1
3 3rd 1 3 6 8 8 6 3 1
3 4th 1 3 6 7 6 3 1
4 1st 1 3 6 7 6 3 1
4 2nd 1 3 5 5 3 1
4 3rd 1 3 4 3 1
4 4th 1 3 3 1
5 1st 1 3 3 1
5 2nd 1 2 1
5 3rd 1 1
5 4th 1
6 1st 1
SUM 141021386291122148167172160131945421

Now that is a satisfying answer.

Friday, September 2, 2016

Snake Rock 'n' Roll: Sound Effects List

Thus far in the series, we have examined the data and workings of the audio module with respect to the background music. In this post, we will examine its support for the sound effects in the game.

There are forty sound effects in Snake Rattle 'n' Roll. Each sound effect is played by a single APU channel, and while it is playing, any song playing in the background is suppressed: the track data continues to be read, and the state of any active effects are updated, but the song's notes never get written to the APU channel. Although the audio module supports playing a sound effect on any of the APU's four channels, Snake Rattle 'n' Roll only plays sound effects on the pulse and noise channels, never on the triangle channel.

Sound effects, like the tracks of a song, are played from tapes. The format of the tape data for sound effects is much simpler than that for song tracks. A list of the available tapes for sound effects can be located at $DB94:

SFX ID$DB94,X\$DB95,XPurpose
$00FB\DBnone
$02FB\DBBigFoot death/Snake pop/Pibbly bomb
$0410\DCPibbly Chomp
$0619\DCPibbly Ejection
$082D\DCLid Opening
$0A34\DC?? Mystery Sound Effect??
$0C3F\DCPLAY ON/1-UP
$0E65\DCControl inverter pickup
$106E\DCTongue extender pickup
$127B\DCWormhole opening
$149E\DCWormhole sucking up object
$16AE\DCBigFoot stomp/-RGGG THUD
$18BE\DCScale bell ring
$1ADC\DCExploding enemy 1/2
$1CE5\DCExploding enemy 2/2
$1EEE\DCSnake OW
$20F7\DCInvincibility diamond pickup
$220E\DDSnake death spin
$2435\DDPibbly countdown (low)
$2627\DDPibbly countdown (medium)
$2843\DDPibbly countdown (high)
$2AE3\00Score rollup (pulse)
$2C51\DDScore rollup (noise)
$2EC6\DCJaws (slow)
$3063\DDARRRGGG-
$3274\DDPibbly chunk spit
$34B1\03Crescendo (game over screen, extra continue pickup)
$368E\DDExit door point scored
$389E\DDBounce/lick enemy
$3AA3\DDLick foot
$3CBD\DDDiving splash
$3ECF\DDWater jump jet
$40E5\DDTime extension pickup
$42EA\DDTime running out beep
$4454\DCTail fin pickup
$46FB\DDWind-up key pickup
$4806\DE?? Mystery Sound Effect??
$4AF3\DBRocket take-off
$4CE6\DBAsteriod Fall
$4E0D\DESnake gulp
$50D1\DCJaws (fast)

Some observations on this table:

  • The table is two bytes per entry, and lists the low byte\high byte of the address of the start of that sound effect's tape data
  • Sound effect ID $00 is not a real sound effect; it is just a sentinel value in the table.
  • There are two sound effects ($0A and $48) whose purpose is unknown to me. I could not find any action in the game that caused them to play, and when I hacked the ROM to play them in place of other sound effects, I did not recognize them.
  • There are two other sound effects ($2A and $34) whose tape address is in RAM instead of ROM. This means that the sound effect actually played is dynamic, and can differ between runs. Sound effect $34 is particularly interesting, in that the audio module itself contains code to dynamically alter the sound effect's tape data. We shall see this firsthand in a later post, when we examine the machine code of the audio module in depth.

The sound effect list has an unusual layout. In most other cases, the tape address data would have been stored in two lists, say $D000,X and $D028,X, and the sound effect IDs would have been ordered sequentially starting from zero. Instead, the address data has been interleaved into a single table, and the sound effect IDs are incremented by two, starting from zero. It turns out that this is not accidental, because it allows a sound effect to have TWO IDs: the ID listed in the table above, or 'primary' ID, and that ID number plus one, or 'locked' ID. But what use is that?

CAUTION: the next section is a bit esoteric. Read carefully.

The audio module contains code to suppress repeat sound effects. For example, sound effect $24 was currently playing on pulse channel 1, and the game logic issued a call to play sound effect $24, it would be ignored. When the sound effect is finished, the audio module resets itself so that it is 'playing' sound effect $00, and then the game logic could issue a call to play sound effect $24 again.

The same example, mutatis mutandis, applies to any given sound effect ID, in particular, $25, the 'locked' ID for $24. The key here is that if sound effect $24 was playing, it could be overwritten by sound effect $25, and vice versa. This fact comes into play when we look at the SFX UNLOCK message we glossed over in the last post.

At the implementation level, the SFX UNLOCK message will cause the audio module to change its record of the currently-playing sound effect from a 'locked' ID to a 'primary' ID (it has no actual effect if it is executed while the audio module is already playing a sound effect by its primary ID). The sound effect will continue to play normally, but it is now able to be preempted by re-playing the sound effect's locked ID.

Snake Rattle 'n' Roll makes use of this advanced feature in a few places, but the most noticeable is in the 'pibbly countdown' at the end of each stage. While it is counting out the number of nibbly pibblys eaten, there is a sound effect being played repeatedly. When the sound effect gets about halfway through, it is started over, until the end of a countdown, when it plays to completion.

Under the hood, the game is attempting to play sound effect $25 every frame (or, 60 hz). Many of these attempts will be rejected, because the audio module is already playing that sound effect, but halfway through the effect it reaches an SFX UNLOCK message, switches to being recorded as playing sound effect $24, and is overwritten on the next frame.

End esoteric section. That was a bit weird, but it is the only complicated part of sound effects: the SFX UNLOCK message is the only control message available in sound effect tape data. We will examine the rest of the sound effect tape data in the next post.

Wednesday, August 31, 2016

Snake Rock 'n' Roll: Effect Messages

Thus far, we have looked at the control messages and the note/rest messages that appear in a track's tape data. There are still a few messages left before we can fully understand the tape data in its entirety. These messages have to do with the various effects which can be applied to change how a note sounds. To begin, let us review the list of effects available:

  • Sound effect rate-limiting control
  • Direct control of APU channel values
  • Applying vibrato to a note
  • Applying a slide effect to a note, dropping its pitch
  • Applying a crescendo or decrescendo to a note's volume
  • Shifting the pitch of the notes in a song
  • Adjusting the tempo of a song

We will examine the use of the first one in a later post, discussing the sound effect processing in the audio module. For now, suffice it to name the message SFX UNLOCK, and note that it is encoded as a single byte: value $02. Let us now examine the rest of the list in order.

Before we do so, however, there is one other concept which must be introduced. Remember last post, when I said that each channel is on a 30hz 'update' timer (assuming that the tempo hasn't been lowered)? Things get a bit more complicated with the effects, because these typically undergo changes ever frame (60hz), regardless of the tempo value. I will refer to this second frequency as an 'output', to differentiate it from an 'update'.


APU channel values

As can be read on the NES dev APU wiki page, each channel in the APU is controlled by writing to four eight-bit registers. The note messages and various other effects control the timer and volume values of the channel, ajusting its period (and also frequency) and volume, respectively.

The other components of a channel's register values are typically set once, and then not modified afterwards. The sweep controls for the pulse channels ($4001/$4005) are initialized with value $43, effectively disabling the channel's sweep unit (the $4009 and $400D registers also get initialized to this value, but it does not have any effect on the triangle and pulse channels). The 'control' registers ($4000/$4004/$4008/$400C) and the length counter ($4003/$4007/$400B/$400F) are initialized from a list located at $DE44 and $DE45. It is indexed similarly to the tape start address table described earlier:

Track #Track NameChannel$DE44,X
(Initial $4000,X)
$DE45,X
(Initial $4003,X)
0Level 1 Pulse 1 $77$9A
Pulse 2 $76$9A
Triangle$1F$03
Noise $17$28
1Levels 5 and 8 Pulse 1 $789A
Pulse 2 $789A
Triangle$1F38
Noise $5828
2Levels 6, 9 and 10 Pulse 1 $7B$9A
Pulse 2 $7B$9A
Triangle$1F$03
Noise $58$28
3Level 2 Pulse 1 $55$9A
Pulse 2 $55$9A
Triangle$1F$03
Noise $19$28
4Level 3 Pulse 1 $16$9A
Pulse 2 $15$9A
Triangle$1F$03
Noise $17$28
5Levels 4 and7 Pulse 1 $77$9A
Pulse 2 $77$9A
Triangle$1F$01
Noise $15$01
6Time Out Pulse 1 $D7$03
Pulse 2 $D5$03
Triangle$1F$03
Noise $00$00
7Game Over Pulse 1 $97$03
Pulse 2 $97$03
Triangle$1F$03
Noise $00$00
8Main Titles and End Credits Pulse 1 $FB$9A
Pulse 2 $F6$9A
Triangle$1F$03
Noise $17$28

The third register of each channel is devoted to the timer period, and so is modified for each note message. If a track wishes to update the values in any of the other registers, the UPDATE APU message can be issued. It is encoded as four bytes: the value $03, and the byte values to be written to the first, second and fourth channel registers for that track's channel.


Vibrado

By itself, a square or triangle wave is a very plain-sounding instrument. The audio module supports a vibrado effect that gets applied to each note, which will increase or decrease the period of a note at regular intervals. To enable this effect, a tape can encode a START VIBRADO message, which is four bytes long. The first byte is the value $08, the second byte specifies (one less than) the number of distinct periods to use, the third byte specifies (one less than) the period of the vibrado effect (in outputs), and the fourth byte specifies the amount by which to adjust the channel period each time the vibrado period changes.

That description is perhaps a bit opaque, and a few examples would clarify. Let us assume that the initial channel period is 100, and examine what the resulting period would look like under various parameter configurations.

Number of PeriodsVibrado PeriodChannel Period DeltaResultant Output Channel Period
215 100,105,100,105,100,105,…
218 100,108,100,108,100,108,…
225 100,100,105,105,100,100,105,105,…
315 100,105,100,95,100,105,100,95,…
415 100,105,110,105,100,95,100,105,110,105,100,95,…

Note that the base channel period becomes the middle period in the progression.

The songs in Snake Rattle 'n' Roll use very small channel period delta values and typically only use two distinct periods. This gives the notes a bit more texture than a plain pulse channel, but do not fundamentally alter the perceived pitch of the notes.


Pitch Slide

In the main titles for Snake Rattle 'n' Roll, you can hear some notes that have a slide effect applied to them, causing their pitch to lower, similar to the effect that a guitar player would get by playing a note and then sliding towards the headstock of their guitar. This effect is enabled by the PITCH DOWN SLIDE message, which is encoded with a single byte, of value $0B.

A pitch slide message only applies the effect to the single note following it on the tape. At a low level, the pitch slide effect will wait for five outputs after the note starts, and then, every other output, the note's period will be increased by sixteen. The period is increased nine times in total.1


(De)crescendo

The NES APU has built-in hardware allowing it to play a note that has a decrescendo: starting at a high volume, and linearly decreasing the volume to nothing. The (de)crescendo effect is more general than this, as it allows a note to play at its initial volume for a length of time, then to linearly decrease OR increase towards a target volume, and then remain at the newer constant volume.

This effect is started with the DECRESCENDO START message, which is four bytes long. The first byte has value $16, the second byte specifies the delay (in outputs) before the (de)crescendo starts, the third byte specifies the delay (in outputs) between each volume adjustment, and the fourth byte specifies the target volume.

This effect also comes with another message: the DECRESCENDO IGNORE message, which is two bytes long. The first byte is value $0C, and the second byte specifies a volume value. When this message appears in tape data, the next note is played as though the (de)crescendo effect is not enabled, instead playing with the specified constant volume. Subsequent notes continue to use the existing (de)crescendo settings.


Pitch Shift

Many of the songs in Snake Rattle 'n' Roll share a common melody, but at different pitches (the music of levels 6, 9 and 10 being notable in this regard). In order to avoid the necessity of writing out the similar melodies repeatedly with different note values, there are three messages which can be used to adjust the pitch of a note.

The simplest message is the ABSOLUTE PITCH SHIFT message, which is two bytes long. The first byte is the value $12, and the second byte specifies the pitch shift. After encountering this message in tape data, the audio module will play subsequent note messages at a pitch that is the specified amount up or down from the note as it appears in the tape data.

There is also a RELATIVE PITCH SHIFT message. It is encoded with byte value $13, followed by a relative offset. Instead of setting the pitch shift for the audio module, it instead adjusts the current pitch shift up or down by the amount specified.

Lastly, there is a CROSS-CHANNEL PITCH SHIFT message. It is encoded with byte value $14, followed by an absolute pitch shift value. This message operates similarly to the absolute pitch shift message, except that the specified absolute pitch shift value is set for all four tracks, instead of just the track for which it is found. It is one of two such messages that can affect channels other than itself. Speaking of which…


Song Tempo

In the previous post, we saw that the tempo of the song can be controlled from its default value (30 updates/second) by a given ratio of skipped updates. Each song has an initial value specified for its tempo value, but this value can be adjusted during the song playback with the ADJUST TEMPO message. It is two bytes long; the first byte is value $11, and the second byte is an adjustment value.

This message is a bit tricky to make use of: the current tempo value for the song is adjusted up or down by the specified value, not set to the specified value. You will end up with unexpected results unless you are aware of the initial tempo value and know how this instruction will adjust it. Noted above, this message affects the tempo for all four tracks, not just the track being played.


Summary

Rolling up the information above and the previous post yields the following list of messages:

MessageEncoding
END OF TRACK[$00]
 
SFX UNLOCK[$02]
UPDATE APU[$03] [channel 1 value] [channel 2 value] [channel 4 value]
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 VIBRADO[$08] [step count] [step delay] [period step]
 
 
PITCH DOWN SLIDE[$0B]
DECRESCENDO IGNORE[$0C] [volume]
 
 
 
ADJUST TEMPO[$11] [tempo offset]
ABSOLUTE PITCH SHIFT[$12] [pitch shift]
RELATIVE PITCH SHIFT[$13] [pitch shift offset]
CROSS-CHANNEL PITCH SHIFT[$14] [pitch shift]
 
DECRESCENDO START[$16]
START LOOP (short format)[$17…$7F] [address low] [address high]
REST[$80] [length?]
NOTE[$81…$BC] [length?]

That's odd. We are still missing eight potential messages. The short answer is that it does not matter: the messages listed above make up all of the messages that appear in the tape data for the songs of Snake Rattle 'n' Roll. It doesn't matter what the other messages do, since they never end up actually doing it while you are playing.

If you are like me, however, such an answer is maddening. We will examine what those messages may or may not do at the end of the series, where we dive into the code.


  1. The machine code implementing the pitch slide effect appears to be more general than this description suggests: there is some seemingly unused code that would cause the change in period to vary, instead of being a constant $10, for example.

Friday, August 26, 2016

Snake Rock 'n' Roll: Note messages and control messages

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$D621Tempo
0Level 1 $80Full
1Levels 5 and 8 $6881.25%
2Levels 6, 9 and 10 $6A82.8125%
3Level 2 $6A82.8125%
4Level 3 $83Full
5Levels 4 and 7 $6A82.8125%
6Time Out $80Full
7Game Over $80Full
8Main Titles and End Credits$80Full

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:

MessageEncoding
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.

  1. Technically, it is actually 60.1 Hz, but what's a tenth of a Hertz between friends?
  2. 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.

Wednesday, August 24, 2016

Snake Rock 'n' Roll: The Location of the Tapes

You see something very odd when you look at the character ROM (CHR ROM) for Snake Rattle 'n' Roll:

CHR Pages 0 & 1 CHR pages 2 & 3 CHR pages 4 & 5 CHR pages 6 & 7

In these images, the upper half corresponds to the first page number listed, and the bottom half to the second page number listed.

In these renderings, you can see areas that look like corrupted data, or static. Pages two and six are the most noticeable, being entirely 'snow', but every page except for page zero contains at least some snow. What is going on here? Anyone who has played the game knows that this visual corruption never actually appears while playing, so something else must be going on.

At the risk of spoilers, it has to do with the audio module. I am pretty sure that you figured that out from the theme of this blog series; I just wanted to eliminate all doubt.

In the PRG ROM, starting at address $9505, is a nine-element list which will be of some interest to us:

Song #Song Name$9505
0Level 1 $02
1Levels 5 and 8 $02
2Levels 6, 9 and 10 $06
3Level 2 $02
4Level 3 $02
5Levels 4 and 7 $02
6Time Out $03
7Game Over $03
8Main Titles and End Credits$06

This table suggests that the tape data specifying the background music is actually stored in character ROM, not program ROM. Now, knowing the page number is nice, but which of the 4096 bytes in a page is the start of the tape? Another list (actually, two) comes to the rescue. These lists are each 36 (9x4) entries long. They start at $DE42 and $DE43, and you must increment your table index by four to get each successive element:

Song #Song NamePulse 1Pulse 2TriangleNoise
$DE42$DE43$DE46$DE47$DE4A$DE4B$DE4E$DE4F
0Level 1 $00$00 $01$07 $02$30 $02$BF
1Levels 5 and 8 $0C$8D $0C$FC $0E$44 $0E$B6
2Levels 6, 9 and 10 $00$00 $00$D9 $01$4D $01$82
3Level 2 $03$2B $03$FD $04$D8 $05$62
4Level 3 $05$98 $06$D0 $07$D0 $08$7F
5Levels 4 and 7 $08$CB $0A$43 $0B$63 $0C$26
6Time Out $01$F0 $02$11 $02$32 $02$45
7Game Over $02$46 $02$63 $02$80 $02$45
8Main Titles and End Credits $03$96 $04$DB $06$14 $06$60

This table provides the address on the corresponding table where the tape data for each track begins. Note that the list data is stored at $DE42 + $10 * SongNumber + TrackNumber, and likewise for the $DE43 list.

We now know that if we wanted to examine the tape data for the audio for the pulse 2 channel on level 3, we should start looking at $06D0 on CHR ROM page 2. In the next post, we will examine the file format of the tape data.

Monday, August 22, 2016

Snake Rock 'n' Roll: Introduction

As I have mentioned previously on this blog, one of my most favourite games of all time is the 1989 classic Snake Rattle 'n' Roll, brought to us by a very young Rare. I fondly remember playing it as a child, and thinking to myself, "Wow, video games are awesome. When I grow up, I am going to make video games for a living." This was a far more pragmatic choice than the average eight-year-old, who imagines being an astronaut, or a cowboy, or a dinosaur when they grow up.

Nowadays, twenty-seven years later, I still say the same thing: when I grow up, I fully intend to make video games for a living. In the meantime, I study the works of the Old Masters carefully, to see how they worked within the limitations of their time, and I find their works fascinating. Regular readers of this blog have already seen the results of this study in the Tileset series I wrote at the inception of this blog. This post marks the first in a series exploring the gritty details, nuts and bolts of the audio module for Snake Rattle 'n' Roll.

Producing this series required many hours of poring over documentation to gain skills that are practically lost to developers today. The excellent 6500 Programming Manual is the go-to source for the NMOS 6502 microprocessor. Knowledge of 6502 assembly programming (or, more specifically, 6502 disassembly) was instrumental in understanding the Snake Rattle 'n' Roll cartridge, as the NES uses a modified 6502 microprocesor: the 2A03 microprocessor manufactured by Ricoh. The wiki at NES DEV was likewise indispensable for its details on the NES architecture, filling in the gaps left behind once the assembly code was understood. For tools, I cannot recommend Nintendulator highly enough: it is by far the most accurate NES emulator available today, and its debugging capabilities were invaluable in producing this document.

This series will not go into the details of how I reverse-engineered the audio module. I thought about including information on my techniques, but opted against it on the grounds that it would make the series much less accessible to the lay reader. If you are sufficiently inclined, you can take the linked resources above and a copy of the Snake Rattle 'n' Roll ROM dump (or another game of your choice), and start working backwards with liberal use of breakpoints in Nintendulator. If you do decide to embark on this path, drop me a line and let me know what you find. I would be keenly interested to know if other games have a similar audio module; I have an untested theory that NES development kits (if they even existed) may have included a boilerplate audio module that developers could leverage, similar to (but much more simpler than) the audio modules in today's consoles.

Let us begin the series, then, by heeding the advice from the novel Dune to "take the most delicate care that the balances are correct", and define some terminology for use in later installments of this series. Snake Rattle 'n' Roll has nine 'songs': the audio you hear in the background while you are playing, according to the following list:

Song NumberSong Name
0Level 1
1Levels 5 and 8
2Levels 6, 9 and 10
3Level 2
4Level 3
5Levels 4 and 7
6Time Out
7Game Over
8Main Titles and End Credits

Each song is composed of four 'tracks', and each track provides the data for one of the four audio channels present in the NES Audio Processing Unit (APU), according to the following list:

Track NumberAPU Channel
$0Pulse 1
$4Pulse 2
$8Triangle
$CNoise

Throughout this series, I will adopt the 6502 convention of writing hexadecimal numbers with a leading dollar sign, as in $DEADBEEF, as opposed to the more modern convention of using '0x', as in 0xCAFEBABE. It may seem odd that I have numbered the song tracks incrementing by four instead of by one; my reasoning will become apparent in later posts.

To continue the cassette metaphor, the data for each track are stored in a 'tape'. A tape consists of a sequence of 'messages'. Some of these are 'control messages', which alter how the audio module interprets the bytes in the tape. Some of these are 'effect messages', which alter how a given note will be played by the APU channel. Most of these are 'note messages', which tell the audio module to emit a note to the APU channel. A large portion of the audio module handles reading of tape bytes, interpreting the messages, and processing them.

The audio module supports many 'effects', as controlled by effect messages. These are:

  • Sound effect rate-limiting control
  • Direct control of APU channel values
  • Applying vibrato to a note
  • Applying a slide effect to a note, dropping its pitch
  • Applying a crescendo or decrescendo to a note's volume
  • Shifting the pitch of the notes in a song
  • Adjusting the tempo of a song

The APU channels do double-duty, playing both the background music and sound effects while the game is being played. You can observe this while playing, noting that while a sound effect is being played, parts of the background music are not heard. This is not unique to Snake Rattle 'n' Roll: most NES games had the same limitation, although there were a few that included additional audio processors in the cartridge itself to produce richer audio than the basic system provided. The audio module for Snake Rattle 'n' Roll supports playing sound effects over the background music, while remembering where in the music it is so that it can continue playing the song once the effect ends.

In the next post in the series, we will find where on the cartridge the track tapes are located.

Wednesday, November 11, 2015

Tilesets: Simulated Elevation

The most popular post on my blog to date has been about isometric tilesets. In a shameless attempt to increase readership, I will give the people more of what they want.

Today, I shall investigate how to add an element of elevation to an isometric tileset. By far my most favourite example of this is the little-known game Snake Rattle 'n' Roll, where the eponymous snakes Rattle and Roll are climbing up a mountain composed of isometric tiles:

However, the technique is as old as video gaming itself, as can be seen in the 1982 class Q-Bert:

In these two examples, the ground is seemingly composed of stacks of cubes. What I am going to show you is a bit more powerful, in that it can bue used to make stacks of half-cubes called 'slabs' that will be immediately familiar to Minecraft players.

I begin by using the ideas in this early blog post to create four tiles, each eight by eight pixels in size, that can be arranged to form an isometric grid, like so:

To create the illusion of depth, we can raise and lower the tiles for individual slabs, like so:

So far, the result looks like an incoherent mess. We must create some new tiles to serve as the 'sides' of the slabs. To do this, I created a sixteen by eight pixel image to represent the side of one slab (that being one by two tiles in size). I then doubled it up to sixteen by sixteen, 'slanted' it, and fit it into the space between two slab tops, as shown below:

This approach creates six tiles. The reason that I chose to double-up a sixteen by eight pixel image instead of creating a sixteen by sixteen image is that my approach will result in the top two and bottom two tiles matching up seamlessly, and result in the middle tiles matching seamlessly with themselves, allowing for height differences in multiples of a slab instead of a cube.

If I slot these new tiles into the earlier image, the result begins to take shape:

I can then do the same procedure again, only this time 'slanting' in the opposite direction:

I can then add these tiles to the earlier image, and complete the look. It now looks like a five-by-five arrangement of slab stacks, at varying heights:

This result is composed of only sixteen tiles:

By applying this technique, and a heaping helping of artistic talent, you can turn out tilesets that let you create amazing environments like those in the Tactics Ogre franchise.

There is one notable restriction on this technique. The tiles that I have created are only able to be used to construct environments where any given slab is no higher than its adjacent northeast and northwest tiles. Stated another way, if you travel northeast or northwest from a given slab, the height must be monotonically increasing. We will relax that restriction a bit in the next post.