Questions About DPCM and PCM

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Well, I've managed to make it work after loading sprites and stuff by doing a very long and repeated loop cycle before loading the next sample. For some reason the BIT or the AND instructions kept being ignored or jumped out. Probably this isn't the best solution, but it worked:

Code: Select all

Sample1: 
  LDA #%00000000
  STA $4015  
  LDA #%00001001
  STA $4010		; Pitch Sample
  LDA #%00000000
  STA $4012		; Starting address - in this case $C000
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL
  JSR LL  
Sample2:
  LDA #%00000000
  STA $4015
  [...]
LL:  
  LDY #$FF
L1:
  LDX #$FF
L2:
  DEX
  BNE L2
  DEY
  BNE L1  
  RTS   
  
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Questions About DPCM and PCM

Post by tokumaru »

It looks like you're manually waiting for the samples to finish, which works in this particular case, but is not a general solution you can use every time.

It *should* definitely be possible to programmatically wait for each sample to finish before starting the next one, so I think you should take some time to figure out why your program fails to do that. Feel free to post the entire source so we can help look for the problem.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

tokumaru wrote: Sat Jun 12, 2021 6:25 pm It looks like you're manually waiting for the samples to finish, which works in this particular case, but is not a general solution you can use every time.

It *should* definitely be possible to programmatically wait for each sample to finish before starting the next one, so I think you should take some time to figure out why your program fails to do that. Feel free to post the entire source so we can help look for the problem.
I find it odd that it works if I put it before turning on the screen but not after.
Here goes the entire code:

Code: Select all

  .inesprg 2   ; 2x 16KB PRG code
  .ineschr 1   ; 1x  8KB CHR data
  .inesmap 0   ; mapper 0 = NROM, no bank swapping
  .inesmir 1   ; background mirroring

;;-------------------VARIABLES---------------------

  .rsset $0000  ;;start variables at ram location 0 in zero page memory

bgLow			.rs 1
bgHigh			.rs 1
counterLow		.rs 1
counterHigh		.rs 1
buttons1        		.rs 1   	; Buttons pressed (P1)
buttons2			.rs 1     	;unused
lastframebuttons1	.rs 1
releasedbuttons1	.rs 1
pressedbuttons1	.rs 1
framecounter 		.rs 1		; this is not being used
pointerLo			.rs 1
pointerHi			.rs 1

;;-------------------CONSTANTS---------------------

JOYPAD1 = $4016
JOYPAD2 = $4017
BUTTON_A      = 1 << 7
BUTTON_B      = 1 << 6
BUTTON_SELECT = 1 << 5
BUTTON_START  = 1 << 4
BUTTON_UP     = 1 << 3
BUTTON_DOWN   = 1 << 2
BUTTON_LEFT   = 1 << 1
BUTTON_RIGHT  = 1 << 0 

;;------------------------------------------------- 
   
  .bank 0
  .org $8000 
RESET:
  SEI
  CLD 
  LDX #$40
  STX $4017 
  LDX #$FF
  TXS  
  INX 
  STX $2000
  STX $2001    
  STX $4010 

vblankwait1:
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x
  INX
  BNE clrmem
   
vblankwait2:
  BIT $2002
  BPL vblankwait2

						
LoadPalettes:
  LDA $2002 
  LDA #$3F
  STA $2006            
  LDA #$00
  STA $2006             
  LDX #$00              
LoadPalettesLoop:
  LDA palette, x   
  STA $2007  
  INX                  
  CPX #$20     
  BNE LoadPalettesLoop

LoadSprites:
  LDX #$00
LoadSpritesLoop:
  LDA sprites, x 
  STA $0200, x 
  INX 
  CPX #$FF  
  BNE LoadSpritesLoop 

LoadBackground:
  LDA $2002
  LDA #$20
  STA $2006 
  LDA #$00
  STA $2006 
  LDX #$00

  LDA #LOW(background)
  STA bgLow
  LDA #HIGH(background)
  STA bgHigh
  LDA #$C0;
  STA counterLow
  LDA #$03
  STA counterHigh

