SpeakerAudioOutput.java

1
package com.rox.audio;
2
3
import javax.sound.sampled.AudioFormat;
4
import javax.sound.sampled.AudioSystem;
5
import javax.sound.sampled.LineUnavailableException;
6
import javax.sound.sampled.SourceDataLine;
7
import java.util.concurrent.atomic.AtomicLong;
8
9
/**
10
 * Plays resampled audio through the system's default speaker via {@code javax.sound.sampled}
11
 * (44100Hz, 16-bit signed PCM, mono, little-endian).
12
 *
13
 * {@link #write(double)} is called from the emulation thread and must never block on real line
14
 * I/O, so it only pushes into a small ring buffer; a dedicated writer thread drains that buffer
15
 * and does the actual (blocking) {@link SourceDataLine#write}, in batches (see {@link #WRITE_CHUNK_SAMPLES})
16
 * rather than one sample at a time - at 44100Hz, one native call per sample is enough per-call
17
 * overhead and scheduling jitter to risk underrunning the hardware's own buffer, which is audible
18
 * as clicks/pops.
19
 *
20
 * The {@link Resampler} produces samples in ~0.0-1.0 (an analog voltage, unipolar - 0.0 is true
21
 * silence, e.g. {@code Mixer.mix(0, 0, 0, 0, 0) == 0.0}, not a signal centered on some midpoint).
22
 * {@link #toPcm16} scales that range directly onto signed PCM16 without any DC-removal/centering
23
 * step; real NES hardware AC-couples this signal (a capacitor removes the DC bias) before it
24
 * reaches a speaker, which would need a proper stateful high-pass filter to replicate correctly -
25
 * out of scope here, not something a stateless per-sample conversion can do right.
26
 */
27
public class SpeakerAudioOutput implements AudioOutput {
28
    private static final float SAMPLE_RATE = 44_100f;
29
    private static final int SAMPLE_SIZE_BITS = 16;
30
    private static final int CHANNELS = 1;
31
    private static final boolean SIGNED = true;
32
    private static final boolean BIG_ENDIAN = false;
33
34
    static final int RING_BUFFER_CAPACITY = 8192;
35
    static final int WRITE_CHUNK_SAMPLES = 1024;
36
    private static final int BYTES_PER_SAMPLE = 2;
37
    private static final long WRITER_THREAD_JOIN_TIMEOUT_MS = 1000;
38
    //a full WRITE_CHUNK_SAMPLES batch normally fills well within this (real production is far denser
39
    //than 1024 samples per 50ms), so this only bounds latency for slow/sparse producers - it stops the
40
    //writer thread waiting forever for a full batch that may never come
41
    private static final long DEFAULT_PARTIAL_BATCH_TIMEOUT_MILLIS = 50;
42
    //requests a generous hardware buffer so producer jitter (GC pauses, OS scheduling, the
43
    //emulation's bursty per-frame production) has room to absorb without underrunning and
44
    //clicking; the JVM's platform-default buffer size can be too small for that
45
    private static final float LINE_BUFFER_DURATION_SECONDS = 0.2f;
46
47
    private final SourceDataLine line;
48
    private final long partialBatchTimeoutMillis;
49
    private final short[] ringBuffer = new short[RING_BUFFER_CAPACITY];
50
    private final Object bufferLock = new Object();
51
    private int writeIndex;
52
    private int readIndex;
53
    private int bufferedCount;
54
55
    private volatile boolean running;
56
    //written under bufferLock so the writer thread's wait loop can't miss a resume()'s notify
57
    private volatile boolean paused;
58
    private Thread writerThread;
59
60
    //DIAGNOSTICS (temporary): tracking down a reported clicking artifact
61
    private final AtomicLong droppedSampleCount = new AtomicLong();
62
    private final AtomicLong underrunCount = new AtomicLong();
63
64
    public SpeakerAudioOutput() throws LineUnavailableException {
65
        this(openDefaultLine());
66
    }
67
68
    SpeakerAudioOutput(final SourceDataLine line){
69
        this(line, DEFAULT_PARTIAL_BATCH_TIMEOUT_MILLIS);
70
    }
71
72
    //test seam: an artificially large timeout lets a test prove "flushes promptly" without a tight
73
    //wall-clock margin below the real default - a regression back to timed waiting then takes seconds
74
    //to detect instead of tens of milliseconds, immune to scheduler jitter on a freshly-started thread
75
    SpeakerAudioOutput(final SourceDataLine line, final long partialBatchTimeoutMillis){
76
        this.line = line;
77
        this.partialBatchTimeoutMillis = partialBatchTimeoutMillis;
78
    }
79
80
    private static SourceDataLine openDefaultLine() throws LineUnavailableException {
81
        //XXX Mutation coverage expected to have issues here since it deals with real hardware - just accepting it for now
82
        final AudioFormat format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE_BITS, CHANNELS, SIGNED, BIG_ENDIAN);
83
        final SourceDataLine line = AudioSystem.getSourceDataLine(format);
84 1 1. openDefaultLine : Replaced integer multiplication with division → NO_COVERAGE
        final int bufferSizeBytes = Math.round(SAMPLE_RATE * LINE_BUFFER_DURATION_SECONDS) * BYTES_PER_SAMPLE;
85 1 1. openDefaultLine : removed call to javax/sound/sampled/SourceDataLine::open → NO_COVERAGE
        line.open(format, bufferSizeBytes);
86 1 1. openDefaultLine : replaced return value with null for com/rox/audio/SpeakerAudioOutput::openDefaultLine → NO_COVERAGE
        return line;
87
    }
