Still workin' on it...
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 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.
NESMUS has the following features:
.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 SndLoadMusicwhere
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
.
dd
: duty cycle/noise type
L
: if set, following byte specifies duration in frames; otherwise hold for one frame
vvvv
: 4-bit volume value
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