gb-tarot/Audio.inc
2025-04-25 20:24:12 -04:00

633 lines
12 KiB
PHP

SECTION "Audio Interface", ROM0
SoundSetup:
ld a, BANK(AudioEngineInit)
ld [rROMB0], a
ld de, gb_tarot_theme
call AudioEngineInit ; Play song
ld a, [cvCardBank]
ld [rROMB0], a
ret
SoundUpdate:
ld a, BANK(AudioEngineUpdate)
ld [rROMB0], a
;call AudioEngineUpdate
ld a, [cvCardBank]
ld [rROMB0], a
ret
SECTION "Audio Variables", WRAM0[AUDIO_VARS_START]
ordersBank: db
orders: dw
speed: db
readHead: dw
tick: db
println "ticks are at ", tick
row: db
println "rows are at ", row
order: db
println "order index at ", order
; i've chosen to pack the variables for each instrument sequentially in memory
; so all of channel 1's vars, then all of channel 2's vars, etc.
; gbt-player instead packs all four notes together, then all four volumes,
; then all four instruments. having thought about it more, gbt-player's approach
; may be better - it makes indexing to a channel's value simpler. but i've
; already written GetChannelAttributeAddress so it's neither here nor there
; at this point.
macro channel_vars
channel_\1:
; inherents
channel_\1_note: db
channel_\1_volume: db
channel_\1_instrument: db
channel_\1_pan: db
; special features!
channel_\1_arpeggio: db
channel_\1_vibrato: db
channel_\1_volume_slide: db
channel_\1_note_cut: db
channel_\1_trigger: db
endm
channel_vars 1
channel_vars 2
channel_vars 3
channel_3_loaded_instrument: db
channel_vars 4
SECTION "Audio Engine", ROMX
Periods:
DW 44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986
DW 1046, 1102, 1155, 1205, 1253, 1297, 1339, 1379, 1417, 1452, 1486, 1517
DW 1546, 1575, 1602, 1627, 1650, 1673, 1694, 1714, 1732, 1750, 1767, 1783
DW 1798, 1812, 1825, 1837, 1849, 1860, 1871, 1881, 1890, 1899, 1907, 1915
DW 1923, 1930, 1936, 1943, 1949, 1954, 1959, 1964, 1969, 1974, 1978, 1982
DW 1985, 1988, 1992, 1995, 1998, 2001, 2004, 2006, 2009, 2011, 2013, 2015
WaveSamples: ; 8 sounds, pilfered from gbt like so many things
DB $A5,$D7,$C9,$E1,$BC,$9A,$76,$31,$0C,$BA,$DE,$60,$1B,$CA,$03,$93 ; random
DB $F0,$E1,$D2,$C3,$B4,$A5,$96,$87,$78,$69,$5A,$4B,$3C,$2D,$1E,$0F
DB $FD,$EC,$DB,$CA,$B9,$A8,$97,$86,$79,$68,$57,$46,$35,$24,$13,$02 ; up-downs
DB $DE,$FE,$DC,$BA,$9A,$A9,$87,$77,$88,$87,$65,$56,$54,$32,$10,$12
DB $AB,$CD,$EF,$ED,$CB,$A0,$12,$3E,$DC,$BA,$BC,$DE,$FE,$DC,$32,$10 ; tri. broken
DB $FF,$EE,$DD,$CC,$BB,$AA,$99,$88,$77,$66,$55,$44,$33,$22,$11,$00 ; triangular
DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 ; square
DB $79,$BC,$DE,$EF,$FF,$EE,$DC,$B9,$75,$43,$21,$10,$00,$11,$23,$45 ; sine
NoiseOptions: ; 16 different ways to configure NR43 for different sounds
; once again this method is from gbt. if you use the gbt sample# as an index into
; this, it will match the gbt s3m template.
DB $5F,$5B,$4B,$2F,$3B,$58,$1F,$0F ; 7 bit
DB $90,$80,$70,$50,$00 ; 15 bit
DB $67,$63,$53
AudioEngineInit:
ld a, c
ld [ordersBank], a
ld a, e
ld [orders], a
ld a, d
ld [orders+1], a
ld a, 6
ld [speed], a
ld a, $ff
ld [tick], a
ld [row], a
ld [order], a
ld a, AUDENA_ON
ld [rNR52], a ; master audio enable
ld a, $ff
ld [rNR51], a ; panning
ld a, $ff
ld [rNR50], a ; stereo volume
ld a, $ff
ld [channel_1_note_cut], a
ld [channel_2_note_cut], a
ld [channel_3_note_cut], a
ld [channel_4_note_cut], a
ld [channel_3_loaded_instrument], a
ret
AudioEngineUpdate:
ld a, [ordersBank]
;ld [rROMB0], a
call IncrementTick
ld a, [tick]
cp a, 0
jp nz, .updateRegisters
call z, IncrementRow
ld a, [row]
cp a, 0
call z, IncrementOrder
ld a, [row]
cp a, 0
call z, hitit
ld a, [row]
cp a, 0
call z, FreshOrder
call FreshRow
.updateRegisters
call UpdateRegisters1
call UpdateRegisters2
call UpdateRegisters3
call UpdateRegisters4
nop ; just throwiing this in here so i can break on it for timing
ret
hitit:
ret
IncrementTick:
ld a, [speed]
ld b, a
ld a, [tick]
inc a
call ArrayClampLoopingB
ld [tick], a
ret
IncrementRow:
ld b, 64
ld a, [row]
inc a
call ArrayClampLoopingB
ld [row], a
ret
IncrementOrder:
ld a, [orders]
ld l, a
ld a, [orders+1]
ld h, a
ld a, [order]
inc a
call ArrayClampLooping
ld [order], a
ret
FreshOrder:
ld a, [orders]
ld l, a
ld a, [orders+1]
ld h, a
inc hl
ld a, [order]
ld c, a
ld b, 0
add hl, bc
add hl, bc
ld b, h
ld c, l
ld a, [bc]
ld [readHead], a
inc bc
ld a, [bc]
ld [readHead+1], a
ret
FreshRow:
ld a, [readHead]
ld l, a
ld a, [readHead+1]
ld h, a
ld a, [hl]
and a, $F0
cp a, 0
; this should always be zero.
inc hl
ld a, [row]
cp a, [hl]
ret nz ; not ready for a fresh row until the current row matches the upcoming row
inc hl
ld e, l
ld a, l
ld [readHead], a
ld d, h
ld a, h
ld [readHead+1], a
.examinePacket
; de is our read head for the time being so we don't have to write
; so many loads in and out of memory
ld a, [de]
and a, $F0
swap a
ld hl, .packetJumpTable ; this all jumps based on what the top nibble is
ld c, a
ld b, 0
add hl, bc
add hl, bc
ld c, [hl]
inc hl
ld b, [hl]
ld l, c
ld h, b
jp hl
.packetJumpTable
dw .newRow ;$0x
dw .setNote ;$1x
dw .setVolume ;$2x
dw .setInstrument ;$3x
dw .setPan ;$4x
dw .setArpeggio ;$5x
dw .setVibrato ;$6x
dw .setVolumeSlide;$7x
dw .setNoteCut ;$8x
dw .jumpOrder ;$90
dw .breakAndSetRow;$A0
dw .setSpeed ;$B0
dw .callbackEvent ;$C0
.newRow
; if we find a new row, set the read head to that value and return
ld a, e
ld [readHead], a
ld a, d
ld [readHead+1], a
ret
.setNote
call TriggerIt ; always trigger on notes
ld hl, channel_1_note - channel_1
jp .WriteAttributeButDoNotTrigger
.setVolume
ld hl, channel_1_volume - channel_1
jp .WriteAttributeAndTriggerIfDifferent
.setInstrument
ld hl, channel_1_instrument - channel_1
jp .WriteAttributeAndTriggerIfDifferent
.setPan
ld hl, channel_1_pan - channel_1
jp .WriteAttributeButDoNotTrigger
.setArpeggio
ld hl, channel_1_arpeggio - channel_1
jp .WriteAttributeAndTriggerIfDifferent
.setVibrato
ld hl, channel_1_vibrato - channel_1
jp .WriteAttributeAndTriggerIfDifferent
.setVolumeSlide
ld hl, channel_1_volume_slide - channel_1
jp .WriteAttributeAndTriggerIfDifferent
.setNoteCut
ld hl, channel_1_note_cut - channel_1
jp .WriteAttributeButDoNotTrigger
.jumpOrder
inc de
ld a, [de]
ld [order], a
ld a, 0
ld [row], a
ld [tick], a
call FreshOrder
call FreshRow
ret
.breakAndSetRow
inc de
call IncrementOrder
call FreshOrder
call FreshRow
ret
.setSpeed
inc de
ld a, [de]
ld [speed], a
inc de
jp .examinePacket
.callbackEvent
; todo, fall through
.skipIt
inc de
inc de
jp .examinePacket
.WriteAttributeAndTriggerIfDifferent:
; de points to a packet
; hl holds an offset to an attribute (e.g. channel_1_volume - channel_1)
ld a, [de]
and a, $0F
call GetChannelAttributeAddress
push hl
inc de
ld a, [de]
cp a, [hl]
call nz, TriggerBack
ld a, [de]
pop hl
ld [hl], a
inc de
jp .examinePacket
.WriteAttributeButDoNotTrigger:
ld a, [de]
and a, $0F
call GetChannelAttributeAddress
inc de
ld a, [de]
ld [hl], a
inc de
jp .examinePacket
GetChannelAttributeAddress:
; this is a weird one.
; a should have the channel number (1, 2, 3, 4)
; hl shoulld have an offset into a channel attribute (like channel_1_note - channel_1)
; htis will fetch into hl that attribute's address on that channel.
push hl
ld hl, .beginningsOfNoteData
ld b, 0
ld c, a
add hl, bc
add hl, bc
ld c, [hl]
inc hl
ld b, [hl]
pop hl
add hl, bc
ret
.beginningsOfNoteData
dw 0
dw channel_1
dw channel_2
dw channel_3
dw channel_4
TriggerBack:
; de points to an address. get the low nibble one address previous,
; take as a channel number, and write 1 to the corresponding channel_1_trigger
; address
dec de
call TriggerIt
inc de
ret
TriggerIt:
ld a, [de]
and a, $0F
ld hl, channel_1_trigger - channel_1
call GetChannelAttributeAddress
ld [hl], 1
ret
UpdateRegisters1:
ld a, [tick]
ld b, a
ld a, [channel_1_note_cut]
cp a, b
jp nz, :+
ld a, 0
ld [channel_1_volume], a
cpl
ld [channel_1_trigger], a
ld a, $ff
ld [channel_1_note_cut], a
:
ld a, [channel_1_trigger]
cp a, 0
ret z
ld a, 0
ld [rNR10], a
ld a, [channel_1_instrument]
ld [rNR11], a
ld a, [channel_1_volume]
swap a
and a, $70
ld [rNR12], a
ld a, [channel_1_note]
ld b, 0
ld c, a
ld hl, Periods
add hl, bc
add hl, bc
ld a, [hl+]
ld [rNR13], a
ld a, [hl]
or a, AUDHIGH_RESTART
ld [rNR14], a
ld a, 0
ld [channel_1_trigger], a
ret
UpdateRegisters2:
ld a, [tick]
ld b, a
ld a, [channel_2_note_cut]
cp a, b
jp nz, :+
ld a, 0
ld [channel_2_volume], a
cpl
ld [channel_2_trigger], a
ld a, $ff
ld [channel_2_note_cut], a
:
ld a, [channel_2_trigger]
cp a, 0
ret z
ld a, [channel_2_instrument]
ld [rNR21], a
ld a, [channel_2_volume]
swap a
and a, $f0
ld [rNR22], a
ld a, [channel_2_note]
ld b, 0
ld c, a
ld hl, Periods
add hl, bc
add hl, bc
ld a, [hl+]
ld [rNR23], a
ld a, [hl]
or a, AUDHIGH_RESTART
ld [rNR24], a
ld a, 0
ld [channel_2_trigger], a
ret
UpdateRegisters3:
ld a, [tick]
ld b, a
ld a, [channel_3_note_cut]
cp a, b
jp nz, :+
ld a, 0
ld [rNR30], a ; turn off channel 3 if we hit a note cut.
ld a, 0
ld [channel_3_volume], a
; we don't need to retrigger because we already turned off the dac.
;ld a, 1
;ld [channel_3_trigger], a
; we do want to reset the note cut tho.
ld a, $ff
ld [channel_3_note_cut], a
:
ld a, [channel_3_trigger]
cp a, 0
ret z ; if we don't have a trigger, then don't do anything else.
; instrument has a special meaning for channel 3; it refers to an index into
; an array of 16 (could be more!) samples, defined at the top of this file.
; (each "sample" is a 16-byte sequence of 4-bit values blah blah read the
; pandoc for that)
ld a, [channel_3_instrument]
ld hl, channel_3_loaded_instrument
cp a, [hl]
call nz, ChangeLoadedWave
ld a, 0 ; length should always be zero
ld [rNR31], a
; volumes are pre-processed by the export script.
ld a, [channel_3_volume]
swap a
and a, $f0
ld [rNR32], a
cp a, 0
call nz, .turnOnDAC
ld a, [channel_3_trigger]
cp a, 0
call nz, .turnOnDAC
; periods don't seem to be different for channel 3, so leave it as is
ld a, [channel_3_note]
ld b, 0
ld c, a
ld hl, Periods
add hl, bc
add hl, bc
ld a, [hl+]
ld [rNR33], a
ld a, [hl]
or a, AUDHIGH_RESTART
ld [rNR34], a
ld a, 0
ld [channel_3_trigger], a
ret
.turnOnDAC:
ld a, $80
ld [rNR30], a
ret
ChangeLoadedWave:
; a has the nidex of the wave in the wave table at the top of the file
ld b, a
ld a, [rNR30]
push af ; save thsi for later so we can restore it.
ld a, 0
ld [rNR30], a ; turn off the DAC for channel 3
ld a, b
ld [channel_3_loaded_instrument], a ; mark that we've switched to it
and a, $0F
swap a ; fast multiply by 16
ld hl, WaveSamples
ld b, 0
ld c, a
add hl, bc ; hl shoulld now ponit to the appropriate wave
ld de, _AUD3WAVERAM
ld bc, 16 / 8 ; using by8s for unnecessary optimization, so divide length by 8
call CopyRangeBy8s
pop af
ld [rNR30], a
ret
UpdateRegisters4:
ld a, [tick]
ld b, a
ld a, [channel_4_note_cut]
cp a, b
jp nz, :+
ld a, 0
ld [channel_4_volume], a
cpl
ld [channel_4_trigger], a
ld a, $ff
ld [channel_4_note_cut], a
:
ld a, [channel_4_trigger]
cp a, 0
ret z
ld a, 0; [channel_2_instrument]
ld [rNR41], a
ld a, [channel_4_volume]
swap a
and a, $f0
ld [rNR42], a
ld a, [channel_4_instrument]
ld b, 0
ld c, a
ld hl, NoiseOptions
add hl, bc
ld a, [hl]
ld [rNR43], a
ld a, AUDHIGH_RESTART
ld [rNR44], a
ld a, 0
ld [channel_4_trigger], a
ret
INCLUDE "theme.inc"