::|CONTENTS
Overview
Resources
Example code
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; This program plays a square wave BRR sample on the SNES APU channel 0
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
org $8000
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Memory address map
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
!AUDIO_MASTER_TICK_CLOCK = $00 ; Zero page address 0x00
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; The BRR sample used in this example
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
brr_sample: ; to be sent at address 0x200 of SNES APU
dw $0204 ; start point of this sample
dw $0204 ; loop start point of this sample
db $c3 ; start of 9 byte block, shifts left 12 bits and sets loop point and sample end flags
db $77,$77,$77,$77,$99,$99,$99,$99
brr_sample_end:
!brr_sample_len = brr_sample_end-brr_sample ;Used to calculate its byte length
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; The code begins executing from here
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
reset_6502_mode_vector:
sei ; Turns off IRQs
cld ; Turns off decimal mode
clc
xce ; set processor 65c816 mode
sep #$30 ; set accumulator and index registers to be 8-bit wide each instead of 16-bit
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Upon turning on the SNES, waiting for $2140 to be $aa and $2141 to read $bb indicateS the SNES APU to be ready
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.wait_for_spu_startup_signal:
lda $2140
cmp #$aa ; The first signal that the SNES APU sends
bne .wait_for_spu_startup_signal
lda $2141
cmp #$bb ; The second signal that the SNES APU sends
bne .wait_for_spu_startup_signal
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Send the $cc to 0x2140 is part of the SNES APU initialization procedure
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
lda #$cc ; It will be sent to, in the next send_snes_apu_command_and_wait subroutine
sta !AUDIO_MASTER_TICK_CLOCK
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Set the address to send the BRR sample to SNES APU memory at
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
stz $2142 ; low byte of address to send data at
lda #$02 ; high byte of address to send sample data: SNES APU address 0x0200 is above its i/o ports and stack
sta $2143 ; high byte of address to send data at
jsr send_snes_apu_command_and_wait ; send start command: Register a is non-zero
stz !AUDIO_MASTER_TICK_CLOCK ; set master tick clock to zero
ldy #$00 ; byte index of the sample
.send_next_byte_of_brr_sample
lda brr_sample, y ; send a byte of the BRR sample to the SNES APU memory
jsr send_snes_apu_command_and_wait
iny ; increase the byte index of the sample
cpy.b #!brr_sample_len
bne .send_next_byte_of_brr_sample
inc !AUDIO_MASTER_TICK_CLOCK
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Set SNES APU registers to produce a sound
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ldx #$6c ; FLG DSP register
ldy #$80 ; All envelopes are set to zero, all eight channels are set key off
jsr write_to_snes_apu_register
ldy #$30 ; echo buffer writes are enabled, mute amplifier is set to mute, and noise frequency is set to 0
jsr write_to_snes_apu_register
ldx #$5d ; address of the sample table DSP register
ldy #$02 ; high byte of address of the sample table, low byte is 0x00
jsr write_to_snes_apu_register
ldy #$20 ; master volume value
ldx #$0c ; channel 0 master volume left master
jsr write_to_snes_apu_register
ldx #$1c ; channel 0 master volume right master
jsr write_to_snes_apu_register
ldx #$04 ; instrument number for channel 0 DSP register
ldy #$00 ; first instrument
jsr write_to_snes_apu_register
ldx #$05 ; ADSR setting DSP register for channel 0: $00 sets it to use GAIN instead of ADSR
jsr write_to_snes_apu_register
ldx #$07 ; GAIN value DSP register for channel 0
ldy #$1f ; Up to $7f
jsr write_to_snes_apu_register
ldy #$7f ; maximum volume value
ldx #$00 ; channel 0 left volume DSP register
jsr write_to_snes_apu_register
ldx #$01 ; channel 0 right volume DSP register
jsr write_to_snes_apu_register
ldx #$4c ; Key on DSP register
ldy #$01 ; Set channel 0 key on
jsr write_to_snes_apu_register
ldx #$02 ; low byte of channel 0 pitch DSP register
ldy #$00
jsr write_to_snes_apu_register
ldx.b #$03 ; high byte of channel 0 pitch DSP register
ldy.b #$04
jsr write_to_snes_apu_register
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; End of code flow
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
main_loop:
bra main_loop
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; A subroutine to send a command to the SNES APU and wait for it to be ready
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
send_snes_apu_command_and_wait:
sta $2141 ; command or data
lda !AUDIO_MASTER_TICK_CLOCK ; set the clock: The first time, sending 0xcc is part of the initialization procedure
sta $2140 ; set the master clock
inc !AUDIO_MASTER_TICK_CLOCK ; increase the master clock
.wait_for_master_clock:
cmp $2140 ; wait for it to be the same as master clock
bne .wait_for_master_clock
rts
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; A subroutine for the SNES APU to write at its DSP registers
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
write_to_snes_apu_register:
lda #$f2 ;\
sta $2142 ; Low byte of destination to SNES APU, $f2 is DSP index register
stz $2143 ; High byte of destination to SNES APU
jsr send_snes_apu_command_and_wait ; begin command, $f2 being non-zero
stz !AUDIO_MASTER_TICK_CLOCK ; Initialize clock
; send a byte to DSP index register, $f2
txa
jsr send_snes_apu_command_and_wait
; send a byte to DSP data register, $f3
tya
jsr send_snes_apu_command_and_wait
inc !AUDIO_MASTER_TICK_CLOCK ; this clock must be bigger than before, and higher than $00
rts
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Interrupt vectors
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
cop_65c816_mode_vector:
brk_65c816_mode_vector:
nmi_65c816_mode_vector:
irq_65c816_mode_vector:
cop_6502_mode_vector:
nmi_6502_mode_vector:
irq_and_brk_6502_mode_vector:
rti
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; Start SNES "header" data
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
org $ffc0
snes_header:
db "SOUND ENGINE TEST " ; 21 bytes upper case title padded with spaces
db $20 ; rom makeup or rom speed, and map mode
db $00 ; chipset rom and ram information
db $05 ; denotes ROM size
db $00 ; denotes RAM size
db $00 ; country: Information one may associate with NTSC or PAL paradigms
db $00 ; informs developer ID code
db $00 ; informs software version
dw $ffff ; checksum complement: the checksum field XORd with $ffff
dw $0000 ; checksum of all bytes in ROM, with checksum complement field as $ffff, and checksum field as $0000
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; SNES vector addresses
; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
dd $00000000 ; may denote wram-bootable file
dw cop_65c816_mode_vector ; COP opcode vector
dw brk_65c816_mode_vector ; BRK opcode vector
fill 2 ; abort vector, unused in SNES
dw nmi_65c816_mode_vector ; v-blank interrupt
fill 2 ; unused
dw irq_65c816_mode_vector ; h-vtimer, or external interrupt
; 6502 mode vectors
fill 4 ; unused
dw cop_6502_mode_vector ; COP opcode vector
fill 2 ; unused
fill 2 ; abort vector, unused in SNES
dw nmi_6502_mode_vector ; v-blank interrupt
dw reset_6502_mode_vector ; entry point
dw irq_and_brk_6502_mode_vector ; external interrupt or BRK opcode