Sweep.java

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
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenNotEnabled()]
negated conditional → KILLED

2.2
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenNotEnabled()]
Replaced bitwise AND with OR → KILLED

51

1.1
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:writingRegisterForcesDividerReloadOnNextClockWithoutAnAdjustment()]
Replaced Shift Right with Shift Left → KILLED

2.2
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:dividerGatesAdjustmentToOncePerPeriodReloadCycle()]
Replaced bitwise AND with OR → KILLED

52

1.1
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:positiveChangeWhenNotNegating()]
Replaced bitwise AND with OR → KILLED

2.2
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:positiveChangeWhenNotNegating()]
negated conditional → KILLED

53

1.1
Location : writeControlRegister
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:positiveChangeWhenNotNegating()]
Replaced bitwise AND with OR → KILLED

62

1.1
Location : isMuted
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:periodOfExactlyMinimumUnmutedIsNotMuted()]
negated conditional → KILLED

2.2
Location : isMuted
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:periodOfExactlyMinimumUnmutedIsNotMuted()]
changed conditional boundary → KILLED

3.3
Location : isMuted
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:periodOfExactlyMinimumUnmutedIsNotMuted()]
negated conditional → KILLED

4.4
Location : isMuted
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:targetPeriodOfExactlyMaximumUnmutedIsNotMuted()]
changed conditional boundary → KILLED

5.5
Location : isMuted
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:periodOfExactlyMinimumUnmutedIsNotMuted()]
replaced boolean return with true for com/rox/apu/Sweep::isMuted → KILLED

67

1.1
Location : clockHalfFrame
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenShiftIsZero()]
negated conditional → KILLED

69

1.1
Location : clockHalfFrame
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:dividerGatesAdjustmentToOncePerPeriodReloadCycle()]
negated conditional → KILLED

2.2
Location : clockHalfFrame
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:dividerGatesAdjustmentToOncePerPeriodReloadCycle()]
negated conditional → KILLED

73

1.1
Location : clockHalfFrame
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:dividerGatesAdjustmentToOncePerPeriodReloadCycle()]
Replaced integer subtraction with addition → KILLED

76

1.1
Location : clockHalfFrame
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenShiftIsZero()]
replaced int return with 0 for com/rox/apu/Sweep::clockHalfFrame → KILLED

81

1.1
Location : isDueToAdjust
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:positiveChangeWhenNotNegating()]
negated conditional → KILLED

2.2
Location : isDueToAdjust
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenShiftIsZero()]
negated conditional → KILLED

3.3
Location : isDueToAdjust
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:twosComplementNegateSubtractsChangeOnly()]
negated conditional → KILLED

4.4
Location : isDueToAdjust
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenShiftIsZero()]
replaced boolean return with true for com/rox/apu/Sweep::isDueToAdjust → KILLED

5.5
Location : isDueToAdjust
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:clockHalfFrameDoesNotAdjustWhenNotEnabled()]
negated conditional → KILLED

85

1.1
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:targetPeriodOfExactlyMaximumUnmutedIsNotMuted()]
Replaced Shift Right with Shift Left → KILLED

86

1.1
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:muteConditionsAreIndependentOfEnabledFlag()]
negated conditional → KILLED

87

1.1
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:muteConditionsAreIndependentOfEnabledFlag()]
replaced int return with 0 for com/rox/apu/Sweep::targetPeriod → KILLED

2.2
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:muteConditionsAreIndependentOfEnabledFlag()]
Replaced integer addition with subtraction → KILLED

89

1.1
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:twosComplementNegateSubtractsChangeOnly()]
Replaced integer subtraction with addition → KILLED

2.2
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:onesComplementNegateSubtractsChangeAndOne()]
negated conditional → KILLED

3.3
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:onesComplementNegateSubtractsChangeAndOne()]
Replaced integer subtraction with addition → KILLED

4.4
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:onesComplementNegateSubtractsChangeAndOne()]
Replaced integer subtraction with addition → KILLED

5.5
Location : targetPeriod
Killed by : com.rox.apu.SweepTest.[engine:junit-jupiter]/[class:com.rox.apu.SweepTest]/[method:onesComplementNegateSubtractsChangeAndOne()]
replaced int return with 0 for com/rox/apu/Sweep::targetPeriod → KILLED

97

1.1
Location : snapshot
Killed by : com.rox.apu.ApuSnapshotTest.[engine:jqwik]/[class:com.rox.apu.ApuSnapshotTest]/[property:sweepRoundTrips(boolean, int, boolean, int, boolean, int)]
replaced return value with null for com/rox/apu/Sweep::snapshot → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1