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