Preface

Before getting into this, I want to stress something: finish your hack. ALTTP hacks have a mortality rate of at least 0.983. There’s a lot of reasons for that. It’s way easier to come up with ideas or simple dungeons than it is to actually build and refine them. The open nature of the game only complicates things further. But let’s get something straight: music (and graphics) do not make a hack good. Very few people are committed enough to both complete a hack and write music. Work on your actual game content before even thinking about music.

If your hack is nearly or already finished or you’re someone who wants to make standalone songs for others, then continue reading. Oh, and get comfortable, because this is not a simple process.

To follow this guide, you will need to be familiar with basic asm patching using Asar.


Legend

Symbol Means
0x Hexadecimal number
$ Hexadecimal number in assembly
; hello Comment in assembly
<param> Parameter or value
[X-Y] Inclusive range of numbers from X up to and including Y
%<thing>() Invocation of macro named “thing”
!thingy Invocation of define variable named “thingy”

Structure of a song

Songs in memory are comprised of two parts: a header of pointers to data and the data itself. Roughly, they will be structured like this:

    SongPointers:
        <song1>
        ...
        <songn>

    song1:
        <part1>
        ...
        <partn>
        0000

    part1:
        <track1>
        ...
        <trackn>

    trackn:
        <musical data>
        00

These pointer tables are impractical to build manually and overall messy. Instead, we will use macros provided in defines_music.asm.

In the end, our music source code will look something like this:

!ARAM = <location>

%song("SongTitle", 2, 1)
    %segment("intro")
        %track("intro_1")
            <musical_data>
        ...
        %track("part_n")
            <musical_data>

    %segment("main_loop")
        %track("melody")
            <musical_data>
        ...
        %track("part_n")
            <musical_data>

%endsong()

Location

The location of the song should be defined at the beginning of each song source file with the !ARAM define.

If you are only changing a handful of songs, it is best practice to place songs directly over an existing song. Pointers for each songID are provided below.

Song ID ARAM ROM Name
01 $D036 $1A9F2F Triforce opening
02 $D0FF $1A9FF8 Light World
03 $D86A $1AA763 Rain
04 $DCA7 $1AB4D5 Bunny Link
05 $DEE5 $1AADDE Lost woods
06 $E36A $1AB263 Legends theme (attract mode)
07 $E8DC $1AB7D5 Kakariko Village
08 $EE11 $1ABD0A Mirror warp
09 $EF6D $1ABE66 Dark World
0A $F813 $1AC70C Restoring the Master Sword
0B $2880 $1A9CE9 Faerie theme
0C $F8F6 $1AC7EF Chase theme
0D $2B00 $1ACCAB Skull Woods
0E $2FA6 $1AD151 Game theme
0F $FAFA $1AC9F3 Intro no Triforce
10 $D046 $1B804A Hyrule Castle
11 $DBEC $1B8BF0 Light World dungeon
12 $E13A $1B913E Caves
13 $E431 $1B9435 Fanfare
14 $E6F9 $1B96FD Sanctuary
15 $E91E $1B9922 Boss theme
16 $EC0B $1B9C0F Dark World dungeon
17 $F1D1 $1BA1D5 Fortune teller
18 - - Caves (song 12)
19 $F304 $1BA308 Zelda rescue
1A $F580 $1BA584 Crystal theme
1B $F909 $1BA90D Faerie theme w/ arpeggio
1C $FB6A $1BAB6E Pre-Agahnim theme
1D $2B00 $1BACC7 Agahnim escape
1E $2F59 $1BB120 Pre-Ganon theme
1F $2BB3 $1BAD7A Ganondorf the Thief
20 $D046 $1AD3CA Triforce room
21 $2900 $1AE3E8 Grabbing Triforce and Epilogue
22 $D2FD $1AD681 Credits

Replace existing songs by using an org command followed by the ROM address. For example, to replace the Skull Woods theme, you should place this at the top of your song source code:

org $1ACCAB
!ARAM = $2B00

