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