// Unit test for the audio mixer math (decode/sum/soft-clip/encode, float32 + int16). #include #include #include #include #include #include "audio/audio_mix.hpp" using namespace coop; namespace { int g_failures = 0; void check(bool ok, const char* what) { if (!ok) { std::printf("FAIL: %s\n", what); ++g_failures; } } bool near_f(float a, float b) { return std::fabs(a - b) < 1e-4f; } } // namespace int main() { check(mix_format_supported(kWaveFormatFloat, 32), "float32 supported"); check(mix_format_supported(kWaveFormatPcm, 16), "int16 supported"); check(!mix_format_supported(kWaveFormatPcm, 24), "24-bit not supported"); // soft_clip is ~identity for small inputs and bounded for large ones. check(near_f(soft_clip(0.0f), 0.0f), "soft_clip(0)=0"); check(soft_clip(10.0f) <= 1.0f && soft_clip(10.0f) > 0.99f, "soft_clip bounds large +"); check(soft_clip(-10.0f) >= -1.0f && soft_clip(-10.0f) < -0.99f, "soft_clip bounds large -"); // --- float32: two streams sum, small values pass ~unchanged --- { const float a[4] = {0.1f, -0.2f, 0.3f, -0.05f}; const float b[4] = {0.2f, 0.1f, -0.1f, 0.05f}; float acc[4] = {}; mix_add(acc, reinterpret_cast(a), 4, kWaveFormatFloat, 32); mix_add(acc, reinterpret_cast(b), 4, kWaveFormatFloat, 32); float out[4] = {}; mix_store(reinterpret_cast(out), acc, 4, kWaveFormatFloat, 32); // Sum then tanh; small sums are ~unchanged. for (int i = 0; i < 4; ++i) { check(near_f(out[i], std::tanh(a[i] + b[i])), "float32 mix == tanh(sum)"); } } // --- float32: summing many loud streams stays within [-1, 1] (soft clip) --- { float acc[2] = {}; const float loud[2] = {0.9f, -0.9f}; for (int s = 0; s < 5; ++s) { mix_add(acc, reinterpret_cast(loud), 2, kWaveFormatFloat, 32); } float out[2] = {}; mix_store(reinterpret_cast(out), acc, 2, kWaveFormatFloat, 32); check(out[0] <= 1.0f && out[0] > 0.99f, "loud sum soft-clipped near +1"); check(out[1] >= -1.0f && out[1] < -0.99f, "loud sum soft-clipped near -1"); } // --- int16: decode/encode round-trip of a single quiet stream --- { const std::int16_t a[2] = {1000, -2000}; float acc[2] = {}; mix_add(acc, reinterpret_cast(a), 2, kWaveFormatPcm, 16); check(near_f(acc[0], 1000.0f / 32768.0f) && near_f(acc[1], -2000.0f / 32768.0f), "int16 decode"); std::int16_t out[2] = {}; mix_store(reinterpret_cast(out), acc, 2, kWaveFormatPcm, 16); // tanh of a tiny value ~ the value, so re-encoding is within a couple of LSB. check(std::abs(out[0] - 1000) <= 3 && std::abs(out[1] - (-2000)) <= 3, "int16 round-trip"); } if (g_failures == 0) { std::printf("PASS: audio_mix_test\n"); return 0; } std::printf("FAIL: %d checks\n", g_failures); return 1; }