;**************************************************************************** ; NoteOnFM* [events $00~$07] ; Does a "note on" for a FM channel ;---------------------------------------------------------------------------- ; input a .... FM channel (bottom 3 bits) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** NoteOnFMSFX: call NoteOnFM ; We're just a wrapper jp ProcessSFXRun ; End of subroutine NoteOnFMBGM: ld b, a PollPCM push hl ld a, b and $07 ; Check if channel is free ld hl, RAM_Locked add l ld l, a ld a, (hl) pop hl or a jp nz, ProcessBGMSkip1 ; Don't play if locked ld a, b call NoteOnFM ; We're just a wrapper jp ProcessBGMRun ; End of subroutine NoteOnFM: and $07 ; Get channel ID ld (ix+0), $28 ; Note off ld (ix+1), a ld b, a ex af, af' PollPCM ld a, b ; Determine which port to write and $04 rrca ld iyl, a PollPCM call GetParam ; Get note PollPCM ex af, af' SetFMSemitone: push de push hl ld d, a and $03 ; Index of first frequency register add $A4 ld e, a PollPCM ld h, FMFreqTable>>8 ; Get address of note ld a, b and $1F add FMFreqTable&$FF ld l, a PollPCM ld a, b ; Set new frequency and $E0 rrca rrca ld b, a ld a, (hl) or b ld (iy+0), e ld (iy+1), a PollPCM ld a, e sub 4 ld e, a dec l ld a, (hl) ld (iy+0), e ld (iy+1), a PollPCM ld a, d ; Note on! or $F0 ld (ix+0), $28 ld (ix+1), a pop hl pop de ret ; End of subroutine ;**************************************************************************** ; NoteOffFM [events $10~$17] ; Does a "note off" for a FM channel ;---------------------------------------------------------------------------- ; input a .... FM channel (bottom 3 bits) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** NoteOffFMSFX: call NoteOffFM ; We're just a wrapper jp ProcessSFXRun ; End of subroutine NoteOffFMBGM: ld b, a PollPCM ld a, b push hl and $07 ; Check if channel is free ld hl, RAM_Locked add l ld l, a ld a, (hl) pop hl or a jp nz, ProcessBGMSkip ; Don't stop if locked ld a, b call NoteOffFM ; We're just a wrapper jp ProcessBGMRun ; End of subroutine NoteOffFM: and $07 ; Get channel ID ld (ix+0), $28 ; Note off ld (ix+1), a ret ; End of subroutine ;**************************************************************************** ; SetFMNote* [events $30~$37] ; Sets the note of a FM channel without "note on" ;---------------------------------------------------------------------------- ; input a .... FM channel (bottom 3 bits) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** SetNoteFMSFX: call SetNoteFM ; We're just a wrapper jp ProcessSFXRun ; End of subroutine SetNoteFMBGM: ld b, a PollPCM ld a, b push hl and $07 ; Check if channel is free ld hl, RAM_Locked add l ld l, a ld a, (hl) pop hl or a jp nz, ProcessBGMSkip2 ; Don't change if locked ld a, b call SetNoteFM ; We're just a wrapper jp ProcessBGMRun ; End of subroutine SetNoteFM: ld b, a ex af, af' PollPCM ld a, b ; Determine which port to write and $04 rrca ld iyl, a PollPCM call GetParam ; Get high byte PollPCM ld a, b ; Is it a semitone? add a, a jp c, .freqtone ex af, af' push de and $07 ld d, a and $03 ; Index of first frequency register add $A4 ld e, a PollPCM ld (iy+0), e ; Load high byte ld (iy+1), b PollPCM ; Load low byte call GetParam PollPCM ld a, e sub 4 ld e, a ld (iy+0), e ld (iy+1), b PollPCM pop de ret ; End of subroutine ;---------------------------------------------------------------------------- .freqtone: inc a ld b, a ex af, af' jp SetFMSemitone ;**************************************************************************** ; LoadFM* [events $40~$47] ; Loads a FM instrument ;---------------------------------------------------------------------------- ; input a .... FM channel (bottom 3 bits) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** LoadFMSFX: call LoadFMEvent ; We're just a wrapper jp ProcessSFXRun ; End of subroutine LoadFMBGM: and $07 ; Get channel ID ld b, a PollPCM push de push bc ld a, b ld de, RAM_BGMFMInstr ; Store instrument ID add e ld e, a PollPCM call GetParam PollPCM ld a, b ld (de), a ld a, e ; Reset volume add 8 ld e, a xor a ld (de), a ld e, c pop bc ld c, e pop de PollPCM push hl ; Check if channel is free ld a, b ld h, RAM_Locked>>8 add RAM_Locked&$FF ld l, a ld a, (hl) pop hl or a jp nz, ProcessBGMSkip ; Don't load if locked PollPCM ld a, b ; Get back instrument ID push hl ld hl, RAM_BGMFMInstr add l ld l, a ld b, (hl) pop hl ex af, af' PollPCM ex af, af' call LoadFMDirect ; We're just a wrapper jp ProcessBGMRun ; End of subroutine LoadFMEvent: and $07 ; Get channel ID ex af, af' PollPCM call GetParam ; Get instrument ID PollPCM ex af, af' LoadFMDirect: push af and $04 ; Determine which port to write rrca ld iyl, a PollPCM pop af push bc push de push hl ld h, RAM_PointerList>>8 ; Get instrument address ld l, b ld d, (hl) inc h ld e, (hl) inc h ld c, (hl) ex de, hl push af PollPCM ld b, 28/4 ; Load FM instrument into our scratch buffer, ld de, RAM_Scratch ; so we don't conflict with PCM bank .getinstr: ; switch (which would slow down A LOT) ld a, b ex af, af' call GetParam ex de, hl ld (hl), b ex de, hl inc e call GetParam ex de, hl ld (hl), b ex de, hl inc e call GetParam ex de, hl ld (hl), b ex de, hl inc e call GetParam ex de, hl ld (hl), b ex de, hl inc e ex af, af' ld b, a djnz .getinstr call GetParam ex de, hl ld (hl), b ex de, hl PollPCM pop af ; Kill ADSR ld b, a call KillFM ld a, b ld de, RAM_FMData ; Get address of FM data and $07 add e ld e, a push af and $03 ; Register index for operator add $B0 ld hl, RAM_Scratch ; Get address of buffer with the instrument ; data ex af, af' PollPCM ; Write operator ex af, af' ld (iy+0), a ld b, (hl) inc l ld (iy+1), b ex af, af' ; Store $B0 register in FM data buffer ld a, b ld (de), a ld a, e add 8 ld e, a PollPCM ex af, af' sub $B0-$30 ld b, 7*4 ; Load operator registers into the YM2612 .loadfmloop: ld (iy+0), a ld c, (hl) ld (iy+1), c add 4 inc l djnz .loadfmloop PollPCM ld a, l ; Store operator TLs (needed to set the sub 24 ; volume properly when it changes) ld l, a ld b, 4 .loadfmsavetl: ld a, (hl) ld (de), a ld a, e add 8 ld e, a inc l djnz .loadfmsavetl PollPCM pop af pop hl pop de pop bc ld b, 0 jp SetFMVolLoad ;**************************************************************************** ; SetFMVol* ; Sets the volume of a FM channel ;**************************************************************************** SetFMVolSFX: push af PollPCM call GetParam ; Get new volume PollPCM pop af call SetFMVolLoad ; We're just a wrapper jp ProcessSFXRun ; End of subroutine SetFMVolBGM: and $07 ld b, a PollPCM push de push bc ld a, b ld d, RAM_BGMFMVol>>8 ; Store BGM volume add RAM_BGMFMVol&$FF ld e, a PollPCM call GetParam PollPCM ex de, hl ld (hl), b ex de, hl ld e, c pop bc ld c, e pop de PollPCM push hl ld a, b ; Check if channel is free ld hl, RAM_Locked add l ld l, a ld a, (hl) pop hl or a jp nz, ProcessBGMSkip ; Don't change if locked PollPCM ld a, b push hl ld hl, RAM_BGMFMVol add l ld l, a ld b, (hl) pop hl push af PollPCM pop af call SetFMVolLoad ; We're just a wrapper jp ProcessBGMRun ; End of subroutine ;---------------------------------------------------------------------------- ; input a = channel ID ; input b = new volume level ;---------------------------------------------------------------------------- SetFMVolTempLoad: push bc push de push hl jp SetFMVolDoIt ;---------------------------------------------------------------------------- ; input a = channel ID (bottom 3 bits) ; input b = new volume level ;---------------------------------------------------------------------------- SetFMVolLoad: push bc push de push hl and $07 ; Get channel ID push af ; Store new FM volume ld h, RAM_FMVol>>8 add RAM_FMVol&$FF ld l, a ld (hl), b pop af SetFMVolDoIt: push af ld h, RAM_FMData>>8 ; Get address of FM data add RAM_FMData&$FF ld l, a ex af, af' PollPCM ex af, af' and $07 ; Get global volume or $E0 ld d, $1F ld e, a ex af, af' ld a, (de) ld d, a PollPCM ex af, af' and $04 ; Determine which port to write rrca ld iyl, a PollPCM pop af and $03 ; Index of first volume register add $40 ld c, a ld a, (hl) ; Get algorithm and $07 ld e, a PollPCM ld a, l add 8 ld l, a ld a, e ; Process operator #1 cp $07 jr c, .noop1 ld (iy+0), c ld a, (hl) add d jp m, .tooquiet1 add b jp p, .notooquiet1 .tooquiet1: ld a, $7F .notooquiet1: ld (iy+1), a .noop1: ld a, c add 4 ld c, a PollPCM ld a, l add 8 ld l, a ld a, e ; Process operator #2 cp $05 jr c, .noop2 ld (iy+0), c ld a, (hl) add d jp m, .tooquiet2 add b jp p, .notooquiet2 .tooquiet2: ld a, $7F .notooquiet2: ld (iy+1), a .noop2: ld a, c add 4 ld c, a PollPCM ld a, l add 8 ld l, a ld a, e ; Process operator #3 cp $04 jr c, .noop3 ld (iy+0), c ld a, (hl) add d jp m, .tooquiet3 add b jp p, .notooquiet3 .tooquiet3: ld a, $7F .notooquiet3: ld (iy+1), a .noop3: ld a, c add 4 ld c, a PollPCM ld a, l add 8 ld l, a ld (iy+0), c ld a, (hl) ; Process operator #4 add d jp m, .tooquiet4 add b jp p, .notooquiet4 .tooquiet4: ld a, $7F .notooquiet4: ld (iy+1), a PollPCM pop hl pop de pop bc ret ; End of subroutine ;**************************************************************************** ; SetFMParam* [events $F0-$F7] ; Sets the different parameters of a FM channel ;---------------------------------------------------------------------------- ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; output b .... value ; output c .... new bank ; output hl ... new address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** SetFMParamSFX: ld b, a PollPCM ld a, b ; Determine which port to write and $04 rrca ld iyl, a ld a, b ex af, af' PollPCM call GetParam ; Get parameters PollPCM ld a, (RAM_Mono) ; Is stereo disabled? (always turn on both or a ; speakers in that case) jr z, .sfxpanok ld a, b or $C0 ld b, a PollPCM .sfxpanok: ex af, af' and $03 ; Get channel ID add $B4 ld (iy+0), a ; Set new parameters ld (iy+1), b jp ProcessSFXRun ; End of subroutine SetFMParamBGM: ld b, a PollPCM ld a, b ; Determine which port to write and $04 rrca ld iyl, a ld a, b ex af, af' PollPCM call GetParam ; Get parameters PollPCM ld a, (RAM_Mono) ; Is stereo disabled? (always turn on both or a ; speakers in that case) jr z, .bgmpanok ld a, b or $C0 ld b, a PollPCM .bgmpanok: ex af, af' push hl and $07 ; Store parameters ld h, RAM_BGMFMPan>>8 add RAM_BGMFMPan&$FF ld l, a ld (hl), b ex af, af' PollPCM ex af, af' and $07 ; Check if channel is free add RAM_Locked&$FF ld l, a ex af, af' ld a, (hl) or a pop hl jp nz, ProcessBGMRun ; Don't modify if locked PollPCM ex af, af' and $03 ; Get channel ID add $B4 ld (iy+0), a ; Set new parameters ld (iy+1), b jp ProcessBGMRun ; End of subroutine ;**************************************************************************** ; SetFMReg* [events $F8-$F9] ; Changes a FM register ;---------------------------------------------------------------------------- ; input a .... YM2612 register bank (0 or 1, in bit 0) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; output b .... value ; output c .... new bank ; output hl ... new address ;---------------------------------------------------------------------------- ; breaks: af, b ;**************************************************************************** SetFMRegSFX: call SetFMReg ; We're just a wrapper jp ProcessSFXRun SetFMRegBGM: call SetFMReg ; We're just a wrapper jp ProcessBGMRun ;---------------------------------------------------------------------------- SetFMReg: and $01 ; Get port address add a ld iyl, a PollPCM ; Get parameters call GetParam ld a, b cp $27 jr z, .setch3 push bc PollPCM call GetParam PollPCM ld a, b pop bc ld (iy+0), b ; Write register ld (iy+1), a ret ; End of subroutine ;---------------------------------------------------------------------------- .setch3: ; Register $27 is the timer triggers, but also holds ch3 mode ; It's handled separately in order to only modify the ch3 mode bits ; and modify the register writes to keep them PollPCM ; Get new ch3 mode call GetParam PollPCM ld a, b ; Set ch3 mode now, without touching and $C0 ; either timer or $0F ld b, a ld a, $27 ld (iy+0), a ld (iy+0), b PollPCM ld a, b ; Update timer writes (so they don't undo and $C0 ; the ch3 mode change) ld a, (UpdatePCM_Tick-1) ; (timer A) and $3F or a, b ld (UpdatePCM_Tick-1), a ld a, (DoTick_Tick-1) ; (timer B) and $3F or a, b ld (DoTick_Tick-1), a PollPCM ret ; End of subroutine ;**************************************************************************** ; LockChannelFM [events $E0-$E7] ; Locks a FM channel ;---------------------------------------------------------------------------- ; input a ... FM channel (0..7, in bottom 3 bits) ; input c .... current bank ; input hl ... current address ;---------------------------------------------------------------------------- ; output b .... value ; output c .... new bank ; output hl ... new address ;---------------------------------------------------------------------------- ; breaks: af, iy ;**************************************************************************** LockChannelFM: and $07 ld b, a PollPCM push hl ld h, RAM_Locked>>8 ; Get address of channel to lock ld a, b add RAM_Locked&$FF ld l, a ld (hl), $01 ; Lock channel pop hl PollPCM ld a, b ; Determine which port to write and $04 rrca ld iyl, a ld a, b ; Reset FM parameters and $03 add a add a add $B4 ld (iy+0), a ld (iy+1), $C0 jp ProcessSFXRun ; End of subroutine ;**************************************************************************** ; KillFM ; Kills a FM channel ;---------------------------------------------------------------------------- ; input a ... FM channel (0..7, in bottom 3 bits) ;---------------------------------------------------------------------------- ; breaks: af, c, iy ;**************************************************************************** KillFM: and $07 ; Get channel ID push af push de push hl ld c, a ; Determine to which port to write and $04 rrca ld iyl, a ld a, c and $03 ; Load dummy FM instrument add $40 ld c, 6 ld hl, DummyFMInstr .loaddummy: ex af, af' PollPCM ex af, af' ld e, (hl) ld (iy+0), a ld (iy+1), e add 4 ex af, af' PollPCM ex af, af' ld (iy+0), a ld (iy+1), e add 4 ex af, af' PollPCM ex af, af' ld (iy+0), a ld (iy+1), e add 4 ex af, af' PollPCM ex af, af' ld (iy+0), a ld (iy+1), e add 4 inc l dec c jp nz, .loaddummy ld e, 0 ; Make the pitch so low it's unherable add 4 ; because the above is STILL not enough ld (iy+0), a ; sometimes. If you ever notice a very low ld (iy+1), e ; pitch waveform... this is why sub 4 ld (iy+0), a ld (iy+1), e pop hl pop de PollPCM pop af ld c, a ; Cause the ADSR to reset or $F0 ld (ix+0), $28 ld (ix+1), c ld (ix+0), $28 ld (ix+1), a ld (ix+0), $28 ld (ix+1), c PollPCM ret ; End of subroutine ;**************************************************************************** ; RestoreFM ; Restores a FM channel (from BGM information) ;---------------------------------------------------------------------------- ; input b ... FM channel (0..7) ;---------------------------------------------------------------------------- ; breaks: af, hl ;**************************************************************************** RestoreFM: ld a, b ; Restore BGM FM instrument ld h, RAM_BGMFMInstr>>8 add RAM_BGMFMInstr&$FF ld l, a push bc ld a, b ld b, (hl) call LoadFMDirect pop bc PollPCM push bc ld a, l ; Restore BGM FM volume add 8 ld l, a ld a, b ld b, (hl) call SetFMVolLoad pop bc ld a, l ; Restore BGM FM panning add 8 ld l, a ld a, b and $03 add $B4 ld (iy+0), a ld a, (hl) ld (iy+1), a PollPCM ret ; End of subroutine ;**************************************************************************** ; SetStereo [command $09] ; Toggles whether stereo is enabled or not. ;---------------------------------------------------------------------------- ; notes: doesn't return ;**************************************************************************** SetStereo: ld a, (RAM_ComBank) ; Just copy the argument as-is ld (RAM_Mono), a jp EndOfCommand ; End of subroutine