88
89
    @Override
90
    public synchronized void start(){
91 1 1. start : negated conditional → KILLED
        if (running){
92
            return;
93
        }
94
        running = true;
95 1 1. start : removed call to javax/sound/sampled/SourceDataLine::start → KILLED
        line.start();
96
        writerThread = new Thread(this::drainBufferToLine, "SpeakerAudioOutput-writer");
97 1 1. start : removed call to java/lang/Thread::setDaemon → SURVIVED
        writerThread.setDaemon(true);
98
        //real-time-sensitive: reduce the chance of the OS scheduler starving this thread in favour
99
        //of the CPU-bound emulation thread, which would otherwise show up as audible clicks
100 1 1. start : removed call to java/lang/Thread::setPriority → SURVIVED
        writerThread.setPriority(Thread.MAX_PRIORITY);
101 1 1. start : removed call to java/lang/Thread::start → KILLED
        writerThread.start();
102
    }
103
104
    @Override
105
    public synchronized void stop(){
106 1 1. stop : negated conditional → KILLED
        if (!running){
107
            return;
108
        }
109
        running = false;
110
        synchronized (bufferLock){
111
            paused = false;
112 1 1. stop : removed call to java/lang/Object::notifyAll → SURVIVED
            bufferLock.notifyAll(); //wake the writer thread so it notices `running` is now false
113
        }
114
        //stopping the line first also unblocks the writer thread if it's currently inside a
115
        //blocking line.write() call, which happens outside bufferLock so notifyAll() can't reach it
116 1 1. stop : removed call to javax/sound/sampled/SourceDataLine::stop → KILLED
        line.stop();
117
        try {
118 1 1. stop : removed call to java/lang/Thread::join → SURVIVED
            writerThread.join(WRITER_THREAD_JOIN_TIMEOUT_MS);
119
        } catch (InterruptedException e){
120 1 1. stop : removed call to java/lang/Thread::interrupt → NO_COVERAGE
            Thread.currentThread().interrupt();
121
        }
122 1 1. stop : removed call to javax/sound/sampled/SourceDataLine::close → KILLED
        line.close();
123 1 1. stop : removed call to java/io/PrintStream::println → SURVIVED
        System.err.println("[SpeakerAudioOutput diagnostics] dropped samples: " + droppedSampleCount.get()
124
                + ", underrun events (line buffer ran completely dry): " + underrunCount.get());
125
    }
126
127
    /**
128
     * Stops the line where it is - unlike {@link #stop()}, which closes it for good - and parks the
129
     * writer thread, so samples already buffered (in the line and the ring buffer) play on after
130
     * {@link #resume()} rather than being lost.
131
     */
