Music Engine Description
There are two separate music engines in Zelda 2. Both are in bank 6 (along
with the song data). The music engine for the title screen resides at
$8000
and the engine for the rest of the game is at
$9000
. Each of these engines is called once per frame during the
appropriate part of the game.
Changes to the music engine should be very easy to make, as there is an abundance of free space in bank 6.
Contents
- 1 Title Music Engine
- 2 Main Music Engine
- 2.1 Data Tables
- 2.2 Variables
- 2.3 Routines
- 2.3.1 Main Music Loop $9000
- 2.3.2 Configure Pulse1 Channel $9031
- 2.3.3 Configure Pulse2 Channel $9038
- 2.3.4 Play Note Pulse1 $9042
- 2.3.5 Play Note $9044
- 2.3.6 Play Note Pulse2 $9067
- 2.3.7 Play Note Triangle $906B
- 2.3.8 Get Note Duration $906F
- 2.3.9 Vibrato Pulse1 $9089
- 2.3.10 Vibrato Pulse2 $9097
- 2.3.11 Vibrato $909E
- 2.3.12 Play E9 SFX $920B
- 2.3.13 Play EF SFX $92F4
- 2.3.14 Play EE SFX $9408
- 2.3.15 Play Noise $959D
- 2.3.16 Crumble Bridge SFX $956C
- 2.3.17 SFX Noise Decay $9587
- 2.3.18 Play ED SFX $95A7
- 2.3.19 Crumble Block / Enemy Hurt SFX $95E6
- 2.3.20 Crumble Block / Enemy Hurt SFX Decay $95EE
- 2.3.21 Play Noise Samples $9612
- 2.3.22 Sword Strike SFX $9604
- 2.3.23 Play EC SFX $990B
- 2.3.24 Play Music $9B18
- 2.3.25 Next Song $9B24
- 2.3.26 Mute All $9B3B
- 2.3.27 Load Song $9B61
- 2.3.28 Load Song Data $9B80
- 2.3.29 Play Notes
Title Music Engine
The title music engine is different from the engine that plays music
during the rest of the game. In particular, it has a wider range of
notes that can be represented. If you want to look into the actual
code, the title screen music engine main loop is at $8000
(vesus
$9000
for the game music loop).
The metadata for the songs is all the same as in the other areas, although the title music is actually broken up into several songs itself. These song boundaries control the timing of the title scroll.
The following songs exist in the title song table:
- Intro
- Start
- Build up
- Main
- Breakdown
The start of song 2 triggers the title to scroll into view. The start of song 4 triggers a countdown until the story scroll begins. After song 5 finishes, the engine loops back to song 2.
Within the note data, a special format is used. Rather than encoding
the duration and pitch together in a single byte, the title music has
"pitch" bytes and "duration" bytes. Any byte with the highest bit set
(i.e. anything >= $80
) is interpreted as a duration change, which
sets the duration of all future notes. Anything else is a pitch value
which indicates a note of the current duration should be played. The
low nibble of duration values keys into a lookup table at bank 6 $8084
and store the duration byte at $07FF
. Value durations are as follows:
$80
- 8 ticks (sixteenth note)$81
- 24 ticks (dotted eighth note)$82
- 16 ticks (eighth note)$83
- 32 ticks (quarter note)$84
- 48 ticks (dotted quarter note)$85
- 64 ticks (half note)$86
- 96 ticks (dotted half note)$87
- 128 ticks (whole note)$88
- 11 ticks (eighth note triplet, first two)$89
- 10 ticks (eighth note triplet, third)$8A
- 80 ticks (half note + eighth note)$8B
- 256 ticks (two whole notes)$8F
- 6 ticks (dotted 32nd note)
Note: $8C
, $8D
and $8E
are duplicates, so these could be used for
custom durations if desired.
The pitch values are much more straightforward. As far as I can tell,
$42
is A4 and every semitone away from that adds or subtracts 2 from
the value. Thus, C5 (three semitones above A4) is $48
. As in the game music engine, the value
$02
represents a rest.
-2- -3- -4- -5- -6- -7- C : $18 $30 $48 $60 $78 C# : $1A $32 $4A $62 $7A D : $04 $1C $34 $4C $64 Eb : $06 $1E $36 $4E $66 E : $08 $20 $38 $50 $68 F : $0A $22 $3A $52 $6A F# : $0C $24 $3C $54 $6C G : $0E $26 $3E $56 $6E G# : $10 $28 $40 $58 $70 A : $12 $2A $42 $5A $72 Bb : $14 $2C $44 $5C $74 B : $16 $2E $46 $5E $76
By way of an example, let's look at the main section of the vanilla
title music. The title metadata starts at bank 6 $84DA
:
08 11 14 16 19 1E 1E 1E ^ Main section
The main section is the fourth song, starting at $84DA + $16 = $84F0
:
3F 3F 00
This means there is a single phrase for the main section, which is
repeated twice. The phrase data is at $84DA + $3F = $8519
.
00 3D 86 3A 1A 5F
From this, we see the melody note data is located at $863D
:
83 Quarter notes 02 Rest 48 Ab4 82 Eighth note 46 G4 83 Quarter notes 3E Eb4 34 Bb3 84 Dotted quarter notes 2E G3 83 Quarter notes 30 Ab3 34 Bb3 3A Db4 38 C4 34 Bb3 30 Ab3 82 Eighth notes 34 Bb3 83 Quarter notes 30 Ab3 85 Half notes 2E G3 82 Quarter notes 02 Rest 00 End of Data
Main Music Engine
Data Tables
There are several data tables used by the music engine.
Pulse Envelope
This table is used to set the volume of the pulse channels over time. The index used starts at a certain value and decrements, so the table is in reverse order. The index is halved before use, so each value lasts two frames.
90 | 91 | 91 | 91 | 92 | 92 | 92 | 92 |
93 | 93 | 94 | 94 | 94 | 95 | 95 | 95 |
96 | 96 | 96 | 97 | 97 | 97 | 98 | 98 |
Duration LUT
This table is used to determine the duration of a note. The note data is stored with 5 bits of pitch information and 3 bits of duration, as such:
D1 D0 P4 P3 P2 P1 P0 D2
The Get Note Duration routine shifts things around to get the D bits into the
three least significant bits and masks it before use. This value is then added
to the tempo value in $E5
which is always a multiple of 8 to index
into the following table:
00 | 04 | 0C | 08 | 10 | 18 | 20 | 05 | 06 |
08 | 04 | 0F | 09 | 12 | 1B | 24 | 06 | 06 |
10 | 05 | 0F | 0A | 14 | 1E | 28 | 07 | 06 |
18 | 06 | 12 | 0C | 18 | 24 | 30 | 08 | 10 |
20 | 07 | 15 | 0E | 1C | 2A | 38 | 13 | 12 |
28 | 07 | 15 | 0E | 1C | 2A | 38 | XX | XX |
The last two values in each row seem a bit strange, as they are not nice multiples of the first entry which is usually thought of as the length of an eighth note. They are usually used to make triplets which don't divide evenly by slightly shortening or lengthening the third triplet. The last row doesn't include these two values because they are nonsensical and actually belong to the next LUT anyway. That row is used for the credits music which presumably avoids those last two values.
Pitch LUT
This table is used to detemine the pitch of a note. The note data is stored as described above, and the fact that the least significant bit is masked out is advantageous since the pulse channels can use 11 bits of timer data, so each entry in this table is actually two bytes wide.
Rather than show the raw bytes here, it's more useful to show the rough note that those values represent. If the raw values are interesting to you, check the ROM for them. It's worth noting that some of the higher notes are several cents off from the listed note.
00 | 02 | 04 | 06 | 08 | 0a | 0c | 0e | |
00 | C3 | --- | E3 | G3 | G#3 | A3 | A#3 | B3 |
10 | C4 | C#4 | D4 | D#4 | E4 | F4 | F#4 | G4 |
20 | G#4 | A4 | A#4 | B4 | C5 | C#5 | D5 | D#5 |
30 | E5 | F5 | F#5 | G5 | A5 | A#5 | B5 | C#3 |
40 | D3 | D#3 | F3 | F#3 | G#5 | C6 | C#6 | D6 |
50 | D#6 | E6 | F6 | F#6 | G6 | G#6 | A6 | A#6 |
60 | B6 | C7 | C#7 | D7 | D#7 | E7 | F7 | F#7 |
70 | G7 | G#7 | A7 | A#7 | B7 | C8 |
Value $02
represents a rest.
Note that although the table goes through $7A
values above
$3E
aren't usable due to the 5 bit limit for pitch data in
the song data. However, the sound effects routines make use of these
extra notes. For example, the sword beam effect alternates between
playing C7 and F6.
Noise Samples
Several sound effects use the same routine to load "samples" for the noise channel to play. These values have the noise volume in the high nybble and the noise period in the low nybble.
$90E8 - Sword slash $9123 - Enemy hurt $912C - Crumble block
Variables
This is just a dump of RAM addresses used by the music engine and a short description of what they are.
$00E0 - Current phrase note data address (low byte) $00E1 - Current phrase note data address (high byte) $00E2 - Song offset, converted from song id to be an index instead of a bit field $00E3 - Current phrase index $00E4 - Drums loop start $00E5 - Tempo $00E6 - Pulse 1 low bits (used for vibrato effect) $00E7 - Pulse 2 low bits (used for vibrato effect) $00E8 - Pitch storage, temporary $00E9 - Play sound effect $00EA - Disable music $00EB - Request new song $00EC - Play sound effect $00ED - Play sound effect $00EE - Play sound effect $00EF - Play sound effect
$0707 - Current world $075F - Queued song, loaded after screen transitions $07DA - Ganon Laugh sample $07DB - Song to resume after fanfare $07DF - ??? Likely sound effect flag for pulse 1 channel override
$07E0 - ??? Likely sound effect flag for noise channel $07E2 - Pulse 2 envelope index $07E3 - Pulse 1 envelope index $07E4 - Drums current note duration $07E5 - Bass current note duration $07E6 - Harmony current note duration $07E7 - Melody current note duration $07E8 - Drums next note index $07E9 - Bass next note index $07EA - Harmony next note index $07EB - Melody next note index
$07EC - Ganon Laugh counter $07ED - Sound FX counter (E9) $07EE - Sound FX counter (E9) $07F5 - Sound FX counter (ED)
$07FA - Current SFX (E9) $07FB - Current song $07FD - Current SFX (ED) $07FE - ??? Likely sound effect flag for pulse 2 channel override $07FF - Current SFX (EF)
Routines
This is a non-exhaustive list of the routines in bank 6 involving the playing of music and sound effects. Each routine also lists its address so you can search in a disassembly for the actual code for that routine if these descriptions aren't clear.
Main Music Loop $9000
Checks that the music disable flag is off, then runs several SFX routines. After the SFX routines, the main music routine runs. After all this, various music related memory locations are cleared.
Configure Pulse1 Channel $9031
Takes registers x and y and configures the APU registers $4000
and $4001
respectively.
Configure Pulse2 Channel $9038
Takes registers x and y and configures the APU registers $4004
and $4005
respectively.
Play Note Pulse1 $9042
Takes a pitch index in register a and plays the note on the pulse 1 channel.
Play Note $9044
Called by the various play note routines. Takes a pitch index in register a and
uses register x as a channel selector. For the pulse channels, this routine
also saves the lower 8 bits of the APU timer register to $E6
or $E7
which
are used for a vibrato effect.
Play Note Pulse2 $9067
Takes a pitch index in register a and plays the note on the pulse 2 channel.
Play Note Triangle $906B
Takes a pitch index in register a and plays the note on the triangle channel.
Get Note Duration $906F
Takes note data in register a and looks up the duration. The original note
data is saved in register x and the result of the duration lookup is put in
register a. The duration lookup is offset by the tempo stored in $E5
.
Vibrato Pulse1 $9089
Applies a vibrato effect on the pulse 1 channel. Takes the duration of the note left in register a and the APU timer low bits in register y.
Vibrato Pulse2 $9097
Applies a vibrato effect on the pulse 2 channel. Takes the duration of the note left in register a and the APU timer low bits in register y.
Vibrato $909E
Takes the duration of the note remaining in register a, the low bits of the APU timer in register y, and the channel to use in register x. This is called by the other vibrato routines after setting x appropriately.
Uses the $04
bit of the duration to either add or subtract 2 from the low
timer bits. Only the low bits are used because writing to the high bits resets
the phase of the channel which can cause clicks. None of the notes from the
pitch LUT are close enough to the boundary for the change to require the high
bits to change anyway, so this works out fine.
Play E9 SFX $920B
Plays the sound effect requested in $E9
.
TODO document further.
Play EF SFX $92F4
Plays the sound effect requested in $EF
.
TODO document further.
Play EE SFX $9408
Plays the sound effect requested in $EE
.
TODO document further.
Play Noise $959D
Configures the APU noise registers to make a noise burst. Takes configuration
for registers $400F
, $400E
, and $400C
in registers y, x, and a.
Crumble Bridge SFX $956C
Starts playing the sound effect for a crumble bridge crumbling. This uses both the noise and the triangle channels so it sets flags to disable the music on those channels. Continues with SFX Noise Decay.
SFX Noise Decay $9587
Counts down the timer in $07F5
and turns off the noise when it reaches 0.
Also clears the flag for the ED sound effect in $07FD
.
Play ED SFX $95A7
Plays the sound effect request in $ED
or continues the sound effect saved in
$07FD
. Existing sound effects which are higher than the current sound effect
will be ignored. If the same sound effect is requested that is already playing,
it will restart the sound effect except for $80
which gives priority to
continuing. When the sound is continued instead of newly played, the decay
routine will be called instead.
$02
- Crumble Bridge
$04
and$10
- Crumble Block / Enemy Hurt
$20
and$80
- Sword Strike
$40
- Very short noise burst, probably unused. Doesn't store the effect, and doesn't have an associated decay.
Crumble Block / Enemy Hurt SFX $95E6
Starts playing the sound of a block breaking or an enemy getting hurt.
Crumble Block / Enemy Hurt SFX Decay $95EE
Looks up noise register values based on the timer and the sound effect value.
Crumble Block samples are at $912C
while Enemy Hurt is at $9123
. Continues
on to Play Noise Samples.
Play Noise Samples $9612
Takes a sample value in register a. The low four bits of this are used to set
the noise period via APU register $400E
. The high four bits are used to set
the noise volume via APU register $400C
.
Continues on the SFX Noise Decay process.
Sword Strike SFX $9604
Starts playing the sound of swinging your sword. This looks up samples based on
the duration left at $90E8
and plays them via Play Noise Samples.
Play EC SFX $990B
Plays the sound effect requested in $EC
.
TODO document further.
Play Music $9B18
Plays the music for the game.
Runs Load Song if a new song is requested in $EB
. Otherwise, runs Play Notes
if the current song is set in $07FB
. If neither of these is true, do nothing.
Next Song $9B24
Checks if the previous song should loop. If not, calls Mute All and returns.
If the previous song was $01
(the "intro" to the main theme), sets the next
song to $02
(the main theme loop).
If the previous song was $10
(the item get / level up fanfare), sets the
current song from $07DB
. Otherwise, leaves the current song the same as the
previous song.
Proceeds to Load Song.
Mute All $9B3B
Clears the current song at $07FB
and sets the APU registers to mute the volume
on both pulse channels, the triangle channel, and the noise channel.
Load Song $9B61
Takes a song id in register a and preps some variables for that song.
If the song is $10
(item get / level up fanfare) then it first saves the
previous song to $07FB
if it is less than $10
so that the previous music
will resume after the fanfare.
Sets the phrase counter at $E3
to 0 and the song offset at $E2
based on the
song id. Song ids are a bit field, and the song offset is the position of the
lowest bit in the song id for indexing into the song table.
Proceeds to Load Song Data.
Load Song Data $9B80
Loads the music variables based on the current world and current song offset.
There are four blocks of code that are basically the same, for each of the four different music tables. Each one just uses a different position for the tables to load.
Based on the current world in $0707
it loads data from these offsets:
$00
- Overworld music -
$9B87
$01
or $02
- Town music -
$9BC6
$03
or $04
- Palace music -
$9C05
$05
or higher
- Great Palace music -
$9C42
First, uses the song offset in $E2
to index into the song table at the base
offset given above.
The phrase counter at $E3
is added to that and used as in index again to
retrieve the current phrase offset. If the current phrase offset is $00
that
means the song is over and the process goes back to the Next Song routine.
Otherwise, the following structure is read at the base offset above + phrase offset just calculated.
$E5
Tempo$E0
Note Data Low$E1
Note Data High$07E9
Bass Note Pointer$07EA
Harmony Note Pointer$07E8
Drum Note Pointer
After this, the drum loop start point at $E4
is also set to the same value as
the drum note pointer. The melody note pointer at $07EB
is set to $00
, and
all the durations (melody at $07E7
, harmony at $07E6
, bass at $07E5
, and
drums at $07E4
) are set to $01
.
Proceeds to Play Notes.
Play Notes
This routine is broken into four major and similar sections: one each for melody, harmony, bass, and drums. The general process is as follows:
decrement duration if duration == 0 { load next note increment note pointer set duration for note if no overriding sound effect { play note on appropriate channel } }
Some channels have unique processing at different points in this. For example,
the melody note data is null terminated, so if the loaded note in $00
then the
routine will jump to Load Song Data instead to go to the next phrase. If the
drums channel gets a $00
note it will reset the drums note pointer to the
drums loop start and repeat the phrase. The bass and harmony channels don't
have this looping behavior. This means that if the bass or harmony channels
aren't long enough, the engine will happily just play whatever data comes next
in the ROM.
The melody is played on pulse 2 channel, the harmony on pulse 1 (which gets subverted by sound effects that need just one pulse channel), the bass on the triangle channel, and the drums on the noise channel.
For the pulse channels, after the note is played, the envelope counter for that
channel ($07E2
for pulse 1, $07E3
for pulse 2) is set. Normally the
envelope is set to $2F
but song $40
(crystal fanfare and final boss music)
sets it to $18
instead. The envelope is not set if no note was played because
the pitch index was $02
which indicates a rest.
Also for the pulse channels, after the above pseudocode, the vibrato and
enveloping happens as long as there is no overriding sound effect. The envelope
works by checking the envelope counter set previously and decrementing it. The
counter value is halved and used to look up the envelope value in the LUT at
$9135
. These values are written directly to APU register $4000
or $4004
depending on the channel.
The bass channel has one special handling as well. The APU register $4008
is
set differently based on the current song id. For song $10
(item get / level
up fanfare) the register is set to $60
but all other songs use $1f
.
The noise channel is not affect at all by the pitch. Any pitch value that is non zero will play a burst of noise.