Music Engine Description

From BindingForce Wiki
Revision as of 00:42, 26 September 2021 by Bentglasstube (talk | contribs) (Created page with "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...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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.

Title Music Engine

   TODO document further.

Main Music Engine

The main loop for starts at bank 6 $9000

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.