| 1 | package com.rox.apu; | |
| 2 | ||
| 3 | /** | |
| 4 | * NES pulse-channel sweep unit ($4001/$4005). Periodically retunes the channel's timer period up | |
| 5 | * or down, clocked once per half-frame. Not shared with other channel types.<br/> | |
| 6 | * <br/> | |
| 7 | * Pulse 1 and pulse 2 negate differently when computing the target period<br/> | |
| 8 | * <ul> | |
| 9 | * <li>one's-complement for pulse 1 <code>period - change - 1</code></li> | |
| 10 | * <li>two's-complement for pulse 2 <code>period - change</code></li> | |
| 11 | * </ul> | |
| 12 | * an asymmetry that exists so both channels mute at the same frequency; which mode applies is fixed per | |
| 13 | * instance via the constructor, not part of the register write. | |
| 14 | */ | |
| 15 | public class Sweep { | |
| 16 | private static final int ENABLED_BIT_MASK = 0x80; | |
| 17 | private static final int PERIOD_MASK = 0x07; | |
| 18 | private static final int NEGATE_BIT_MASK = 0x08; | |
| 19 | private static final int SHIFT_MASK = 0x07; | |
| 20 | private static final int PERIOD_SHIFT = 4; | |
| 21 | private static final int MIN_UNMUTED_PERIOD = 8; | |
| 22 | private static final int MAX_UNMUTED_TARGET_PERIOD = 0x7FF; | |
| 23 | ||
| 24 | private final boolean onesComplementNegate; | |
| 25 | ||
| 26 | private boolean enabled; | |
| 27 | private int periodReload; | |
| 28 | /** which direction to sweet in: 0 = positive, 1 = negative */ | |
| 29 | private boolean negate; | |
| 30 | /** The current timer period is right-shifted by this many bits to compute a "change amount." A shift of 0 means the change amount equals the period itself; higher shift values make each adjustment smaller/finer. */ | |
| 31 | private int shiftCount; | |
| 32 | ||
| 33 | private boolean reloadRequested; | |
| 34 | private int divider; | |
| 35 | ||
| 36 | public Sweep(final boolean onesComplementNegate){ | |
| 37 | this.onesComplementNegate = onesComplementNegate; | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Handle a $4001/$4005 write. | |
| 42 | * | |
| 43 | * Register: EPPP NSSS | |
| 44 | * E (bit 7): enabled | |
| 45 | * P (bits 4-6): divider reload period | |
| 46 | * N (bit 3): negate | |
| 47 | * S (bits 0-2): shift count | |
| 48 | */ | |
| 49 | public void writeControlRegister(final int value){ | |
| 50 |
2
1. writeControlRegister : negated conditional → KILLED 2. writeControlRegister : Replaced bitwise AND with OR → KILLED |
enabled = (value & ENABLED_BIT_MASK) != 0; |
| 51 |
2
1. writeControlRegister : Replaced Shift Right with Shift Left → KILLED 2. writeControlRegister : Replaced bitwise AND with OR → KILLED |
periodReload = (value >> PERIOD_SHIFT) & PERIOD_MASK; |
| 52 |
2
1. writeControlRegister : Replaced bitwise AND with OR → KILLED 2. writeControlRegister : negated conditional → KILLED |
negate = (value & NEGATE_BIT_MASK) != 0; |
| 53 |
1
1. writeControlRegister : Replaced bitwise AND with OR → KILLED |
shiftCount = value & SHIFT_MASK; |
| 54 | reloadRequested = true; | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * True if the channel should be silenced regardless of clocking or the enabled flag: the | |
| 59 | * current period is too low, or adjusting it would push the target period out of range. | |
| 60 | */ | |
| 61 | public boolean isMuted(final int currentPeriod){ | |
| 62 |
5
1. isMuted : negated conditional → KILLED 2. isMuted : changed conditional boundary → KILLED 3. isMuted : negated conditional → KILLED 4. isMuted : changed conditional boundary → KILLED 5. isMuted : replaced boolean return with true for com/rox/apu/Sweep::isMuted → KILLED |
return currentPeriod < MIN_UNMUTED_PERIOD || targetPeriod(currentPeriod) > MAX_UNMUTED_TARGET_PERIOD; |
| 63 | } | |
| 64 | ||
| 65 | /** Half-frame clock: applies the target period (if due) and returns the channel's new timer period. */ | |
| 66 | public int clockHalfFrame(final int currentPeriod){ | |
| 67 |
1
1. clockHalfFrame : negated conditional → KILLED |
final int newPeriod = isDueToAdjust(currentPeriod) ? targetPeriod(currentPeriod) : currentPeriod; |
| 68 | ||
| 69 |
2
1. clockHalfFrame : negated conditional → KILLED 2. clockHalfFrame : negated conditional → KILLED |
if (divider == 0 || reloadRequested){ |
| 70 | divider = periodReload; | |
| 71 | reloadRequested = false; | |
| 72 | } else { | |
| 73 |
1
1. clockHalfFrame : Replaced integer subtraction with addition → KILLED |
divider--; |
| 74 | } | |
| 75 | ||
| 76 |
1
1. clockHalfFrame : replaced int return with 0 for com/rox/apu/Sweep::clockHalfFrame → KILLED |
return newPeriod; |
| 77 | } | |
| 78 | ||
| 79 | /** True if the divider has just run out and sweeping is actually active (enabled, non-zero shift, not muted). */ | |
| 80 | private boolean isDueToAdjust(final int currentPeriod){ | |
| 81 |
5
1. isDueToAdjust : negated conditional → KILLED 2. isDueToAdjust : negated conditional → KILLED 3. isDueToAdjust : negated conditional → KILLED 4. isDueToAdjust : replaced boolean return with true for com/rox/apu/Sweep::isDueToAdjust → KILLED 5. isDueToAdjust : negated conditional → KILLED |
return divider == 0 && enabled && shiftCount != 0 && !isMuted(currentPeriod); |
| 82 | } | |
| 83 | ||
| 84 | private int targetPeriod(final int currentPeriod){ | |
| 85 |
1
1. targetPeriod : Replaced Shift Right with Shift Left → KILLED |
final int changeAmount = currentPeriod >> shiftCount; |
| 86 |
1
1. targetPeriod : negated conditional → KILLED |
if (!negate){ |
| 87 |
2
1. targetPeriod : replaced int return with 0 for com/rox/apu/Sweep::targetPeriod → KILLED 2. targetPeriod : Replaced integer addition with subtraction → KILLED |
return currentPeriod + changeAmount; |
| 88 | } | |
| 89 |
5
1. targetPeriod : Replaced integer subtraction with addition → KILLED 2. targetPeriod : negated conditional → KILLED 3. targetPeriod : Replaced integer subtraction with addition → KILLED 4. targetPeriod : Replaced integer subtraction with addition → KILLED 5. targetPeriod : replaced int return with 0 for com/rox/apu/Sweep::targetPeriod → KILLED |
return onesComplementNegate ? currentPeriod - changeAmount - 1 : currentPeriod - changeAmount; |
| 90 | } | |
| 91 | ||
| 92 | /** Everything a {@link Sweep} needs to carry on exactly where it was (its negate style is fixed by construction, not state). */ | |
| 93 | public record Snapshot(boolean enabled, int periodReload, boolean negate, int shiftCount, boolean reloadRequested, int divider) { | |
| 94 | } | |
| 95 | ||
| 96 | public Snapshot snapshot(){ | |
| 97 |
1
1. snapshot : replaced return value with null for com/rox/apu/Sweep::snapshot → KILLED |
return new Snapshot(enabled, periodReload, negate, shiftCount, reloadRequested, divider); |
| 98 | } | |
| 99 | ||
| 100 | public void restore(final Snapshot snapshot){ | |
| 101 | enabled = snapshot.enabled(); | |
| 102 | periodReload = snapshot.periodReload(); | |
| 103 | negate = snapshot.negate(); | |
| 104 | shiftCount = snapshot.shiftCount(); | |
| 105 | reloadRequested = snapshot.reloadRequested(); | |
| 106 | divider = snapshot.divider(); | |
| 107 | } | |
| 108 | } | |
Mutations | ||
| 50 |
1.1 2.2 |
|
| 51 |
1.1 2.2 |
|
| 52 |
1.1 2.2 |
|
| 53 |
1.1 |
|
| 62 |
1.1 2.2 3.3 4.4 5.5 |
|
| 67 |
1.1 |
|
| 69 |
1.1 2.2 |
|
| 73 |
1.1 |
|
| 76 |
1.1 |
|
| 81 |
1.1 2.2 3.3 4.4 5.5 |
|
| 85 |
1.1 |
|
| 86 |
1.1 |
|
| 87 |
1.1 2.2 |
|
| 89 |
1.1 2.2 3.3 4.4 5.5 |
|
| 97 |
1.1 |