// Copyright (c) 2026 Ourpad Network // See LICENSE file in the project root for full license text. #include #include #include #include #include "mixer/MixerEngine.hpp" #include "mixer/MixerChannelStrip.hpp" #include "mixer/MixerBus.hpp" using namespace mixer; // Counters for test results static int testsPassed = 0; static int testsFailed = 0; #define TEST(name) \ do { \ printf(" TEST: %s ... ", name); \ bool ok = true; #define END_TEST \ if (ok) { \ printf("PASS\n"); \ testsPassed++; \ } else { \ printf("FAIL\n"); \ testsFailed++; \ } \ } while(0) // --------------------------------------------------------------------------- // Test: Channel strip volume control // --------------------------------------------------------------------------- void testChannelVolume() { TEST("Channel strip volume control"); MixerChannelStrip strip(0); strip.setVolume(0.0f); // unity assert(std::abs(strip.volume() - 0.0f) < 0.001f); strip.setVolume(-6.0f); assert(std::abs(strip.volume() - (-6.0f)) < 0.001f); strip.setVolume(12.0f); // max assert(std::abs(strip.volume() - 12.0f) < 0.001f); strip.setVolume(-999.0f); // clamp assert(std::abs(strip.volume() - (-96.0f)) < 0.001f); END_TEST; } // --------------------------------------------------------------------------- // Test: Channel strip pan // --------------------------------------------------------------------------- void testChannelPan() { TEST("Channel strip pan"); MixerChannelStrip strip(0); strip.setPan(0.0f); // center assert(std::abs(strip.pan() - 0.0f) < 0.001f); strip.setPan(-1.0f); // full left assert(std::abs(strip.pan() - (-1.0f)) < 0.001f); strip.setPan(1.0f); // full right assert(std::abs(strip.pan() - 1.0f) < 0.001f); strip.setPan(-2.0f); // clamp assert(std::abs(strip.pan() - (-1.0f)) < 0.001f); END_TEST; } // --------------------------------------------------------------------------- // Test: Channel strip mute and solo // --------------------------------------------------------------------------- void testChannelMuteSolo() { TEST("Channel strip mute/solo"); MixerChannelStrip strip(0); assert(strip.mute() == false); assert(strip.solo() == false); strip.setMute(true); assert(strip.mute() == true); strip.setSolo(true); assert(strip.solo() == true); END_TEST; } // --------------------------------------------------------------------------- // Test: Channel strip HPF // --------------------------------------------------------------------------- void testChannelHpf() { TEST("Channel strip HPF"); MixerChannelStrip strip(0); strip.setHpEnabled(true); assert(strip.hpEnabled() == true); strip.setHpFrequency(120.0f); assert(std::abs(strip.hpFrequency() - 120.0f) < 0.001f); END_TEST; } // --------------------------------------------------------------------------- // Test: Bus volume and mute // --------------------------------------------------------------------------- void testBusControl() { TEST("Bus volume and mute"); MixerBus bus(1, MixerBusType::Subgroup, "TestBus", 2); bus.allocateBuffers(256); bus.setVolume(0.0f); assert(std::abs(bus.volume() - 0.0f) < 0.001f); bus.setVolume(-12.0f); assert(std::abs(bus.volume() - (-12.0f)) < 0.001f); bus.setMute(true); assert(bus.mute() == true); END_TEST; } // --------------------------------------------------------------------------- // Test: Bus accumulate and process // --------------------------------------------------------------------------- void testBusAccumulate() { TEST("Bus accumulate and process"); MixerBus bus(2, MixerBusType::Master, "Master", 2); bus.allocateBuffers(64); // Create a sine wave buffer float buffer[64]; for (int i = 0; i < 64; ++i) { buffer[i] = sinf(2.0f * M_PI * i / 16.0f) * 0.5f; } const float* src[2] = { buffer, buffer }; bus.accumulate(src, 64, 1.0f, 2); // After accumulation, buffers should contain the sine wave for (int i = 0; i < 64; ++i) { assert(std::abs(bus.buffer(0)[i] - buffer[i]) < 0.001f); assert(std::abs(bus.buffer(1)[i] - buffer[i]) < 0.001f); } // Process with unity gain — should keep values bus.process(64); // VU should show something (peak ~= 0.5 = -6dB) assert(bus.vuLeft() > -12.0f && bus.vuLeft() < 0.0f); // Clear and check zero bus.clear(); for (int i = 0; i < 64; ++i) { assert(std::abs(bus.buffer(0)[i]) < 0.0001f); } END_TEST; } // --------------------------------------------------------------------------- // Test: MixerEngine lifecycle // --------------------------------------------------------------------------- void testEngineLifecycle() { TEST("MixerEngine lifecycle"); MixerEngine engine; // Start with master bus only assert(engine.channelCount() == 0); assert(engine.busIds().size() == 1); // master // Add channels engine.setSampleRate(48000); engine.setMaxBufferSize(256); auto* ch0 = engine.addChannel(0); assert(ch0 != nullptr); assert(engine.channelCount() == 1); auto* ch1 = engine.addChannel(1); assert(ch1 != nullptr); assert(engine.channelCount() == 2); // Add a subgroup bus int64_t sgId = engine.addBus(MixerBusType::Subgroup, "Drums", 2); assert(engine.getBus(sgId) != nullptr); // Route channel 0 to subgroup engine.routeChannelToBus(0, sgId, 0.0f); engine.Prepare(); engine.Activate(); // Capture and restore snapshot auto snap = engine.captureSnapshot(); assert(snap.channels.size() == 2); assert(snap.buses.size() == 2); // master + subgroup // Modify, then restore ch0->setVolume(-12.0f); engine.applySnapshot(snap); // After applySnapshot, volume should be back to snapshot value (-96.0f default) assert(std::abs(ch0->volume() - (-96.0f)) < 0.001f); // Actually, applySnapshot should reset to the captured state // Let's check: we captured BEFORE changing volume, so snapshot has volume=0 // After apply, volume should be 0 (the snapshot value) not -12 // But we set volume after capture... wait, let me re-read the test // Volume was set to default (-96), then snapshot captured (volume=-96) // Then set to -12, then apply snapshot -> should go back to -96 engine.Deactivate(); END_TEST; } // --------------------------------------------------------------------------- // Test: MixerEngine processing // --------------------------------------------------------------------------- void testEngineProcess() { TEST("MixerEngine processing (simple passthrough)"); MixerEngine engine; engine.setSampleRate(48000); engine.setMaxBufferSize(128); // Create 1 channel engine.addChannel(0); engine.getChannel(0)->setVolume(0.0f); // unity engine.Prepare(); engine.Activate(); // Create input buffer with a ramp float input0[128]; float output0[128] = {0}; float output1[128] = {0}; for (int i = 0; i < 128; ++i) { input0[i] = (float)i / 128.0f; } float* deviceInputs[] = { input0 }; float* deviceOutputs[] = { output0, output1 }; engine.process(deviceInputs, 1, deviceOutputs, 2, 128); // Output should match input (unity gain, pan center = -3dB each) for (int i = 0; i < 128; ++i) { // Center pan = -3dB per channel = ~0.707 float expected = input0[i] * 0.707f; assert(std::abs(output0[i] - expected) < 0.01f); assert(std::abs(output1[i] - expected) < 0.01f); } engine.Deactivate(); END_TEST; } // --------------------------------------------------------------------------- // Test: MixerEngine autoCreateChannels // --------------------------------------------------------------------------- void testAutoCreateChannels() { TEST("MixerEngine autoCreateChannels"); MixerEngine engine; engine.autoCreateChannels(4, 2); assert(engine.channelCount() == 4); assert(engine.physicalInputCount() == 4); assert(engine.physicalOutputCount() == 2); // Each channel should have a default label for (size_t i = 0; i < engine.channelCount(); ++i) { auto* ch = engine.getChannel(i); assert(ch != nullptr); assert(ch->label().find("Input") != std::string::npos); } // Master bus should be there assert(engine.masterBus() != nullptr); // Default output route should route master to 1-2 assert(engine.outputRoutes().size() == 1); assert(engine.outputRoutes()[0].sourceBusId == engine.masterBus()->id()); END_TEST; } // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- int main() { printf("OPLabs Mixer Engine Standalone — Core Tests\n"); printf("=============================================\n\n"); testChannelVolume(); testChannelPan(); testChannelMuteSolo(); testChannelHpf(); testBusControl(); testBusAccumulate(); testEngineLifecycle(); testEngineProcess(); testAutoCreateChannels(); printf("\n=============================================\n"); printf("Results: %d passed, %d failed out of %d\n", testsPassed, testsFailed, testsPassed + testsFailed); return testsFailed > 0 ? 1 : 0; }