Questions About DPCM and PCM
Moderator: Moderators
Questions About DPCM and PCM
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.
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.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Questions About DPCM and PCM
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.
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.
Re: Questions About DPCM and PCM
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.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 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?
Re: Questions About DPCM and PCM
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?
Re: Questions About DPCM and PCM
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.
P.S. If you don't get this note, let me know and I'll write you another.
Re: Questions About DPCM and PCM
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.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.
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
Re: Questions About DPCM and PCM
So, I see several problems: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
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.
P.S. If you don't get this note, let me know and I'll write you another.
Re: Questions About DPCM and PCM
1. 3. Hopefully I fixed all that on the new code below.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.
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
Re: Questions About DPCM and PCM
That's not what I meant - I meant something more like this:Shauing wrote: ↑Tue Jun 08, 2021 12:05 pm2. But Branching to the 5th NOP instruction makes it sound higher than intended.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).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
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.
P.S. If you don't get this note, let me know and I'll write you another.
Re: Questions About DPCM and PCM
Aha, I see. It didn't seem to make anything different though than when having BNE to the first NOP.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
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.
Re: Questions About DPCM and PCM
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:
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
Re: Questions About DPCM and PCM
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.
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.
Re: Questions About DPCM and PCM
Or the more elegant :
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.
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.
Re: Questions About DPCM and PCM
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.
Thank you tokumaru and Bregalad, will test both.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.
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