NoiseChannel.java

1
package com.rox.apu;
2
3
import com.rox.clock.ClockWatcher;
4
5
/**
6
 * NES noise channel ($400C / $400E / $400F). Composes an {@link Envelope} and {@link LengthCounter}
7
 * (same shape as pulse, minus the sweep) with a period looked up from a fixed table and a 15-bit
8
 * linear-feedback shift register (LFSR) that produces the pseudo-random bit stream.
9
 *
10
 * Like pulse, the timer is clocked once per APU cycle (every other CPU cycle - see the parity flag
11
 * in {@link #tick()}). Unlike pulse, the period isn't an arbitrary 11-bit value split across two
12
 * register writes; $400E's low nibble simply selects one of 16 fixed periods from
13
 * {@link #NTSC_NOISE_PERIODS}.
14
 *
15
 * On each timer reload, the shift register is clocked: feedback is the XOR of bit 0 and either bit 1
16
 * (mode clear, the usual ~32767-step-long sequence) or bit 6 (mode set, a shorter, more metallic-
17
 * sounding ~93-step sequence that repeats sooner), the register shifts right by one, and the vacated
18
 * bit 14 is set to that feedback. The channel is silenced whenever bit 0 of the register is set -
19
 * this is what makes the output "pseudo-random" rather than a fixed tone.
20
 */
