/* * MIT License * * Copyright (c) Robin E.R. Davies * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "pch.h" #include "catch.hpp" #include #include #include #include #include "AlsaDriver.hpp" #include "ChannelRouterSettings.hpp" using namespace pipedal; using namespace std; class AlsaTester : private AudioDriverHost { public: enum class TestType { Oscillator, LatencyMonitor, NullTest }; private: AudioDriver *audioDriver = nullptr; TestType testType; public: AlsaTester(TestType testType) : testType(testType) { // audioDriver = CreateAlsaDriver(this); // audioDriver = CreateJackDriver(this); } ~AlsaTester() { delete audioDriver; delete[] inputBuffers; delete[] outputBuffers; } bool OnRealtimeUpdateDeviceVus(size_t nFrames) override { return false; } void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection) { size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2)); size_t nOutputs = std::max(jackConfiguration.outputAudioPorts().size(),size_t(2)); auto &inputSelections = channelSelection.mainInputChannels(); auto &outputSelections = channelSelection.mainOutputChannels(); inputSelections.resize(nInputs); for (size_t i = 0; i < nInputs; ++i) { inputSelections[i] = i; } outputSelections.resize(nOutputs); for (size_t i = 0; i < nOutputs; ++i) { outputSelections[i] = i; } } void Test() { test::AlsaFormatEncodeDecodeTest(this); JackServerSettings serverSettings("hw:M2","hw:M2", 48000, 32, 3); JackConfiguration jackConfiguration; jackConfiguration.AlsaInitialize(serverSettings); ChannelSelection channelSelection; audioDriver = CreateAlsaDriver(this); initializeChannelSelection(jackConfiguration, channelSelection); oscillator.Init(440, jackConfiguration.sampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate()); audioDriver->Open(serverSettings, channelSelection); inputBuffers = new float *[channelSelection.mainInputChannels().size()]; outputBuffers = new float *[channelSelection.mainOutputChannels().size()]; audioDriver->Activate(); for (int i = 0; i < 10; ++i) { sleep(1); if (testType == TestType::LatencyMonitor) { auto latency = this->latencyMonitor.GetLatency(); double ms = 1000.0 * latency / jackConfiguration.sampleRate(); cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl; } } audioDriver->Deactivate(); audioDriver->Close(); } float **inputBuffers = nullptr; float **outputBuffers = nullptr; class Oscillator { private: double dx = 0; double x = 0; double dx2 = 0; double x2 = 0; public: void Init(float frequency, size_t sampleRate) { dx = frequency * 3.141592736 * 2 / sampleRate; dx2 = 0.5 * 3.141592736 * 2 / sampleRate; } float Next() { float result = (float)std::cos(x); float env = (float)std::cos(x2); x += dx; x2 += dx2; return result * env; } }; class LatencyMonitor { enum class State { Idle, Waiting, }; State state = State::Idle; uint64_t t; uint64_t idle_samples; uint64_t waiting_samples; size_t current_latency = 0; size_t latency = 0; std::mutex sync; public: void Init(uint64_t sampleRate) { idle_samples = (uint64_t)sampleRate * 2; waiting_samples = (uint64_t)sampleRate * 2; state = State::Idle; t = idle_samples; latency = 0; } size_t GetLatency() { std::lock_guard lock{sync}; return latency; } float Next(float input) { switch (state) { default: case State::Idle: { if (t-- == 0) { state = State::Waiting; current_latency = 0; } return 0.01; } break; case State::Waiting: { if (std::abs(input) > 0.1 || current_latency > 500) { { std::lock_guard lock{sync}; latency = current_latency; } state = State::Idle; t = idle_samples; } else { ++current_latency; } return current_latency < 100 ? 0.25 : 0.0; } break; } } }; Oscillator oscillator; LatencyMonitor latencyMonitor; virtual void OnAudioTerminated() { } virtual void OnAlsaDriverStopped() { } virtual void OnProcess(size_t nFrames) { if (testType == TestType::NullTest) return; size_t inputs = audioDriver->MainInputBufferCount(); size_t outputs = audioDriver->MainOutputBufferCount(); for (size_t i = 0; i < inputs; ++i) { inputBuffers[i] = audioDriver->GetMainInputBuffer(i); } for (size_t i = 0; i < outputs; ++i) { outputBuffers[i] = audioDriver->GetMainOutputBuffer(i); } if (this->testType == TestType::Oscillator) { for (size_t i = 0; i < nFrames; ++i) { float v = oscillator.Next() * 0.25f; for (size_t c = 0; c < outputs; ++c) { outputBuffers[c][i] = v; } } } else { for (size_t i = 0; i < nFrames; ++i) { float v = latencyMonitor.Next(inputBuffers[0][i]); for (size_t c = 0; c < outputs; ++c) { outputBuffers[c][i] = v; } } } } std::mutex sync; uint64_t xruns; uint64_t GetXruns() { lock_guard lock{sync}; return xruns; } virtual void OnUnderrun() { lock_guard lock{sync}; ++xruns; } }; TEST_CASE("alsa_test", "[alsa_test]") { AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); alsaDriver.Test(); } TEST_CASE("alsa_midi_test", "[alsa_midi_test]") { AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); test::MidiDecoderTest(); }