NESMUS (pronounced "NEZ-mus") is an NES/Famicom sound driver, free for use in NES homebrew projects. The source code assembles with the ASM6 assembler (which is not mine, but I'm mirroring it for convenience.)

Download

Download the source code for NESMUS and a simple demonstration project here (demo.65s and data.65s comprise the demo project). The assembled demo ROM is here.

Features

NESMUS has the following features:

Using NESMUS in your project

.include all files in your project (data.65s is optional, but SndEnvelopeTable label must be defined somewhere in your project). Do jsr SndInit during your startup code to initialize, and call jsr SndUpdate once per frame to run the sound engine.

vars.65s declares all variables used by the sound engine. Feel free to move them around as needed.

Virtual channels are numbered 0-7. Channels 0,1,2,3 are the music on the two pulse, triangle, and noise channels respectively. Add 4 to get the corresponding sound effect channel.

To play a music track, do the following:

    lda #<YourMusic_Header  ; music header ptr low byte
    ldx #>YourMusic_Header  ; music header ptr high byte
    jsr SndLoadMusic
where YourMusic_Header points to the structure
YourMusic_Header
  db $04            ; initial tempo minus one
  dw YourMusic_Ch0  ; pulse 1 stream data
  dw YourMusic_Ch1  ; pulse 2 stream data
  dw YourMusic_Ch2  ; triangle stream data
  dw YourMusic_Ch3  ; noise stream data

To play a sound effect, use the following code:

    lda #<YourSfx  ; stream data ptr low byte
    ldy #>YourSfx  ; stream data ptr high byte
    ldx #4         ; channel to play SFX on
    jsr SndLoadSfx

The note data stream consists of the following commands. For commands 5n,6n,7n,8n,9n, if n is $0-E, then that is the command parameter; if it is $F, then the following byte contains an 8-bit parameter.

00-4F       Play note
5n          Cut note, rest for n+1 ticks
6n          Release note, rest for n+1 ticks
7n          Hold previous note for n+1 ticks
8n          Set length in ticks for following notes (length = n+1)
9n          Set length in ticks for next note only  (length = n+1)
Ax          Set note velocity
Bx          Use envelope $0x (0-15)
Cx          Play DMC percussion sample (currently unimplemented)
D0-EF       Set tempo to (byte & $1F) + 1 frames per tick
F0 pppp     Call subroutine (no nested calls!)
F1          Return from subroutine
F2 xx       Set loop start and count
F3          End loop
F4 xx       Cut note, rest for xx+1 frames
F5 xx       Release note, rest for xx+1 frames
F6 xx       Hold previous note for xx+1 frames
F7 pppp     Jump to address
F8          Turn off pitch effect
F9 xy       Pitch effect/Vibrato, rate=x, depth=y
FA xx       Pitch effect/Sweep down, delta=xx
FB xx       Pitch effect/Sweep up, delta=xx
FC xx       Set rate for pitch bends
FD xx       Set pitch effect delay (frames after note on)
FE xx       Use envelope $xx (0-127)
FF          End of stream, silence and disable this channel

Parsing of note commands continues until a note, rest, hold, or $FF command is found.

The envelope data stream consists of a string of bytes, in the format dd-Lvvvv.

Additionally, bytes F0-F7 are special commands:

F0      Hold forever
F1      Hold until note release
F2 xx   Set loop start and count
F3      End loop
F4      Loop if note not yet released
F5      Loop forever
F6 xx   Relative pitch bend down
F7 xx   Relative pitch bend up