mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-10 08:36:32 +01:00
prepare MIDI latency measurement (backend)
This commit is contained in:
parent
6416a429a8
commit
81182b5bf6
6 changed files with 251 additions and 39 deletions
|
|
@ -1699,7 +1699,7 @@ EngineControl::start_latency_detection ()
|
||||||
ARDOUR::AudioEngine::instance()->set_latency_input_port (lm_input_channel_combo.get_active_text());
|
ARDOUR::AudioEngine::instance()->set_latency_input_port (lm_input_channel_combo.get_active_text());
|
||||||
ARDOUR::AudioEngine::instance()->set_latency_output_port (lm_output_channel_combo.get_active_text());
|
ARDOUR::AudioEngine::instance()->set_latency_output_port (lm_output_channel_combo.get_active_text());
|
||||||
|
|
||||||
if (ARDOUR::AudioEngine::instance()->start_latency_detection () == 0) {
|
if (ARDOUR::AudioEngine::instance()->start_latency_detection (false) == 0) {
|
||||||
lm_results.set_markup (string_compose (results_markup, _("Detecting ...")));
|
lm_results.set_markup (string_compose (results_markup, _("Detecting ...")));
|
||||||
latency_timeout = Glib::signal_timeout().connect (mem_fun (*this, &EngineControl::check_latency_measurement), 100);
|
latency_timeout = Glib::signal_timeout().connect (mem_fun (*this, &EngineControl::check_latency_measurement), 100);
|
||||||
lm_measure_label.set_text (_("Cancel"));
|
lm_measure_label.set_text (_("Cancel"));
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ namespace ARDOUR {
|
||||||
|
|
||||||
class InternalPort;
|
class InternalPort;
|
||||||
class MidiPort;
|
class MidiPort;
|
||||||
|
class MIDIDM;
|
||||||
class Port;
|
class Port;
|
||||||
class Session;
|
class Session;
|
||||||
class ProcessThread;
|
class ProcessThread;
|
||||||
|
|
@ -191,14 +192,24 @@ public:
|
||||||
|
|
||||||
/* latency measurement */
|
/* latency measurement */
|
||||||
|
|
||||||
MTDM* mtdm();
|
MTDM* mtdm() { return _mtdm; }
|
||||||
|
MIDIDM* mididm() { return _mididm; }
|
||||||
|
|
||||||
int prepare_for_latency_measurement ();
|
int prepare_for_latency_measurement ();
|
||||||
int start_latency_detection ();
|
int start_latency_detection (bool);
|
||||||
void stop_latency_detection ();
|
void stop_latency_detection ();
|
||||||
void set_latency_input_port (const std::string&);
|
void set_latency_input_port (const std::string&);
|
||||||
void set_latency_output_port (const std::string&);
|
void set_latency_output_port (const std::string&);
|
||||||
uint32_t latency_signal_delay () const { return _latency_signal_latency; }
|
uint32_t latency_signal_delay () const { return _latency_signal_latency; }
|
||||||
|
|
||||||
|
enum LatencyMeasurement {
|
||||||
|
MeasureNone,
|
||||||
|
MeasureAudio,
|
||||||
|
MeasureMIDI
|
||||||
|
};
|
||||||
|
|
||||||
|
LatencyMeasurement measuring_latency () const { return _measuring_latency; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AudioEngine ();
|
AudioEngine ();
|
||||||
|
|
||||||
|
|
@ -221,7 +232,8 @@ public:
|
||||||
Glib::Threads::Thread* m_meter_thread;
|
Glib::Threads::Thread* m_meter_thread;
|
||||||
ProcessThread* _main_thread;
|
ProcessThread* _main_thread;
|
||||||
MTDM* _mtdm;
|
MTDM* _mtdm;
|
||||||
bool _measuring_latency;
|
MIDIDM* _mididm;
|
||||||
|
LatencyMeasurement _measuring_latency;
|
||||||
PortEngine::PortHandle _latency_input_port;
|
PortEngine::PortHandle _latency_input_port;
|
||||||
PortEngine::PortHandle _latency_output_port;
|
PortEngine::PortHandle _latency_output_port;
|
||||||
framecnt_t _latency_flush_frames;
|
framecnt_t _latency_flush_frames;
|
||||||
|
|
|
||||||
62
libs/ardour/ardour/mididm.h
Normal file
62
libs/ardour/ardour/mididm.h
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2014 Robin Gareus <robin@gareus.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __libardour_mididm_h__
|
||||||
|
#define __libardour_mididm_h__
|
||||||
|
|
||||||
|
#include "ardour/types.h"
|
||||||
|
#include "ardour/libardour_visibility.h"
|
||||||
|
|
||||||
|
namespace ARDOUR {
|
||||||
|
|
||||||
|
class PortEngine;
|
||||||
|
|
||||||
|
class LIBARDOUR_API MIDIDM
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
MIDIDM (framecnt_t sample_rate);
|
||||||
|
|
||||||
|
int process (pframes_t nframes, PortEngine &pe, void *midi_in, void *midi_out);
|
||||||
|
|
||||||
|
framecnt_t latency (void) { return _cnt_total > 10 ? _avg_delay : 0; }
|
||||||
|
framecnt_t processed (void) { return _cnt_total; }
|
||||||
|
double deviation (void) { return _cnt_total > 1 ? sqrt(_var_s / ((double)(_cnt_total - 1))) : 0; }
|
||||||
|
bool ok (void) { return _cnt_total > 200; }
|
||||||
|
bool have_signal (void) { return (_monotonic_cnt - _last_signal_tme) < (uint64_t) _sample_rate ; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
framecnt_t _sample_rate;
|
||||||
|
|
||||||
|
uint64_t _monotonic_cnt;
|
||||||
|
uint64_t _last_signal_tme;
|
||||||
|
|
||||||
|
uint64_t _cnt_total;
|
||||||
|
uint64_t _dly_total;
|
||||||
|
uint32_t _min_delay;
|
||||||
|
uint32_t _max_delay;
|
||||||
|
double _avg_delay;
|
||||||
|
double _var_m;
|
||||||
|
double _var_s;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __libardour_mididm_h__ */
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
#include "ardour/meter.h"
|
#include "ardour/meter.h"
|
||||||
#include "ardour/midi_port.h"
|
#include "ardour/midi_port.h"
|
||||||
#include "ardour/midiport_manager.h"
|
#include "ardour/midiport_manager.h"
|
||||||
|
#include "ardour/mididm.h"
|
||||||
#include "ardour/mtdm.h"
|
#include "ardour/mtdm.h"
|
||||||
#include "ardour/port.h"
|
#include "ardour/port.h"
|
||||||
#include "ardour/process_thread.h"
|
#include "ardour/process_thread.h"
|
||||||
|
|
@ -73,7 +74,8 @@ AudioEngine::AudioEngine ()
|
||||||
, m_meter_thread (0)
|
, m_meter_thread (0)
|
||||||
, _main_thread (0)
|
, _main_thread (0)
|
||||||
, _mtdm (0)
|
, _mtdm (0)
|
||||||
, _measuring_latency (false)
|
, _mididm (0)
|
||||||
|
, _measuring_latency (MeasureNone)
|
||||||
, _latency_input_port (0)
|
, _latency_input_port (0)
|
||||||
, _latency_output_port (0)
|
, _latency_output_port (0)
|
||||||
, _latency_flush_frames (0)
|
, _latency_flush_frames (0)
|
||||||
|
|
@ -195,7 +197,7 @@ AudioEngine::process_callback (pframes_t nframes)
|
||||||
|
|
||||||
bool return_after_remove_check = false;
|
bool return_after_remove_check = false;
|
||||||
|
|
||||||
if (_measuring_latency && _mtdm) {
|
if (_measuring_latency == MeasureAudio && _mtdm) {
|
||||||
/* run a normal cycle from the perspective of the PortManager
|
/* run a normal cycle from the perspective of the PortManager
|
||||||
so that we get silence on all registered ports.
|
so that we get silence on all registered ports.
|
||||||
|
|
||||||
|
|
@ -218,6 +220,28 @@ AudioEngine::process_callback (pframes_t nframes)
|
||||||
PortManager::cycle_end (nframes);
|
PortManager::cycle_end (nframes);
|
||||||
return_after_remove_check = true;
|
return_after_remove_check = true;
|
||||||
|
|
||||||
|
} else if (_measuring_latency == MeasureMIDI && _mididm) {
|
||||||
|
/* run a normal cycle from the perspective of the PortManager
|
||||||
|
so that we get silence on all registered ports.
|
||||||
|
|
||||||
|
we overwrite the silence on the two ports used for latency
|
||||||
|
measurement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PortManager::cycle_start (nframes);
|
||||||
|
PortManager::silence (nframes);
|
||||||
|
|
||||||
|
if (_latency_input_port && _latency_output_port) {
|
||||||
|
PortEngine& pe (port_engine());
|
||||||
|
|
||||||
|
_mididm->process (nframes, pe,
|
||||||
|
pe.get_buffer (_latency_input_port, nframes),
|
||||||
|
pe.get_buffer (_latency_output_port, nframes));
|
||||||
|
}
|
||||||
|
|
||||||
|
PortManager::cycle_end (nframes);
|
||||||
|
return_after_remove_check = true;
|
||||||
|
|
||||||
} else if (_latency_flush_frames) {
|
} else if (_latency_flush_frames) {
|
||||||
|
|
||||||
/* wait for the appropriate duration for the MTDM signal to
|
/* wait for the appropriate duration for the MTDM signal to
|
||||||
|
|
@ -660,7 +684,7 @@ AudioEngine::stop (bool for_latency)
|
||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
_processed_frames = 0;
|
_processed_frames = 0;
|
||||||
_measuring_latency = false;
|
_measuring_latency = MeasureNone;
|
||||||
_latency_output_port = 0;
|
_latency_output_port = 0;
|
||||||
_latency_input_port = 0;
|
_latency_input_port = 0;
|
||||||
_started_for_latency = false;
|
_started_for_latency = false;
|
||||||
|
|
@ -1029,12 +1053,6 @@ AudioEngine::setup_required () const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTDM*
|
|
||||||
AudioEngine::mtdm()
|
|
||||||
{
|
|
||||||
return _mtdm;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
AudioEngine::prepare_for_latency_measurement ()
|
AudioEngine::prepare_for_latency_measurement ()
|
||||||
{
|
{
|
||||||
|
|
@ -1052,7 +1070,7 @@ AudioEngine::prepare_for_latency_measurement ()
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
AudioEngine::start_latency_detection ()
|
AudioEngine::start_latency_detection (bool for_midi)
|
||||||
{
|
{
|
||||||
if (!running()) {
|
if (!running()) {
|
||||||
if (prepare_for_latency_measurement ()) {
|
if (prepare_for_latency_measurement ()) {
|
||||||
|
|
@ -1065,6 +1083,9 @@ AudioEngine::start_latency_detection ()
|
||||||
delete _mtdm;
|
delete _mtdm;
|
||||||
_mtdm = 0;
|
_mtdm = 0;
|
||||||
|
|
||||||
|
delete _mididm;
|
||||||
|
_mididm = 0;
|
||||||
|
|
||||||
/* find the ports we will connect to */
|
/* find the ports we will connect to */
|
||||||
|
|
||||||
PortEngine::PortHandle out = pe.get_port_by_name (_latency_output_name);
|
PortEngine::PortHandle out = pe.get_port_by_name (_latency_output_name);
|
||||||
|
|
@ -1076,27 +1097,57 @@ AudioEngine::start_latency_detection ()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create the ports we will use to read/write data */
|
/* create the ports we will use to read/write data */
|
||||||
|
if (for_midi) {
|
||||||
if ((_latency_output_port = pe.register_port ("latency_out", DataType::AUDIO, IsOutput)) == 0) {
|
if ((_latency_output_port = pe.register_port ("latency_out", DataType::MIDI, IsOutput)) == 0) {
|
||||||
stop (true);
|
stop (true);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (pe.connect (_latency_output_port, _latency_output_name)) {
|
if (pe.connect (_latency_output_port, _latency_output_name)) {
|
||||||
pe.unregister_port (_latency_output_port);
|
pe.unregister_port (_latency_output_port);
|
||||||
stop (true);
|
stop (true);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string portname ("latency_in");
|
||||||
|
if ((_latency_input_port = pe.register_port (portname, DataType::MIDI, IsInput)) == 0) {
|
||||||
|
pe.unregister_port (_latency_output_port);
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) {
|
||||||
|
pe.unregister_port (_latency_output_port);
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mididm = new MIDIDM (sample_rate());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ((_latency_output_port = pe.register_port ("latency_out", DataType::AUDIO, IsOutput)) == 0) {
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pe.connect (_latency_output_port, _latency_output_name)) {
|
||||||
|
pe.unregister_port (_latency_output_port);
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string portname ("latency_in");
|
||||||
|
if ((_latency_input_port = pe.register_port (portname, DataType::AUDIO, IsInput)) == 0) {
|
||||||
|
pe.unregister_port (_latency_output_port);
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) {
|
||||||
|
pe.unregister_port (_latency_output_port);
|
||||||
|
stop (true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mtdm = new MTDM (sample_rate());
|
||||||
|
|
||||||
const string portname ("latency_in");
|
|
||||||
if ((_latency_input_port = pe.register_port (portname, DataType::AUDIO, IsInput)) == 0) {
|
|
||||||
pe.unregister_port (_latency_output_port);
|
|
||||||
stop (true);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) {
|
|
||||||
pe.unregister_port (_latency_output_port);
|
|
||||||
stop (true);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LatencyRange lr;
|
LatencyRange lr;
|
||||||
|
|
@ -1107,10 +1158,8 @@ AudioEngine::start_latency_detection ()
|
||||||
_latency_signal_latency += lr.max;
|
_latency_signal_latency += lr.max;
|
||||||
|
|
||||||
/* all created and connected, lets go */
|
/* all created and connected, lets go */
|
||||||
|
_latency_flush_frames = samples_per_cycle();
|
||||||
_mtdm = new MTDM (sample_rate());
|
_measuring_latency = for_midi ? MeasureMIDI : MeasureAudio;
|
||||||
_measuring_latency = true;
|
|
||||||
_latency_flush_frames = samples_per_cycle();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1167,7 @@ AudioEngine::start_latency_detection ()
|
||||||
void
|
void
|
||||||
AudioEngine::stop_latency_detection ()
|
AudioEngine::stop_latency_detection ()
|
||||||
{
|
{
|
||||||
_measuring_latency = false;
|
_measuring_latency = MeasureNone;
|
||||||
|
|
||||||
if (_latency_output_port) {
|
if (_latency_output_port) {
|
||||||
port_engine().unregister_port (_latency_output_port);
|
port_engine().unregister_port (_latency_output_port);
|
||||||
|
|
|
||||||
88
libs/ardour/mididm.cc
Normal file
88
libs/ardour/mididm.cc
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2014 Robin Gareus <robin@gareus.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ardour/mididm.h"
|
||||||
|
#include "ardour/port_engine.h"
|
||||||
|
|
||||||
|
using namespace ARDOUR;
|
||||||
|
|
||||||
|
MIDIDM::MIDIDM (framecnt_t sample_rate)
|
||||||
|
: _sample_rate (sample_rate)
|
||||||
|
, _monotonic_cnt (sample_rate)
|
||||||
|
, _last_signal_tme (0)
|
||||||
|
, _cnt_total (0)
|
||||||
|
, _dly_total (0)
|
||||||
|
, _min_delay (INT32_MAX)
|
||||||
|
, _max_delay (0)
|
||||||
|
, _avg_delay (0)
|
||||||
|
, _var_m (0)
|
||||||
|
, _var_s (0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int MIDIDM::process (pframes_t nframes, PortEngine &pe, void *midi_in, void *midi_out)
|
||||||
|
{
|
||||||
|
/* send midi event */
|
||||||
|
uint8_t obuf[3];
|
||||||
|
obuf[0] = 0xf2;
|
||||||
|
obuf[1] = (_monotonic_cnt) & 0x7f;
|
||||||
|
obuf[2] = (_monotonic_cnt >> 7) & 0x7f;
|
||||||
|
|
||||||
|
pe.midi_clear(midi_out);
|
||||||
|
pe.midi_event_put (midi_out, 0, obuf, 3);
|
||||||
|
|
||||||
|
/* process incoming */
|
||||||
|
const pframes_t nevents = pe.get_midi_event_count (midi_in);
|
||||||
|
for (pframes_t n = 0; n < nevents; ++n) {
|
||||||
|
pframes_t timestamp;
|
||||||
|
size_t size;
|
||||||
|
uint8_t* buf;
|
||||||
|
pe.midi_event_get (timestamp, size, &buf, midi_in, n);
|
||||||
|
|
||||||
|
if (size != 3 || buf[0] != 0xf2 ) continue;
|
||||||
|
|
||||||
|
_last_signal_tme = _monotonic_cnt;
|
||||||
|
|
||||||
|
/* calculate time difference */
|
||||||
|
#define MODX (16384) // 1<<(2*7)
|
||||||
|
#define MASK (0x3fff) // MODX - 1
|
||||||
|
const int64_t tc = (_monotonic_cnt + timestamp) & MASK;
|
||||||
|
const int64_t ti = (buf[2] << 7) | buf[1];
|
||||||
|
const int64_t tdiff = (MODX + tc - ti) % MODX;
|
||||||
|
|
||||||
|
/* running variance */
|
||||||
|
if (_cnt_total == 0) {
|
||||||
|
_var_m = tdiff;
|
||||||
|
} else {
|
||||||
|
const double var_m1 = _var_m;
|
||||||
|
_var_m = _var_m + ((double)tdiff - _var_m) / (double)(_cnt_total + 1);
|
||||||
|
_var_s = _var_s + ((double)tdiff - _var_m) * ((double)tdiff - var_m1);
|
||||||
|
}
|
||||||
|
/* average and mix/max */
|
||||||
|
++_cnt_total;
|
||||||
|
_dly_total += tdiff;
|
||||||
|
_avg_delay = _dly_total / _cnt_total;
|
||||||
|
if (tdiff < _min_delay) _min_delay = tdiff;
|
||||||
|
if (tdiff > _max_delay) _max_delay = tdiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
_monotonic_cnt += nframes;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -130,6 +130,7 @@ libardour_sources = [
|
||||||
'mix.cc',
|
'mix.cc',
|
||||||
'monitor_processor.cc',
|
'monitor_processor.cc',
|
||||||
'mtc_slave.cc',
|
'mtc_slave.cc',
|
||||||
|
'mididm.cc',
|
||||||
'mtdm.cc',
|
'mtdm.cc',
|
||||||
'mute_master.cc',
|
'mute_master.cc',
|
||||||
'onset_detector.cc',
|
'onset_detector.cc',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue