Mostly full implementations of the pulse channels

This commit is contained in:
Lucas Scharenbroich 2023-06-16 01:07:19 -05:00
parent 60d566e78c
commit 99184396fc
6 changed files with 423 additions and 42 deletions

View File

@ -1,3 +1,4 @@
mx %00
HexToChar dfb '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
; Convert a byte (Acc) into a string and store at (Y)
@ -40,6 +41,18 @@ Addr2ToString xba
jsr ByteToString
rts
; A=Value
; X=Screen offset
DrawByte phx ; Save register value
phy
ldy #ByteBuff+1
jsr ByteToString
ply
plx
lda #ByteBuff
jsr DrawString
rts
; A=Value
; X=Screen offset
DrawWord phx ; Save register value
@ -57,6 +70,7 @@ ClearWord lda #EmptyBuff
rts
EmptyBuff str ' '
ByteBuff str '00'
WordBuff str '0000'
Addr3Buff str '000000' ; str adds leading length byte

View File

@ -197,15 +197,15 @@ EvtLoop
stz nmiCount
; sep #$20
; lda #7
; lda #1
; stal ROMBase+$075f
; stal ROMBase+$0766
; lda #3
; lda #2
; stal ROMBase+$0763
; stal ROMBase+$075c
; lda #3
; lda #2
; stal ROMBase+$0767
; stal ROMBase+$0760
; rep #$30

View File

@ -1 +1,4 @@
SMBProto.SHK=Type(E0),AuxType(8002),VersionCreate(80),MinVersion(87),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
SMBProto2.SHK=Type(E0),AuxType(8002),VersionCreate(80),MinVersion(87),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
APUSim=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)
SuperMarioGS=Type(B3),AuxType(0000),VersionCreate(70),MinVersion(BE),Access(E3),FolderInfo1(000000000000000000000000000000000000),FolderInfo2(000000000000000000000000000000000000)

View File

