; 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 ; unused dw Music_RestartChannel dw Music_NewSong ; unused 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