Signal below threshold, soft expander (ratio 2:1, floor -15 dB): expected reduction = min(floor, (T - env) * (ratio - 1)) with env = -50 dBFS, T = -40 dB, r = 2 → shortfall 10, slope 1 → -10 dB
| 147 | // expected reduction = min(floor, (T - env) * (ratio - 1)) |
| 148 | // with env = -50 dBFS, T = -40 dB, r = 2 → shortfall 10, slope 1 → -10 dB |
| 149 | void testExpanderBelowThreshold() |
| 150 | { |
| 151 | ClientGate gate; |
| 152 | gate.prepare(kSampleRate); |
| 153 | gate.setEnabled(true); |
| 154 | gate.setThresholdDb(-40.0f); |
| 155 | gate.setRatio(2.0f); |
| 156 | gate.setAttackMs(0.5f); |
| 157 | gate.setReleaseMs(10.0f); // fast release so envelope settles quickly |
| 158 | gate.setHoldMs(0.0f); |
| 159 | gate.setFloorDb(-40.0f); // deep enough not to clamp the -10 dB expected |
| 160 | |
| 161 | const float ampIn = dbToLin(-50.0f); |
| 162 | // Long burst for envelope + gain to settle. |
| 163 | auto prime = makeTone(1000.0, 24000, ampIn); // 1 s |
| 164 | processBlocks(gate, prime.data(), 24000); |
| 165 | |
| 166 | const int tailFrames = 512; |
| 167 | auto tail = makeTone(1000.0, tailFrames, ampIn); |
| 168 | processBlocks(gate, tail.data(), tailFrames); |
| 169 | |
| 170 | const float outPeak = peakAbsStereo(tail.data(), tailFrames); |
| 171 | const float grDb = linToDb(outPeak / ampIn); |
| 172 | |
| 173 | // Expected ≈ -10 dB; allow ±1.5 dB for envelope ripple at low frequency. |
| 174 | report("expander: -50 dBFS → ~-10 dB reduction (2:1)", |
| 175 | std::fabs(grDb - (-10.0f)) < 1.5f, |
| 176 | "GR=" + std::to_string(grDb) + " dB (expected ~-10)"); |
| 177 | } |
| 178 | |
| 179 | // Hard gate (ratio 10:1): signal far below threshold gets clamped to `floor`. |
| 180 | void testGateClampsToRange() |
no test coverage detected