FDS registers

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
FDS registers
by on (#149413)
I am trying to get a sense of how to implement FDS support from the wiki. I am having difficulty understanding the relationship between the registers and the actual file data. I can't figure out how it knows what it is reading from or what it is writing to. Can something provide an overview of how and when the registers are used in accessing the file data? Thanks.
Re: FDS registers
by on (#149610)
I made a lot of progress by studying the source codes of various emulators. For FDS Audio, I am having trouble getting the modulator unit to function properly. I implemented it as described in the wiki, but it does not modify the wave pitch appropriately. If anyone has had experience with it, please respond.
Re: FDS registers
by on (#149612)
What trouble are you having with the modulator?
Re: FDS registers
by on (#149620)
Some pitfalls I ran into when I got this working last week:

- The address into the modulator table is incremented after reading a value, not before. (This is very important with Bio Miracle Bokutte Upa because it resets the modulator value constantly and will result in weird pitch sweeps in that game if you get it wrong.)

- The modulator output is not added to the value of the pitch register and then put back into the pitch register; that would cause a feedback effect with the pitch rising or falling exponentially. Instead the modulator output is added to the wavetable counter at the same time as the pitch value.

- Make sure that the modulator counter wraps instead of saturating, and that you are sign-extending it correctly when storing in an 8 bit or larger variable. That would be
Code:
modCtr = (modCtr << 25) >> 25;
if in C with a 32 bit int.
Re: FDS registers
by on (#149630)
Grapeshot wrote:
- The modulator output is not added to the value of the pitch register and then put back into the pitch register; that would cause a feedback effect with the pitch rising or falling exponentially. Instead the modulator output is added to the wavetable counter at the same time as the pitch value.


This sounds like the issue. I'll do some experimentation and let you know. Thanks for your help.

By the way, for the low-pass single-pole filter, did you actually use a cutoff frequency of 2000 Hz ?

Edit: I made a change based on your suggestion and the result it closer, but it's still not there. Could I take a peek at your code? :)
Re: FDS registers
by on (#149633)
This is my implementation of the FDS audio:
https://code.google.com/p/nsfplay/source/browse/trunk/xgm/devices/Sound/nes_fds.h
https://code.google.com/p/nsfplay/source/browse/trunk/xgm/devices/Sound/nes_fds.cpp
Re: FDS registers
by on (#149636)
rainwarrior wrote:


This is quite helpful. Thanks.
Re: FDS registers
by on (#149651)
With that source code as a guide, I fixed my modulator unit implementation. Most of my code was correct. The issue was really sorting out what triggers what. The wiki provides information on the pieces, but it is often unclear on how to put those pieces together properly.

Grapeshot wrote:
Code:
modCtr = (modCtr << 25) >> 25;


That's a clever trick that I took advantage of.

Rainwarrior, I found it interesting that you combined the accumulators with the wave indices into a single phase variable to make the frequency addition overflow automatically update the indices. But, I decided to keep the variables separated for clarity.

Also, on lines 345 and 347, val is AND'ed with 0x7F. It's unclear why 0x07 is not used.

On line 372, I think it should read adr <= 0x407F, not adr < 0x407F.

On line 63, you chose to use the continuous-time formula to compute the smoothing factor. The discrete version produces a slightly different value. But, this is pretty irrelevant. In fact, I chose an arbitrary smoothing factor based on what I thought sounded right.
Re: FDS registers
by on (#149654)
zeroone wrote:
Rainwarrior, I found it interesting that you combined the accumulators with the wave indices into a single phase variable to make the frequency addition overflow automatically update the indices. But, I decided to keep the variables separated for clarity.

I don't think of that as a trick or anything. I'm pretty sure most wavetable hardware does it this way internally. You have a digital phase accumulator, where some of its high bits address the wavetable, and the lower bits just act as a fixed point precision for the pitch. Why would you want to separate them? They're always updated together in one step.

zeroone wrote:
Also, on lines 345 and 347, val is AND'ed with 0x7F. It's unclear why 0x07 is not used.

On line 372, I think it should read adr <= 0x407F, not adr < 0x407F.

On line 63, you chose to use the continuous-time formula to compute the smoothing factor. The discrete version produces a slightly different value. But, this is pretty irrelevant. In fact, I chose an arbitrary smoothing factor based on what I thought sounded right.

Those are both bugs. Thanks. I'll fix them. I probably didn't notice the mistake because there aren't any games that attempt to read back the wavetable, and I don't think anything writes the mod-table with out of bounds values either, but that could do some crummy things. :S

As for the RC filter implementation, I'll look into that. It's not important for emulation (the real FDS filter isn't a simple lowpass, this is just an approximation that is similar), but if the cutoff is inaccurate vs. the specified value it would be good to improve them. I think the calculations I used were taken from an old DSP reference book that I like, but I don't mind taking another look at the theory.
Re: FDS registers
by on (#149663)
rainwarrior wrote:
I don't think of that as a trick or anything. I'm pretty sure most wavetable hardware does it this way internally. You have a digital phase accumulator, where some of its high bits address the wavetable, and the lower bits just act as a fixed point precision for the pitch. Why would you want to separate them? They're always updated together in one step.


First of all, I just want to say that your code is brilliant and I greatly appreciate that you made it readily available to help other developers like myself. I hope that the details eventually makes it into the wiki.

I agree that it is likely that the hardware uses a combined register and that a single addition effectively updates both values. But, there are several instances of reads and writes to just the wavetable index bits requiring shifts and masks:

Code:
UINT32 start_pos = phase[TMOD] >> 16;
phase[TMOD] += (clocks * freq[TMOD]);
UINT32 end_pos = phase[TMOD] >> 16;

phase[TMOD] = phase[TMOD] & 0x3FFFFF;

phase[TMOD] = mod_write_pos << 16;

phase[TMOD] = phase[TMOD] & 0x3F0000;

wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x7F;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x7F;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
mod_write_pos = phase[TMOD] >> 16;

fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out;


In other words, combining them in code simplifies the update. But, everywhere else, it is difficult to use, in my humble opinion. So, I opted to break it up.
Re: FDS registers
by on (#149669)
Heh, fair enough, I suppose.

Actually, I've kinda wondered about the internal mod table application, actually. I guess it's actually clocked by the carry. It doesn't necessarily have to store an index; the mod table might be some kind of bucket-brigade? Since there's actually no way to externally reset or determine the mod table index, it's kind of unclear whether it really exists.

I tried to keep the mod table implementation mostly parallel to the wave table, since in most other ways they work the same way. (To a lesser extent, it's also parallel to the N163.)
Re: FDS registers
by on (#149707)
Here's a link to FDS audio emulation comparisons:

https://www.youtube.com/watch?v=C2fIJFhh1sc

How close is the model described in the wiki to reality?
Re: FDS registers
by on (#149719)
The wiki should describe the best known knowledge at the moment. I revised it as I did my own reverse engineering work on it last year.

NSFPlay's emulation is very close to the authentic sound. I don't have any test cases that would show a significant difference, except the discontinuous-DAC issue (which is mentioned on the Wiki, and I will add an option for in the future).

As for that video comparing the state of emulators circa 2009, I don't have much to say about them. Most emulators have only OK sound emulation. Nestopia was usually one of the better ones.