FDS modulator formula revision

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: FDS modulator formula revision

Post by lidnariq »

I think that curve from jsr's unit looks the same as (or at least very close to) the curve from rainwarrior's once the lowpass filter is removed.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: FDS modulator formula revision

Post by rainwarrior »

My recording was also running at a higher frequency (I hadn't made it with catching the DAC characteristics in mind, the only saw wave recording I had on hand was at 440Hz). If I ran it at a lower frequency it'd probably look a lot like jsr's. The errors do look like they're around the same place, and of similar magnitude.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: FDS modulator formula revision

Post by tepples »

Try a test that plays square waves [0 0 0 ... 0 1 1 1 ... 1], [1 1 1 ... 1 2 2 2 ... 2], through [62 62 62 ... 62 63 63 63 ... 63], with a period of 1000 Hz or so. Waveforms like that should coast through any audio-frequency band-pass filter, isolating the contribution of the DAC from the contribution of the analog band-pass that comes after it.
jsr
Posts: 42
Joined: Thu Oct 07, 2004 2:47 am
Contact:

Re: FDS modulator formula revision

Post by jsr »

I tested it on a higher frequency and yeah it looks more similar to rainwarrior's now.

Anyway it's a minor issue I guess, but it is audible on waves with low number of harmonics. NEZplug++ actually emulates this and the output is very similar to the measured ones.
User avatar
loopy
Posts: 405
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Re: FDS modulator formula revision

Post by loopy »

MEGA-NECRO-BUMP!
rainwarrior wrote:I've been making audio tests of my FDS over the last few days, and I've spent a lot of time trying to verify the currently used formula for modulation strength outlined in disch's old document (and currently on the wiki FDS audio page), which always struck me as a very odd formula, especially since disch himself had a disclaimer that he'd never peresonally tested the real hardware. I found it to be mostly correct, but requiring a few tweaks to get it to match the empirical data I gathered from my test recordings.
That modulator formula bugged me, so I did my own investigating. I found some differences from the wiki, and some new stuff. Probably none of this will affect emulation in any noticable way, but there are some interesting tidbits I thought worth documenting. Going down the list, looking at the wiki:

Wavetable RAM ($4040-$407F)
This can always be read by the CPU.
Sort of. When writing is disabled ($4089.7), reading anywhere in 4040-407F returns the value at the current wave position.

Volume envelope ($4080)
Changes to the volume envelope only take effect while the wavetable pointer is 0. Kinda goofy. The volume envelope is basically a PWM unit, so apparently (wave_addr==0) is its comparator latch signal.

Frequency high ($4083)
When 4083.7=1, envelopes run 4x faster (envelopes are NOT disabled). 4083.7 also stops the mod table accumulator.

Mod frequency ($4086/7)
(4086) If the 12-bit frequency is set to 0, modulation is disabled.
(4087) Setting the high bit of this register halts the mod unit
The mod table counter is stopped, that's all. The freq mod formula is ALWAYS in effect, 4084/4085 still modify the wave frequency.
4087 bit 6 forces the mod table to step every clock (more details below.)

Mod table write ($4088)
The double write thing always seemed weird to me. I'm pretty sure there are just 32 entries. Address LSB isn't used and each entry is stepped through twice (more details below.)

Frequency calculation and timing
This should be c = 8 * (e + 1) * (m + 1)

----
More registers! These don't have much practical use, clearly left in for hardware testing. They do come in handy for reverse engineering though ;)

$4091

Code: Select all

7  bit  0  (read)
---- ----
AAAA AAAA
|||| ||||
++++-++++- Bits 12-19 of the wavetable address accumulator
$4093

Code: Select all

7  bit  0  (read)
---- ----
0AAA AAAA
|||| ||||
|+++-++++- Bits 5-11 of the modtable address accumulator
+--------- 0 (open bus?)
$4094

Code: Select all

7  bit  0  (read)
---- ----
MMMM MMMM
|||| ||||
++++-++++- Bits 4-11 of mod counter*gain intermediate result.
The mod unit uses a sequential multiplier. By reading $4094 at different times, you can see it do its thing. Neat.


$4095

Code: Select all

7  bit  0  (read)
---- ----
???? MMMM
|||| ||||
|||| ++++- Next mod counter (4085) increment.
++++------ Looks like some counter, maybe.
This shows the modtable contents at its current position, translated to mod counter increment value (0,1,2,3,4,5,6,7 ==> 0,1,2,4,C,C,E,F). In other words, what will be added to the counter (sign extend to 7 bits) on the next address tick.


$4096

Code: Select all

7  bit  0  (read)
---- ----
01VV VVVV
|||| ||||
||++-++++- Value at current wavetable position, masked by PWM from volume envelope. (What's being fed to the DAC, probably.)
++-------- 01 (open bus?)
$4097

Code: Select all

7  bit  0  (read)
---- ----
0CCC CCCC
|||| ||||
|+++-++++- Current mod counter (4085) value
+--------- 0 (open bus?)
-------
Wavetables
Wave (4082/3) and mod (4086/7) accumulators aren't as similar as they seem like they should be.

Code: Select all

          ++++-+++-------- 4093 read
          |||| |||
[ AAAAA a FFFF ffff ffff ]  Mod accumulator
  ||||| | |||| |||| ||||
  ||||| | |||| ++++-++++-- +freq low (4086[7:0]) add
  ||||| | ++++------------ +freq hi (4087[3:0]) add
  ||||| +----------------- "ghost" modtable address bit(0) makes mod unit step thru each entry twice
  +++++------------------- modtable address
(Obviously there are a number of ways this can be implemented but this is how I think of it conceptually, looking at it from a hardware point of view)
  • - Mod accumulator is 18 bits, updated every 16 M2 clocks.
    - I'm guessing there's a 4-bit free running counter, held in reset by 4083.7 (haven't fully analyzed this yet).
    - When it overflows, add the mod frequency [4086/7] to accumulator.
    - On a carry out from bit 11, update the mod counter (increment $4085 with modtable).
    - 4087.6 forces a carry out from bit 11.
    - Bits 13-17 are the modtable address.
    - Bits 0-12 are reset by 4087.7=1. Bits 13-17 have no reset.
    - Writing $4088 increments the address (bits 13-17) when 4087.7=1.

Finally circling back to your original question:
rainwarrior wrote:

Code: Select all

// pitch   = $4082/4083 (12-bit unsigned pitch value)
// counter = $4085 (7-bit signed mod counter)
// gain    = $4084 (6-bit unsigned mod gain)

// 1. multiply counter by gain, lose lowest 4 bits of result but "round" in a strange way
temp = counter * gain;
remainder = temp & 0xF;
temp >>= 4;
if ((remainder > 0) && ((temp & 0x80) == 0))
{
    if (counter < 0) temp -= 1;
    else temp += 2;
}

// 2. wrap if a certain range is exceeded
if (temp >= 192) temp -= 256;
else if (temp < -64) temp += 256;

// 3. multiply result by pitch, then round to nearest while dropping 6 bits
temp = pitch * temp;
remainder = temp & 0x3F;
temp >>= 6;
if (remainder >= 32) temp += 1;

// final mod result is in temp
wave_pitch = pitch + temp;
I will add this to the wiki in a few days, as I am still doing another round of tests at the moment. In the meantime, is there anybody here who can independently verify this? I can detail the methods I used to determine this if anyone's interested, but I would appreciate verification by a different experiment if possible.
Close, not quite. I simplified it a lot more.

Code: Select all

// pitch   = $4082/4083 (12-bit unsigned pitch value)
// counter = $4085 (7-bit signed mod counter)
// gain    = $4084 (6-bit unsigned mod gain)

temp = counter * gain;
if((temp & 0x0f) && !(temp & 0x800))
    temp += 0x20;
temp += 0x400;
temp = (temp >> 4) & 0xff;
wave_pitch = pitch * temp;      //20-bit unsigned result
Note the lowest 6 bits are NOT dropped. Yeah, that means it's possible to get a ridiculously low frequency (e.g. pitch=1, counter=50, gain=62 gives about 0.4Hz wave tick rate). I verified using the test registers to tease out the full wave_pitch result for all combinations of inputs, so I'm pretty darn sure it's right.

Code: Select all

      ++++++++--------------- 4091 read (bits 12-19)
      ||||||||
[ AAAAAAXXXXXXXXXXXXXXXXXX ]  Wave accumulator
  ||||||||||||||||||||||||
  ||||++++++++++++++++++++--- +wave_pitch (bits 0-19) add
  ||||||
  ++++++--------------------- wave address (bits 23-18)
  • - Wave accumulator is 24 bits.
    - Bits 23-18 are the wavetable address.
    - wave_pitch is added to accumulator every 16th M2 clock.
    - Accumulator is reset when 4083.7=1.
------
Sorry for the brain dump. And messy ascii art. It's overkill, but the Powerpak FDS mapper has 100% of this implemented now. You can get it at the usual place. I updated the NSF player with the important stuff fixed. Space is tight so I left out the more useless, esoteric parts. Again, probably none of this actually matters for emulators, FDS audio is already nailed down pretty well so ignore it if you like. But I had fun figuring this all out so I thought I'd share.
Rahsennor
Posts: 479
Joined: Thu Aug 20, 2015 3:09 am

Re: FDS modulator formula revision

Post by Rahsennor »

:!:

Wow, that's a lot of new info! My NSF player got left by the wayside when I got busy, but I'm aiming for 100% bit-and-cycle-perfect accuracy where possible so this'll be at the top of my list when I get back to it. Thanks a ton!
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: FDS modulator formula revision

Post by rainwarrior »

New registers are a very nice surprise! Wow those would have been helpful in the past. I have put all this on my list of stuff to review and implement for NSFPlay.


Reviewing the new modulator formula, I can see how some of it is close or the same, but some things I'm wondering about:

Is it missing the different rounding if counter < 0?

I think the -64 to 196 wrapping is still applied by the + 0x400 and subsequent & mask, right?

What happens at the end? Is the "temp" calculation no longer an offset, and now a multiplier?


I take a closer look later though, so I'll answer these for myself eventually, but it's great to get some new input on all this stuff!
User avatar
loopy
Posts: 405
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Re: FDS modulator formula revision

Post by loopy »

Is it missing the different rounding if counter < 0?
It's not needed. *shrug*
I think the -64 to 196 wrapping is still applied by the + 0x400 and subsequent & mask, right?
Yeah.
What happens at the end? Is the "temp" calculation no longer an offset, and now a multiplier?
Yeah. A little math shows how that works out...

Code: Select all

wave_pitch = pitch + (pitch*temp)>>6       (old formula)
           = pitch * (1+temp>>6)
           = pitch * (0x40+temp)>>6
           = pitch * (0x40+temp)           (no shift, we keep the low bits now)
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: FDS modulator formula revision

Post by rainwarrior »

loopy wrote:
Is it missing the different rounding if counter < 0?
It's not needed. *shrug*
Yeah I'm happy to see it eliminated, though I definitely put it in there to match the measurements I'd made. I'm hoping that this was somehow an accidental surrogate for the missing bits at the bottom. It seemed strange, but it was the best model I managed to come up with at the time.
User avatar
loopy
Posts: 405
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Re: FDS modulator formula revision

Post by loopy »

rainwarrior wrote:I'm hoping that this was somehow an accidental surrogate for the missing bits at the bottom. It seemed strange, but it was the best model I managed to come up with at the time.
Could be. Everything matches up when I look at it with a 'scope, I'm fairly confident it's right. How were you doing your measurements?
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: FDS modulator formula revision

Post by rainwarrior »

loopy wrote:How were you doing your measurements?
Well, it's been many years, and I can't find the actual work at the moment. (I really wish I'd been better at documenting that part of it at the time.)

I would have written a test program with various permutations of settings, and then recorded the audio and measured the wavelengths produced. Would have then compared various models against the data.

Doesn't matter though, I'll do new tests to verify this anyway. I'm mostly just curious to figure out what led me to put that in (though apparently it was in the previous formula, but I think I tried ideas with and without it).
Post Reply