SECTION "Audio Interface", ROM0
SoundSetup:
  ld a, BANK(AudioEngineInit)
  ld [rROMB0], a 
  
  ld de, gb_tarot_theme
  ld c, BANK(gb_tarot_theme)
  call AudioEngineInit ; Play song
 
  ld a, [cvCardBank]
  ld [rROMB0], a 
  ret
  
SoundUpdate:
  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", ROM0

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
  ret