132
    @Override
133
    public synchronized void pause(){
134 2 1. pause : negated conditional → KILLED
2. pause : negated conditional → KILLED
        if (!running || paused){
135
            return;
136
        }
137
        synchronized (bufferLock){
138
            paused = true;
139
        }
140 1 1. pause : removed call to javax/sound/sampled/SourceDataLine::stop → KILLED
        line.stop();
141
    }
142
143
    @Override
144
    public synchronized void resume(){
145 2 1. resume : negated conditional → KILLED
2. resume : negated conditional → KILLED
        if (!running || !paused){
146
            return;
147
        }
148 1 1. resume : removed call to javax/sound/sampled/SourceDataLine::start → KILLED
        line.start();
149
        synchronized (bufferLock){
150
            paused = false;
151 1 1. resume : removed call to java/lang/Object::notifyAll → KILLED
            bufferLock.notifyAll();
152
        }
153
    }
154
155
    @Override
156
    public void write(final double sample){
157
        final short pcm = toPcm16(sample);
158
        synchronized (bufferLock){
159 1 1. write : negated conditional → KILLED
            if (bufferedCount == ringBuffer.length){
160
                //buffer full: drop the oldest sample rather than block the emulation thread
161 2 1. write : Replaced integer modulus with multiplication → KILLED
2. write : Replaced integer addition with subtraction → KILLED
                readIndex = (readIndex + 1) % ringBuffer.length;
162 1 1. write : Replaced integer subtraction with addition → SURVIVED
                bufferedCount--;
163
                droppedSampleCount.incrementAndGet();
164
            }
165
            ringBuffer[writeIndex] = pcm;
166 2 1. write : Replaced integer modulus with multiplication → KILLED
2. write : Replaced integer addition with subtraction → KILLED
            writeIndex = (writeIndex + 1) % ringBuffer.length;
167 1 1. write : Replaced integer addition with subtraction → KILLED
            bufferedCount++;
168 1 1. write : removed call to java/lang/Object::notify → KILLED
            bufferLock.notify();
169
        }
170
    }
171
172
    static short toPcm16(final double sample){
173 1 1. toPcm16 : Replaced double multiplication with division → KILLED
        final double scaled = sample * Short.MAX_VALUE;
174
        final double clamped = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, scaled));
175 1 1. toPcm16 : replaced short return with 0 for com/rox/audio/SpeakerAudioOutput::toPcm16 → KILLED
        return (short) Math.round(clamped);
176
    }