LDY #$00
LoadBackgroundLoop:
  LDA [bgLow], y
  STA $2007             
  LDA bgLow 
  CLC
  ADC #$01
  STA bgLow 
  LDA bgHigh
  ADC #$00
  STA bgHigh 

  LDA counterLow 
  SEC 
  SBC #$01
  STA counterLow 
  LDA counterHigh
  SBC #$00
  STA counterHigh

  LDA counterLow 
  CMP #$00 
  BNE LoadBackgroundLoop
  LDA counterHigh
  CMP #$00
  BNE LoadBackgroundLoop

LoadAttribute:
  LDA $2002
  LDA #$23
  STA $2006
  LDA #$C0
  STA $2006
  LDX #$00
LoadAttributeLoop:
  LDA attribute, x     
  STA $2007             
  INX                  
  CPX #$40              
  BNE LoadAttributeLoop
  
  LDA #%00010000
  STA $4015

  LDA #%00000000 
  STA $2000
  STA $2001   
            
  LDA #%10010000
  STA $2000

  LDA #%00011110
  STA $2001

Forever:
  JMP Forever 
  
;;--------------------------------------------------------------------------------------------- 

NMI:
  LDA #$00
  STA $2003
  LDA #$02
  STA $4014
  
  JSR ReadJoy
  
ReadJoySafe:
  JSR ReadJoy
ReRead:
  LDA buttons1
  PHA
  JSR ReadJoy
  PLA
  CMP buttons1
  BNE ReRead

  LDA buttons1,x
  EOR #%11111111
  AND lastframebuttons1,x
  STA releasedbuttons1,x
  LDA lastframebuttons1
  EOR #%11111111
  AND buttons1,x
  STA pressedbuttons1,x   
    
ReadA:
  LDA buttons1
  AND #BUTTON_A
  BEQ notPressingA
  JSR Sample1
   
notPressingA
  
  
  LDA #%10010000 
  STA $2000
  LDA #%00011110
  STA $2001
  LDA #$00
  STA $2005
  STA $2005  

NMIDone:  
  RTI 

 
ReadJoy:
  LDA #$01
  STA JOYPAD1
  STA buttons1
  STA $4016
  LSR A
  STA JOYPAD1 
LoopJ:
  LDA JOYPAD1
  LSR A
  ROL buttons1
  BCC LoopJ
  RTS

Sample1: 
  LDA #%00000000
  STA $4015  
  LDA #%00001001
  STA $4010		; Pitch Sample
  LDA #%00000000
  STA $4012		; Starting address - in this case $C000
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
Sample1Loop:
  BIT $4015		; Either this or BIT $4015
  BNE Sample1Loop
  
Sample2:
  LDA #%00000000
  STA $4015
  LDA #%00001001
  STA $4010		; Pitch Sample
  LDA #%00000000
  STA $4012		; Starting address - in this case $C000
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
Sample2Loop:
  BIT $4015
  BNE Sample2Loop
  
Sample3:
  LDA #%00000000
  STA $4015
  LDA #%00001001
  STA $4010
  LDA #$35
  STA $4012		; Starting address - in this case $C000
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
Sample3Loop:
  BIT $4015
  BNE Sample3Loop
  RTS  
 
;---------------------------------------------------------------------------------------  
  .bank 1
  .org $A000

palette:
  .db $0F,$15,$25,$36, $0F,$01,$26,$36, $0F,$1A,$2A,$30, $0F,$01,$21,$30   ;backgroundp
  .db $0F,$15,$25,$36, $0F,$17,$27,$37, $0F,$21,$26,$36, $0F,$01,$21,$30   ;spritesp

