From 676ff806970925972b165cd7621ba7ea8c82c08a Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 10 Sep 2013 22:58:33 -0400 Subject: basic functionality for hardware latency measurement --- gtk2_ardour/engine_dialog.cc | 182 ++++++++++++++++++++++++++++++++++++++- gtk2_ardour/engine_dialog.h | 24 +++++- libs/ardour/ardour/audioengine.h | 17 +++- libs/ardour/audioengine.cc | 84 ++++++++++++++++++ libs/ardour/midi_port.cc | 2 +- libs/ardour/port_insert.cc | 4 +- 6 files changed, 307 insertions(+), 6 deletions(-) diff --git a/gtk2_ardour/engine_dialog.cc b/gtk2_ardour/engine_dialog.cc index b97a7133e6..8c84c5d3b9 100644 --- a/gtk2_ardour/engine_dialog.cc +++ b/gtk2_ardour/engine_dialog.cc @@ -36,12 +36,15 @@ #include "ardour/audio_backend.h" #include "ardour/audioengine.h" +#include "ardour/mtdm.h" #include "ardour/rc_configuration.h" +#include "ardour/types.h" #include "pbd/convert.h" #include "pbd/error.h" #include "engine_dialog.h" +#include "gui_thread.h" #include "i18n.h" using namespace std; @@ -63,6 +66,9 @@ EngineControl::EngineControl () , ports_adjustment (128, 8, 1024, 1, 16) , ports_spinner (ports_adjustment) , control_app_button (_("Launch Control App")) + , lm_measure_button (_("Measure latency")) + , lm_use_button (_("Use results")) + , lm_table (5, 2) , basic_packer (9, 3) , ignore_changes (0) , _desired_sample_rate (0) @@ -89,6 +95,15 @@ EngineControl::EngineControl () if (audio_setup) { set_state (*audio_setup); } + + ARDOUR::AudioEngine::instance()->Stopped.connect (*this, MISSING_INVALIDATOR, boost::bind (&EngineControl::disable_latency_tab, this), gui_context()); + + if (!ARDOUR::AudioEngine::instance()->connected()) { + ARDOUR::AudioEngine::instance()->Running.connect (*this, MISSING_INVALIDATOR, boost::bind (&EngineControl::enable_latency_tab, this), gui_context()); + disable_latency_tab (); + } else { + enable_latency_tab (); + } } void @@ -131,7 +146,7 @@ EngineControl::build_notebook () row = 0; - const AttachOptions xopt = AttachOptions (FILL|EXPAND); + AttachOptions xopt = AttachOptions (FILL|EXPAND); label = manage (left_aligned_label (_("Audio System:"))); basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); @@ -200,8 +215,59 @@ EngineControl::build_notebook () midi_packer.set_border_width (12); + /* latency measurement tab */ + + lm_title.set_markup (string_compose ("%1", _("Latency Measurement Tool"))); + + row = 0; + lm_table.set_row_spacings (12); + + lm_table.attach (lm_title, 0, 2, row, row+1, xopt, (AttachOptions) 0); + row++; + + lm_preamble.set_width_chars (60); + lm_preamble.set_line_wrap (true); + lm_preamble.set_markup (_("This tool will allow you to precisely measure the signal delay \ +within your audio hardware setup that is not controlled by Ardour or its audio backend.\n\n\ +Connect the two channels that you select below using either a cable or (less ideally) a speaker \ +and microphone.\n\n\ +Once the channels are connected, click the \"Measure latency\" button.\n\n\ +When you are satisfied with the results, click the \"Use results\" button to use them with your audio \ +setup parameters. Note: they will not take effect until you restart")); + + lm_table.attach (lm_preamble, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0); + row++; + + label = manage (left_aligned_label (_("Output channel"))); + lm_table.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + lm_table.attach (lm_output_channel_combo, 1, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; + + label = manage (left_aligned_label (_("Input channel"))); + lm_table.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + lm_table.attach (lm_input_channel_combo, 1, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; + + xopt = AttachOptions(0); + + lm_measure_button.signal_toggled().connect (sigc::mem_fun (*this, &EngineControl::latency_button_toggled)); + + lm_table.attach (lm_measure_button, 0, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; + lm_table.attach (lm_results, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0); + ++row; + lm_table.attach (lm_use_button, 0, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; + + lm_results.set_text ("Measured results: 786 samples"); + + lm_vbox.pack_start (lm_table, false, false); + + /* pack it all up */ + notebook.pages().push_back (TabElem (basic_vbox, _("Audio"))); notebook.pages().push_back (TabElem (midi_hbox, _("MIDI"))); + notebook.pages().push_back (TabElem (lm_vbox, _("Latency"))); notebook.set_border_width (12); notebook.set_tab_pos (POS_RIGHT); @@ -225,6 +291,28 @@ EngineControl::~EngineControl () } +void +EngineControl::disable_latency_tab () +{ + vector empty; + set_popdown_strings (lm_output_channel_combo, empty); + set_popdown_strings (lm_input_channel_combo, empty); +} + +void +EngineControl::enable_latency_tab () +{ + vector outputs; + ARDOUR::AudioEngine::instance()->get_physical_outputs (ARDOUR::DataType::AUDIO, outputs); + set_popdown_strings (lm_output_channel_combo, outputs); + lm_output_channel_combo.set_active_text (outputs.front()); + + vector inputs; + ARDOUR::AudioEngine::instance()->get_physical_inputs (ARDOUR::DataType::AUDIO, inputs); + set_popdown_strings (lm_input_channel_combo, inputs); + lm_input_channel_combo.set_active_text (inputs.front()); +} + void EngineControl::backend_changed () { @@ -861,3 +949,95 @@ EngineControl::set_desired_sample_rate (uint32_t sr) _desired_sample_rate = sr; device_changed (); } + +/* latency measurement */ + +void +EngineControl::update_latency_display () +{ + ARDOUR::framecnt_t const sample_rate = ARDOUR::AudioEngine::instance()->sample_rate(); + if (sample_rate == 0) { + lm_results.set_text (_("Disconnected from audio engine")); + } else { + char buf[64]; + //snprintf (buf, sizeof (buf), "%10.3lf frames %10.3lf ms", + //(float)_pi->latency(), (float)_pi->latency() * 1000.0f/sample_rate); + strcpy (buf, "got something"); + lm_results.set_text(buf); + } +} + +bool +EngineControl::check_latency_measurement () +{ + MTDM* mtdm = ARDOUR::AudioEngine::instance()->mtdm (); + static uint32_t cnt = 0; + + if (mtdm->resolve () < 0) { + cerr << "no resolution\n"; + string txt = _("No signal detected "); + uint32_t dots = cnt++%10; + for (uint32_t i = 0; i < dots; ++i) { + txt += '.'; + } + lm_results.set_text (txt); + return true; + } + + if (mtdm->err () > 0.3) { + mtdm->invert (); + mtdm->resolve (); + } + + char buf[128]; + ARDOUR::framecnt_t const sample_rate = ARDOUR::AudioEngine::instance()->sample_rate(); + + if (sample_rate == 0) { + lm_results.set_text (_("Disconnected from audio engine")); + ARDOUR::AudioEngine::instance()->stop_latency_detection (); + return false; + } + + snprintf (buf, sizeof (buf), "%10.3lf frames %10.3lf ms", mtdm->del (), mtdm->del () * 1000.0f/sample_rate); + + bool solid = true; + + if (mtdm->err () > 0.2) { + strcat (buf, " ??"); + solid = false; + } + + if (mtdm->inv ()) { + strcat (buf, " (Inv)"); + solid = false; + } + + if (solid) { + // _pi->set_measured_latency (rint (mtdm->del())); + lm_measure_button.set_active (false); + strcat (buf, " (set)"); + } + + lm_results.set_text (buf); + + return true; +} + +void +EngineControl::latency_button_toggled () +{ + if (lm_measure_button.get_active ()) { + + 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()); + cerr << "latency detection on " << lm_input_channel_combo.get_active_text() << " => " << lm_output_channel_combo.get_active_text() << endl; + ARDOUR::AudioEngine::instance()->start_latency_detection (); + lm_results.set_text (_("Detecting ...")); + latency_timeout = Glib::signal_timeout().connect (mem_fun (*this, &EngineControl::check_latency_measurement), 250); + + } else { + ARDOUR::AudioEngine::instance()->stop_latency_detection (); + latency_timeout.disconnect (); + update_latency_display (); + } +} diff --git a/gtk2_ardour/engine_dialog.h b/gtk2_ardour/engine_dialog.h index a92d0629f2..940f594421 100644 --- a/gtk2_ardour/engine_dialog.h +++ b/gtk2_ardour/engine_dialog.h @@ -33,9 +33,11 @@ #include #include +#include "pbd/signals.h" + #include "ardour_dialog.h" -class EngineControl : public ArdourDialog { +class EngineControl : public ArdourDialog, public PBD::ScopedConnectionList { public: EngineControl (); ~EngineControl (); @@ -71,6 +73,18 @@ class EngineControl : public ArdourDialog { Gtk::Button control_app_button; + /* latency measurement */ + + Gtk::ComboBoxText lm_output_channel_combo; + Gtk::ComboBoxText lm_input_channel_combo; + Gtk::ToggleButton lm_measure_button; + Gtk::Button lm_use_button; + Gtk::Label lm_title; + Gtk::Label lm_preamble; + Gtk::Label lm_results; + Gtk::Table lm_table; + Gtk::VBox lm_vbox; + /* JACK specific */ Gtk::CheckButton realtime_button; @@ -156,6 +170,14 @@ class EngineControl : public ArdourDialog { void manage_control_app_sensitivity (); int push_state_to_backend (bool start); uint32_t _desired_sample_rate; + + /* latency measurement */ + void latency_button_toggled (); + bool check_latency_measurement (); + void update_latency_display (); + sigc::connection latency_timeout; + void enable_latency_tab (); + void disable_latency_tab (); }; #endif /* __gtk2_ardour_engine_dialog_h__ */ diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index 4db1604345..980f507be5 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -47,6 +47,8 @@ #include #endif +class MTDM; + namespace ARDOUR { class InternalPort; @@ -182,6 +184,14 @@ public: /* sets up the process callback thread */ static void thread_init_callback (void *); + /* latency measurement */ + + MTDM* mtdm(); + void start_latency_detection (); + void stop_latency_detection (); + void set_latency_input_port (const std::string&); + void set_latency_output_port (const std::string&); + private: AudioEngine (); @@ -205,7 +215,12 @@ public: framecnt_t _processed_frames; Glib::Threads::Thread* m_meter_thread; ProcessThread* _main_thread; - + MTDM* _mtdm; + bool _measuring_latency; + PortEngine::PortHandle _latency_input_port; + PortEngine::PortHandle _latency_output_port; + framecnt_t _latency_flush_frames; + void meter_thread (); void start_metering_thread (); void stop_metering_thread (); diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 5b19253a26..09478e7ef7 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -50,6 +50,7 @@ #include "ardour/meter.h" #include "ardour/midi_port.h" #include "ardour/midiport_manager.h" +#include "ardour/mtdm.h" #include "ardour/port.h" #include "ardour/process_thread.h" #include "ardour/session.h" @@ -73,6 +74,11 @@ AudioEngine::AudioEngine () , _processed_frames (0) , m_meter_thread (0) , _main_thread (0) + , _mtdm (0) + , _measuring_latency (false) + , _latency_input_port (0) + , _latency_output_port (0) + , _latency_flush_frames (0) { g_atomic_int_set (&m_meter_exit, 0); discover_backends (); @@ -192,6 +198,43 @@ AudioEngine::process_callback (pframes_t nframes) return 0; } + /* If measuring latency, do it now and get out of here */ + + if (_measuring_latency && _mtdm) { + // PortManager::cycle_start (nframes); + // PortManager::silence (nframes); + + if (_latency_input_port && _latency_output_port) { + PortEngine& pe (port_engine()); + + Sample* in = (Sample*) pe.get_buffer (_latency_input_port, nframes); + Sample* out = (Sample*) pe.get_buffer (_latency_output_port, nframes); + + _mtdm->process (nframes, in, out); + } + + // PortManager::cycle_end (nframes); + return 0; + + } else if (_latency_flush_frames) { + + /* wait for the appropriate duration for the MTDM signal to + * drain from the ports before we revert to normal behaviour. + */ + + PortManager::cycle_start (nframes); + PortManager::silence (nframes); + PortManager::cycle_end (nframes); + + if (_latency_flush_frames > nframes) { + _latency_flush_frames -= nframes; + } else { + _latency_flush_frames = 0; + } + + return 0; + } + if (session_remove_pending) { /* perform the actual session removal */ @@ -581,6 +624,9 @@ AudioEngine::stop () _running = false; _processed_frames = 0; + _measuring_latency = false; + _latency_output_port = 0; + _latency_input_port = 0; stop_metering_thread (); Port::PortDrop (); @@ -930,3 +976,41 @@ AudioEngine::setup_required () const return true; } +MTDM* +AudioEngine::mtdm() +{ + return _mtdm; +} + +void +AudioEngine::start_latency_detection () +{ + delete _mtdm; + + _mtdm = new MTDM (sample_rate()); + _measuring_latency = true; + _latency_flush_frames = samples_per_cycle(); +} + +void +AudioEngine::stop_latency_detection () +{ + port_engine().unregister_port (_latency_output_port); + port_engine().unregister_port (_latency_input_port); + _measuring_latency = false; +} + +void +AudioEngine::set_latency_output_port (const string& name) +{ + _latency_output_port = port_engine().register_port ("latency_out", DataType::AUDIO, IsOutput); + port_engine().connect (_latency_output_port, name); +} + +void +AudioEngine::set_latency_input_port (const string& name) +{ + const string portname ("latency_in"); + _latency_input_port = port_engine().register_port (portname, DataType::AUDIO, IsInput); + port_engine().connect (name, make_port_name_non_relative (portname)); +} diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index eb6759f308..de2263fad6 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -88,7 +88,7 @@ MidiPort::get_midi_buffer (pframes_t nframes) void* buffer = port_engine.get_buffer (_port_handle, nframes); const pframes_t event_count = port_engine.get_midi_event_count (buffer); - + /* suck all relevant MIDI events from the MIDI port buffer into our MidiBuffer */ diff --git a/libs/ardour/port_insert.cc b/libs/ardour/port_insert.cc index d64920b1e2..97fe082c81 100644 --- a/libs/ardour/port_insert.cc +++ b/libs/ardour/port_insert.cc @@ -49,7 +49,7 @@ PortInsert::PortInsert (Session& s, boost::shared_ptr pannable, boost: { _mtdm = 0; _latency_detect = false; - _latency_flush_frames = false; + _latency_flush_frames = 0; _measured_latency = 0; } @@ -64,7 +64,7 @@ PortInsert::start_latency_detection () { delete _mtdm; _mtdm = new MTDM (_session.frame_rate()); - _latency_flush_frames = false; + _latency_flush_frames = 0; _latency_detect = true; _measured_latency = 0; } -- cgit v1.2.3