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 timingThis 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:**

7 bit 0 (read)

---- ----

AAAA AAAA

|||| ||||

++++-++++- Bits 12-19 of the wavetable address accumulator

**$4093****Code:**

7 bit 0 (read)

---- ----

0AAA AAAA

|||| ||||

|+++-++++- Bits 5-11 of the modtable address accumulator

+--------- 0 (open bus?)

**$4094****Code:**

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:**

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:**

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:**

7 bit 0 (read)

---- ----

0CCC CCCC

|||| ||||

|+++-++++- Current mod counter (4085) value

+--------- 0 (open bus?)

-------

WavetablesWave (4082/3) and mod (4086/7) accumulators aren't as similar as they seem like they should be.

**Code:**

++++-+++-------- 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:**

// 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:**

// 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:**

++++++++--------------- 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.