Music
This page will detail how the Oracles game handles music data and reading it. You should already be familiar with the disassembly. Also, see https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware regarding how the Gameboy Color sound hardware works.
Structure
Music and sound effects use the same system. Music is defined as indices 0x00 through 0x4b. Sound effects are indices 0x4c through 0xd4. Indices 0xde and 0xf0 - 0xfc are used for sound controls, like stopping music, sound effects, fade-ins and fade-outs.
Indices 0xd5 - 0xdc and 0xdf - 0xef are undefined and unused by the games.
The Gameboy Color has four channels. Two square channels, one wave channel, and one noise channel. The first square channel is capable of using sweeps, but this is not used by the Oracle games (at least not for music).
The disassembly has several files regarding the music data.
First, there are a few files in the audio/{game}/
folder:
soundPointers.s
soundChannelPointers.s
soundChannelData.s
Sound Pointers
This is a list of pointers to soundChannelPointers.s
.
The order of these pointers are actually what define the index of music.
So, for example, if musTitlescreen
is swapped with musOverworld
, then the title screen music will play in the overworld, and vice versa.
In the Disassembly, macros are used for easier presentation of these pointers. The macro helps define the bank of the channel data offset from the audio code (code/audio
), and then the actual pointer to soundChannelPointers.s
.
Sound Channel Pointers
These are the pointers utilized by the game to define the channels used for the music.
For music, the square channels are defined as $00 and $01. The wave channel is $04 and the noise channel is $06. For sound effects, square channels are $02 and $03. Wave channel is $05, and the noise channel is $07. Reminder that the hardware only has the four channels. The games only separate them like this so that sound effects can play over music without the games losing track of either.
Here is an example of one of the pointers, this one for the overworld:
musOverworld:
.db $00
.dw musOverworldChannel0
.db $01
.dw musOverworldChannel1
.db $04
.dw musOverworldChannel4
.db $06
.dw musOverworldChannel6
.db $ff
This is an example of the sound effects pointers. Note that not all channels have to be defined for one sound effect, and thus keeping it open for other effects. Further, only bits 0-2 are utilized to define the channel index. Bits 3-7, set to $a0 in the example, are not yet fully understood.
sndTimewarpInitiated:
.db $a2
.dw sndTimewarpInitiatedChannel2
.db $a3
.dw sndTimewarpInitiatedChannel3
.db $a5
.dw sndTimewarpInitiatedChannel5
.db $a7
.dw sndTimewarpInitiatedChannel7
.db $ff
Sound Channel Data
This file is a compilation of all of the music data that the game holds. While soundPointers.s
and soundChannelPointers.s
are housed within the first bank, with the audio code (code/audio.s
), this data is so large that it spans multiple banks. However, music data for one song is always in its own, single bank.
Although all the data used to be in this single file, the Disassembly has since been restructured so that this file only has .include
instructions, so that all the music can be in its separate, unique .s
file. These are within the audio/{game}/mus/
or audio/{game}/sfx/
folders. These files are named the same as the constants in constants/music.s
.
Music Data
While much of the information in this section is applicable to sound effects data as well, the author is much more accustomed to music data than sound effect data. Therefore, for clarity, this section will call it "music data" rather than the generic "channel data."
The data are bytes 0x00 through 0xff. The audio code interprets each byte as a sort of scripting language, where the first byte defines the command, and subsequent bytes can define parameters, such as note lengths and volume levels.
The disassembly uses macros as to make the music data easier to read. These are located in include/musicMacros.s
.
beat
octave, octaved, octaveu
note
rest, rest2
vol
env
cmdf0
duty
cmdf8
vibrato
cmdfd
goto
cmdff, cmdf4, cmdf5
Pitch Macros
Commands $00 through $d0 are for pitches (frequencies). These have one parameter, defining the length the pitch is played.
The pitch frequencies starts at C at Octave 1 and goes up to b at Octave 8.
In include/musicMacros.s
, these pitches are enumerated so that instead of using $00
in the following macros, you can use c1
, etc. Note that these pitches are only defined with sharps. As such, gs4
exists, but ab4
or af4
does not.
Allowable frequencies for the noise channel are as follows:
$22: Crash
$23: Tom-like drum sound
$24: Snare drum, short dash noise
$26: Long dash noise
$27: Longer dash noise
$28: Wave crash
$29: digging, bumping
$2a: Dash noise
$2e: Long dash noise, like a bush break
$2f: Crumble noise
$30: Crash
$32: Tom-like, dash sound
$52: Dash noise
The descriptions for these noises are not scientific.
Note
This macro is utilized by the disassembly for the games' original music data.
This is because it is usually used for one frequency-length pair. Lengths can range from $00 through $ff; however, note that a length of $00 underflows so that the true length is $100.
This macro is a lesser version of Beat
.
note d5 24
Although the games' music data uses hexadecimal values for the lengths, decimal values are allowed and recommended. Thus, a quarter note at 150 BPM will have a value of 24, or $18. Shortening note lengths are intuitive, such that an eighth note at 150 BPM will be 12, or $0c.
However, see the Tempo
macro below detailing how to avoid using absolute values at all.
Beat
This is Stewmat's custom version of the note
macro. The main input for this macro is frequency-length pairs. However, Stewmat has introduced a few other commands that can be used inside this macro that is helpful for music creation, but does not actually affect the number of bytes that is put into the game.
Before this macro is used, the user must define BEAT
with a value, as this constant is multiplied by the input lengths to arrive at the final, true length. For example,
.redefine BEAT 1
beat d5 24 c3 6 g4 12
is the same as
.redefine BEAT 6
beat d5 4 c3 1 g4 2
This macro also supports using pitches without a defined octave (i.e., relative octaves). Rather, the octave can first be defined with the Octave macro, then shifted up and down as needed. For example, these two lead to the same output:
beat d5 24 c3 6 g4 12
octave 5
beat d 24 od od c 24 ou g 12
Either are entirely viable and up to the user on how they are input.
As shown above, od
and ou
are the beat
macro's version of octaved
and octaveu
, respectively. See below.
Further, this macro also allows r
as an input. This is the same as the rest
macro.
Finally, you can make NOTE_END_WAIT
greater than $00 so that notes are shortened by that length and a rest will be placed in between.
.redefine NOTE_END_WAIT 2
beat c4 12 c4 12
.redefine NOTE_END_WAIT 0
beat c4 12
is the same as
beat c4 10 r 2 c4 10 r 2 c4 12
This can be necessary if multiple notes have the same pitch, and so there needs to be a break for the rhythm to come out. However, the second parameter of env
command can be used instead for shorter, more staccato notes.
NOTE_END_WAIT
is assumed to be $00, but make sure to redefine it to $00 after its use, as it can affect music data from other songs if it's not reset.
Octave
Octaves start at C and go up to B. Middle C is located in Octave 4.
This macro is utilized in conjunction with the beat macro if relative octaves are being used.
The user can also use octaveu
and octaved
to shift the octave up and down, respectively.
Rest
This command defines a period that the channel should wait until the next command is read. It only has one parameter, the length.
rest 24
Note that if you need the channel to rest longer than $100 (the highest possible with $00), then it is recommended to set the volume to $0 and play a note with a low frequency. This is because the channels play small blips at the end of a rest
, but this is avoided when the volume is already down and it's already "playing" a pitch. gs3
is the pitch used by the games.
Therefore, it could look like this:
vol $0
beat gs3 $00 gs3 24
Volume
This command is only actually applicable to the square and noise channels. It does not affect the wave channel. Inputs can range from $0 to $f, with $0 being complete silence and $f being the loudest. The games only ever go up to $8, and $6 is used the most as a good, forte volume. Examples:
vol $0
vol $6
Volume levels can be combined to create an "echo" effect.
env $1 $00
vibrato $81
vol $6
beat c4 40
env $0 $00
vibrato $01
vol $4
beat c4 20
This will cause Middle C to play for 60 periods, but will only be loud for 40. Make sure to unset the wait on the vibrato and the first parameter of the volume envelope, if they're difference than $0.
This command does nothing for the wave channel.
Envelopes
Volume envelopes can affect either how a note begins or ends. This is an example of the envelope macro:
env $1 $05
The first parameter causes a note to start at low volume and increase to the actual volume within a short time. The effect is soft, and is usually used to simulate instruments like Guru Guru's phonograph. The larger the value, the longer it takes for the note to reach full volume. The second parameter causes a note to decrease in volume after a certain time. The larger the value, the longer it takes for the note to decrease. It is usually used to create a staccato without introducing a rest between notes. It's not required for the envelope to have both effects at once, and in fact this is usually not utilized.
See example above in the Volume section to see how an "echo" effect can be achieved.
This does nothing for the wave channel.
Cmdf0
This command is only used for sound effects and is not fully understood.
It sets wc039
at times.
Duties
The duty
command sets the waveform that the channel will use. It works differently depending on if it's used on the square channel or on the wave channel. (Has no effect on the noise channel?)
Square Channels
The square channels can be set only at values $00, $01, $02, and $03. In effect, lower values are more harsh while high values are more soft.
Wave Channel
This command sets the wave form to use for the wave channel. You may use any preset waveform at the end of code/audio.s, or define your own.
Note that as you cannot control volume with the "vol" command on the wave channel, the volume level needs to be built into the waveform. (It is technically possible to control the waveform channel's volume but this is reserved for volume dampening on the inventory menu.)
These are the values you can use (ie. duty $1e
would configure the wave channel as a sawtooth wave):
Square waves, 50% duty cycle (equivalent to square channel with duty $02):
$0a: Volume C
$0c: Volume 1
$0d: Volume 3
$0e: Volume 8
$0f: Volume 3 (same as $0d)
$10: Volume 7
$11: Volume 8
$17: Volume 5
$18: Volume 9
$2b: Volume 4
$2c: Volume 2
Square waves, 75% duty cycle (equivalent to square channel with duty $03):
$02: Volume F
$21: Volume 7
$29: Volume 2
$2a: Volume 3
Square waves, 75%+ duty cycle:
$01: Volume 8
$12: Volume 9
$13: Volume 7
Other square waves:
$0b
$19
Triangle waveform:
$00: Medium volume
$03: High volume
$05: Identical to $00
$08: Low volume
$16: High volume (alternate)
$20: High-ish volume with slightly unusual pattern?
Sawtooth waves:
$1d: Double frequency?
$1e: High volume
$23: Low volume
Waveforms that I don't know the names for, if they have names at all (these are interesting):
$04
$06
$07
$09
$14 - Partial triangle?
$15 - Partial triangle?
$16
$1a
$1b
$1c
$1f - Techno feel
$22
$24
$25
$26
$27
$28
$2d
Cmdf8
This command is only used for sound effects and is not fully understood.
When used on square channel 1 (corresponding to music channel 0 or sfx channel 2), this controls the frequency sweep feature specific to that channel.
Values from $01-$7f make the note increase in frequency as it plays, while values $80-$7f make it decrease in frequency. Value $00 turns the feature off.
(This feature is more useful to sound effects than to music, which is why sound effects prefer to use square channel 1, while music tracks usually put their main melody on square channel 2, to minimize conflicts.)
Vibrato
This command sets the channels vibratos. It is mainly used for the square channels. The upper nybble is the amount of time waited until the vibrato starts. The lower nybble is the intensity of the vibrato. Common values:
$00: No vibrato
$81: short wait, low intensity
$e2: long wait, high intensity
$02: No wait, high intensity
See example above in the Volume section to see how an "echo" effect can be achieved.
Cmdfd
This command shifts the pitch in an absolute way. It sets wc033
.
It will NOT shift pitches by a half step, but rather by the actual frequency value. It is not used by the games for music.
Jumps
The goto
macro, this command, is utilized by the games to loop the entire piece. There are no conditional jumps in the music data. Therefore, unless the audio code is edited, the entire piece must be expanded with all conditional repeats.
Take this paragraph as a suggestion when creating music:
When creating labels, note the music name, channel name, and the applicable measure or other pertinent information. For example, the overworld music loops at measure 5 on channel 1. Therefore, the label would be musOverworldChannel1Measure5Loop
. However, as the channel data labels were created prior to analyzing it, the disassmebly usually just uses the address. Therefore, currently this label is musiceca67
.
Cmdff
This command mutes the channel - the internal index, not the actual hardware channel, and causes the channel to stop reading music data. It usually marks the end of a channel's music data for a piece, or is used right after a goto
command, although needless.
Additionally, certain commands are copies of this one, such as cmdf4
, cmdf5
, $f7, and $fa - $fc. The first two have cmd
macros as the games use these for some unknown reason.
Tempo
This is ZerotoKoops's macro that allows for relative note lengths.
The input is the desired tempo. However, as note lengths must be kept at whole values, the actual output tempo might vary slightly from the input tempo.
For example, tempo 150
sets the tempo-related variables to relate to 150 BPM. tempo 140
sets the tempo at around 138.46.
The usable variables are:
Whole: W
Half: HF
Quarter: Q
Eighth: E1, E2
Sixteenth: S1, S2, S3, S4
Thirty-second: T1, T2, T3, T4, T5, T6, T7, T8
Quarter Triplet: R1, R2, R3
Eighth Triplet: Y1, Y2, Y3, Y4, Y5, Y6
Sixteenth Triplet: W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12
With tempos above about 170, certain subdivisions of the quarter note will be calculated at $00, and therefore cause the note to be played for 256 periods rather than none due to an underflow error. Tempos above 170 can still be used, just beware of using small subdivisions.
Thus, an example of the notes in used would be the following:
tempo 150
octave 4
beat c W ou c HF
octaved
beat b Q as E1+S3 a S4
beat gs R1 g R2 fs R3
beat f R1+R2 g R3 b Q
beat c W
is the same as
octave 4
beat c 96 ou c 48
octaved
beat b 24 as 18 a 6
beat gs 8 g 8 fs 8
beat f 16 g 8 b 24
beat c 96
Note that for certain tempos and certain subdivisions, one might not equal another. For example, with a quarter note length of 29, thirty-second notes are as follows:
T1: 3
T2: 4
T3: 3
T4: 4
T5: 4
T6: 3
T7: 4
T8: 4
The pattern of these subdivisions are random and already determined by the macro.
Moving and Importing Music Data
Currently, there are a few custom pieces which have been created for the Oracle games' engines. Furthermore, a user might want to import music from either game to the other.
Sounds Pointers
For this example, we will import the Past Overworld music from Oracle of Ages into Seasons. The three main .s
files must be edited. We will start with audio/seasons/soundPointers.s
.
Decide which index will be replaced with musOverworldPast
(the base label for the music data). Reminder that music only goes up to index $4b, and therefore another piece must be replaced. There are several blank indices, but if you use those, you might run into storage issues in a later step. For our example, we will replace musCarnival
, index $08.
Constants
An optional step would be to change the constant at constants/common/music.s
, but this is not required. It is however recommended to close the project in LynnaLab when editing these constants.
Sound Channel Pointers
The next step is to add/replace the the channel pointers at audio/seasons/soundChannelPointers.s
. In our example, we would replace the one referencing musCarnival to musOverworldPast.
musCarnival:
.db $00
.dw musCarnivalChannel0
.db $01
.dw musCarnivalChannel1
.db $04
.dw musCarnivalChannel4
.db $06
.dw musCarnivalChannel6
.db $ff
becomes
musOverworldPast:
.db $00
.dw musOverworldPastChannel0
.db $01
.dw musOverworldPastChannel1
.db $04
.dw musOverworldPastChannel4
.db $06
.dw musOverworldPastChannel6
.db $ff
Sound Channel Data
Finally, we will need to add/replace the actual music data into soundChannelData.s
. We can place this anywhere as long as it fits within the bank, but we'll replace the .include
of carnival to overworldPast. If it doesn't fit, we can delete other music data, but we would need to remove the references as well.
Therefore,
.include "audio/seasons/mus/carnival.s"
is replaced by
.include "audio/ages/mus/overworldPast.s"
For consistency, we could move the music data to the appropriate games' folder, but this is not necessary.