diff options
-rw-r--r-- | src/server/Engine.cpp | 6 | ||||
-rw-r--r-- | src/server/Engine.hpp | 2 | ||||
-rw-r--r-- | src/server/FrameTimer.hpp | 110 | ||||
-rw-r--r-- | src/server/PortAudioDriver.cpp | 24 | ||||
-rw-r--r-- | src/server/PortAudioDriver.hpp | 40 | ||||
-rw-r--r-- | src/server/PreProcessor.cpp | 2 |
6 files changed, 153 insertions, 31 deletions
diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp index c858fc6b..9a7f9e54 100644 --- a/src/server/Engine.cpp +++ b/src/server/Engine.cpp @@ -381,7 +381,7 @@ Engine::event_time() } uint64_t -Engine::current_time(const RunContext& context) const +Engine::current_time() const { return _clock.now_microseconds(); } @@ -449,7 +449,7 @@ unsigned Engine::run(uint32_t sample_count) { RunContext& ctx = run_context(); - _cycle_start_time = current_time(ctx); + _cycle_start_time = current_time(); post_processor()->set_end_time(ctx.end()); @@ -479,7 +479,7 @@ Engine::run(uint32_t sample_count) // Update load for this cycle if (ctx.duration() > 0) { - _run_load.update(current_time(ctx) - _cycle_start_time, ctx.duration()); + _run_load.update(current_time() - _cycle_start_time, ctx.duration()); } return n_processed_events; diff --git a/src/server/Engine.hpp b/src/server/Engine.hpp index 1ea048eb..96648afe 100644 --- a/src/server/Engine.hpp +++ b/src/server/Engine.hpp @@ -108,7 +108,7 @@ public: } /** Return the current time in microseconds. */ - uint64_t current_time(const RunContext& context) const; + uint64_t current_time() const; /** Reset the load statistics (when the expected DSP load changes). */ void reset_load(); diff --git a/src/server/FrameTimer.hpp b/src/server/FrameTimer.hpp new file mode 100644 index 00000000..367ac900 --- /dev/null +++ b/src/server/FrameTimer.hpp @@ -0,0 +1,110 @@ +/* + This file is part of Ingen. + Copyright 2017 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_ENGINE_FRAMETIMER_HPP +#define INGEN_ENGINE_FRAMETIMER_HPP + +#include <chrono> +#include <cmath> +#include <cstdint> + +namespace Ingen { +namespace Server { + +/** Delay-locked loop for monotonic sample time. + * + * See "Using a DLL to filter time" by Fons Adriaensen + * http://kokkinizita.linuxaudio.org/papers/usingdll.pdf + */ +class FrameTimer +{ +public: + static constexpr double PI = 3.14159265358979323846; + static constexpr double bandwidth = 1.0 / 8.0; // Hz + static constexpr double us_per_s = 1000000.0; + + FrameTimer(uint32_t period_size, uint32_t sample_rate) + : tper(((double)period_size / (double)sample_rate) * us_per_s) + , omega(2 * PI * bandwidth / us_per_s * tper) + , b(sqrt(2) * omega) + , c(omega * omega) + , nper(period_size) + { + } + + /** Update the timer for current real time `usec` and frame `frame`. */ + void update(uint64_t usec, uint64_t frame) { + if (!initialized || frame != n1) { + init(usec, frame); + return; + } + + // Calculate loop error + const double e = ((double)usec - t1); + + // Update loop + t0 = t1; + t1 += b * e + e2; + e2 += c * e; + + // Update frame counts + n0 = n1; + n1 += nper; + } + + /** Return an estimate of the frame time for current real time `usec`. */ + uint64_t frame_time(uint64_t usec) const { + if (!initialized) { + return 0; + } + + const double delta = (double)usec - t0; + const double period = t1 - t0; + return n0 + std::round(delta / period * nper); + } + +private: + void init(uint64_t now, uint64_t frame) { + // Init loop + e2 = tper; + t0 = now; + t1 = t0 + e2; + + // Init sample counts + n0 = frame; + n1 = n0 + nper; + + initialized = true; + } + + const double tper; + const double omega; + const double b; + const double c; + + uint64_t nper; + double e2; + double t0; + double t1; + uint64_t n0; + uint64_t n1; + bool initialized; +}; + +} // namespace Server +} // namespace Ingen + +#endif // INGEN_ENGINE_FRAMETIMER_HPP diff --git a/src/server/PortAudioDriver.cpp b/src/server/PortAudioDriver.cpp index 396ec4d4..5ceb0aa8 100644 --- a/src/server/PortAudioDriver.cpp +++ b/src/server/PortAudioDriver.cpp @@ -34,6 +34,7 @@ #include "GraphImpl.hpp" #include "PortAudioDriver.hpp" #include "PortImpl.hpp" +#include "FrameTimer.hpp" #include "ThreadManager.hpp" #include "util.hpp" @@ -53,12 +54,13 @@ pa_error(const char* msg, PaError err) PortAudioDriver::PortAudioDriver(Engine& engine) : _engine(engine) , _sem(0) - , _flag(false) + , _stream(nullptr) , _seq_size(4096) - , _block_length(4096) + , _block_length(1024) , _sample_rate(48000) , _n_inputs(0) , _n_outputs(0) + , _flag(false) , _is_activated(false) { } @@ -86,11 +88,18 @@ PortAudioDriver::attach() return pa_error("No default output device", paDeviceUnavailable); } - const PaDeviceInfo* in_dev = Pa_GetDeviceInfo(_inputParameters.device); + const PaDeviceInfo* in_dev = Pa_GetDeviceInfo(_inputParameters.device); + + /* TODO: It looks like it is somehow actually impossible to request the + best/native buffer size then retrieve what it actually is with + PortAudio. How such a glaring useless flaw exists in such a widespread + library is beyond me... */ + + _sample_rate = in_dev->defaultSampleRate; + + _timer = std::unique_ptr<FrameTimer>( + new FrameTimer(_block_length, _sample_rate)); - _sample_rate = in_dev->defaultSampleRate; - _block_length = 4096; // FIXME - _seq_size = 4096; return true; } @@ -152,7 +161,7 @@ PortAudioDriver::deactivate() SampleCount PortAudioDriver::frame_time() const { - return _engine.run_context().start(); + return _timer->frame_time(_engine.current_time()) + _engine.block_length(); } EnginePort* @@ -268,6 +277,7 @@ PortAudioDriver::process_cb(const void* inputs, PaStreamCallbackFlags flags) { _engine.advance(nframes); + _timer->update(_engine.current_time(), _engine.run_context().start()); // Read input for (auto& p : _ports) { diff --git a/src/server/PortAudioDriver.hpp b/src/server/PortAudioDriver.hpp index 24d10925..3659b4ff 100644 --- a/src/server/PortAudioDriver.hpp +++ b/src/server/PortAudioDriver.hpp @@ -20,6 +20,7 @@ #include "ingen_config.h" #include <atomic> +#include <memory> #include <portaudio.h> #include <string> @@ -40,6 +41,7 @@ class Engine; class GraphImpl; class PortAudioDriver; class PortImpl; +class FrameTimer; class PortAudioDriver : public Driver { @@ -86,12 +88,11 @@ private: inputs, outputs, nframes, time, flags); } - int - process_cb(const void* inputs, - void* outputs, - unsigned long nframes, - const PaStreamCallbackTimeInfo* time, - PaStreamCallbackFlags flags); + int process_cb(const void* inputs, + void* outputs, + unsigned long nframes, + const PaStreamCallbackTimeInfo* time, + PaStreamCallbackFlags flags); void pre_process_port(RunContext& context, EnginePort* port, @@ -108,19 +109,20 @@ protected: boost::intrusive::cache_last<true> > Ports; - Engine& _engine; - Ports _ports; - PaStreamParameters _inputParameters; - PaStreamParameters _outputParameters; - Raul::Semaphore _sem; - std::atomic<bool> _flag; - PaStream* _stream; - size_t _seq_size; - uint32_t _block_length; - uint32_t _sample_rate; - uint32_t _n_inputs; - uint32_t _n_outputs; - bool _is_activated; + Engine& _engine; + Ports _ports; + PaStreamParameters _inputParameters; + PaStreamParameters _outputParameters; + Raul::Semaphore _sem; + std::unique_ptr<FrameTimer> _timer; + PaStream* _stream; + size_t _seq_size; + uint32_t _block_length; + uint32_t _sample_rate; + uint32_t _n_inputs; + uint32_t _n_outputs; + std::atomic<bool> _flag; + bool _is_activated; }; } // namespace Server diff --git a/src/server/PreProcessor.cpp b/src/server/PreProcessor.cpp index 4efe88fe..211816c6 100644 --- a/src/server/PreProcessor.cpp +++ b/src/server/PreProcessor.cpp @@ -144,7 +144,7 @@ PreProcessor::process(RunContext& context, PostProcessor& dest, size_t limit) Engine& engine = context.engine(); if (engine.world()->conf().option("trace").get<int32_t>()) { const uint64_t start = engine.cycle_start_time(context); - const uint64_t end = engine.current_time(context); + const uint64_t end = engine.current_time(); fprintf(stderr, "Processed %zu events in %u us\n", n_processed, (unsigned)(end - start)); } |