Skip to content

Sound engine commands (2019)

mid-kid edited this page Jun 24, 2022 · 1 revision

This site should/will describe all commands that are used by the Sound Engine of Pokemon Crystal (and in the future Pokemon Red). This is the result of revising the Code for the Sound Engine. While I'm working on it the name of the commands and their number of parametes may not match with the macros currently used in Pokecrystal.
It should also be a place to suggest some good names for the commands (which stand in the headline of the commands).

$d0 - $d7: octave byte

\1: number of the chosen octave (1-8)
db $d8 - (\1)

  • usage:
    • octave 2

Sets the octave for the following notes

$d8: notetype byte (byte)

\1: Note length (# frames per 16th note, shouldn't be greater than 16)
\2: Initial Volume of envelope (0-0Fh) (0=No Sound)
\3: Envelope Direction (0=Decrease, 1=Increase)
\4: Number of envelope sweep (n: 0-7) (If zero, stop envelope operation.)
db $d8, (\1), (\2 << 4) | (\3 << 3) | (\4)

  • usage:
    • notetype $c, $a, $0, $7
      • following notes have length $c and start with volume=$a and slowly fade out
    • notetype $7, $2, $1, $4
      • following notes have length $7 and start with volume=$2 and get louder a bit faster than in the previous example
    • notetype $a
      • following notes have length $a (channel 4 has no volume envelope setting)

Sets the note length and (if we're not in channel 4) the volume envelope setting (see the command 'volenvelope') for the following notes.
The note length shouldn't be greater than 16, because it gets multiplied with the note duration (which is set with the note command, i.e. C_, 12) and of the result only the lower 8-Bits are taken into further calculation.

$d9: forceoctave byte (or setabsnote, transpose, key)

\1: octave (0 - 15)
\2: pitch (0 - 15)
db $d9, (\1 << 4) | (\2)

  • usage:
    • setabsnote 2, 0

Sets an absolute starting octave and pitch for all notes.
This forces all notes up by the starting pitch and octave.

$da: tempo word

\1: 8.8 fixed point number (with 8bit fractional part) (higher value = slower tempo)
db $da
dw (\1)

  • usage:
    • tempo 140

Sets the tempo of all channels to the chosen value.
The audio engine initialise this with $100.

Corresponds to bpm (beats per minute):
You also have to set the note length with the Cmd 'notetype', which corresponds to 'Ticks/Row'.

  • 240 beats per minute
    3600 frames per minute / (6 tickets per row * 4 row per beat) / 240 bpm = 0.625 frames per tick
    0.625 frames per tick * 256 = 160 (this step is to convert to a fixed point value)
    240 bpm = tempo 160

  • 170 beats per minute
    3600 frames per minute / (6 tickets per row * 4 rows per beat) / 170 bpm = 0.882 frames per tick
    0.882 frames per tick * 256 = ~226
    170 bpm = tempo 226

$db: dutycycle byte (or waveduty)

\1: Wave pattern duty setting (0-3)
db $db, (\1)

  • usage:
    • dutycycle 2

Sets the Wave pattern duty of the channel in register NRX1 (Bits 6-7).

$dc: intensity byte (or volenvelope)

\1: Initial Volume of envelope (0-0Fh) (0=No Sound)
\2: Envelope Direction (0=Decrease, 1=Increase)
\3: Number of envelope sweep (n: 0-7) (If zero, stop envelope operation.)
db $dc, (\1 << 4) | (\2 << 3) | (\3)

  • usage:
    • volenvelope $a, $0, $7
      • following notes start with volume=$a and slowly fade out
    • volenvelope $2, $1, $4
      • following notes start with volume=$2 and get louder a bit faster than in the previous example
    • volenvelope $a, $0, $0
      • following notes have volume=$a without fading

parameter is directly written in hardware register NRX2 of the corresponding channel

FF12 - NR12 - Channel 1 Volume Envelope (R/W)  
* Bit 7-4 - Initial Volume of envelope (0-0Fh) (0=No Sound)  
* Bit 3   - Envelope Direction (0=Decrease, 1=Increase)  
* Bit 2-0 - Number of envelope sweep (n: 0-7) (If zero, stop envelope operation.)  
Length of 1 step = n*(1/64) seconds  

$dd: soundinput byte (or sweep)

\1: Sweep Time (0-7)
\2: Sweep Direction (0=Increase, 1=Decrease)
\3: Number of sweep shift (n: 0-7)
db $dd, (\1 << 4) | (\2 << 3) | (\3)

  • usage:

Loads Parameter into 'SoundInput'
Sets 3rd Bit in ChannelXNoteFlags
which later loads 'SoundInput' into NR10

FF10 - NR10 - Channel 1 Sweep register (R/W)
* Bit 6-4 - Sweep Time
* Bit 3   - Sweep Increase/Decrease
  * 0: Addition    (frequency increases)
  * 1: Subtraction (frequency decreases)
* Bit 2-0 - Number of sweep shift (n: 0-7)
Sweep Time:
* 000: sweep off - no freq change
* 001: 7.8 ms  (1/128Hz)
* 010: 15.6 ms (2/128Hz)
* 011: 23.4 ms (3/128Hz)
* 100: 31.3 ms (4/128Hz)
* 101: 39.1 ms (5/128Hz)
* 110: 46.9 ms (6/128Hz)
* 111: 54.7 ms (7/128Hz)
The change of frequency (NR13,NR14) at each shift is calculated by 
the following formula where X(0) is initial freq & X(t-1) is last freq:  
X(t) = X(t-1) +/- X(t-1)/2^n  

$de: unknownmusic0xde byte (or dutycycle, or wavepatterndutycycle)

\1: Wave pattern duty setting 1 (0-3)
\2: Wave pattern duty setting 2 (0-3)
\3: Wave pattern duty setting 3 (0-3)
\4: Wave pattern duty setting 4 (0-3)
db $de, (\1) | (\2 << 2) | (\3 << 4) | (\4 << 6)

  • usage:
    • dutycycle 0, 1, 2, 3

Rotates through 4 Settings of Wave Duty every frame.
Begins with Bits 0-1, then 2-3, 4-5, 6-7. Only used for cries and sound effects.

FF11 - NR11 - Channel 1 Sound length/Wave pattern duty (R/W)
* Bit 7-6 - Wave Pattern Duty (Read/Write)
Wave Duty:
00: 12.5% ( _-------_-------_------- )
01: 25%   ( __------__------__------ )
10: 50%   ( ____----____----____---- ) (normal)
11: 75%   ( ______--______--______-- )

$e0: unknownmusic0xe0 byte byte (or pitchbend)

\1: duration of the pitch
\2: octave
\3: pitch
db $e0, \1, (\2 << 4) | (\3 << 0)

  • usage:
    • unknownmusic0xe0 0, 1, 2

$e1: vibrato byte byte

\1: vibrato delay (in frames)
\2: extent
\3: rate (# frames per cycle)
db $e1, \1, (\2 << 4) | \3

  • usage:

$e2: unknownmusic0xe2 byte

Parameter is saved into the channel structure, but never used again.
The command is also never used by any song or sound effect.

$e3: togglenoise byte (or drumkittoggle)

\1:
db $e3, (\1)

  • usage:
    • togglenoise $05

The command is used for channel 4 to set the drumkit and with it the available drumsounds.
If noise sampling is off (the initial state) then it gets turned on, if noise sampling is on it gets turned off and no drumkit is set.
The Cmd is only used once per song in PokeCrystal so it's only used to set the drumkit.

$e4: panning byte

\1:
db $e4, (\1)

  • usage:
    • panning $f0

The parameter acts like a BitMask for register NR51.

FF25 - NR51 - Selection of Sound output terminal (R/W)
Bit 7 - Output sound 4 to SO2 terminal
Bit 6 - Output sound 3 to SO2 terminal
Bit 5 - Output sound 2 to SO2 terminal
Bit 4 - Output sound 1 to SO2 terminal
Bit 3 - Output sound 4 to SO1 terminal
Bit 2 - Output sound 3 to SO1 terminal
Bit 1 - Output sound 2 to SO1 terminal
Bit 0 - Output sound 1 to SO1 terminal

$e5: volume byte

\1: SO2 output level (volume) (0-7)
\2: SO1 output level (volume) (0-7)
db $e5, (\1 << 4) | (\2)

  • usage:
    • volume $7, $7 ; set volume of Left and Right to their maximum

parameter is directly written in hardware register NR50

**FF24 - NR50 - Channel control / ON-OFF / Volume (R/W)**  
The volume bits specify the "Master Volume" for Left/Right sound output.  
* Bit 7   - Output Vin to SO2 terminal (1=Enable)  
* Bit 6-4 - SO2 output level (volume)  (0-7)  
* Bit 3   - Output Vin to SO1 terminal (1=Enable)  
* Bit 2-0 - SO1 output level (volume)  (0-7)  
The Vin signal is received from the game cartridge bus, allowing external hardware  
in the cartridge to supply a fifth sound channel, additionally to the gameboys  
internal four channels. As far as I know this feature isn't used by any existing games.  

$e6: tone word (pitchoffset)

\1: 11bit frequency
db $e6
dw (\1)

  • usage:
    • tone 0

The frequency gets added to the intern NRX3 and NRX4 register of the corresponding channel, so that every note gets shifted by the frequency that is set with this command.
The frequency is calculated with following formula.

Frequency = 131072/(2048-x) Hz

$e7: unknownmusic0xe7 byte

Parameter is saved into the channel structure, but never used again.
The command is also never used by any song or sound effect.

$e8: unknownmusic0xe8 byte

Parameter is saved into the channel structure, but never used again.
The command is also never used by any song or sound effect.

$e9: globaltempo word (or addtempo)

\1: signed 16bit-number (-2^8 - (2^8-1))
db $e9
dw (\1)

  • usage:
    • addtempo 20
    • addtempo -30

Sets global tempo to current channel tempo +- parameter.

$ee: unknownmusic0xee word (or conditionaljump)

\1: 16bit-pointer
db $ee
dw (\1)

  • usage:
    • unknownmusic0xee Label

The command checks a byte that corresponds to the channel and decides to jump or not, but the byte that gets checked is never set.

$ef: stereopanning byte

\1:
db $ef, (\1)

  • usage:
    • stereopanning $f0

The command is only executed if stereo is activated in the options and then it acts the same as Cmd $e4.

$f0: sfxtogglenoise byte (or sfxdrumkittoggle)

\1:
db $f0, (\1)

  • usage:
    • sfxtogglenoise $4

Does the same as Cmd $e3 just for the SFX Channel.

$f1 - $f8:

Commands don't do anything.

$f9: unknownmusic0xf9

db $f9

Sets a flag which is never used.

$fa: condition byte

\1: conditionbyte
db $fa, \1

  • usage:
    • condition conditionbyte

Sets the condition read by Cmd $fb.

$fb: jumpif byte byte

\1: condition
\2: address
db $fb, \1, \2

  • usage:
    • jumpif condition Label

Reads and Compares the condition set by Cmd $fa. If it's equa to the condition in this Cmd the jump is executed.

$fc: jumpchannel word

\1: 16bit-pointer
db $fc
dw (\1)

  • usage:
    • jumpchannel Label

The engine of the channel jumps to the given adress. MusicAdress of the corresponding channel is set to the paramter.

$fd: loopchannel byte word

\1: count (0:infinite)
\2: address
db $fd, \1
dw (\2)

  • usage:
    • loopchannel 1, Label

$fe: callchannel word

\1: address
db $fe
dw (\1)

$ff: endchannel

  • usage:
    • endchannel

Command ends the music stream.
When this command is encountered with subroutine flag set, we return to caller of the subroutine.

Clone this wiki locally