633 lines
12 KiB
PHP
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"
|