If you are replacing every song in a song bank, it is more practical to use a completely custom list to maximize the available space. In this case, neither the org directive nor the !ARAM definition should appear in individual files. Instead, use the songbank macros:

    %songbank(<song_count>)
    %songbankaux()

The songbank macro sets up the pointers at the standard location of $D000 and then populates the section that follows with data. The songbankaux macro gives access to the extra space in lower memory.

Here’s an outline of how this should be set up:

org $1B8000
    %songbank(16)
    incsrc "song01.asm"
    ...
    incsrc "song0A.asm"
    %endbank()

    %songbankaux()
    incsrc "song0B.asm"
    ...
    incsrc "song10.asm"
    %endbank()

    %endtransfer()

Without custom changes, there are only 3 valid locations for song banks:

Location Size (main,aux) Contains
$1A9EF5 (0x2DAE, 0x0688) Overworld songs
$1B8000 (0x2CBF, 0x50C) Underworld songs
$1AD380 (0x1060, 0x1038) Credits songs

The main song banks should never exceed 0x2F00 (12,032) bytes, and auxiliary song banks should never exceed 0x1300 (4,864) bytes. Breaking either of these limits will break the engine. You will also need to take care not to exceed the total length of the banks in ROM unless you are moving song data yourself.

The fairy theme (song 0B) is a special case. This song is always loaded and sits in a song bank of its own. To replace the fairy theme by itself, use the single song transfer method. When building custom banks, use the skipsong macro to avoid overwriting song 0B.


Structural macros

The topmost hierachy of the song should be the song macro which has 3 parameters:

    %song(<SongTitle, <Segments>, <LoopSegment>)

The next level below song is a segment. Segments allow for more complex structure such as repeating a segment in multple parts of a song or playing an introduction a piece only once.

    %segment(<SegmentTitle>)

Segments can be repeated by using the macro repeatsegment, for example:

%song("Example", 4, 1)
    %segment("Intro")
        ...
    %segment("Part1")
        ...
    %segment("Part2")
        ...
    %repeatsegment("Part1")

Within each segment, up to 8 tracks can be defined:

    %segment("Intro")
        %track("Piano1")
            ...
        %track("Piano2")
            ...
        %silenttrack()

        %track("Strings2")

In the example above, three tracks will be defined:

Track 2 will be a silent track. Tracks are always defined in the order they appear. To skip a track, the silenttrack macro must be used.

Tracks 4, 5, 6, and 7 will all be silent as well. Any tracks not defined after the last track will be considered silent.

Attempting to add more than 8 tracks will break something somewhere.

Tracks can be repeated in two ways:

    %repeattrack("TrackName")
    %repeattrackfromseg("SegmentName", "TrackName")

The very end of a song, at the bottom of its source file, should contain the %endsong macro.


Song data

Song data is comprised of several components:

Named notes and rests

The N-SPC engine contains 72 addressable notes, defined by the values 0x80 through to 0xC7. By convention, these are named with scientific pitch notation starting at 0x80=C1 up to 0xC7=B6. This is merely for simplification, as different instruments are tuned and transposed differently.

Notes are played by simply typing them. The example below plays a C-major scale:

    db C3, D3, E3, F3, G3, A3, B3, C4

Take note of the db at the beginning of the line. This directive is required on every data statement. It stands for “define byte” and tells the assembler to put a value directly into the ROM.

“Black key” notes are available with the s or f—for “sharp” and “flat”, respectively—accidentals after the note’s letter name. For example, Cs3 will play the note one half-step above C3, and Af5 will play the note one half-step below A5.

All available note names are defined; e.g., either Cs3 or Df3 can be used despite both compiling to the value 0x99.


Included in this category is the rest, which can be used by typing R.

    db C4, R, B3, R, C4, R

Note value and articulation

Note value