177
178
    private void drainBufferToLine(){
179
        //XXX Mutation coverage expected to have issues here since it deals with real hardware - just accepting it for now
180
        final short[] chunk = new short[WRITE_CHUNK_SAMPLES];
181
        boolean firstChunkWritten = false;
182 1 1. drainBufferToLine : negated conditional → KILLED
        while (running){
183
            final int chunkLength;
184
            synchronized (bufferLock){
185
                //wait for a full batch (not just "any data") so line.write() is actually called in
186
                //WRITE_CHUNK_SAMPLES-sized batches as intended, rather than draining whatever trickle
187
                //is present the instant a single write() call notifies us - the latter defeats the
188
                //batching entirely (a fresh byte[] + native call per 1-3 samples instead of per 1024),
189
                //and the resulting overhead/GC churn was itself a source of the underruns it was meant
190
                //to avoid. Once any data has arrived, partialBatchTimeoutMillis bounds how long we'll
191
                //hold out for a full batch before flushing a smaller one - without this, a producer that
192
                //never quite reaches a full batch (sparse/slow production, or simply winding down) would
193
                //never get flushed at all.
194
                long batchStartNanos = -1;
195 4 1. drainBufferToLine : changed conditional boundary → KILLED
2. drainBufferToLine : negated conditional → KILLED
3. drainBufferToLine : negated conditional → KILLED
4. drainBufferToLine : negated conditional → KILLED
                while ((paused || bufferedCount < WRITE_CHUNK_SAMPLES) && running){
196 1 1. drainBufferToLine : negated conditional → KILLED
                    if (paused){
197
                        try {
198 1 1. drainBufferToLine : removed call to java/lang/Object::wait → TIMED_OUT
                            bufferLock.wait();
199
                        } catch (InterruptedException e){
200 1 1. drainBufferToLine : removed call to java/lang/Thread::interrupt → NO_COVERAGE
                            Thread.currentThread().interrupt();
201
                            return;
202
                        }
203
                        batchStartNanos = -1; //time spent paused doesn't count against a partial batch
204
                        continue;
205
                    }
206 1 1. drainBufferToLine : negated conditional → KILLED
                    if (bufferedCount == 0){
207
                        try {
208 1 1. drainBufferToLine : removed call to java/lang/Object::wait → SURVIVED
                            bufferLock.wait();
209
                        } catch (InterruptedException e){
210 1 1. drainBufferToLine : removed call to java/lang/Thread::interrupt → NO_COVERAGE
                            Thread.currentThread().interrupt();
211
                            return;
212
                        }
213
                        continue;
214
                    }
215 2 1. drainBufferToLine : changed conditional boundary → SURVIVED
2. drainBufferToLine : negated conditional → SURVIVED
                    if (batchStartNanos < 0){
216
                        batchStartNanos = System.nanoTime();
217
                    }
218
                    final long remainingMillis = partialBatchTimeoutMillis
219 3 1. drainBufferToLine : Replaced long subtraction with addition → SURVIVED
2. drainBufferToLine : Replaced long division with multiplication → KILLED
3. drainBufferToLine : Replaced long subtraction with addition → KILLED
                            - (System.nanoTime() - batchStartNanos) / 1_000_000;
220 2 1. drainBufferToLine : changed conditional boundary → SURVIVED
2. drainBufferToLine : negated conditional → KILLED
                    if (remainingMillis <= 0){
221
                        break;
222
                    }
223
                    try {
224 1 1. drainBufferToLine : removed call to java/lang/Object::wait → SURVIVED
                        bufferLock.wait(remainingMillis);
225
                    } catch (InterruptedException e){
226 1 1. drainBufferToLine : removed call to java/lang/Thread::interrupt → NO_COVERAGE
                        Thread.currentThread().interrupt();
227
                        return;
228
                    }
229
                }
230 1 1. drainBufferToLine : negated conditional → KILLED
                if (!running){
231
                    return;
232
                }
233
                chunkLength = Math.min(bufferedCount, WRITE_CHUNK_SAMPLES);
234 2 1. drainBufferToLine : negated conditional → KILLED
2. drainBufferToLine : changed conditional boundary → KILLED
                for (int i = 0; i < chunkLength; i++){
235
                    chunk[i] = ringBuffer[readIndex];
236 2 1. drainBufferToLine : Replaced integer modulus with multiplication → KILLED
2. drainBufferToLine : Replaced integer addition with subtraction → KILLED
                    readIndex = (readIndex + 1) % ringBuffer.length;
237
                }
238 1 1. drainBufferToLine : Replaced integer subtraction with addition → KILLED
                bufferedCount -= chunkLength;
239
            }
240
            //a fresh array each call (not the reused `chunk`) so the bytes handed to the line can't
241
            //be mutated by the next iteration before they're actually consumed
242 1 1. drainBufferToLine : Replaced integer multiplication with division → KILLED
            final byte[] frame = new byte[chunkLength * BYTES_PER_SAMPLE];
243 2 1. drainBufferToLine : negated conditional → KILLED
2. drainBufferToLine : changed conditional boundary → KILLED
            for (int i = 0; i < chunkLength; i++){
244 2 1. drainBufferToLine : Replaced integer multiplication with division → SURVIVED
2. drainBufferToLine : Replaced bitwise AND with OR → KILLED
                frame[i * BYTES_PER_SAMPLE] = (byte) (chunk[i] & 0xFF);
245 4 1. drainBufferToLine : Replaced integer multiplication with division → SURVIVED
2. drainBufferToLine : Replaced Shift Right with Shift Left → KILLED
3. drainBufferToLine : Replaced integer addition with subtraction → KILLED
4. drainBufferToLine : Replaced bitwise AND with OR → KILLED
                frame[i * BYTES_PER_SAMPLE + 1] = (byte) ((chunk[i] >> 8) & 0xFF);
246
            }
247
            //diagnostic: if the line's own buffer is completely empty right before we feed it more
248
            //data (and this isn't the very first chunk, when that's expected), playback has already
249
            //gone silent since the last write - a definite underrun, not just a risk of one
250 2 1. drainBufferToLine : negated conditional → SURVIVED
2. drainBufferToLine : negated conditional → SURVIVED
            if (firstChunkWritten && line.available() == line.getBufferSize()){
251
                underrunCount.incrementAndGet();
252
            }
253 1 1. drainBufferToLine : negated conditional → KILLED
            if (!writeFrameSurvivingPauses(frame)){
254
                return;
255
            }
256
            firstChunkWritten = true;
257
        }
258
    }
