mirror of https://github.com/pret/pokecrystal.git
2853 lines
47 KiB
NASM
2853 lines
47 KiB
NASM
; The entire sound engine. Uses section "audio" in WRAM.
|
|
|
|
; Interfaces are in bank 0.
|
|
|
|
; Notable functions:
|
|
; FadeMusic
|
|
; PlayStereoSFX
|
|
|
|
_InitSound::
|
|
; 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 ; rNR52 ; 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, wAudio
|
|
ld de, wAudioEnd - wAudio
|
|
.clearaudio
|
|
xor a
|
|
ld [hli], a
|
|
dec de
|
|
ld a, e
|
|
or d
|
|
jr nz, .clearaudio
|
|
|
|
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 _InitSound
|
|
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_SLIDE, [hl]
|
|
; get next note
|
|
call ParseMusic
|
|
.continue_sound_update
|
|
call ApplyPitchSlide
|
|
; duty cycle
|
|
ld hl, CHANNEL_DUTY_CYCLE
|
|
add hl, bc
|
|
ld a, [hli]
|
|
ld [wCurTrackDuty], a
|
|
; volume envelope
|
|
ld a, [hli]
|
|
ld [wCurTrackVolumeEnvelope], 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, .ChannelFunctions
|
|
ld a, [wCurChannel]
|
|
maskbits NUM_CHANNELS
|
|
add a
|
|
ld e, a
|
|
ld d, 0
|
|
add hl, de
|
|
ld a, [hli]
|
|
ld h, [hl]
|
|
ld l, a
|
|
jp hl
|
|
|
|
.ChannelFunctions:
|
|
table_width 2, UpdateChannels.ChannelFunctions
|
|
; music channels
|
|
dw .Channel1
|
|
dw .Channel2
|
|
dw .Channel3
|
|
dw .Channel4
|
|
assert_table_length NUM_MUSIC_CHANS
|
|
; sfx channels
|
|
; identical to music channels, except .Channel5 is not disabled by the low-HP danger sound
|
|
; (instead, PlayDanger does not play the danger sound if sfx is playing)
|
|
dw .Channel5
|
|
dw .Channel6
|
|
dw .Channel7
|
|
dw .Channel8
|
|
assert_table_length NUM_CHANNELS
|
|
|
|
.Channel1:
|
|
ld a, [wLowHealthAlarm]
|
|
bit DANGER_ON_F, a
|
|
ret nz
|
|
.Channel5:
|
|
ld hl, CHANNEL_NOTE_FLAGS
|
|
add hl, bc
|
|
bit NOTE_PITCH_SWEEP, [hl]
|
|
jr z, .noPitchSweep
|
|
;
|
|
ld a, [wPitchSweep]
|
|
ldh [rNR10], a
|
|
.noPitchSweep
|
|
bit NOTE_REST, [hl] ; rest
|
|
jr nz, .ch1_rest
|
|
bit NOTE_NOISE_SAMPLING, [hl]
|
|
jr nz, .ch1_noise_sampling
|
|
bit NOTE_FREQ_OVERRIDE, [hl]
|
|
jr nz, .ch1_frequency_override
|
|
bit NOTE_VIBRATO_OVERRIDE, [hl]
|
|
jr nz, .ch1_vibrato_override
|
|
jr .ch1_check_duty_override
|
|
|
|
.ch1_frequency_override
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR13], a
|
|
ld a, [wCurTrackFrequency + 1]
|
|
ldh [rNR14], a
|
|
.ch1_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
|
|
|
|
.ch1_vibrato_override
|
|
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
|
|
|
|
.ch1_rest
|
|
ldh a, [rNR52]
|
|
and %10001110 ; ch1 off
|
|
ldh [rNR52], a
|
|
ld hl, rNR10
|
|
call ClearChannel
|
|
ret
|
|
|
|
.ch1_noise_sampling
|
|
ld hl, wCurTrackDuty
|
|
ld a, $3f ; sound length
|
|
or [hl]
|
|
ldh [rNR11], a
|
|
ld a, [wCurTrackVolumeEnvelope]
|
|
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, .ch2_rest
|
|
bit NOTE_NOISE_SAMPLING, [hl]
|
|
jr nz, .ch2_noise_sampling
|
|
bit NOTE_VIBRATO_OVERRIDE, [hl]
|
|
jr nz, .ch2_vibrato_override
|
|
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
|
|
|
|
.ch2_frequency_override ; unreferenced
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR23], a
|
|
ld a, [wCurTrackFrequency + 1]
|
|
ldh [rNR24], a
|
|
ret
|
|
|
|
.ch2_vibrato_override
|
|
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
|
|
|
|
.ch2_rest
|
|
ldh a, [rNR52]
|
|
and %10001101 ; ch2 off
|
|
ldh [rNR52], a
|
|
ld hl, rNR21 - 1 ; there is no rNR20
|
|
call ClearChannel
|
|
ret
|
|
|
|
.ch2_noise_sampling
|
|
ld hl, wCurTrackDuty
|
|
ld a, $3f ; sound length
|
|
or [hl]
|
|
ldh [rNR21], a
|
|
ld a, [wCurTrackVolumeEnvelope]
|
|
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]
|
|
jr nz, .ch3_rest
|
|
bit NOTE_NOISE_SAMPLING, [hl]
|
|
jr nz, .ch3_noise_sampling
|
|
bit NOTE_VIBRATO_OVERRIDE, [hl]
|
|
jr nz, .ch3_vibrato_override
|
|
ret
|
|
|
|
.ch3_frequency_override ; unreferenced
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR33], a
|
|
ld a, [wCurTrackFrequency + 1]
|
|
ldh [rNR34], a
|
|
ret
|
|
|
|
.ch3_vibrato_override
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR33], a
|
|
ret
|
|
|
|
.ch3_rest
|
|
ldh a, [rNR52]
|
|
and %10001011 ; ch3 off
|
|
ldh [rNR52], a
|
|
ld hl, rNR30
|
|
call ClearChannel
|
|
ret
|
|
|
|
.ch3_noise_sampling
|
|
ld a, $3f ; sound length
|
|
ldh [rNR31], a
|
|
xor a
|
|
ldh [rNR30], a
|
|
call .load_wave_pattern
|
|
ld a, $80
|
|
ldh [rNR30], a
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR33], a
|
|
ld a, [wCurTrackFrequency + 1]
|
|
or $80
|
|
ldh [rNR34], a
|
|
ret
|
|
|
|
.load_wave_pattern
|
|
push hl
|
|
ld a, [wCurTrackVolumeEnvelope]
|
|
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, [wCurTrackVolumeEnvelope]
|
|
and $f0
|
|
sla a
|
|
ldh [rNR32], a
|
|
ret
|
|
|
|
.Channel4:
|
|
.Channel8:
|
|
ld hl, CHANNEL_NOTE_FLAGS
|
|
add hl, bc
|
|
bit NOTE_REST, [hl]
|
|
jr nz, .ch4_rest
|
|
bit NOTE_NOISE_SAMPLING, [hl]
|
|
jr nz, .ch4_noise_sampling
|
|
ret
|
|
|
|
.ch4_frequency_override ; unreferenced
|
|
ld a, [wCurTrackFrequency]
|
|
ldh [rNR43], a
|
|
ret
|
|
|
|
.ch4_rest
|
|
ldh a, [rNR52]
|
|
and %10000111 ; ch4 off
|
|
ldh [rNR52], a
|
|
ld hl, rNR41 - 1 ; there is no rNR40
|
|
call ClearChannel
|
|
ret
|
|
|
|
.ch4_noise_sampling
|
|
ld a, $3f ; sound length
|
|
ldh [rNR41], a
|
|
ld a, [wCurTrackVolumeEnvelope]
|
|
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 ~(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 slide to finish
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
bit SOUND_PITCH_SLIDE, [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 slide
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET
|
|
add hl, bc
|
|
ld a, e
|
|
sub [hl]
|
|
ld e, a
|
|
ld a, d
|
|
sbc 0
|
|
ld d, a
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET + 1
|
|
add hl, bc
|
|
sub [hl]
|
|
jr nc, .greater_than
|
|
ld hl, CHANNEL_FLAGS3
|
|
add hl, bc
|
|
set SOUND_PITCH_SLIDE_DIR, [hl]
|
|
; get frequency
|
|
ld hl, CHANNEL_FREQUENCY
|
|
add hl, bc
|
|
ld e, [hl]
|
|
inc hl
|
|
ld d, [hl]
|
|
; ????
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET
|
|
add hl, bc
|
|
ld a, [hl]
|
|
sub e
|
|
ld e, a
|
|
ld a, d
|
|
sbc 0
|
|
ld d, a
|
|
; ????
|
|
ld hl, CHANNEL_PITCH_SLIDE_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_SLIDE_DIR, [hl]
|
|
; get frequency
|
|
ld hl, CHANNEL_FREQUENCY
|
|
add hl, bc
|
|
ld e, [hl]
|
|
inc hl
|
|
ld d, [hl]
|
|
; get distance from pitch slide target
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET
|
|
add hl, bc
|
|
ld a, e
|
|
sub [hl]
|
|
ld e, a
|
|
ld a, d
|
|
sbc 0
|
|
ld d, a
|
|
ld hl, CHANNEL_PITCH_SLIDE_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_SLIDE_AMOUNT
|
|
add hl, bc
|
|
ld [hl], d ; quotient
|
|
ld hl, CHANNEL_PITCH_SLIDE_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_LOOP, [hl] ; duty cycle looping
|
|
jr z, .next
|
|
ld hl, CHANNEL_DUTY_CYCLE_PATTERN
|
|
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_PITCH_OFFSET, [hl]
|
|
jr z, .vibrato
|
|
ld hl, CHANNEL_PITCH_OFFSET
|
|
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
|
|
|
|
ApplyPitchSlide:
|
|
; quit if pitch slide inactive
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
bit SOUND_PITCH_SLIDE, [hl]
|
|
ret z
|
|
; de = Frequency
|
|
ld hl, CHANNEL_FREQUENCY
|
|
add hl, bc
|
|
ld e, [hl]
|
|
inc hl
|
|
ld d, [hl]
|
|
; check whether pitch slide is going up or down
|
|
ld hl, CHANNEL_FLAGS3
|
|
add hl, bc
|
|
bit SOUND_PITCH_SLIDE_DIR, [hl]
|
|
jr z, .decreasing
|
|
; frequency += [Channel*PitchSlideAmount]
|
|
ld hl, CHANNEL_PITCH_SLIDE_AMOUNT
|
|
add hl, bc
|
|
ld l, [hl]
|
|
ld h, 0
|
|
add hl, de
|
|
ld d, h
|
|
ld e, l
|
|
; [Channel*Field25] += [Channel*PitchSlideAmountFraction]
|
|
; if rollover: Frequency += 1
|
|
ld hl, CHANNEL_PITCH_SLIDE_AMOUNT_FRACTION
|
|
add hl, bc
|
|
ld a, [hl]
|
|
ld hl, CHANNEL_FIELD25
|
|
add hl, bc
|
|
add [hl]
|
|
ld [hl], a
|
|
; could have done "jr nc, .no_rollover / inc de / .no_rollover"
|
|
ld a, 0
|
|
adc e
|
|
ld e, a
|
|
ld a, 0
|
|
adc d
|
|
ld d, a
|
|
; Compare the dw at [Channel*PitchSlideTarget] to de.
|
|
; If frequency is greater, we're finished.
|
|
; Otherwise, load the frequency and set two flags.
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET + 1
|
|
add hl, bc
|
|
ld a, [hl]
|
|
cp d
|
|
jp c, .finished_pitch_slide
|
|
jr nz, .continue_pitch_slide
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET
|
|
add hl, bc
|
|
ld a, [hl]
|
|
cp e
|
|
jp c, .finished_pitch_slide
|
|
jr .continue_pitch_slide
|
|
|
|
.decreasing
|
|
; frequency -= [Channel*PitchSlideAmount]
|
|
ld a, e
|
|
ld hl, CHANNEL_PITCH_SLIDE_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_SLIDE_AMOUNT_FRACTION
|
|
add hl, bc
|
|
ld a, [hl]
|
|
add a
|
|
ld [hl], a
|
|
; could have done "jr nc, .no_rollover / dec de / .no_rollover"
|
|
ld a, e
|
|
sbc 0
|
|
ld e, a
|
|
ld a, d
|
|
sbc 0
|
|
ld d, a
|
|
; Compare the dw at [Channel*PitchSlideTarget] to de.
|
|
; If frequency is lower, we're finished.
|
|
; Otherwise, load the frequency and set two flags.
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET + 1
|
|
add hl, bc
|
|
ld a, d
|
|
cp [hl]
|
|
jr c, .finished_pitch_slide
|
|
jr nz, .continue_pitch_slide
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET
|
|
add hl, bc
|
|
ld a, e
|
|
cp [hl]
|
|
jr nc, .continue_pitch_slide
|
|
.finished_pitch_slide
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
res SOUND_PITCH_SLIDE, [hl]
|
|
ld hl, CHANNEL_FLAGS3
|
|
add hl, bc
|
|
res SOUND_PITCH_SLIDE_DIR, [hl]
|
|
ret
|
|
|
|
.continue_pitch_slide
|
|
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: volume envelope
|
|
; 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 sound_ret_cmd
|
|
jr z, .quit
|
|
|
|
and $f
|
|
inc a
|
|
ld [wNoiseSampleDelay], a
|
|
ld a, [de]
|
|
inc de
|
|
ld [wCurTrackVolumeEnvelope], 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 sound_ret_cmd
|
|
jr z, .sound_ret
|
|
cp FIRST_MUSIC_CMD
|
|
jr c, .readnote
|
|
.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, ParseSFXOrCry
|
|
bit SOUND_CRY, [hl]
|
|
jp nz, ParseSFXOrCry
|
|
bit SOUND_NOISE, [hl]
|
|
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
|
|
|
|
.sound_ret
|
|
; $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_CRY, [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, wChannel6PitchOffset
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld hl, wChannel8PitchOffset
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld a, [wLastVolume]
|
|
ld [wVolume], a
|
|
xor a
|
|
ld [wLastVolume], a
|
|
ld [wSFXPriority], a
|
|
ret
|
|
|
|
ParseSFXOrCry:
|
|
; 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 volume envelope from next param
|
|
call GetMusicByte
|
|
ld hl, CHANNEL_VOLUME_ENVELOPE
|
|
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 audio constants (see macros/scripts/audio.asm)
|
|
table_width 2, MusicCommands
|
|
dw Music_Octave8
|
|
dw Music_Octave7
|
|
dw Music_Octave6
|
|
dw Music_Octave5
|
|
dw Music_Octave4
|
|
dw Music_Octave3
|
|
dw Music_Octave2
|
|
dw Music_Octave1
|
|
dw Music_NoteType ; note length + volume envelope
|
|
dw Music_Transpose
|
|
dw Music_Tempo
|
|
dw Music_DutyCycle
|
|
dw Music_VolumeEnvelope
|
|
dw Music_PitchSweep
|
|
dw Music_DutyCyclePattern
|
|
dw Music_ToggleSFX
|
|
dw Music_PitchSlide
|
|
dw Music_Vibrato
|
|
dw MusicE2 ; unused
|
|
dw Music_ToggleNoise
|
|
dw Music_ForceStereoPanning
|
|
dw Music_Volume
|
|
dw Music_PitchOffset
|
|
dw MusicE7 ; unused
|
|
dw MusicE8 ; unused
|
|
dw Music_TempoRelative
|
|
dw Music_RestartChannel
|
|
dw Music_NewSong
|
|
dw Music_SFXPriorityOn
|
|
dw Music_SFXPriorityOff
|
|
dw MusicEE ; unused
|
|
dw Music_StereoPanning
|
|
dw Music_SFXToggleNoise
|
|
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
|
|
dw Music_JumpIf
|
|
dw Music_Jump
|
|
dw Music_Loop
|
|
dw Music_Call
|
|
dw Music_Ret
|
|
assert_table_length $100 - FIRST_MUSIC_CMD
|
|
|
|
MusicF1:
|
|
MusicF2:
|
|
MusicF3:
|
|
MusicF4:
|
|
MusicF5:
|
|
MusicF6:
|
|
MusicF7:
|
|
MusicF8:
|
|
ret
|
|
|
|
Music_Ret:
|
|
; 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_Call:
|
|
; 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_Jump:
|
|
; 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_Loop:
|
|
; 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:
|
|
; unused
|
|
; conditional jump
|
|
; checks a byte in ram corresponding to the current channel
|
|
; 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:
|
|
; unused
|
|
; sets some flag
|
|
; params: 0
|
|
ld a, TRUE
|
|
ld [wUnusedMusicF9Flag], a
|
|
ret
|
|
|
|
MusicE2:
|
|
; unused
|
|
; 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_PitchSlide:
|
|
; set the target for pitch slide
|
|
; 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_SLIDE_TARGET
|
|
add hl, bc
|
|
ld [hl], e
|
|
ld hl, CHANNEL_PITCH_SLIDE_TARGET + 1
|
|
add hl, bc
|
|
ld [hl], d
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
set SOUND_PITCH_SLIDE, [hl]
|
|
ret
|
|
|
|
Music_PitchOffset:
|
|
; tone
|
|
; params: 1 (dw)
|
|
; offset to add to each note frequency
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
set SOUND_PITCH_OFFSET, [hl]
|
|
ld hl, CHANNEL_PITCH_OFFSET + 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_DutyCyclePattern:
|
|
; 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_LOOP, [hl] ; duty cycle looping
|
|
; sound duty sequence
|
|
call GetMusicByte
|
|
rrca
|
|
rrca
|
|
ld hl, CHANNEL_DUTY_CYCLE_PATTERN
|
|
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
|
|
; volume envelope: see Music_VolumeEnvelope
|
|
; 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
|
|
; volume envelope
|
|
call Music_VolumeEnvelope
|
|
ret
|
|
|
|
Music_PitchSweep:
|
|
; update pitch sweep
|
|
; params: 1
|
|
call GetMusicByte
|
|
ld [wPitchSweep], a
|
|
ld hl, CHANNEL_NOTE_FLAGS
|
|
add hl, bc
|
|
set NOTE_PITCH_SWEEP, [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_VolumeEnvelope:
|
|
; volume envelope
|
|
; params: 1
|
|
; hi: volume
|
|
; lo: fade
|
|
call GetMusicByte
|
|
ld hl, CHANNEL_VOLUME_ENVELOPE
|
|
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_Transpose:
|
|
; set starting octave
|
|
; this forces all notes up by the starting octave
|
|
; params: 1
|
|
call GetMusicByte
|
|
ld hl, CHANNEL_TRANSPOSITION
|
|
add hl, bc
|
|
ld [hl], a
|
|
ret
|
|
|
|
Music_StereoPanning:
|
|
; stereo panning
|
|
; params: 1
|
|
; stereo on?
|
|
ld a, [wOptions]
|
|
bit STEREO, a
|
|
jr nz, Music_ForceStereoPanning
|
|
; skip param
|
|
call GetMusicByte
|
|
ret
|
|
|
|
Music_ForceStereoPanning:
|
|
; 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
|
|
ld hl, CHANNEL_MUSIC_ADDRESS
|
|
add hl, bc
|
|
ld a, [hli]
|
|
ld e, a
|
|
ld d, [hl]
|
|
ld hl, CHANNEL_MUSIC_BANK
|
|
add hl, bc
|
|
ld a, [hl]
|
|
call _LoadMusicByte ; load data into [wCurMusicByte]
|
|
inc de ; advance to next byte for next time this is called
|
|
ld hl, CHANNEL_MUSIC_ADDRESS
|
|
add hl, bc
|
|
ld a, e
|
|
ld [hli], a
|
|
ld [hl], d
|
|
pop de
|
|
pop hl
|
|
ld a, [wCurMusicByte]
|
|
ret
|
|
|
|
GetFrequency:
|
|
; generate frequency
|
|
; input:
|
|
; d: octave
|
|
; e: pitch
|
|
; output:
|
|
; de: frequency
|
|
|
|
; get octave
|
|
; get starting octave
|
|
ld hl, CHANNEL_TRANSPOSITION
|
|
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_TRANSPOSITION
|
|
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
|
|
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
|
|
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 ; bc = current channel
|
|
|
|
ld hl, CHANNEL_FLAGS1
|
|
add hl, bc
|
|
set SOUND_CRY, [hl]
|
|
|
|
ld hl, CHANNEL_FLAGS2
|
|
add hl, bc
|
|
set SOUND_PITCH_OFFSET, [hl]
|
|
|
|
ld hl, CHANNEL_PITCH_OFFSET
|
|
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
|
|
|
|
; [CHANNEL_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 [wPitchSweep], a ; pitch sweep off
|
|
ldh [rNR10], a ; pitch sweep off
|
|
.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:
|
|
; input: de = audio pointer
|
|
; sets bc to current channel pointer
|
|
call LoadMusicByte
|
|
inc de
|
|
maskbits NUM_CHANNELS
|
|
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:
|
|
table_width 2, ChannelPointers
|
|
; music channels
|
|
dw wChannel1
|
|
dw wChannel2
|
|
dw wChannel3
|
|
dw wChannel4
|
|
assert_table_length NUM_MUSIC_CHANS
|
|
; sfx channels
|
|
dw wChannel5
|
|
dw wChannel6
|
|
dw wChannel7
|
|
dw wChannel8
|
|
assert_table_length NUM_CHANNELS
|
|
|
|
ClearChannels::
|
|
; runs ClearChannel for all 4 channels
|
|
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
|