The duration of a note is defined by a number of ticks rather than a fraction of a beat. In our macros, a quarter note is of length 72. This allows the smallest subdivision to be a 32nd note, which will be of length 9. Although this can be further subdivided into a 64th note triplet—using notes of length 3—there exists no 64th note proper. Other subdivisions would be fairly exotic.

Our macros also include definitions for triplets and dotted notes. The full table of available notes is:

Note name Normal Dotted Triplet
Quarter !4 !4d !4t
Eighth !8 !8d !8t
Sixteenth !16 !16d !16t
Thirty-second !32 - !32t

The longest possible duration is one of length 127, just shy of a double-dotted quarter note, which would be 128.

For convenience, common note values are written with an exclamation mark (!) followed by the denominator of the value’s name. For example, as per the table above, a quarter note (1/4 of a common time measure) is written with !4.

    db !4, C4, B3, D4
    db !8, E4, E4, E4

Note value applies until it is changed. In the example above, the first three notes are played as quarter notes, and the next three notes are played as eighth notes.

Notes values without a define—including values equivalent to tied notes—can be used by adding defines together with a plus sign (+). For example, the line below plays several notes each with a duration of a quarter noted tied to a sixteenth note:

    db !4+!16, C4, B3, G3, C4

Ties

Any note whose value would resolve to a number greater than 127 must be performed with a tie, using the !tie define after the note being played. For example, to play a half note:

    db !4, C4, !tie

Ties can be extended indefinitely:

    db !4, C4, !tie, !tie, !tie, !tie, !tie, !tie

And note value can be changed between ties:

    db !4, C4, !tie, !tie, !8, !tie, !16, !tie, !tie, !4, !tie

Ties only apply to the note they follow. The value of subsequent notes will use the base value:

    db !4, C4       ; quarter note
    db B3, !tie     ; half note
    db C4           ; quarter note
    db A3, !tie     ; half note

Articulation

The articulation of a note can be individualized by using an articulation parameter after a duration.

    db !4, $<duration><attack>, C4

This number is written in hexadecimal with the duration being the upper nibble. For example, to have a duration of 5 and an attack of 12, you would want to use $5C.

Articulation parameters have the caveat that they must always follow a note value definition. As such, to change articulation, you must define a note value, even if it should be unchanged:

    db !4, $6C, C4, B3, !4, $7F, C4, !4, $6C, B3, A3

As a shortcut, the define !n can be used for the default value of $7F.


Song commands

Song commands are commands which affect the song as a whole—they apply to every channel. Generally, these commands are best invoked by the first track; however, any track can use them, and it may be more intuitive to have a different track—such as the bassline—call these commands.

Song volume

The volume level of the song as a whole can be changed with the !master_volume command:

    db !master_volume, 180

Most songs in the vanilla game use a master volume of around 170–190.

Master volume can be changed gradually over time with the !master_volume_slide command:

    db !master_volume_slide, <duration [0-255]>, <target [0-255]>

Tempo

The tempo of the song can be changed with the !tempo command.

    db !tempo, bpm(<tempo>)

The exact tempo is not practical to calculate yourself. Instead, use the bpm function (note the lack of % as it is not a macro).

For example, to set the tempo of a song to 120 beats per minute:

    db !tempo, bpm(120)

Tempo can be changed gradually over time with the !tempo_slide command:

    db !tempo_slide, <duration [0-255]>, bpm(<tempo>)

Global transposition

The transposition of an entire song can be changed with the !global_transpose command:

    db !global_transpose, <transposition [-72-72]>

The parameter of this command defines the number of half-steps from the base note to transpose each pitch.

Echo

Echo properties are configured by invoking multiple commands to create a specific set of properties:

    db !echo_set, <channels>, <left_volume [0-255]>, <right_volume [0-255]>
    db !echo_props, <echo_delay [0-2]>, <feedback <0-255>, <filter_id [0-3]>
    db !echo_set, %00101100, $50, $50

⚠WARNING · Setting echo_delay to a value of 3 or higher in the vanilla engine will destroy sample data and be generally unrecoverable.