259
260
    /**
261
     * {@link SourceDataLine#write} returns early, part-written, only if the line is stopped, closed or
262
     * flushed mid-write. This class never flushes and only closes after {@code running} goes false, so a
263
     * short write while still running means {@link #pause()} stopped the line - even if a quick
264
     * {@link #resume()} has already cleared {@code paused} again, which is why that flag isn't what's
265
     * checked. The frame has already left the ring buffer, so rather than dropping its unwritten tail
266
     * (up to a whole batch of audio skipped on resume), wait out any pause still in effect and write
267
     * the rest.
268
     *
269
     * @return false if stopped or interrupted while waiting to resume
270
     */
271
    private boolean writeFrameSurvivingPauses(final byte[] frame){
272
        int offset = 0;
273
        while (true){
274 2 1. writeFrameSurvivingPauses : Replaced integer subtraction with addition → KILLED
2. writeFrameSurvivingPauses : Replaced integer addition with subtraction → KILLED
            offset += line.write(frame, offset, frame.length - offset);
275 2 1. writeFrameSurvivingPauses : negated conditional → KILLED
2. writeFrameSurvivingPauses : changed conditional boundary → KILLED
            if (offset >= frame.length){
276 1 1. writeFrameSurvivingPauses : replaced boolean return with false for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → KILLED
                return true;
277
            }
278
            synchronized (bufferLock){
279
                //no separate running check needed: stop() clears paused under this same lock
280 1 1. writeFrameSurvivingPauses : negated conditional → KILLED
                while (paused){
281
                    try {
282 1 1. writeFrameSurvivingPauses : removed call to java/lang/Object::wait → TIMED_OUT
                        bufferLock.wait();
283
                    } catch (InterruptedException e){
284 1 1. writeFrameSurvivingPauses : removed call to java/lang/Thread::interrupt → NO_COVERAGE
                        Thread.currentThread().interrupt();
285 2 1. writeFrameSurvivingPauses : replaced boolean return with true for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → NO_COVERAGE
2. writeFrameSurvivingPauses : replaced boolean return with false for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → NO_COVERAGE
                        return false;
286
                    }
287
                }
288
            }
289 1 1. writeFrameSurvivingPauses : negated conditional → KILLED
            if (!running){
290 1 1. writeFrameSurvivingPauses : replaced boolean return with true for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → SURVIVED
                return false;
291
            }
292
        }
293
    }
294
}

Mutations

84

1.1
Location : openDefaultLine
Killed by : none
Replaced integer multiplication with division → NO_COVERAGE

85

1.1
Location : openDefaultLine
Killed by : none
removed call to javax/sound/sampled/SourceDataLine::open → NO_COVERAGE

86

1.1
Location : openDefaultLine
Killed by : none
replaced return value with null for com/rox/audio/SpeakerAudioOutput::openDefaultLine → NO_COVERAGE

91

1.1
Location : start
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:stopWhilePausedStillClosesTheLine()]
negated conditional → KILLED

95

1.1
Location : start
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:resumeWithoutPauseDoesNotRestartTheLine()]
removed call to javax/sound/sampled/SourceDataLine::start → KILLED

97