@ -315,17 +315,143 @@ pulse2_sound_settings = *
triangle_sound_settings = *
dfb $00+triangle_oscillator,default_freq ; frequency low register
dfb $20+triangle_oscillator,default_freq/256 ; frequency high register
dfb $40+triangle_oscillator,128 ; volume register, volume = 0
dfb $40+triangle_oscillator,0 ; volume register, volume = 0
dfb $80+triangle_oscillator,5 ; wavetable pointer register, point to $0500
dfb $c0+triangle_oscillator,0 ; wavetable size register, 256 byte length
dfb $a0+triangle_oscillator,0 ; mode register, set to free run
backup_interrupt_ptr ds 4
;-----------------------------------------------------------------------------------------
; APU internals
;-----------------------------------------------------------------------------------------
mx %11
clock_length mac
lda ]1+{APU_PULSE1_REG1-APU_PULSE1}
bit #PULSE_HALT_FLAG
bne no_count
lda ]1+{APU_PULSE1_LENGTH_COUNTER-APU_PULSE1}
beq no_count
dec
sta ]1+{APU_PULSE1_LENGTH_COUNTER-APU_PULSE1}
no_count <<<
clock_sweep mac
lda ]1+{APU_PULSE1_SWEEP_DIVIDER-APU_PULSE1}
dec
sta ]1+{APU_PULSE1_SWEEP_DIVIDER-APU_PULSE1}
bpl no_sweep
lda #1
sta ]1+{APU_PULSE1_RELOAD_FLAG-APU_PULSE1}
lda ]1+{APU_PULSE1_REG2-APU_PULSE1} ; get the barrel shift argument from the register
bpl no_sweep ; if sweep is not enabled, do nothing
and #$07
beq no_sweep ; shift must be != 0
asl
tax
lda ]1+{APU_PULSE1_REG2-APU_PULSE1} ; put the negate flag in the y register
and #$08
tay
rep #$20
lda ]1+{APU_PULSE1_CURRENT_PERIOD-APU_PULSE1}
cmp #8
bcc no_sweep0 ; current period must be >= 8
lda ]1+{APU_PULSE1_REG3-APU_PULSE1} ; raw period stored in the register
and #$7FF
jmp (bitshift,x) ; shift it by the shifter amount
bitshift da bitshift_0,bitshift_1,bitshift_2,bitshift_3,bitshift_4,bitshift_5,bitshift_6,bitshift_7
bitshift_7 lsr
bitshift_6 lsr
bitshift_5 lsr
bitshift_4 lsr
bitshift_3 lsr
bitshift_2 lsr
bitshift_1 lsr
bitshift_0
cpy #0 ; check if the negate flag was set
beq no_negate
eor #$FFFF ; pulse 1 uses 1's complement
DO ]2
inc
FIN
no_negate clc
adc ]1+{APU_PULSE1_CURRENT_PERIOD-APU_PULSE1}
sta ]1+{APU_PULSE1_TARGET_PERIOD-APU_PULSE1}
stz ]1+{APU_PULSE1_MUTE-APU_PULSE1}
ldy #1
cmp #$800
bcc *+5
sty ]1+{APU_PULSE1_MUTE-APU_PULSE1} ; mute the output is the period is too large
bcs *+5
sta ]1+{APU_PULSE1_CURRENT_PERIOD-APU_PULSE1} ; set the current period if the target is within a valid range
lda ]1+{APU_PULSE1_CURRENT_PERIOD-APU_PULSE1}
cmp #8
bcs *+5
sty ]1+{APU_PULSE1_MUTE-APU_PULSE1} ; mute the output if the period is too small
no_sweep0
sep #$20
no_sweep
lda ]1+{APU_PULSE1_RELOAD_FLAG-APU_PULSE1} ; check if we need to reload the sweep delay
beq no_reload
stz ]1+{APU_PULSE1_RELOAD_FLAG-APU_PULSE1}
lda ]1+{APU_PULSE1_REG2-APU_PULSE1}
lsr
lsr
lsr
lsr
and #7
sta ]1+{APU_PULSE1_SWEEP_DIVIDER-APU_PULSE1}
no_reload <<<
clock_envelope mac
lda ]1+{APU_PULSE1_START_FLAG-APU_PULSE1}
beq no_start
stz ]1+{APU_PULSE1_START_FLAG-APU_PULSE1} ; clear the start flag
lda #15
sta ]1+{APU_PULSE1_ENVELOPE-APU_PULSE1} ; reset the envelope saw wave decay value
lda ]1+{APU_PULSE1_REG1-APU_PULSE1}
and #$0F
sta ]1+{APU_PULSE1_ENVELOPE_DIVIDER-APU_PULSE1} ; reset the divider value
bra envelope_out ; nothing else to do
no_start
lda ]1+{APU_PULSE1_ENVELOPE_DIVIDER-APU_PULSE1} ; clock the divider
dec
sta ]1+{APU_PULSE1_ENVELOPE_DIVIDER-APU_PULSE1}
bpl envelope_out ; as long as divider is >=0, nothing to do
lda ]1+{APU_PULSE1_REG1-APU_PULSE1} ; reset the divider to the volume/envelope value
and #$0F
sta ]1+{APU_PULSE1_ENVELOPE_DIVIDER-APU_PULSE1}
lda ]1+{APU_PULSE1_ENVELOPE-APU_PULSE1}
bne tick_envelope
lda ]1+{APU_PULSE1_REG1-APU_PULSE1} ; if decay level counter is 0, check the loop bit and set counter to 15 if loop bit is set
bit #PULSE_HALT_FLAG
beq envelope_out
lda #16 ; Set to 15
tick_envelope
dec
sta ]1+{APU_PULSE1_ENVELOPE-APU_PULSE1}
envelope_out <<<
;-----------------------------------------------------------------------------------------
; interupt handler
;-----------------------------------------------------------------------------------------
apu_frame_steps equ 5
PULSE_HALT_FLAG equ $20
PULSE_CONST_VOL_FLAG equ $10
interrupt_handler = *
phb
@ -345,6 +471,36 @@ interrupt_handler = *
sep #$30
mx %11
; Update the frame counter. We double-count so that frame counter can be used directly to dispatch to the
; appropriate tick handler
ldx apu_frame_counter
inx
inx
cpx #2*apu_frame_steps ; TODO: This is set by MSB in $4017 (4 or 5). 4 = PAL, 5 = NTSC.
bcc *+4
ldx #0
stx apu_frame_counter
jmp (:frame_counter_proc,x)
:frame_counter_proc da :quarter_frame,:half_frame,:quarter_frame,:no_frame,:half_frame
:half_frame
; clock the length counters
clock_length APU_PULSE1
clock_length APU_PULSE2
; clock the sweep units
clock_sweep APU_PULSE1;0
clock_sweep APU_PULSE2;1
; quarter frame updates run every APU frame (for 4-cycle)
:quarter_frame
; clock the envelopes and triangle linear counter
clock_envelope APU_PULSE1
clock_envelope APU_PULSE2
:no_frame
jsr access_doc_registers
ldal osc_interrupt ; which oscillator generated the interrupt?
@ -358,19 +514,30 @@ interrupt_handler = *
lda #$80+pulse1_oscillator
sta sound_address
lda APU_PULSE1_REG1 ; Get the cycle duty bits
lda APU_PULSE1_REG1 ; Get the cycle duty bits
jsr set_pulse_duty_cycle
lda #$40+pulse1_oscillator
sta sound_address
lda APU_PULSE1_MUTE ; If the sweep muted the channel, no output
beq :no_mute_pulse1
lda #0
bra :set_volume_pulse1
:no_mute_pulse1
lda APU_PULSE1_LENGTH_COUNTER ; If the length counter is zero, no output
beq :set_volume_pulse1
lda APU_PULSE1_REG1
bit #PULSE_CONST_VOL_FLAG ; Check the constant volume bit
bne :set_volume_pulse1
lda APU_PULSE1_ENVELOPE
:set_volume_pulse1
jsr set_pulse_volume
rep #$30
lda APU_PULSE1_REG3
jsr get_pulse_freq ; return freq in 16-bic accumulator
lda APU_PULSE1_CURRENT_PERIOD
jsr get_pulse_freq ; return freq in 16-bit accumulator
sep #$30
ldx #$00+pulse1_oscillator
stx sound_address
sta sound_data
@ -388,14 +555,25 @@ interrupt_handler = *
lda #$40+pulse2_oscillator
sta sound_address
lda APU_PULSE2_MUTE ; If the sweep muted the channel, no output
beq :no_mute_pulse2
lda #0
bra :set_volume_pulse2
:no_mute_pulse2
lda APU_PULSE2_LENGTH_COUNTER ; If the length counter is zero, no output
beq :set_volume_pulse2
lda APU_PULSE2_REG1
bit #PULSE_CONST_VOL_FLAG ; Check the constant volume bit
bne :set_volume_pulse2
lda APU_PULSE2_ENVELOPE
:set_volume_pulse2
jsr set_pulse_volume
rep #$30
lda APU_PULSE2_REG3
lda APU_PULSE2_CURRENT_PERIOD
jsr get_pulse_freq ; return freq in 16-bic accumulator
sep #$30
ldx #$00+pulse2_oscillator
stx sound_address
sta sound_data
@ -405,6 +583,7 @@ interrupt_handler = *
sta sound_data
; Now the triangle wave. This wave needs linear counter support to be silenced
; brl :not_timer
rep #$30
lda APU_TRIANGLE_REG3
@ -463,7 +642,7 @@ set_pulse_volume
; Solving for F_HL = (1 / 0.200807) * 111860.812 / (t + 1)
; = 557056.338 / (t + 1)
;
; if t < 8 this value is out of range and the scillator should be silenced
; if t < 8 this value is out of range and the oscillator should be silenced
;
; otherwise, break apart the ratio
;
@ -471,9 +650,7 @@ set_pulse_volume
;
get_pulse_freq
mx %00
and #$07FF ; Load the timer value (11-bits); freq = 1.79MHz / (16 * (t - 1)) = 111860Hz / (t-1)
cmp #8
bcc :no_sound
and #$7FF ; prevent overflow...
inc
sta divisor
lda #55706
@ -495,13 +672,8 @@ get_pulse_freq
asl
asl
clc
adc dividend ; multiple by 10 to get the approx DOC value (0.2Hz per + post-multiple)
adc dividend ; multiple by 10 to get the DOC value
asl
; sta dividend
rts
:no_sound
lda #0
rts
turn_off_interrupts
@ -514,6 +686,33 @@ turn_off_interrupts
plp
rts
; Internal APU registers.
;
; These variables track the internal flags, counters and other status bits that make up
; the core functionality of the different channel hardware
apu_frame_counter dw 0 ; frame counter, clocked at 240Hz from the interrupt handler
; Internal APU register offsets
;length_counter_halt equ 0
;length_counter equ 1
;constant_volume_flag equ 2
;volume equ 3
;current_period equ 4
;target_period equ 6
; Internal APU registers for channel 1
;apu_pulse1 ds 8
;apu_pulse2 ds 8
;apu_pulse1_length_counter dfb 0
;apu_pulse1_decay_level dfb 0
;apu_pulse1_current_period dw 0
;apu_pulse2_length_counter dfb 0
;apu_pulse2_decay_level dfb 0
;apu_pulse2_current_period dw 0
duty_cycle_page dfb $01,$02,$03,$04 ; Page of DOC RAM that holds the different duty cycle wavforms
border_color dw 0
dividend dw 0
@ -539,17 +738,43 @@ _SetDOCReg mac
; Pulse Channel 1
APU_PULSE1
APU_PULSE1_REG1 ds 1 ; DDLC NNNN - Duty, loop envelope/disable length counter, constant volume, envelope period/volume
APU_PULSE1_REG1 ds 1 ; DDLC NNNN - Duty, length counter halt, constant volume/evelope, envelope period/volume
APU_PULSE1_REG2 ds 1 ; EPPP NSSS - Sweep unit: enabled, period, negative, shift count
APU_PULSE1_REG3 ds 1 ; LLLL LLLL - Timer Low
APU_PULSE1_REG4 ds 1 ; llll lHHH - Length counter load, timer high (also resets duty and starts envelope)
APU_PULSE1_LENGTH_COUNTER dfb 0 ; internal register for the length counter
APU_PULSE1_RELOAD_FLAG dfb 0 ; internal register to reload the sweep divider value
APU_PULSE1_SWEEP_DIVIDER dfb 0 ; internal register to track the sweep divider value
APU_PULSE1_TARGET_PERIOD dw 0 ; internal register to hold the sweep unit target period
APU_PULSE1_CURRENT_PERIOD dw 0 ; internal register to hold the current period driving the oscillator
APU_PULSE1_MUTE dfb 0
APU_PULSE1_START_FLAG dfb 0
APU_PULSE1_ENVELOPE_DIVIDER dfb 0
APU_PULSE1_ENVELOPE dfb 0
_apu_pulse1_last_period dw 0 ; optimization
APU_PULSE2
APU_PULSE2_REG1 ds 1 ; DDLC NNNN - Duty, loop envelope/disable length counter, constant volume, envelope period/volume
APU_PULSE2_REG1 ds 1 ; DDLC NNNN - Duty, length counter halt, constant volume/evelope, envelope period/volume
APU_PULSE2_REG2 ds 1 ; EPPP NSSS - Sweep unit: enabled, period, negative, shift count
APU_PULSE2_REG3 ds 1 ; LLLL LLLL - Timer Low
APU_PULSE2_REG4 ds 1 ; llll lHHH - Length counter load, timer high (also resets duty and starts envelope)
APU_PULSE2_LENGTH_COUNTER dfb 0 ; internal register for the length counter
APU_PULSE2_RELOAD_FLAG dfb 0 ; internal register to reload the sweep divider value
APU_PULSE2_SWEEP_DIVIDER dfb 0 ; internal register to track the sweep divider value
APU_PULSE2_TARGET_PERIOD dw 0 ; internal register to hold the sweep unit target period
APU_PULSE2_CURRENT_PERIOD dw 0 ; internal register to hold the current period driving the oscillator
APU_PULSE2_MUTE dfb 0
APU_PULSE2_START_FLAG dfb 0
APU_PULSE2_ENVELOPE_DIVIDER dfb 0
APU_PULSE2_ENVELOPE dfb 0
_apu_pulse2_last_period dw 0 ; optimization
APU_TRIANGLE
APU_TRIANGLE_REG1 ds 1 ; DDLC NNNN - Duty, loop envelope/disable length counter, constant volume, envelope period/volume
APU_TRIANGLE_REG2 ds 1 ; EPPP NSSS - Sweep unit: enabled, period, negative, shift count
@ -564,32 +789,103 @@ APU_PULSE1_REG1_WRITE ENT
rtl
APU_PULSE1_REG2_WRITE ENT
php
pha
stal APU_PULSE1_REG2
lda #1
stal APU_PULSE1_RELOAD_FLAG ; mark that this register was written to
pla
plp
rtl
APU_PULSE1_REG3_WRITE ENT
stal APU_PULSE1_CURRENT_PERIOD
stal APU_PULSE1_REG3
rtl
APU_PULSE1_REG4_WRITE ENT
php
phx
pha
stal APU_PULSE1_REG4
and #$07
stal APU_PULSE1_CURRENT_PERIOD+1
; If the APU_STATUS bit is enabled, then load the length counter
ldal APU_STATUS
bit #$01
beq :no_reload
ldal APU_PULSE1_REG4
and #$F8
lsr
lsr
lsr
tax
ldal LengthTable,x
stal APU_PULSE1_LENGTH_COUNTER ; Immediately start the counter
lda #1
stal APU_PULSE1_START_FLAG
:no_reload
pla
plx
plp
rtl
; From https://www.nesdev.org/wiki/APU_Length_Counter
LengthTable
db 10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14
db 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
APU_PULSE2_REG1_WRITE ENT
stal APU_PULSE2_REG1
rtl
APU_PULSE2_REG2_WRITE ENT
php
pha
stal APU_PULSE2_REG2
lda #1
stal APU_PULSE2_RELOAD_FLAG
pla
plp
rtl
APU_PULSE2_REG3_WRITE ENT
stal APU_PULSE2_CURRENT_PERIOD
stal APU_PULSE2_REG3
rtl
APU_PULSE2_REG4_WRITE ENT
php
phx
pha
stal APU_PULSE2_REG4
and #$07
stal APU_PULSE2_CURRENT_PERIOD+1
ldal APU_STATUS
bit #$01
beq :no_reload
ldal APU_PULSE2_REG4
and #$F8
lsr
lsr
lsr
tax
ldal LengthTable,x
stal APU_PULSE2_LENGTH_COUNTER ; Immediately start the counter
lda #1
stal APU_PULSE2_START_FLAG
:no_reload
pla
plx
plp
rtl
@ -614,23 +910,21 @@ APU_STATUS_WRITE ENT
stal APU_STATUS
pha
; Pulse 1 is OSC 0
bit #$01
beq :pulse1_off
; _SetDOCReg #$40+pulse1_oscillator;#128
bra :pulse1_end
:pulse1_off
; _SetDOCReg #$40+pulse1_oscillator;#0
:pulse1_end
; From NESDev Wiki: When the enabled bit is cleared (via $4015), the length counter is forced to 0
; and cannot be changed until enabled is set again (the length counter's previous value is lost).
; There is no immediate effect when enabled is set.
; Pulse 2 is OSC 2
; Pulse 1
bit #$01
bne :pulse1_on
stz APU_PULSE1_LENGTH_COUNTER
:pulse1_on
; Pulse 2
bit #$02
beq :pulse2_off
; _SetDOCReg #$40+pulse2_oscillator;#128
bra :pulse2_end
:pulse2_off
; _SetDOCReg #$40+pulse2_oscillator;#0
:pulse2_end
bne :pulse2_on
stz APU_PULSE2_LENGTH_COUNTER
:pulse2_on
; Triangle is OSC 4
; bit #$03

View File

@ -11,7 +11,7 @@
; draw char at loc
; update loc
; see if length hit - no? back to draw char
rel
; rel
mx %00
]F_Length ds 2 ;length of string (only one byte currently used)
]F_CharIdx ds 2 ;index of current character
@ -19,6 +19,55 @@
]F_StrPtr equ $01 ;pointer to string (including length byte) / DP
]F_StrClr equ $03
;x = TopLeft screen pos
;y = font mask
;a = 8-bit char
DrawBottom
pha ; local variable space
pha
tsc
phd
tcd
sty ]F_StrClr
lda 3,s
sec
sbc #' '
asl ;*2
tay
jsr drawBottom
pld
pla
pla
rts
;x = TopLeft screen pos
;y = font mask
;a = 8-bit char
DrawChar
pha ; local variable space
pha
tsc
phd
tcd
sty ]F_StrClr
lda 3,s
sec
sbc #' '
asl ;*2
tay
jsr drawChar
pld
pla
pla
rts
DrawString
pha ; local variable space
pha
@ -52,16 +101,20 @@ NextChar lda ]F_CharIdx
asl ;*2
tay
ldx ]F_CurrentPos
jsr :drawChar
jsr drawChar
inc ]F_CurrentPos ;compare to addition time (?)
inc ]F_CurrentPos
inc ]F_CurrentPos
inc ]F_CurrentPos ;update screen pos (2 words=8 pixels)
bra NextChar
;x = TopLeft screen pos
;y = char table offset
:drawChar lda FontTable,y ;get real address of char data
drawBottom lda FontTable,y ;get real address of char data
sec
sbc #FontData ;pivot offset - now a is offset of fontdata
tay ;so we'll index with that
brl bottom
drawChar lda FontTable,y ;get real address of char data
sec
sbc #FontData ;pivot offset - now a is offset of fontdata
tay ;so we'll index with that
@ -106,6 +159,7 @@ NextChar lda ]F_CharIdx
and ]F_StrClr
stal {$E12000+160*4+2},x
bottom
lda FontData+20,y
and ]F_StrClr
stal {$E12000+160*5},x

