FrameSequencer.java

1
package com.rox.apu;
2
3
import com.rox.clock.ClockWatcher;
4
5
import java.util.ArrayList;
6
import java.util.List;
7
8
/**
9
 * NES frame counter ($4017). Counts CPU cycles and clocks two independent watcher streams at
10
 * fixed boundaries, in either 4-step (default, IRQ-generating) or 5-step (IRQ-free) mode: a
11
 * quarter-frame stream (envelopes, triangle linear counter) and a half-frame stream (length
12
 * counters, sweep). A half-frame boundary always implies a simultaneous quarter-frame clock.
13
 *
14
 * Boundary values are CPU-cycle counts (intentionally 2x the APU-cycle values quoted by NESdev),
15
 * since {@link #tick()} is driven once per CPU cycle rather than once per APU cycle.
16
 */
17
public class FrameSequencer implements ClockWatcher {
18
    static final int QUARTER_FRAME_1 = 7457;
19
    static final int HALF_FRAME_1 = 14913;
20
    static final int QUARTER_FRAME_2 = 22371;
21
    static final int FOUR_STEP_END = 29829;
22
    static final int FIVE_STEP_END = 37281;
23
24
    private static final int FIVE_STEP_MODE_BIT = 0x80;
25
    private static final int IRQ_INHIBIT_BIT = 0x40;
26
27
    private final List<ClockWatcher> quarterFrameWatchers = new ArrayList<>();
28
    private final List<ClockWatcher> halfFrameWatchers = new ArrayList<>();
29
30
    private boolean fiveStepMode;
31
    private boolean irqInhibit;
32
    private int cycle; //counts up, when it reaches the max value for a mode, resets
33
    private boolean frameIrqPending;
34
35
    public void addQuarterFrameWatcher(final ClockWatcher watcher){
36
        quarterFrameWatchers.add(watcher);
37
    }
38
39
    public void addHalfFrameWatcher(final ClockWatcher watcher){
40
        halfFrameWatchers.add(watcher);
41
    }
42
43
    @Override
44
    public void tick(){
45 1 1. tick : Replaced integer addition with subtraction → KILLED
        cycle++;
46 1 1. tick : negated conditional → KILLED
        if (fiveStepMode){
47 1 1. tick : removed call to com/rox/apu/FrameSequencer::clockFiveStepMode → KILLED
            clockFiveStepMode();
48
        } else {
49 1 1. tick : removed call to com/rox/apu/FrameSequencer::clockFourStepMode → KILLED
            clockFourStepMode();
50
        }
51
    }
52
53
    private void clockFourStepMode(){
54
        switch (cycle){
55 1 1. clockFourStepMode : removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED
            case QUARTER_FRAME_1, QUARTER_FRAME_2 -> fireQuarterFrame();
56 1 1. clockFourStepMode : removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED
            case HALF_FRAME_1 -> fireHalfFrame();
57
            case FOUR_STEP_END -> {
58 1 1. clockFourStepMode : removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED
                fireHalfFrame();
59 1 1. clockFourStepMode : negated conditional → KILLED
                if (!irqInhibit){
60
                    frameIrqPending = true;
61
                }
62
                cycle = 0;
63
            }
64
            default -> {}
65
        }
66
    }
67
68
    private void clockFiveStepMode(){
69
        switch (cycle){
70 1 1. clockFiveStepMode : removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED
            case QUARTER_FRAME_1, QUARTER_FRAME_2 -> fireQuarterFrame();
71 1 1. clockFiveStepMode : removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED
            case HALF_FRAME_1 -> fireHalfFrame();
72
            case FIVE_STEP_END -> {
73 1 1. clockFiveStepMode : removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED
                fireHalfFrame();
74
                cycle = 0;
75
            }
76
            default -> {}
77
        }
78
    }
79
80
    private void fireQuarterFrame(){
81
        for (final ClockWatcher watcher : quarterFrameWatchers){
82 1 1. fireQuarterFrame : removed call to com/rox/clock/ClockWatcher::tick → KILLED
            watcher.tick();
83
        }
84
    }
85
86
    private void fireHalfFrame(){
87 1 1. fireHalfFrame : removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED
        fireQuarterFrame();
88
        for (final ClockWatcher watcher : halfFrameWatchers){
89 1 1. fireHalfFrame : removed call to com/rox/clock/ClockWatcher::tick → KILLED
            watcher.tick();
90
        }
91
    }
92
93
    /** Handle a $4017 write: sets mode/inhibit, resets the cycle counter, and (5-step only) fires an immediate clock. */
94
    public void writeControlRegister(final int value){
95 2 1. writeControlRegister : Replaced bitwise AND with OR → KILLED
2. writeControlRegister : negated conditional → KILLED
        fiveStepMode = (value & FIVE_STEP_MODE_BIT) != 0;
96 2 1. writeControlRegister : negated conditional → KILLED
2. writeControlRegister : Replaced bitwise AND with OR → KILLED
        irqInhibit = (value & IRQ_INHIBIT_BIT) != 0;
97
        cycle = 0;
98 1 1. writeControlRegister : negated conditional → KILLED
        if (irqInhibit){
99
            frameIrqPending = false;
100
        }
101 1 1. writeControlRegister : negated conditional → KILLED
        if (fiveStepMode){
102 1 1. writeControlRegister : removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED
            fireHalfFrame();
103
        }
104
    }
105
106
    public boolean isFrameIrqPending(){
107 2 1. isFrameIrqPending : replaced boolean return with false for com/rox/apu/FrameSequencer::isFrameIrqPending → KILLED
2. isFrameIrqPending : replaced boolean return with true for com/rox/apu/FrameSequencer::isFrameIrqPending → KILLED
        return frameIrqPending;
108
    }
109
110
    public void clearFrameIrq(){
111
        frameIrqPending = false;
112
    }
113
114
    /** Everything a {@link FrameSequencer} needs to carry on exactly where it was (its watchers are wiring, not state). */
115
    public record Snapshot(boolean fiveStepMode, boolean irqInhibit, int cycle, boolean frameIrqPending) {
116
    }
117
118
    public Snapshot snapshot(){
119 1 1. snapshot : replaced return value with null for com/rox/apu/FrameSequencer::snapshot → KILLED
        return new Snapshot(fiveStepMode, irqInhibit, cycle, frameIrqPending);
120
    }
121
122
    public void restore(final Snapshot snapshot){
123
        fiveStepMode = snapshot.fiveStepMode();
124
        irqInhibit = snapshot.irqInhibit();
125
        cycle = snapshot.cycle();
126
        frameIrqPending = snapshot.frameIrqPending();
127
    }
128
}