21
public class NoiseChannel implements ClockWatcher {
22
    /** NTSC noise periods (in APU cycles), selected by the low nibble of $400E. */
23
    static final int[] NTSC_NOISE_PERIODS = {
24
            4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
25
    };
26
27
    private static final int LENGTH_HALT_BIT = 0x20;
28
29
    private static final int MODE_BIT = 0x80;
30
    private static final int PERIOD_INDEX_MASK = 0x0F;
31
32
    private static final int LENGTH_LOAD_SHIFT = 3;
33
    private static final int LENGTH_LOAD_MASK = 0x1F;
34
35
    private static final int SHIFT_REGISTER_RESET = 1;
36
    private static final int SHORT_MODE_FEEDBACK_TAP = 6;
37
    private static final int LONG_MODE_FEEDBACK_TAP = 1;
38
    private static final int VACATED_BIT_SHIFT = 14;
39
40
    private final Envelope envelope;
41
    private final LengthCounter lengthCounter;
42
43
    private boolean mode;
44
    private int shiftRegister = SHIFT_REGISTER_RESET;
45
46
    private final ParityCountdownFrequencyDivider frequencyDivider;
47
48
    public NoiseChannel(){
49
        this(new Envelope(), new LengthCounter());
50
    }
51
52
    NoiseChannel(final Envelope envelope, final LengthCounter lengthCounter){
53
        this.envelope = envelope;
54
        this.lengthCounter = lengthCounter;
55
56
        this.frequencyDivider = new ParityCountdownFrequencyDivider(this::clockShiftRegister, false, 0);
57
    }
58
59
    /**
60
     * Handle a $400C write.
61
     *
62
     * Register: --LC VVVV
63
     * L (bit 5): length-counter halt / envelope loop (shared bit)
64
     * C (bit 4): constant volume
65
     * V (bits 0-3): volume / envelope period
66
     */
67
    public void writeControlRegister(final int value){
68 3 1. writeControlRegister : Replaced bitwise AND with OR → KILLED
2. writeControlRegister : negated conditional → KILLED
3. writeControlRegister : removed call to com/rox/apu/LengthCounter::setHalt → KILLED
        lengthCounter.setHalt((value & LENGTH_HALT_BIT) != 0);
69 1 1. writeControlRegister : removed call to com/rox/apu/Envelope::writeControlRegister → KILLED
        envelope.writeControlRegister(value);
70
    }
71
72
    /**
73
     * Handle a $400E write.
74
     *
75
     * Register: M--- PPPP
76
     * M (bit 7): mode flag (selects the feedback tap)
77
     * P (bits 0-3): index into {@link #NTSC_NOISE_PERIODS}
78
     */
79
    public void writeModeAndPeriod(final int value){
80 2 1. writeModeAndPeriod : Replaced bitwise AND with OR → KILLED
2. writeModeAndPeriod : negated conditional → KILLED
        mode = (value & MODE_BIT) != 0;
81 2 1. writeModeAndPeriod : removed call to com/rox/apu/ParityCountdownFrequencyDivider::setCounterPeriod → KILLED
2. writeModeAndPeriod : Replaced bitwise AND with OR → KILLED
        frequencyDivider.setCounterPeriod(NTSC_NOISE_PERIODS[value & PERIOD_INDEX_MASK]);
82
    }
83
84
    /**
85
     * Handle a $400F write.
86
     *
87
     * Register: LLLL L---
88
     * L (bits 3-7): length-counter table index
89
     */
90
    public void writeLengthLoad(final int value){
91 3 1. writeLengthLoad : Replaced bitwise AND with OR → KILLED
2. writeLengthLoad : removed call to com/rox/apu/LengthCounter::load → KILLED
3. writeLengthLoad : Replaced Shift Right with Shift Left → KILLED
        lengthCounter.load((value >> LENGTH_LOAD_SHIFT) & LENGTH_LOAD_MASK);
92 1 1. writeLengthLoad : removed call to com/rox/apu/Envelope::restart → KILLED
        envelope.restart();
93
    }
94
95
    /** Handle a $4015 enable-bit change - see {@link LengthCounter#setEnabled}. */
96
    public void setEnabled(final boolean enabled){
97 1 1. setEnabled : removed call to com/rox/apu/LengthCounter::setEnabled → KILLED
        lengthCounter.setEnabled(enabled);
98
    }
99
100
    /** Whether the length counter is currently nonzero - for the $4015 status read. */
101
    public boolean isLengthCounterActive(){
102 2 1. isLengthCounterActive : replaced boolean return with true for com/rox/apu/NoiseChannel::isLengthCounterActive → KILLED
2. isLengthCounterActive : negated conditional → KILLED
        return !lengthCounter.isZero();
103
    }
104
105
    /** CPU-cycle clock: the timer runs at half this rate (once per APU cycle), same as pulse. */
106
    @Override
107
    public void tick(){
108 1 1. tick : removed call to com/rox/apu/ParityCountdownFrequencyDivider::tick → KILLED
        frequencyDivider.tick();
109
    }
110
111
    private void clockShiftRegister(){
112 1 1. clockShiftRegister : negated conditional → KILLED
        final int otherTap = mode ? SHORT_MODE_FEEDBACK_TAP : LONG_MODE_FEEDBACK_TAP;
113 3 1. clockShiftRegister : Replaced bitwise AND with OR → KILLED
2. clockShiftRegister : Replaced XOR with AND → KILLED
3. clockShiftRegister : Replaced Shift Right with Shift Left → KILLED
        final int feedback = (shiftRegister ^ (shiftRegister >> otherTap)) & 1;
114 1 1. clockShiftRegister : Replaced Shift Right with Shift Left → KILLED
        shiftRegister >>= 1;
115 2 1. clockShiftRegister : Replaced Shift Left with Shift Right → KILLED
2. clockShiftRegister : Replaced bitwise OR with AND → KILLED
        shiftRegister |= feedback << VACATED_BIT_SHIFT;
116
    }
117
118
    /** Quarter-frame clock: advances the envelope. */
119
    public void quarterFrameTick(){
120 1 1. quarterFrameTick : removed call to com/rox/apu/Envelope::tick → KILLED
        envelope.tick();
121
    }
122
123
    /** Half-frame clock: advances the length counter. */
124
    public void halfFrameTick(){
125 1 1. halfFrameTick : removed call to com/rox/apu/LengthCounter::tick → KILLED
        lengthCounter.tick();
126
    }
127
128
    /** Current output: 0 if silenced by the length counter or the shift register's bit 0, else the envelope volume. */
129
    public int outputSample(){
130 3 1. outputSample : negated conditional → KILLED
2. outputSample : negated conditional → KILLED
3. outputSample : Replaced bitwise AND with OR → KILLED
        if (lengthCounter.isZero() || (shiftRegister & 1) != 0){
131
            return 0;
132
        }
133 1 1. outputSample : replaced int return with 0 for com/rox/apu/NoiseChannel::outputSample → KILLED
        return envelope.volume();
134
    }
135
136
    int currentTimerPeriod(){
137 1 1. currentTimerPeriod : replaced int return with 0 for com/rox/apu/NoiseChannel::currentTimerPeriod → KILLED
        return frequencyDivider.getCounterPeriod();
138
    }
139
140
    int currentShiftRegister(){
141 1 1. currentShiftRegister : replaced int return with 0 for com/rox/apu/NoiseChannel::currentShiftRegister → KILLED
        return shiftRegister;
142
    }
143
144
    boolean currentMode(){
145 2 1. currentMode : replaced boolean return with true for com/rox/apu/NoiseChannel::currentMode → KILLED
2. currentMode : replaced boolean return with false for com/rox/apu/NoiseChannel::currentMode → KILLED
        return mode;
146
    }
147
148
    /** Everything a {@link NoiseChannel} needs to carry on exactly where it was, sub-units included. */
149
    public record Snapshot(boolean mode, int shiftRegister, Envelope.Snapshot envelope,
150
                           LengthCounter.Snapshot lengthCounter, ParityCountdownFrequencyDivider.Snapshot frequencyDivider) {
151
    }
152
153
    public Snapshot snapshot(){
154 1 1. snapshot : replaced return value with null for com/rox/apu/NoiseChannel::snapshot → KILLED
        return new Snapshot(mode, shiftRegister, envelope.snapshot(), lengthCounter.snapshot(),
155
                frequencyDivider.snapshot());
156
    }
157
158
    public void restore(final Snapshot snapshot){
159
        mode = snapshot.mode();
160
        shiftRegister = snapshot.shiftRegister();
161 1 1. restore : removed call to com/rox/apu/Envelope::restore → KILLED
        envelope.restore(snapshot.envelope());
162 1 1. restore : removed call to com/rox/apu/LengthCounter::restore → KILLED
        lengthCounter.restore(snapshot.lengthCounter());
163 1 1. restore : removed call to com/rox/apu/ParityCountdownFrequencyDivider::restore → KILLED
        frequencyDivider.restore(snapshot.frequencyDivider());
164
    }
165
}