View File

@ -722,30 +722,37 @@ APU_IND_X_REG3_W
:reg_tbl dw APU_PULSE1_REG3_W,APU_PULSE1_REG3_W
dw APU_PULSE2_REG3_W,APU_PULSE2_REG3_W,
dw APU_TRIANGLE_REG3_W,APU_TRIANGLE_REG3_W
dw NO_OP,NO_OP
APU_IND_X_REG4_W
jmp (:reg_tbl,x)
:reg_tbl dw APU_PULSE1_REG4_W,APU_PULSE1_REG4_W
dw APU_PULSE2_REG4_W,APU_PULSE2_REG4_W,
dw APU_TRIANGLE_REG4_W,APU_TRIANGLE_REG4_W
dw NO_OP,NO_OP
APU_PULSE1_REG1_W
jsl APU_PULSE1_REG1_WRITE
NO_OP
rts
APU_PULSE1_REG1_WX
phx
pha
txa
jsl APU_PULSE1_REG1_WRITE
pla
plx
rts
APU_PULSE1_REG2_W
jsl APU_PULSE1_REG2_WRITE
rts
APU_PULSE1_REG2_WY
phy
pha
tya
jsl APU_PULSE1_REG2_WRITE
pla
ply
rts
APU_PULSE1_REG3_W
jsl APU_PULSE1_REG3_WRITE
@ -758,25 +765,31 @@ APU_PULSE2_REG1_W
jsl APU_PULSE2_REG1_WRITE
rts
APU_PULSE2_REG1_WX
phx
pha
txa
jsl APU_PULSE2_REG1_WRITE
pla
plx
rts
APU_PULSE2_REG2_W
jsl APU_PULSE2_REG2_WRITE
rts
APU_PULSE2_REG2_WY
phy
pha
tya
jsl APU_PULSE2_REG2_WRITE
pla
ply
rts
APU_PULSE2_REG2_WX
phx
pha
txa
jsl APU_PULSE2_REG2_WRITE
pla
plx
rts
APU_PULSE2_REG3_W
jsl APU_PULSE2_REG3_WRITE
@ -802,7 +815,10 @@ APU_STATUS_W
rts
APU_STATUS_WX
phx
pha
txa
jsl APU_STATUS_WRITE
pla
plx
rts
; Hooks to call back to the GTE harness for PPU memory-mapped accesses