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

Questions About DPCM and PCM

Post by Shauing »

Hello, I've been experimenting with inserting DPCM and PCM stuff, but because I couldn't find tutorials for both, I had to rely on what I could find.

So far, I've managed to playback at least one DPCM sample, loop and without, but I have no idea how to code to play more samples after the first finishes, and I also haven't managed to make it loop back to the first one or stop.

I have found next to no info on how to code/playback RAW PCM, as well as tools or anything regarding how to convert WAV to 7-bit RAW, as I can only get 8-bit RAW from, say, Audacity.

I'm using NESASM3.1 to do all this. I hope someone can help me with this.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Questions About DPCM and PCM

Post by unregistered »

Hi Shauing,

Ummm… NES music, in our game, is created with Famitracker. Then that text file is changed into a file that will work with Famitone2, using Famitone2’s text2data.exe file. Last, I just include that data text file in my game and let Famitone2 run in when I want.

Obviously, my music creation does NOT involve conversion of WAV to NES music. But, thought this description may help you succeed anyway. Making an NES game isn’t instantaneous; takes some work. :)


EDIT: oooh, we are using an asm6 assembler. ImO, asm6 > NESASM3.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

unregistered wrote: Tue Jun 08, 2021 8:27 am Hi Shauing,

Ummm… NES music, in our game, is created with Famitracker. Then that text file is changed into a file that will work with Famitone2, using Famitone2’s text2data.exe file. Last, I just include that data text file in my game and let Famitone2 run in when I want.

Obviously, my music creation does NOT involve conversion of WAV to NES music. But, thought this description may help you succeed anyway. Making an NES game isn’t instantaneous; takes some work. :)


EDIT: oooh, we are using an asm6 assembler. ImO, asm6 > NESASM3.
I'm just experimenting with inserting and playback DPCM ($4015) and PCM ($4011) via NESASM3.1. Problem is, I can't find how to code specific things like playing more than one DPCM sample continuously and either loop back to the first one or stop after a certain number of samples have been played, as well as how to code to playback and stop the RAW 7-bit PCM.
I know that people use ASM6 more as it is not clunky like NESASM3, but the coding itself for what I'm asking is too different between both?
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Questions About DPCM and PCM

Post by Pokun »

There are tools for converting WAV to the used 7-bit format here and there.

NESASM or ASM6 shouldn't matter. You start and stop DPCM samples by writing 1 or 0 respectively to $4015 D4. It's not like the other channels where you can just mute them.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Pokun wrote: Tue Jun 08, 2021 9:28 am There are tools for converting WAV to the used 7-bit format here and there.

NESASM or ASM6 shouldn't matter. You start and stop DPCM samples by writing 1 or 0 respectively to $4015 D4. It's not like the other channels where you can just mute them.
Thank you for the conversion tools, will check them. But then, what do you code to load and play this 7-bit PCM?

Ok, but to play another DPCM sample after the first one, how is it coded? And then another after the second one, and so on?
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Questions About DPCM and PCM

Post by Quietust »

Shauing wrote: Tue Jun 08, 2021 10:15 am
Pokun wrote: Tue Jun 08, 2021 9:28 am There are tools for converting WAV to the used 7-bit format here and there.

NESASM or ASM6 shouldn't matter. You start and stop DPCM samples by writing 1 or 0 respectively to $4015 D4. It's not like the other channels where you can just mute them.
Thank you for the conversion tools, will check them. But then, what do you code to load and play this 7-bit PCM?

Ok, but to play another DPCM sample after the first one, how is it coded? And then another after the second one, and so on?
You play 7-bit raw PCM by writing each individual sample byte to $4011 with appropriate delays in between them - writes take effect immediately, so you'll need carefully-timed code in order to ensure that the sound is played at the correct sample rate.

To play multiple DPCM samples in sequence, you just need to wait for the first sample to finish, typically by checking bit 4 of $4015 (around once per frame should suffice), and then start the next one. Alternatively, you could configure DPCM to generate an IRQ when the sample is finished playing, then have your interrupt handler start the next sample.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Quietust wrote: Tue Jun 08, 2021 10:27 am
You play 7-bit raw PCM by writing each individual sample byte to $4011 with appropriate delays in between them - writes take effect immediately, so you'll need carefully-timed code in order to ensure that the sound is played at the correct sample rate.

To play multiple DPCM samples in sequence, you just need to wait for the first sample to finish, typically by checking bit 4 of $4015 (around once per frame should suffice), and then start the next one. Alternatively, you could configure DPCM to generate an IRQ when the sample is finished playing, then have your interrupt handler start the next sample.
Hmm, I think I'm doing something a bit wrong as, if I put this code and is not being activated by a button, it kind of sounds murky and just loops infinitely (only want to play it once), but if I do map it to a button press, the sample plays pretty much right but garbage also plays before repeating the PCM like 3 times or so.
This is my code (the RAW data is located at $A000):