Mutations

45

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

46

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

47

1.1
Location : tick
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fiveStepModeFirstQuarterBoundary()]
removed call to com/rox/apu/FrameSequencer::clockFiveStepMode → KILLED

49

1.1
Location : tick
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeFirstQuarterBoundary()]
removed call to com/rox/apu/FrameSequencer::clockFourStepMode → KILLED

55

1.1
Location : clockFourStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeFirstQuarterBoundary()]
removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED

56

1.1
Location : clockFourStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeSecondQuarterBoundary()]
removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED

58

1.1
Location : clockFourStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeEndBoundaryFiresHalfAndSetsIrq()]
removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED

59

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

70

1.1
Location : clockFiveStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fiveStepModeFirstQuarterBoundary()]
removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED

71

1.1
Location : clockFiveStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fiveStepModeNoEventAtOldFourStepEndBoundary()]
removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED

73

1.1
Location : clockFiveStepMode
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fiveStepModeEndBoundaryFiresHalfButNeverIrq()]
removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED

82

1.1
Location : fireQuarterFrame
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeFirstQuarterBoundary()]
removed call to com/rox/clock/ClockWatcher::tick → KILLED

87

1.1
Location : fireHalfFrame
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:writingFourStepModeAfterFiveStepDoesNotFireExtraClock()]
removed call to com/rox/apu/FrameSequencer::fireQuarterFrame → KILLED

89

1.1
Location : fireHalfFrame
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:writingFourStepModeAfterFiveStepDoesNotFireExtraClock()]
removed call to com/rox/clock/ClockWatcher::tick → KILLED

95

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

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

96

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

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

98

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

101

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

102

1.1
Location : writeControlRegister
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:writingFourStepModeAfterFiveStepDoesNotFireExtraClock()]
removed call to com/rox/apu/FrameSequencer::fireHalfFrame → KILLED

107

1.1
Location : isFrameIrqPending
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:fourStepModeEndBoundaryFiresHalfAndSetsIrq()]
replaced boolean return with false for com/rox/apu/FrameSequencer::isFrameIrqPending → KILLED

2.2
Location : isFrameIrqPending
Killed by : com.rox.apu.FrameSequencerTest.[engine:junit-jupiter]/[class:com.rox.apu.FrameSequencerTest]/[method:clearFrameIrqClearsPendingFlag()]
replaced boolean return with true for com/rox/apu/FrameSequencer::isFrameIrqPending → KILLED

119

1.1
Location : snapshot
Killed by : com.rox.save.SaveFileFormatTest.[engine:junit-jupiter]/[class:com.rox.save.SaveFileFormatTest]/[method:readLiveSnapshotIsEmptyForASnapshotPayloadInAFileTaggedAsAnotherSaveType(java.nio.file.Path)]
replaced return value with null for com/rox/apu/FrameSequencer::snapshot → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1