| 1 | package com.rox.apu; | |
| 2 | ||
| 3 | /** | |
| 4 | * Combines the five NES APU channel outputs into a single sample (~0.0-1.0). | |
| 5 | * | |
| 6 | * <p>Real NES hardware doesn't mix its 5 channels with a simple average, and it doesn't mix them | |
| 7 | * all together in one place either. Two separate analog resistor networks on the chip each sum a | |
| 8 | * subset of the channels, and each network's output voltage is a <em>non-linear</em> function of | |
| 9 | * its inputs (a consequence of the physical circuit, not a deliberate audio design choice): | |
| 10 | * <ul> | |
| 11 | * <li>one network sums {@code pulse1} + {@code pulse2} ({@link #pulseOut});</li> | |
| 12 | * <li>the other sums {@code triangle} + {@code noise} + {@code dmc} - commonly abbreviated | |
| 13 | * "TND" in NES documentation and emulator source - ({@link #tndOut}).</li> | |
| 14 | * </ul> | |
| 15 | * The two formulas below are curve-fits (reverse-engineered from real hardware measurements) for | |
| 16 | * those two circuits' voltage output, not something derived from first principles - which is why | |
| 17 | * their constants (95.88, 8128, 8227, 12241, 22638, ...) don't individually mean anything; they're | |
| 18 | * just the coefficients that reproduce the measured curves. {@code output} is simply the sum of | |
| 19 | * both circuits' voltages, since on real hardware that's the final combined analog signal. | |
| 20 | * | |
| 21 | * <p>{@code pulse1}/{@code pulse2}/{@code triangle}/{@code noise} are 0-15, {@code dmc} is 0-127. | |
| 22 | * | |
| 23 | * <p>Deliberately not precomputing a single lookup table for {@link #tndOut} keyed by a combined | |
| 24 | * index like {@code 3*triangle + 2*noise + dmc} (a common optimisation in other NES emulators): | |
| 25 | * that collapses three independently-weighted terms (divisors 8227/12241/22638 - not proportional | |
| 26 | * to 3:2:1) into one dimension, which is only an approximation of the real circuit's curve, not an | |
| 27 | * exact optimisation of it. The direct computation below is exact and cheap enough per call that | |
| 28 | * the accuracy trade-off isn't worth making. | |
| 29 | */ | |
| 30 | public final class Mixer { | |
| 31 | /** Curve-fit coefficients for the pulse1+pulse2 summing circuit's output voltage. */ | |
| 32 | private static final double PULSE_CIRCUIT_NUMERATOR = 95.88; | |
| 33 | private static final double PULSE_CIRCUIT_DIVISOR_CONSTANT = 8128.0; | |
| 34 | private static final double PULSE_CIRCUIT_OFFSET = 100.0; | |
| 35 | ||
| 36 | /** Curve-fit coefficients for the triangle+noise+dmc ("TND") summing circuit's output voltage. */ | |
| 37 | private static final double TND_CIRCUIT_NUMERATOR = 159.79; | |
| 38 | private static final double TRIANGLE_CONTRIBUTION_DIVISOR = 8227.0; | |
| 39 | private static final double NOISE_CONTRIBUTION_DIVISOR = 12241.0; | |
| 40 | private static final double DMC_CONTRIBUTION_DIVISOR = 22638.0; | |
| 41 | private static final double TND_CIRCUIT_OFFSET = 100.0; | |
| 42 | ||
| 43 | private Mixer(){ | |
| 44 | /** | |
| 45 | * XXX Code coverage fails us here. This constructors only job is to make `new Mixer()` a compile error, | |
| 46 | * since a utility class with only static methods being instantiable is meaningless. If I remove it, | |
| 47 | * Java auto-generates a public no-arg constructor, so `new Mixer()` becomes legal (harmless, but there's | |
| 48 | * no longer anything stopping it). | |
| 49 | */ | |
| 50 | } | |
| 51 | ||
| 52 | /** The combined analog output voltage of both summing circuits, as they'd appear on real hardware. */ | |
| 53 | public static double mix(final int pulse1, | |
| 54 | final int pulse2, | |
| 55 | final int triangle, | |
| 56 | final int noise, | |
| 57 | final int dmc){ | |
| 58 |
2
1. mix : Replaced double addition with subtraction → KILLED 2. mix : replaced double return with 0.0d for com/rox/apu/Mixer::mix → KILLED |
return pulseOut(pulse1, pulse2) + tndOut(triangle, noise, dmc); |
| 59 | } | |
| 60 | ||
| 61 | /** The pulse1+pulse2 summing circuit's output voltage (0 when both channels are silent). */ | |
| 62 | private static double pulseOut(final int pulse1, | |
| 63 | final int pulse2){ | |
| 64 |
1
1. pulseOut : Replaced integer addition with subtraction → KILLED |
final int sum = pulse1 + pulse2; |
| 65 |
1
1. pulseOut : negated conditional → KILLED |
if (sum == 0){ |
| 66 | return 0; | |
| 67 | } | |
| 68 |
4
1. pulseOut : replaced double return with 0.0d for com/rox/apu/Mixer::pulseOut → KILLED 2. pulseOut : Replaced double division with multiplication → KILLED 3. pulseOut : Replaced double addition with subtraction → KILLED 4. pulseOut : Replaced double division with multiplication → KILLED |
return PULSE_CIRCUIT_NUMERATOR / (PULSE_CIRCUIT_DIVISOR_CONSTANT / sum + PULSE_CIRCUIT_OFFSET); |
| 69 | } | |
| 70 | ||
| 71 | /** The triangle+noise+dmc ("TND") summing circuit's output voltage (0 when all three are silent). */ | |
| 72 | private static double tndOut(final int triangle, final int noise, final int dmc){ | |
| 73 |
3
1. tndOut : negated conditional → SURVIVED 2. tndOut : negated conditional → KILLED 3. tndOut : negated conditional → KILLED |
if (triangle == 0 && noise == 0 && dmc == 0){ |
| 74 | return 0; | |
| 75 | } | |
| 76 |
5
1. tndOut : Replaced double division with multiplication → KILLED 2. tndOut : Replaced double addition with subtraction → KILLED 3. tndOut : Replaced double addition with subtraction → KILLED 4. tndOut : Replaced double division with multiplication → KILLED 5. tndOut : Replaced double division with multiplication → KILLED |
final double combinedContribution = triangle / TRIANGLE_CONTRIBUTION_DIVISOR |
| 77 | + noise / NOISE_CONTRIBUTION_DIVISOR | |
| 78 | + dmc / DMC_CONTRIBUTION_DIVISOR; | |
| 79 |
4
1. tndOut : Replaced double division with multiplication → KILLED 2. tndOut : Replaced double addition with subtraction → KILLED 3. tndOut : Replaced double division with multiplication → KILLED 4. tndOut : replaced double return with 0.0d for com/rox/apu/Mixer::tndOut → KILLED |
return TND_CIRCUIT_NUMERATOR / (1.0 / combinedContribution + TND_CIRCUIT_OFFSET); |
| 80 | } | |
| 81 | } | |
Mutations | ||
| 58 |
1.1 2.2 |
|
| 64 |
1.1 |
|
| 65 |
1.1 |
|
| 68 |
1.1 2.2 3.3 4.4 |
|
| 73 |
1.1 2.2 3.3 |
|
| 76 |
1.1 2.2 3.3 4.4 5.5 |
|
| 79 |
1.1 2.2 3.3 4.4 |