CutterCross
Active member
I was just wondering, what EXACTLY are the issues that come up with using DPCM samples? Is it purely a ROM space issue, or are there other problems that arise in the engine itself when using them?
Thanks for the info Kasumi!Kasumi said:The easy way:
Each frame:
1. Read each button's state and store them all temporary RAM
2. Read each button's state again and compare them to the value in temporary RAM
3. If the values don't match, use the buttons from last frame as this frame's buttons.
An equally easy way:
1. Read each button's state and store them all temporary RAM
2. Read each button's state again and compare them to the value in temporary RAM
3. If the values don't match, go back to 1. (This will get slightly more accurate input, but eats more CPU time. It can also effectively be made into an infinite loop by trolly hardware hooked up to the controller port.)
This topic describes a way (some ways?) to avoid most problems with exactly 3 reads: http://forums.nesdev.com/viewtopic.php?f=2&t=14197
But even ignoring the controller corruption, DPCM samples are inconvenient to use. They need to be in a very specific place in memory, and always present there. (One shouldn't bankswitch that area while a sample is playing.) For Mapper 30, where they need to be can't be bankswitched at all and is an area you'd want to put most of your engine code. Samples can end up competing for space with the NES Maker engine.
Okay, that explains a lot! Thanks!Kasumi said:CutterCross - If a sample is playing at all, input corruption is always possible. But it's not specifically playing the sample that causes the corruption. Playback only relies on the thing that causes the corruption.
If you're familiar with streaming services like youtube: A portion of the video loads. When you've watched most of the portion that loaded, more of the video is sent to you. And when you've watched most of that, more of the video is sent to you.
Sample playback works similarly. A portion of the sample data is loaded into a buffer by the hardware to play back later. When that portion is done playing, the next portion of the sample data is loaded.
If the read the hardware does that loads more sample data into the buffer happens at exactly the same time as the software reads the "controller register", the value given to the software will be incorrect. (But "at exactly the same time" is very specific. It's basically a 50/50 shot whether the read will be wrong even if it's as close to the sample read as is possible for the CPU to be at that point in time.)
High quality samples mean the buffer needs to be filled more often which increases the likelihood of the two things happening at the same time. It's like how a 1080p 60FPS stereo video would request data more often than a 240p mono video on youtube.
So input data can be read correctly while a sample is playing, but not when the buffer is getting filled (well, even that has a 50/50 shot of no effect). Since the buffer is not getting filled all the time, input data won't always be corrupted when playing back samples.
Anyway, this is just information. I could probably just provide joypad code that's DPCM safe and you wouldn't have to worry about the specifics. It's a one time change.
I see the harder bit as where the sample data needs to be and the implications of that. The engine growing could get in the way of someone's samples that used to fit. A user's samples growing could crash into the engine. So it's an "over time" change rather than a "one time" change.
lda <p1curstate
sta <p1oldstate
lda #$01
sta <p1curstate ;initialize the buffer with a flag
sta $4016;Strobe joypads
lsr a
sta $4016
p1and2loop:
lda $4016
and #%00000011
cmp #$01
rol <p1curstate
bcc p1and2loop ;loop if the flag wasn't shifted out yet
lda #$01
sta <reserved0 ;initialize the buffer with a flag
sta $4016;Strobe joypads
lsr a
sta $4016
p1testloop:
lda $4016
and #%00000011
cmp #$01
rol <reserved0
bcc p1testloop;loop if the flag wasn't shifted out yet
lda <reserved0
cmp <p1curstate
beq joypad1matched
lda <p1oldstate
sta <p1curstate
joypad1matched:
lda <p1curstate
sta <p1oldstate
joypad1didnotmatch:
lda #$01
sta <p1curstate ;initialize the buffer with a flag
sta $4016;Strobe joypads
lsr a
sta $4016
p1and2loop:
lda $4016
and #%00000011
cmp #$01
rol <p1curstate
bcc p1and2loop ;loop if the flag wasn't shifted out yet
lda #$01
sta <reserved0 ;initialize the buffer with a flag
sta $4016;Strobe joypads
lsr a
sta $4016
p1testloop:
lda $4016
and #%00000011
cmp #$01
rol <reserved0
bcc p1testloop;loop if the flag wasn't shifted out yet
lda <reserved0
cmp <p1curstate
bne joypad1didnotmatch
lda <p1curstate
sta <p1oldstate
lda #$01
sta <p1curstate ;initialize the buffer with a flag
sta $4016;Strobe joypads
lsr a
sta $4016
p1and2loop:
lda $4016
and #%00000011
cmp #$01
rol <p1curstate
bcc p1and2loop ;loop if the flag wasn't shifted out yet
I also think it was a good idea to disable DPCM for newbies just as long as it's simple for craftier users to enable it back again. After all, you shouldn't exclude others from innovating if they know the means to do so, whether it involves DPCM samples or anything else the NES is capable of. If nobody was innovating, we probably wouldn't see anything notable coming from NESmaker. It would kinda be like Unity, a very capable game engine but is really only known for cheap shovelware-type games, despite being capable of so much more than that.Mihoshi20 said:There and interesting forum thread I stumbled across on NESDEV titled "DPCM generates extra $4016 read pulse" that talks about DPCM and it's relation to the controller inputs you might find interesting. http://forums.nesdev.com/viewtopic.php?t=4116
There's also this little nugget of info on the NESDEV wiki. "DPCM Safety using Repeated Reads: If your code is intended to be used with APU DMC playback, this code will need to be altered. The NES occasionally glitches the controller port twice in a row if sample playback is enabled, and games using samples need to work around this. For example, Super Mario Bros. 3 reads each controller's data at least two times each frame. First it reads it as normal, then it reads it again. If the two results differ, it does the procedure all over. Because repeated rereads can take a long time, another way is to just use the previous frame's button press data if the results differ: "
I think the disabling DPCM capabilities for the engine and letting advanced users deal with it was the overall right choice given the tool's market and scope as nice as it would be to have the feature and once the tool is release to the public, a clever user may work the functionality back in that doesn't cause issues with controller input and release release the code for others to use.
Kasumi, you are AWESOME! I think you just saved all of us several hours of work. (Granted, I won't be able to test it out until August when NESmaker releases, but it's incredible nonetheless!)Kasumi said:I've attached a specifically NES Maker joypad routine that allows for what Super Mario Bros. 3 does.
It's tied to the same define that GGSound uses to enable the DPCM features. If DPCM is enabled in GGSound, you get a routine that attempts to avoid controller corruption. If DPCM is not enabled in GGSound, you get a regular controller read routine.
No need to swap out files, just the define. (Which you'd do anyway to enable/disable the feature within GGSound.)
Kasumi said:Well, I'm not that awesome. I realized the code would have done bad things to NES Maker. There's a limited time to do certain kinds of graphical updates. NES Maker reads the controller before all such updates are done. The SMB3 method of reading controllers in this context could take enough time to make certain graphical updates happen outside the time when they're allowed.
The non Super Mario Bros 3. method (read both, but take the previous frame if they don't match) would be a little better. (Since the time used would be fixed.)
I know how to fix it (you'd move jsr GamePadCheck to right above skipNMUstuff but the file the fix needs to go in is not as isolated. (MainASM.asm is much more likely to change in updates such that just replacing the file would break it.)
Edit: If you wanna get your hands really dirty, you could also just try this: https://forums.nesdev.com/viewtopic.php?f=2&t=14319&start=15#p172188 Since NES Maker reads the controller very near to a sprite DMA currently anyway, that's a method that could be tried. (But I haven't tested it.) You'd have to modify it to read the buttons as NES Maker does.
Yeah, I understand that. You never know what sort of things will change in the future, but it doesn't hurts to start planning things out early. I know that I probably won't use DPCM samples in most of my games anyway, but it's good to experiment to see what you can get away with.Mihoshi20 said:Kasumi said:Well, I'm not that awesome. I realized the code would have done bad things to NES Maker. There's a limited time to do certain kinds of graphical updates. NES Maker reads the controller before all such updates are done. The SMB3 method of reading controllers in this context could take enough time to make certain graphical updates happen outside the time when they're allowed.
The non Super Mario Bros 3. method (read both, but take the previous frame if they don't match) would be a little better. (Since the time used would be fixed.)
I know how to fix it (you'd move jsr GamePadCheck to right above skipNMUstuff but the file the fix needs to go in is not as isolated. (MainASM.asm is much more likely to change in updates such that just replacing the file would break it.)
Edit: If you wanna get your hands really dirty, you could also just try this: https://forums.nesdev.com/viewtopic.php?f=2&t=14319&start=15#p172188 Since NES Maker reads the controller very near to a sprite DMA currently anyway, that's a method that could be tried. (But I haven't tested it.) You'd have to modify it to read the buttons as NES Maker does.
This is mostly why I'm not focusing on major projects until after August when I'm more confident that the code will at least be a bit more stable or in a more finalize state as far as MainASM and primary areas of the code not designed to be edited by the general user. In 'feature lock' if you will. Currently I'm opting more to work on smaller projects to get used to the tool and it's functions and changes during the beta period so if a major part of the tool changes and I have to start over, it's not as big of a deal.
CutterCross said:Okay, that explains a lot! Thanks!Kasumi said:CutterCross - If a sample is playing at all, input corruption is always possible. But it's not specifically playing the sample that causes the corruption. Playback only relies on the thing that causes the corruption.
If you're familiar with streaming services like youtube: A portion of the video loads. When you've watched most of the portion that loaded, more of the video is sent to you. And when you've watched most of that, more of the video is sent to you.
Sample playback works similarly. A portion of the sample data is loaded into a buffer by the hardware to play back later. When that portion is done playing, the next portion of the sample data is loaded.
If the read the hardware does that loads more sample data into the buffer happens at exactly the same time as the software reads the "controller register", the value given to the software will be incorrect. (But "at exactly the same time" is very specific. It's basically a 50/50 shot whether the read will be wrong even if it's as close to the sample read as is possible for the CPU to be at that point in time.)
High quality samples mean the buffer needs to be filled more often which increases the likelihood of the two things happening at the same time. It's like how a 1080p 60FPS stereo video would request data more often than a 240p mono video on youtube. visit phenq review here
So input data can be read correctly while a sample is playing, but not when the buffer is getting filled (well, even that has a 50/50 shot of no effect). Since the buffer is not getting filled all the time, input data won't always be corrupted when playing back samples.
Anyway, this is just information. I could probably just provide joypad code that's DPCM safe and you wouldn't have to worry about the specifics. It's a one time change.
I see the harder bit as where the sample data needs to be and the implications of that. The engine growing could get in the way of someone's samples that used to fit. A user's samples growing could crash into the engine. So it's an "over time" change rather than a "one time" change.
So, what if I had a very small sample that constantly loops to make the sound last longer? Does the buffer need to constantly refill the same sample data over and over again, or is it a one-time thing at the start of sample playback?
I guess another question I'm asking is, how much sample data can the buffer hold at one time?