The echo volume levels can be changed gradually with the !echo_slide_command:

    db !echo_slide, <duration [0-255]>, <left_volume_target [0-255]>, <right_volume_target [0-255]>

Echo can be disabled with the !echo_off command.

Percussion base

The base instrument of percussion notes (discussed below) can be changed with the !setpbase command:

    db !setpbase, <sample id>

Track Commands

Track commands are those which only affect the channel that calls them.

End of part or track

The end of a track or part (covered below) should be marked with the !PART_END command. When any track is ended, every other track is stopped where it is and the entire song proceeds to the next segment. Technically, only the earliest track in each segment needs the !PART_END, but it is good practice to use it for every track.

Instrument

Instruments are set by invoking the !instr command followed by the instrument’s ID. Instruments can be changed at any point during a track; however, the first instance of each track must define an instrument before any notes are played.

    db !instr, <id>

The vanilla instruments along with their define and properties are tabulated below:

ID Name Define ADSR GAIN Transposition
$00 Noise !NOISE $FF,$E0 $B8 $0470
$01 Rain !RAIN $FF,$E0 $B8 $0790
$02 Timpani !TIMPANI $FF,$E0 $B8 $09C0
$03 Square wave !SQUARE $FF,$E0 $B8 $0400
$04 Saw wave !SAW $FF,$E0 $B8 $0400
$05 Clink !CLINK $FF,$E0 $B8 $0470
$06 Wobbly lead !WOBBLE $FF,$E0 $B8 $0470
$07 Compound Saw !COMPSAW $FF,$E0 $B8 $0470
$08 Tweet !TWEET $FF,$E0 $B8 $07A0
$09 Strings A !STRINGSA $8F,$E9 $B8 $01E0
$0A Strings B !STRINGSB $8A,$E9 $B8 $01E0
$0B Trombone !TROMBONE $FF,$E0 $B8 $0300
$0C Cymbal !CYMBAL $FF,$E0 $B8 $03A0
$0D Ocarina !OCARINA $FF,$E0 $B8 $0100
$0E Chimes !CHIMES $FF,$EF $B8 $0EA0
$0F Harp !HARP $FF,$EF $B8 $0600
$10 Splash !SPLASH $FF,$E0 $B8 $03D0
$11 Trumpet !TRUMPET $8F,$E0 $B8 $0300
$12 Horn !HORN $8F,$E0 $B8 $06F0
$13 Snare A !SNAREA $FD,$E0 $B8 $07A0
$14 Snare B !SNAREB $FF,$E0 $B8 $07A0
$15 Choir !CHOIR $FF,$E0 $B8 $03D0
$16 Flute !FLUTE $8F,$E0 $B8 $0300
$17 Oof !OOF $FF,$E0 $B8 $02C0
$18 Piano !PIANO $FE,$8F $B8 $06F0

Transposition

The transposition of each track can be set with the !transpose command to specify the number of half-steps from the base note to transpose each pitch.

    db !transpose, <transposition [-72-72]>

Each channel can be tuned sharper slightly using the !tune command:

    db !tune, <amount [0-255]>

A value of 0 will use the instrument’s default tuning while a value of 255 will be just shy of one half-step sharper.

Glissando

The pitch of a note can be changed gradually using slide commands:

    db !slide_to, <delay [0-255]>, <duration [0-255]>, <change [-72,72]>
    db !slide_from, <delay [0-255]>, <duration [0-255]>, <change [-72,72]>

The !slide_to command will begin on the note specified then slide to the note the number of half-steps away as defined in the parameter. The !slide_from command will start from the away note and slide to the target. For example:

    db !slide_to, 20, 30, +4
    db C4 ; slides C4 to E4
    db B2 ; slides B2 to Ds3
    db !slide_from, 20, 30, +4
    db A3 ; slides Cs4 to A3
    db G5 ; slides B5 to G5

The !slide_to and !slide_from commands apply until another slide command is used to change the pitch slide properties or until disabled with the !disable_slide command.

