prepare MIDI latency measurement (backend)

This commit is contained in:
Robin Gareus 2014-06-08 11:18:18 +02:00
parent 6416a429a8
commit 81182b5bf6
6 changed files with 251 additions and 39 deletions

View file

@ -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"));

View file

@ -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;

View 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__ */

View file

@ -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
View 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;
}

View file

@ -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',