;читает:
;OperMode (0=выключить звук и не играть)
;EventMusicQueue (номер джингла типа TimeRunningOutMusic, EndOfCastleMusic) - при вызове плейера он это копирует в EventMusicBuffer и зануляет
;AreaMusicQueue (номер музыки) - при вызове плейера он это копирует в AreaMusicBuffer и зануляет
;PauseSoundQueue (1=доигрываем звук паузы и выключаем звук - как туда попадает 1???)
;Square1SoundQueue (jump, flagpole sound и др.)
;Square2SoundQueue (1-up sound, fireworks/gunfire)
; lda Square2SoundQueue
; oran ++Sfx_Blast ;play fireworks/gunfire sound
; sta Square2SoundQueue
;NoiseSoundQueue (brick shatter sound)
; lda NoiseSoundQueue
; oran ++Sfx_BowserFlame ;load bowser's flame sound into queue
; sta NoiseSoundQueue
;возвращает:
;EventMusicBuffer (0=музыкальный эффект кончился)
SoundEngine_noint
;play sound logically (for end of level music)
if 1==0
EventMusicQueue_noint=$+1
ld hl,0
ld a,h
or l
ret z
dec hl
ld (EventMusicQueue_noint),hl
endif
ret
SoundEngine:
lda OperMode ;are we in title screen mode?
checka
bne SndOn
sta SND_MASTERCTRL_REG ;if so, disable sound and leave
rts
SndOn: ldan ++$ff
sta JOYPAD_PORT2 ;disable irqs and set frame counter mode???
ldan ++$0f
sta SND_MASTERCTRL_REG ;enable first four channels
lda PauseModeFlag ;is sound already in pause mode?
checka
bne InPause
lda PauseSoundQueue ;if not, check pause sfx queue
cmpn ++$01
bne RunSoundSubroutines ;if queue is empty, skip pause mode routine
InPause: lda PauseSoundBuffer ;check pause sfx buffer
checka
bne ContPau
lda PauseSoundQueue ;check pause queue
checka
beq SkipSoundSubroutines
sta PauseSoundBuffer ;if queue full, store in buffer and activate
sta PauseModeFlag ;pause mode to interrupt game sounds
ldan ++$00 ;disable sound and clear sfx buffers
sta SND_MASTERCTRL_REG
sta Square1SoundBuffer
sta Square2SoundBuffer
sta NoiseSoundBuffer
ldan ++$0f
sta SND_MASTERCTRL_REG ;enable sound again
ldan ++$2a ;store length of sound in pause counter
sta Squ1_SfxLenCounter
PTone1F: ldan ++$44 ;play first tone
checka
bne PTRegC ;unconditional branch
ContPau: lda Squ1_SfxLenCounter ;check pause length left
cmpn ++$24 ;time to play second?
beq PTone2F
cmpn ++$1e ;time to play first again?
beq PTone1F
cmpn ++$18 ;time to play second again?
bne DecPauC ;only load regs during times, otherwise skip
PTone2F: ldan ++$64 ;store reg contents and play the pause sfx
PTRegC: ldxn ++$84
ldyn ++$7f
jsr PlaySqu1Sfx
DecPauC: deci Squ1_SfxLenCounter ;decrement pause sfx counter
bne SkipSoundSubroutines
ldan ++$00 ;disable sound if in pause mode and
sta SND_MASTERCTRL_REG ;not currently playing the pause sfx
lda PauseSoundBuffer ;if no longer playing pause sfx, check to see
cmpn ++$02 ;if we need to be playing sound again
bne SkipPIn
ldan ++$00 ;clear pause mode to allow game sounds again
sta PauseModeFlag
SkipPIn: ldan ++$00 ;clear pause sfx buffer
sta PauseSoundBuffer
checka
beq SkipSoundSubroutines ;unconditional???
RunSoundSubroutines:
jsr Square1SfxHandler ;play sfx on square channel 1
jsr Square2SfxHandler ; '' '' '' square channel 2
jsr NoiseSfxHandler ; '' '' '' noise channel
jsr MusicHandler ;play music on all channels
ldan ++$00 ;clear the music queues
sta AreaMusicQueue
sta EventMusicQueue
SkipSoundSubroutines:
ldan ++$00 ;clear the sound effects queues
sta Square1SoundQueue
sta Square2SoundQueue
sta NoiseSoundQueue
sta PauseSoundQueue
ldy DAC_Counter ;load some sort of counter
lda AreaMusicBuffer
andn ++%00000011 ;check for specific music
beq NoIncDAC
inci DAC_Counter ;increment and check counter
cpyn ++$30
cmpcy
bcc StrWave ;if not there yet, just store it
NoIncDAC: tya
checka
beq StrWave ;if we are at zero, do not decrement
deci DAC_Counter ;decrement counter
StrWave: sty SND_DELTA_REG+1 ;store into DMC load register (??)
rts ;we are done here
;--------------------------------
Dump_Squ1_Regs:
sty SND_SQUARE1_REG+1 ;dump the contents of X and Y into square 1's control regs
stx SND_SQUARE1_REG
rts
PlaySqu1Sfx:
jsr Dump_Squ1_Regs ;do sub to set ctrl regs for square 1, then set frequency regs
SetFreq_Squ1:
;out: Z=NoTone
ldxn ++$00 ;set frequency reg offset for square 1 sound channel
Dump_Freq_Regs:
;out: Z=NoTone
tay
lday FreqRegLookupTbl+1,y ;use previous contents of A for sound reg offset
checka
beq NoTone ;if zero, then do not load
stax SND_REGISTER+2,x ;first byte goes into LSB of frequency divider
lday FreqRegLookupTbl,y ;second byte goes into 3 MSB plus extra bit for
oran ++%00001000 ;length counter
stax SND_REGISTER+3,x
if Z80
;write to SND_REGISTER+3 causes counter loading from a table
rra
rra
rra
and 0x1f
ld hl,tcounterload
add a,l
ld l,a
adc a,h
sub l
ld h,a
ld a,(hl) ;читает 5, а на слух надо примерно 0x10 для музыки, для флага больше, только эффекты покороче
add a,a
;jr $
stax SND_COUNTER,x
;Only a write out to $4003/$4007/$400F will reset the current envelope decay counter to a known state (to $F, the maximum volume level) for the appropriate channel's envelope decay hardware.
ld a,0x0f
stax SND_DECAYVOL,x
endif
NoTone: rts
Dump_Sq2_Regs:
stx SND_SQUARE2_REG ;dump the contents of X and Y into square 2's control regs
sty SND_SQUARE2_REG+1
rts
PlaySqu2Sfx:
jsr Dump_Sq2_Regs ;do sub to set ctrl regs for square 2, then set frequency regs
SetFreq_Squ2:
;out: Z=NoTone
ldxn ++$04 ;set frequency reg offset for square 2 sound channel
checkx
bne Dump_Freq_Regs ;unconditional branch
SetFreq_Tri:
;out: Z=NoTone
ldxn ++$08 ;set frequency reg offset for triangle sound channel
checkx
bne Dump_Freq_Regs ;unconditional branch
;--------------------------------
SwimStompEnvelopeData:
.db $9f, $9b, $98, $96, $95, $94, $92, $90
.db $90, $9a, $97, $95, $93, $92
PlayFlagpoleSlide:
ldan ++$40 ;store length of flagpole sound
sta Squ1_SfxLenCounter
ldan ++$62 ;load part of reg contents for flagpole sound
jsr SetFreq_Squ1
ldxn ++$99 ;now load the rest
checkx
bne FPS2nd ;unconditional???
PlaySmallJump:
ldan ++$26 ;branch here for small mario jumping sound
checka
bne JumpRegContents ;unconditional???
PlayBigJump:
ldan ++$18 ;branch here for big mario jumping sound
JumpRegContents:
ldxn ++$82 ;note that small and big jump borrow each others' reg contents
ldyn ++$a7 ;anyway, this loads the first part of mario's jumping sound
jsr PlaySqu1Sfx
ldan ++$28 ;store length of sfx for both jumping sounds
sta Squ1_SfxLenCounter ;then continue on here
ContinueSndJump:
lda Squ1_SfxLenCounter ;jumping sounds seem to be composed of three parts
cmpn ++$25 ;check for time to play second part yet
bne N2Prt
ldxn ++$5f ;load second part
ldyn ++$f6
checky
bne DmpJpFPS ;unconditional branch
N2Prt: cmpn ++$20 ;check for third part
bne DecJpFPS
ldxn ++$48 ;load third part
FPS2nd: ldyn ++$bc ;the flagpole slide sound shares part of third part
DmpJpFPS: jsr Dump_Squ1_Regs
bne DecJpFPS ;unconditional branch outta here ;???
PlayFireballThrow:
ldan ++$05
ldyn ++$99 ;load reg contents for fireball throw sound
checky
bne Fthrow ;unconditional branch
PlayBump:
ldan ++$0a ;load length of sfx and reg contents for bump sound
ldyn ++$93
Fthrow: ldxn ++$9e ;the fireball sound shares reg contents with the bump sound
sta Squ1_SfxLenCounter
ldan ++$0c ;load offset for bump sound
jsr PlaySqu1Sfx
ContinueBumpThrow:
lda Squ1_SfxLenCounter ;check for second part of bump sound
cmpn ++$06
bne DecJpFPS
ldan ++$bb ;load second part directly
sta SND_SQUARE1_REG+1
checka
DecJpFPS: bne BranchToDecLength1 ;unconditional branch
Square1SfxHandler:
ldy Square1SoundQueue ;check for sfx in queue
checky
beq CheckSfx1Buffer
sty Square1SoundBuffer ;if found, put in buffer
;checky ;???
bmi PlaySmallJump ;small jump
lsri Square1SoundQueue
bcs PlayBigJump ;big jump
lsri Square1SoundQueue
bcs PlayBump ;bump
lsri Square1SoundQueue
bcs PlaySwimStomp ;swim/stomp
lsri Square1SoundQueue
bcs PlaySmackEnemy ;smack enemy
lsri Square1SoundQueue
bcs PlayPipeDownInj ;pipedown/injury
lsri Square1SoundQueue
bcs PlayFireballThrow ;fireball throw
lsri Square1SoundQueue
bcs PlayFlagpoleSlide ;slide flagpole
CheckSfx1Buffer:
lda Square1SoundBuffer ;check for sfx in buffer
checka
beq ExS1H ;if not found, exit sub
bmi ContinueSndJump ;small mario jump
lsr
bcs ContinueSndJump ;big mario jump
lsr
bcs ContinueBumpThrow ;bump
lsr
bcs ContinueSwimStomp ;swim/stomp
lsr
bcs ContinueSmackEnemy ;smack enemy
lsr
bcs ContinuePipeDownInj ;pipedown/injury
lsr
bcs ContinueBumpThrow ;fireball throw
lsr
bcs DecrementSfx1Length ;slide flagpole
ExS1H: rts
PlaySwimStomp:
ldan ++$0e ;store length of swim/stomp sound
sta Squ1_SfxLenCounter
ldyn ++$9c ;store reg contents for swim/stomp sound
ldxn ++$9e
ldan ++$26
jsr PlaySqu1Sfx
ContinueSwimStomp:
ldy Squ1_SfxLenCounter ;look up reg contents in data section based on
lday SwimStompEnvelopeData-1,y ;length of sound left, used to control sound's
sta SND_SQUARE1_REG ;envelope
cpyn ++$06
bne BranchToDecLength1
ldan ++$9e ;when the length counts down to a certain point, put this
sta SND_SQUARE1_REG+2 ;directly into the LSB of square 1's frequency divider
checka
BranchToDecLength1:
bne DecrementSfx1Length ;unconditional branch (regardless of how we got here)
PlaySmackEnemy:
ldan ++$0e ;store length of smack enemy sound
ldyn ++$cb
ldxn ++$9f
sta Squ1_SfxLenCounter
ldan ++$28 ;store reg contents for smack enemy sound
jsr PlaySqu1Sfx
bne DecrementSfx1Length ;unconditional branch ;??? если выход из PlaySqu1Sfx по NoTone, то перехода не будет!!!
ContinueSmackEnemy:
ldy Squ1_SfxLenCounter ;check about halfway through
cpyn ++$08
bne SmSpc
ldan ++$a0 ;if we're at the about-halfway point, make the second tone
sta SND_SQUARE1_REG+2 ;in the smack enemy sound
ldan ++$9f
checka
bne SmTick ;unconditional???
SmSpc: ldan ++$90 ;this creates spaces in the sound, giving it its distinct noise
SmTick: sta SND_SQUARE1_REG
DecrementSfx1Length:
deci Squ1_SfxLenCounter ;decrement length of sfx
bne ExSfx1
StopSquare1Sfx:
ldxn ++$00 ;if end of sfx reached, clear buffer
stx Square1SoundBuffer;SCRATCHPAD+$f1 ;and stop making the sfx
ldxn ++$0e
stx SND_MASTERCTRL_REG
ldxn ++$0f
stx SND_MASTERCTRL_REG
ExSfx1: rts
PlayPipeDownInj:
ldan ++$2f ;load length of pipedown sound
sta Squ1_SfxLenCounter
ContinuePipeDownInj:
lda Squ1_SfxLenCounter ;some bitwise logic, forces the regs
lsr ;to be written to only during six specific times
bcs NoPDwnL ;during which d3 must be set and d1-0 must be clear
lsr
bcs NoPDwnL
andn ++%00000010
beq NoPDwnL
ldyn ++$91 ;and this is where it actually gets written in
ldxn ++$9a
ldan ++$44
jsr PlaySqu1Sfx
NoPDwnL: jmp DecrementSfx1Length
;--------------------------------
ExtraLifeFreqData:
.db $58, $02, $54, $56, $4e, $44
PowerUpGrabFreqData:
.db $4c, $52, $4c, $48, $3e, $36, $3e, $36, $30
.db $28, $4a, $50, $4a, $64, $3c, $32, $3c, $32
.db $2c, $24, $3a, $64, $3a, $34, $2c, $22, $2c
;residual frequency data
.db $22, $1c, $14
PUp_VGrow_FreqData:
.db $14, $04, $22, $24, $16, $04, $24, $26 ;used by both
.db $18, $04, $26, $28, $1a, $04, $28, $2a
.db $1c, $04, $2a, $2c, $1e, $04, $2c, $2e ;used by vinegrow
.db $20, $04, $2e, $30, $22, $04, $30, $32
PlayCoinGrab:
ldan ++$35 ;load length of coin grab sound
ldxn ++$8d ;and part of reg contents
checkx
bne CGrab_TTickRegL
PlayTimerTick:
ldan ++$06 ;load length of timer tick sound
ldxn ++$98 ;and part of reg contents
CGrab_TTickRegL:
sta Squ2_SfxLenCounter
ldyn ++$7f ;load the rest of reg contents
ldan ++$42 ;of coin grab and timer tick sound
jsr PlaySqu2Sfx
ContinueCGrabTTick:
lda Squ2_SfxLenCounter ;check for time to play second tone yet
cmpn ++$30 ;timer tick sound also executes this, not sure why
bne N2Tone
ldan ++$54 ;if so, load the tone directly into the reg
sta SND_SQUARE2_REG+2
checka
N2Tone: bne DecrementSfx2Length ;unconditional???
PlayBlast:
ldan ++$20 ;load length of fireworks/gunfire sound
sta Squ2_SfxLenCounter
ldyn ++$94 ;load reg contents of fireworks/gunfire sound
ldan ++$5e
checka
bne SBlasJ ;unconditional???
ContinueBlast:
lda Squ2_SfxLenCounter ;check for time to play second part
cmpn ++$18
bne DecrementSfx2Length
ldyn ++$93 ;load second part reg contents then
ldan ++$18
checka
SBlasJ: bne BlstSJp ;unconditional branch to load rest of reg contents
PlayPowerUpGrab:
ldan ++$36 ;load length of power-up grab sound
sta Squ2_SfxLenCounter
ContinuePowerUpGrab:
lda Squ2_SfxLenCounter ;load frequency reg based on length left over
lsr ;divide by 2
bcs DecrementSfx2Length ;alter frequency every other frame
tay
lday PowerUpGrabFreqData-1,y ;use length left over / 2 for frequency offset
ldxn ++$5d ;store reg contents of power-up grab sound
ldyn ++$7f
LoadSqu2Regs:
jsr PlaySqu2Sfx
DecrementSfx2Length:
deci Squ2_SfxLenCounter ;decrement length of sfx
bne ExSfx2
EmptySfx2Buffer:
ldxn ++$00 ;initialize square 2's sound effects buffer
stx Square2SoundBuffer
StopSquare2Sfx:
ldxn ++$0d ;stop playing the sfx
stx SND_MASTERCTRL_REG
ldxn ++$0f
stx SND_MASTERCTRL_REG
ExSfx2: rts
Square2SfxHandler:
lda Square2SoundBuffer ;special handling for the 1-up sound to keep it
andn ++Sfx_ExtraLife ;from being interrupted by other sounds on square 2
bne ContinueExtraLife
ldy Square2SoundQueue ;check for sfx in queue
checky
beq CheckSfx2Buffer
sty Square2SoundBuffer ;if found, put in buffer and check for the following
;checky ;???
bmi PlayBowserFall ;bowser fall
lsri Square2SoundQueue
bcs PlayCoinGrab ;coin grab
lsri Square2SoundQueue
bcs PlayGrowPowerUp ;power-up reveal
lsri Square2SoundQueue
bcs PlayGrowVine ;vine grow
lsri Square2SoundQueue
bcs PlayBlast ;fireworks/gunfire
lsri Square2SoundQueue
bcs PlayTimerTick ;timer tick
lsri Square2SoundQueue
bcs PlayPowerUpGrab ;power-up grab
lsri Square2SoundQueue
bcs PlayExtraLife ;1-up
CheckSfx2Buffer:
lda Square2SoundBuffer ;check for sfx in buffer
checka
beq ExS2H ;if not found, exit sub
bmi ContinueBowserFall ;bowser fall
lsr
bcs Cont_CGrab_TTick ;coin grab
lsr
bcs ContinueGrowItems ;power-up reveal
lsr
bcs ContinueGrowItems ;vine grow
lsr
bcs ContinueBlast ;fireworks/gunfire
lsr
bcs Cont_CGrab_TTick ;timer tick
lsr
bcs ContinuePowerUpGrab ;power-up grab
lsr
bcs ContinueExtraLife ;1-up
ExS2H: rts
Cont_CGrab_TTick:
jmp ContinueCGrabTTick
JumpToDecLength2:
jmp DecrementSfx2Length
PlayBowserFall:
ldan ++$38 ;load length of bowser defeat sound
sta Squ2_SfxLenCounter
ldyn ++$c4 ;load contents of reg for bowser defeat sound
ldan ++$18
checka
BlstSJp: bne PBFRegs ;unconditional???
ContinueBowserFall:
lda Squ2_SfxLenCounter ;check for almost near the end
cmpn ++$08
bne DecrementSfx2Length
ldyn ++$a4 ;if so, load the rest of reg contents for bowser defeat sound
ldan ++$5a
PBFRegs: ldxn ++$9f ;the fireworks/gunfire sound shares part of reg contents here
checkx
EL_LRegs: bne LoadSqu2Regs ;this is an unconditional branch outta here
PlayExtraLife:
ldan ++$30 ;load length of 1-up sound
sta Squ2_SfxLenCounter
ContinueExtraLife:
lda Squ2_SfxLenCounter
ldxn ++$03 ;load new tones only every eight frames
DivLLoop: lsr
bcs JumpToDecLength2 ;if any bits set here, branch to dec the length
dex
bne DivLLoop ;do this until all bits checked, if none set, continue
tay
lday ExtraLifeFreqData-1,y ;load our reg contents
ldxn ++$82
ldyn ++$7f
checky
bne EL_LRegs ;unconditional branch
PlayGrowPowerUp:
ldan ++$10 ;load length of power-up reveal sound
checka
bne GrowItemRegs ;unconditional???
PlayGrowVine:
ldan ++$20 ;load length of vine grow sound
GrowItemRegs:
sta Squ2_SfxLenCounter
ldan ++$7f ;load contents of reg for both sounds directly
sta SND_SQUARE2_REG+1
ldan ++$00 ;start secondary counter for both sounds
sta Sfx_SecondaryCounter
ContinueGrowItems:
inci Sfx_SecondaryCounter ;increment secondary counter for both sounds
lda Sfx_SecondaryCounter ;this sound doesn't decrement the usual counter
lsr ;divide by 2 to get the offset
tay
cpyi Squ2_SfxLenCounter ;have we reached the end yet?
beq StopGrowItems ;if so, branch to jump, and stop playing sounds
ldan ++$9d ;load contents of other reg directly
sta SND_SQUARE2_REG
lday PUp_VGrow_FreqData,y ;use secondary counter / 2 as offset for frequency regs
jsr SetFreq_Squ2
rts
StopGrowItems:
jmp EmptySfx2Buffer ;branch to stop playing sounds
;--------------------------------
BrickShatterFreqData:
.db $01, $0e, $0e, $0d, $0b, $06, $0c, $0f
.db $0a, $09, $03, $0d, $08, $0d, $06, $0c
PlayBrickShatter:
ldan ++$20 ;load length of brick shatter sound
sta Noise_SfxLenCounter
ContinueBrickShatter:
lda Noise_SfxLenCounter
lsr ;divide by 2 and check for bit set to use offset
bcc DecrementSfx3Length
tay
ldxy BrickShatterFreqData,y ;load reg contents of brick shatter sound
lday BrickShatterEnvData,y
PlayNoiseSfx:
sta SND_NOISE_REG ;play the sfx
stx SND_NOISE_REG+2
ldan ++$18
sta SND_NOISE_REG+3
if Z80
call wrnoise3
endif
DecrementSfx3Length:
deci Noise_SfxLenCounter ;decrement length of sfx
bne ExSfx3
ldan ++$f0 ;if done, stop playing the sfx
sta SND_NOISE_REG
ldan ++$00
sta NoiseSoundBuffer
ExSfx3: rts
NoiseSfxHandler:
ldy NoiseSoundQueue ;check for sfx in queue
checky
beq CheckNoiseBuffer
sty NoiseSoundBuffer ;if found, put in buffer
lsri NoiseSoundQueue
bcs PlayBrickShatter ;brick shatter
lsri NoiseSoundQueue
bcs PlayBowserFlame ;bowser flame
CheckNoiseBuffer:
lda NoiseSoundBuffer ;check for sfx in buffer
checka
beq ExNH ;if not found, exit sub
lsr
bcs ContinueBrickShatter ;brick shatter
lsr
bcs ContinueBowserFlame ;bowser flame
ExNH: rts
PlayBowserFlame:
ldan ++$40 ;load length of bowser flame sound
sta Noise_SfxLenCounter
ContinueBowserFlame:
lda Noise_SfxLenCounter
lsr
tay
ldxn ++$0f ;load reg contents of bowser flame sound
lday BowserFlameEnvData-1,y
checka
bne PlayNoiseSfx ;unconditional branch here
;--------------------------------
ContinueMusic:
jmp HandleSquare2Music ;if we have music, start with square 2 channel
MusicHandler:
lda EventMusicQueue ;check event music queue
checka
bne LoadEventMusic
lda AreaMusicQueue ;check area music queue
checka
bne LoadAreaMusic
lda EventMusicBuffer ;check both buffers
orai AreaMusicBuffer
bne ContinueMusic
rts ;no music, then leave
LoadEventMusic:
if 1==0
cp EndOfLevelMusic
jr nz,noendlevelmusicpatch
ld hl,0xffff
ld (EventMusicQueue_noint),hl
noendlevelmusicpatch
endif
sta EventMusicBuffer ;copy event music queue contents to buffer
cmpn ++DeathMusic ;is it death music?
bne NoStopSfx ;if not, jump elsewhere
jsr StopSquare1Sfx ;stop sfx in square 1 and 2
jsr StopSquare2Sfx ;but clear only square 1's sfx buffer
NoStopSfx: ldx AreaMusicBuffer
stx AreaMusicBuffer_Alt ;save current area music buffer to be re-obtained later
ldyn ++$00
sty NoteLengthTblAdder ;default value for additional length byte offset
sty AreaMusicBuffer ;clear area music buffer
cmpn ++TimeRunningOutMusic ;is it time running out music?
bne FindEventMusicHeader
ldxn ++$08 ;load offset to be added to length byte of header
stx NoteLengthTblAdder
checkx
bne FindEventMusicHeader ;unconditional branch
LoadAreaMusic:
cmpn ++$04 ;is it underground music?
bne NoStop1 ;no, do not stop square 1 sfx
jsr StopSquare1Sfx
NoStop1: ldyn ++$10 ;start counter used only by ground level music
GMLoopB: sty GroundMusicHeaderOfs
HandleAreaMusicLoopB:
ldyn ++$00 ;clear event music buffer
sty EventMusicBuffer
sta AreaMusicBuffer ;copy area music queue contents to buffer
cmpn ++$01 ;is it ground level music?
bne FindAreaMusicHeader
inci GroundMusicHeaderOfs ;increment but only if playing ground level music
ldy GroundMusicHeaderOfs ;is it time to loopback ground level music?
cpyn ++$32
bne LoadHeader ;branch ahead with alternate offset
ldyn ++$11
checky
bne GMLoopB ;unconditional branch
FindAreaMusicHeader:
ldyn ++$08 ;load Y for offset of area music
sty MusicOffset_Square2 ;residual instruction here
FindEventMusicHeader:
iny ;increment Y pointer based on previously loaded queue contents
lsr ;bit shift and increment until we find a set bit for music
bcc FindEventMusicHeader
LoadHeader:
lday MusicHeaderOffsetData,y ;load offset for header
tay
lday MusicHeaderData,y ;now load the header
sta NoteLenLookupTblOfs
lday MusicHeaderData+1,y
sta MusicDataLow
lday MusicHeaderData+2,y
sta MusicDataHigh
lday MusicHeaderData+3,y
sta MusicOffset_Triangle
lday MusicHeaderData+4,y
sta MusicOffset_Square1
lday MusicHeaderData+5,y
sta MusicOffset_Noise
sta NoiseDataLoopbackOfs
ldan ++$01 ;initialize music note counters
sta Squ2_NoteLenCounter
sta Squ1_NoteLenCounter
sta Tri_NoteLenCounter
sta Noise_BeatLenCounter
ldan ++$00 ;initialize music data offset for square 2
sta MusicOffset_Square2
sta AltRegContentFlag ;initialize alternate control reg data used by square 1
ldan ++$0b ;disable triangle channel and reenable it
sta SND_MASTERCTRL_REG
ldan ++$0f
sta SND_MASTERCTRL_REG
HandleSquare2Music:
deci Squ2_NoteLenCounter ;decrement square 2 note length
bne MiscSqu2MusicTasks ;is it time for more data? if not, branch to end tasks
ldy MusicOffset_Square2 ;increment square 2 music offset and fetch data
inci MusicOffset_Square2
ldayindirect (MusicData),y
checka
beq EndOfMusicData ;if zero, the data is a null terminator
bpl Squ2NoteHandler ;if non-negative, data is a note
bne Squ2LengthHandler ;otherwise it is length data
EndOfMusicData:
lda EventMusicBuffer ;check secondary buffer for time running out music
cmpn ++TimeRunningOutMusic
bne NotTRO
lda AreaMusicBuffer_Alt ;load previously saved contents of primary buffer
checka
bne MusicLoopBack ;and start playing the song again if there is one
NotTRO: andn ++VictoryMusic ;check for victory music (the only secondary that loops)
bne VictoryMLoopBack
lda AreaMusicBuffer ;check primary buffer for any music except pipe intro
andn ++%01011111
bne MusicLoopBack ;if any area music except pipe intro, music loops
ldan ++$00 ;clear primary and secondary buffers and initialize
sta AreaMusicBuffer ;control regs of square and triangle channels
sta EventMusicBuffer
sta SND_TRIANGLE_REG
ldan ++$90
sta SND_SQUARE1_REG
sta SND_SQUARE2_REG
rts
MusicLoopBack:
jmp HandleAreaMusicLoopB
VictoryMLoopBack:
jmp LoadEventMusic
Squ2LengthHandler:
jsr ProcessLengthData ;store length of note
sta Squ2_NoteLenBuffer
ldy MusicOffset_Square2 ;fetch another byte (MUST NOT BE LENGTH BYTE!)
inci MusicOffset_Square2
ldayindirect (MusicData),y
Squ2NoteHandler:
ldx Square2SoundBuffer ;is there a sound playing on this channel?
checkx
bne SkipFqL1
jsr SetFreq_Squ2 ;no, then play the note ;out: Z=NoTone
beq Rest ;check to see if note is rest
jsr LoadControlRegs ;if not, load control regs for square 2
Rest: sta Squ2_EnvelopeDataCtrl ;save contents of A
jsr Dump_Sq2_Regs ;dump X and Y into square 2 control regs
SkipFqL1: lda Squ2_NoteLenBuffer ;save length in square 2 note counter
sta Squ2_NoteLenCounter
MiscSqu2MusicTasks:
lda Square2SoundBuffer ;is there a sound playing on square 2?
checka
bne HandleSquare1Music
lda EventMusicBuffer ;check for death music or d4 set on secondary buffer
andn ++%10010001 ;note that regs for death music or d4 are loaded by default
bne HandleSquare1Music
ldy Squ2_EnvelopeDataCtrl ;check for contents saved from LoadControlRegs
checky
beq NoDecEnv1
deci Squ2_EnvelopeDataCtrl ;decrement unless already zero
NoDecEnv1: jsr LoadEnvelopeData ;do a load of envelope data to replace default
sta SND_SQUARE2_REG ;based on offset set by first load unless playing
ldxn ++$7f ;death music or d4 set on secondary buffer
stx SND_SQUARE2_REG+1
HandleSquare1Music:
ldy MusicOffset_Square1 ;is there a nonzero offset here?
checky
beq HandleTriangleMusic ;if not, skip ahead to the triangle channel
deci Squ1_NoteLenCounter ;decrement square 1 note length
bne MiscSqu1MusicTasks ;is it time for more data?
FetchSqu1MusicData:
ldy MusicOffset_Square1 ;increment square 1 music offset and fetch data
inci MusicOffset_Square1
ldayindirect (MusicData),y
checka
bne Squ1NoteHandler ;if nonzero, then skip this part
ldan ++$83
sta SND_SQUARE1_REG ;store some data into control regs for square 1
ldan ++$94 ;and fetch another byte of data, used to give
sta SND_SQUARE1_REG+1 ;death music its unique sound
sta AltRegContentFlag
checka
bne FetchSqu1MusicData ;unconditional branch
Squ1NoteHandler:
jsr AlternateLengthHandler
sta Squ1_NoteLenCounter ;save contents of A in square 1 note counter
ldy Square1SoundBuffer ;is there a sound playing on square 1?
checky
bne HandleTriangleMusic
txa
andn ++%00111110 ;change saved data to appropriate note format
jsr SetFreq_Squ1 ;play the note ;out: Z=NoTone
beq SkipCtrlL
jsr LoadControlRegs
SkipCtrlL: sta Squ1_EnvelopeDataCtrl ;save envelope offset
jsr Dump_Squ1_Regs
MiscSqu1MusicTasks:
lda Square1SoundBuffer ;is there a sound playing on square 1?
checka
bne HandleTriangleMusic
lda EventMusicBuffer ;check for death music or d4 set on secondary buffer
andn ++%10010001
bne DeathMAltReg
ldy Squ1_EnvelopeDataCtrl ;check saved envelope offset
checky
beq NoDecEnv2
deci Squ1_EnvelopeDataCtrl ;decrement unless already zero
NoDecEnv2: jsr LoadEnvelopeData ;do a load of envelope data
sta SND_SQUARE1_REG ;based on offset set by first load
DeathMAltReg: lda AltRegContentFlag ;check for alternate control reg data
checka
bne DoAltLoad
ldan ++$7f ;load this value if zero, the alternate value
DoAltLoad: sta SND_SQUARE1_REG+1 ;if nonzero, and let's move on
HandleTriangleMusic:
lda MusicOffset_Triangle
deci Tri_NoteLenCounter ;decrement triangle note length
bne HandleNoiseMusic ;is it time for more data?
ldy MusicOffset_Triangle ;increment square 1 music offset and fetch data
inci MusicOffset_Triangle
ldayindirect (MusicData),y
checka
beq LoadTriCtrlReg ;if zero, skip all this and move on to noise
bpl TriNoteHandler ;if non-negative, data is note
jsr ProcessLengthData ;otherwise, it is length data
sta Tri_NoteLenBuffer ;save contents of A
ldan ++$1f
sta SND_TRIANGLE_REG ;load some default data for triangle control reg
ldy MusicOffset_Triangle ;fetch another byte
inci MusicOffset_Triangle
ldayindirect (MusicData),y
checka
beq LoadTriCtrlReg ;check once more for nonzero data
TriNoteHandler:
jsr SetFreq_Tri
ldx Tri_NoteLenBuffer ;save length in triangle note counter
stx Tri_NoteLenCounter
lda EventMusicBuffer
andn ++%01101110 ;check for death music or d4 set on secondary buffer
bne NotDOrD4 ;if playing any other secondary, skip primary buffer check
lda AreaMusicBuffer ;check primary buffer for water or castle level music
andn ++%00001010
beq HandleNoiseMusic ;if playing any other primary, or death or d4, go on to noise routine
NotDOrD4: txa ;if playing water or castle music or any secondary
cmpn ++$12 ;besides death music or d4 set, check length of note
cmpcy
bcs LongN
lda EventMusicBuffer ;check for win castle music again if not playing a long note
andn ++EndOfCastleMusic
beq MediN
ldan ++$0f ;load value $0f if playing the win castle music and playing a short
checka
bne LoadTriCtrlReg ;unconditional??? ;note, load value $1f if playing water or castle level music or any
MediN: ldan ++$1f ;secondary besides death and d4 except win castle or win castle and playing
checka
bne LoadTriCtrlReg ;unconditional??? ;a short note, and load value $ff if playing a long note on water, castle
LongN: ldan ++$ff ;or any secondary (including win castle) except death and d4
LoadTriCtrlReg:
sta SND_TRIANGLE_REG ;save final contents of A into control reg for triangle
HandleNoiseMusic:
lda AreaMusicBuffer ;check if playing underground or castle music
andn ++%11110011
beq ExitMusicHandler ;if so, skip the noise routine
deci Noise_BeatLenCounter ;decrement noise beat length
bne ExitMusicHandler ;is it time for more data?
FetchNoiseBeatData:
ldy MusicOffset_Noise ;increment noise beat offset and fetch data
inci MusicOffset_Noise
ldayindirect (MusicData),y ;get noise beat data, if nonzero, branch to handle
checka
bne NoiseBeatHandler
lda NoiseDataLoopbackOfs ;if data is zero, reload original noise beat offset
sta MusicOffset_Noise ;and loopback next time around
checka
bne FetchNoiseBeatData ;unconditional branch
NoiseBeatHandler:
jsr AlternateLengthHandler
sta Noise_BeatLenCounter ;store length in noise beat counter
txa
andn ++%00111110 ;reload data and erase length bits
beq SilentBeat ;if no beat data, silence
cmpn ++$30 ;check the beat data and play the appropriate
beq LongBeat ;noise accordingly
cmpn ++$20
beq StrongBeat
andn ++%00010000
beq SilentBeat
ldan ++$1c ;short beat data
ldxn ++$03
ldyn ++$18
checky
bne PlayBeat ;unconditional???
StrongBeat:
ldan ++$1c ;strong beat data
ldxn ++$0c
ldyn ++$18
checky
bne PlayBeat ;unconditional???
LongBeat:
ldan ++$1c ;long beat data
ldxn ++$03
ldyn ++$58
checky
bne PlayBeat ;unconditional???
SilentBeat:
ldan ++$10 ;silence
PlayBeat:
sta SND_NOISE_REG ;load beat data into noise regs
stx SND_NOISE_REG+2
sty SND_NOISE_REG+3
if Z80
;write to SND_REGISTER+3 causes counter loading from a table
ld a,e
wrnoise3
rra
rra
rra
and 0x1f
ld hl,tcounterload
add a,l
ld l,a
adc a,h
sub l
ld h,a
ld a,(hl) ;читает 5, а на слух надо примерно 0x10 для музыки, для флага больше, только эффекты покороче
add a,a
ld (SND_COUNTER+12),a
;Only a write out to $4003/$4007/$400F will reset the current envelope decay counter to a known state (to $F, the maximum volume level) for the appropriate channel's envelope decay hardware.
ld a,0x0f
ld (SND_DECAYVOL+12),a
endif
ExitMusicHandler:
rts
AlternateLengthHandler:
tax ;save a copy of original byte into X
ror ;save LSB from original byte into carry
txa ;reload original byte and rotate three times
rol ;turning xx00000x into 00000xxx, with the
rol ;bit in carry as the MSB here
rol
ProcessLengthData:
andn ++%00000111 ;clear all but the three LSBs
clc
adci NoteLenLookupTblOfs;SCRATCHPAD+$f0 ;add offset loaded from first header byte
adci NoteLengthTblAdder ;add extra if time running out music
tay
lday MusicLengthLookupTbl,y ;load length
rts
LoadControlRegs:
lda EventMusicBuffer ;check secondary buffer for win castle music
andn ++EndOfCastleMusic
beq NotECstlM
ldan ++$04 ;this value is only used for win castle music
checka
bne AllMus ;unconditional branch
NotECstlM: lda AreaMusicBuffer
andn ++%01111101 ;check primary buffer for water music
beq WaterMus
ldan ++$08 ;this is the default value for all other music
checka
bne AllMus
WaterMus: ldan ++$28 ;this value is used for water music and all other event music
AllMus: ldxn ++$82 ;load contents of other sound regs for square 2
ldyn ++$7f
rts
LoadEnvelopeData:
lda EventMusicBuffer ;check secondary buffer for win castle music
andn ++EndOfCastleMusic
beq LoadUsualEnvData
lday EndOfCastleMusicEnvData,y ;load data from offset for win castle music
rts
LoadUsualEnvData:
lda AreaMusicBuffer ;check primary buffer for water music
andn ++%01111101
beq LoadWaterEventMusEnvData
lday AreaMusicEnvData,y ;load default data from offset for all other music
rts
LoadWaterEventMusEnvData:
lday WaterEventMusEnvData,y ;load data from offset for water music and all other event music
rts