1.1
Location : start
Killed by : none
removed call to java/lang/Thread::setDaemon → SURVIVED
Covering tests

100

1.1
Location : start
Killed by : none
removed call to java/lang/Thread::setPriority → SURVIVED
Covering tests

101

1.1
Location : start
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
removed call to java/lang/Thread::start → KILLED

106

1.1
Location : stop
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[test-template:toPcm16ScalesTheUnipolarAnalogRangeDirectlyOntoSignedPcm16(double, short)]/[test-template-invocation:#4]
negated conditional → KILLED

112

1.1
Location : stop
Killed by : none
removed call to java/lang/Object::notifyAll → SURVIVED
Covering tests

116

1.1
Location : stop
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:stopAfterStartStopsAndClosesTheLineOnce()]
removed call to javax/sound/sampled/SourceDataLine::stop → KILLED

118

1.1
Location : stop
Killed by : none
removed call to java/lang/Thread::join → SURVIVED
Covering tests

120

1.1
Location : stop
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

122

1.1
Location : stop
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:stopWhilePausedStillClosesTheLine()]
removed call to javax/sound/sampled/SourceDataLine::close → KILLED

123

1.1
Location : stop
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

134

1.1
Location : pause
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:pauseBeforeStartDoesNotTouchTheLine()]
negated conditional → KILLED

2.2
Location : pause
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:resumeRestartsAPausedLineOnce()]
negated conditional → KILLED

140

1.1
Location : pause
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:pauseStopsTheLineOnceWithoutClosingIt()]
removed call to javax/sound/sampled/SourceDataLine::stop → KILLED

145

1.1
Location : resume
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:resumeRestartsAPausedLineOnce()]
negated conditional → KILLED

2.2
Location : resume
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:resumeWithoutPauseDoesNotRestartTheLine()]
negated conditional → KILLED

148

1.1
Location : resume
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:resumeRestartsAPausedLineOnce()]
removed call to javax/sound/sampled/SourceDataLine::start → KILLED

151

1.1
Location : resume
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:pausedWriterHoldsBufferedSamplesUntilResumed()]
removed call to java/lang/Object::notifyAll → KILLED

159

1.1
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
negated conditional → KILLED

161

1.1
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writingBeyondBufferCapacityDropsTheOldestSampleNotTheNewest()]
Replaced integer modulus with multiplication → KILLED

2.2
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writingBeyondBufferCapacityDropsTheOldestSampleNotTheNewest()]
Replaced integer addition with subtraction → KILLED

162

1.1
Location : write
Killed by : none
Replaced integer subtraction with addition → SURVIVED
Covering tests

166

1.1
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer modulus with multiplication → KILLED

2.2
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer addition with subtraction → KILLED

167

1.1
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer addition with subtraction → KILLED

168

1.1
Location : write
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writtenSampleReachesTheLineAsLittleEndianPcm16()]
removed call to java/lang/Object::notify → KILLED

173

1.1
Location : toPcm16
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[test-template:toPcm16ScalesTheUnipolarAnalogRangeDirectlyOntoSignedPcm16(double, short)]/[test-template-invocation:#4]
Replaced double multiplication with division → KILLED

175

1.1
Location : toPcm16
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[test-template:toPcm16ScalesTheUnipolarAnalogRangeDirectlyOntoSignedPcm16(double, short)]/[test-template-invocation:#4]
replaced short return with 0 for com/rox/audio/SpeakerAudioOutput::toPcm16 → KILLED

182

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
negated conditional → KILLED

195

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
changed conditional boundary → KILLED

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
negated conditional → KILLED

3.3
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
negated conditional → KILLED

4.4
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
negated conditional → KILLED

196

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
negated conditional → KILLED

198

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Object::wait → TIMED_OUT

200

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

206

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
negated conditional → KILLED

208

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Object::wait → SURVIVED
Covering tests

210

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

215

1.1
Location : drainBufferToLine
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : drainBufferToLine
Killed by : none
negated conditional → SURVIVED Covering tests

219

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
Replaced long division with multiplication → KILLED

2.2
Location : drainBufferToLine
Killed by : none
Replaced long subtraction with addition → SURVIVED
Covering tests

3.3
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
Replaced long subtraction with addition → KILLED

220

1.1
Location : drainBufferToLine
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aPartialBatchIsFlushedAfterABoundedWaitRatherThanInstantlyOrNever()]
negated conditional → KILLED

224

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Object::wait → SURVIVED
Covering tests

226

1.1
Location : drainBufferToLine
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

230

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
negated conditional → KILLED

234

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writtenSampleReachesTheLineAsLittleEndianPcm16()]
negated conditional → KILLED

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
changed conditional boundary → KILLED

