summaryrefslogtreecommitdiff
path: root/audio/engine.asm
diff options
context:
space:
mode:
Diffstat (limited to 'audio/engine.asm')
-rw-r--r--audio/engine.asm2850
1 files changed, 2850 insertions, 0 deletions
diff --git a/audio/engine.asm b/audio/engine.asm
new file mode 100644
index 00000000..6b0fe1a6
--- /dev/null
+++ b/audio/engine.asm
@@ -0,0 +1,2850 @@
+; The entire sound engine. Uses section "audio" in WRAM.
+
+; Interfaces are in bank 0.
+
+; Notable functions:
+; FadeMusic
+; PlayStereoSFX
+
+_MapSetup_Sound_Off::
+; restart sound operation
+; clear all relevant hardware registers & wram
+ push hl
+ push de
+ push bc
+ push af
+ call MusicOff
+ ld hl, rNR50 ; channel control registers
+ xor a
+ ld [hli], a ; rNR50 ; volume/vin
+ ld [hli], a ; rNR51 ; sfx channels
+ ld a, $80 ; all channels on
+ ld [hli], a ; ff26 ; music channels
+
+ ld hl, rNR10 ; sound channel registers
+ ld e, NUM_MUSIC_CHANS
+.clearsound
+; sound channel 1 2 3 4
+ xor a
+ ld [hli], a ; rNR10, rNR20, rNR30, rNR40 ; sweep = 0
+
+ ld [hli], a ; rNR11, rNR21, rNR31, rNR41 ; length/wavepattern = 0
+ ld a, $8
+ ld [hli], a ; rNR12, rNR22, rNR32, rNR42 ; envelope = 0
+ xor a
+ ld [hli], a ; rNR13, rNR23, rNR33, rNR43 ; frequency lo = 0
+ ld a, $80
+ ld [hli], a ; rNR14, rNR24, rNR34, rNR44 ; restart sound (freq hi = 0)
+ dec e
+ jr nz, .clearsound
+
+ ld hl, wChannels ; start of channel data
+ ld de, wChannelsEnd - wChannels ; length of area to clear (entire sound wram area)
+.clearchannels
+ xor a
+ ld [hli], a
+ dec de
+ ld a, e
+ or d
+ jr nz, .clearchannels
+ ld a, MAX_VOLUME
+ ld [wVolume], a
+ call MusicOn
+ pop af
+ pop bc
+ pop de
+ pop hl
+ ret
+
+MusicFadeRestart:
+; restart but keep the music id to fade in to
+ ld a, [wMusicFadeID + 1]
+ push af
+ ld a, [wMusicFadeID]
+ push af
+ call _MapSetup_Sound_Off
+ pop af
+ ld [wMusicFadeID], a
+ pop af
+ ld [wMusicFadeID + 1], a
+ ret
+
+MusicOn:
+ ld a, 1
+ ld [wMusicPlaying], a
+ ret
+
+MusicOff:
+ xor a
+ ld [wMusicPlaying], a
+ ret
+
+_UpdateSound::
+; called once per frame
+ ; no use updating audio if it's not playing
+ ld a, [wMusicPlaying]
+ and a
+ ret z
+ ; start at ch1
+ xor a
+ ld [wCurChannel], a ; just
+ ld [wSoundOutput], a ; off
+ ld bc, wChannel1
+.loop
+ ; is the channel active?
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_CHANNEL_ON, [hl]
+ jp z, .nextchannel
+ ; check time left in the current note
+ ld hl, CHANNEL_NOTE_DURATION
+ add hl, bc
+ ld a, [hl]
+ cp $2 ; 1 or 0?
+ jr c, .noteover
+ dec [hl]
+ jr .continue_sound_update
+
+.noteover
+ ; reset vibrato delay
+ ld hl, CHANNEL_VIBRATO_DELAY
+ add hl, bc
+ ld a, [hl]
+ ld hl, CHANNEL_VIBRATO_DELAY_COUNT
+ add hl, bc
+ ld [hl], a
+ ; turn vibrato off for now
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ res SOUND_PITCH_WHEEL, [hl]
+ ; get next note
+ call ParseMusic
+.continue_sound_update
+ call ApplyPitchWheel
+ ; duty cycle
+ ld hl, CHANNEL_DUTY_CYCLE
+ add hl, bc
+ ld a, [hli]
+ ld [wCurTrackDuty], a
+ ; intensity
+ ld a, [hli]
+ ld [wCurTrackIntensity], a
+ ; frequency
+ ld a, [hli]
+ ld [wCurTrackFrequency], a
+ ld a, [hl]
+ ld [wCurTrackFrequency + 1], a
+ ; vibrato, noise
+ call HandleTrackVibrato ; handle vibrato and other things
+ call HandleNoise
+ ; turn off music when playing sfx?
+ ld a, [wSFXPriority]
+ and a
+ jr z, .next
+ ; are we in a sfx channel right now?
+ ld a, [wCurChannel]
+ cp NUM_MUSIC_CHANS
+ jr nc, .next
+ ; are any sfx channels active?
+ ; if so, mute
+ ld hl, wChannel5Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .restnote
+ ld hl, wChannel6Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .restnote
+ ld hl, wChannel7Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .restnote
+ ld hl, wChannel8Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr z, .next
+.restnote
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_REST, [hl] ; Rest
+.next
+ ; are we in a sfx channel right now?
+ ld a, [wCurChannel]
+ cp NUM_MUSIC_CHANS
+ jr nc, .sfx_channel
+ ld hl, CHANNEL_STRUCT_LENGTH * NUM_MUSIC_CHANS + CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .sound_channel_on
+.sfx_channel
+ call UpdateChannels
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ ld a, [wSoundOutput]
+ or [hl]
+ ld [wSoundOutput], a
+.sound_channel_on
+ ; clear note flags
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ xor a
+ ld [hl], a
+.nextchannel
+ ; next channel
+ ld hl, CHANNEL_STRUCT_LENGTH
+ add hl, bc
+ ld c, l
+ ld b, h
+ ld a, [wCurChannel]
+ inc a
+ ld [wCurChannel], a
+ cp NUM_CHANNELS ; are we done?
+ jp nz, .loop ; do it all again
+
+ call PlayDanger
+ ; fade music in/out
+ call FadeMusic
+ ; write volume to hardware register
+ ld a, [wVolume]
+ ldh [rNR50], a
+ ; write SO on/off to hardware register
+ ld a, [wSoundOutput]
+ ldh [rNR51], a
+ ret
+
+UpdateChannels:
+ ld hl, .ChannelFnPtrs
+ ld a, [wCurChannel]
+ and $7
+ add a
+ ld e, a
+ ld d, 0
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+.ChannelFnPtrs:
+ dw .Channel1
+ dw .Channel2
+ dw .Channel3
+ dw .Channel4
+; sfx ch ptrs are identical to music chs
+; ..except 5
+ dw .Channel5
+ dw .Channel6
+ dw .Channel7
+ dw .Channel8
+
+.Channel1:
+ ld a, [wLowHealthAlarm]
+ bit DANGER_ON_F, a
+ ret nz
+.Channel5:
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ bit NOTE_UNKN_3, [hl]
+ jr z, .asm_e8159
+ ;
+ ld a, [wSoundInput]
+ ldh [rNR10], a
+.asm_e8159
+ bit NOTE_REST, [hl] ; rest
+ jr nz, .ch1rest
+ bit NOTE_NOISE_SAMPLING, [hl]
+ jr nz, .asm_e81a2
+ bit NOTE_FREQ_OVERRIDE, [hl]
+ jr nz, .frequency_override
+ bit NOTE_VIBRATO_OVERRIDE, [hl]
+ jr nz, .asm_e8184
+ jr .check_duty_override
+
+.frequency_override
+ ld a, [wCurTrackFrequency]
+ ldh [rNR13], a
+ ld a, [wCurTrackFrequency + 1]
+ ldh [rNR14], a
+.check_duty_override
+ bit NOTE_DUTY_OVERRIDE, [hl]
+ ret z
+ ld a, [wCurTrackDuty]
+ ld d, a
+ ldh a, [rNR11]
+ and $3f ; sound length
+ or d
+ ldh [rNR11], a
+ ret
+
+.asm_e8184
+ ld a, [wCurTrackDuty]
+ ld d, a
+ ldh a, [rNR11]
+ and $3f ; sound length
+ or d
+ ldh [rNR11], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR13], a
+ ret
+
+.ch1rest
+ ldh a, [rNR52]
+ and %10001110 ; ch1 off
+ ldh [rNR52], a
+ ld hl, rNR10
+ call ClearChannel
+ ret
+
+.asm_e81a2
+ ld hl, wCurTrackDuty
+ ld a, $3f ; sound length
+ or [hl]
+ ldh [rNR11], a
+ ld a, [wCurTrackIntensity]
+ ldh [rNR12], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR13], a
+ ld a, [wCurTrackFrequency + 1]
+ or $80
+ ldh [rNR14], a
+ ret
+
+.Channel2:
+.Channel6:
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ bit NOTE_REST, [hl] ; rest
+ jr nz, .ch2rest
+ bit NOTE_NOISE_SAMPLING, [hl]
+ jr nz, .asm_e8204
+ bit NOTE_VIBRATO_OVERRIDE, [hl]
+ jr nz, .asm_e81e6
+ bit NOTE_DUTY_OVERRIDE, [hl]
+ ret z
+ ld a, [wCurTrackDuty]
+ ld d, a
+ ldh a, [rNR21]
+ and $3f ; sound length
+ or d
+ ldh [rNR21], a
+ ret
+
+.asm_e81db ; unused
+ ld a, [wCurTrackFrequency]
+ ldh [rNR23], a
+ ld a, [wCurTrackFrequency + 1]
+ ldh [rNR24], a
+ ret
+
+.asm_e81e6
+ ld a, [wCurTrackDuty]
+ ld d, a
+ ldh a, [rNR21]
+ and $3f ; sound length
+ or d
+ ldh [rNR21], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR23], a
+ ret
+
+.ch2rest
+ ldh a, [rNR52]
+ and %10001101 ; ch2 off
+ ldh [rNR52], a
+ ld hl, rNR20
+ call ClearChannel
+ ret
+
+.asm_e8204
+ ld hl, wCurTrackDuty
+ ld a, $3f ; sound length
+ or [hl]
+ ldh [rNR21], a
+ ld a, [wCurTrackIntensity]
+ ldh [rNR22], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR23], a
+ ld a, [wCurTrackFrequency + 1]
+ or $80 ; initial (restart)
+ ldh [rNR24], a
+ ret
+
+.Channel3:
+.Channel7:
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ bit NOTE_REST, [hl] ; rest
+ jr nz, .ch3rest
+ bit NOTE_NOISE_SAMPLING, [hl]
+ jr nz, .asm_e824d
+ bit NOTE_VIBRATO_OVERRIDE, [hl]
+ jr nz, .asm_e823a
+ ret
+
+.asm_e822f ; unused
+ ld a, [wCurTrackFrequency]
+ ldh [rNR33], a
+ ld a, [wCurTrackFrequency + 1]
+ ldh [rNR34], a
+ ret
+
+.asm_e823a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR33], a
+ ret
+
+.ch3rest
+ ldh a, [rNR52]
+ and %10001011 ; ch3 off
+ ldh [rNR52], a
+ ld hl, rNR30
+ call ClearChannel
+ ret
+
+.asm_e824d
+ ld a, $3f ; sound length
+ ldh [rNR31], a
+ xor a
+ ldh [rNR30], a
+ call .asm_e8268
+ ld a, $80
+ ldh [rNR30], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR33], a
+ ld a, [wCurTrackFrequency + 1]
+ or $80
+ ldh [rNR34], a
+ ret
+
+.asm_e8268
+ push hl
+ ld a, [wCurTrackIntensity]
+ and $f ; only 0-9 are valid
+ ld l, a
+ ld h, 0
+ ; hl << 4
+ ; each wavepattern is $f bytes long
+ ; so seeking is done in $10s
+rept 4
+ add hl, hl
+endr
+ ld de, WaveSamples
+ add hl, de
+ ; load wavepattern into rWave_0-rWave_f
+ ld a, [hli]
+ ldh [rWave_0], a
+ ld a, [hli]
+ ldh [rWave_1], a
+ ld a, [hli]
+ ldh [rWave_2], a
+ ld a, [hli]
+ ldh [rWave_3], a
+ ld a, [hli]
+ ldh [rWave_4], a
+ ld a, [hli]
+ ldh [rWave_5], a
+ ld a, [hli]
+ ldh [rWave_6], a
+ ld a, [hli]
+ ldh [rWave_7], a
+ ld a, [hli]
+ ldh [rWave_8], a
+ ld a, [hli]
+ ldh [rWave_9], a
+ ld a, [hli]
+ ldh [rWave_a], a
+ ld a, [hli]
+ ldh [rWave_b], a
+ ld a, [hli]
+ ldh [rWave_c], a
+ ld a, [hli]
+ ldh [rWave_d], a
+ ld a, [hli]
+ ldh [rWave_e], a
+ ld a, [hli]
+ ldh [rWave_f], a
+ pop hl
+ ld a, [wCurTrackIntensity]
+ and $f0
+ sla a
+ ldh [rNR32], a
+ ret
+
+.Channel4:
+.Channel8:
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ bit NOTE_REST, [hl] ; rest
+ jr nz, .ch4rest
+ bit NOTE_NOISE_SAMPLING, [hl]
+ jr nz, .asm_e82d4
+ ret
+
+.asm_e82c1 ; unused
+ ld a, [wCurTrackFrequency]
+ ldh [rNR43], a
+ ret
+
+.ch4rest
+ ldh a, [rNR52]
+ and %10000111 ; ch4 off
+ ldh [rNR52], a
+ ld hl, rNR40
+ call ClearChannel
+ ret
+
+.asm_e82d4
+ ld a, $3f ; sound length
+ ldh [rNR41], a
+ ld a, [wCurTrackIntensity]
+ ldh [rNR42], a
+ ld a, [wCurTrackFrequency]
+ ldh [rNR43], a
+ ld a, $80
+ ldh [rNR44], a
+ ret
+
+_CheckSFX:
+; return carry if any sfx channels are active
+ ld hl, wChannel5Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .sfxon
+ ld hl, wChannel6Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .sfxon
+ ld hl, wChannel7Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .sfxon
+ ld hl, wChannel8Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .sfxon
+ and a
+ ret
+
+.sfxon
+ scf
+ ret
+
+PlayDanger:
+ ld a, [wLowHealthAlarm]
+ bit DANGER_ON_F, a
+ ret z
+
+ ; Don't do anything if SFX is being played
+ and $ff ^ (1 << DANGER_ON_F)
+ ld d, a
+ call _CheckSFX
+ jr c, .increment
+
+ ; Play the high tone
+ and a
+ jr z, .begin
+
+ ; Play the low tone
+ cp 16
+ jr z, .halfway
+
+ jr .increment
+
+.halfway
+ ld hl, DangerSoundLow
+ jr .applychannel
+
+.begin
+ ld hl, DangerSoundHigh
+
+.applychannel
+ xor a
+ ldh [rNR10], a
+ ld a, [hli]
+ ldh [rNR11], a
+ ld a, [hli]
+ ldh [rNR12], a
+ ld a, [hli]
+ ldh [rNR13], a
+ ld a, [hli]
+ ldh [rNR14], a
+
+.increment
+ ld a, d
+ inc a
+ cp 30 ; Ending frame
+ jr c, .noreset
+ xor a
+.noreset
+ ; Make sure the danger sound is kept on
+ or 1 << DANGER_ON_F
+ ld [wLowHealthAlarm], a
+
+ ; Enable channel 1 if it's off
+ ld a, [wSoundOutput]
+ and $11
+ ret nz
+ ld a, [wSoundOutput]
+ or $11
+ ld [wSoundOutput], a
+ ret
+
+DangerSoundHigh:
+ db $80 ; duty 50%
+ db $e2 ; volume 14, envelope decrease sweep 2
+ db $50 ; frequency: $750
+ db $87 ; restart sound
+
+DangerSoundLow:
+ db $80 ; duty 50%
+ db $e2 ; volume 14, envelope decrease sweep 2
+ db $ee ; frequency: $6ee
+ db $86 ; restart sound
+
+FadeMusic:
+; fade music if applicable
+; usage:
+; write to wMusicFade
+; song fades out at the given rate
+; load song id in wMusicFadeID
+; fade new song in
+; notes:
+; max # frames per volume level is $3f
+
+ ; fading?
+ ld a, [wMusicFade]
+ and a
+ ret z
+ ; has the count ended?
+ ld a, [wMusicFadeCount]
+ and a
+ jr z, .update
+ ; count down
+ dec a
+ ld [wMusicFadeCount], a
+ ret
+
+.update
+ ld a, [wMusicFade]
+ ld d, a
+ ; get new count
+ and $3f
+ ld [wMusicFadeCount], a
+ ; get SO1 volume
+ ld a, [wVolume]
+ and VOLUME_SO1_LEVEL
+ ; which way are we fading?
+ bit MUSIC_FADE_IN_F, d
+ jr nz, .fadein
+ ; fading out
+ and a
+ jr z, .novolume
+ dec a
+ jr .updatevolume
+
+.novolume
+ ; make sure volume is off
+ xor a
+ ld [wVolume], a
+ ; did we just get on a bike?
+ ld a, [wPlayerState]
+ cp PLAYER_BIKE
+ jr z, .bicycle
+ push bc
+ ; restart sound
+ call MusicFadeRestart
+ ; get new song id
+ ld a, [wMusicFadeID]
+ and a
+ jr z, .quit ; this assumes there are fewer than 256 songs!
+ ld e, a
+ ld a, [wMusicFadeID + 1]
+ ld d, a
+ ; load new song
+ call _PlayMusic
+.quit
+ ; cleanup
+ pop bc
+ ; stop fading
+ xor a
+ ld [wMusicFade], a
+ ret
+
+.bicycle
+ push bc
+ ; restart sound
+ call MusicFadeRestart
+ ; this turns the volume up
+ ; turn it back down
+ xor a
+ ld [wVolume], a
+ ; get new song id
+ ld a, [wMusicFadeID]
+ ld e, a
+ ld a, [wMusicFadeID + 1]
+ ld d, a
+ ; load new song
+ call _PlayMusic
+ pop bc
+ ; fade in
+ ld hl, wMusicFade
+ set MUSIC_FADE_IN_F, [hl]
+ ret
+
+.fadein
+ ; are we done?
+ cp MAX_VOLUME & $f
+ jr nc, .maxvolume
+ ; inc volume
+ inc a
+ jr .updatevolume
+
+.maxvolume
+ ; we're done
+ xor a
+ ld [wMusicFade], a
+ ret
+
+.updatevolume
+ ; hi = lo
+ ld d, a
+ swap a
+ or d
+ ld [wVolume], a
+ ret
+
+LoadNote:
+ ; wait for pitch wheel to finish
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ bit SOUND_PITCH_WHEEL, [hl]
+ ret z
+ ; get note duration
+ ld hl, CHANNEL_NOTE_DURATION
+ add hl, bc
+ ld a, [hl]
+ ld hl, wCurNoteDuration
+ sub [hl]
+ jr nc, .ok
+ ld a, 1
+.ok
+ ld [hl], a
+ ; get frequency
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; get direction of pitch wheel
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld a, e
+ sub [hl]
+ ld e, a
+ ld a, d
+ sbc 0
+ ld d, a
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ sub [hl]
+ jr nc, .greater_than
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ set SOUND_PITCH_WHEEL_DIR, [hl]
+ ; get frequency
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; ????
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld a, [hl]
+ sub e
+ ld e, a
+ ld a, d
+ sbc 0
+ ld d, a
+ ; ????
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ ld a, [hl]
+ sub d
+ ld d, a
+ jr .resume
+
+.greater_than
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ res SOUND_PITCH_WHEEL_DIR, [hl]
+ ; get frequency
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; get distance from pitch wheel target
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld a, e
+ sub [hl]
+ ld e, a
+ ld a, d
+ sbc 0
+ ld d, a
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ sub [hl]
+ ld d, a
+.resume
+ ; de = x * [wCurNoteDuration] + y
+ ; x + 1 -> d
+ ; y -> a
+ push bc
+ ld hl, wCurNoteDuration
+ ld b, 0 ; quotient
+.loop
+ inc b
+ ld a, e
+ sub [hl]
+ ld e, a
+ jr nc, .loop
+ ld a, d
+ and a
+ jr z, .quit
+ dec d
+ jr .loop
+
+.quit
+ ld a, e ; remainder
+ add [hl]
+ ld d, b ; quotient
+ pop bc
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT
+ add hl, bc
+ ld [hl], d ; quotient
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT_FRACTION
+ add hl, bc
+ ld [hl], a ; remainder
+ ld hl, CHANNEL_FIELD25
+ add hl, bc
+ xor a
+ ld [hl], a
+ ret
+
+HandleTrackVibrato:
+; handle duty, cry pitch, and vibrato
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ bit SOUND_DUTY, [hl] ; duty
+ jr z, .next
+ ld hl, CHANNEL_SFX_DUTY_LOOP
+ add hl, bc
+ ld a, [hl]
+ rlca
+ rlca
+ ld [hl], a
+ and $c0
+ ld [wCurTrackDuty], a
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_DUTY_OVERRIDE, [hl]
+.next
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ bit SOUND_CRY_PITCH, [hl]
+ jr z, .vibrato
+ ld hl, CHANNEL_CRY_PITCH
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld hl, wCurTrackFrequency
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ add hl, de
+ ld e, l
+ ld d, h
+ ld hl, wCurTrackFrequency
+ ld [hl], e
+ inc hl
+ ld [hl], d
+.vibrato
+ ; is vibrato on?
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ bit SOUND_VIBRATO, [hl] ; vibrato
+ jr z, .quit
+ ; is vibrato active for this note yet?
+ ; is the delay over?
+ ld hl, CHANNEL_VIBRATO_DELAY_COUNT
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr nz, .subexit
+ ; is the extent nonzero?
+ ld hl, CHANNEL_VIBRATO_EXTENT
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .quit
+ ; save it for later
+ ld d, a
+ ; is it time to toggle vibrato up/down?
+ ld hl, CHANNEL_VIBRATO_RATE
+ add hl, bc
+ ld a, [hl]
+ and $f ; count
+ jr z, .toggle
+.subexit
+ dec [hl]
+ jr .quit
+
+.toggle
+ ; refresh count
+ ld a, [hl]
+ swap [hl]
+ or [hl]
+ ld [hl], a
+ ; ????
+ ld a, [wCurTrackFrequency]
+ ld e, a
+ ; toggle vibrato up/down
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ bit SOUND_VIBRATO_DIR, [hl] ; vibrato up/down
+ jr z, .down
+; up
+ ; vibrato down
+ res SOUND_VIBRATO_DIR, [hl]
+ ; get the delay
+ ld a, d
+ and $f ; lo
+ ;
+ ld d, a
+ ld a, e
+ sub d
+ jr nc, .no_carry
+ ld a, 0
+ jr .no_carry
+
+.down
+ ; vibrato up
+ set SOUND_VIBRATO_DIR, [hl]
+ ; get the delay
+ ld a, d
+ and $f0 ; hi
+ swap a ; move it to lo
+ ;
+ add e
+ jr nc, .no_carry
+ ld a, $ff
+.no_carry
+ ld [wCurTrackFrequency], a
+ ;
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_VIBRATO_OVERRIDE, [hl]
+.quit
+ ret
+
+ApplyPitchWheel:
+ ; quit if pitch wheel inactive
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ bit SOUND_PITCH_WHEEL, [hl]
+ ret z
+ ; de = Frequency
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; check whether pitch wheel is going up or down
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ bit SOUND_PITCH_WHEEL_DIR, [hl]
+ jr z, .decreasing
+ ; frequency += [Channel*PitchWheelAmount]
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT
+ add hl, bc
+ ld l, [hl]
+ ld h, 0
+ add hl, de
+ ld d, h
+ ld e, l
+ ; [Channel*Field25] += [Channel*PitchWheelAmountFraction]
+ ; if rollover: Frequency += 1
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT_FRACTION
+ add hl, bc
+ ld a, [hl]
+ ld hl, CHANNEL_FIELD25
+ add hl, bc
+ add [hl]
+ ld [hl], a
+ ld a, 0
+ adc e
+ ld e, a
+ ld a, 0
+ adc d
+ ld d, a
+ ; Compare the dw at [Channel*PitchWheelTarget] to de.
+ ; If frequency is greater, we're finished.
+ ; Otherwise, load the frequency and set two flags.
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ ld a, [hl]
+ cp d
+ jp c, .finished_pitch_wheel
+ jr nz, .continue_pitch_wheel
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld a, [hl]
+ cp e
+ jp c, .finished_pitch_wheel
+ jr .continue_pitch_wheel
+
+.decreasing
+ ; frequency -= [Channel*PitchWheelAmount]
+ ld a, e
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT
+ add hl, bc
+ ld e, [hl]
+ sub e
+ ld e, a
+ ld a, d
+ sbc 0
+ ld d, a
+ ; [Channel*Field25] *= 2
+ ; if rollover: Frequency -= 1
+ ld hl, CHANNEL_PITCH_WHEEL_AMOUNT_FRACTION
+ add hl, bc
+ ld a, [hl]
+ add a
+ ld [hl], a
+ ld a, e
+ sbc 0
+ ld e, a
+ ld a, d
+ sbc 0
+ ld d, a
+ ; Compare the dw at [Channel*PitchWheelTarget] to de.
+ ; If frequency is lower, we're finished.
+ ; Otherwise, load the frequency and set two flags.
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ ld a, d
+ cp [hl]
+ jr c, .finished_pitch_wheel
+ jr nz, .continue_pitch_wheel
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld a, e
+ cp [hl]
+ jr nc, .continue_pitch_wheel
+.finished_pitch_wheel
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ res SOUND_PITCH_WHEEL, [hl]
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ res SOUND_PITCH_WHEEL_DIR, [hl]
+ ret
+
+.continue_pitch_wheel
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_FREQ_OVERRIDE, [hl]
+ set NOTE_DUTY_OVERRIDE, [hl]
+ ret
+
+HandleNoise:
+ ; is noise sampling on?
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_NOISE, [hl] ; noise sampling
+ ret z
+ ; are we in a sfx channel?
+ ld a, [wCurChannel]
+ bit NOISE_CHAN_F, a
+ jr nz, .next
+ ; is ch8 on? (noise)
+ ld hl, wChannel8Flags1
+ bit SOUND_CHANNEL_ON, [hl] ; on?
+ jr z, .next
+ ; is ch8 playing noise?
+ bit SOUND_NOISE, [hl]
+ ret nz ; quit if so
+ ;
+.next
+ ld a, [wNoiseSampleDelay]
+ and a
+ jr z, ReadNoiseSample
+ dec a
+ ld [wNoiseSampleDelay], a
+ ret
+
+ReadNoiseSample:
+; sample struct:
+; [wx] [yy] [zz]
+; w: ? either 2 or 3
+; x: duration
+; zz: intensity
+; yy: frequency
+
+ ; de = [wNoiseSampleAddress]
+ ld hl, wNoiseSampleAddress
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+
+ ; is it empty?
+ ld a, e
+ or d
+ jr z, .quit
+
+ ld a, [de]
+ inc de
+
+ cp endchannel_cmd
+ jr z, .quit
+
+ and $f
+ inc a
+ ld [wNoiseSampleDelay], a
+ ld a, [de]
+ inc de
+ ld [wCurTrackIntensity], a
+ ld a, [de]
+ inc de
+ ld [wCurTrackFrequency], a
+ xor a
+ ld [wCurTrackFrequency + 1], a
+
+ ld hl, wNoiseSampleAddress
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_NOISE_SAMPLING, [hl]
+ ret
+
+.quit
+ ret
+
+ParseMusic:
+; parses until a note is read or the song is ended
+ call GetMusicByte ; store next byte in a
+ cp endchannel_cmd
+ jr z, .endchannel
+ cp FIRST_MUSIC_CMD
+ jr c, .readnote
+ ; then it's a command
+.readcommand
+ call ParseMusicCommand
+ jr ParseMusic ; start over
+
+.readnote
+; wCurMusicByte contains current note
+; special notes
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_SFX, [hl]
+ jp nz, ParseSFXOrRest
+ bit SOUND_REST, [hl] ; rest
+ jp nz, ParseSFXOrRest
+ bit SOUND_NOISE, [hl] ; noise sample
+ jp nz, GetNoiseSample
+; normal note
+ ; set note duration (bottom nybble)
+ ld a, [wCurMusicByte]
+ and $f
+ call SetNoteDuration
+ ; get note pitch (top nybble)
+ ld a, [wCurMusicByte]
+ swap a
+ and $f
+ jr z, .rest ; pitch 0-> rest
+ ; update pitch
+ ld hl, CHANNEL_PITCH
+ add hl, bc
+ ld [hl], a
+ ; store pitch in e
+ ld e, a
+ ; store octave in d
+ ld hl, CHANNEL_OCTAVE
+ add hl, bc
+ ld d, [hl]
+ ; update frequency
+ call GetFrequency
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ; ????
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_NOISE_SAMPLING, [hl]
+ jp LoadNote
+
+.rest
+; note = rest
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_REST, [hl] ; Rest
+ ret
+
+.endchannel
+; $ff is reached in music data
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_SUBROUTINE, [hl] ; in a subroutine?
+ jr nz, .readcommand ; execute
+ ld a, [wCurChannel]
+ cp CHAN5
+ jr nc, .chan_5to8
+ ; ????
+ ld hl, CHANNEL_STRUCT_LENGTH * NUM_MUSIC_CHANS + CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_CHANNEL_ON, [hl]
+ jr nz, .ok
+.chan_5to8
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_REST, [hl]
+ call nz, RestoreVolume
+ ; end music
+ ld a, [wCurChannel]
+ cp CHAN5
+ jr nz, .ok
+ ; ????
+ xor a
+ ldh [rNR10], a ; sweep = 0
+.ok
+; stop playing
+ ; turn channel off
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ res SOUND_CHANNEL_ON, [hl]
+ ; note = rest
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_REST, [hl]
+ ; clear music id & bank
+ ld hl, CHANNEL_MUSIC_ID
+ add hl, bc
+ xor a
+ ld [hli], a ; id hi
+ ld [hli], a ; id lo
+ ld [hli], a ; bank
+ ret
+
+RestoreVolume:
+ ; ch5 only
+ ld a, [wCurChannel]
+ cp CHAN5
+ ret nz
+ xor a
+ ld hl, wChannel6CryPitch
+ ld [hli], a
+ ld [hl], a
+ ld hl, wChannel8CryPitch
+ ld [hli], a
+ ld [hl], a
+ ld a, [wLastVolume]
+ ld [wVolume], a
+ xor a
+ ld [wLastVolume], a
+ ld [wSFXPriority], a
+ ret
+
+ParseSFXOrRest:
+ ; turn noise sampling on
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_NOISE_SAMPLING, [hl] ; noise sample
+ ; update note duration
+ ld a, [wCurMusicByte]
+ call SetNoteDuration ; top nybble doesnt matter?
+ ; update intensity from next param
+ call GetMusicByte
+ ld hl, CHANNEL_INTENSITY
+ add hl, bc
+ ld [hl], a
+ ; update lo frequency from next param
+ call GetMusicByte
+ ld hl, CHANNEL_FREQUENCY
+ add hl, bc
+ ld [hl], a
+ ; are we on the last channel? (noise sampling)
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ cp CHAN4
+ ret z
+ ; update hi frequency from next param
+ call GetMusicByte
+ ld hl, CHANNEL_FREQUENCY + 1
+ add hl, bc
+ ld [hl], a
+ ret
+
+GetNoiseSample:
+; load ptr to sample header in wNoiseSampleAddress
+ ; are we on the last channel?
+ ld a, [wCurChannel]
+ and NUM_MUSIC_CHANS + -1
+ cp CHAN4
+ ; ret if not
+ ret nz
+ ; update note duration
+ ld a, [wCurMusicByte]
+ and $f
+ call SetNoteDuration
+ ; check current channel
+ ld a, [wCurChannel]
+ bit NOISE_CHAN_F, a
+ jr nz, .sfx
+ ld hl, wChannel8Flags1
+ bit SOUND_CHANNEL_ON, [hl] ; is ch8 on? (noise)
+ ret nz
+ ld a, [wMusicNoiseSampleSet]
+ jr .next
+
+.sfx
+ ld a, [wSFXNoiseSampleSet]
+.next
+ ; load noise sample set id into de
+ ld e, a
+ ld d, 0
+ ; load ptr to noise sample set in hl
+ ld hl, Drumkits
+ add hl, de
+ add hl, de
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ; get pitch
+ ld a, [wCurMusicByte]
+ swap a
+ ; non-rest note?
+ and $f
+ ret z
+ ; use 'pitch' to seek noise sample set
+ ld e, a
+ ld d, 0
+ add hl, de
+ add hl, de
+ ; load sample pointer into wNoiseSampleAddress
+ ld a, [hli]
+ ld [wNoiseSampleAddress], a
+ ld a, [hl]
+ ld [wNoiseSampleAddress + 1], a
+ ; clear ????
+ xor a
+ ld [wNoiseSampleDelay], a
+ ret
+
+ParseMusicCommand:
+ ; reload command
+ ld a, [wCurMusicByte]
+ ; get command #
+ sub FIRST_MUSIC_CMD
+ ld e, a
+ ld d, 0
+ ; seek command pointer
+ ld hl, MusicCommands
+ add hl, de
+ add hl, de
+ ; jump to the new pointer
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ jp hl
+
+MusicCommands:
+; entries correspond to macros/sound.asm enumeration
+ dw Music_Octave8 ; octave 8
+ dw Music_Octave7 ; octave 7
+ dw Music_Octave6 ; octave 6
+ dw Music_Octave5 ; octave 5
+ dw Music_Octave4 ; octave 4
+ dw Music_Octave3 ; octave 3
+ dw Music_Octave2 ; octave 2
+ dw Music_Octave1 ; octave 1
+ dw Music_NoteType ; note length + intensity
+ dw Music_ForceOctave ; set starting octave
+ dw Music_Tempo ; tempo
+ dw Music_DutyCycle ; duty cycle
+ dw Music_Intensity ; intensity
+ dw Music_SoundStatus ; update sound status
+ dw Music_SoundDuty ; sfx duty
+ dw Music_ToggleSFX ; sound on/off
+ dw Music_SlidePitchTo ; pitch wheel
+ dw Music_Vibrato ; vibrato
+ dw MusicE2 ; unused
+ dw Music_ToggleNoise ; music noise sampling
+ dw Music_Panning ; force panning
+ dw Music_Volume ; volume
+ dw Music_Tone ; tone
+ dw MusicE7 ; unused
+ dw MusicE8 ; unused
+ dw Music_TempoRelative ; global tempo
+ dw Music_RestartChannel ; restart current channel from header
+ dw Music_NewSong ; new song
+ dw Music_SFXPriorityOn ; sfx priority on
+ dw Music_SFXPriorityOff ; sfx priority off
+ dw MusicEE ; unused
+ dw Music_StereoPanning ; stereo panning
+ dw Music_SFXToggleNoise ; sfx noise sampling
+ dw MusicF1 ; nothing
+ dw MusicF2 ; nothing
+ dw MusicF3 ; nothing
+ dw MusicF4 ; nothing
+ dw MusicF5 ; nothing
+ dw MusicF6 ; nothing
+ dw MusicF7 ; nothing
+ dw MusicF8 ; nothing
+ dw MusicF9 ; unused
+ dw Music_SetCondition ; setcondition
+ dw Music_JumpIf ; jumpif
+ dw Music_JumpChannel ; jump
+ dw Music_LoopChannel ; loop
+ dw Music_CallChannel ; call
+ dw Music_EndChannel ; return
+
+MusicF1:
+MusicF2:
+MusicF3:
+MusicF4:
+MusicF5:
+MusicF6:
+MusicF7:
+MusicF8:
+ ret
+
+Music_EndChannel:
+; called when $ff is encountered w/ subroutine flag set
+; end music stream
+; return to caller of the subroutine
+ ; reset subroutine flag
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ res SOUND_SUBROUTINE, [hl]
+ ; copy LastMusicAddress to MusicAddress
+ ld hl, CHANNEL_LAST_MUSIC_ADDRESS
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+Music_CallChannel:
+; call music stream (subroutine)
+; parameters: ll hh ; pointer to subroutine
+ ; get pointer from next 2 bytes
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ push de
+ ; copy MusicAddress to LastMusicAddress
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld hl, CHANNEL_LAST_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ; load pointer into MusicAddress
+ pop de
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ; set subroutine flag
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_SUBROUTINE, [hl]
+ ret
+
+Music_JumpChannel:
+; jump
+; parameters: ll hh ; pointer
+ ; get pointer from next 2 bytes
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+Music_LoopChannel:
+; loops xx - 1 times
+; 00: infinite
+; params: 3
+; xx ll hh
+; xx : loop count
+; ll hh : pointer
+
+ ; get loop count
+ call GetMusicByte
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_LOOPING, [hl] ; has the loop been initiated?
+ jr nz, .checkloop
+ and a ; loop counter 0 = infinite
+ jr z, .loop
+ ; initiate loop
+ dec a
+ set SOUND_LOOPING, [hl] ; set loop flag
+ ld hl, CHANNEL_LOOP_COUNT
+ add hl, bc
+ ld [hl], a ; store loop counter
+.checkloop
+ ld hl, CHANNEL_LOOP_COUNT
+ add hl, bc
+ ld a, [hl]
+ and a ; are we done?
+ jr z, .endloop
+ dec [hl]
+.loop
+ ; get pointer
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ ; load new pointer into MusicAddress
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+.endloop
+ ; reset loop flag
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ res SOUND_LOOPING, [hl]
+ ; skip to next command
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc de ; skip
+ inc de ; pointer
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ ret
+
+Music_SetCondition:
+; set condition for a jump
+; used with FB
+; params: 1
+; xx ; condition
+
+ ; set condition
+ call GetMusicByte
+ ld hl, CHANNEL_CONDITION
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_JumpIf:
+; conditional jump
+; used with FA
+; params: 3
+; xx: condition
+; ll hh: pointer
+
+; check condition
+ ; a = condition
+ call GetMusicByte
+ ; if existing condition matches, jump to new address
+ ld hl, CHANNEL_CONDITION
+ add hl, bc
+ cp [hl]
+ jr z, .jump
+; skip to next command
+ ; get address
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; skip pointer
+ inc de
+ inc de
+ ; update address
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ ret
+
+.jump
+; jump to the new address
+ ; get pointer
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ ; update pointer in MusicAddress
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+MusicEE:
+; conditional jump
+; checks a byte in ram corresponding to the current channel
+; doesn't seem to be set by any commands
+; params: 2
+; ll hh ; pointer
+
+; if ????, jump
+ ; get channel
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ ld e, a
+ ld d, 0
+ ; hl = wChannel1JumpCondition + channel id
+ ld hl, wChannel1JumpCondition
+ add hl, de
+ ; if set, jump
+ ld a, [hl]
+ and a
+ jr nz, .jump
+; skip to next command
+ ; get address
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; skip pointer
+ inc de
+ inc de
+ ; update address
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ ret
+
+.jump
+ ; reset jump flag
+ ld [hl], 0
+ ; de = pointer
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ ; update address
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ret
+
+MusicF9:
+; sets some flag
+; seems to be unused
+; params: 0
+ ld a, TRUE
+ ld [wUnusedMusicF9Flag], a
+ ret
+
+MusicE2:
+; seems to have been dummied out
+; params: 1
+ call GetMusicByte
+ ld hl, CHANNEL_FIELD2C
+ add hl, bc
+ ld [hl], a
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_UNKN_0B, [hl]
+ ret
+
+Music_Vibrato:
+; vibrato
+; params: 2
+; 1: [xx]
+ ; delay in frames
+; 2: [yz]
+ ; y: extent
+ ; z: rate (# frames per cycle)
+
+ ; set vibrato flag?
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_VIBRATO, [hl]
+ ; start at lower frequency (extent is positive)
+ ld hl, CHANNEL_FLAGS3
+ add hl, bc
+ res SOUND_VIBRATO_DIR, [hl]
+ ; get delay
+ call GetMusicByte
+; update delay
+ ld hl, CHANNEL_VIBRATO_DELAY
+ add hl, bc
+ ld [hl], a
+; update delay count
+ ld hl, CHANNEL_VIBRATO_DELAY_COUNT
+ add hl, bc
+ ld [hl], a
+; update extent
+; this is split into halves only to get added back together at the last second
+ ; get extent/rate
+ call GetMusicByte
+ ld hl, CHANNEL_VIBRATO_EXTENT
+ add hl, bc
+ ld d, a
+ ; get top nybble
+ and $f0
+ swap a
+ srl a ; halve
+ ld e, a
+ adc 0 ; round up
+ swap a
+ or e
+ ld [hl], a
+; update rate
+ ld hl, CHANNEL_VIBRATO_RATE
+ add hl, bc
+ ; get bottom nybble
+ ld a, d
+ and $f
+ ld d, a
+ swap a
+ or d
+ ld [hl], a
+ ret
+
+Music_SlidePitchTo:
+; set the target for pitch wheel
+; params: 2
+; note duration
+; target note
+ call GetMusicByte
+ ld [wCurNoteDuration], a
+
+ call GetMusicByte
+ ; pitch in e
+ ld d, a
+ and $f
+ ld e, a
+
+ ; octave in d
+ ld a, d
+ swap a
+ and $f
+ ld d, a
+ call GetFrequency
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET
+ add hl, bc
+ ld [hl], e
+ ld hl, CHANNEL_PITCH_WHEEL_TARGET + 1
+ add hl, bc
+ ld [hl], d
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_PITCH_WHEEL, [hl]
+ ret
+
+Music_Tone:
+; tone
+; params: 1 (dw)
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_CRY_PITCH, [hl]
+ ld hl, CHANNEL_CRY_PITCH + 1
+ add hl, bc
+ call GetMusicByte
+ ld [hld], a
+ call GetMusicByte
+ ld [hl], a
+ ret
+
+MusicE7:
+; unused
+; params: 1
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_UNKN_0E, [hl]
+ call GetMusicByte
+ ld hl, CHANNEL_FIELD29
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_SoundDuty:
+; sequence of 4 duty cycles to be looped
+; params: 1 (4 2-bit duty cycle arguments)
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_DUTY, [hl] ; duty cycle
+ ; sound duty sequence
+ call GetMusicByte
+ rrca
+ rrca
+ ld hl, CHANNEL_SFX_DUTY_LOOP
+ add hl, bc
+ ld [hl], a
+ ; update duty cycle
+ and $c0 ; only uses top 2 bits
+ ld hl, CHANNEL_DUTY_CYCLE
+ add hl, bc
+ ld [hl], a
+ ret
+
+MusicE8:
+; unused
+; params: 1
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_UNKN_0D, [hl]
+ call GetMusicByte
+ ld hl, CHANNEL_FIELD2A
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_ToggleSFX:
+; toggle something
+; params: none
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_SFX, [hl]
+ jr z, .on
+ res SOUND_SFX, [hl]
+ ret
+
+.on
+ set SOUND_SFX, [hl]
+ ret
+
+Music_ToggleNoise:
+; toggle music noise sampling
+; can't be used as a straight toggle since the param is not read from on->off
+; params:
+; noise on: 1
+; noise off: 0
+ ; check if noise sampling is on
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_NOISE, [hl]
+ jr z, .on
+ ; turn noise sampling off
+ res SOUND_NOISE, [hl]
+ ret
+
+.on
+ ; turn noise sampling on
+ set SOUND_NOISE, [hl]
+ call GetMusicByte
+ ld [wMusicNoiseSampleSet], a
+ ret
+
+Music_SFXToggleNoise:
+; toggle sfx noise sampling
+; params:
+; on: 1
+; off: 0
+ ; check if noise sampling is on
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ bit SOUND_NOISE, [hl]
+ jr z, .on
+ ; turn noise sampling off
+ res SOUND_NOISE, [hl]
+ ret
+
+.on
+ ; turn noise sampling on
+ set SOUND_NOISE, [hl]
+ call GetMusicByte
+ ld [wSFXNoiseSampleSet], a
+ ret
+
+Music_NoteType:
+; note length
+; # frames per 16th note
+; intensity: see Music_Intensity
+; params: 2
+ ; note length
+ call GetMusicByte
+ ld hl, CHANNEL_NOTE_LENGTH
+ add hl, bc
+ ld [hl], a
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ cp CHAN4
+ ret z
+ ; intensity
+ call Music_Intensity
+ ret
+
+Music_SoundStatus:
+; update sound status
+; params: 1
+ call GetMusicByte
+ ld [wSoundInput], a
+ ld hl, CHANNEL_NOTE_FLAGS
+ add hl, bc
+ set NOTE_UNKN_3, [hl]
+ ret
+
+Music_DutyCycle:
+; duty cycle
+; params: 1
+ call GetMusicByte
+ rrca
+ rrca
+ and $c0
+ ld hl, CHANNEL_DUTY_CYCLE
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_Intensity:
+; intensity
+; params: 1
+; hi: pressure
+; lo: velocity
+ call GetMusicByte
+ ld hl, CHANNEL_INTENSITY
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_Tempo:
+; global tempo
+; params: 2
+; de: tempo
+ call GetMusicByte
+ ld d, a
+ call GetMusicByte
+ ld e, a
+ call SetGlobalTempo
+ ret
+
+Music_Octave8:
+Music_Octave7:
+Music_Octave6:
+Music_Octave5:
+Music_Octave4:
+Music_Octave3:
+Music_Octave2:
+Music_Octave1:
+; set octave based on lo nybble of the command
+ ld hl, CHANNEL_OCTAVE
+ add hl, bc
+ ld a, [wCurMusicByte]
+ and 7
+ ld [hl], a
+ ret
+
+Music_ForceOctave:
+; set starting octave
+; this forces all notes up by the starting octave
+; params: 1
+ call GetMusicByte
+ ld hl, CHANNEL_PITCH_OFFSET
+ add hl, bc
+ ld [hl], a
+ ret
+
+Music_StereoPanning:
+; stereo panning
+; params: 1
+ ; stereo on?
+ ld a, [wOptions]
+ bit STEREO, a
+ jr nz, Music_Panning
+ ; skip param
+ call GetMusicByte
+ ret
+
+Music_Panning:
+; force panning
+; params: 1
+ call SetLRTracks
+ call GetMusicByte
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ and [hl]
+ ld [hl], a
+ ret
+
+Music_Volume:
+; set volume
+; params: 1
+; see Volume
+ ; read param even if it's not used
+ call GetMusicByte
+ ; is the song fading?
+ ld a, [wMusicFade]
+ and a
+ ret nz
+ ; reload param
+ ld a, [wCurMusicByte]
+ ; set volume
+ ld [wVolume], a
+ ret
+
+Music_TempoRelative:
+; set global tempo to current channel tempo +/- param
+; params: 1 signed
+ call GetMusicByte
+ ld e, a
+ ; check sign
+ cp $80
+ jr nc, .negative
+;positive
+ ld d, 0
+ jr .ok
+
+.negative
+ ld d, -1
+.ok
+ ld hl, CHANNEL_TEMPO
+ add hl, bc
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ add hl, de
+ ld e, l
+ ld d, h
+ call SetGlobalTempo
+ ret
+
+Music_SFXPriorityOn:
+; turn sfx priority on
+; params: none
+ ld a, 1
+ ld [wSFXPriority], a
+ ret
+
+Music_SFXPriorityOff:
+; turn sfx priority off
+; params: none
+ xor a
+ ld [wSFXPriority], a
+ ret
+
+Music_RestartChannel:
+; restart current channel from channel header (same bank)
+; params: 2 (5)
+; ll hh: pointer to new channel header
+; header format: 0x yy zz
+; x: channel # (0-3)
+; zzyy: pointer to new music data
+
+ ; update music id
+ ld hl, CHANNEL_MUSIC_ID
+ add hl, bc
+ ld a, [hli]
+ ld [wMusicID], a
+ ld a, [hl]
+ ld [wMusicID + 1], a
+ ; update music bank
+ ld hl, CHANNEL_MUSIC_BANK
+ add hl, bc
+ ld a, [hl]
+ ld [wMusicBank], a
+ ; get pointer to new channel header
+ call GetMusicByte
+ ld l, a
+ call GetMusicByte
+ ld h, a
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ push bc ; save current channel
+ call LoadChannel
+ call StartChannel
+ pop bc ; restore current channel
+ ret
+
+Music_NewSong:
+; new song
+; params: 2
+; de: song id
+ call GetMusicByte
+ ld e, a
+ call GetMusicByte
+ ld d, a
+ push bc
+ call _PlayMusic
+ pop bc
+ ret
+
+GetMusicByte:
+; returns byte from current address in a
+; advances to next byte in music data
+; input: bc = start of current channel
+ push hl
+ push de
+ ; load address into de
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld a, [hli]
+ ld e, a
+ ld d, [hl]
+ ; load bank into a
+ ld hl, CHANNEL_MUSIC_BANK
+ add hl, bc
+ ld a, [hl]
+ ; get byte
+ call _LoadMusicByte ; load data into wCurMusicByte
+ inc de ; advance to next byte for next time this is called
+ ; update channeldata address
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ ld a, e
+ ld [hli], a
+ ld [hl], d
+ ; cleanup
+ pop de
+ pop hl
+ ; store channeldata in a
+ ld a, [wCurMusicByte]
+ ret
+
+GetFrequency:
+; generate frequency
+; input:
+; d: octave
+; e: pitch
+; output:
+; de: frequency
+
+; get octave
+ ; get starting octave
+ ld hl, CHANNEL_PITCH_OFFSET
+ add hl, bc
+ ld a, [hl]
+ swap a ; hi nybble
+ and $f
+ ; add current octave
+ add d
+ push af ; we'll use this later
+ ; get starting octave
+ ld hl, CHANNEL_PITCH_OFFSET
+ add hl, bc
+ ld a, [hl]
+ and $f ; lo nybble
+ ld l, a ; ok
+ ld d, 0
+ ld h, d
+ add hl, de ; add current pitch
+ add hl, hl ; skip 2 bytes for each
+ ld de, FrequencyTable
+ add hl, de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; get our octave
+ pop af
+ ; shift right by [7 - octave] bits
+.loop
+ ; [7 - octave] loops
+ cp $7
+ jr nc, .ok
+ ; sra de
+ sra d
+ rr e
+ inc a
+ jr .loop
+
+.ok
+ ld a, d
+ and $7 ; top 3 bits for frequency (11 total)
+ ld d, a
+ ret
+
+SetNoteDuration:
+; input: a = note duration in 16ths
+ ; store delay units in de
+ inc a
+ ld e, a
+ ld d, 0
+ ; store NoteLength in a
+ ld hl, CHANNEL_NOTE_LENGTH
+ add hl, bc
+ ld a, [hl]
+ ; multiply NoteLength by delay units
+ ld l, 0 ; just multiply
+ call .Multiply
+ ld a, l ; low
+ ; store Tempo in de
+ ld hl, CHANNEL_TEMPO
+ add hl, bc
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; add ???? to the next result
+ ld hl, CHANNEL_FIELD16
+ add hl, bc
+ ld l, [hl]
+ ; multiply Tempo by last result (NoteLength * LOW(delay))
+ call .Multiply
+ ; copy result to de
+ ld e, l
+ ld d, h
+ ; store result in ????
+ ld hl, CHANNEL_FIELD16
+ add hl, bc
+ ld [hl], e
+ ; store result in NoteDuration
+ ld hl, CHANNEL_NOTE_DURATION
+ add hl, bc
+ ld [hl], d
+ ret
+
+.Multiply:
+; multiplies a and de
+; adds the result to l
+; stores the result in hl
+ ld h, 0
+.loop
+ ; halve a
+ srl a
+ ; is there a remainder?
+ jr nc, .skip
+ ; add it to the result
+ add hl, de
+.skip
+ ; add de, de
+ sla e
+ rl d
+ ; are we done?
+ and a
+ jr nz, .loop
+ ret
+
+SetGlobalTempo:
+ push bc ; save current channel
+ ; are we dealing with music or sfx?
+ ld a, [wCurChannel]
+ cp CHAN5
+ jr nc, .sfxchannels
+ ld bc, wChannel1
+ call Tempo
+ ld bc, wChannel2
+ call Tempo
+ ld bc, wChannel3
+ call Tempo
+ ld bc, wChannel4
+ call Tempo
+ jr .end
+
+.sfxchannels
+ ld bc, wChannel5
+ call Tempo
+ ld bc, wChannel6
+ call Tempo
+ ld bc, wChannel7
+ call Tempo
+ ld bc, wChannel8
+ call Tempo
+.end
+ pop bc ; restore current channel
+ ret
+
+Tempo:
+; input:
+; de: note length
+ ; update Tempo
+ ld hl, CHANNEL_TEMPO
+ add hl, bc
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ; clear ????
+ xor a
+ ld hl, CHANNEL_FIELD16
+ add hl, bc
+ ld [hl], a
+ ret
+
+StartChannel:
+ call SetLRTracks
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_CHANNEL_ON, [hl] ; turn channel on
+ ret
+
+SetLRTracks:
+; set tracks for a the current channel to default
+; seems to be redundant since this is overwritten by stereo data later
+ push de
+ ; store current channel in de
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ ld e, a
+ ld d, 0
+ ; get this channel's lr tracks
+ call GetLRTracks
+ add hl, de ; de = channel 0-3
+ ld a, [hl]
+ ; load lr tracks into Tracks
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ ld [hl], a
+ pop de
+ ret
+
+_PlayMusic::
+; load music
+ call MusicOff
+ ld hl, wMusicID
+ ld [hl], e ; song number
+ inc hl
+ ld [hl], d ; (always 0)
+ ld hl, Music
+ add hl, de ; three
+ add hl, de ; byte
+ add hl, de ; pointer
+ ld a, [hli]
+ ld [wMusicBank], a
+ ld e, [hl]
+ inc hl
+ ld d, [hl] ; music header address
+ call LoadMusicByte ; store first byte of music header in a
+ rlca
+ rlca
+ maskbits NUM_MUSIC_CHANS
+ inc a
+.loop
+; start playing channels
+ push af
+ call LoadChannel
+ call StartChannel
+ pop af
+ dec a
+ jr nz, .loop
+ xor a
+ ld [wUnusedMusicF9Flag], a
+ ld [wChannel1JumpCondition], a
+ ld [wChannel2JumpCondition], a
+ ld [wChannel3JumpCondition], a
+ ld [wChannel4JumpCondition], a
+ ld [wNoiseSampleAddress], a
+ ld [wNoiseSampleAddress + 1], a
+ ld [wNoiseSampleDelay], a
+ ld [wMusicNoiseSampleSet], a
+ call MusicOn
+ ret
+
+_PlayCry::
+; Play cry de using parameters:
+; wCryPitch
+; wCryLength
+
+ call MusicOff
+
+; Overload the music id with the cry id
+ ld hl, wMusicID
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+; 3-byte pointers (bank, address)
+ ld hl, Cries
+ add hl, de
+ add hl, de
+ add hl, de
+
+ ld a, [hli]
+ ld [wMusicBank], a
+
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+
+; Read the cry's sound header
+ call LoadMusicByte
+ ; Top 2 bits contain the number of channels
+ rlca
+ rlca
+ maskbits NUM_MUSIC_CHANS
+
+; For each channel:
+ inc a
+.loop
+ push af
+ call LoadChannel
+
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_REST, [hl]
+
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_CRY_PITCH, [hl]
+
+ ld hl, CHANNEL_CRY_PITCH
+ add hl, bc
+ ld a, [wCryPitch]
+ ld [hli], a
+ ld a, [wCryPitch + 1]
+ ld [hl], a
+
+; No tempo for channel 4
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ cp CHAN4
+ jr nc, .start
+
+; Tempo is effectively length
+ ld hl, CHANNEL_TEMPO
+ add hl, bc
+ ld a, [wCryLength]
+ ld [hli], a
+ ld a, [wCryLength + 1]
+ ld [hl], a
+.start
+ call StartChannel
+ ld a, [wStereoPanningMask]
+ and a
+ jr z, .next
+
+; Stereo only: Play cry from the monster's side.
+; This only applies in-battle.
+
+ ld a, [wOptions]
+ bit STEREO, a
+ jr z, .next
+
+; [Tracks] &= [wCryTracks]
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ ld a, [hl]
+ ld hl, wCryTracks
+ and [hl]
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ ld [hl], a
+
+.next
+ pop af
+ dec a
+ jr nz, .loop
+
+; Cries play at max volume, so we save the current volume for later.
+ ld a, [wLastVolume]
+ and a
+ jr nz, .end
+
+ ld a, [wVolume]
+ ld [wLastVolume], a
+ ld a, MAX_VOLUME
+ ld [wVolume], a
+
+.end
+ ld a, 1 ; stop playing music
+ ld [wSFXPriority], a
+ call MusicOn
+ ret
+
+_PlaySFX::
+; clear channels if they aren't already
+ call MusicOff
+ ld hl, wChannel5Flags1
+ bit SOUND_CHANNEL_ON, [hl] ; ch5 on?
+ jr z, .ch6
+ res SOUND_CHANNEL_ON, [hl] ; turn it off
+ xor a
+ ldh [rNR11], a ; length/wavepattern = 0
+ ld a, $8
+ ldh [rNR12], a ; envelope = 0
+ xor a
+ ldh [rNR13], a ; frequency lo = 0
+ ld a, $80
+ ldh [rNR14], a ; restart sound (freq hi = 0)
+ xor a
+ ld [wSoundInput], a ; global sound off
+ ldh [rNR10], a ; sweep = 0
+.ch6
+ ld hl, wChannel6Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr z, .ch7
+ res SOUND_CHANNEL_ON, [hl] ; turn it off
+ xor a
+ ldh [rNR21], a ; length/wavepattern = 0
+ ld a, $8
+ ldh [rNR22], a ; envelope = 0
+ xor a
+ ldh [rNR23], a ; frequency lo = 0
+ ld a, $80
+ ldh [rNR24], a ; restart sound (freq hi = 0)
+.ch7
+ ld hl, wChannel7Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr z, .ch8
+ res SOUND_CHANNEL_ON, [hl] ; turn it off
+ xor a
+ ldh [rNR30], a ; sound mode #3 off
+ ldh [rNR31], a ; length/wavepattern = 0
+ ld a, $8
+ ldh [rNR32], a ; envelope = 0
+ xor a
+ ldh [rNR33], a ; frequency lo = 0
+ ld a, $80
+ ldh [rNR34], a ; restart sound (freq hi = 0)
+.ch8
+ ld hl, wChannel8Flags1
+ bit SOUND_CHANNEL_ON, [hl]
+ jr z, .chscleared
+ res SOUND_CHANNEL_ON, [hl] ; turn it off
+ xor a
+ ldh [rNR41], a ; length/wavepattern = 0
+ ld a, $8
+ ldh [rNR42], a ; envelope = 0
+ xor a
+ ldh [rNR43], a ; frequency lo = 0
+ ld a, $80
+ ldh [rNR44], a ; restart sound (freq hi = 0)
+ xor a
+ ld [wNoiseSampleAddress], a
+ ld [wNoiseSampleAddress + 1], a
+.chscleared
+; start reading sfx header for # chs
+ ld hl, wMusicID
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ ld hl, SFX
+ add hl, de ; three
+ add hl, de ; byte
+ add hl, de ; pointers
+ ; get bank
+ ld a, [hli]
+ ld [wMusicBank], a
+ ; get address
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ; get # channels
+ call LoadMusicByte
+ rlca ; top 2
+ rlca ; bits
+ maskbits NUM_MUSIC_CHANS
+ inc a ; # channels -> # loops
+.startchannels
+ push af
+ call LoadChannel ; bc = current channel
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_SFX, [hl]
+ call StartChannel
+ pop af
+ dec a
+ jr nz, .startchannels
+ call MusicOn
+ xor a
+ ld [wSFXPriority], a
+ ret
+
+PlayStereoSFX::
+; play sfx de
+
+ call MusicOff
+
+; standard procedure if stereo's off
+ ld a, [wOptions]
+ bit STEREO, a
+ jp z, _PlaySFX
+
+; else, let's go ahead with this
+ ld hl, wMusicID
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+; get sfx ptr
+ ld hl, SFX
+ add hl, de
+ add hl, de
+ add hl, de
+
+; bank
+ ld a, [hli]
+ ld [wMusicBank], a
+; address
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+
+; bit 2-3
+ call LoadMusicByte
+ rlca
+ rlca
+ maskbits NUM_MUSIC_CHANS
+ inc a
+
+.loop
+ push af
+ call LoadChannel
+
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_SFX, [hl]
+
+ push de
+ ; get tracks for this channel
+ ld a, [wCurChannel]
+ maskbits NUM_MUSIC_CHANS
+ ld e, a
+ ld d, 0
+ call GetLRTracks
+ add hl, de
+ ld a, [hl]
+ ld hl, wStereoPanningMask
+ and [hl]
+
+ ld hl, CHANNEL_TRACKS
+ add hl, bc
+ ld [hl], a
+
+ ld hl, CHANNEL_FIELD30
+ add hl, bc
+ ld [hl], a
+
+ ld a, [wCryTracks]
+ cp 2 ; ch 1-2
+ jr c, .skip
+
+; ch3-4
+ ld a, [wSFXDuration]
+
+ ld hl, CHANNEL_FIELD2E
+ add hl, bc
+ ld [hl], a
+
+ ld hl, CHANNEL_FIELD2F
+ add hl, bc
+ ld [hl], a
+
+ ld hl, CHANNEL_FLAGS2
+ add hl, bc
+ set SOUND_UNKN_0F, [hl]
+
+.skip
+ pop de
+
+; turn channel on
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ set SOUND_CHANNEL_ON, [hl] ; on
+
+; done?
+ pop af
+ dec a
+ jr nz, .loop
+
+; we're done
+ call MusicOn
+ ret
+
+LoadChannel:
+; prep channel for use
+; input:
+; de:
+ ; get pointer to current channel
+ call LoadMusicByte
+ inc de
+ and $7 ; bit 0-2 (current channel)
+ ld [wCurChannel], a
+ ld c, a
+ ld b, 0
+ ld hl, ChannelPointers
+ add hl, bc
+ add hl, bc
+ ld c, [hl]
+ inc hl
+ ld b, [hl] ; bc = channel pointer
+ ld hl, CHANNEL_FLAGS1
+ add hl, bc
+ res SOUND_CHANNEL_ON, [hl] ; channel off
+ call ChannelInit
+ ; load music pointer
+ ld hl, CHANNEL_MUSIC_ADDRESS
+ add hl, bc
+ call LoadMusicByte
+ ld [hli], a
+ inc de
+ call LoadMusicByte
+ ld [hl], a
+ inc de
+ ; load music id
+ ld hl, CHANNEL_MUSIC_ID
+ add hl, bc
+ ld a, [wMusicID]
+ ld [hli], a
+ ld a, [wMusicID + 1]
+ ld [hl], a
+ ; load music bank
+ ld hl, CHANNEL_MUSIC_BANK
+ add hl, bc
+ ld a, [wMusicBank]
+ ld [hl], a
+ ret
+
+ChannelInit:
+; make sure channel is cleared
+; set default tempo and note length in case nothing is loaded
+; input:
+; bc = channel struct pointer
+ push de
+ xor a
+ ; get channel struct location and length
+ ld hl, CHANNEL_MUSIC_ID ; start
+ add hl, bc
+ ld e, CHANNEL_STRUCT_LENGTH ; channel struct length
+ ; clear channel
+.loop
+ ld [hli], a
+ dec e
+ jr nz, .loop
+ ; set tempo to default ($100)
+ ld hl, CHANNEL_TEMPO
+ add hl, bc
+ xor a
+ ld [hli], a
+ inc a
+ ld [hl], a
+ ; set note length to default ($1) (fast)
+ ld hl, CHANNEL_NOTE_LENGTH
+ add hl, bc
+ ld [hl], a
+ pop de
+ ret
+
+LoadMusicByte::
+; input:
+; de = current music address
+; output:
+; a = wCurMusicByte
+ ld a, [wMusicBank]
+ call _LoadMusicByte
+ ld a, [wCurMusicByte]
+ ret
+
+INCLUDE "audio/notes.asm"
+
+INCLUDE "audio/wave_samples.asm"
+
+INCLUDE "audio/drumkits.asm"
+
+GetLRTracks:
+; gets the default sound l/r channels
+; stores mono/stereo table in hl
+ ld a, [wOptions]
+ bit STEREO, a
+ ; made redundant, could have had a purpose in gold
+ jr nz, .stereo
+ ld hl, MonoTracks
+ ret
+
+.stereo
+ ld hl, StereoTracks
+ ret
+
+MonoTracks:
+; bit corresponds to track #
+; hi: left channel
+; lo: right channel
+ db $11, $22, $44, $88
+
+StereoTracks:
+; made redundant
+; seems to be modified on a per-song basis
+ db $11, $22, $44, $88
+
+ChannelPointers:
+; music channels
+ dw wChannel1
+ dw wChannel2
+ dw wChannel3
+ dw wChannel4
+; sfx channels
+ dw wChannel5
+ dw wChannel6
+ dw wChannel7
+ dw wChannel8
+
+ClearChannels::
+; runs ClearChannel for all 4 channels
+; doesn't seem to be used, but functionally identical to MapSetup_Sound_Off
+ ld hl, rNR50
+ xor a
+ ld [hli], a
+ ld [hli], a
+ ld a, $80
+ ld [hli], a
+ ld hl, rNR10
+ ld e, NUM_MUSIC_CHANS
+.loop
+ call ClearChannel
+ dec e
+ jr nz, .loop
+ ret
+
+ClearChannel:
+; input: hl = beginning hw sound register (rNR10, rNR20, rNR30, rNR40)
+; output: 00 00 80 00 80
+
+; sound channel 1 2 3 4
+ xor a
+ ld [hli], a ; rNR10, rNR20, rNR30, rNR40 ; sweep = 0
+
+ ld [hli], a ; rNR11, rNR21, rNR31, rNR41 ; length/wavepattern = 0
+ ld a, $8
+ ld [hli], a ; rNR12, rNR22, rNR32, rNR42 ; envelope = 0
+ xor a
+ ld [hli], a ; rNR13, rNR23, rNR33, rNR43 ; frequency lo = 0
+ ld a, $80
+ ld [hli], a ; rNR14, rNR24, rNR34, rNR44 ; restart sound (freq hi = 0)
+ ret
+
+PlayTrainerEncounterMusic::
+; input: e = trainer type
+ ; turn fade off
+ xor a
+ ld [wMusicFade], a
+ ; play nothing for one frame
+ push de
+ ld de, MUSIC_NONE
+ call PlayMusic
+ call DelayFrame
+ ; play new song
+ call MaxVolume
+ pop de
+ ld d, $00
+ ld hl, TrainerEncounterMusic
+ add hl, de
+ ld e, [hl]
+ call PlayMusic
+ ret