| 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 |
|
| 46 |
1.1 |
|
| 47 |
1.1 |
|
| 49 |
1.1 |
|
| 55 |
1.1 |
|
| 56 |
1.1 |
|
| 58 |
1.1 |
|
| 59 |
1.1 |
|
| 70 |
1.1 |
|
| 71 |
1.1 |
|
| 73 |
1.1 |
|
| 82 |
1.1 |
|
| 87 |
1.1 |
|
| 89 |
1.1 |
|
| 95 |
1.1 2.2 |
|
| 96 |
1.1 2.2 |
|
| 98 |
1.1 |
|
| 101 |
1.1 |
|
| 102 |
1.1 |
|
| 107 |
1.1 2.2 |
|
| 119 |
1.1 |