Mutations

68

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

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

3.3
Location : writeControlRegister
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingControlRegisterWithHaltBitClearSetsHaltFalse()]
removed call to com/rox/apu/LengthCounter::setHalt → KILLED

69

1.1
Location : writeControlRegister
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingControlRegisterSetsLengthHaltAndDelegatesToEnvelope()]
removed call to com/rox/apu/Envelope::writeControlRegister → KILLED

80

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

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

81

1.1
Location : writeModeAndPeriod
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[test-template:writingModeAndPeriodSelectsPeriodFromNtscTable(int)]/[test-template-invocation:#9]
removed call to com/rox/apu/ParityCountdownFrequencyDivider::setCounterPeriod → KILLED

2.2
Location : writeModeAndPeriod
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[test-template:writingModeAndPeriodSelectsPeriodFromNtscTable(int)]/[test-template-invocation:#9]
Replaced bitwise AND with OR → KILLED

91

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

2.2
Location : writeLengthLoad
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingLengthLoadLoadsLengthCounterAndRestartsEnvelope()]
removed call to com/rox/apu/LengthCounter::load → KILLED

3.3
Location : writeLengthLoad
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingLengthLoadLoadsLengthCounterAndRestartsEnvelope()]
Replaced Shift Right with Shift Left → KILLED

92

1.1
Location : writeLengthLoad
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingLengthLoadLoadsLengthCounterAndRestartsEnvelope()]
removed call to com/rox/apu/Envelope::restart → KILLED