Code: Select all

  LDA #$A0
  STA pointerHi		
  STA pointerLo
No:
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  LDA [pointerHi],Y
  STA $4011		
  INY
  BNE No
  INC pointerLo
  BNE No 
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Questions About DPCM and PCM

Post by Quietust »

Shauing wrote: Tue Jun 08, 2021 11:42 am Hmm, I think I'm doing something a bit wrong as, if I put this code and is not being activated by a button, it kind of sounds murky and just loops infinitely (only want to play it once), but if I do map it to a button press, the sample plays pretty much right but garbage also plays before repeating the PCM like 3 times or so.
This is my code (the RAW data is located at $A000):

Code: Select all

  LDA #$A0
  STA pointerHi		
  STA pointerLo
No:
  NOP
  NOP
...
  NOP
  LDA [pointerHi],Y
  STA $4011		
  INY
  BNE No
  INC pointerLo
  BNE No 
So, I see several problems:
1. You're initializing your pointer to $A0A0 rather than $A000, so you're skipping the first 160 bytes of your sample (and your sample rate will vary slightly due to page-cross delays).
2. Your code to update the pointer after the index register overflows will take extra time, causing that specific loop iteration to be longer than normal (and possibly causing an audible error in the sound output). You should be able to fix it by branching to the 5th NOP instruction instead of the first one, since "INC $zp" + "BNE $xxxx" should take exactly 8 cycles (and a single NOP will take 2).
3. Your variable names are wrong - you should be doing "LDA [pointerLo],Y" and "INC pointerHi", and the two variables should be defined in zeropage in that same order (since the 6502 is Little Endian).
4. Your sample loop is going to go all the way to $FFFF, which probably isn't what you want.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Quietust wrote: Tue Jun 08, 2021 11:49 am
So, I see several problems:
1. You're initializing your pointer to $A0A0 rather than $A000, so you're skipping the first 160 bytes of your sample (and your sample rate will vary slightly due to page-cross delays).
2. Your code to update the pointer after the index register overflows will take extra time, causing that specific loop iteration to be longer than normal (and possibly causing an audible error in the sound output). You should be able to fix it by branching to the 5th NOP instruction instead of the first one, since "INC $zp" + "BNE $xxxx" should take exactly 8 cycles (and a single NOP will take 2).
3. Your variable names are wrong - you should be doing "LDA [pointerLo],Y" and "INC pointerHi", and the two variables should be defined in zeropage in that same order (since the 6502 is Little Endian).
4. Your sample loop is going to go all the way to $FFFF, which probably isn't what you want.
1. 3. Hopefully I fixed all that on the new code below.
2. But Branching to the 5th NOP instruction makes it sound higher than intended.
4. Indeed, it's not that big. What should I write/code so it doesn't go all the way to there?

Moved the data a bit earlier (in case I wanna test with even longer data):

Code: Select all

  LDY #$00
  STY pointerLo
  LDA #$90
  STA pointerHi
  NOP
  NOP
  NOP
  NOP
  NOP
No:  
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  LDA [pointerLo],Y
  STA $4011		
  INY
  BNE No
  INC pointerHi
  BNE No
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Questions About DPCM and PCM

Post by Quietust »

Shauing wrote: Tue Jun 08, 2021 12:05 pm
Quietust wrote: Tue Jun 08, 2021 11:49 am 2. Your code to update the pointer after the index register overflows will take extra time, causing that specific loop iteration to be longer than normal (and possibly causing an audible error in the sound output). You should be able to fix it by branching to the 5th NOP instruction instead of the first one, since "INC $zp" + "BNE $xxxx" should take exactly 8 cycles (and a single NOP will take 2).
2. But Branching to the 5th NOP instruction makes it sound higher than intended.

Code: Select all

  LDY #$00
  STY pointerLo
  LDA #$90
  STA pointerHi
  NOP
  NOP
  NOP
  NOP
  NOP
No:  
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  LDA [pointerLo],Y
  STA $4011		
  INY
  BNE No
  INC pointerHi
  BNE No
That's not what I meant - I meant something more like this:

Code: Select all

  LDY #$00
  STY pointerLo
  LDA #$90
  STA pointerHi
No:
  NOP
  NOP
  NOP
  NOP
No2:  
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  LDA [pointerLo],Y
  STA $4011		
  INY
  BNE No
  INC pointerHi
  BNE No2
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Quietust wrote: Tue Jun 08, 2021 12:28 pm
That's not what I meant - I meant something more like this:

Code: Select all

  LDY #$00
  STY pointerLo
  LDA #$90
  STA pointerHi
No:
  NOP
  NOP
  NOP
  NOP