sprites:
     ;vert tile attr horiz
  .db $47, $05, $01, $78
  .db $47, $06, $01, $80
  .db $4F, $14, $01, $70
  .db $4F, $15, $01, $78
  .db $4F, $16, $01, $80
  .db $4F, $17, $01, $88
  .db $4F, $18, $01, $90 
  .db $57, $24, $01, $70
  .db $57, $25, $01, $78
  .db $57, $26, $01, $80
  .db $57, $27, $01, $88
  .db $57, $28, $01, $90
  .db $57, $29, $02, $98
  .db $57, $2A, $02, $A0
  .db $5F, $34, $01, $70
  .db $5F, $35, $01, $78
  .db $5F, $36, $01, $80
  .db $5F, $37, $01, $88
  .db $67, $41, $03, $58
  .db $67, $42, $03, $60
  .db $67, $44, $01, $70
  .db $67, $47, $01, $88
  .db $6F, $54, $01, $70
  .db $6F, $57, $01, $88
  .db $6F, $58, $03, $90
  .db $6F, $59, $02, $98
  .db $6F, $5A, $02, $A0
  .db $77, $61, $03, $58
  .db $77, $62, $03, $60
  .db $77, $63, $03, $68
  .db $77, $64, $01, $70
  .db $77, $67, $01, $88
  .db $77, $68, $03, $90
  .db $77, $69, $03, $98
  .db $77, $6A, $03, $A0
  .db $7F, $70, $00, $50
  .db $7F, $71, $00, $58
  .db $7F, $72, $00, $60
  .db $7F, $73, $00, $68
  .db $7F, $74, $00, $70
  .db $7F, $7A, $03, $A0
  .db $7F, $7B, $03, $A8
  .db $87, $80, $00, $50
  .db $87, $81, $00, $58
  .db $87, $84, $03, $70
  .db $87, $88, $00, $90
  .db $87, $8A, $03, $A0
  .db $87, $8B, $03, $A8
  .db $8F, $91, $00, $58
  .db $8F, $93, $00, $68
  .db $8F, $98, $00, $90
  .db $97, $A3, $00, $68
  .db $97, $A8, $00, $90
  .db $9F, $B1, $03, $58
  .db $9F, $B2, $03, $60
  .db $9F, $B3, $03, $68
  


background:
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01,$02,$03,$04,$05
  .db $06,$07,$08,$09,$0A,$0B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$10,$11,$12,$13,$14,$15
  .db $16,$17,$18,$19,$1A,$1B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$20,$21,$22,$23,$24,$25
  .db $26,$27,$28,$29,$2A,$2B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$30,$31,$32,$33,$34,$35
  .db $36,$37,$38,$39,$3A,$3B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$40,$41,$42,$43,$44,$45
  .db $46,$47,$48,$49,$4A,$4B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$50,$51,$52,$53,$54,$55
  .db $56,$57,$58,$59,$5A,$5B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$60,$61,$62,$63,$64,$65
  .db $66,$67,$68,$69,$6A,$6B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$70,$71,$72,$73,$74,$75
  .db $76,$77,$78,$79,$7A,$7B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$80,$81,$82,$83,$84,$85
  .db $86,$87,$88,$89,$8A,$8B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$90,$91,$92,$93,$94,$95
  .db $96,$97,$98,$99,$9A,$9B,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$A0,$A1,$A2,$A3,$A4,$A5
  .db $A6,$A7,$A8,$A9,$AA,$AB,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$B0,$B1,$B2,$B3,$B4,$B5
  .db $B6,$B7,$B8,$B9,$BA,$BB,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00


attribute:
   ; 64 bytes following a nametable
  .db $00,$00,$00,$00, $00,$00,$00,$00
  .db $00,$00,$00,$00, $00,$00,$00,$00
  .db $00,$00,$00,$00, $88,$22,$00,$00
  .db $00,$00,$00,$00, $84,$29,$00,$00
  .db $00,$00,$CC,$33, $CC,$3E,$00,$00
  .db $00,$00,$00,$00, $08,$0A,$00,$00
  .db $00,$00,$00,$00, $00,$00,$00,$00
  .db $00,$00,$00,$00, $00,$00,$00,$00   
  
  .bank 2
  .org $C000
  .incbin "SP1.dmc"
  .incbin "SP2.dmc"
  
  .bank 3
  .org $E000
  

  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                   ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                   ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial
  
  
;;;;;;;;;;;;;;  
  
  .bank 4
  .org $0000
  .incbin "world.chr"