A single pitch slide can be performed with the !slide_once command:

    db !slide_once, <delay [0-255]>, <duration [0-255]>, <target_note [C0-B6]>

Note that unlike the other slide commands, !slide_once takes the absolute note rather than a relative shift. A single pitch slide will only be played on the note that follows it. If a permanent pitch slide is active, that will take priority.

Track volume

The volume of each track can be set individually.

    db !volume, <level [0-255]>

An individual track’s volume can be changed gradually as well:

    db !volume_slide, <duration [0-255]>, <target [0-255]>

Panning

Panning allows a track to change the volume ratio between the left and right channels.

    db !pan, <level [0-20]>

The default pan level is 10, giving both channels equal volumes. Lower values pan towards the right, with 0 producing sound out of only the right channel. Higher values pan towards the left, with 20 producing sound out of only the left channel. The defines !pan0, !panR, and !panL are provided for these values, respectively.

Panning can be changed gradually as well:

    db !pan_slide, <duration [0-255]>, <target [0-20]>

Tremolo

Tremolo on a channel causes its volume to gradually oscillate.

    db !tremolo, <delay [0-255]>, <period [0-255]>, <amplitude [0-255]>

Tremolo stays active on a channel until disabled or changed. To disable tremolo, use the !tremolo_off command.

Vibrato

Vibrato on a channel causes the pitch it plays to gradually oscillate.

    db !vibrato, <delay [0-255]>, <period [0-255]>, <amplitude [0-255]>

Vibrato stays active on a channel until disabled or changed. To disable vibrato, use the !vibrato_off command.

    db !vibrato, 5, 20, 50
    db C4, D4, E4
    db !vibrato_off
    db D4, Fs4

The amplitude of vibrato can be changed by itself with the !vibrato_level command:

    db !vibrato_level, <amplitude [0-255]>

Percussive notes

A single C4 note can be played with a specific sample with one command. This is theoretically useful for percussion tracks. The percussion commands are named !perc<id> with IDs from 00 up through 15 in hexadecimal. This ID is added to the percussion base to define the sample to switch to and play. For example:

    db !setpbase, 3
    db !perc00 ; plays sample 3 (0+3)
    db !perc0B ; plays sample 14 (11+3)
    db !setpbase, 0
    db !perc0B ; plays sample 11 (11+0)

The sample changed to by a percussion hit will persist until it is changed again with another percussive hit or the !instr command.


Subroutines

Segments of music can be repeated by defining subparts and calling them:

    %part("<PartTitle>")
        <musical_data>
        db !PART_END

    %playpart("<PartTitle>", <count>)

Use the part macro anywhere to mark a location that can be called elsewhere. Parts can be defined as isolated segments of data or reusing other parts; however, a part must always be ended with the !PART_END command.

    %track("Piano")
        %playpart("Gees", 3) ; Calls a part that is isolated

        %playpart("Bees", 3) ; calls a portion of its own track

        %playpart("Gee1", 3) ; Calls a part that is a shorter part of another part

    %part("Bees") ; This is also the end of the "Piano" teack
        db B3, B3, B3
        db !PART_END

    %part("Gees") ; This part is completely isolated
        db G3, G3
    %part("Gee1") ; This part shares with "Gees"
        db G3
        db !PART_END

In the example above, the !PART_END when the “Bees” subroutine is called will simply mark the end of the part; however, when it is encountered again, it will end the segment, because it is no longer being invoked while in a subroutine. The “Gees” subroutine will play the note G3 for each time it is repeat, and “Gee1” will play the note G3 only once per repeat. Overall, this song plays:

Parentheses indicate the separation of parts in our example.


Parts are essentially decompiled ahead of time to create a coherent stream of data. For example, the data below is perfectly valid:

    %track("Example")
        db !4, !n
        db A3, B3
        %playpart("Scale", 1)
        db !tie, !tie, !tie
        %playpart("Scale", 3)
        db !tie
        db !PART_END

    %part("Scale")
        db C4, D4, E4, Fs4
        db !PART_END