97

1.1
Location : setEnabled
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:setEnabledPassesThroughToTheLengthCounter()]
removed call to com/rox/apu/LengthCounter::setEnabled → KILLED

102

1.1
Location : isLengthCounterActive
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:isLengthCounterActiveDelegatesToLengthCounter()]
replaced boolean return with true for com/rox/apu/NoiseChannel::isLengthCounterActive → KILLED

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

108

1.1
Location : tick
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:tickIsParityGatedLikePulseSoOnlyEveryOtherCallClocksTheShiftRegister()]
removed call to com/rox/apu/ParityCountdownFrequencyDivider::tick → KILLED

112

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

113

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

2.2
Location : clockShiftRegister
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:tickIsParityGatedLikePulseSoOnlyEveryOtherCallClocksTheShiftRegister()]
Replaced XOR with AND → KILLED

3.3
Location : clockShiftRegister
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:longModeFeedbackTapDivergesFromShortModeAfterTenClocks()]
Replaced Shift Right with Shift Left → KILLED

114

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

115

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

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

120

1.1
Location : quarterFrameTick
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:quarterFrameTickClocksOnlyTheEnvelope()]
removed call to com/rox/apu/Envelope::tick → KILLED

125

1.1
Location : halfFrameTick
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:halfFrameTickClocksOnlyTheLengthCounter()]
removed call to com/rox/apu/LengthCounter::tick → KILLED

130

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

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

3.3
Location : outputSample
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:outputSampleReturnsEnvelopeVolumeOnceTheShiftRegisterClocksBitZeroClear()]
Replaced bitwise AND with OR → KILLED

133

1.1
Location : outputSample
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:outputSampleReturnsEnvelopeVolumeOnceTheShiftRegisterClocksBitZeroClear()]
replaced int return with 0 for com/rox/apu/NoiseChannel::outputSample → KILLED

137

1.1
Location : currentTimerPeriod
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[test-template:writingModeAndPeriodSelectsPeriodFromNtscTable(int)]/[test-template-invocation:#1]
replaced int return with 0 for com/rox/apu/NoiseChannel::currentTimerPeriod → KILLED

141

1.1
Location : currentShiftRegister
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:outputIsSilentImmediatelyAfterConstructionBecauseTheShiftRegisterResetsWithBitZeroSet()]
replaced int return with 0 for com/rox/apu/NoiseChannel::currentShiftRegister → KILLED

145

1.1
Location : currentMode
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingModeAndPeriodSetsAndClearsModeFlag()]
replaced boolean return with true for com/rox/apu/NoiseChannel::currentMode → KILLED

2.2
Location : currentMode
Killed by : com.rox.apu.NoiseChannelTest.[engine:junit-jupiter]/[class:com.rox.apu.NoiseChannelTest]/[method:writingModeAndPeriodSetsAndClearsModeFlag()]
replaced boolean return with false for com/rox/apu/NoiseChannel::currentMode → KILLED

154

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/NoiseChannel::snapshot → KILLED

161

1.1
Location : restore
Killed by : com.rox.apu.ApuSnapshotTest.[engine:junit-jupiter]/[class:com.rox.apu.ApuSnapshotTest]/[method:restoredMidSampleRunsInLockstep()]
removed call to com/rox/apu/Envelope::restore → KILLED

162

1.1
Location : restore
Killed by : com.rox.apu.ApuSnapshotTest.[engine:junit-jupiter]/[class:com.rox.apu.ApuSnapshotTest]/[method:restoredMidSampleRunsInLockstep()]
removed call to com/rox/apu/LengthCounter::restore → KILLED

163

1.1
Location : restore
Killed by : com.rox.apu.ApuSnapshotTest.[engine:junit-jupiter]/[class:com.rox.apu.ApuSnapshotTest]/[method:restoredMidSampleRunsInLockstep()]
removed call to com/rox/apu/ParityCountdownFrequencyDivider::restore → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1