FDS audio

From NESdev Wiki
Jump to navigationJump to search

The Famicom Disk System audio is an audio channel generated by the 2C33 chip on the Famicom Disk System's RAM card and output through the Famicom cart edge connector's expansion audio pins.

This channel rapidly repeats a wavetable set up by the CPU in a manner similar to channel 3 of the Game Boy but with more sophisticated modulation. By changing the waveform, the program can have it simulate many different instruments, such as in the musical game Otocky.


Master I/O enable ($4023)

This register must be written to with bit 1 set for the sound registers to function. The FDS bios initializes this by writing $00 followed by $83 to it.

7  bit  0
xxxx xxSD
       |+- Enable disk I/O registers
       +-- Enable sound I/O registers

Wavetable RAM ($4040-$407F)

The 64-step waveform to be fed to the DAC. Each step consists of an unsigned sample value in the range [0, 63]. However, it cannot be modified unless it is write-enabled, and it cannot be write-enabled while the sound is being played. When writing is disabled ($4089.7), reading anywhere in 4040-407F returns the value at the current wave position. (See also $4089 below.)

7  bit  0  (read/write)
---- ----
|||| ||||
||++-++++- Sample
++-------- Returns 01 on read, likely from open bus

Volume envelope ($4080)

The envelope speed is set by this register whether or not the envelope is enabled by the high bit, but the current volume is set only if the high bit is set.

The volume gain can range from 0 to 63; however, volume values above 32 are clamped to 32 before output.

Changes to the volume envelope only take effect while the wavetable pointer (top 6 bits of wave accumulator) is 0. The volume envelope is basically a PWM unit, so apparently (wave_addr==0) is its comparator latch signal.

Writing to this register immediately resets the clock timer that ticks the volume envelope (delaying the next tick slightly).

7  bit  0  (write; read through $4090)
---- ----
|||| ||||
||++-++++- (M=0) Volume envelope speed
||         (M=1) Volume gain and envelope speed.
|+-------- Volume change direction (0: decrease; 1: increase)
+--------- Volume envelope mode (0: on; 1: off)

Frequency low ($4082)

7  bit  0  (write)
---- ----
|||| ||||
++++-++++- Bits 0-7 of frequency

Frequency high ($4083)

The high bit of this register halts the waveform and resets its phase to 0. Note that if halted it will output the constant value at $4040, and writes to the volume register $4080 or master volume $4089 will affect the output. The envelopes are not ticked while the waveform is halted.

Bit 6 halts just the envelopes without halting the waveform, and also resets both of their timers.

7  bit  0  (write)
---- ----
||   ||||
||   ++++- Bits 8-11 of frequency
|+-------- Disable volume and sweep envelopes (but not modulation)
+--------- When enabled, envelopes run 4x faster. Also stops the mod table accumulator.

Mod envelope ($4084)

The envelope speed is set by this register whether or not the envelope is enabled by the high bit, but the current mod gain is set only if the high bit is set.

Writing to this register immediately resets the clock timer that ticks the modulator envelope (delaying the next tick slightly).

7  bit  0  (write; read through $4092)
---- ----
|||| ||||
||++-++++- (M=0) Mod envelope speed
||         (M=1) Mod gain and envelope speed.
|+-------- Mod envelope direction (0: decrease; 1: increase)
+--------- Mod envelope mode (0: on; 1: off)

Mod counter ($4085)

This directly sets the 7-bit signed modulator counter that is otherwise controlled by the mod unit.

Because the current playback position of the modulator unit is generally hard to predict while active, it is bad practice to write $4085 unless the mod unit is disabled via $4087, because it will generally result in a detuned note. Bio Miracle Bokutte Upa does this, and it requires cycle-accurate timing to emulate correctly. Some emulators incorrectly treat $4085 as a phase-reset for the mod table, which will obviate this timing issue.

It is generally good practice to write 0 to $4085 to reset the counter after writing the mod table via $4088.

7  bit  0  (write)
---- ----
 ||| ||||
 +++-++++- Mod counter (7-bit signed; minimum $40; maximum $3F)

Mod frequency low ($4086)

If the 12-bit frequency is set to 0, mod table counter is stopped. The freq mod formula of the modulation unit is always in effect, $4084/$4085 still modify the wave frequency.

7  bit  0  (write)
---- ----
|||| ||||
++++-++++- Bits 0-7 of modulation unit frequency

Mod frequency high ($4087)

Setting the high bit of this register halts the mod unit, and allows the mod table to be written via $4088.

Disabling the resets its timer accumulator, delaying the first tick after re-enabling.

7  bit  0  (write)
---- ----
||   ||||
||   ++++- Bits 8-11 of modulation frequency
|+-------- Force a carry out from bit 11 of mod accumulator. Step every clock.
+--------- Halt mod table counter. (Freq mod table of modulation unit is always in effect.)

On a carry out from bit 11 of the mod, update the mod counter (increment $4085 with mod table).

Mod table write ($4088)

This register has no effect unless the mod unit is disabled via the high bit of $4087.

The mod table is a ring buffer containing 32 entries. Writing to this register replaces an entry at the current mod table playback position with the written value, then advances the playback position to the following entry.

The position of the mod table actually has 64 steps, but the least significant bit is not used to index the 32 entry table. Each entry will get applied twice as the mod table is stepped through.

Writing to this register 32 times will effectively reset the phase of the mod table, having advanced the playback position back to its starting point. You should normally always write all 32 entries at once, since the starting write position is not easily predictable.

Writing $4088 also increments the address (bits 13-17 of wave accumulator) when $4087.7=1.

7  bit  0  (write)
---- ----
xxxx xMMM
      +++- Modulation input