Joe
Posts: 650
Joined: Mon Apr 01, 2013 11:17 pm

Re: Questions About DPCM and PCM

Post by Joe »

Shauing wrote: Sat Jun 12, 2021 7:41 pmI find it odd that it works if I put it before turning on the screen but not after.
It's not odd at all. Right around where you turn on the screen, you also enable NMI. Your NMI handler doesn't preserve the contents of A/X/Y, so whatever code it interrupts will suddenly have different values in the CPU registers. This doesn't cause a problem when it interrupts "JMP Forever" because that code isn't using any registers, but it causes problems if it interrupts something else.

The typical solution looks like this:

Code: Select all

NMI:
    PHA
    TXA
    PHA
    TYA
    PHA
    (...your NMI handler here...)
    PLA
    TAY
    PLA
    TAX
    PLA
    RTI
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Joe wrote: Sat Jun 12, 2021 7:57 pm It's not odd at all. Right around where you turn on the screen, you also enable NMI. Your NMI handler doesn't preserve the contents of A/X/Y, so whatever code it interrupts will suddenly have different values in the CPU registers. This doesn't cause a problem when it interrupts "JMP Forever" because that code isn't using any registers, but it causes problems if it interrupts something else.

The typical solution looks like this:

Code: Select all

NMI:
    PHA
    TXA
    PHA
    TYA
    PHA
    (...your NMI handler here...)
    PLA
    TAY
    PLA
    TAX
    PLA
    RTI
Aha! That did the job. Now I can insert before the JMP Forever or activate it via a button press and it will play all the samples. I had seen these bits of code somewhere else but didn't know exactly what they could do. Thank you very much! I really appreciate the help and patience from all of you! (Now onto solving the sprite animation thing...)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Questions About DPCM and PCM

Post by tokumaru »

Exactly what I suspected - your NMI handler is trashing the CPU registers. Look at it like this: the code that plays the samples takes several frames to complete, meaning that when NMIs are on, several will fire while that playback code is running. The last version of the code that polled $4015 relied on a specific value being loaded in the accumulator for that bit $4015 to work, but then the NMI fires and you do a lot of things with A, the last of which appears to be lda #$00 before resetting the scroll and returning from the NMI. Then, the $4015 polling resumes, but A doesn't hold the bitmasks it did before, if holds 0, which causes the BIT instruction to always result in 0, so the code gets stuck in that loop and never detects the end of the first sample and never gets to the second one.

In NMI and IRQ handlers you always need to backup any registers and variables that you change and are used in code that gets interrupted by those handlers, otherwise the code that gets interrupted can't resume what it was doing before. Normally, pushing A, X and Y to the stack and restoring them later like Joe suggested is enough.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

tokumaru wrote: Sat Jun 12, 2021 9:05 pm Exactly what I suspected - your NMI handler is trashing the CPU registers. Look at it like this: the code that plays the samples takes several frames to complete, meaning that when NMIs are on, several will fire while that playback code is running. The last version of the code that polled $4015 relied on a specific value being loaded in the accumulator for that bit $4015 to work, but then the NMI fires and you do a lot of things with A, the last of which appears to be lda #$00 before resetting the scroll and returning from the NMI. Then, the $4015 polling resumes, but A doesn't hold the bitmasks it did before, if holds 0, which causes the BIT instruction to always result in 0, so the code gets stuck in that loop and never detects the end of the first sample and never gets to the second one.

In NMI and IRQ handlers you always need to backup any registers and variables that you change and are used in code that gets interrupted by those handlers, otherwise the code that gets interrupted can't resume what it was doing before. Normally, pushing A, X and Y to the stack and restoring them later like Joe suggested is enough.
I think I understand. For what I looked on Mesen (when putting the code after NMI), it would read and stay on the first loop apparently reading it but suddenly jump back to the NMI. Sometimes though after that jump it would keep going and get as far as to the second sample loop (probably ignoring the first one) but it would get stuck there or jump again to the NMI.
So, if I'm understanding correctly, with these pushing and restore instructions, it saves and loads those registers and variables used by $4010-4015.
Post Reply