No2:  
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  NOP
  LDA [pointerLo],Y
  STA $4011		
  INY
  BNE No
  INC pointerHi
  BNE No2
Aha, I see. It didn't seem to make anything different though than when having BNE to the first NOP.

I also noticed that if I don't map it to a button press to start and play, the sample gets stuck at the beginning.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

Well I sort of solved the PCM playback to just to the length of the sample by experimenting further (though the quality kind of sucks, probably because it's a very small ROM where I'm testing).
But now onto the DPCM, I have no idea how to play more than one DPCM sample back to back. I tried this code, but it only plays the first sample as it's seems the code it's either not slow enough or does nothing at all to start the second one:

Code: Select all

Sample1:  
  LDA #%00001001
  STA $4010		; Pitch Sample
  LDA #%00000000
  STA $4012		; Starting address
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
Sample1Loop:
  LDA $4015
  CMP #%00000000
  BNE Sample1Loop

Sample2:
  
  LDA #%00001001
  STA $4010		; Pitch Sample  
  LDA #%01000000
  STA $4012		; Starting address
  LDA #%11011011
  STA $4013		; Sample Length
  LDA #%00010000
  STA $4015
Sample2Loop:
  LDA $4015  
  CMP #%00000000
  BNE Sample2Loop
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 »

I'm not an APU expert by any means, but you're waiting for the value read back from $4015 to have ALL bits cleared, while the only bit you're interested in is bit 4. It is possible that the other unrelated bits are getting in your way and the value never reads back as 0, causing your program to get stuck in that wait loop. You should run your program in an emulator that lets you debug the code (FCEUX, Mesen, Nintendulator, etc.) and check which value is being returned by $4015 after the first sample finishes playing to see if this is the case.

Instead of the CMP #%00000000 (which after a load is a bogus instruction anyway since a comparison to 0 is implicitly made on every load), you need an AND #%00010000 to clear the bits that don't matter in this case, and set the Z flag according to the value of bit 4 exclusively.

Quietust also suggested checking this flag once per frame, not in a tight loop, but I don't know if that makes any difference.
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: Questions About DPCM and PCM

Post by Bregalad »

Or the more elegant :

Code: Select all

 lda #%00010000
 sta $4015
-
 bit $4015
 bne -
;)

But if you're going to spend 100% of your CPU time for sample playback anyway, you might just as well use $4011 instead and get better quality/bitrate ratio.

A real DPCM engine in a real game will most likely handle this stuff only once per frame, this is not a hard requirement however.
Useless, lumbering half-wits don't scare us.
Shauing
Posts: 15
Joined: Sun Mar 21, 2021 6:45 pm

Re: Questions About DPCM and PCM

Post by Shauing »

tokumaru wrote: Fri Jun 11, 2021 4:11 am I'm not an APU expert by any means, but you're waiting for the value read back from $4015 to have ALL bits cleared, while the only bit you're interested in is bit 4. It is possible that the other unrelated bits are getting in your way and the value never reads back as 0, causing your program to get stuck in that wait loop. You should run your program in an emulator that lets you debug the code (FCEUX, Mesen, Nintendulator, etc.) and check which value is being returned by $4015 after the first sample finishes playing to see if this is the case.

Instead of the CMP #%00000000 (which after a load is a bogus instruction anyway since a comparison to 0 is implicitly made on every load), you need an AND #%00010000 to clear the bits that don't matter in this case, and set the Z flag according to the value of bit 4 exclusively.

Quietust also suggested checking this flag once per frame, not in a tight loop, but I don't know if that makes any difference.
Bregalad wrote: Fri Jun 11, 2021 5:28 am Or the more elegant :

Code: Select all

 lda #%00010000
 sta $4015
-
 bit $4015
 bne -
;)

But if you're going to spend 100% of your CPU time for sample playback anyway, you might just as well use $4011 instead and get better quality/bitrate ratio.

A real DPCM engine in a real game will most likely handle this stuff only once per frame, this is not a hard requirement however.
Thank you tokumaru and Bregalad, will test both.
How can I get better quality/bitrate ratio with $4011? I have tried implementing several small snippets of RAW 7-PCM data with different sample rates and they all sound very crunched/distorted and loud.

EDIT: Checking on Mesen, at some point during the first or second sample loop it just jumps to the NMI for some reason. This is the code as of now:

Code: Select all

ReadA:
  LDA buttons1
  AND #BUTTON_A
  BEQ notPressingA
Sample1:  
  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 #%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		; Either this or BIT $4015
  BNE Sample2Loop
  
Sample3:
  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 
  
  notPressingA
  
EDIT 2: If I remove the input that activates this and put this code instead to before the JMP Forever, specifically before turning off the screen, it works. But if I try to activate it afterwards or with a press of a button it only plays the first sample and stops. For what I saw on Mesen, it seems that it's just ignoring the loops.
Post Reply