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 
 | |
|   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"
 |