summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2017-03-20 02:07:19 +0100
committerDavid Robillard <d@drobilla.net>2017-03-20 02:58:56 +0100
commit4d4721f984e0a22a9874b5d4c4c9ee6674fca856 (patch)
treeeea16bc8e63f9b63299fedd0942575b8e2275371
parentd2927c78d9719119d7db2f97742f9596d64e4003 (diff)
downloadingen-4d4721f984e0a22a9874b5d4c4c9ee6674fca856.tar.gz
ingen-4d4721f984e0a22a9874b5d4c4c9ee6674fca856.tar.bz2
ingen-4d4721f984e0a22a9874b5d4c4c9ee6674fca856.zip
Fix event timing with PortAudio driver
-rw-r--r--src/server/Engine.cpp6
-rw-r--r--src/server/Engine.hpp2
-rw-r--r--src/server/FrameTimer.hpp110
-rw-r--r--src/server/PortAudioDriver.cpp24
-rw-r--r--src/server/PortAudioDriver.hpp40
-rw-r--r--src/server/PreProcessor.cpp2
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));
}