Wave write / master volume ($4089)

When the high bit is set, the current waveform output is held at its current level until the bit is cleared again. During this time, the wave unit will continue to run, even though the output level is held.

7  bit  0  (write)
---- ----
Wxxx xxVV
|      ||
|      ++- Master volume (0: full; 1: 2/3; 2: 2/4; 3: 2/5)
|          Output volume = current volume (see $4080 above) * master volume
+--------- Wavetable write enable
           (0: write protect RAM; 1: write enable RAM and hold channel)

Envelope speed ($408A)

This sets a clock multiplier for the volume and modulator envelopes. Few FDS NSFs write to this register. The BIOS initializes this to $E8.

7  bit  0  (write)
---- ----
|||| ||||
++++-++++- Sets speed of volume envelope and sweep envelope
           (0: disable them)

Volume gain ($4090)

7  bit  0  (read; write through $4080)
---- ----
|||| ||||
||++-++++- Current volume gain level
++-------- Returns 01 on read, likely from open bus

Wave accumlator ($4091)

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

Mod gain ($4092)

7  bit  0  (read; write through $4084)
---- ----
|||| ||||
||++-++++- Current mod gain level
++-------- Returns 01 on read, likely from open bus

Mod table address accumulator ($4093)

7  bit  0  (read)
---- ----
|||| ||||
|+++-++++- Bits 5-11 of the modtable address accumulator
+--------- Returns 0 on read, likely from open bus

Mod counter*gain result ($4094)

The mod unit uses a sequential multiplier. By reading $4094 at different times, you can see the result.

7  bit  0  (read)
---- ----
|||| ||||
++++-++++- Bits 4-11 of mod counter*gain intermediate result.

Mod counter increment ($4095)

This shows the mod table 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.

7  bit  0  (read)
---- ----
???? MMMM
|||| ||||
|||| ++++- Next mod counter ($4085) increment.
++++------ Unknown counter

Wavetable value ($4096)

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

Mod counter value ($4097)

7  bit  0  (read)
---- ----
|||| ||||
|+++-++++- Current mod counter ($4085) value
+--------- Returns 0 from read, likely from open bus.

Frequency calculation and timing


The volume and modulator envelopes tick once after a specific number of CPU clocks, calculated in the following way:

c = CPU clocks per tick
e = envelope speed ($4080/4084)
m = master envelope speed ($408A)

c =  8 * (e + 1) * (m + 1)

To determine the frequency:

f = frequency of tick
n = CPU clock rate (≈1789773 Hz)

f = n / c

Writing the envelope control registers for these units will reset the timer for the respective unit, meaning that it will next tick c cycles after the write.


The wave output and modulator accumulate a 16-bit phase value by adding the current 12-bit pitch value ($4082/$4083 or $4086/$4087) once per CPU clock. When the accumulator overflows, the table output position will advance by one.

f = frequency of tick
n = CPU clock rate (≈1789773 Hz)
p = current pitch value ($4082/$4083 or $4086/$4087) plus modulation if wave output
f = n * p / 65536

Note that to determine the frequency of the entire waveform, divide this result by 64 (the length of the wavetable).

The pitch value for the wave output not simply the 12-bit value from registers $4082/$4083, but is modified by the modulator counter in an obtuse way. See below for details.

Disabling either unit via the high bit of $4083/$4087 immediately resets the accumulator, delaying the next tick after they are enabled again until the next overflow. For the wave output, disabling the unit also resets the wave position to 0 (i.e. the $4040 value).

Unit tick


If enabled, when the volume or mod table envelope is ticked by its timer, it will do one of the following based on $4080/$4084 bit 6:

  • Increase: if gain is less than 32, increase it by 1
  • Decrease: if gain is more than 0, decrease it by 1

Note that the gains manually can be set higher than 32, but they can no longer be increased at this point. The gain can still be decreased if above 32.

The volume gain's final output will always be clamped to 32, even though the internal gain can be higher. Otherwise, the volume and mod envelops are functionally identical.

Modulation unit

When the modulation unit is ticked, it applies the modulation value at the current position to the mod counter, then advances to the next position. Note that because values are written to the table in pairs, the same value will always be written twice on consecutive ticks.

Each 3-bit value in the mod table corresponds to one of the following adjustment of the mod counter when ticked:

0 = %000 -->  0
1 = %001 --> +1
2 = %010 --> +2
3 = %011 --> +4
4 = %100 --> reset to 0
5 = %101 --> -4
6 = %110 --> -2
7 = %111 --> -1

The mod counter is a signed 7-bit value, and will wrap if overflowed, i.e. 63 + 1 = -64 after wrap, and -64 - 1 = 63.

The value of the mod counter will modify the pitch of the wave output unit in a complicated way, described by the following C-style 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;

Note that the resulting pitch value can go higher than 4095 (it will not wrap to 12-bits), and if it goes lower than 0 it is presumably clamped to 0 (again, will not wrap).

Wave output unit

When this unit is ticked, it advances to the next position in its wave table.


The current wave output value is attenuated by the current volume gain and master volume. This output signal is affected by a filter that attenuates higher frequencies. This filter can be approximated as a 1-pole lowpass with a cutoff of ~2000Hz.

The maximum volume of the FDS signal on a Famicom is roughly 2.4x the maximum volume of the APU square, and the polarity is the same as the 2A03. On other machines, such as the Twin Famicom, the output may be significantly louder.

The DAC output of the waveform is 6-bits (0-63), and ideally should be linear, but recent tests have revealed jagged discontinuities at binary nodes. (More works needs to be done to measure this effect.)

The volume may be emulated simply as a linear volume control.