In this example, the last note played during the subroutine will have its duration extended by the !tie commands that follow the subroutine call. The second call site repeats the “Scale” subroutine 3 times. The first two times the part is repeated, the Fs4 will be played as a quarter note; for the final repeat, it will be played as a half note (quarter note plus one tie), because only then after all repeats are played will it continue to the data that follows.

Only one part can be played per channel. Attempting to call a subroutine while already inside another will cause the original subroutine to be forgotten.


Initialization

The state of any given channel will be in an indeterminate state the first time it is played. This is also true of the song as a whole. At minimum, the following properties should be set at the beginning of every song and track:

For songs:

For tracks:

One way this can be accomplished is by using segment 0 as an initialization pickup:

%song("MySongIsFreakinAwesome", 2, 1)
    %segment("init")
        %track("Piano1")
            db !master_volume, 180
            db !tempo, bpm(160)
            db !echo_off
            db !transpose, -12
            db !instrument, !PIANO
            db !volume, 180
            db !32, !n, R
            db !PART_END

        %track("Piano2")
            db !transpose, 0
            db !instrument, !PIANO
            db !volume, 180
            db !32, !n, R
            db !PART_END

        %track("Piano3")
            db !transpose, 12
            db !instrument, !PIANO
            db !volume, 200
            db !32, $59, R
            db !PART_END

    %segment("main")
        %track("Piano1")
            ...

        %track("Piano2")
            ...

        %track("Piano3")
            ...

Take note that for every channel to run its data, they must include a very short rest; otherwise the segment will be immediately ended by the first channel.

For songs with an intro that is only played once, a dedicated initialization segment is not necessary:

%song("MySongIsFreakinAwesome", 2, 1)
    %segment("init")
        %track("Piano1")
            db !master_volume, 190
            db !tempo, bpm(112)
            db !echo_off
            db !transpose, 0
            db !instrument, !PIANO
            db !volume, 180
            db !4, !n
            db C4, G4, A4
            db B4, !tie
            db !PART_END

        %track("Piano2")
            db !transpose, 0
            db !instrument, !PIANO
            db !volume, 180
            db !4, !n
            db A3, E4, D4
            db Fs4, !tie
            db !PART_END

        %track("Piano3")
            db !transpose, -12
            db !instrument, !PIANO
            db !volume, 200
            db !8, !n
            db C4, C5, C4, E4, E5, E4
            db !4, B4, R
            db !PART_END

    %segment("main")
        %track("Piano1")
            ...

        %track("Piano2")
            ...

        %track("Piano3")
            ...

Testing songs

I have provided a very easy method of testing songs with the file songlisten.bat. Have this file in the same folder as your song and an ALTTP ROM image named alttp.sfc. Then drag your song’s source .asm file over songlisten.bat each time you want to compile and test. This will create a file named songlisten.sfc that can be run in an emulator to hear your song.

When a song is finished, it can either be included directly as an asm file or compiled using the compilesong.bat file. Select every song to compile and drag it onto compilesong.bat to create .bin files of each song. These can then be included with the incbin directive or copied directly into a file (not recommended).

I have also provided three example songs:

You are free to use these songs in your own hacks (so long as I am credited, preferably as “kan”).

Bugs

The vanilla N-SPC engine has a variety of bugs, of particular note is that the following properties are inherited by sound effects from the music channels:

I offer an improved engine that fixes these bugs, plus other things. You can use this engine yourself by putting the following in your main asm patch:

check bankcross off
org $19FBCE : incbin "newspc.bin"
check bankcross on

Improvements:

Custom instruments

Custom instruments are difficult to add because ALTTP likes having all of its instruments loaded at the same time, leaving little room for swapping samples out. For those brave enough to attempt this, I offer this template and BRRSuiteGUI. The latter is a custom tool for creating and previewing SNES sound samples in a more comfortable environment.

I may expand this section in the future…