APU Pulse
The NES APU pulse (square) channels generate a pulse wave with variable pulse width.
Sweep Unit ---> Timer
| |
| |
| v
| Sequencer Length Counter
| | |
| | |
v v v
Envelope -------> Gate -----> Gate -------> Gate ---> (to mixer)
Register $4000, $4001, $4002 and $4003 correspond to the first Pulse channel, $4004, $4005, $4006, $4007 correspond to the second Pulse channel.
7 bit 0
---- ----
DDLC VVVV
|||| ||||
|||| ++++- When C is set: channel volume value
|||| When C is clear: envelope divider reload value
|||+------ Constant volume flag (override envelope with V)
||+------- Length counter halt flag and envelope loop flag
++-------- Pulse width
0 = 12.5%
1 = 25.0%
2 = 50.0%
3 = 75.0%
7 bit 0
---- ----
EPPP NSSS
|||| ||||
|||| |+++- Bit shift count
|||| +---- Negate flag
|||| 0: add to period, sweeping toward lower frequencies
|||| 1: subtract from period, sweeping toward higher frequencies
|+++------ Clock rate
+--------- Sweep enable flag
Writing to this register also sets sweep reload flag (the write does not have immediate effect, see below).
Each of the two pulse channels contain sweep units, which can automatically change the period by up or down without needing CPU’s help to calculate it.
Whenever the current period changes (either from the period register writes or update from the sweep unit), the target period is also calculated immediately, in this way:
- The change amount is calculated by shifting the current period’s bits right by the shift count (
Sbits). Becomes negative if the negate flag (Nbit) is set. - The target period is the sum of the current period and the change amount, clamped to zero if this sum is negative.
- The two pulse channels have their adders’ carry inputs wired differently, which produces different results when each channel’s change amount is made negative:
- Pulse 1 adds the ones’ complement (−c − 1). Making 20 negative produces a change amount of −21.
- Pulse 2 adds the two’s complement (−c). Making 20 negative produces a change amount of −20.
- The two pulse channels have their adders’ carry inputs wired differently, which produces different results when each channel’s change amount is made negative:
- If the target period is larger than $7FF, it silences the channel.
- This muting happens regardless of the state of the sweep unit. Meaning, even if sweep unit isn’t enabled and the current period itself does’t exceed $7FF, the sweep unit mutes the channel if the calculated result overflows $7FF. Thus to fully disable the sweep unit, a program must additionally turn on the negate flag (
Nbit), such as by writing $08. This ensures that the target period is not greater than the current period and therefore not greater than $7FF.
- This muting happens regardless of the state of the sweep unit. Meaning, even if sweep unit isn’t enabled and the current period itself does’t exceed $7FF, the sweep unit mutes the channel if the calculated result overflows $7FF. Thus to fully disable the sweep unit, a program must additionally turn on the negate flag (
When the frame counter generates a half-frame clock (at ≈120 (NTSC) or ≈96Hz (PAL)), following occur:
- If-
- If the sweep clock counter is zero or if the sweep reload flag is set, the counter is reloaded to
Pand the sweep reload flag is cleared. Otherwise, the counter is decremented.
7 bit 0
---- ----
TTTT TTTT
|||| ||||
++++-++++- Lower 8 bits of timer period value
7 bit 0
---- ----
LLLL LTTT
|||| ||||
|||| |+++- Upper 3 bits of timer period value
++++-+---- Length counter load value
Writing to this register also resets the sequencer index (i.e., the pulse phase) to the first value, restart envelope.
The sequencer is clocked by a timer where the period is the current 11-bit period value plus one. Given “t” as the timer value, this timer is updated every APU cycles (i.e., every second CPU cycles), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t.
Since the period of the timer is t+1 APU cycles and the sequencer has 8 steps, the period of the waveform is 8*(t+1) APU cycles, or equivalently 16*(t+1) CPU cycles.
With:
- fCPU = the clock rate of the CPU
- tval = the current period value
- fpulse = the frequency of the wave generated by this channel
Resulting:
- fpulse = fCPU/(16*(tval+1))
- tval = fCPU/(16*fpulse) - 1
Note: A period of t < 8, either set explicitly or via a sweep period update, silences the corresponding pulse channel.
| Duty | Output waveform |
|---|---|
| 0 | 0 1 0 0 0 0 0 0 (12.5%) |
| 1 | 0 1 1 0 0 0 0 0 (25%) |
| 2 | 0 1 1 1 1 0 0 0 (50%) |
| 3 | 1 0 0 1 1 1 1 1 (25% negated) |
Notice that a few Famiclone units have swapped APU duty cycles, works as if the meaning of the two V bits are swapped each other.
The reason for the odd output from the sequencer is that the counter is initialized to zero but counts downward rather than upward. Thus it reads the sequence lookup table in the order 0, 7, 6, 5, 4, 3, 2, 1.
| Duty | Sequence lookup table | Output waveform |
|---|---|---|
| 0 | 0 0 0 0 0 0 0 1 | 0 1 0 0 0 0 0 0 (12.5%) |
| 1 | 0 0 0 0 0 0 1 1 | 0 1 1 0 0 0 0 0 (25%) |
| 2 | 0 0 0 0 1 1 1 1 | 0 1 1 1 1 0 0 0 (50%) |
| 3 | 1 1 1 1 1 1 0 0 | 1 0 0 1 1 1 1 1 (25% negated) |
The mixer receives the pulse channel’s current envelope volume (lower 4 bits from $4000 / $4004) except when
- the sequencer output is zero, or
- overflow from the sweep unit’s adder is silencing the channel, or
- the length counter is zero, or
- the timer value is less than eight (t<8, noted above).
If any of the above are true, then the pulse channel sends zero (silence) to the mixer.
| V | | |
|---|---|
| Concepts | Registers | Basics | Period table |
| Channels | Pulse 1 and Pulse 2 | Triangle | Noise | DMC |
| Components | Envelope | Sweep | Frame Counter | Length Counter | DMA | Mixer |
| Expansion audio | 2C33 (Disk System) | MMC5 | VRC6 | VRC7 | Namco 163 | Sunsoft 5B | µPD7756 |