APU Sweep: Difference between revisions
(→Updating the period: Zero before reload does the same thing whether reload is set or clear. Thus it can be factored out into a separate step.) |
(→Updating the period: enumerate things a little more clearly, fix DRY, move enumeration in) |
||
(12 intermediate revisions by 3 users not shown) | |||
Line 2: | Line 2: | ||
An [[APU|NES APU]] sweep unit can be made to periodically adjust a [[APU Pulse|pulse channel's]] period up or down. | An [[APU|NES APU]] sweep unit can be made to periodically adjust a [[APU Pulse|pulse channel's]] period up or down. | ||
Each sweep unit contains the following: [[APU Misc|divider]] | Each sweep unit contains the following: | ||
*[[APU Misc|divider]] | |||
*reload flag | |||
== Registers == | |||
{| class="tabular" | {| class="tabular" | ||
Line 11: | Line 15: | ||
| bit 7 || <tt>E--- ----</tt> || Enabled flag | | bit 7 || <tt>E--- ----</tt> || Enabled flag | ||
|- | |- | ||
| bits 6-4 || <tt>-PPP ----</tt> || The divider's period is | | bits 6-4 || <tt>-PPP ----</tt> || The divider's period is P + 1 half-frames | ||
|- | |- | ||
| bit 3 || <tt>---- N---</tt> || Negate flag<br>0: add to period, sweeping toward lower frequencies<br>1: subtract from period, sweeping toward higher frequencies | | bit 3 || <tt>---- N---</tt> || Negate flag<br>0: add to period, sweeping toward lower frequencies<br>1: subtract from period, sweeping toward higher frequencies | ||
|- | |- | ||
| bits 2-0 || <tt>---- -SSS</tt> || Shift count (number of bits) | | bits 2-0 || <tt>---- -SSS</tt> || Shift count (number of bits).<br>If SSS is 0, then behaves like E=0. | ||
|- | |- | ||
|colspan=2| Side effects || Sets the reload flag | |colspan=2| Side effects || Sets the reload flag | ||
Line 22: | Line 26: | ||
== Calculating the target period == | == Calculating the target period == | ||
The sweep unit continuously calculates each channel's '''target period''' in this way: | The sweep unit continuously calculates each pulse channel's '''target period''' in this way: | ||
# A barrel shifter shifts the channel's 11-bit [[APU Pulse|raw timer period]] right by the shift count, producing the change amount. | # A barrel shifter shifts the pulse channel's 11-bit [[APU Pulse|raw timer period]] right by the shift count, producing the change amount. | ||
# If the negate flag is true, the change amount is made negative. | # If the negate flag is true, the change amount is made negative. | ||
# The target period is the sum of the current period and the change amount. | # The target period is the sum of the current period and the change amount, clamped to zero if this sum is negative. | ||
For example, if the negate flag is false and the shift amount is zero, the change amount equals the current period, making the target period equal to twice the current period. | For example, if the negate flag is false and the shift amount is zero, the change amount equals the current period, making the target period equal to twice the current period. | ||
Line 33: | Line 37: | ||
* Pulse 2 adds the [[wikipedia:Two's complement|two's complement]] (''−c''). Making 20 negative produces a change amount of −20. | * Pulse 2 adds the [[wikipedia:Two's complement|two's complement]] (''−c''). Making 20 negative produces a change amount of −20. | ||
Whenever the current period changes | Whenever the current period or sweep setting changes, whether by $400x writes or by sweep updating the period, the target period also changes. | ||
== Muting == | == Muting == | ||
'''Muting''' means that the pulse channel sends 0 to the [[APU Mixer|mixer]] instead of the current volume. | |||
Muting | Muting happens regardless of whether the sweep unit is disabled (because either the Enabled flag or the Shift count are zero) and regardless of whether the sweep divider is outputting a clock signal. | ||
If at any time the ''target'' period is greater than $7FF, the channel | Two conditions cause the sweep unit to mute the channel: | ||
# If the ''current'' period is less than 8, the sweep unit mutes the channel. | |||
# If at any time the ''target'' period is greater than $7FF, the sweep unit mutes the channel. | |||
In particular, if the negate flag is false, the shift count is zero, and the current period is at least $400, the target period will be large enough to mute the channel. | In particular, if the negate flag is false, the shift count is zero, and the current period is at least $400, the target period will be large enough to mute the channel. | ||
(This is why several publishers' NES games never seem to use the bottom octave of the pulse waves.) | (This is why several publishers' NES games never seem to use the bottom octave of the pulse waves.) | ||
Because the target period is computed continuously, a target period overflow from the sweep unit's adder can silence a channel ''even when the | Because the target period is computed continuously, a target period overflow from the sweep unit's adder can silence a channel ''even when the sweep unit is disabled'' and even when the sweep divider is not outputting a clock signal. | ||
Thus to fully disable the sweep unit, a program must | Thus to fully disable the sweep unit, a program must additionally turn on the Negate flag, 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 ensures that the target period is not greater than the current period and therefore not greater than $7FF. | ||
== Updating the period == | == Updating the period == | ||
When the [[APU Frame Counter|frame counter]] sends a half-frame clock (at 120 or 96 Hz), two things happen | When the [[APU Frame Counter|frame counter]] sends a half-frame clock (at 120 or 96 Hz), two things happen: | ||
# If the divider's counter is zero, the sweep is enabled, the shift count is nonzero, | |||
## and the sweep unit is ''not'' muting the channel: The pulse's period is set to the target period. | |||
## and the sweep unit ''is'' muting the channel: the pulse's period remains unchanged, but the sweep unit's divider continues to count down and reload the divider's period as normal. | |||
# If the divider's counter is zero ''or'' the reload flag is true: The divider counter is set to P and the reload flag is cleared. Otherwise, the divider counter is decremented. | |||
If the shift count is zero, the channel's period is never updated, but muting logic still applies. | If the sweep unit is disabled including because the shift count is zero, the pulse channel's period is never updated, but muting logic still applies. |
Latest revision as of 21:23, 8 February 2024
An NES APU sweep unit can be made to periodically adjust a pulse channel's period up or down.
Each sweep unit contains the following:
- divider
- reload flag
Registers
$4001 | EPPP.NSSS | Pulse channel 1 sweep setup (write) |
$4005 | EPPP.NSSS | Pulse channel 2 sweep setup (write) |
bit 7 | E--- ---- | Enabled flag |
bits 6-4 | -PPP ---- | The divider's period is P + 1 half-frames |
bit 3 | ---- N--- | Negate flag 0: add to period, sweeping toward lower frequencies 1: subtract from period, sweeping toward higher frequencies |
bits 2-0 | ---- -SSS | Shift count (number of bits). If SSS is 0, then behaves like E=0. |
Side effects | Sets the reload flag |
Calculating the target period
The sweep unit continuously calculates each pulse channel's target period in this way:
- A barrel shifter shifts the pulse channel's 11-bit raw timer period right by the shift count, producing the change amount.
- If the negate flag is true, the change amount is made negative.
- The target period is the sum of the current period and the change amount, clamped to zero if this sum is negative.
For example, if the negate flag is false and the shift amount is zero, the change amount equals the current period, making the target period equal to twice the current period.
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.
Whenever the current period or sweep setting changes, whether by $400x writes or by sweep updating the period, the target period also changes.
Muting
Muting means that the pulse channel sends 0 to the mixer instead of the current volume. Muting happens regardless of whether the sweep unit is disabled (because either the Enabled flag or the Shift count are zero) and regardless of whether the sweep divider is outputting a clock signal.
Two conditions cause the sweep unit to mute the channel:
- If the current period is less than 8, the sweep unit mutes the channel.
- If at any time the target period is greater than $7FF, the sweep unit mutes the channel.
In particular, if the negate flag is false, the shift count is zero, and the current period is at least $400, the target period will be large enough to mute the channel. (This is why several publishers' NES games never seem to use the bottom octave of the pulse waves.)
Because the target period is computed continuously, a target period overflow from the sweep unit's adder can silence a channel even when the sweep unit is disabled and even when the sweep divider is not outputting a clock signal. Thus to fully disable the sweep unit, a program must additionally turn on the Negate flag, such as by writing $08. This ensures that the target period is not greater than the current period and therefore not greater than $7FF.
Updating the period
When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen:
- If the divider's counter is zero, the sweep is enabled, the shift count is nonzero,
- and the sweep unit is not muting the channel: The pulse's period is set to the target period.
- and the sweep unit is muting the channel: the pulse's period remains unchanged, but the sweep unit's divider continues to count down and reload the divider's period as normal.
- If the divider's counter is zero or the reload flag is true: The divider counter is set to P and the reload flag is cleared. Otherwise, the divider counter is decremented.
If the sweep unit is disabled including because the shift count is zero, the pulse channel's period is never updated, but muting logic still applies.