| 1 | package com.rox.apu; | |
| 2 | ||
| 3 | import com.rox.clock.ClockWatcher; | |
| 4 | ||
| 5 | /** | |
| 6 | * NES triangle channel's linear counter ($4008). A second, independently-configured counter | |
| 7 | * (alongside the triangle's shared {@link LengthCounter}) that must also be nonzero for the | |
| 8 | * triangle's sequencer to advance - this is what lets games give the triangle a note duration | |
| 9 | * shorter than the length counter's coarse table allows. | |
| 10 | * | |
| 11 | * The control flag (bit7 of $4008) is the same physical bit as the channel's length-counter halt | |
| 12 | * flag - a hardware quirk, not a coincidence - so callers read both meanings from the one write. | |
| 13 | */ | |
| 14 | public class LinearCounter implements ClockWatcher { | |
| 15 | private static final int CONTROL_FLAG_BIT = 0x80; | |
| 16 | private static final int RELOAD_VALUE_MASK = 0x7F; | |
| 17 | ||
| 18 | private int reloadValue; | |
| 19 | private boolean controlFlagSet; | |
| 20 | private int counter; | |
| 21 | private boolean reloadFlagSet; | |
| 22 | ||
| 23 | /** | |
| 24 | * Handle a $4008 write. | |
| 25 | * | |
| 26 | * Register: CRRR RRRR | |
| 27 | * C (bit 7): control flag (shared with the channel's length-counter halt flag) | |
| 28 | * R (bits 0-6): reload value | |
| 29 | */ | |
| 30 | public void writeControlRegister(final int value){ | |
| 31 |
2
1. writeControlRegister : Replaced bitwise AND with OR → KILLED 2. writeControlRegister : negated conditional → KILLED |
controlFlagSet = (value & CONTROL_FLAG_BIT) != 0; |
| 32 |
1
1. writeControlRegister : Replaced bitwise AND with OR → KILLED |
reloadValue = value & RELOAD_VALUE_MASK; |
| 33 | } | |
| 34 | ||
| 35 | /** Requests a reload on the next {@link #tick()} - triggered by a $400B (timer high) write. */ | |
| 36 | public void requestReload(){ | |
| 37 | reloadFlagSet = true; | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Quarter-frame clock: reloads from {@link #reloadValue} if a reload is pending, else decrements | |
| 42 | * towards 0; the reload flag itself only survives past this tick if the control flag is set. | |
| 43 | */ | |
| 44 | @Override | |
| 45 | public void tick(){ | |
| 46 |
1
1. tick : negated conditional → KILLED |
if (reloadFlagSet){ |
| 47 | counter = reloadValue; | |
| 48 |
2
1. tick : changed conditional boundary → KILLED 2. tick : negated conditional → KILLED |
} else if (counter > 0){ |
| 49 |
1
1. tick : Replaced integer subtraction with addition → KILLED |
counter--; |
| 50 | } | |
| 51 |
1
1. tick : negated conditional → KILLED |
if (!controlFlagSet){ |
| 52 | reloadFlagSet = false; | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | public boolean isZero(){ | |
| 57 |
2
1. isZero : replaced boolean return with true for com/rox/apu/LinearCounter::isZero → KILLED 2. isZero : negated conditional → KILLED |
return counter == 0; |
| 58 | } | |
| 59 | ||
| 60 | /** Everything a {@link LinearCounter} needs to carry on exactly where it was. */ | |
| 61 | public record Snapshot(int reloadValue, boolean controlFlagSet, int counter, boolean reloadFlagSet) { | |
| 62 | } | |
| 63 | ||
| 64 | public Snapshot snapshot(){ | |
| 65 |
1
1. snapshot : replaced return value with null for com/rox/apu/LinearCounter::snapshot → KILLED |
return new Snapshot(reloadValue, controlFlagSet, counter, reloadFlagSet); |
| 66 | } | |
| 67 | ||
| 68 | public void restore(final Snapshot snapshot){ | |
| 69 | reloadValue = snapshot.reloadValue(); | |
| 70 | controlFlagSet = snapshot.controlFlagSet(); | |
| 71 | counter = snapshot.counter(); | |
| 72 | reloadFlagSet = snapshot.reloadFlagSet(); | |
| 73 | } | |
| 74 | } | |
Mutations | ||
| 31 |
1.1 2.2 |
|
| 32 |
1.1 |
|
| 46 |
1.1 |
|
| 48 |
1.1 2.2 |
|
| 49 |
1.1 |
|
| 51 |
1.1 |
|
| 57 |
1.1 2.2 |
|
| 65 |
1.1 |