236

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer modulus with multiplication → KILLED

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer addition with subtraction → KILLED

238

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writerKeepsDeliveringFramesAfterAFullyWrittenOne()]
Replaced integer subtraction with addition → KILLED

242

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer multiplication with division → KILLED

243

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writtenSampleReachesTheLineAsLittleEndianPcm16()]
negated conditional → KILLED

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
changed conditional boundary → KILLED

244

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writingBeyondBufferCapacityDropsTheOldestSampleNotTheNewest()]
Replaced bitwise AND with OR → KILLED

2.2
Location : drainBufferToLine
Killed by : none
Replaced integer multiplication with division → SURVIVED
Covering tests

245

1.1
Location : drainBufferToLine
Killed by : none
Replaced integer multiplication with division → SURVIVED
Covering tests

2.2
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writtenSampleReachesTheLineAsLittleEndianPcm16()]
Replaced Shift Right with Shift Left → KILLED

3.3
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:aFullBatchIsFlushedPromptlyWithoutWaitingForThePartialBatchTimeout()]
Replaced integer addition with subtraction → KILLED

4.4
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writingBeyondBufferCapacityDropsTheOldestSampleNotTheNewest()]
Replaced bitwise AND with OR → KILLED

250

1.1
Location : drainBufferToLine
Killed by : none
negated conditional → SURVIVED
Covering tests

2.2
Location : drainBufferToLine
Killed by : none
negated conditional → SURVIVED Covering tests

253

1.1
Location : drainBufferToLine
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writerKeepsDeliveringFramesAfterAFullyWrittenOne()]
negated conditional → KILLED

274

1.1
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:frameCutShortByAPauseAndResumeBothDuringTheWriteStillHasItsRemainderWritten()]
Replaced integer subtraction with addition → KILLED

2.2
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:frameCutShortByAPauseAndResumeBothDuringTheWriteStillHasItsRemainderWritten()]
Replaced integer addition with subtraction → KILLED

275

1.1
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:frameCutShortByAPauseAndResumeBothDuringTheWriteStillHasItsRemainderWritten()]
negated conditional → KILLED

2.2
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writerKeepsDeliveringFramesAfterAFullyWrittenOne()]
changed conditional boundary → KILLED

276

1.1
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:writerKeepsDeliveringFramesAfterAFullyWrittenOne()]
replaced boolean return with false for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → KILLED

280

1.1
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:frameCutShortByAPauseAndResumeBothDuringTheWriteStillHasItsRemainderWritten()]
negated conditional → KILLED

282

1.1
Location : writeFrameSurvivingPauses
Killed by : none
removed call to java/lang/Object::wait → TIMED_OUT

284

1.1
Location : writeFrameSurvivingPauses
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

285

1.1
Location : writeFrameSurvivingPauses
Killed by : none
replaced boolean return with true for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → NO_COVERAGE

2.2
Location : writeFrameSurvivingPauses
Killed by : none
replaced boolean return with false for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → NO_COVERAGE

289

1.1
Location : writeFrameSurvivingPauses
Killed by : com.rox.audio.SpeakerAudioOutputTest.[engine:junit-jupiter]/[class:com.rox.audio.SpeakerAudioOutputTest]/[method:frameCutShortByAPauseAndResumeBothDuringTheWriteStillHasItsRemainderWritten()]
negated conditional → KILLED

290

1.1
Location : writeFrameSurvivingPauses
Killed by : none
replaced boolean return with true for com/rox/audio/SpeakerAudioOutput::writeFrameSurvivingPauses → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.22.1