Music driver
The routine that runs a game's music once a frame, reading note and length bytes per channel and writing the sound chip's registers.
A music driver turns song data into register writes. The NES’s audio unit offers two pulse channels with duty settings of 12.5%, 25%, 50%, 75%1, a triangle with a fixed waveform2, a noise channel3, and a sample channel4. A driver called once a frame decides, channel by channel, when each note starts, what period it plays at, and how its volume moves.
- Length tableSuper Mario Bros. (1985). Each song times its notes in whole frames from its own row of lengths; the hurry-up tunes take a faster row.5,6,7,8,9The Legend of Zelda (1986). Super Mario Bros.' layout: per-song length rows, big-endian periods.10
- Fractional tempoSuper Mario World (1990). A 2 ms timer tick adds the tempo to an 8-bit accumulator, and each carry calls the sound engine's step.11Pokémon Red and Blue (1996). A note lasts length × speed × tempo / 256 frames, the remainder carried to the next.12
#Super Mario Bros.
The NMI handler calls the sound engine every frame, and the music handler steps square 2, square 1, the triangle, and the noise channel in that order5. Each channel reads a byte when its counter reaches 0; on square 2 and the triangle a negative byte sets a new length and a positive one plays a note5.
- Tempo. The song’s header picks a row of the length table, and the overworld theme’s row is 54, 3, 9, 6, 18, 27, 36, 12 frames6. The hurry-up version moves to a faster row6.
- Pitch. A note byte indexes a table of timer periods7, and a square channel plays period at 13; the triangle plays the same period an octave lower2.
- Envelope. A square note’s volume follows a table one step per frame: volumes 0, 8, 7, 6, 5, 5, 4, 4, then 0, at 50% duty8.
- Drums. The noise channel plays three drum sounds, written to its registers as
$400C,$400E,$400F: short$1C $03 $18, strong$1C $0C $18, long$1C $03 $589.
#Pokémon Red and Blue
The Game Boy has two pulse channels, a wave channel that plays a wave the program supplies, and noise14. Pokémon’s engine runs once a frame and times notes with a fraction: a note lasts frames, and the remainder carries to the next note12.
#Super Mario World
Super Mario World’s sound engine runs on a timer. Each tick adds the song’s tempo byte to an 8-bit accumulator, and each carry calls APU_0BC0, a routine the atlas hasn’t read11.
Games
| Game | Year | How | Note |
|---|---|---|---|
| Super Mario Bros. | 1985 | Length table | each song times its notes in whole frames from its own row of lengths; the hurry-up tunes take a faster row |
| The Legend of Zelda | 1986 | Length table | Super Mario Bros.' layout: per-song length rows, big-endian periods |
| Super Mario World | 1990 | Fractional tempo | a 2 ms timer tick adds the tempo to an 8-bit accumulator, and each carry calls the sound engine's step |
| Pokémon Red and Blue | 1996 | Fractional tempo | a note lasts length × speed × tempo / 256 frames, the remainder carried to the next |
Every claim about this mechanic, by game (50)
References
- ^The APU has two pulse channels, each with four duty settings, 12.5%, 25%, 50%, and 25% negated (75%), as 8-step sequences.WikiNESdev Wiki, “APU Pulse”
How we know
Each of the two NES APU pulse (square) wave channels generate a pulse wave with variable duty.
lead · archived copy ↗ 1 0 0 1 1 1 1 1 (25% negated)
Sequencer behavior · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abThe triangle channel steps a fixed 32-step sequence, 15 down to 0 and back up, at , so it has no duty or volume setting.WikiNESdev Wiki, “APU Triangle”
How we know
The sequencer sends the following looping 32-step sequence of values to the mixer: … f = fCPU ÷ (32 × (tval + 1)) … The triangle channel contains the following: timer, length counter, linear counter, linear counter reload flag, control flag, sequencer.
lead · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^The noise channel clocks a 15-bit shift register at one of 16 periods; in normal mode the sequence is 32,767 steps long, and the periodic mode gives 93- or 31-step loops.WikiNESdev Wiki, “APU Noise”
How we know
The period determines how many CPU cycles happen between shift register clocks.
Noise mode and period ($400E, write) · archived copy ↗ This results in a pseudo-random bit sequence, 32767 steps long when Mode flag is clear, and randomly 93 or 31 steps long otherwise.
Operation · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^The delta modulation channel (DMC) plays 1-bit delta-encoded samples from , bytes long, at one of 16 rates; a program can also load its 7-bit output level directly through
$4011.WikiNESdev Wiki, “APU DMC”How we know
The NES APU's delta modulation channel (DMC) can output 1-bit delta-encoded samples or can have its 7-bit counter directly loaded, allowing flexible manual sample playback.
lead · archived copy ↗ Sample address = %11AAAAAA.AA000000 = $C000 + (A * 64) … Sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes
Overview · archived copy ↗ For example, on NTSC (1.789773 MHz), a rate of 428 gives a frequency of 1789773/428 Hz = 4181.71 Hz.
Pitch table · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abcThe NMI handler calls the sound engine once a frame; its music handler steps square 2, square 1, the triangle, and the noise channel in turn, and each channel counts its note length down by 1 per frame and reads its next byte at 0.CodeXkeeper0/smb1
How we know
115 JSR SoundEngine ; play soundsrc/prg.asm line 115 at 1cc3c92 · upstream ↗ 691HandleSquare2Music: 692 DEC Squ2_NoteLenCounter ; decrement square 2 note length 693 BNE MiscSqu2MusicTasks ; is it time for more data? if not, branch to end tasks 694 LDY MusicOffset_Square2 ; increment square 2 music offset and fetch data 695 INC MusicOffset_Square2 696 LDA (MusicData),y 697 BEQ EndOfMusicData ; if zero, the data is a null terminator 698 BPL Squ2NoteHandler ; if non-negative, data is a note
src/music-engine.asm lines 691–698 at 1cc3c92 · upstream ↗ 763HandleSquare1Music: 764 LDY MusicOffset_Square1 ; is there a nonzero offset here? 765 BEQ HandleTriangleMusic ; if not, skip ahead to the triangle channel 766 DEC Squ1_NoteLenCounter ; decrement square 1 note length
src/music-engine.asm lines 763–766 at 1cc3c92 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abcA song's header picks an 8-entry row of
MusicLengthLookupTbl, and a length code of 0-7 indexes it in frames; the overworld theme's row is 54, 3, 9, 6, 18, 27, 36, 12, a quarter note of 18 frames, and the hurry-up version adds 8 to the row offset for 36, 2, 6, 4, 12, 18, 24, 8.CodeXkeeper0/smb1How we know
34; header format is as follows: 35; 1 byte - length byte offset
src/music-data.asm lines 34–39 at 1cc3c92 · upstream ↗ 662LoadHeader: 663 LDA MusicHeaderOffsetData,y ; load offset for header 664 TAY 665 LDA MusicHeaderData,y ; now load the header 666 STA NoteLenLookupTblOfs
src/music-engine.asm lines 662–666 at 1cc3c92 · upstream ↗ 521NoteLenLookupTblOfs = $f0src/ram.asm line 521 at 1cc3c92 · upstream ↗ 63GroundLevelPart1Hdr: 64 .db $18, <GroundM_P1Data, >GroundM_P1Data, $2d, $1c, $b8
src/music-data.asm lines 63–64 at 1cc3c92 · upstream ↗ 377MusicLengthLookupTbl: 378 .db $05, $0a, $14, $28, $50, $1e, $3c, $02 379 .db $04, $08, $10, $20, $40, $18, $30, $0c 380 .db $03, $06, $0c, $18, $30, $12, $24, $08 381 .db $36, $03, $09, $06, $12, $1b, $24, $0c 382 .db $24, $02, $06, $04, $0c, $12, $18, $08 383 .db $12, $01, $03, $02, $06, $09, $0c, $04
src/music-data.asm lines 377–383 at 1cc3c92 · upstream ↗ 925ProcessLengthData: 926 AND #%00000111 ; clear all but the three LSBs 927 CLC 928 ADC $f0 ; add offset loaded from first header byte 929 ADC NoteLengthTblAdder ; add extra if time running out music
src/music-engine.asm lines 925–933 at 1cc3c92 · upstream ↗ 625 CMP #TimeRunningOutMusic ; is it time running out music? 626 BNE FindEventMusicHeader 627 LDX #$08 ; load offset to be added to length byte of header
src/music-engine.asm lines 625–627 at 1cc3c92 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-25.
Checked by a second AI model, 2026-10-21.
Checked by a second AI model, 2026-10-25.
- ^ abA note byte is an offset into
FreqRegLookupTbl, a table of 51 two-byte timer periods that the driver writes to the channel's timer registers; a recreation turns period into Hz on a square channel and half that on the triangle.CodeXkeeper0/smb1 · NESdev Wiki, “APU Pulse” · NESdev Wiki, “APU Triangle”How we know
107Dump_Freq_Regs: 108 TAY 109 LDA FreqRegLookupTbl+1,y ; use previous contents of A for sound reg offset 110 BEQ NoTone ; if zero, then do not load 111 STA SND_REGISTER+2,x ; first byte goes into LSB of frequency divider 112 LDA FreqRegLookupTbl,y ; second byte goes into 3 MSB plus extra bit for
src/music-engine.asm lines 107–115 at 1cc3c92 · upstream ↗ 362FreqRegLookupTbl: 363 .db $00, $88, $00, $2f, $00, $00 364 .db $02, $a6, $02, $80, $02, $5c, $02, $3a ⋮ 375 .db $02, $f9, $02, $cf, $01, $fc, $00, $6a
src/music-data.asm lines 362–375 at 1cc3c92 · upstream ↗ fpulse = fCPU/(16*(t+1))
Pulse channel output to mixer · archived copy ↗ f = fCPU ÷ (32 × (tval + 1))
lead · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abA new area-music square note sets duty and volume byte
$82and an envelope index of 8; each frame the driver writesAreaMusicEnvData[index]to the channel and counts the index down, so the note plays at 50% duty with volumes 0, 8, 7, 6, 5, 5, 4, 4 and then 0 until the next note.CodeXkeeper0/smb1How we know
934LoadControlRegs: ⋮ 944 LDA #$08 ; this is the default value for all other music ⋮ 949 LDX #$82 ; load contents of other sound regs for square 2 950 LDY #$7f
src/music-engine.asm lines 934–952 at 1cc3c92 · upstream ↗ 754 LDY Squ2_EnvelopeDataCtrl ; check for contents saved from LoadControlRegs 755 BEQ NoDecEnv1 756 DEC Squ2_EnvelopeDataCtrl ; decrement unless already zero 757NoDecEnv1: 758 JSR LoadEnvelopeData ; do a load of envelope data to replace default 759 STA SND_SQUARE2_REG ; based on offset set by first load unless playing
src/music-engine.asm lines 754–759 at 1cc3c92 · upstream ↗ 388AreaMusicEnvData: 389 .db $90, $94, $94, $95, $95, $96, $97, $98 ⋮ 392 .db $90, $91, $92, $92, $93, $93, $93, $94
src/music-data.asm lines 388–392 at 1cc3c92 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abThe noise channel plays three drum sounds, all at constant volume 12 (
$400C=$1C), a short beat at period index 3, a strong beat at period index 12, and a long beat at period index 3 with a longer length-counter load ($58instead of$18).CodeXkeeper0/smb1How we know
889 LDA #$1c ; short beat data 890 LDX #$03 891 LDY #$18 892 BNE PlayBeat ⋮ 894StrongBeat: 895 LDA #$1c ; strong beat data 896 LDX #$0c 897 LDY #$18 898 BNE PlayBeat ⋮ 900LongBeat: 901 LDA #$1c ; long beat data 902 LDX #$03 ⋮
src/music-engine.asm lines 888–913 at 1cc3c92 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abThe music driver has Super Mario Bros.' layout; a song entry sets a base into a note-length table, a length code's low 3 bits index it, notes index a table of big-endian 16-bit timer periods, and a square note starts with duty byte
$82and sweep byte$7F, the same register values Super Mario Bros. uses.Codealdonunez/zelda1-disassembly · Xkeeper0/smb1How we know
786PrepPhrase: 787 LDA SongTable-1, Y 788 TAY 789 LDA SongTable, Y 790 STA NoteLengthTableBase
src/Z_00.asm lines 786–790 at 50a1c86 · upstream ↗ 1087GetSongNoteLength: 1088 AND #$07 1089 CLC 1090 ADC NoteLengthTableBase 1091 TAY 1092 LDA NoteLengthTable0, Y
src/Z_00.asm lines 1087–1092 at 50a1c86 · upstream ↗ 1112; Big-endian 16-bit period values. ⋮ 1114NotePeriodTable: 1115 .BYTE $00, $23, $00, $6A, $03, $27, $00, $97 1116 .BYTE $00, $00, $02, $F9, $02, $CF, $02, $A6 1117 .BYTE $02, $80, $02, $5C, $02, $3A, $02, $1A 1118 .BYTE $01, $FC, $01, $DF, $01, $C4, $01, $AB
src/Z_00.asm lines 1112–1118 at 50a1c86 · upstream ↗ 1136PrepareCustomSongEnvelope: 1137 LDA SongEnvelopeSelector 1138 LDA #$20 1139 LDX #$82 1140 LDY #$7F
src/Z_00.asm lines 1136–1140 at 50a1c86 · upstream ↗ 949 LDX #$82 ; load contents of other sound regs for square 2 950 LDY #$7f
src/music-engine.asm lines 949–950 at 1cc3c92 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abThe engine's main loop waits on timer 0, set to tick every 2 ms; for each tick it adds the song's tempo byte to an 8-bit accumulator, and on a carry out it calls the routine APU_0BC0, unless a flag at audio RAM
$0388is set. That routine wasn't read; the structure suggests it is one step of the music, which would make the tempo set the step rate. The tempo starts at$36, which gives 500 × 54 / 256, about 105 carries a second.CodeSMWDisXrevisedHow we know
56 MOV A,#$10 57 MOV.W HW_TIMER0,A ; set timer0 freq to 2ms 58 MOV A,#$36 59 MOV.B MasterTempo+1,A ; set $51 to #$36 60 MOV A,#$01 61 MOV.W HW_SPCCONTROL,A ; start timer 0
bank_0E.asm lines 56–62 at 30643c7 · upstream ↗ 63APU_Loop: 64 MOV.W Y,HW_COUNTER0 ; main loop 65 BEQ APU_Loop ; wait for counter 0 increment; ⋮ 68 MUL YA 69 CLRC ⋮ 83 + MOV.B A,MasterTempo+1 84 POP Y 85 MUL YA 86 CLRC 87 ADC.B A,ARam_49 88 MOV.B ARam_49,A 89 BCC APU_058D ⋮
bank_0E.asm lines 63–95 at 30643c7 · upstream ↗ 1294 CALL NextVCmd46 ; vcmd E2: tempo; ⋮ 1297 MOV.B MasterTempo+1,A 1298 MOV.B MasterTempo,#$00
bank_0E.asm lines 1294–1298 at 30643c7 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Revised after a check by a second AI model, 2026-10-21.
- ^ abThe sound engine runs once a frame from the vertical blank handler. A note lasts frames, where is its length in 16ths, the channel's note speed, the song's 16-bit tempo, and the channel's leftover fraction, which the low byte carries to the next note.Codepret/pokered
How we know
59 cp BANK(Audio1_UpdateMusic) 60 jr nz, .checkForAudio2 61.audio1 62 call Audio1_UpdateMusic ⋮ 64.checkForAudio2
home/vblank.asm lines 59–72 at af51989 · upstream ↗ 41 ld hl, wChannelNoteDelayCounters ; delay until next note ⋮ 44 cp 1 ; if the delay is 1, play next note 45 jp z, Audio1_PlayNextNote 46 dec a ; otherwise, decrease the delay timer
audio/engine_1.asm lines 39–47 at af51989 · upstream ↗ 690Audio1_note_length: 691 ld a, d ⋮ 696 ld e, a ; store note length (in 16ths) ⋮ 698 ld hl, wChannelNoteSpeeds ⋮ 702 call Audio1_MultiplyAdd ⋮ 706 ld a, [wMusicTempo] ⋮ 709 ld e, a ⋮ 720 ld e, a ⋮ 722 ld a, l ; a = note_length * note_speed ⋮ 724 ld hl, wChannelNoteDelayCountersFractionalPart ⋮ 727 call Audio1_MultiplyAdd ⋮ 729 ld d, h ; de = note_delay_frac_part + (note_length * note_speed * tempo) ⋮
audio/engine_1.asm lines 690–737 at af51989 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^A pulse channel's frequency is for an 11-bit timer period , with = 1.789773 MHz on NTSC; periods below 8 silence the channel.WikiNESdev Wiki, “APU Pulse”
How we know
fpulse = fCPU/(16*(t+1)) (where fCPU is 1.789773 MHz for NTSC … A period of t < 8, either set explicitly or via a sweep period update, silences the corresponding pulse channel.
Pulse channel output to mixer · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^The Game Boy has four sound channels, each specialized, with two pulse channels with four fixed duty settings, a wave channel that plays a user-supplied wave, and a noise channel.WikiPan Docs, “Pan Docs: Audio Overview”
How we know
Channels 1 and 2, the “pulse channels”, produce pulse width modulated waves with 4 fixed pulse width settings. Channel 3, the “wave” channel, produces arbitrary user-supplied waves. Channel 4 is the “noise” channel, producing a pseudo-random wave.
Architecture · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
Sources
Code
- doppelganger and Xkeeper. 2022. “smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1).” Commit 1cc3c92. Xkeeper0/smb1 @ 1cc3c92 · licence, all uses
- pret contributors. 2026. “pokered: disassembly of Pokémon Red and Blue.” pret. Commit af51989. pret/pokered @ af51989 · licence, all uses
- IsoFrieze, kaizoman666, mikeyk, and loveemu. 2026. “SMWDisX: Super Mario World disassembly (IsoFrieze/SMWDisX).” Commit 30643c7. IsoFrieze/SMWDisX @ 30643c7 · licence, all uses
- Núñez, Aldo. 2021. “zelda1-disassembly: A complete disassembly of The Legend of Zelda.” Commit 50a1c86. aldonunez/zelda1-disassembly @ 50a1c86 · licence, all uses
Secondary
- NESdev Wiki contributors. n.d. “APU Pulse.” NESdev Wiki. Version oldid 23444. original · archived · licence, all uses
- NESdev Wiki contributors. n.d. “APU Triangle.” NESdev Wiki. Version oldid 23816. original · archived · licence, all uses
- NESdev Wiki contributors. n.d. “APU Noise.” NESdev Wiki. Version oldid 24381. original · archived · licence, all uses
- NESdev Wiki contributors. n.d. “APU DMC.” NESdev Wiki. Version oldid 23783. original · archived · licence, all uses
- Pan Docs contributors. n.d. “Pan Docs: Audio Overview.” Pan Docs (gbdev.io). original · archived · licence, all uses