diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2013-09-12 11:28:50 -0400 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2013-09-12 11:28:50 -0400 |
commit | bb826f4beebfcedc50570b818c305560d2233e47 (patch) | |
tree | 1484460f914e64fdbdce58230ffe7934bc0761f4 | |
parent | 8c5cff60912c7e0a7256f635641399500d8d00d9 (diff) | |
parent | f85b362351a5f9167f93b6988f2c8a4c7e03a33c (diff) |
incomplete merge of master into windows (requires upcoming changes to master to be complete)
153 files changed, 9337 insertions, 5196 deletions
diff --git a/gtk2_ardour/actions.cc b/gtk2_ardour/actions.cc index 3b0133b535..cefaeeb4cf 100644 --- a/gtk2_ardour/actions.cc +++ b/gtk2_ardour/actions.cc @@ -58,8 +58,8 @@ vector<RefPtr<Gtk::Action> > ActionManager::playlist_selection_sensitive_actions vector<RefPtr<Gtk::Action> > ActionManager::mouse_edit_point_requires_canvas_actions; vector<RefPtr<Gtk::Action> > ActionManager::range_sensitive_actions; -vector<RefPtr<Gtk::Action> > ActionManager::jack_sensitive_actions; -vector<RefPtr<Gtk::Action> > ActionManager::jack_opposite_sensitive_actions; +vector<RefPtr<Gtk::Action> > ActionManager::engine_sensitive_actions; +vector<RefPtr<Gtk::Action> > ActionManager::engine_opposite_sensitive_actions; vector<RefPtr<Gtk::Action> > ActionManager::transport_sensitive_actions; vector<RefPtr<Gtk::Action> > ActionManager::edit_point_in_region_sensitive_actions; diff --git a/gtk2_ardour/actions.h b/gtk2_ardour/actions.h index 20c0532636..f3fba51de5 100644 --- a/gtk2_ardour/actions.h +++ b/gtk2_ardour/actions.h @@ -46,8 +46,8 @@ namespace ActionManager { extern std::vector<Glib::RefPtr<Gtk::Action> > range_sensitive_actions; extern std::vector<Glib::RefPtr<Gtk::Action> > transport_sensitive_actions; - extern std::vector<Glib::RefPtr<Gtk::Action> > jack_sensitive_actions; - extern std::vector<Glib::RefPtr<Gtk::Action> > jack_opposite_sensitive_actions; + extern std::vector<Glib::RefPtr<Gtk::Action> > engine_sensitive_actions; + extern std::vector<Glib::RefPtr<Gtk::Action> > engine_opposite_sensitive_actions; extern std::vector<Glib::RefPtr<Gtk::Action> > edit_point_in_region_sensitive_actions; extern void map_some_state (const char* group, const char* action, bool (ARDOUR::RCConfiguration::*get)() const); diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index 897bfcf7c2..850a83c954 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -17,6 +17,7 @@ export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour: export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:. export ARDOUR_MCP_PATH=$TOP/mcp:. export ARDOUR_EXPORT_FORMATS_PATH=$TOP/export:. +export ARDOUR_BACKEND_PATH=$libs/backends/jack # # even though we set the above variables, ardour requires that these diff --git a/gtk2_ardour/ardour.menus.in b/gtk2_ardour/ardour.menus.in index 15f1bde1b6..197b484698 100644 --- a/gtk2_ardour/ardour.menus.in +++ b/gtk2_ardour/ardour.menus.in @@ -495,27 +495,27 @@ <menuitem action='show-marker-lines'/> </menu> <menu name='JACK' action='JACK'> - <menuitem action='JACKDisconnect'/> - <menuitem action='JACKReconnect'/> + <menuitem action='EngineDisconnect'/> + <menuitem action='EngineReconnect'/> <menu name='Latency' action='Latency'> - <menuitem action='JACKLatency32'/> - <menuitem action='JACKLatency64'/> - <menuitem action='JACKLatency128'/> - <menuitem action='JACKLatency256'/> - <menuitem action='JACKLatency512'/> - <menuitem action='JACKLatency1024'/> - <menuitem action='JACKLatency2048'/> - <menuitem action='JACKLatency4096'/> - <menuitem action='JACKLatency8192'/> + <menuitem action='EngineLatency32'/> + <menuitem action='EngineLatency64'/> + <menuitem action='EngineLatency128'/> + <menuitem action='EngineLatency256'/> + <menuitem action='EngineLatency512'/> + <menuitem action='EngineLatency1024'/> + <menuitem action='EngineLatency2048'/> + <menuitem action='EngineLatency4096'/> + <menuitem action='EngineLatency8192'/> </menu> </menu> <menu action = 'WindowMenu'> + <menuitem action='toggle-audio-midi-setup'/> + <separator/> <menuitem action='toggle-mixer'/> <menuitem action='toggle-meterbridge'/> <menuitem action='toggle-editor-mixer'/> - <separator/> - <menuitem action='toggle-inspector'/> <menuitem action='toggle-locations'/> <menuitem action='toggle-key-editor'/> diff --git a/gtk2_ardour/ardour3_fonts.rc.in b/gtk2_ardour/ardour3_fonts.rc.in index 7d017df4b3..8d09ff0f1d 100644 --- a/gtk2_ardour/ardour3_fonts.rc.in +++ b/gtk2_ardour/ardour3_fonts.rc.in @@ -50,6 +50,11 @@ style "large_bold_text" font_name = "bold @FONT_LARGE@" } +style "big_text" +{ + font_name = "@FONT_BIG@" +} + style "bigger_mono_text" { font_name = "@MONOSPACE@ @FONT_BIGGER@" diff --git a/gtk2_ardour/ardour3_styles.rc.in b/gtk2_ardour/ardour3_styles.rc.in index e3978d063a..8b5b5e4c99 100644 --- a/gtk2_ardour/ardour3_styles.rc.in +++ b/gtk2_ardour/ardour3_styles.rc.in @@ -1011,3 +1011,8 @@ style "meter_strip_sep" = "default" { bg[NORMAL] = { 0.0, 0.0, 0.0 } } + +style "settings_notebook" = "big_text" +{ + +} diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 33f5a5099e..307eb925b3 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -62,9 +62,8 @@ #include "gtkmm2ext/popup.h" #include "gtkmm2ext/window_title.h" -#include "midi++/manager.h" - #include "ardour/ardour.h" +#include "ardour/audio_backend.h" #include "ardour/audioengine.h" #include "ardour/audiofilesource.h" #include "ardour/automation_watch.h" @@ -162,10 +161,10 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) /* start of private members */ , _startup (0) - , engine (0) , nsm (0) , _was_dirty (false) , _mixer_on_top (false) + , first_time_engine_run (true) /* transport */ @@ -207,7 +206,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) , _status_bar_visibility (X_("status-bar")) , _feedback_exists (false) - + , _audio_midi_setup (0) { Gtkmm2ext::init(localedir); @@ -218,12 +217,12 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) } ui_config = new UIConfiguration(); + _audio_midi_setup = new EngineControl; editor = 0; mixer = 0; meterbridge = 0; editor = 0; - engine = 0; _session_is_new = false; session_selector_window = 0; last_key_press_time = 0; @@ -271,6 +270,10 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) ARDOUR::Session::AskAboutPendingState.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::pending_state_dialog, this)); + /* handle Audio/MIDI setup when session requires it */ + + ARDOUR::Session::AudioEngineSetupRequired.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::do_audio_midi_setup, this, _1)); + /* handle sr mismatch with a dialog (PROBLEM: needs to return a value and thus cannot be x-thread) */ ARDOUR::Session::AskAboutSampleRateMismatch.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::sr_mismatch_dialog, this, _1, _2)); @@ -369,6 +372,8 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) _process_thread->init (); DPIReset.connect (sigc::mem_fun (*this, &ARDOUR_UI::resize_text_widgets)); + + attach_to_engine (); } GlobalPortMatrixWindow* @@ -380,42 +385,146 @@ ARDOUR_UI::create_global_port_matrix (ARDOUR::DataType type) return new GlobalPortMatrixWindow (_session, type); } -int -ARDOUR_UI::create_engine () +void +ARDOUR_UI::attach_to_engine () { - // this gets called every time by new_session() + AudioEngine::instance()->Stopped.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::engine_stopped, this), gui_context()); + AudioEngine::instance()->Running.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::engine_running, this), gui_context()); + AudioEngine::instance()->SampleRateChanged.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::update_sample_rate, this, _1), gui_context()); - if (engine) { - return 0; + AudioEngine::instance()->Halted.connect_same_thread (halt_connection, boost::bind (&ARDOUR_UI::engine_halted, this, _1, false)); + + ARDOUR::Port::set_connecting_blocked (ARDOUR_COMMAND_LINE::no_connect_ports); + + /* if there is only one audio/midi backend, and it does not require setup, get our use of it underway + * right here (we need to know the client name and potential session ID + * to do this, which is why this is here, rather than in, say, + * ARDOUR::init(). + */ + + if (!AudioEngine::instance()->setup_required()) { + const AudioBackendInfo* backend = AudioEngine::instance()->available_backends().front(); + AudioEngine::instance()->set_backend (backend->name, ARDOUR_COMMAND_LINE::backend_client_name, ARDOUR_COMMAND_LINE::backend_session_uuid); + AudioEngine::instance()->start (); } +} - loading_message (_("Starting audio engine")); +void +ARDOUR_UI::engine_stopped () +{ + ENSURE_GUI_THREAD (*this, &ARDOUR_UI::engine_stopped) + ActionManager::set_sensitive (ActionManager::engine_sensitive_actions, false); + ActionManager::set_sensitive (ActionManager::engine_opposite_sensitive_actions, true); +} - try { - engine = new ARDOUR::AudioEngine (ARDOUR_COMMAND_LINE::jack_client_name, ARDOUR_COMMAND_LINE::jack_session_uuid); +void +ARDOUR_UI::engine_running () +{ + if (first_time_engine_run) { + post_engine(); + first_time_engine_run = false; + } + + ActionManager::set_sensitive (ActionManager::engine_sensitive_actions, true); + ActionManager::set_sensitive (ActionManager::engine_opposite_sensitive_actions, false); - } catch (...) { + Glib::RefPtr<Action> action; + const char* action_name = 0; - return -1; + switch (AudioEngine::instance()->samples_per_cycle()) { + case 32: + action_name = X_("JACKLatency32"); + break; + case 64: + action_name = X_("JACKLatency64"); + break; + case 128: + action_name = X_("JACKLatency128"); + break; + case 512: + action_name = X_("JACKLatency512"); + break; + case 1024: + action_name = X_("JACKLatency1024"); + break; + case 2048: + action_name = X_("JACKLatency2048"); + break; + case 4096: + action_name = X_("JACKLatency4096"); + break; + case 8192: + action_name = X_("JACKLatency8192"); + break; + default: + /* XXX can we do anything useful ? */ + break; } - engine->Stopped.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::engine_stopped, this), gui_context()); - engine->Running.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::engine_running, this), gui_context()); - engine->SampleRateChanged.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::update_sample_rate, this, _1), gui_context()); + if (action_name) { - engine->Halted.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::engine_halted, this, _1, false)); + action = ActionManager::get_action (X_("JACK"), action_name); - ARDOUR::Port::set_connecting_blocked (ARDOUR_COMMAND_LINE::no_connect_ports); + if (action) { + Glib::RefPtr<RadioAction> ract = Glib::RefPtr<RadioAction>::cast_dynamic (action); + ract->set_active (); + } - post_engine (); + update_disk_space (); + update_cpu_load (); + update_sample_rate (AudioEngine::instance()->sample_rate()); + update_timecode_format (); + } +} - return 0; +void +ARDOUR_UI::engine_halted (const char* reason, bool free_reason) +{ + if (!Gtkmm2ext::UI::instance()->caller_is_ui_thread()) { + /* we can't rely on the original string continuing to exist when we are called + again in the GUI thread, so make a copy and note that we need to + free it later. + */ + char *copy = strdup (reason); + Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&ARDOUR_UI::engine_halted, this, copy, true)); + return; + } + + ActionManager::set_sensitive (ActionManager::engine_sensitive_actions, false); + ActionManager::set_sensitive (ActionManager::engine_opposite_sensitive_actions, true); + + update_sample_rate (0); + + string msgstr; + + /* if the reason is a non-empty string, it means that the backend was shutdown + rather than just Ardour. + */ + + if (strlen (reason)) { + msgstr = string_compose (_("The audio backend (JACK) was shutdown because:\n\n%1"), reason); + } else { + msgstr = string_compose (_("\ +JACK has either been shutdown or it\n\ +disconnected %1 because %1\n\ +was not fast enough. Try to restart\n\ +JACK, reconnect and save the session."), PROGRAM_NAME); + } + + MessageDialog msg (*editor, msgstr); + pop_back_splash (msg); + msg.set_keep_above (true); + msg.run (); + + if (free_reason) { + free (const_cast<char*> (reason)); + } } void ARDOUR_UI::post_engine () { - /* Things to be done once we create the AudioEngine + /* Things to be done once (and once ONLY) after we have a backend running in the AudioEngine */ ARDOUR::init_post_engine (); @@ -481,25 +590,9 @@ ARDOUR_UI::post_engine () Glib::signal_timeout().connect_seconds (sigc::mem_fun(*this, &ARDOUR_UI::update_wall_clock), 1); #endif - update_disk_space (); - update_cpu_load (); - update_sample_rate (engine->frame_rate()); - update_timecode_format (); - Config->ParameterChanged.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::parameter_changed, this, _1), gui_context()); boost::function<void (string)> pc (boost::bind (&ARDOUR_UI::parameter_changed, this, _1)); Config->map_parameters (pc); - - /* now start and maybe save state */ - - if (do_engine_start () == 0) { - if (_session && _session_is_new) { - /* we need to retain initial visual - settings for a new session - */ - _session->save_state (""); - } - } } ARDOUR_UI::~ARDOUR_UI () @@ -685,6 +778,7 @@ ARDOUR_UI::startup () { Application* app = Application::instance (); char *nsm_url; + app->ShouldQuit.connect (sigc::mem_fun (*this, &ARDOUR_UI::queue_finish)); app->ShouldLoad.connect (sigc::mem_fun (*this, &ARDOUR_UI::idle_load)); @@ -779,7 +873,7 @@ ARDOUR_UI::check_memory_locking () XMLNode* memory_warning_node = Config->instant_xml (X_("no-memory-warning")); - if (engine->is_realtime() && memory_warning_node == 0) { + if (AudioEngine::instance()->is_realtime() && memory_warning_node == 0) { struct rlimit limits; int64_t ram; @@ -919,7 +1013,8 @@ If you still wish to quit, please use the\n\n\ _session = 0; } - engine->stop (true); + halt_connection.disconnect (); + AudioEngine::instance()->stop (); quit (); } @@ -1048,22 +1143,28 @@ ARDOUR_UI::update_sample_rate (framecnt_t) ENSURE_GUI_THREAD (*this, &ARDOUR_UI::update_sample_rate, ignored) - if (!engine->connected()) { + if (!AudioEngine::instance()->connected()) { - snprintf (buf, sizeof (buf), "%s", _("disconnected")); + snprintf (buf, sizeof (buf), _("Audio: <span foreground=\"red\">none</span>")); } else { - framecnt_t rate = engine->frame_rate(); + framecnt_t rate = AudioEngine::instance()->sample_rate(); - if (fmod (rate, 1000.0) != 0.0) { - snprintf (buf, sizeof (buf), _("JACK: <span foreground=\"green\">%.1f kHz / %4.1f ms</span>"), - (float) rate/1000.0f, - (engine->frames_per_cycle() / (float) rate) * 1000.0f); + if (rate == 0) { + /* no sample rate available */ + snprintf (buf, sizeof (buf), _("Audio: <span foreground=\"red\">none</span>")); } else { - snprintf (buf, sizeof (buf), _("JACK: <span foreground=\"green\">%" PRId64 " kHz / %4.1f ms</span>"), - rate/1000, - (engine->frames_per_cycle() / (float) rate) * 1000.0f); + + if (fmod (rate, 1000.0) != 0.0) { + snprintf (buf, sizeof (buf), _("Audio: <span foreground=\"green\">%.1f kHz / %4.1f ms</span>"), + (float) rate / 1000.0f, + (AudioEngine::instance()->usecs_per_cycle() / 1000.0f)); + } else { + snprintf (buf, sizeof (buf), _("Audio: <span foreground=\"green\">%" PRId64 " kHz / %4.1f ms</span>"), + rate/1000, + (AudioEngine::instance()->usecs_per_cycle() / 1000.0f)); + } } } @@ -1133,7 +1234,7 @@ ARDOUR_UI::update_cpu_load () should also be changed. */ - float const c = engine->get_cpu_load (); + float const c = AudioEngine::instance()->get_cpu_load (); snprintf (buf, sizeof (buf), _("DSP: <span foreground=\"%s\">%5.1f%%</span>"), c >= 90 ? X_("red") : X_("green"), c); cpu_load_label.set_markup (buf); } @@ -1187,6 +1288,11 @@ ARDOUR_UI::update_disk_space() char buf[64]; framecnt_t fr = _session->frame_rate(); + if (fr == 0) { + /* skip update - no SR available */ + return; + } + if (!opt_frames) { /* Available space is unknown */ snprintf (buf, sizeof (buf), "%s", _("Disk: <span foreground=\"green\">Unknown</span>")); @@ -1449,20 +1555,16 @@ ARDOUR_UI::open_recent_session () bool ARDOUR_UI::check_audioengine () { - if (engine) { - if (!engine->connected()) { - MessageDialog msg (string_compose ( - _("%1 is not connected to JACK\n" - "You cannot open or close sessions in this condition"), - PROGRAM_NAME)); - pop_back_splash (msg); - msg.run (); - return false; - } - return true; - } else { + if (!AudioEngine::instance()->connected()) { + MessageDialog msg (string_compose ( + _("%1 is not connected to any audio backend.\n" + "You cannot open or close sessions in this condition"), + PROGRAM_NAME)); + pop_back_splash (msg); + msg.run (); return false; } + return true; } void @@ -1668,10 +1770,17 @@ ARDOUR_UI::transport_goto_wallclock () time (&now); localtime_r (&now, &tmnow); + + int frame_rate = _session->frame_rate(); + + if (frame_rate == 0) { + /* no frame rate available */ + return; + } - frames = tmnow.tm_hour * (60 * 60 * _session->frame_rate()); - frames += tmnow.tm_min * (60 * _session->frame_rate()); - frames += tmnow.tm_sec * _session->frame_rate(); + frames = tmnow.tm_hour * (60 * 60 * frame_rate); + frames += tmnow.tm_min * (60 * frame_rate); + frames += tmnow.tm_sec * frame_rate; _session->request_locate (frames, _session->transport_rolling ()); @@ -2032,127 +2141,6 @@ ARDOUR_UI::map_transport_state () } void -ARDOUR_UI::engine_stopped () -{ - ENSURE_GUI_THREAD (*this, &ARDOUR_UI::engine_stopped) - ActionManager::set_sensitive (ActionManager::jack_sensitive_actions, false); - ActionManager::set_sensitive (ActionManager::jack_opposite_sensitive_actions, true); -} - -void -ARDOUR_UI::engine_running () -{ - ENSURE_GUI_THREAD (*this, &ARDOUR_UI::engine_running) - ActionManager::set_sensitive (ActionManager::jack_sensitive_actions, true); - ActionManager::set_sensitive (ActionManager::jack_opposite_sensitive_actions, false); - - Glib::RefPtr<Action> action; - const char* action_name = 0; - - switch (engine->frames_per_cycle()) { - case 32: - action_name = X_("JACKLatency32"); - break; - case 64: - action_name = X_("JACKLatency64"); - break; - case 128: - action_name = X_("JACKLatency128"); - break; - case 512: - action_name = X_("JACKLatency512"); - break; - case 1024: - action_name = X_("JACKLatency1024"); - break; - case 2048: - action_name = X_("JACKLatency2048"); - break; - case 4096: - action_name = X_("JACKLatency4096"); - break; - case 8192: - action_name = X_("JACKLatency8192"); - break; - default: - /* XXX can we do anything useful ? */ - break; - } - - if (action_name) { - - action = ActionManager::get_action (X_("JACK"), action_name); - - if (action) { - Glib::RefPtr<RadioAction> ract = Glib::RefPtr<RadioAction>::cast_dynamic (action); - ract->set_active (); - } - } -} - -void -ARDOUR_UI::engine_halted (const char* reason, bool free_reason) -{ - if (!Gtkmm2ext::UI::instance()->caller_is_ui_thread()) { - /* we can't rely on the original string continuing to exist when we are called - again in the GUI thread, so make a copy and note that we need to - free it later. - */ - char *copy = strdup (reason); - Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&ARDOUR_UI::engine_halted, this, copy, true)); - return; - } - - ActionManager::set_sensitive (ActionManager::jack_sensitive_actions, false); - ActionManager::set_sensitive (ActionManager::jack_opposite_sensitive_actions, true); - - update_sample_rate (0); - - string msgstr; - - /* if the reason is a non-empty string, it means that the backend was shutdown - rather than just Ardour. - */ - - if (strlen (reason)) { - msgstr = string_compose (_("The audio backend (JACK) was shutdown because:\n\n%1"), reason); - } else { - msgstr = string_compose (_("\ -JACK has either been shutdown or it\n\ -disconnected %1 because %1\n\ -was not fast enough. Try to restart\n\ -JACK, reconnect and save the session."), PROGRAM_NAME); - } - - MessageDialog msg (*editor, msgstr); - pop_back_splash (msg); - msg.set_keep_above (true); - msg.run (); - - if (free_reason) { - free (const_cast<char*> (reason)); - } -} - -int32_t -ARDOUR_UI::do_engine_start () -{ - try { - engine->start(); - } - - catch (...) { - engine->stop (); - error << _("Unable to start the session running") - << endmsg; - unload_session (); - return -2; - } - - return 0; -} - -void ARDOUR_UI::update_clocks () { if (!editor || !editor->dragging_playhead()) { @@ -2702,10 +2690,6 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri } } - if (create_engine ()) { - break; - } - if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { if (likely_new && !nsm) { @@ -2801,25 +2785,23 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, int unload_status; int retval = -1; - session_loaded = false; - - if (!check_audioengine()) { - return -2; + if (_session) { + unload_status = unload_session (); + + if (unload_status < 0) { + goto out; + } else if (unload_status > 0) { + retval = 0; + goto out; + } } - unload_status = unload_session (); - - if (unload_status < 0) { - goto out; - } else if (unload_status > 0) { - retval = 0; - goto out; - } + session_loaded = false; loading_message (string_compose (_("Please wait while %1 loads your session"), PROGRAM_NAME)); try { - new_session = new Session (*engine, path, snap_name, 0, mix_template); + new_session = new Session (*AudioEngine::instance(), path, snap_name, 0, mix_template); } /* this one is special */ @@ -2920,12 +2902,7 @@ ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name, Session *new_session; int x; - if (!check_audioengine()) { - return -1; - } - session_loaded = false; - x = unload_session (); if (x < 0) { @@ -2937,7 +2914,7 @@ ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name, _session_is_new = true; try { - new_session = new Session (*engine, path, snap_name, &bus_profile); + new_session = new Session (*AudioEngine::instance(), path, snap_name, &bus_profile); } catch (...) { @@ -3832,31 +3809,33 @@ audio may be played at the wrong sample rate.\n"), desired, PROGRAM_NAME, actual return 1; } - void -ARDOUR_UI::disconnect_from_jack () +ARDOUR_UI::disconnect_from_engine () { - if (engine) { - if (engine->disconnect_from_jack ()) { - MessageDialog msg (*editor, _("Could not disconnect from JACK")); - msg.run (); - } - - update_sample_rate (0); + /* drop connection to AudioEngine::Halted so that we don't act + * as if the engine unexpectedly shut down + */ + halt_connection.disconnect (); + + if (AudioEngine::instance()->stop ()) { + MessageDialog msg (*editor, _("Could not disconnect from JACK")); + msg.run (); + } else { + AudioEngine::instance()->Halted.connect_same_thread (halt_connection, boost::bind (&ARDOUR_UI::engine_halted, this, _1, false)); } + + update_sample_rate (0); } void -ARDOUR_UI::reconnect_to_jack () +ARDOUR_UI::reconnect_to_engine () { - if (engine) { - if (engine->reconnect_to_jack ()) { - MessageDialog msg (*editor, _("Could not reconnect to JACK")); - msg.run (); - } - - update_sample_rate (0); + if (AudioEngine::instance()->start ()) { + MessageDialog msg (*editor, _("Could not reconnect to JACK")); + msg.run (); } + + update_sample_rate (0); } void @@ -4163,3 +4142,49 @@ ARDOUR_UI::reset_route_peak_display (Route* route) reset_peak_display (); } } + +void +ARDOUR_UI::toggle_audio_midi_setup () +{ + Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("toggle-audio-midi-setup")); + if (!act) { + return; + } + + Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act); + + if (tact->get_active()) { + launch_audio_midi_setup (); + } else { + _audio_midi_setup->hide (); + } +} + +void +ARDOUR_UI::launch_audio_midi_setup () +{ + if (!_audio_midi_setup) { + _audio_midi_setup = new EngineControl (); + } + + _audio_midi_setup->present (); +} + +int +ARDOUR_UI::do_audio_midi_setup (uint32_t desired_sample_rate) +{ + launch_audio_midi_setup (); + + _audio_midi_setup->set_desired_sample_rate (desired_sample_rate); + + int r = _audio_midi_setup->run (); + + switch (r) { + case Gtk::RESPONSE_OK: + return 0; + case Gtk::RESPONSE_APPLY: + return 0; + default: + return -1; + } +} diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index 15413b5ac9..64309c7e38 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -90,6 +90,7 @@ class BigClockWindow; class BundleManager; class ButtonJoiner; class ConnectionEditor; +class EngineControl; class KeyEditor; class LocationUIWindow; class MainClock; @@ -264,7 +265,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr session_add_midi_route (false); }*/ - int create_engine (); + void attach_to_engine (); void post_engine (); gint exit_on_main_window_close (GdkEventAny *); @@ -302,19 +303,18 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr void reenable_hide_loop_punch_ruler_if_appropriate (); void toggle_auto_return (); void toggle_click (); - + void toggle_audio_midi_setup (); void toggle_session_auto_loop (); - void toggle_rc_options_window (); void toggle_session_options_window (); private: ArdourStartup* _startup; - ARDOUR::AudioEngine *engine; Gtk::Tooltips _tooltips; NSM_Client *nsm; bool _was_dirty; bool _mixer_on_top; + bool first_time_engine_run; void goto_editor_window (); void goto_mixer_window (); @@ -667,9 +667,9 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr int pending_state_dialog (); int sr_mismatch_dialog (ARDOUR::framecnt_t, ARDOUR::framecnt_t); - void disconnect_from_jack (); - void reconnect_to_jack (); - void set_jack_buffer_size (ARDOUR::pframes_t); + void disconnect_from_engine (); + void reconnect_to_engine (); + void set_engine_buffer_size (ARDOUR::pframes_t); Gtk::MenuItem* jack_disconnect_item; Gtk::MenuItem* jack_reconnect_item; @@ -710,6 +710,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr void loading_message (const std::string& msg); PBD::ScopedConnectionList forever_connections; + PBD::ScopedConnection halt_connection; void step_edit_status_change (bool); @@ -746,6 +747,10 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr std::string _announce_string; void check_announcements (); + + EngineControl* _audio_midi_setup; + void launch_audio_midi_setup (); + int do_audio_midi_setup (uint32_t); }; #endif /* __ardour_gui_h__ */ diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc index bca762569f..a0d61afdff 100644 --- a/gtk2_ardour/ardour_ui_dialogs.cc +++ b/gtk2_ardour/ardour_ui_dialogs.cc @@ -165,10 +165,6 @@ ARDOUR_UI::set_session (Session *s) _session->locations()->removed.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::handle_locations_change, this, _1), gui_context()); _session->config.ParameterChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::session_parameter_changed, this, _1), gui_context ()); -#ifdef HAVE_JACK_SESSION - engine->JackSessionEvent.connect (*_session, MISSING_INVALIDATOR, boost::bind (&Session::jack_session_event, _session, _1), gui_context()); -#endif - /* Clocks are on by default after we are connected to a session, so show that here. */ @@ -283,7 +279,10 @@ ARDOUR_UI::unload_session (bool hide_stuff) rec_button.set_sensitive (false); WM::Manager::instance().set_session ((ARDOUR::Session*) 0); - ARDOUR_UI::instance()->video_timeline->close_session(); + + if (ARDOUR_UI::instance()->video_timeline) { + ARDOUR_UI::instance()->video_timeline->close_session(); + } stop_blinking (); stop_clocking (); diff --git a/gtk2_ardour/ardour_ui_ed.cc b/gtk2_ardour/ardour_ui_ed.cc index 6c050578ec..53ea7c02de 100644 --- a/gtk2_ardour/ardour_ui_ed.cc +++ b/gtk2_ardour/ardour_ui_ed.cc @@ -183,38 +183,38 @@ ARDOUR_UI::install_actions () ActionManager::write_sensitive_actions.push_back (act); ActionManager::session_sensitive_actions.push_back (act); - /* JACK actions for controlling ... JACK */ - - Glib::RefPtr<ActionGroup> jack_actions = ActionGroup::create (X_("JACK")); - ActionManager::register_action (jack_actions, X_("JACK"), _("JACK")); - ActionManager::register_action (jack_actions, X_("Latency"), _("Latency")); - - act = ActionManager::register_action (jack_actions, X_("JACKReconnect"), _("Reconnect"), sigc::mem_fun (*(ARDOUR_UI::instance()), &ARDOUR_UI::reconnect_to_jack)); - ActionManager::jack_opposite_sensitive_actions.push_back (act); - - act = ActionManager::register_action (jack_actions, X_("JACKDisconnect"), _("Disconnect"), sigc::mem_fun (*(ARDOUR_UI::instance()), &ARDOUR_UI::disconnect_from_jack)); - ActionManager::jack_sensitive_actions.push_back (act); - - RadioAction::Group jack_latency_group; - - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency32"), X_("32"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 32)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency64"), X_("64"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 64)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency128"), X_("128"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 128)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency256"), X_("256"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 256)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency512"), X_("512"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 512)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency1024"), X_("1024"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 1024)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency2048"), X_("2048"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 2048)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency4096"), X_("4096"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 4096)); - ActionManager::jack_sensitive_actions.push_back (act); - act = ActionManager::register_radio_action (jack_actions, jack_latency_group, X_("JACKLatency8192"), X_("8192"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_jack_buffer_size), (pframes_t) 8192)); - ActionManager::jack_sensitive_actions.push_back (act); + /* AudioEngine actions */ + + Glib::RefPtr<ActionGroup> engine_actions = ActionGroup::create (X_("Audio")); + ActionManager::register_action (engine_actions, X_("JACK"), _("JACK")); + ActionManager::register_action (engine_actions, X_("Latency"), _("Latency")); + + act = ActionManager::register_action (engine_actions, X_("EngineReconnect"), _("Reconnect"), sigc::mem_fun (*(ARDOUR_UI::instance()), &ARDOUR_UI::reconnect_to_engine)); + ActionManager::engine_opposite_sensitive_actions.push_back (act); + + act = ActionManager::register_action (engine_actions, X_("EngineDisconnect"), _("Disconnect"), sigc::mem_fun (*(ARDOUR_UI::instance()), &ARDOUR_UI::disconnect_from_engine)); + ActionManager::engine_sensitive_actions.push_back (act); + + RadioAction::Group latency_group; + + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency32"), X_("32"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 32)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency64"), X_("64"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 64)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency128"), X_("128"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 128)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency256"), X_("256"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 256)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency512"), X_("512"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 512)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency1024"), X_("1024"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 1024)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency2048"), X_("2048"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 2048)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency4096"), X_("4096"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 4096)); + ActionManager::engine_sensitive_actions.push_back (act); + act = ActionManager::register_radio_action (engine_actions, latency_group, X_("EngineLatency8192"), X_("8192"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_engine_buffer_size), (pframes_t) 8192)); + ActionManager::engine_sensitive_actions.push_back (act); /* these actions are intended to be shared across all windows */ @@ -230,6 +230,7 @@ ARDOUR_UI::install_actions () ActionManager::register_toggle_action (common_actions, X_("toggle-mixer"), S_("Window|Mixer"), sigc::mem_fun(*this, &ARDOUR_UI::toggle_mixer_window)); ActionManager::register_action (common_actions, X_("toggle-editor-mixer"), _("Toggle Editor+Mixer"), sigc::mem_fun(*this, &ARDOUR_UI::toggle_editor_mixer)); ActionManager::register_toggle_action (common_actions, X_("toggle-meterbridge"), S_("Window|Meterbridge"), sigc::mem_fun(*this, &ARDOUR_UI::toggle_meterbridge)); + ActionManager::register_toggle_action (common_actions, X_("toggle-audio-midi-setup"), S_("Window|Audio/MIDI Setup"), sigc::mem_fun(*this, &ARDOUR_UI::toggle_audio_midi_setup)); act = ActionManager::register_action (common_actions, X_("NewMIDITracer"), _("MIDI Tracer"), sigc::mem_fun(*this, &ARDOUR_UI::new_midi_tracer_window)); ActionManager::session_sensitive_actions.push_back (act); @@ -421,7 +422,7 @@ ARDOUR_UI::install_actions () ActionManager::add_action_group (shuttle_actions); ActionManager::add_action_group (option_actions); - ActionManager::add_action_group (jack_actions); + ActionManager::add_action_group (engine_actions); ActionManager::add_action_group (transport_actions); ActionManager::add_action_group (main_actions); ActionManager::add_action_group (main_menu_actions); @@ -430,38 +431,38 @@ ARDOUR_UI::install_actions () } void -ARDOUR_UI::set_jack_buffer_size (pframes_t nframes) +ARDOUR_UI::set_engine_buffer_size (pframes_t nframes) { Glib::RefPtr<Action> action; const char* action_name = 0; switch (nframes) { case 32: - action_name = X_("JACKLatency32"); + action_name = X_("EngineLatency32"); break; case 64: - action_name = X_("JACKLatency64"); + action_name = X_("EngineLatency64"); break; case 128: - action_name = X_("JACKLatency128"); + action_name = X_("EngineLatency128"); break; case 256: - action_name = X_("JACKLatency256"); + action_name = X_("EngineLatency256"); break; case 512: - action_name = X_("JACKLatency512"); + action_name = X_("EngineLatency512"); break; case 1024: - action_name = X_("JACKLatency1024"); + action_name = X_("EngineLatency1024"); break; case 2048: - action_name = X_("JACKLatency2048"); + action_name = X_("EngineLatency2048"); break; case 4096: - action_name = X_("JACKLatency4096"); + action_name = X_("EngineLatency4096"); break; case 8192: - action_name = X_("JACKLatency8192"); + action_name = X_("EngineLatency8192"); break; default: /* XXX can we do anything useful ? */ @@ -476,7 +477,7 @@ ARDOUR_UI::set_jack_buffer_size (pframes_t nframes) Glib::RefPtr<RadioAction> ract = Glib::RefPtr<RadioAction>::cast_dynamic (action); if (ract && ract->get_active()) { - engine->request_buffer_size (nframes); + AudioEngine::instance()->request_buffer_size (nframes); update_sample_rate (0); } } @@ -647,11 +648,10 @@ ARDOUR_UI::save_ardour_state () window_node->add_child_nocopy (*tearoff_node); Config->add_extra_xml (*window_node); + Config->add_extra_xml (_audio_midi_setup->get_state()); - if (_startup && _startup->engine_control() && _startup->engine_control()->was_used()) { - Config->add_extra_xml (_startup->engine_control()->get_state()); - } Config->save_state(); + if (ui_config->dirty()) { ui_config->save_state (); } diff --git a/gtk2_ardour/arval b/gtk2_ardour/arval index 6503bc1ede..2209dcb110 100755 --- a/gtk2_ardour/arval +++ b/gtk2_ardour/arval @@ -10,5 +10,6 @@ LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH export ARDOUR_RUNNING_UNDER_VALGRIND=TRUE exec valgrind --tool=memcheck \ $VALGRIND_OPTIONS \ + --track-origins=yes \ --suppressions=`dirname "$0"`/../tools/valgrind.supp \ $TOP/$EXECUTABLE --novst "$@" diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 300e317a38..a0418892e7 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -1258,7 +1258,8 @@ Editor::set_session (Session *t) /* These signals can all be emitted by a non-GUI thread. Therefore the handlers for them must not attempt to directly interact with the GUI, - but use Gtkmm2ext::UI::instance()->call_slot(); + but use PBD::Signal<T>::connect() which accepts an event loop + ("context") where the handler will be asked to run. */ _session->StepEditStatusChange.connect (_session_connections, invalidator (*this), boost::bind (&Editor::step_edit_status_change, this, _1), gui_context()); diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index b083e851c5..9c65892ebd 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -2371,9 +2371,14 @@ CursorDrag::fake_locate (framepos_t t) Session* s = _editor->session (); if (s->timecode_transmission_suspended ()) { framepos_t const f = _editor->playhead_cursor->current_frame; + /* This is asynchronous so it will be sent "now" + */ s->send_mmc_locate (f); - s->send_full_time_code (f); - s->send_song_position_pointer (f); + /* These are synchronous and will be sent during the next + process cycle + */ + s->queue_full_time_code (); + s->queue_song_position_pointer (); } show_verbose_cursor_time (t); diff --git a/gtk2_ardour/engine_dialog.cc b/gtk2_ardour/engine_dialog.cc index 05cd9a661e..873498f6ec 100644 --- a/gtk2_ardour/engine_dialog.cc +++ b/gtk2_ardour/engine_dialog.cc @@ -17,6 +17,7 @@ */ +#include <exception> #include <vector> #include <cmath> #include <fstream> @@ -24,37 +25,26 @@ #include <boost/scoped_ptr.hpp> -#include <glibmm.h> #include <gtkmm/messagedialog.h> -#include "pbd/epa.h" +#include "pbd/error.h" #include "pbd/xml++.h" -#ifdef __APPLE__ -#include <CoreAudio/CoreAudio.h> -#include <CoreFoundation/CFString.h> -#include <sys/param.h> -#include <mach-o/dyld.h> -#elif !defined(__FreeBSD__) -#include <alsa/asoundlib.h> -#endif - -#include <jack/jack.h> - #include <gtkmm/stock.h> +#include <gtkmm/notebook.h> #include <gtkmm2ext/utils.h> +#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 "pbd/pathscanner.h" - -#ifdef __APPLE -#include <CFBundle.h> -#endif #include "engine_dialog.h" +#include "gui_thread.h" #include "i18n.h" using namespace std; @@ -64,322 +54,236 @@ using namespace PBD; using namespace Glib; EngineControl::EngineControl () - : periods_adjustment (2, 2, 16, 1, 2), - periods_spinner (periods_adjustment), - ports_adjustment (128, 8, 1024, 1, 16), - ports_spinner (ports_adjustment), - input_latency_adjustment (0, 0, 99999, 1), - input_latency (input_latency_adjustment), - output_latency_adjustment (0, 0, 99999, 1), - output_latency (output_latency_adjustment), - realtime_button (_("Realtime")), - no_memory_lock_button (_("Do not lock memory")), - unlock_memory_button (_("Unlock memory")), - soft_mode_button (_("No zombies")), - monitor_button (_("Provide monitor ports")), - force16bit_button (_("Force 16 bit")), - hw_monitor_button (_("H/W monitoring")), - hw_meter_button (_("H/W metering")), - verbose_output_button (_("Verbose output")), - start_button (_("Start")), - stop_button (_("Stop")), -#ifdef __APPLE___ - basic_packer (5, 2), - options_packer (4, 2), - device_packer (4, 2) -#else - basic_packer (8, 2), - options_packer (14, 2), - device_packer (6, 2) -#endif + : ArdourDialog (_("Audio/MIDI Setup")) + , input_latency_adjustment (0, 0, 99999, 1) + , input_latency (input_latency_adjustment) + , output_latency_adjustment (0, 0, 99999, 1) + , output_latency (output_latency_adjustment) + , input_channels_adjustment (0, 0, 256, 1) + , input_channels (input_channels_adjustment) + , output_channels_adjustment (0, 0, 256, 1) + , output_channels (output_channels_adjustment) + , 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) +{ + build_notebook (); + + get_vbox()->set_border_width (12); + get_vbox()->pack_start (notebook); + + control_app_button.signal_clicked().connect (mem_fun (*this, &EngineControl::control_app_button_clicked)); + manage_control_app_sensitivity (); + + add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK); + add_button (Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY); + + /* Pick up any existing audio setup configuration, if appropriate */ + + XMLNode* audio_setup = ARDOUR::Config->extra_xml ("AudioMIDISetup"); + + /* push a change as if we altered the backend */ + backend_changed (); + + 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 +EngineControl::on_response (int response_id) +{ + ArdourDialog::on_response (response_id); + + switch (response_id) { + case RESPONSE_APPLY: + push_state_to_backend (true); + break; + case RESPONSE_OK: + push_state_to_backend (true); + hide (); + break; + default: + hide (); + } +} + +void +EngineControl::build_notebook () { using namespace Notebook_Helpers; Label* label; vector<string> strings; int row = 0; - _used = false; - - strings.push_back (_("8000Hz")); - strings.push_back (_("22050Hz")); - strings.push_back (_("44100Hz")); - strings.push_back (_("48000Hz")); - strings.push_back (_("88200Hz")); - strings.push_back (_("96000Hz")); - strings.push_back (_("192000Hz")); - set_popdown_strings (sample_rate_combo, strings); - sample_rate_combo.set_active_text ("48000Hz"); - - strings.clear (); - strings.push_back ("32"); - strings.push_back ("64"); - strings.push_back ("128"); - strings.push_back ("256"); - strings.push_back ("512"); - strings.push_back ("1024"); - strings.push_back ("2048"); - strings.push_back ("4096"); - strings.push_back ("8192"); - set_popdown_strings (period_size_combo, strings); - period_size_combo.set_active_text ("1024"); - - strings.clear (); - strings.push_back (_("None")); - strings.push_back (_("Triangular")); - strings.push_back (_("Rectangular")); - strings.push_back (_("Shaped")); - set_popdown_strings (dither_mode_combo, strings); - dither_mode_combo.set_active_text (_("None")); - - /* basic parameters */ + vector<const ARDOUR::AudioBackendInfo*> backends = ARDOUR::AudioEngine::instance()->available_backends(); + for (vector<const ARDOUR::AudioBackendInfo*>::const_iterator b = backends.begin(); b != backends.end(); ++b) { + strings.push_back ((*b)->name); + } + + set_popdown_strings (backend_combo, strings); + backend_combo.set_active_text (strings.front()); basic_packer.set_spacings (6); + basic_packer.set_border_width (12); + basic_packer.set_homogeneous (true); - strings.clear (); -#ifdef __APPLE__ - strings.push_back (X_("CoreAudio")); -#else -#ifndef __FreeBSD__ - strings.push_back (X_("ALSA")); -#endif - strings.push_back (X_("OSS")); - strings.push_back (X_("FreeBoB")); - strings.push_back (X_("FFADO")); -#endif - strings.push_back (X_("NetJACK")); - strings.push_back (X_("Dummy")); - set_popdown_strings (driver_combo, strings); - driver_combo.set_active_text (strings.front()); + row = 0; - driver_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::driver_changed)); - driver_changed (); - - strings.clear (); - strings.push_back (_("Playback/recording on 1 device")); - strings.push_back (_("Playback/recording on 2 devices")); - strings.push_back (_("Playback only")); - strings.push_back (_("Recording only")); - set_popdown_strings (audio_mode_combo, strings); - audio_mode_combo.set_active_text (strings.front()); - - audio_mode_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::audio_mode_changed)); - audio_mode_changed (); - - strings.clear (); - strings.push_back (_("None")); -#ifdef __APPLE__ - strings.push_back (_("coremidi")); -#else - strings.push_back (_("seq")); - strings.push_back (_("raw")); -#endif - set_popdown_strings (midi_driver_combo, strings); - midi_driver_combo.set_active_text (strings.front ()); + AttachOptions xopt = AttachOptions (FILL|EXPAND); - row = 0; + label = manage (left_aligned_label (_("Audio System:"))); + basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); + basic_packer.attach (backend_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0); + row++; label = manage (left_aligned_label (_("Driver:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (driver_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); + basic_packer.attach (driver_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0); row++; - label = manage (left_aligned_label (_("Audio Interface:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (interface_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + label = manage (left_aligned_label (_("Device:"))); + basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); + basic_packer.attach (device_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0); row++; label = manage (left_aligned_label (_("Sample rate:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (sample_rate_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); + basic_packer.attach (sample_rate_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0); row++; - label = manage (left_aligned_label (_("Buffer size:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (period_size_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - row++; -#if !defined(__APPLE__) && !defined(__FreeBSD__) - label = manage (left_aligned_label (_("Number of buffers:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (periods_spinner, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - periods_spinner.set_value (2); + label = manage (left_aligned_label (_("Buffer size:"))); + basic_packer.attach (*label, 0, 1, row, row + 1, xopt, (AttachOptions) 0); + basic_packer.attach (buffer_size_combo, 1, 2, row, row + 1, xopt, (AttachOptions) 0); + buffer_size_duration_label.set_alignment (0.0); /* left-align */ + basic_packer.attach (buffer_size_duration_label, 2, 3, row, row+1, xopt, (AttachOptions) 0); row++; -#endif - label = manage (left_aligned_label (_("Approximate latency:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (latency_label, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - row++; - sample_rate_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::redisplay_latency)); - periods_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &EngineControl::redisplay_latency)); - period_size_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::redisplay_latency)); - redisplay_latency(); - row++; - /* no audio mode with CoreAudio, its duplex or nuthin' */ + label = manage (left_aligned_label (_("Input Channels:"))); + basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + basic_packer.attach (input_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; -#if !defined(__APPLE__) && !defined(__FreeBSD__) - label = manage (left_aligned_label (_("Audio mode:"))); - basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - basic_packer.attach (audio_mode_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - row++; -#endif - interface_combo.set_size_request (250, -1); - input_device_combo.set_size_request (250, -1); - output_device_combo.set_size_request (250, -1); + label = manage (left_aligned_label (_("Output Channels:"))); + basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + basic_packer.attach (output_channels, 1, 2, row, row+1, xopt, (AttachOptions) 0); + ++row; - /* - if (engine_running()) { - start_button.set_sensitive (false); - } else { - stop_button.set_sensitive (false); - } + label = manage (left_aligned_label (_("Hardware input latency:"))); + basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + basic_packer.attach (input_latency, 1, 2, row, row+1, xopt, (AttachOptions) 0); + label = manage (left_aligned_label (_("samples"))); + basic_packer.attach (*label, 2, 3, row, row+1, xopt, (AttachOptions) 0); + ++row; - start_button.signal_clicked().connect (sigc::mem_fun (*this, &EngineControl::start_engine)); - stop_button.signal_clicked().connect (sigc::mem_fun (*this, &EngineControl::start_engine)); - */ + label = manage (left_aligned_label (_("Hardware output latency:"))); + basic_packer.attach (*label, 0, 1, row, row+1, xopt, (AttachOptions) 0); + basic_packer.attach (output_latency, 1, 2, row, row+1, xopt, (AttachOptions) 0); + label = manage (left_aligned_label (_("samples"))); + basic_packer.attach (*label, 2, 3, row, row+1, xopt, (AttachOptions) 0); + ++row; - button_box.pack_start (start_button, false, false); - button_box.pack_start (stop_button, false, false); + basic_hbox.pack_start (basic_packer, false, false); + basic_vbox.pack_start (basic_hbox, false, false); - // basic_packer.attach (button_box, 0, 2, 8, 9, FILL|EXPAND, (AttachOptions) 0); + Gtk::HBox* hpacker = manage (new HBox); + hpacker->set_border_width (12); + hpacker->pack_start (control_app_button, false, false); + hpacker->show (); + control_app_button.show(); + basic_vbox.pack_start (*hpacker); - /* options */ + midi_packer.set_border_width (12); - options_packer.set_spacings (6); + /* latency measurement tab */ + + lm_title.set_markup (string_compose ("<span size=\"large\" weight=\"bold\">%1</span>", _("Latency Measurement Tool"))); + row = 0; + lm_table.set_row_spacings (12); - options_packer.attach (realtime_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; + lm_table.attach (lm_title, 0, 2, row, row+1, xopt, (AttachOptions) 0); + row++; - realtime_button.set_active (true); + lm_preamble.set_width_chars (60); + lm_preamble.set_line_wrap (true); + lm_preamble.set_markup (_("1. <span weight=\"bold\">Turn down the volume on your hardware to a very low level.</span>\n\n\ +2. Connect the two channels that you select below using either a cable or (less ideally) a speaker \ +and microphone.\n\n\ +3. Once the channels are connected, click the \"Measure latency\" button.\n\n\ +4. When satisfied with the results, click the \"Use results\" button.")); -#if PROVIDE_TOO_MANY_OPTIONS + lm_table.attach (lm_preamble, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0); + row++; -#if !defined(__APPLE__) && !defined(__FreeBSD__) - options_packer.attach (no_memory_lock_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (unlock_memory_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (soft_mode_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (monitor_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (force16bit_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (hw_monitor_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (hw_meter_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - options_packer.attach (verbose_output_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; -#else - options_packer.attach (verbose_output_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; -#endif - - strings.clear (); - strings.push_back (_("Ignore")); - strings.push_back ("500 msec"); - strings.push_back ("1 sec"); - strings.push_back ("2 sec"); - strings.push_back ("10 sec"); - set_popdown_strings (timeout_combo, strings); - timeout_combo.set_active_text (strings.front ()); - - label = manage (new Label (_("Client timeout"))); - label->set_alignment (1.0, 0.5); - options_packer.attach (timeout_combo, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0)); - options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + label = manage (new 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; -#endif /* PROVIDE_TOO_MANY_OPTIONS */ - label = manage (left_aligned_label (_("Number of ports:"))); - options_packer.attach (ports_spinner, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0)); - options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + label = manage (new 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; - label = manage (left_aligned_label (_("MIDI driver:"))); - options_packer.attach (midi_driver_combo, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0)); - options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; + xopt = AttachOptions(0); -#if !defined(__APPLE__) && !defined(__FreeBSD__) - label = manage (left_aligned_label (_("Dither:"))); - options_packer.attach (dither_mode_combo, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0)); - options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); + lm_measure_button.signal_toggled().connect (sigc::mem_fun (*this, &EngineControl::latency_button_toggled)); + lm_use_button.signal_clicked().connect (sigc::mem_fun (*this, &EngineControl::use_latency_button_clicked)); + lm_use_button.set_sensitive (false); + + lm_table.attach (lm_measure_button, 0, 2, row, row+1, xopt, (AttachOptions) 0); ++row; -#endif - - find_jack_servers (server_strings); - - if (server_strings.empty()) { - fatal << _("No JACK server found anywhere on this system. Please install JACK and restart") << endmsg; - /*NOTREACHED*/ - } - - set_popdown_strings (serverpath_combo, server_strings); - serverpath_combo.set_active_text (server_strings.front()); - - if (server_strings.size() > 1) { - label = manage (left_aligned_label (_("Server:"))); - options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - options_packer.attach (serverpath_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0); - ++row; - } - - /* device settings */ - - device_packer.set_spacings (6); - row = 0; - -#if !defined(__APPLE__) && !defined(__FreeBSD__) - label = manage (left_aligned_label (_("Input device:"))); - device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0); - device_packer.attach (input_device_combo, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0); + lm_table.attach (lm_results, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), (AttachOptions) 0); ++row; - label = manage (left_aligned_label (_("Output device:"))); - device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0); - device_packer.attach (output_device_combo, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0); - ++row; -#endif - label = manage (left_aligned_label (_("Hardware input latency:"))); - device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0); - device_packer.attach (input_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0); - label = manage (left_aligned_label (_("samples"))); - device_packer.attach (*label, 2, 3, row, row+1, FILL|EXPAND, (AttachOptions) 0); - ++row; - label = manage (left_aligned_label (_("Hardware output latency:"))); - device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0); - device_packer.attach (output_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0); - label = manage (left_aligned_label (_("samples"))); - device_packer.attach (*label, 2, 3, row, row+1, FILL|EXPAND, (AttachOptions) 0); + lm_table.attach (lm_use_button, 0, 2, row, row+1, xopt, (AttachOptions) 0); ++row; - basic_hbox.pack_start (basic_packer, false, false); - options_hbox.pack_start (options_packer, false, false); + lm_results.set_markup ("<i>No measurement results yet</i>"); - device_packer.set_border_width (12); - options_packer.set_border_width (12); - basic_packer.set_border_width (12); + lm_vbox.pack_start (lm_table, false, false); - notebook.pages().push_back (TabElem (basic_hbox, _("Device"))); - notebook.pages().push_back (TabElem (options_hbox, _("Options"))); - notebook.pages().push_back (TabElem (device_packer, _("Advanced"))); + /* 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); - set_border_width (12); - pack_start (notebook); + notebook.set_tab_pos (POS_RIGHT); + notebook.show_all (); - /* Pick up any existing audio setup configuration, if appropriate */ + notebook.set_name ("SettingsNotebook"); - XMLNode* audio_setup = ARDOUR::Config->extra_xml ("AudioSetup"); - - if (audio_setup) { - set_state (*audio_setup); - } + /* Connect to signals */ + + backend_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::backend_changed)); + driver_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::driver_changed)); + sample_rate_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::sample_rate_changed)); + buffer_size_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::buffer_size_changed)); + input_channels.signal_output().connect (sigc::bind (sigc::ptr_fun (&EngineControl::print_channel_count), &input_channels)); + output_channels.signal_output().connect (sigc::bind (sigc::ptr_fun (&EngineControl::print_channel_count), &output_channels)); + device_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::device_changed)); } EngineControl::~EngineControl () @@ -388,555 +292,543 @@ EngineControl::~EngineControl () } void -EngineControl::build_command_line (vector<string>& cmd) +EngineControl::disable_latency_tab () { - string str; - string driver; - bool using_alsa = false; - bool using_coreaudio = false; - bool using_dummy = false; - bool using_ffado = false; - - /* first, path to jackd */ - - cmd.push_back (serverpath_combo.get_active_text ()); - - /* now jackd arguments */ - - str = timeout_combo.get_active_text (); - - if (str != _("Ignore")) { - - double secs = 0; - uint32_t msecs; - secs = atof (str); - msecs = (uint32_t) floor (secs * 1000.0); + vector<string> empty; + set_popdown_strings (lm_output_channel_combo, empty); + set_popdown_strings (lm_input_channel_combo, empty); +} - if (msecs > 0) { - cmd.push_back ("-t"); - cmd.push_back (to_string (msecs, std::dec)); - } - } +void +EngineControl::enable_latency_tab () +{ + vector<string> 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<string> 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()); +} - if (no_memory_lock_button.get_active()) { - cmd.push_back ("-m"); /* no munlock */ +void +EngineControl::backend_changed () +{ + if (ignore_changes) { + return; } - cmd.push_back ("-p"); /* port max */ - cmd.push_back (to_string ((uint32_t) floor (ports_spinner.get_value()), std::dec)); + string backend_name = backend_combo.get_active_text(); + boost::shared_ptr<ARDOUR::AudioBackend> backend; - if (realtime_button.get_active()) { - cmd.push_back ("-R"); - } else { - cmd.push_back ("-r"); /* override jackd's default --realtime */ + if (!(backend = ARDOUR::AudioEngine::instance()->set_backend (backend_name, "ardour", ""))) { + /* eh? setting the backend failed... how ? */ + return; } - if (unlock_memory_button.get_active()) { - cmd.push_back ("-u"); + if (backend->requires_driver_selection()) { + vector<string> drivers = backend->enumerate_drivers(); + driver_combo.set_sensitive (true); + set_popdown_strings (driver_combo, drivers); + driver_combo.set_active_text (drivers.front()); + driver_changed (); + } else { + driver_combo.set_sensitive (false); + list_devices (); } + + maybe_display_saved_state (); +} - if (verbose_output_button.get_active()) { - cmd.push_back ("-v"); +bool +EngineControl::print_channel_count (Gtk::SpinButton* sb) +{ + uint32_t cnt = (uint32_t) sb->get_value(); + if (cnt == 0) { + sb->set_text (_("all available channels")); + } else { + char buf[32]; + snprintf (buf, sizeof (buf), "%d", cnt); + sb->set_text (buf); } + return true; +} - /* now add fixed arguments (not user-selectable) */ +void +EngineControl::list_devices () +{ + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); + assert (backend); - cmd.push_back ("-T"); // temporary */ + /* now fill out devices, mark sample rates, buffer sizes insensitive */ + + vector<ARDOUR::AudioBackend::DeviceStatus> all_devices = backend->enumerate_devices (); + + /* NOTE: Ardour currently does not display the "available" field of the + * returned devices. + * + * Doing so would require a different GUI widget than the combo + * box/popdown that we currently use, since it has no way to list + * items that are not selectable. Something more like a popup menu, + * which could have unselectable items, would be appropriate. + */ - /* setup coremidi before the driver, otherwise jack won't start */ + vector<string> available_devices; - if (midi_driver_combo.get_active_text() == _("coremidi")) { - cmd.push_back ("-X coremidi"); + for (vector<ARDOUR::AudioBackend::DeviceStatus>::const_iterator i = all_devices.begin(); i != all_devices.end(); ++i) { + available_devices.push_back (i->name); } - /* next the driver */ - - cmd.push_back ("-d"); - - driver = driver_combo.get_active_text (); - - if (driver == X_("ALSA")) { - using_alsa = true; - cmd.push_back ("alsa"); - } else if (driver == X_("OSS")) { - cmd.push_back ("oss"); - } else if (driver == X_("CoreAudio")) { - using_coreaudio = true; - cmd.push_back ("coreaudio"); - } else if (driver == X_("NetJACK")) { - cmd.push_back ("netjack"); - } else if (driver == X_("FreeBoB")) { - cmd.push_back ("freebob"); - } else if (driver == X_("FFADO")) { - using_ffado = true; - cmd.push_back ("firewire"); - } else if ( driver == X_("Dummy")) { - using_dummy = true; - cmd.push_back ("dummy"); + set_popdown_strings (device_combo, available_devices); + + if (!available_devices.empty()) { + device_combo.set_active_text (available_devices.front()); } - /* driver arguments */ - - if (!using_coreaudio) { - str = audio_mode_combo.get_active_text(); - - if (str == _("Playback/recording on 1 device")) { - - /* relax */ - - } else if (str == _("Playback/recording on 2 devices")) { - - string input_device = get_device_name (driver, input_device_combo.get_active_text()); - string output_device = get_device_name (driver, output_device_combo.get_active_text()); - - if (input_device.empty() || output_device.empty()) { - cmd.clear (); - return; - } - - cmd.push_back ("-C"); - cmd.push_back (input_device); - - cmd.push_back ("-P"); - cmd.push_back (output_device); - - } else if (str == _("Playback only")) { - cmd.push_back ("-P"); - } else if (str == _("Recording only")) { - cmd.push_back ("-C"); - } - - if (!using_dummy) { - cmd.push_back ("-n"); - cmd.push_back (to_string ((uint32_t) floor (periods_spinner.get_value()), std::dec)); - } + device_changed (); +} + +void +EngineControl::driver_changed () +{ + if (ignore_changes) { + return; } - cmd.push_back ("-r"); - cmd.push_back (to_string (get_rate(), std::dec)); - - cmd.push_back ("-p"); - cmd.push_back (period_size_combo.get_active_text()); + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); + assert (backend); - if (using_alsa || using_ffado || using_coreaudio) { + backend->set_driver (driver_combo.get_active_text()); + list_devices (); - double val = input_latency_adjustment.get_value(); - - if (val) { - cmd.push_back ("-I"); - cmd.push_back (to_string ((uint32_t) val, std::dec)); - } - - val = output_latency_adjustment.get_value(); + maybe_display_saved_state (); +} - if (val) { - cmd.push_back ("-O"); - cmd.push_back (to_string ((uint32_t) val, std::dec)); - } +void +EngineControl::device_changed () +{ + if (ignore_changes) { + return; } - if (using_alsa) { + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); + assert (backend); + string device_name = device_combo.get_active_text (); + vector<string> s; - if (audio_mode_combo.get_active_text() != _("Playback/recording on 2 devices")) { - - string device = get_device_name (driver, interface_combo.get_active_text()); - if (device.empty()) { - cmd.clear (); - return; - } + /* don't allow programmatic change to sample_rate_combo to cause a + recursive call to this method. + */ + + ignore_changes++; - cmd.push_back ("-d"); - cmd.push_back (device); - } + /* sample rates */ + + string desired; - if (hw_meter_button.get_active()) { - cmd.push_back ("-M"); + vector<float> sr = backend->available_sample_rates (device_name); + for (vector<float>::const_iterator x = sr.begin(); x != sr.end(); ++x) { + char buf[32]; + if (fmod (*x, 1000.0f)) { + snprintf (buf, sizeof (buf), "%.1f kHz", (*x)/1000.0); + } else { + snprintf (buf, sizeof (buf), "%.0f kHz", (*x)/1000.0); } - - if (hw_monitor_button.get_active()) { - cmd.push_back ("-H"); + s.push_back (buf); + if (*x == _desired_sample_rate) { + desired = buf; } + } - str = dither_mode_combo.get_active_text(); + set_popdown_strings (sample_rate_combo, s); + if (desired.empty()) { + sample_rate_combo.set_active_text (s.front()); + } else { + sample_rate_combo.set_active_text (desired); + } - if (str == _("None")) { - } else if (str == _("Triangular")) { - cmd.push_back ("-z triangular"); - } else if (str == _("Rectangular")) { - cmd.push_back ("-z rectangular"); - } else if (str == _("Shaped")) { - cmd.push_back ("-z shaped"); - } + vector<uint32_t> bs = backend->available_buffer_sizes(device_name); + s.clear (); + for (vector<uint32_t>::const_iterator x = bs.begin(); x != bs.end(); ++x) { + char buf[32]; + /* Translators: "samples" is always plural here, so no + need for plural+singular forms. + */ + snprintf (buf, sizeof (buf), _("%u samples"), *x); + s.push_back (buf); + } - if (force16bit_button.get_active()) { - cmd.push_back ("-S"); - } + set_popdown_strings (buffer_size_combo, s); + buffer_size_combo.set_active_text (s.front()); + show_buffer_duration (); - if (soft_mode_button.get_active()) { - cmd.push_back ("-s"); - } + manage_control_app_sensitivity (); - str = midi_driver_combo.get_active_text (); + ignore_changes--; - if (str == _("seq")) { - cmd.push_back ("-X seq"); - } else if (str == _("raw")) { - cmd.push_back ("-X raw"); - } - } else if (using_coreaudio) { + maybe_display_saved_state (); +} -#ifdef __APPLE__ - // note: older versions of the CoreAudio JACK backend use -n instead of -d here +void +EngineControl::sample_rate_changed () +{ + if (ignore_changes) { + return; + } - string device = get_device_name (driver, interface_combo.get_active_text()); - if (device.empty()) { - cmd.clear (); - return; - } + /* reset the strings for buffer size to show the correct msec value + (reflecting the new sample rate). + */ - cmd.push_back ("-d"); - cmd.push_back (device); -#endif + show_buffer_duration (); + save_state (); - } } -bool -EngineControl::need_setup () +void +EngineControl::buffer_size_changed () { - return !engine_running(); + if (ignore_changes) { + return; + } + + show_buffer_duration (); + save_state (); } -bool -EngineControl::engine_running () +void +EngineControl::show_buffer_duration () { - EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); - boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; - - /* revert all environment settings back to whatever they were when - * ardour started, because ardour's startup script may have reset - * something in ways that interfere with finding/starting JACK. - */ - if (global_epa) { - current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ - global_epa->restore (); - } + /* buffer sizes - convert from just samples to samples + msecs for + * the displayed string + */ - jack_status_t status; - jack_client_t* c = jack_client_open ("ardourprobe", JackNoStartServer, &status); + string bs_text = buffer_size_combo.get_active_text (); + uint32_t samples = atoi (bs_text); /* will ignore trailing text */ + uint32_t rate = get_rate(); - if (status == 0) { - jack_client_close (c); - return true; - } - return false; + /* Translators: "msecs" is ALWAYS plural here, so we do not + need singular form as well. + */ + /* Developers: note the hard-coding of a double buffered model + in the (2 * samples) computation of latency. we always start + the audiobackend in this configuration. + */ + char buf[32]; + snprintf (buf, sizeof (buf), _("(%.1f msecs)"), (2 * samples) / (rate/1000.0)); + buffer_size_duration_label.set_text (buf); } -int -EngineControl::setup_engine () +EngineControl::State* +EngineControl::get_matching_state (const string& backend, + const string& driver, + const string& device) { - vector<string> args; - std::string cwd = "/tmp"; - - build_command_line (args); - - if (args.empty()) { - return 1; // try again + for (StateList::iterator i = states.begin(); i != states.end(); ++i) { + if ((*i).backend == backend && + (*i).driver == driver && + (*i).device == device) { + return &(*i); + } } + return 0; +} - std::string jackdrc_path = Glib::get_home_dir(); - jackdrc_path += "/.jackdrc"; - - ofstream jackdrc (jackdrc_path.c_str()); - if (!jackdrc) { - error << string_compose (_("cannot open JACK rc file %1 to store parameters"), jackdrc_path) << endmsg; - return -1; - } +EngineControl::State* +EngineControl::get_current_state () +{ + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); - for (vector<string>::iterator i = args.begin(); i != args.end(); ++i) { - jackdrc << (*i) << ' '; + if (backend) { + return get_matching_state (backend_combo.get_active_text(), + (backend->requires_driver_selection() ? (std::string) driver_combo.get_active_text() : string()), + device_combo.get_active_text()); } - jackdrc << endl; - jackdrc.close (); - - _used = true; - return 0; + return get_matching_state (backend_combo.get_active_text(), + string(), + device_combo.get_active_text()); } void -EngineControl::enumerate_devices (const string& driver) +EngineControl::save_state () { - /* note: case matters for the map keys */ - - if (driver == "CoreAudio") { -#ifdef __APPLE__ - devices[driver] = enumerate_coreaudio_devices (); -#endif - -#if !defined(__APPLE__) && !defined(__FreeBSD__) - } else if (driver == "ALSA") { - devices[driver] = enumerate_alsa_devices (); - } else if (driver == "FreeBOB") { - devices[driver] = enumerate_freebob_devices (); - } else if (driver == "FFADO") { - devices[driver] = enumerate_ffado_devices (); - } else if (driver == "OSS") { - devices[driver] = enumerate_oss_devices (); - } else if (driver == "Dummy") { - devices[driver] = enumerate_dummy_devices (); - } else if (driver == "NetJACK") { - devices[driver] = enumerate_netjack_devices (); + bool existing = true; + State* state = get_current_state (); + + if (!state) { + existing = false; + state = new State; + } + + state->backend = backend_combo.get_active_text (); + state->driver = driver_combo.get_active_text (); + state->device = device_combo.get_active_text (); + state->buffer_size = buffer_size_combo.get_active_text (); + state->sample_rate = sample_rate_combo.get_active_text (); + state->input_latency = (uint32_t) input_latency.get_value(); + state->output_latency = (uint32_t) output_latency.get_value(); + state->input_channels = (uint32_t) input_channels.get_value(); + state->output_channels = (uint32_t) output_channels.get_value(); + + if (!existing) { + states.push_back (*state); } -#else - } -#endif } -#ifdef __APPLE__ -static OSStatus -getDeviceUIDFromID( AudioDeviceID id, char *name, size_t nsize) +void +EngineControl::maybe_display_saved_state () { - UInt32 size = sizeof(CFStringRef); - CFStringRef UI; - OSStatus res = AudioDeviceGetProperty(id, 0, false, - kAudioDevicePropertyDeviceUID, &size, &UI); - if (res == noErr) - CFStringGetCString(UI,name,nsize,CFStringGetSystemEncoding()); - CFRelease(UI); - return res; -} + State* state = get_current_state (); -vector<string> -EngineControl::enumerate_coreaudio_devices () -{ - vector<string> devs; - - // Find out how many Core Audio devices are there, if any... - // (code snippet gently "borrowed" from St?hane Letz jackdmp;) - OSStatus err; - Boolean isWritable; - UInt32 outSize = sizeof(isWritable); - - backend_devs.clear (); - - err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, - &outSize, &isWritable); - if (err == noErr) { - // Calculate the number of device available... - int numCoreDevices = outSize / sizeof(AudioDeviceID); - // Make space for the devices we are about to get... - AudioDeviceID *coreDeviceIDs = new AudioDeviceID [numCoreDevices]; - err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, - &outSize, (void *) coreDeviceIDs); - if (err == noErr) { - // Look for the CoreAudio device name... - char coreDeviceName[256]; - UInt32 nameSize; - - for (int i = 0; i < numCoreDevices; i++) { - - nameSize = sizeof (coreDeviceName); - - /* enforce duplex devices only */ - - err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], - 0, true, kAudioDevicePropertyStreams, - &outSize, &isWritable); - - if (err != noErr || outSize == 0) { - continue; - } - - err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], - 0, false, kAudioDevicePropertyStreams, - &outSize, &isWritable); - - if (err != noErr || outSize == 0) { - continue; - } - - err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], - 0, true, kAudioDevicePropertyDeviceName, - &outSize, &isWritable); - if (err == noErr) { - err = AudioDeviceGetProperty(coreDeviceIDs[i], - 0, true, kAudioDevicePropertyDeviceName, - &nameSize, (void *) coreDeviceName); - if (err == noErr) { - char drivername[128]; - - // this returns the unique id for the device - // that must be used on the commandline for jack - - if (getDeviceUIDFromID(coreDeviceIDs[i], drivername, sizeof (drivername)) == noErr) { - devs.push_back (coreDeviceName); - backend_devs.push_back (drivername); - } - } - } - } + if (state) { + ignore_changes++; + if (!_desired_sample_rate) { + sample_rate_combo.set_active_text (state->sample_rate); } - delete [] coreDeviceIDs; + buffer_size_combo.set_active_text (state->buffer_size); + input_latency.set_value (state->input_latency); + output_latency.set_value (state->output_latency); + ignore_changes--; } +} + +XMLNode& +EngineControl::get_state () +{ + XMLNode* root = new XMLNode ("AudioMIDISetup"); + std::string path; - - if (devs.size() == 0) { - MessageDialog msg (string_compose (_("\ -You do not have any audio devices capable of\n\ -simultaneous playback and recording.\n\n\ -Please use Applications -> Utilities -> Audio MIDI Setup\n\ -to create an \"aggregrate\" device, or install a suitable\n\ -audio interface.\n\n\ -Please send email to Apple and ask them why new Macs\n\ -have no duplex audio device.\n\n\ -Alternatively, if you really want just playback\n\ -or recording but not both, start JACK before running\n\ -%1 and choose the relevant device then." - ), PROGRAM_NAME), - true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK); - msg.set_title (_("No suitable audio devices")); - msg.set_position (Gtk::WIN_POS_MOUSE); - msg.run (); - exit (1); + if (!states.empty()) { + XMLNode* state_nodes = new XMLNode ("EngineStates"); + + for (StateList::const_iterator i = states.begin(); i != states.end(); ++i) { + + XMLNode* node = new XMLNode ("State"); + + node->add_property ("backend", (*i).backend); + node->add_property ("driver", (*i).driver); + node->add_property ("device", (*i).device); + node->add_property ("sample-rate", (*i).sample_rate); + node->add_property ("buffer-size", (*i).buffer_size); + node->add_property ("input-latency", (*i).input_latency); + node->add_property ("output-latency", (*i).output_latency); + node->add_property ("input-channels", (*i).input_channels); + node->add_property ("output-channels", (*i).output_channels); + node->add_property ("active", (*i).active ? "yes" : "no"); + + state_nodes->add_child_nocopy (*node); + } + + root->add_child_nocopy (*state_nodes); } - - return devs; + return *root; } -#else -#if !defined(__FreeBSD__) -vector<string> -EngineControl::enumerate_alsa_devices () +void +EngineControl::set_state (const XMLNode& root) { - vector<string> devs; + XMLNodeList clist, cclist; + XMLNodeConstIterator citer, cciter; + XMLNode* child; + XMLNode* grandchild; + XMLProperty* prop = NULL; - snd_ctl_t *handle; - snd_ctl_card_info_t *info; - snd_pcm_info_t *pcminfo; - snd_ctl_card_info_alloca(&info); - snd_pcm_info_alloca(&pcminfo); - string devname; - int cardnum = -1; - int device = -1; + if (root.name() != "AudioMIDISetup") { + return; + } - backend_devs.clear (); + clist = root.children(); - while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) { + states.clear (); - devname = "hw:"; - devname += to_string (cardnum, std::dec); + for (citer = clist.begin(); citer != clist.end(); ++citer) { - if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) { + child = *citer; + + if (child->name() != "EngineStates") { + continue; + } - while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) { + cclist = child->children(); - snd_pcm_info_set_device (pcminfo, device); - snd_pcm_info_set_subdevice (pcminfo, 0); - snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK); + for (cciter = cclist.begin(); cciter != cclist.end(); ++cciter) { + State state; + + grandchild = *cciter; - if (snd_ctl_pcm_info (handle, pcminfo) >= 0) { - devs.push_back (snd_pcm_info_get_name (pcminfo)); - devname += ','; - devname += to_string (device, std::dec); - backend_devs.push_back (devname); - } + if (grandchild->name() != "State") { + continue; + } + + if ((prop = grandchild->property ("backend")) == 0) { + continue; } + state.backend = prop->value (); + + if ((prop = grandchild->property ("driver")) == 0) { + continue; + } + state.driver = prop->value (); + + if ((prop = grandchild->property ("device")) == 0) { + continue; + } + state.device = prop->value (); + + if ((prop = grandchild->property ("sample-rate")) == 0) { + continue; + } + state.sample_rate = prop->value (); + + if ((prop = grandchild->property ("buffer-size")) == 0) { + continue; + } + state.buffer_size = prop->value (); + + if ((prop = grandchild->property ("input-latency")) == 0) { + continue; + } + state.input_latency = atoi (prop->value ()); + + if ((prop = grandchild->property ("output-latency")) == 0) { + continue; + } + state.output_latency = atoi (prop->value ()); + + if ((prop = grandchild->property ("input-channels")) == 0) { + continue; + } + state.input_channels = atoi (prop->value ()); + + if ((prop = grandchild->property ("output-channels")) == 0) { + continue; + } + state.output_channels = atoi (prop->value ()); - snd_ctl_close(handle); + if ((prop = grandchild->property ("active")) == 0) { + continue; + } + state.active = string_is_affirmative (prop->value ()); + + states.push_back (state); } } - return devs; + /* now see if there was an active state and switch the setup to it */ + + for (StateList::const_iterator i = states.begin(); i != states.end(); ++i) { + if ((*i).active) { + ignore_changes++; + backend_combo.set_active_text ((*i).backend); + driver_combo.set_active_text ((*i).driver); + device_combo.set_active_text ((*i).device); + sample_rate_combo.set_active_text ((*i).sample_rate); + buffer_size_combo.set_active_text ((*i).buffer_size); + input_latency.set_value ((*i).input_latency); + output_latency.set_value ((*i).output_latency); + ignore_changes--; + + push_state_to_backend (false); + break; + } + } } -#endif -vector<string> -EngineControl::enumerate_ffado_devices () -{ - vector<string> devs; - backend_devs.clear (); - return devs; -} -vector<string> -EngineControl::enumerate_freebob_devices () +int +EngineControl::push_state_to_backend (bool start) { - vector<string> devs; - return devs; -} + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); -vector<string> -EngineControl::enumerate_oss_devices () -{ - vector<string> devs; - return devs; -} -vector<string> -EngineControl::enumerate_dummy_devices () -{ - vector<string> devs; - return devs; -} -vector<string> -EngineControl::enumerate_netjack_devices () -{ - vector<string> devs; - return devs; -} -#endif + if (!backend) { + return 0; + } -void -EngineControl::driver_changed () -{ - string driver = driver_combo.get_active_text(); - string::size_type maxlen = 0; - int n = 0; + /* grab the parameters from the GUI and apply them */ - enumerate_devices (driver); + try { + if (backend->requires_driver_selection()) { + if (backend->set_driver (get_driver())) { + return -1; + } + } - vector<string>& strings = devices[driver]; + if (backend->set_device_name (get_device_name())) { + return -1; + } - if (strings.empty() && driver != "FreeBoB" && driver != "FFADO" && driver != "Dummy") { - return; - } + if (backend->set_sample_rate (get_rate())) { + error << string_compose (_("Cannot set sample rate to %1"), get_rate()) << endmsg; + return -1; + } + if (backend->set_buffer_size (get_buffer_size())) { + error << string_compose (_("Cannot set buffer size to %1"), get_buffer_size()) << endmsg; + return -1; + } + if (backend->set_input_channels (get_input_channels())) { + error << string_compose (_("Cannot set input channels to %1"), get_input_channels()) << endmsg; + return -1; + } + if (backend->set_output_channels (get_output_channels())) { + error << string_compose (_("Cannot set output channels to %1"), get_output_channels()) << endmsg; + return -1; + } + if (backend->set_systemic_input_latency (get_input_latency())) { + error << string_compose (_("Cannot set input latency to %1"), get_input_latency()) << endmsg; + return -1; + } + if (backend->set_systemic_output_latency (get_output_latency())) { + error << string_compose (_("Cannot set output latency to %1"), get_output_latency()) << endmsg; + return -1; + } - for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i, ++n) { - if ((*i).length() > maxlen) { - maxlen = (*i).length(); + /* get a pointer to the current state object, creating one if + * necessary + */ + + State* state = get_current_state (); + + if (!state) { + save_state (); + state = get_current_state (); + assert (state); } - } - set_popdown_strings (interface_combo, strings); - set_popdown_strings (input_device_combo, strings); - set_popdown_strings (output_device_combo, strings); + /* all off */ - if (!strings.empty()) { - interface_combo.set_active_text (strings.front()); - input_device_combo.set_active_text (strings.front()); - output_device_combo.set_active_text (strings.front()); - } + for (StateList::iterator i = states.begin(); i != states.end(); ++i) { + (*i).active = false; + } - if (driver == "ALSA") { - soft_mode_button.set_sensitive (true); - force16bit_button.set_sensitive (true); - hw_monitor_button.set_sensitive (true); - hw_meter_button.set_sensitive (true); - monitor_button.set_sensitive (true); - } else { - soft_mode_button.set_sensitive (false); - force16bit_button.set_sensitive (false); - hw_monitor_button.set_sensitive (false); - hw_meter_button.set_sensitive (false); - monitor_button.set_sensitive (false); + /* mark this one active (to be used next time the dialog is + * shown) + */ + + state->active = true; + + if (start) { + if (ARDOUR::AudioEngine::instance()->start()) { + return -1; + } + } + + manage_control_app_sensitivity (); + return 0; + + } catch (...) { + cerr << "exception thrown...\n"; + return -1; } } uint32_t -EngineControl::get_rate () +EngineControl::get_rate () const { double r = atof (sample_rate_combo.get_active_text ()); /* the string may have been translated with an abbreviation for @@ -948,412 +840,200 @@ EngineControl::get_rate () return lrint (r); } -void -EngineControl::redisplay_latency () +uint32_t +EngineControl::get_buffer_size () const { - uint32_t rate = get_rate(); -#if defined(__APPLE__) || defined(__FreeBSD__) - float periods = 2; -#else - float periods = periods_adjustment.get_value(); -#endif - float period_size = atof (period_size_combo.get_active_text()); + string txt = buffer_size_combo.get_active_text (); + uint32_t samples; - char buf[32]; - snprintf (buf, sizeof(buf), "%.1fmsec", (periods * period_size) / (rate/1000.0)); + if (sscanf (txt.c_str(), "%d", &samples) != 1) { + throw exception (); + } - latency_label.set_text (buf); - latency_label.set_alignment (0, 0.5); + return samples; } -void -EngineControl::audio_mode_changed () +uint32_t +EngineControl::get_input_channels() const { - std::string str = audio_mode_combo.get_active_text(); - - if (str == _("Playback/recording on 1 device")) { - input_device_combo.set_sensitive (false); - output_device_combo.set_sensitive (false); - } else if (str == _("Playback/recording on 2 devices")) { - input_device_combo.set_sensitive (true); - output_device_combo.set_sensitive (true); - } else if (str == _("Playback only")) { - output_device_combo.set_sensitive (true); - input_device_combo.set_sensitive (false); - } else if (str == _("Recording only")) { - input_device_combo.set_sensitive (true); - output_device_combo.set_sensitive (false); - } + return (uint32_t) input_channels_adjustment.get_value(); } -static bool jack_server_filter(const string& str, void */*arg*/) +uint32_t +EngineControl::get_output_channels() const { - return str == "jackd" || str == "jackdmp"; + return (uint32_t) output_channels_adjustment.get_value(); } -void -EngineControl::find_jack_servers (vector<string>& strings) +uint32_t +EngineControl::get_input_latency() const { -#ifdef __APPLE__ - /* this magic lets us finds the path to the OSX bundle, and then - we infer JACK's location from there - */ - - char execpath[MAXPATHLEN+1]; - uint32_t pathsz = sizeof (execpath); + return (uint32_t) input_latency_adjustment.get_value(); +} - _NSGetExecutablePath (execpath, &pathsz); +uint32_t +EngineControl::get_output_latency() const +{ + return (uint32_t) output_latency_adjustment.get_value(); +} - string path (Glib::path_get_dirname (execpath)); - path += "/jackd"; +string +EngineControl::get_driver () const +{ + return driver_combo.get_active_text (); +} - if (Glib::file_test (path, FILE_TEST_EXISTS)) { - strings.push_back (path); - } +string +EngineControl::get_device_name () const +{ + return device_combo.get_active_text (); +} - if (getenv ("ARDOUR_WITH_JACK")) { - /* no other options - only use the JACK we supply */ - if (strings.empty()) { - fatal << string_compose (_("JACK appears to be missing from the %1 bundle"), PROGRAM_NAME) << endmsg; - /*NOTREACHED*/ - } +void +EngineControl::control_app_button_clicked () +{ + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); + + if (!backend) { return; } -#else - string path; -#endif - - PathScanner scanner; - vector<string *> *jack_servers; - std::map<string,int> un; - char *p; - bool need_minimal_path = false; - - p = getenv ("PATH"); - - if (p && *p) { - path = p; - } else { - need_minimal_path = true; - } - -#ifdef __APPLE__ - // many mac users don't have PATH set up to include - // likely installed locations of JACK - need_minimal_path = true; -#endif - - if (need_minimal_path) { - if (path.empty()) { - path = "/usr/bin:/bin:/usr/local/bin:/opt/local/bin"; - } else { - path += ":/usr/local/bin:/opt/local/bin"; - } - } - -#ifdef __APPLE__ - // push it back into the environment so that auto-started JACK can find it. - // XXX why can't we just expect OS X users to have PATH set correctly? we can't ... - setenv ("PATH", path.c_str(), 1); -#endif + + backend->launch_control_app (); +} - jack_servers = scanner (path, jack_server_filter, 0, false, true); - if (!jack_servers) { +void +EngineControl::manage_control_app_sensitivity () +{ + boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend(); + + if (!backend) { return; } + + string appname = backend->control_app_name(); - vector<string *>::iterator iter; - - for (iter = jack_servers->begin(); iter != jack_servers->end(); iter++) { - string p = **iter; - - if (un[p]++ == 0) { - strings.push_back(p); - } + if (appname.empty()) { + control_app_button.set_sensitive (false); + } else { + control_app_button.set_sensitive (true); } } - -string -EngineControl::get_device_name (const string& driver, const string& human_readable) +void +EngineControl::set_desired_sample_rate (uint32_t sr) { - vector<string>::iterator n; - vector<string>::iterator i; - - if (human_readable.empty()) { - /* this can happen if the user's .ardourrc file has a device name from - another computer system in it - */ - MessageDialog msg (_("You need to choose an audio device first.")); - msg.set_position (WIN_POS_MOUSE); - msg.run (); - return string(); - } - - if (backend_devs.empty()) { - return human_readable; - } + _desired_sample_rate = sr; + device_changed (); +} - for (i = devices[driver].begin(), n = backend_devs.begin(); i != devices[driver].end(); ++i, ++n) { - if (human_readable == (*i)) { - return (*n); - } - } +/* latency measurement */ - if (i == devices[driver].end()) { - warning << string_compose (_("Audio device \"%1\" not known on this computer."), human_readable) << endmsg; - } - - return string(); +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); + } } -XMLNode& -EngineControl::get_state () +bool +EngineControl::check_latency_measurement () { - XMLNode* root = new XMLNode ("AudioSetup"); - XMLNode* child; - std::string path; - - child = new XMLNode ("periods"); - child->add_property ("val", to_string (periods_adjustment.get_value(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("ports"); - child->add_property ("val", to_string (ports_adjustment.get_value(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("inlatency"); - child->add_property ("val", to_string (input_latency.get_value(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("outlatency"); - child->add_property ("val", to_string (output_latency.get_value(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("realtime"); - child->add_property ("val", to_string (realtime_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("nomemorylock"); - child->add_property ("val", to_string (no_memory_lock_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("unlockmemory"); - child->add_property ("val", to_string (unlock_memory_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("softmode"); - child->add_property ("val", to_string (soft_mode_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("force16bit"); - child->add_property ("val", to_string (force16bit_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("hwmonitor"); - child->add_property ("val", to_string (hw_monitor_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("hwmeter"); - child->add_property ("val", to_string (hw_meter_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("verbose"); - child->add_property ("val", to_string (verbose_output_button.get_active(), std::dec)); - root->add_child_nocopy (*child); - - child = new XMLNode ("samplerate"); - child->add_property ("val", sample_rate_combo.get_active_text()); - root->add_child_nocopy (*child); - - child = new XMLNode ("periodsize"); - child->add_property ("val", period_size_combo.get_active_text()); - root->add_child_nocopy (*child); + MTDM* mtdm = ARDOUR::AudioEngine::instance()->mtdm (); + static uint32_t cnt = 0; + + if (mtdm->resolve () < 0) { + 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; + } - child = new XMLNode ("serverpath"); - child->add_property ("val", serverpath_combo.get_active_text()); - root->add_child_nocopy (*child); + if (mtdm->err () > 0.3) { + mtdm->invert (); + mtdm->resolve (); + } - child = new XMLNode ("driver"); - child->add_property ("val", driver_combo.get_active_text()); - root->add_child_nocopy (*child); + char buf[128]; + ARDOUR::framecnt_t const sample_rate = ARDOUR::AudioEngine::instance()->sample_rate(); - child = new XMLNode ("interface"); - child->add_property ("val", interface_combo.get_active_text()); - root->add_child_nocopy (*child); + if (sample_rate == 0) { + lm_results.set_text (_("Disconnected from audio engine")); + ARDOUR::AudioEngine::instance()->stop_latency_detection (); + return false; + } - child = new XMLNode ("timeout"); - child->add_property ("val", timeout_combo.get_active_text()); - root->add_child_nocopy (*child); + uint32_t frames_total = mtdm->del(); + uint32_t extra = frames_total - ARDOUR::AudioEngine::instance()->latency_signal_delay(); - child = new XMLNode ("dither"); - child->add_property ("val", dither_mode_combo.get_active_text()); - root->add_child_nocopy (*child); + snprintf (buf, sizeof (buf), "%u samples %10.3lf ms", extra, extra * 1000.0f/sample_rate); - child = new XMLNode ("audiomode"); - child->add_property ("val", audio_mode_combo.get_active_text()); - root->add_child_nocopy (*child); + bool solid = true; - child = new XMLNode ("inputdevice"); - child->add_property ("val", input_device_combo.get_active_text()); - root->add_child_nocopy (*child); + if (mtdm->err () > 0.2) { + strcat (buf, " ??"); + solid = false; + } - child = new XMLNode ("outputdevice"); - child->add_property ("val", output_device_combo.get_active_text()); - root->add_child_nocopy (*child); + if (mtdm->inv ()) { + strcat (buf, " (Inv)"); + solid = false; + } - child = new XMLNode ("mididriver"); - child->add_property ("val", midi_driver_combo.get_active_text()); - root->add_child_nocopy (*child); + if (solid) { + // _pi->set_measured_latency (rint (mtdm->del())); + lm_measure_button.set_active (false); + lm_use_button.set_sensitive (true); + strcat (buf, " (set)"); + } + + lm_results.set_text (buf); - return *root; + return true; } void -EngineControl::set_state (const XMLNode& root) +EngineControl::latency_button_toggled () { - XMLNodeList clist; - XMLNodeConstIterator citer; - XMLNode* child; - XMLProperty* prop = NULL; - bool using_dummy = false; - bool using_ffado = false; - - int val; - string strval; - - if ( (child = root.child ("driver"))){ - prop = child->property("val"); + 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 (); + } +} - if (prop && (prop->value() == "Dummy") ) { - using_dummy = true; - } - if (prop && (prop->value() == "FFADO") ) { - using_ffado = true; - } +void +EngineControl::use_latency_button_clicked () +{ + MTDM* mtdm = ARDOUR::AudioEngine::instance()->mtdm (); + if (!mtdm) { + return; } - clist = root.children(); - - for (citer = clist.begin(); citer != clist.end(); ++citer) { - - child = *citer; - - prop = child->property ("val"); - - if (!prop || prop->value().empty()) { + uint32_t frames_total = mtdm->del(); + uint32_t extra = frames_total - ARDOUR::AudioEngine::instance()->latency_signal_delay(); + uint32_t one_way = extra/2; - if (((using_dummy || using_ffado) - && ( child->name() == "interface" - || child->name() == "inputdevice" - || child->name() == "outputdevice")) - || child->name() == "timeout") - { - continue; - } - - error << string_compose (_("AudioSetup value for %1 is missing data"), child->name()) << endmsg; - continue; - } - - strval = prop->value(); - - /* adjustments/spinners */ - - if (child->name() == "periods") { - val = atoi (strval); - periods_adjustment.set_value(val); - } else if (child->name() == "ports") { - val = atoi (strval); - ports_adjustment.set_value(val); - } else if (child->name() == "inlatency") { - val = atoi (strval); - input_latency.set_value(val); - } else if (child->name() == "outlatency") { - val = atoi (strval); - output_latency.set_value(val); - } - - /* buttons */ - - else if (child->name() == "realtime") { - val = atoi (strval); - realtime_button.set_active(val); - } else if (child->name() == "nomemorylock") { - val = atoi (strval); - no_memory_lock_button.set_active(val); - } else if (child->name() == "unlockmemory") { - val = atoi (strval); - unlock_memory_button.set_active(val); - } else if (child->name() == "softmode") { - val = atoi (strval); - soft_mode_button.set_active(val); - } else if (child->name() == "force16bit") { - val = atoi (strval); - force16bit_button.set_active(val); - } else if (child->name() == "hwmonitor") { - val = atoi (strval); - hw_monitor_button.set_active(val); - } else if (child->name() == "hwmeter") { - val = atoi (strval); - hw_meter_button.set_active(val); - } else if (child->name() == "verbose") { - val = atoi (strval); - verbose_output_button.set_active(val); - } - - /* combos */ - - else if (child->name() == "samplerate") { - sample_rate_combo.set_active_text(strval); - } else if (child->name() == "periodsize") { - period_size_combo.set_active_text(strval); - } else if (child->name() == "serverpath") { - - /* only attempt to set this if we have bothered to look - up server names already. otherwise this is all - redundant (actually, all of this dialog/widget - is redundant in that case ...) - */ - - if (!server_strings.empty()) { - /* do not allow us to use a server path that doesn't - exist on this system. this handles cases where - the user has an RC file listing a serverpath - from some other machine. - */ - vector<string>::iterator x; - for (x = server_strings.begin(); x != server_strings.end(); ++x) { - if (*x == strval) { - break; - } - } - if (x != server_strings.end()) { - serverpath_combo.set_active_text (strval); - } else { - warning << string_compose (_("configuration files contain a JACK server path that doesn't exist (%1)"), - strval) - << endmsg; - } - } - - } else if (child->name() == "driver") { - driver_combo.set_active_text(strval); - } else if (child->name() == "interface") { - interface_combo.set_active_text(strval); - } else if (child->name() == "timeout") { - timeout_combo.set_active_text(strval); - } else if (child->name() == "dither") { - dither_mode_combo.set_active_text(strval); - } else if (child->name() == "audiomode") { - audio_mode_combo.set_active_text(strval); - } else if (child->name() == "inputdevice") { - input_device_combo.set_active_text(strval); - } else if (child->name() == "outputdevice") { - output_device_combo.set_active_text(strval); - } else if (child->name() == "mididriver") { - midi_driver_combo.set_active_text(strval); - } - } + input_latency_adjustment.set_value (one_way); + output_latency_adjustment.set_value (one_way); } diff --git a/gtk2_ardour/engine_dialog.h b/gtk2_ardour/engine_dialog.h index 0d7ce29b46..3872917add 100644 --- a/gtk2_ardour/engine_dialog.h +++ b/gtk2_ardour/engine_dialog.h @@ -26,7 +26,6 @@ #include <gtkmm/checkbutton.h> #include <gtkmm/spinbutton.h> -#include <gtkmm/notebook.h> #include <gtkmm/comboboxtext.h> #include <gtkmm/table.h> #include <gtkmm/expander.h> @@ -34,93 +33,149 @@ #include <gtkmm/buttonbox.h> #include <gtkmm/button.h> -class EngineControl : public Gtk::VBox { - public: - EngineControl (); - ~EngineControl (); - - static bool need_setup (); - int setup_engine (); +#include "pbd/signals.h" - bool was_used() const { return _used; } - XMLNode& get_state (); - void set_state (const XMLNode&); +#include "ardour_dialog.h" +class EngineControl : public ArdourDialog, public PBD::ScopedConnectionList { + public: + EngineControl (); + ~EngineControl (); + + static bool need_setup (); + + XMLNode& get_state (); + void set_state (const XMLNode&); + + void set_desired_sample_rate (uint32_t); + private: - Gtk::Adjustment periods_adjustment; - Gtk::SpinButton periods_spinner; - Gtk::Adjustment ports_adjustment; - Gtk::SpinButton ports_spinner; - Gtk::Adjustment input_latency_adjustment; - Gtk::SpinButton input_latency; - Gtk::Adjustment output_latency_adjustment; - Gtk::SpinButton output_latency; - Gtk::Label latency_label; - - Gtk::CheckButton realtime_button; - Gtk::CheckButton no_memory_lock_button; - Gtk::CheckButton unlock_memory_button; - Gtk::CheckButton soft_mode_button; - Gtk::CheckButton monitor_button; - Gtk::CheckButton force16bit_button; - Gtk::CheckButton hw_monitor_button; - Gtk::CheckButton hw_meter_button; - Gtk::CheckButton verbose_output_button; - - Gtk::Button start_button; - Gtk::Button stop_button; - Gtk::HButtonBox button_box; - - Gtk::ComboBoxText sample_rate_combo; - Gtk::ComboBoxText period_size_combo; - - Gtk::ComboBoxText preset_combo; - Gtk::ComboBoxText serverpath_combo; - Gtk::ComboBoxText driver_combo; - Gtk::ComboBoxText interface_combo; - Gtk::ComboBoxText timeout_combo; - Gtk::ComboBoxText dither_mode_combo; - Gtk::ComboBoxText audio_mode_combo; - Gtk::ComboBoxText input_device_combo; - Gtk::ComboBoxText output_device_combo; - Gtk::ComboBoxText midi_driver_combo; - - Gtk::Table basic_packer; - Gtk::Table options_packer; - Gtk::Table device_packer; - Gtk::HBox basic_hbox; - Gtk::HBox options_hbox; - Gtk::HBox device_hbox; - Gtk::Notebook notebook; - - bool _used; - - static bool engine_running (); - - void driver_changed (); - void build_command_line (std::vector<std::string>&); - - std::map<std::string,std::vector<std::string> > devices; - std::vector<std::string> backend_devs; - void enumerate_devices (const std::string& driver); - -#ifdef __APPLE__ - std::vector<std::string> enumerate_coreaudio_devices (); -#else - std::vector<std::string> enumerate_alsa_devices (); - std::vector<std::string> enumerate_oss_devices (); - std::vector<std::string> enumerate_netjack_devices (); - std::vector<std::string> enumerate_freebob_devices (); - std::vector<std::string> enumerate_ffado_devices (); - std::vector<std::string> enumerate_dummy_devices (); -#endif - - void redisplay_latency (); - uint32_t get_rate(); - void audio_mode_changed (); - std::vector<std::string> server_strings; - void find_jack_servers (std::vector<std::string>&); - std::string get_device_name (const std::string& driver, const std::string& human_readable_name); + Gtk::Notebook notebook; + + /* core fields used by all backends */ + + Gtk::ComboBoxText backend_combo; + Gtk::ComboBoxText sample_rate_combo; + Gtk::ComboBoxText buffer_size_combo; + Gtk::Label buffer_size_duration_label; + Gtk::Adjustment input_latency_adjustment; + Gtk::SpinButton input_latency; + Gtk::Adjustment output_latency_adjustment; + Gtk::SpinButton output_latency; + Gtk::Adjustment input_channels_adjustment; + Gtk::SpinButton input_channels; + Gtk::Adjustment output_channels_adjustment; + Gtk::SpinButton output_channels; + Gtk::Adjustment ports_adjustment; + Gtk::SpinButton ports_spinner; + + 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; + Gtk::CheckButton no_memory_lock_button; + Gtk::CheckButton unlock_memory_button; + Gtk::CheckButton soft_mode_button; + Gtk::CheckButton monitor_button; + Gtk::CheckButton force16bit_button; + Gtk::CheckButton hw_monitor_button; + Gtk::CheckButton hw_meter_button; + Gtk::CheckButton verbose_output_button; + + Gtk::ComboBoxText preset_combo; + Gtk::ComboBoxText serverpath_combo; + Gtk::ComboBoxText driver_combo; + Gtk::ComboBoxText device_combo; + Gtk::ComboBoxText timeout_combo; + Gtk::ComboBoxText dither_mode_combo; + Gtk::ComboBoxText audio_mode_combo; + Gtk::ComboBoxText midi_driver_combo; + + Gtk::Table basic_packer; + Gtk::Table midi_packer; + Gtk::HBox basic_hbox; + Gtk::VBox basic_vbox; + Gtk::HBox midi_hbox; + + uint32_t ignore_changes; + + static bool engine_running (); + + void driver_changed (); + void backend_changed (); + void sample_rate_changed (); + void buffer_size_changed (); + + uint32_t get_rate() const; + uint32_t get_buffer_size() const; + uint32_t get_input_channels() const; + uint32_t get_output_channels() const; + uint32_t get_input_latency() const; + uint32_t get_output_latency() const; + std::string get_device_name() const; + std::string get_driver() const; + + void device_changed (); + void list_devices (); + void show_buffer_duration (); + + struct State { + std::string backend; + std::string driver; + std::string device; + std::string sample_rate; + std::string buffer_size; + uint32_t input_latency; + uint32_t output_latency; + uint32_t input_channels; + uint32_t output_channels; + bool active; + + State() : active (false) {}; + }; + + typedef std::list<State> StateList; + + StateList states; + + State* get_matching_state (const std::string& backend, + const std::string& driver, + const std::string& device); + State* get_current_state (); + void maybe_display_saved_state (); + void save_state (); + + static bool print_channel_count (Gtk::SpinButton*); + + void build_notebook (); + + void on_response (int); + void control_app_button_clicked (); + void use_latency_button_clicked (); + 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/gtk2_ardour/export_video_dialog.cc b/gtk2_ardour/export_video_dialog.cc index 09283d4c8d..f54232a681 100644 --- a/gtk2_ardour/export_video_dialog.cc +++ b/gtk2_ardour/export_video_dialog.cc @@ -259,8 +259,7 @@ ExportVideoDialog::ExportVideoDialog (PublicEditor& ed, Session* s) video_codec_combo.append_text("mjpeg"); video_codec_combo.append_text("mpeg2video"); video_codec_combo.append_text("mpeg4"); - video_codec_combo.append_text("x264 (baseline)"); - video_codec_combo.append_text("x264 (hq)"); + video_codec_combo.append_text("h264"); video_codec_combo.append_text("vpx (webm)"); video_codec_combo.append_text("copy"); video_codec_combo.set_active(4); @@ -614,13 +613,8 @@ ExportVideoDialog::encode_pass (int pass) ffs["-strict"] = "-2"; } - if (video_codec_combo.get_active_text() == "x264 (hq)" ) { + if (video_codec_combo.get_active_text() == "h264" ) { ffs["-vcodec"] = "libx264"; - ffs["-vprofile"] = "high"; - } - else if (video_codec_combo.get_active_text() == "x264 (baseline)" ) { - ffs["-vcodec"] = "libx264"; - ffs["-vpre"] = "baseline"; } else if (video_codec_combo.get_active_text() == "vpx (webm)" ) { ffs["-vcodec"] = "libvpx"; diff --git a/gtk2_ardour/gain_meter.cc b/gtk2_ardour/gain_meter.cc index c2e4a43858..d4610da601 100644 --- a/gtk2_ardour/gain_meter.cc +++ b/gtk2_ardour/gain_meter.cc @@ -32,7 +32,6 @@ #include <gtkmm2ext/fastmeter.h> #include <gtkmm2ext/barcontroller.h> #include <gtkmm2ext/gtk_ui.h> -#include "midi++/manager.h" #include "pbd/fastlog.h" #include "pbd/stacktrace.h" @@ -280,14 +279,15 @@ GainMeterBase::setup_meters (int len) case Wide: meter_ticks1_area.show(); meter_ticks2_area.show(); + meter_metric_area.show(); if (_route && _route->shared_peak_meter()->input_streams().n_total() == 1) { meter_width = 10; } break; case Narrow: - meter_width = 2; meter_ticks1_area.hide(); meter_ticks2_area.hide(); + meter_metric_area.hide(); break; } level_meter->setup_meters(len, meter_width); @@ -864,7 +864,7 @@ GainMeterBase::update_meters() } } -void GainMeterBase::color_handler(bool /* dpi */) +void GainMeterBase::color_handler(bool /*dpi*/) { setup_meters(); } diff --git a/gtk2_ardour/generic_pluginui.cc b/gtk2_ardour/generic_pluginui.cc index 6cea8f3c0b..83f091a1e0 100644 --- a/gtk2_ardour/generic_pluginui.cc +++ b/gtk2_ardour/generic_pluginui.cc @@ -37,8 +37,6 @@ #include <gtkmm2ext/doi.h> #include <gtkmm2ext/slider_controller.h> -#include "midi++/manager.h" - #include "ardour/plugin.h" #include "ardour/plugin_insert.h" #include "ardour/session.h" diff --git a/gtk2_ardour/global_port_matrix.cc b/gtk2_ardour/global_port_matrix.cc index e1bdfb199e..218cfe7a1c 100644 --- a/gtk2_ardour/global_port_matrix.cc +++ b/gtk2_ardour/global_port_matrix.cc @@ -19,6 +19,7 @@ #include <gtkmm/image.h> #include <gtkmm/stock.h> + #include "global_port_matrix.h" #include "utils.h" @@ -82,9 +83,9 @@ GlobalPortMatrix::set_state (BundleChannel c[2], bool s) } else { /* two non-Ardour ports */ if (s) { - jack_connect (_session->engine().jack (), j->c_str(), i->c_str()); + AudioEngine::instance()->connect (*j, *i); } else { - jack_disconnect (_session->engine().jack (), j->c_str(), i->c_str()); + AudioEngine::instance()->disconnect (*j, *i); } } } @@ -113,33 +114,25 @@ GlobalPortMatrix::get_state (BundleChannel c[2]) const for (Bundle::PortList::const_iterator i = in_ports.begin(); i != in_ports.end(); ++i) { for (Bundle::PortList::const_iterator j = out_ports.begin(); j != out_ports.end(); ++j) { - boost::shared_ptr<Port> p = _session->engine().get_port_by_name (*i); - boost::shared_ptr<Port> q = _session->engine().get_port_by_name (*j); + boost::shared_ptr<Port> p = AudioEngine::instance()->get_port_by_name (*i); + boost::shared_ptr<Port> q = AudioEngine::instance()->get_port_by_name (*j); if (!p && !q) { /* two non-Ardour ports; things are slightly more involved */ - /* XXX: is this the easiest way to do this? */ - /* XXX: isn't this very inefficient? */ - jack_client_t* jack = _session->engine().jack (); - jack_port_t* jp = jack_port_by_name (jack, i->c_str()); - if (jp == 0) { + /* get a port handle for one of them .. */ + + PortEngine::PortHandle ph = AudioEngine::instance()->port_engine().get_port_by_name (*i); + if (!ph) { return PortMatrixNode::NOT_ASSOCIATED; } - char const ** c = jack_port_get_all_connections (jack, jp); - - char const ** p = c; + /* see if it is connected to the other one ... */ - while (p && *p != 0) { - if (strcmp (*p, j->c_str()) == 0) { - free (c); - return PortMatrixNode::ASSOCIATED; - } - ++p; + if (AudioEngine::instance()->port_engine().connected_to (ph, *j, false)) { + return PortMatrixNode::ASSOCIATED; } - free (c); return PortMatrixNode::NOT_ASSOCIATED; } diff --git a/gtk2_ardour/level_meter.cc b/gtk2_ardour/level_meter.cc index 6f48864992..bf9823518b 100644 --- a/gtk2_ardour/level_meter.cc +++ b/gtk2_ardour/level_meter.cc @@ -23,7 +23,6 @@ #include <gtkmm2ext/utils.h> #include <gtkmm2ext/barcontroller.h> -#include "midi++/manager.h" #include "pbd/fastlog.h" #include "ardour_ui.h" diff --git a/gtk2_ardour/main.cc b/gtk2_ardour/main.cc index dfa0bda945..f55405a84d 100644 --- a/gtk2_ardour/main.cc +++ b/gtk2_ardour/main.cc @@ -373,34 +373,30 @@ static void load_custom_fonts() { #endif static gboolean -tell_about_jack_death (void* /* ignored */) +tell_about_backend_death (void* /* ignored */) { if (AudioEngine::instance()->processed_frames() == 0) { /* died during startup */ - MessageDialog msg (_("JACK exited"), false); + MessageDialog msg (string_compose (_("The audio backend (%1) has failed, or terminated"), AudioEngine::instance()->current_backend_name()), false); msg.set_position (Gtk::WIN_POS_CENTER); msg.set_secondary_text (string_compose (_( -"JACK exited unexpectedly, and without notifying %1.\n\ +"%2 exited unexpectedly, and without notifying %1.\n\ \n\ -This could be due to misconfiguration or to an error inside JACK.\n\ +This could be due to misconfiguration or to an error inside %2.\n\ \n\ -Click OK to exit %1."), PROGRAM_NAME)); +Click OK to exit %1."), PROGRAM_NAME, AudioEngine::instance()->current_backend_name())); msg.run (); _exit (0); } else { - /* engine has already run, so this is a mid-session JACK death */ - - MessageDialog* msg = manage (new MessageDialog (_("JACK exited"), false)); - msg->set_secondary_text (string_compose (_( -"JACK exited unexpectedly, and without notifying %1.\n\ -\n\ -This is probably due to an error inside JACK. You should restart JACK\n\ -and reconnect %1 to it, or exit %1 now. You cannot save your\n\ -session at this time, because we would lose your connection information.\n"), PROGRAM_NAME)); - msg->present (); + /* engine has already run, so this is a mid-session backend death */ + + MessageDialog msg (string_compose (_("The audio backend (%1) has failed, or terminated"), AudioEngine::instance()->current_backend_name()), false); + msg.set_secondary_text (string_compose (_("%2 exited unexpectedly, and without notifying %1."), + PROGRAM_NAME, AudioEngine::instance()->current_backend_name())); + msg.present (); } return false; /* do not call again */ } @@ -408,15 +404,15 @@ session at this time, because we would lose your connection information.\n"), PR static void sigpipe_handler (int /*signal*/) { - /* XXX fix this so that we do this again after a reconnect to JACK + /* XXX fix this so that we do this again after a reconnect to the backend */ - static bool done_the_jack_thing = false; + static bool done_the_backend_thing = false; - if (!done_the_jack_thing) { + if (!done_the_backend_thing) { AudioEngine::instance()->died (); - g_idle_add (tell_about_jack_death, 0); - done_the_jack_thing = true; + g_idle_add (tell_about_backend_death, 0); + done_the_backend_thing = true; } } diff --git a/gtk2_ardour/meterbridge.cc b/gtk2_ardour/meterbridge.cc index 4a4f1dd8dd..8756089e00 100644 --- a/gtk2_ardour/meterbridge.cc +++ b/gtk2_ardour/meterbridge.cc @@ -702,11 +702,11 @@ Meterbridge::sync_order_keys (RouteSortOrderKey) MeterType nmt = (*i).s->meter_type(); if (nmt == MeterKrms) nmt = MeterPeak; // identical metrics - if (pos == 0) { + if (vis == 1) { (*i).s->set_tick_bar(1); } - if ((*i).visible && nmt != lmt && pos == 0) { + if ((*i).visible && nmt != lmt && vis == 1) { lmt = nmt; metrics_left.set_metric_mode(1, lmt); } else if ((*i).visible && nmt != lmt) { diff --git a/gtk2_ardour/midi_tracer.cc b/gtk2_ardour/midi_tracer.cc index c4fe76252b..934dffd11c 100644 --- a/gtk2_ardour/midi_tracer.cc +++ b/gtk2_ardour/midi_tracer.cc @@ -27,7 +27,10 @@ #include "pbd/timersub.h" #include "midi++/parser.h" -#include "midi++/manager.h" + +#include "ardour/async_midi_port.h" +#include "ardour/midi_port.h" +#include "ardour/audioengine.h" #include "midi_tracer.h" #include "gui_thread.h" @@ -56,7 +59,8 @@ MidiTracer::MidiTracer () , collect_button (_("Enabled")) , delta_time_button (_("Delta times")) { - Manager::instance()->PortsChanged.connect (_manager_connection, invalidator (*this), boost::bind (&MidiTracer::ports_changed, this), gui_context()); + ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect + (_manager_connection, invalidator (*this), boost::bind (&MidiTracer::ports_changed, this), gui_context()); _last_receipt.tv_sec = 0; _last_receipt.tv_usec = 0; @@ -129,24 +133,58 @@ MidiTracer::ports_changed () { string const c = _port_combo.get_active_text (); _port_combo.clear (); + + ARDOUR::PortManager::PortList pl; + ARDOUR::AudioEngine::instance()->get_ports (ARDOUR::DataType::MIDI, pl); + + if (pl.empty()) { + _port_combo.set_active_text (""); + return; + } - boost::shared_ptr<const Manager::PortList> p = Manager::instance()->get_midi_ports (); - for (Manager::PortList::const_iterator i = p->begin(); i != p->end(); ++i) { + for (ARDOUR::PortManager::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) { _port_combo.append_text ((*i)->name()); } - _port_combo.set_active_text (c); + if (c.empty()) { + _port_combo.set_active_text (pl.front()->name()); + } else { + _port_combo.set_active_text (c); + } } void MidiTracer::port_changed () { + using namespace ARDOUR; + disconnect (); - Port* p = Manager::instance()->port (_port_combo.get_active_text()); + boost::shared_ptr<ARDOUR::Port> p = AudioEngine::instance()->get_port_by_name (_port_combo.get_active_text()); + + if (!p) { + std::cerr << "port not found\n"; + return; + } + + /* The inheritance heirarchy makes this messy. AsyncMIDIPort has two + * available MIDI::Parsers what we could connect to, ::self_parser() + * (from ARDOUR::MidiPort) and ::parser() from MIDI::Port. One day, + * this mess will all go away ... + */ + + boost::shared_ptr<AsyncMIDIPort> async = boost::dynamic_pointer_cast<AsyncMIDIPort> (p); + + if (!async) { - if (p) { - p->parser()->any.connect_same_thread (_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3)); + boost::shared_ptr<ARDOUR::MidiPort> mp = boost::dynamic_pointer_cast<ARDOUR::MidiPort> (p); + + if (mp) { + mp->self_parser().any.connect_same_thread (_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3)); + } + + } else { + async->parser()->any.connect_same_thread (_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3)); } } @@ -369,9 +407,9 @@ MidiTracer::tracer (Parser&, byte* msg, size_t len) fifo.write (&buf, 1); - if (g_atomic_int_get (&_update_queued) == 0) { + if (g_atomic_int_get (const_cast<gint*> (&_update_queued)) == 0) { gui_context()->call_slot (invalidator (*this), boost::bind (&MidiTracer::update, this)); - g_atomic_int_inc (&_update_queued); + g_atomic_int_inc (const_cast<gint*> (&_update_queued)); } } @@ -379,7 +417,7 @@ void MidiTracer::update () { bool updated = false; - g_atomic_int_dec_and_test (&_update_queued); + g_atomic_int_dec_and_test (const_cast<gint*> (&_update_queued)); RefPtr<TextBuffer> buf (text.get_buffer()); diff --git a/gtk2_ardour/nsm.cc b/gtk2_ardour/nsm.cc index 18d88fec0a..e3ceba355f 100644 --- a/gtk2_ardour/nsm.cc +++ b/gtk2_ardour/nsm.cc @@ -50,7 +50,7 @@ NSM_Client::command_open(const char* name, int r = ERR_OK; ARDOUR_COMMAND_LINE::session_name = name; - ARDOUR_COMMAND_LINE::jack_client_name = client_id; + ARDOUR_COMMAND_LINE::backend_client_name = client_id; if (ARDOUR_UI::instance()->get_session_parameters(true, false, "")) { return ERR_GENERAL; diff --git a/gtk2_ardour/opts.cc b/gtk2_ardour/opts.cc index b29f106bfe..90d753af4c 100644 --- a/gtk2_ardour/opts.cc +++ b/gtk2_ardour/opts.cc @@ -32,7 +32,8 @@ using namespace std; string ARDOUR_COMMAND_LINE::session_name = ""; -string ARDOUR_COMMAND_LINE::jack_client_name = "ardour"; +string ARDOUR_COMMAND_LINE::backend_client_name = "ardour"; +string ARDOUR_COMMAND_LINE::backend_session_uuid; bool ARDOUR_COMMAND_LINE::show_key_actions = false; bool ARDOUR_COMMAND_LINE::no_splash = false; bool ARDOUR_COMMAND_LINE::just_version = false; @@ -45,7 +46,6 @@ string ARDOUR_COMMAND_LINE::keybindings_path = ""; /* empty means use builtin de std::string ARDOUR_COMMAND_LINE::menus_file = "ardour.menus"; bool ARDOUR_COMMAND_LINE::finder_invoked_ardour = false; string ARDOUR_COMMAND_LINE::immediate_save; -string ARDOUR_COMMAND_LINE::jack_session_uuid; string ARDOUR_COMMAND_LINE::load_template; bool ARDOUR_COMMAND_LINE::check_announcements = true; @@ -60,7 +60,7 @@ print_help (const char *execname) << _(" -h, --help Print this message\n") << _(" -a, --no-announcements Do not contact website for announcements\n") << _(" -b, --bindings Print all possible keyboard binding names\n") - << _(" -c, --name <name> Use a specific jack client name, default is ardour\n") + << _(" -c, --name <name> Use a specific backend client name, default is ardour\n") << _(" -d, --disable-plugins Disable all plugins in an existing session\n") << _(" -D, --debug <options> Set debug flags. Use \"-D list\" to see available options\n") << _(" -n, --no-splash Do not show splash screen\n") @@ -199,7 +199,7 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) break; case 'c': - jack_client_name = optarg; + backend_client_name = optarg; break; case 'C': @@ -215,7 +215,7 @@ ARDOUR_COMMAND_LINE::parse_opts (int argc, char *argv[]) break; case 'U': - jack_session_uuid = optarg; + backend_session_uuid = optarg; break; default: diff --git a/gtk2_ardour/opts.h b/gtk2_ardour/opts.h index b9faa36d72..fdf29157d2 100644 --- a/gtk2_ardour/opts.h +++ b/gtk2_ardour/opts.h @@ -28,7 +28,8 @@ extern std::string session_name; extern bool show_key_actions; extern bool no_splash; extern bool just_version; -extern std::string jack_client_name; +extern std::string backend_client_name; +extern std::string backend_session_uuid; extern bool use_vst; extern bool new_session; extern char* curvetest_file; @@ -39,7 +40,6 @@ extern std::string keybindings_path; extern std::string menus_file; extern bool finder_invoked_ardour; extern std::string immediate_save; -extern std::string jack_session_uuid; extern std::string load_template; extern bool check_announcements; diff --git a/gtk2_ardour/panner_ui.cc b/gtk2_ardour/panner_ui.cc index a5ed0c7aee..95d7491921 100644 --- a/gtk2_ardour/panner_ui.cc +++ b/gtk2_ardour/panner_ui.cc @@ -21,7 +21,6 @@ #include <gtkmm2ext/utils.h> #include <gtkmm2ext/barcontroller.h> -#include "midi++/manager.h" #include "pbd/fastlog.h" #include "ardour/pannable.h" diff --git a/gtk2_ardour/plugin_ui.cc b/gtk2_ardour/plugin_ui.cc index c19738f02f..28ef9b1297 100644 --- a/gtk2_ardour/plugin_ui.cc +++ b/gtk2_ardour/plugin_ui.cc @@ -40,8 +40,6 @@ #include <gtkmm2ext/slider_controller.h> #include <gtkmm2ext/application.h> -#include "midi++/manager.h" - #include "ardour/session.h" #include "ardour/plugin.h" #include "ardour/plugin_insert.h" diff --git a/gtk2_ardour/port_group.cc b/gtk2_ardour/port_group.cc index 531a5cf2c4..2a93cf6bee 100644 --- a/gtk2_ardour/port_group.cc +++ b/gtk2_ardour/port_group.cc @@ -21,7 +21,6 @@ #include <boost/shared_ptr.hpp> #include <boost/algorithm/string.hpp> -#include "midi++/manager.h" #include "midi++/mmc.h" #include "ardour/audioengine.h" @@ -29,9 +28,12 @@ #include "ardour/bundle.h" #include "ardour/control_protocol_manager.h" #include "ardour/io_processor.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" #include "ardour/session.h" #include "ardour/user_bundle.h" #include "ardour/port.h" + #include "control_protocol/control_protocol.h" #include "gui_thread.h" @@ -452,37 +454,35 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp /* Ardour's sync ports */ - MIDI::Manager* midi_manager = MIDI::Manager::instance (); - if (midi_manager && (type == DataType::MIDI || type == DataType::NIL)) { + if ((type == DataType::MIDI || type == DataType::NIL)) { boost::shared_ptr<Bundle> sync (new Bundle (_("Sync"), inputs)); - MIDI::MachineControl* mmc = midi_manager->mmc (); - AudioEngine& ae = session->engine (); + AudioEngine* ae = AudioEngine::instance(); if (inputs) { sync->add_channel ( - _("MTC in"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->mtc_input_port()->name()) + _("MTC in"), DataType::MIDI, ae->make_port_name_non_relative (session->mtc_input_port()->name()) ); sync->add_channel ( - _("MIDI control in"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->midi_input_port()->name()) + _("MIDI control in"), DataType::MIDI, ae->make_port_name_non_relative (session->midi_input_port()->name()) ); sync->add_channel ( - _("MIDI clock in"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->midi_clock_input_port()->name()) + _("MIDI clock in"), DataType::MIDI, ae->make_port_name_non_relative (session->midi_clock_input_port()->name()) ); sync->add_channel ( - _("MMC in"), DataType::MIDI, ae.make_port_name_non_relative (mmc->input_port()->name()) + _("MMC in"), DataType::MIDI, ae->make_port_name_non_relative (session->mmc_input_port()->name()) ); } else { sync->add_channel ( - _("MTC out"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->mtc_output_port()->name()) + _("MTC out"), DataType::MIDI, ae->make_port_name_non_relative (session->mtc_output_port()->name()) ); sync->add_channel ( - _("MIDI control out"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->midi_output_port()->name()) + _("MIDI control out"), DataType::MIDI, ae->make_port_name_non_relative (session->midi_output_port()->name()) ); sync->add_channel ( - _("MIDI clock out"), DataType::MIDI, ae.make_port_name_non_relative (midi_manager->midi_clock_output_port()->name()) + _("MIDI clock out"), DataType::MIDI, ae->make_port_name_non_relative (session->midi_clock_output_port()->name()) ); sync->add_channel ( - _("MMC out"), DataType::MIDI, ae.make_port_name_non_relative (mmc->output_port()->name()) + _("MMC out"), DataType::MIDI, ae->make_port_name_non_relative (session->mmc_output_port()->name()) ); } @@ -499,20 +499,12 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp string lpnc = lpn; lpnc += ':'; - const char ** ports = 0; - if (type == DataType::NIL) { - ports = session->engine().get_ports ("", "", inputs ? JackPortIsInput : JackPortIsOutput); - } else { - ports = session->engine().get_ports ("", type.to_jack_type(), inputs ? JackPortIsInput : JackPortIsOutput); - } - - if (ports) { - - int n = 0; + vector<string> ports; + if (AudioEngine::instance()->get_ports ("", type, inputs ? IsInput : IsOutput, ports) > 0) { - while (ports[n]) { + for (vector<string>::const_iterator s = ports.begin(); s != ports.end(); ) { - std::string const p = ports[n]; + std::string const p = *s; if (!system->has_port(p) && !bus->has_port(p) && @@ -526,7 +518,7 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp */ if (p.find ("Midi-Through") != string::npos) { - ++n; + ++s; continue; } @@ -539,15 +531,15 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp if ((lp.find (N_(":monitor")) != string::npos) && (lp.find (lpn) != string::npos)) { - ++n; + ++s; continue; } /* can't use the audio engine for this as we are looking at non-Ardour ports */ - jack_port_t* jp = jack_port_by_name (session->engine().jack(), p.c_str()); - if (jp) { - DataType t (jack_port_type (jp)); + PortEngine::PortHandle ph = AudioEngine::instance()->port_engine().get_port_by_name (p); + if (ph) { + DataType t (AudioEngine::instance()->port_engine().port_data_type (ph)); if (t != DataType::NIL) { if (port_has_prefix (p, N_("system:")) || port_has_prefix (p, N_("alsa_pcm")) || @@ -560,10 +552,8 @@ PortGroupList::gather (ARDOUR::Session* session, ARDOUR::DataType type, bool inp } } - ++n; + ++s; } - - free (ports); } for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { diff --git a/gtk2_ardour/port_insert_ui.cc b/gtk2_ardour/port_insert_ui.cc index 0511b357db..3c495f0e79 100644 --- a/gtk2_ardour/port_insert_ui.cc +++ b/gtk2_ardour/port_insert_ui.cc @@ -66,7 +66,7 @@ PortInsertUI::PortInsertUI (Gtk::Window* parent, ARDOUR::Session* sess, boost::s void PortInsertUI::update_latency_display () { - framecnt_t const sample_rate = input_selector.session()->engine().frame_rate(); + framecnt_t const sample_rate = AudioEngine::instance()->sample_rate(); if (sample_rate == 0) { latency_display.set_text (_("Disconnected from audio engine")); } else { @@ -93,7 +93,7 @@ PortInsertUI::check_latency_measurement () } char buf[128]; - framecnt_t const sample_rate = AudioEngine::instance()->frame_rate(); + framecnt_t const sample_rate = AudioEngine::instance()->sample_rate(); if (sample_rate == 0) { latency_display.set_text (_("Disconnected from audio engine")); diff --git a/gtk2_ardour/processor_box.cc b/gtk2_ardour/processor_box.cc index 77f0e2da01..07781fe7ee 100644 --- a/gtk2_ardour/processor_box.cc +++ b/gtk2_ardour/processor_box.cc @@ -2202,10 +2202,10 @@ ProcessorBox::register_actions () act = ActionManager::register_action (popup_act_grp, X_("newinsert"), _("New Insert"), sigc::ptr_fun (ProcessorBox::rb_choose_insert)); - ActionManager::jack_sensitive_actions.push_back (act); + ActionManager::engine_sensitive_actions.push_back (act); act = ActionManager::register_action (popup_act_grp, X_("newsend"), _("New External Send ..."), sigc::ptr_fun (ProcessorBox::rb_choose_send)); - ActionManager::jack_sensitive_actions.push_back (act); + ActionManager::engine_sensitive_actions.push_back (act); ActionManager::register_action (popup_act_grp, X_("newaux"), _("New Aux Send ...")); diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc index 98a4ffd014..ed9ffda4ef 100644 --- a/gtk2_ardour/rc_option_editor.cc +++ b/gtk2_ardour/rc_option_editor.cc @@ -31,8 +31,6 @@ #include "pbd/fpu.h" #include "pbd/cpus.h" -#include "midi++/manager.h" - #include "ardour/audioengine.h" #include "ardour/dB.h" #include "ardour/rc_configuration.h" @@ -1468,8 +1466,8 @@ RCOptionEditor::RCOptionEditor () #ifndef __APPLE__ /* no JACK monitoring on CoreAudio */ - if (AudioEngine::instance()->can_request_hardware_monitoring()) { - mm->add (HardwareMonitoring, _("JACK")); + if (AudioEngine::instance()->port_engine().can_monitor_input()) { + mm->add (HardwareMonitoring, _("via Audio Driver")); } #endif mm->add (SoftwareMonitoring, _("ardour")); diff --git a/gtk2_ardour/route_params_ui.cc b/gtk2_ardour/route_params_ui.cc index bb10bea16f..76f3d4e264 100644 --- a/gtk2_ardour/route_params_ui.cc +++ b/gtk2_ardour/route_params_ui.cc @@ -277,7 +277,7 @@ RouteParams_UI::cleanup_latency_frame () void RouteParams_UI::setup_latency_frame () { - latency_widget = new LatencyGUI (*(_route->output()), _session->frame_rate(), _session->engine().frames_per_cycle()); + latency_widget = new LatencyGUI (*(_route->output()), _session->frame_rate(), AudioEngine::instance()->samples_per_cycle()); char buf[128]; snprintf (buf, sizeof (buf), _("Playback delay: %" PRId64 " samples"), _route->initial_delay()); diff --git a/gtk2_ardour/route_ui.cc b/gtk2_ardour/route_ui.cc index 9494d42353..3b3f32ac04 100644 --- a/gtk2_ardour/route_ui.cc +++ b/gtk2_ardour/route_ui.cc @@ -1692,7 +1692,7 @@ RouteUI::map_frozen () void RouteUI::adjust_latency () { - LatencyDialog dialog (_route->name() + _(" latency"), *(_route->output()), _session->frame_rate(), _session->engine().frames_per_cycle()); + LatencyDialog dialog (_route->name() + _(" latency"), *(_route->output()), _session->frame_rate(), AudioEngine::instance()->samples_per_cycle()); } void diff --git a/gtk2_ardour/startup.cc b/gtk2_ardour/startup.cc index e24e7d6989..266a7e2a27 100644 --- a/gtk2_ardour/startup.cc +++ b/gtk2_ardour/startup.cc @@ -34,6 +34,7 @@ #include "pbd/stacktrace.h" #include "pbd/openuri.h" +#include "ardour/audioengine.h" #include "ardour/filesystem_paths.h" #include "ardour/recent_sessions.h" #include "ardour/session.h" @@ -74,7 +75,6 @@ ArdourStartup::ArdourStartup (bool require_new, const std::string& session_name, , monitor_via_hardware_button (string_compose (_("Use an external mixer or the hardware mixer of your audio interface.\n" "%1 will play NO role in monitoring"), PROGRAM_NAME)) , monitor_via_ardour_button (string_compose (_("Ask %1 to play back material as it is being recorded"), PROGRAM_NAME)) - , engine_dialog (0) , new_folder_chooser (FILE_CHOOSER_ACTION_SELECT_FOLDER) , more_new_session_options_button (_("I'd like more options for this session")) , _output_limit_count_adj (1, 0, 100, 1, 10, 0) @@ -91,13 +91,12 @@ ArdourStartup::ArdourStartup (bool require_new, const std::string& session_name, , _existing_session_chooser_used (false) { new_user = !Glib::file_test (been_here_before_path(), Glib::FILE_TEST_EXISTS); - need_audio_setup = EngineControl::need_setup (); need_session_info = (session_name.empty() || require_new); _provided_session_name = session_name; _provided_session_path = session_path; - if (need_audio_setup || need_session_info || new_user) { + if (need_session_info || new_user) { use_template_button.set_group (session_template_group); use_session_as_template_button.set_group (session_template_group); @@ -139,18 +138,10 @@ ArdourStartup::ArdourStartup (bool require_new, const std::string& session_name, setup_monitoring_choice_page (); setup_monitor_section_choice_page (); - if (need_audio_setup) { - setup_audio_page (); - } - ic_new_session_button.set_active (true); // always create new session on first run } else { - if (need_audio_setup) { - setup_audio_page (); - } - setup_initial_choice_page (); } @@ -183,7 +174,7 @@ ArdourStartup::~ArdourStartup () bool ArdourStartup::ready_without_display () const { - return !new_user && !need_audio_setup && !need_session_info; + return !new_user && !need_session_info; } void @@ -311,24 +302,6 @@ ArdourStartup::session_folder () } void -ArdourStartup::setup_audio_page () -{ - engine_dialog = manage (new EngineControl); - - engine_dialog->set_border_width (12); - - engine_dialog->show_all (); - - audio_page_index = append_page (*engine_dialog); - set_page_type (*engine_dialog, ASSISTANT_PAGE_CONTENT); - set_page_title (*engine_dialog, _("Audio / MIDI Setup")); - - /* the default parameters should work, so the page is potentially complete */ - - set_page_complete (*engine_dialog, true); -} - -void ArdourStartup::setup_new_user_page () { Label* foomatic = manage (new Label); @@ -403,8 +376,6 @@ Where would you like new %1 sessions to be stored by default?\n\n\ vbox->pack_start (*txt, false, false); vbox->pack_start (*hbox, false, true); - cerr << "Setting defaultDIR session dir to [" << Config->get_default_session_parent_dir() << "]\n"; - default_dir_chooser->set_current_folder (poor_mans_glob (Config->get_default_session_parent_dir())); default_dir_chooser->signal_current_folder_changed().connect (sigc::mem_fun (*this, &ArdourStartup::default_dir_changed)); default_dir_chooser->show (); @@ -661,13 +632,6 @@ ArdourStartup::on_delete_event (GdkEventAny*) void ArdourStartup::on_apply () { - if (engine_dialog) { - if (engine_dialog->setup_engine ()) { - set_current_page (audio_page_index); - return; - } - } - if (config_modified) { if (default_dir_chooser) { diff --git a/gtk2_ardour/startup.h b/gtk2_ardour/startup.h index 4af6ffbc2a..4dcc3a9fb2 100644 --- a/gtk2_ardour/startup.h +++ b/gtk2_ardour/startup.h @@ -56,8 +56,6 @@ class ArdourStartup : public Gtk::Assistant { bool use_session_template(); std::string session_template_name(); - EngineControl* engine_control() { return engine_dialog; } - // advanced session options bool create_master_bus() const; @@ -82,7 +80,6 @@ class ArdourStartup : public Gtk::Assistant { gint _response; bool config_modified; bool new_user; - bool need_audio_setup; bool need_session_info; bool new_only; std::string _provided_session_name; @@ -175,11 +172,6 @@ class ArdourStartup : public Gtk::Assistant { void existing_session_selected (); - /* audio setup page */ - - void setup_audio_page (); - EngineControl* engine_dialog; - /* new sessions */ void setup_new_session_page (); diff --git a/gtk2_ardour/utils.cc b/gtk2_ardour/utils.cc index 93e4ee98c7..28e0907f44 100644 --- a/gtk2_ardour/utils.cc +++ b/gtk2_ardour/utils.cc @@ -395,7 +395,7 @@ emulate_key_event (Gtk::Widget* w, unsigned int keyval) ev.state = 0; ev.keyval = keyval; ev.length = 0; - ev.string = ""; + ev.string = (const gchar*) ""; ev.hardware_keycode = keymapkey[0].keycode; ev.group = keymapkey[0].group; g_free(keymapkey); diff --git a/gtk2_ardour/window_manager.cc b/gtk2_ardour/window_manager.cc index 5fa1b6c7b0..3ab9e1adff 100644 --- a/gtk2_ardour/window_manager.cc +++ b/gtk2_ardour/window_manager.cc @@ -410,7 +410,7 @@ ProxyBase::hide () } bool -ProxyBase::handle_win_event (GdkEventAny *ev) +ProxyBase::handle_win_event (GdkEventAny* /*ev*/) { hide(); return true; diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 12681cfe2d..a02e27f83d 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -520,6 +520,7 @@ def build(bld): 'SMALLER' : '9', 'SMALL' : '10', 'NORMAL' : '11', + 'BIG' : '13', 'BIGGER' : '17', 'LARGE' : '18', 'LARGER' : '28', @@ -545,6 +546,7 @@ def build(bld): 'SMALLER' : '8', 'SMALL' : '9', 'NORMAL' : '10', + 'BIG' : '14', 'BIGGER' : '17', 'LARGE' : '18', 'LARGER' : '24', diff --git a/libs/ardour/ardour/ardour.h b/libs/ardour/ardour/ardour.h index eaf6b572fd..311611997f 100644 --- a/libs/ardour/ardour/ardour.h +++ b/libs/ardour/ardour/ardour.h @@ -75,9 +75,7 @@ namespace ARDOUR { bool translations_are_enabled (); bool set_translations_enabled (bool); - static inline microseconds_t get_microseconds () { - return (microseconds_t) jack_get_time(); - } + microseconds_t get_microseconds (); void setup_fpu (); std::vector<SyncSource> get_available_sync_options(); diff --git a/libs/midi++2/midi++/jack_midi_port.h b/libs/ardour/ardour/async_midi_port.h index 284df0ef2d..896301b398 100644 --- a/libs/midi++2/midi++/jack_midi_port.h +++ b/libs/ardour/ardour/async_midi_port.h @@ -16,14 +16,12 @@ */ -#ifndef __libmidi_port_h__ -#define __libmidi_port_h__ +#ifndef __libardour_async_midiport_h__ +#define __libardour_async_midiport_h__ #include <string> #include <iostream> -#include <jack/types.h> - #include "pbd/xml++.h" #include "pbd/crossthread.h" #include "pbd/signals.h" @@ -36,26 +34,26 @@ #include "midi++/parser.h" #include "midi++/port.h" -namespace MIDI { +#include "ardour/midi_port.h" + +namespace ARDOUR { -class Channel; -class PortRequest; + class AsyncMIDIPort : public ARDOUR::MidiPort, public MIDI::Port { -class JackMIDIPort : public Port { public: - JackMIDIPort (std::string const &, Port::Flags, jack_client_t *); - JackMIDIPort (const XMLNode&, jack_client_t *); - ~JackMIDIPort (); + AsyncMIDIPort (std::string const &, PortFlags); + ~AsyncMIDIPort (); - XMLNode& get_state () const; - void set_state (const XMLNode&); + /* called from an RT context */ void cycle_start (pframes_t nframes); - void cycle_end (); - + void cycle_end (pframes_t nframes); + + /* called from non-RT context */ + void parse (framecnt_t timestamp); - int write (const byte *msg, size_t msglen, timestamp_t timestamp); - int read (byte *buf, size_t bufsize); + int write (const MIDI::byte *msg, size_t msglen, MIDI::timestamp_t timestamp); + int read (MIDI::byte *buf, size_t bufsize); void drain (int check_interval_usecs); int selectable () const { #ifdef PLATFORM_WINDOWS @@ -65,26 +63,15 @@ class JackMIDIPort : public Port { #endif } - pframes_t nframes_this_cycle() const { return _nframes_this_cycle; } - - void reestablish (jack_client_t *); - void reconnect (); - static void set_process_thread (pthread_t); static pthread_t get_process_thread () { return _process_thread; } static bool is_process_thread(); - static PBD::Signal0<void> MakeConnections; - static PBD::Signal0<void> JackHalted; - -private: - bool _currently_in_cycle; - pframes_t _nframes_this_cycle; - jack_client_t* _jack_client; - jack_port_t* _jack_port; - timestamp_t _last_write_timestamp; + private: + bool _currently_in_cycle; + MIDI::timestamp_t _last_write_timestamp; RingBuffer< Evoral::Event<double> > output_fifo; - Evoral::EventRingBuffer<timestamp_t> input_fifo; + Evoral::EventRingBuffer<MIDI::timestamp_t> input_fifo; Glib::Threads::Mutex output_fifo_lock; #ifndef PLATFORM_WINDOWS CrossThreadChannel xthread; @@ -102,10 +89,11 @@ private: void make_connections (); void init (std::string const &, Flags); - static pthread_t _process_thread; + void flush_output_fifo (pframes_t); + static pthread_t _process_thread; }; -} // namespace MIDI +} // namespace ARDOUR -#endif // __libmidi_port_h__ +#endif /* __libardour_async_midiport_h__ */ diff --git a/libs/ardour/ardour/audio_backend.h b/libs/ardour/ardour/audio_backend.h new file mode 100644 index 0000000000..ab37bea526 --- /dev/null +++ b/libs/ardour/ardour/audio_backend.h @@ -0,0 +1,432 @@ +/* + Copyright (C) 2013 Paul Davis + + 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_audiobackend_h__ +#define __libardour_audiobackend_h__ + +#include <string> +#include <vector> + +#include <stdint.h> +#include <stdlib.h> + +#include <boost/function.hpp> + +#include "ardour/types.h" + +namespace ARDOUR { + +class AudioEngine; +class PortEngine; +class PortManager; + +class AudioBackend { + public: + + AudioBackend (AudioEngine& e) : engine (e){} + virtual ~AudioBackend () {} + + /** Return the name of this backend. + * + * Should use a well-known, unique term. Expected examples + * might include "JACK", "CoreAudio", "ASIO" etc. + */ + virtual std::string name() const = 0; + + /** Return a private, type-free pointer to any data + * that might be useful to a concrete implementation + */ + virtual void* private_handle() const = 0; + + /** Return true if the underlying mechanism/API is still available + * for us to utilize. return false if some or all of the AudioBackend + * API can no longer be effectively used. + */ + virtual bool connected() const = 0; + + /** Return true if the callback from the underlying mechanism/API + * (CoreAudio, JACK, ASIO etc.) occurs in a thread subject to realtime + * constraints. Return false otherwise. + */ + virtual bool is_realtime () const = 0; + + /* Discovering devices and parameters */ + + /** Return true if this backend requires the selection of a "driver" + * before any device can be selected. Return false otherwise. + * + * Intended mainly to differentiate between meta-APIs like JACK + * which can still expose different backends (such as ALSA or CoreAudio + * or FFADO or netjack) and those like ASIO or CoreAudio which + * do not. + */ + virtual bool requires_driver_selection() const { return false; } + + /** If the return value of requires_driver_selection() is true, + * then this function can return the list of known driver names. + * + * If the return value of requires_driver_selection() is false, + * then this function should not be called. If it is called + * its return value is an empty vector of strings. + */ + virtual std::vector<std::string> enumerate_drivers() const { return std::vector<std::string>(); } + + /** Returns zero if the backend can successfully use @param name as the + * driver, non-zero otherwise. + * + * Should not be used unless the backend returns true from + * requires_driver_selection() + */ + virtual int set_driver (const std::string& /*drivername*/) { return 0; } + + /** used to list device names along with whether or not they are currently + * available. + */ + struct DeviceStatus { + std::string name; + bool available; + + DeviceStatus (const std::string& s, bool avail) : name (s), available (avail) {} + }; + + /** Returns a collection of DeviceStatuses identifying devices discovered + * by this backend since the start of the process. + * + * Any of the names in each DeviceStatus may be used to identify a + * device in other calls to the backend, though any of them may become + * invalid at any time. + */ + virtual std::vector<DeviceStatus> enumerate_devices () const = 0; + + /** Returns a collection of float identifying sample rates that are + * potentially usable with the hardware identified by @param device. + * Any of these values may be supplied in other calls to this backend + * as the desired sample rate to use with the name device, but the + * requested sample rate may turn out to be unavailable, or become invalid + * at any time. + */ + virtual std::vector<float> available_sample_rates (const std::string& device) const = 0; + /** Returns a collection of uint32 identifying buffer sizes that are + * potentially usable with the hardware identified by @param device. + * Any of these values may be supplied in other calls to this backend + * as the desired buffer size to use with the name device, but the + * requested buffer size may turn out to be unavailable, or become invalid + * at any time. + */ + virtual std::vector<uint32_t> available_buffer_sizes (const std::string& device) const = 0; + + /** Returns the maximum number of input channels that are potentially + * usable with the hardware identified by @param device. Any number from 1 + * to the value returned may be supplied in other calls to this backend as + * the input channel count to use with the name device, but the requested + * count may turn out to be unavailable, or become invalid at any time. + */ + virtual uint32_t available_input_channel_count (const std::string& device) const = 0; + + /** Returns the maximum number of output channels that are potentially + * usable with the hardware identified by @param device. Any number from 1 + * to the value returned may be supplied in other calls to this backend as + * the output channel count to use with the name device, but the requested + * count may turn out to be unavailable, or become invalid at any time. + */ + virtual uint32_t available_output_channel_count (const std::string& device) const = 0; + + /* Return true if the derived class can change the sample rate of the + * device in use while the device is already being used. Return false + * otherwise. (example: JACK cannot do this as of September 2013) + */ + virtual bool can_change_sample_rate_when_running () const = 0; + /* Return true if the derived class can change the buffer size of the + * device in use while the device is already being used. Return false + * otherwise. + */ + virtual bool can_change_buffer_size_when_running () const = 0; + + /* Set the hardware parameters. + * + * If called when the current state is stopped or paused, + * the changes will not take effect until the state changes to running. + * + * If called while running, the state will change as fast as the + * implementation allows. + * + * All set_*() methods return zero on success, non-zero otherwise. + */ + + /** Set the name of the device to be used + */ + virtual int set_device_name (const std::string&) = 0; + /** Set the sample rate to be used + */ + virtual int set_sample_rate (float) = 0; + /** Set the buffer size to be used. + * + * The device is assumed to use a double buffering scheme, so that one + * buffer's worth of data can be processed by hardware while software works + * on the other buffer. All known suitable audio APIs support this model + * (though ALSA allows for alternate numbers of buffers, and CoreAudio + * doesn't directly expose the concept). + */ + virtual int set_buffer_size (uint32_t) = 0; + /** Set the preferred underlying hardware sample format + * + * This does not change the sample format (32 bit float) read and + * written to the device via the Port API. + */ + virtual int set_sample_format (SampleFormat) = 0; + /** Set the preferred underlying hardware data layout. + * If @param yn is true, then the hardware will interleave + * samples for successive channels; otherwise, the hardware will store + * samples for a single channel contiguously. + * + * Setting this does not change the fact that all data streams + * to and from Ports are mono (essentially, non-interleaved) + */ + virtual int set_interleaved (bool yn) = 0; + /** Set the number of input channels that should be used + */ + virtual int set_input_channels (uint32_t) = 0; + /** Set the number of output channels that should be used + */ + virtual int set_output_channels (uint32_t) = 0; + /** Set the (additional) input latency that cannot be determined via + * the implementation's underlying code (e.g. latency from + * external D-A/D-A converters. Units are samples. + */ + virtual int set_systemic_input_latency (uint32_t) = 0; + /** Set the (additional) output latency that cannot be determined via + * the implementation's underlying code (e.g. latency from + * external D-A/D-A converters. Units are samples. + */ + virtual int set_systemic_output_latency (uint32_t) = 0; + + /* Retrieving parameters */ + + virtual std::string device_name () const = 0; + virtual float sample_rate () const = 0; + virtual uint32_t buffer_size () const = 0; + virtual SampleFormat sample_format () const = 0; + virtual bool interleaved () const = 0; + virtual uint32_t input_channels () const = 0; + virtual uint32_t output_channels () const = 0; + virtual uint32_t systemic_input_latency () const = 0; + virtual uint32_t systemic_output_latency () const = 0; + + /** Return the name of a control application for the + * selected/in-use device. If no such application exists, + * or if no device has been selected or is in-use, + * return an empty string. + */ + virtual std::string control_app_name() const = 0; + /** Launch the control app for the currently in-use or + * selected device. May do nothing if the control + * app is undefined or cannot be launched. + */ + virtual void launch_control_app () = 0; + /* Basic state control */ + + /** Start using the device named in the most recent call + * to set_device(), with the parameters set by various + * the most recent calls to set_sample_rate() etc. etc. + * + * At some undetermined time after this function is successfully called, + * the backend will start calling the ::process_callback() method of + * the AudioEngine referenced by @param engine. These calls will + * occur in a thread created by and/or under the control of the backend. + * + * Return zero if successful, negative values otherwise. + */ + virtual int start () = 0; + + /** Stop using the device currently in use. + * + * If the function is successfully called, no subsequent calls to the + * process_callback() of @param engine will be made after the function + * returns, until parameters are reset and start() are called again. + * + * The backend is considered to be un-configured after a successful + * return, and requires calls to set hardware parameters before it can be + * start()-ed again. See pause() for a way to avoid this. stop() should + * only be used when reconfiguration is required OR when there are no + * plans to use the backend in the future with a reconfiguration. + * + * Return zero if successful, 1 if the device is not in use, negative values on error + */ + virtual int stop () = 0; + + /** Temporarily cease using the device named in the most recent call to set_parameters(). + * + * If the function is successfully called, no subsequent calls to the + * process_callback() of @param engine will be made after the function + * returns, until start() is called again. + * + * The backend will retain its existing parameter configuration after a successful + * return, and does NOT require any calls to set hardware parameters before it can be + * start()-ed again. + * + * Return zero if successful, 1 if the device is not in use, negative values on error + */ + virtual int pause () = 0; + + /** While remaining connected to the device, and without changing its + * configuration, start (or stop) calling the process_callback() of @param engine + * without waiting for the device. Once process_callback() has returned, it + * will be called again immediately, thus allowing for faster-than-realtime + * processing. + * + * All registered ports remain in existence and all connections remain + * unaltered. However, any physical ports should NOT be used by the + * process_callback() during freewheeling - the data behaviour is undefined. + * + * If @param start_stop is true, begin this behaviour; otherwise cease this + * behaviour if it currently occuring, and return to calling + * process_callback() of @param engine by waiting for the device. + * + * Return zero on success, non-zero otherwise. + */ + virtual int freewheel (bool start_stop) = 0; + + /** return the fraction of the time represented by the current buffer + * size that is being used for each buffer process cycle, as a value + * from 0.0 to 1.0 + * + * E.g. if the buffer size represents 5msec and current processing + * takes 1msec, the returned value should be 0.2. + * + * Implementations can feel free to smooth the values returned over + * time (e.g. high pass filtering, or its equivalent). + */ + virtual float cpu_load() const = 0; + + /* Transport Control (JACK is the only audio API that currently offers + the concept of shared transport control) + */ + + /** Attempt to change the transport state to TransportRolling. + */ + virtual void transport_start () {} + /** Attempt to change the transport state to TransportStopped. + */ + virtual void transport_stop () {} + /** return the current transport state + */ + virtual TransportState transport_state () const { return TransportStopped; } + /** Attempt to locate the transport to @param pos + */ + virtual void transport_locate (framepos_t /*pos*/) {} + /** Return the current transport location, in samples measured + * from the origin (defined by the transport time master) + */ + virtual framepos_t transport_frame() const { return 0; } + + /** If @param yn is true, become the time master for any inter-application transport + * timebase, otherwise cease to be the time master for the same. + * + * Return zero on success, non-zero otherwise + * + * JACK is the only currently known audio API with the concept of a shared + * transport timebase. + */ + virtual int set_time_master (bool /*yn*/) { return 0; } + + virtual int usecs_per_cycle () const { return 1000000 * (buffer_size() / sample_rate()); } + virtual size_t raw_buffer_size (DataType t) = 0; + + /* Process time */ + + /** return the time according to the sample clock in use, measured in + * samples since an arbitrary zero time in the past. The value should + * increase monotonically and linearly, without interruption from any + * source (including CPU frequency scaling). + * + * It is extremely likely that any implementation will use a DLL, since + * this function can be called from any thread, at any time, and must be + * able to accurately determine the correct sample time. + * + * Can be called from any thread. + */ + virtual pframes_t sample_time () = 0; + + /** Return the time according to the sample clock in use when the most + * recent buffer process cycle began. Can be called from any thread. + */ + virtual pframes_t sample_time_at_cycle_start () = 0; + + /** Return the time since the current buffer process cycle started, + * in samples, according to the sample clock in use. + * + * Can ONLY be called from within a process() callback tree (which + * implies that it can only be called by a process thread) + */ + virtual pframes_t samples_since_cycle_start () = 0; + + /** Return true if it possible to determine the offset in samples of the + * first video frame that starts within the current buffer process cycle, + * measured from the first sample of the cycle. If returning true, + * set @param offset to that offset. + * + * Eg. if it can be determined that the first video frame within the cycle + * starts 28 samples after the first sample of the cycle, then this method + * should return true and set @param offset to 28. + * + * May be impossible to support outside of JACK, which has specific support + * (in some cases, hardware support) for this feature. + * + * Can ONLY be called from within a process() callback tree (which implies + * that it can only be called by a process thread) + */ + virtual bool get_sync_offset (pframes_t& /*offset*/) const { return false; } + + /** Create a new thread suitable for running part of the buffer process + * cycle (i.e. Realtime scheduling, memory allocation, etc. etc are all + * correctly setup), with a stack size given in bytes by specified @param + * stacksize. The thread will begin executing @param func, and will exit + * when that function returns. + */ + virtual int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize) = 0; + + virtual void update_latencies () = 0; + + protected: + AudioEngine& engine; +}; + +struct AudioBackendInfo { + const char* name; + + int (*instantiate) (const std::string& arg1, const std::string& arg2); + int (*deinstantiate) (void); + + boost::shared_ptr<AudioBackend> (*backend_factory) (AudioEngine&); + boost::shared_ptr<PortEngine> (*portengine_factory) (PortManager&); + + /** Return true if the underlying mechanism/API has been + * configured and does not need (re)configuration in order + * to be usable. Return false otherwise. + * + * Note that this may return true if (re)configuration, even though + * not currently required, is still possible. + */ + bool (*already_configured)(); +}; + +} // namespace + +#endif /* __libardour_audiobackend_h__ */ + diff --git a/libs/ardour/ardour/audio_diskstream.h b/libs/ardour/ardour/audio_diskstream.h index b636cb4734..5a856e9b36 100644 --- a/libs/ardour/ardour/audio_diskstream.h +++ b/libs/ardour/ardour/audio_diskstream.h @@ -114,7 +114,7 @@ class AudioDiskstream : public Diskstream XMLNode& get_state(void); int set_state(const XMLNode& node, int version); - void request_jack_monitors_input (bool); + void request_input_monitoring (bool); static void swap_by_ptr (Sample *first, Sample *last) { while (first < last) { @@ -160,7 +160,7 @@ class AudioDiskstream : public Diskstream std::string name; bool is_physical () const; - void request_jack_monitors_input (bool) const; + void request_input_monitoring (bool) const; }; /** Information about one of our channels */ diff --git a/libs/ardour/ardour/audio_port.h b/libs/ardour/ardour/audio_port.h index 7f084a5c85..f87b134e9e 100644 --- a/libs/ardour/ardour/audio_port.h +++ b/libs/ardour/ardour/audio_port.h @@ -46,10 +46,10 @@ class AudioPort : public Port AudioBuffer& get_audio_buffer (pframes_t nframes); protected: - friend class AudioEngine; + friend class PortManager; + AudioPort (std::string const &, PortFlags); - AudioPort (std::string const &, Flags); - /* special access for engine only */ + /* special access for PortManager only (hah, C++) */ Sample* engine_get_whole_audio_buffer (); private: diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h index ec31dd1ac2..cf4f91d4d0 100644 --- a/libs/ardour/ardour/audioengine.h +++ b/libs/ardour/ardour/audioengine.h @@ -33,29 +33,22 @@ #include <glibmm/threads.h> -#include "pbd/rcu.h" #include "pbd/signals.h" #include "pbd/stacktrace.h" -#ifndef PLATFORM_WINDOWS -#include <jack/weakjack.h> -#endif - -#include <jack/jack.h> -#include <jack/transport.h> -#include <jack/thread.h> - #include "ardour/ardour.h" - #include "ardour/data_type.h" #include "ardour/session_handle.h" #include "ardour/types.h" #include "ardour/chan_count.h" +#include "ardour/port_manager.h" #ifdef HAVE_JACK_SESSION #include <jack/session.h> #endif +class MTDM; + namespace ARDOUR { class InternalPort; @@ -63,300 +56,190 @@ class MidiPort; class Port; class Session; class ProcessThread; +class AudioBackend; +class AudioBackendInfo; -class AudioEngine : public SessionHandlePtr +class AudioEngine : public SessionHandlePtr, public PortManager { public: - typedef std::map<std::string,boost::shared_ptr<Port> > Ports; - - AudioEngine (std::string client_name, std::string session_uuid); - virtual ~AudioEngine (); - - jack_client_t* jack() const; - bool connected() const { return _jack != 0; } - - bool is_realtime () const; - - ProcessThread* main_thread() const { return _main_thread; } - - std::string client_name() const { return jack_client_name; } - - int reconnect_to_jack (); - int disconnect_from_jack(); - - int stop (bool forever = false); - int start (); - bool running() const { return _running; } - - Glib::Threads::Mutex& process_lock() { return _process_lock; } - - framecnt_t frame_rate () const; - pframes_t frames_per_cycle () const; - - size_t raw_buffer_size(DataType t); - - int usecs_per_cycle () const { return _usecs_per_cycle; } - - bool get_sync_offset (pframes_t & offset) const; - - pframes_t frames_since_cycle_start () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_frames_since_cycle_start (_priv_jack); - } - - pframes_t frame_time () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_frame_time (_priv_jack); - } - - pframes_t frame_time_at_cycle_start () { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_last_frame_time (_priv_jack); - } - - pframes_t transport_frame () const { - const jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_get_current_transport_frame (_priv_jack); - } - - int request_buffer_size (pframes_t); - - framecnt_t processed_frames() const { return _processed_frames; } - - float get_cpu_load() { - jack_client_t* _priv_jack = _jack; - if (!_running || !_priv_jack) { - return 0; - } - return jack_cpu_load (_priv_jack); - } - - void set_session (Session *); - void remove_session (); // not a replacement for SessionHandle::session_going_away() - - class PortRegistrationFailure : public std::exception { - public: - PortRegistrationFailure (std::string const & why = "") - : reason (why) {} - - ~PortRegistrationFailure () throw () {} - - virtual const char *what() const throw () { return reason.c_str(); } - - private: - std::string reason; - }; - - class NoBackendAvailable : public std::exception { - public: - virtual const char *what() const throw() { return "could not connect to engine backend"; } - }; - - boost::shared_ptr<Port> register_input_port (DataType, const std::string& portname); - boost::shared_ptr<Port> register_output_port (DataType, const std::string& portname); - int unregister_port (boost::shared_ptr<Port>); - - bool port_is_physical (const std::string&) const; - void request_jack_monitors_input (const std::string&, bool) const; - - void split_cycle (pframes_t offset); - - int connect (const std::string& source, const std::string& destination); - int disconnect (const std::string& source, const std::string& destination); - int disconnect (boost::shared_ptr<Port>); - - const char ** get_ports (const std::string& port_name_pattern, const std::string& type_name_pattern, uint32_t flags); - - bool can_request_hardware_monitoring (); - - ChanCount n_physical_outputs () const; - ChanCount n_physical_inputs () const; - - void get_physical_outputs (DataType type, std::vector<std::string>&); - void get_physical_inputs (DataType type, std::vector<std::string>&); - boost::shared_ptr<Port> get_port_by_name (const std::string &); - void port_renamed (const std::string&, const std::string&); - - enum TransportState { - TransportStopped = JackTransportStopped, - TransportRolling = JackTransportRolling, - TransportLooping = JackTransportLooping, - TransportStarting = JackTransportStarting - }; - - void transport_start (); - void transport_stop (); - void transport_locate (framepos_t); - TransportState transport_state (); - - int reset_timebase (); - - void update_latencies (); - - /* start/stop freewheeling */ - - int freewheel (bool onoff); - bool freewheeling() const { return _freewheeling; } - - /* this signal is sent for every process() cycle while freewheeling. -_ the regular process() call to session->process() is not made. - */ - - PBD::Signal1<int, pframes_t> Freewheel; - - PBD::Signal0<void> Xrun; - - /* this signal is if JACK notifies us of a graph order event */ - - PBD::Signal0<void> GraphReordered; - -#ifdef HAVE_JACK_SESSION - PBD::Signal1<void,jack_session_event_t *> JackSessionEvent; -#endif - - - /* this signal is emitted if the sample rate changes */ - - PBD::Signal1<void, framecnt_t> SampleRateChanged; - - /* this signal is sent if JACK ever disconnects us */ - - PBD::Signal1<void,const char*> Halted; - - /* these two are emitted when the engine itself is - started and stopped - */ - - PBD::Signal0<void> Running; - PBD::Signal0<void> Stopped; - - /** Emitted if a JACK port is registered or unregistered */ - PBD::Signal0<void> PortRegisteredOrUnregistered; - - /** Emitted if a JACK port is connected or disconnected. - * The Port parameters are the ports being connected / disconnected, or 0 if they are not known to Ardour. - * The std::string parameters are the (long) port names. - * The bool parameter is true if ports were connected, or false for disconnected. - */ - PBD::Signal5<void, boost::weak_ptr<Port>, std::string, boost::weak_ptr<Port>, std::string, bool> PortConnectedOrDisconnected; - - std::string make_port_name_relative (std::string) const; - std::string make_port_name_non_relative (std::string) const; - bool port_is_mine (const std::string&) const; - - static AudioEngine* instance() { return _instance; } - static void destroy(); - void died (); - - int create_process_thread (boost::function<void()>, jack_native_thread_t*, size_t stacksize); - bool stop_process_thread (jack_native_thread_t); - -private: - static AudioEngine* _instance; - - jack_client_t* volatile _jack; /* could be reset to null by SIGPIPE or another thread */ - std::string jack_client_name; - Glib::Threads::Mutex _process_lock; - Glib::Threads::Cond session_removed; - bool session_remove_pending; - frameoffset_t session_removal_countdown; - gain_t session_removal_gain; - gain_t session_removal_gain_step; - bool _running; - bool _has_run; - mutable framecnt_t _buffer_size; - std::map<DataType,size_t> _raw_buffer_sizes; - mutable framecnt_t _frame_rate; - /// number of frames between each check for changes in monitor input - framecnt_t monitor_check_interval; - /// time of the last monitor check in frames - framecnt_t last_monitor_check; - /// the number of frames processed since start() was called - framecnt_t _processed_frames; - bool _freewheeling; - bool _pre_freewheel_mmc_enabled; - int _usecs_per_cycle; - bool port_remove_in_progress; - Glib::Threads::Thread* m_meter_thread; - ProcessThread* _main_thread; - - SerializedRCUManager<Ports> ports; - - boost::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input); - - int process_callback (pframes_t nframes); - void* process_thread (); - void remove_all_ports (); - - ChanCount n_physical (unsigned long) const; - void get_physical (DataType, unsigned long, std::vector<std::string> &); - - void port_registration_failure (const std::string& portname); - - static int _xrun_callback (void *arg); -#ifdef HAVE_JACK_SESSION - static void _session_callback (jack_session_event_t *event, void *arg); -#endif - static int _graph_order_callback (void *arg); - static void* _process_thread (void *arg); - static int _sample_rate_callback (pframes_t nframes, void *arg); - static int _bufsize_callback (pframes_t nframes, void *arg); - static void _jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int, void*); - static int _jack_sync_callback (jack_transport_state_t, jack_position_t*, void *arg); - static void _freewheel_callback (int , void *arg); - static void _registration_callback (jack_port_id_t, int, void *); - static void _connect_callback (jack_port_id_t, jack_port_id_t, int, void *); - - void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); - int jack_sync_callback (jack_transport_state_t, jack_position_t*); - int jack_bufsize_callback (pframes_t); - int jack_sample_rate_callback (pframes_t); - void freewheel_callback (int); - void connect_callback (jack_port_id_t, jack_port_id_t, int); - - void set_jack_callbacks (); - - static void _latency_callback (jack_latency_callback_mode_t, void*); - void jack_latency_callback (jack_latency_callback_mode_t); - - int connect_to_jack (std::string client_name, std::string session_uuid); - - static void halted (void *); - static void halted_info (jack_status_t,const char*,void *); - - void meter_thread (); - void start_metering_thread (); - void stop_metering_thread (); - - static gint m_meter_exit; - - struct ThreadData { - AudioEngine* engine; - boost::function<void()> f; - size_t stacksize; - - ThreadData (AudioEngine* ae, boost::function<void()> fp, size_t stacksz) - : engine (ae) , f (fp) , stacksize (stacksz) {} - }; - - static void* _start_process_thread (void*); - void parameter_changed (const std::string&); - PBD::ScopedConnection config_connection; + static AudioEngine* create (); + + virtual ~AudioEngine (); + + int discover_backends(); + std::vector<const AudioBackendInfo*> available_backends() const; + std::string current_backend_name () const; + boost::shared_ptr<AudioBackend> set_backend (const std::string&, const std::string& arg1, const std::string& arg2); + boost::shared_ptr<AudioBackend> current_backend() const { return _backend; } + bool setup_required () const; + + ProcessThread* main_thread() const { return _main_thread; } + + /* START BACKEND PROXY API + * + * See audio_backend.h for full documentation and semantics. These wrappers + * just forward to a backend implementation. + */ + + int start (); + int stop (); + int pause (); + int freewheel (bool start_stop); + float get_cpu_load() const ; + void transport_start (); + void transport_stop (); + TransportState transport_state (); + void transport_locate (framepos_t pos); + framepos_t transport_frame(); + framecnt_t sample_rate () const; + pframes_t samples_per_cycle () const; + int usecs_per_cycle () const; + size_t raw_buffer_size (DataType t); + pframes_t sample_time (); + pframes_t sample_time_at_cycle_start (); + pframes_t samples_since_cycle_start (); + bool get_sync_offset (pframes_t& offset) const; + int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize); + bool is_realtime() const; + bool connected() const; + + int set_device_name (const std::string&); + int set_sample_rate (float); + int set_buffer_size (uint32_t); + int set_sample_format (SampleFormat); + int set_interleaved (bool yn); + int set_input_channels (uint32_t); + int set_output_channels (uint32_t); + int set_systemic_input_latency (uint32_t); + int set_systemic_output_latency (uint32_t); + + /* END BACKEND PROXY API */ + + bool freewheeling() const { return _freewheeling; } + bool running() const { return _running; } + + Glib::Threads::Mutex& process_lock() { return _process_lock; } + + int request_buffer_size (pframes_t samples) { + return set_buffer_size (samples); + } + + framecnt_t processed_frames() const { return _processed_frames; } + + void set_session (Session *); + void remove_session (); // not a replacement for SessionHandle::session_going_away() + Session* session() const { return _session; } + + class NoBackendAvailable : public std::exception { + public: + virtual const char *what() const throw() { return "could not connect to engine backend"; } + }; + + void split_cycle (pframes_t offset); + + int reset_timebase (); + + void update_latencies (); + + /* this signal is sent for every process() cycle while freewheeling. + (the regular process() call to session->process() is not made) + */ + + PBD::Signal1<int, pframes_t> Freewheel; + + PBD::Signal0<void> Xrun; + + /* this signal is emitted if the sample rate changes */ + + PBD::Signal1<void, framecnt_t> SampleRateChanged; + + /* this signal is sent if the backend ever disconnects us */ + + PBD::Signal1<void,const char*> Halted; + + /* these two are emitted when the engine itself is + started and stopped + */ + + PBD::Signal0<void> Running; + PBD::Signal0<void> Stopped; + + static AudioEngine* instance() { return _instance; } + static void destroy(); + void died (); + + /* The backend will cause these at the appropriate time(s) + */ + int process_callback (pframes_t nframes); + int buffer_size_change (pframes_t nframes); + int sample_rate_change (pframes_t nframes); + void freewheel_callback (bool); + void timebase_callback (TransportState state, pframes_t nframes, framepos_t pos, int new_position); + int sync_callback (TransportState state, framepos_t position); + int port_registration_callback (); + void latency_callback (bool for_playback); + void halted_callback (const char* reason); + + /* 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&); + uint32_t latency_signal_delay () const { return _latency_signal_latency; } + + private: + AudioEngine (); + + static AudioEngine* _instance; + + boost::shared_ptr<AudioBackend> _backend; + + Glib::Threads::Mutex _process_lock; + Glib::Threads::Cond session_removed; + bool session_remove_pending; + frameoffset_t session_removal_countdown; + gain_t session_removal_gain; + gain_t session_removal_gain_step; + bool _running; + bool _freewheeling; + /// number of frames between each check for changes in monitor input + framecnt_t monitor_check_interval; + /// time of the last monitor check in frames + framecnt_t last_monitor_check; + /// the number of frames processed since start() was called + 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; + std::string _latency_input_name; + std::string _latency_output_name; + framecnt_t _latency_signal_latency; + + void meter_thread (); + void start_metering_thread (); + void stop_metering_thread (); + + static gint m_meter_exit; + + void parameter_changed (const std::string&); + PBD::ScopedConnection config_connection; + + typedef std::map<std::string,AudioBackendInfo*> BackendMap; + BackendMap _backends; + AudioBackendInfo* backend_discover (const std::string&); + void drop_backend (); }; - + } // namespace ARDOUR #endif /* __ardour_audioengine_h__ */ diff --git a/libs/ardour/ardour/backend_search_path.h b/libs/ardour/ardour/backend_search_path.h new file mode 100644 index 0000000000..e8a5082c71 --- /dev/null +++ b/libs/ardour/ardour/backend_search_path.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2011 Paul Davis + + 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 __ardour_backend_search_path_h__ +#define __ardour_backend_search_path_h__ + +#include "pbd/search_path.h" + +namespace ARDOUR { + + /** + * return a SearchPath containing directories in which to look for + * backend plugins. + * + * If ARDOUR_BACKEND_PATH is defined then the SearchPath returned + * will contain only those directories specified in it, otherwise it will + * contain the user and system directories which may contain audio/MIDI + * backends. + */ + PBD::Searchpath backend_search_path (); + +} // namespace ARDOUR + +#endif /* __ardour_backend_search_path_h__ */ diff --git a/libs/ardour/ardour/buffer_set.h b/libs/ardour/ardour/buffer_set.h index 12e9dbc63b..26d47682af 100644 --- a/libs/ardour/ardour/buffer_set.h +++ b/libs/ardour/ardour/buffer_set.h @@ -70,7 +70,7 @@ public: void clear(); void attach_buffers (PortSet& ports); - void get_jack_port_addresses (PortSet &, framecnt_t); + void get_backend_port_addresses (PortSet &, framecnt_t); /* the capacity here is a size_t and has a different interpretation depending on the DataType of the buffers. for audio, its a frame count. for MIDI diff --git a/libs/ardour/ardour/data_type.h b/libs/ardour/ardour/data_type.h index 6a016ae75a..b4129e2629 100644 --- a/libs/ardour/ardour/data_type.h +++ b/libs/ardour/ardour/data_type.h @@ -21,11 +21,11 @@ #define __ardour_data_type_h__ #include <string> -#include <jack/jack.h> +#include <stdint.h> +#include <glib.h> namespace ARDOUR { - /** A type of Data Ardour is capable of processing. * * The majority of this class is dedicated to conversion to and from various @@ -61,33 +61,25 @@ public: /** Construct from a string (Used for loading from XML and Ports) * The string can be as in an XML file (eg "audio" or "midi"), or a - * Jack type string (from jack_port_type) */ + */ DataType(const std::string& str) : _symbol(NIL) { - if (str == "audio" || str == JACK_DEFAULT_AUDIO_TYPE) + if (!g_ascii_strncasecmp(str.c_str(), "audio", str.length())) { _symbol = AUDIO; - else if (str == "midi" || str == JACK_DEFAULT_MIDI_TYPE) + } else if (!g_ascii_strncasecmp(str.c_str(), "midi", str.length())) { _symbol = MIDI; - } - - /** Get the Jack type this DataType corresponds to */ - const char* to_jack_type() const { - switch (_symbol) { - case AUDIO: return JACK_DEFAULT_AUDIO_TYPE; - case MIDI: return JACK_DEFAULT_MIDI_TYPE; - default: return ""; } } /** Inverse of the from-string constructor */ const char* to_string() const { switch (_symbol) { - case AUDIO: return "audio"; - case MIDI: return "midi"; - default: return "unknown"; // reeeally shouldn't ever happen + case AUDIO: return "audio"; + case MIDI: return "midi"; + default: return "unknown"; // reeeally shouldn't ever happen } } - + const char* to_i18n_string () const; inline operator uint32_t() const { return (uint32_t)_symbol; } @@ -125,7 +117,6 @@ private: }; - } // namespace ARDOUR #endif // __ardour_data_type_h__ diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index ae18e59c04..47eee69df5 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -64,6 +64,7 @@ namespace PBD { extern uint64_t OrderKeys; extern uint64_t Automation; extern uint64_t WiimoteControl; + extern uint64_t Ports; } } diff --git a/libs/ardour/ardour/directory_names.h b/libs/ardour/ardour/directory_names.h index cb4701d336..72a456efe6 100644 --- a/libs/ardour/ardour/directory_names.h +++ b/libs/ardour/ardour/directory_names.h @@ -39,6 +39,7 @@ extern const char* const surfaces_dir_name; extern const char* const ladspa_dir_name; extern const char* const user_config_dir_name; extern const char* const panner_dir_name; +extern const char* const backend_dir_name; }; diff --git a/libs/ardour/ardour/diskstream.h b/libs/ardour/ardour/diskstream.h index 427b52b054..7a4bccbb46 100644 --- a/libs/ardour/ardour/diskstream.h +++ b/libs/ardour/ardour/diskstream.h @@ -133,8 +133,8 @@ class Diskstream : public SessionObject, public PublicDiskstream virtual XMLNode& get_state(void); virtual int set_state(const XMLNode&, int version); - virtual void request_jack_monitors_input (bool) {} - virtual void ensure_jack_monitors_input (bool) {} + virtual void request_input_monitoring (bool) {} + virtual void ensure_input_monitoring (bool) {} framecnt_t capture_offset() const { return _capture_offset; } virtual void set_capture_offset (); diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h index 0a288d68d3..cac09d34af 100644 --- a/libs/ardour/ardour/graph.h +++ b/libs/ardour/ardour/graph.h @@ -92,7 +92,7 @@ protected: virtual void session_going_away (); private: - std::list<jack_native_thread_t> _thread_list; + std::list<pthread_t> _thread_list; volatile bool _quit_threads; void reset_thread_list (); diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h index 5ef5e4c845..781396a598 100644 --- a/libs/ardour/ardour/midi_buffer.h +++ b/libs/ardour/ardour/midi_buffer.h @@ -44,7 +44,6 @@ public: void copy(const MidiBuffer& copy); bool push_back(const Evoral::MIDIEvent<TimeType>& event); - bool push_back(const jack_midi_event_t& event); bool push_back(TimeType time, size_t size, const uint8_t* data); uint8_t* reserve(TimeType time, size_t size); diff --git a/libs/ardour/ardour/midi_diskstream.h b/libs/ardour/ardour/midi_diskstream.h index 34fa0ae79a..b1c126b339 100644 --- a/libs/ardour/ardour/midi_diskstream.h +++ b/libs/ardour/ardour/midi_diskstream.h @@ -81,7 +81,7 @@ class MidiDiskstream : public Diskstream XMLNode& get_state(void); int set_state(const XMLNode&, int version); - void ensure_jack_monitors_input (bool); + void ensure_input_monitoring (bool); boost::shared_ptr<SMFSource> write_source () { return _write_source; } diff --git a/libs/ardour/ardour/midi_port.h b/libs/ardour/ardour/midi_port.h index 5dc55398cb..00617d90ec 100644 --- a/libs/ardour/ardour/midi_port.h +++ b/libs/ardour/ardour/midi_port.h @@ -21,6 +21,8 @@ #ifndef __ardour_midi_port_h__ #define __ardour_midi_port_h__ +#include "midi++/parser.h" + #include "ardour/port.h" #include "ardour/midi_buffer.h" #include "ardour/midi_state_tracker.h" @@ -56,18 +58,35 @@ class MidiPort : public Port { MidiBuffer& get_midi_buffer (pframes_t nframes); + void set_always_parse (bool yn); + MIDI::Parser& self_parser() { return _self_parser; } + protected: - friend class AudioEngine; + friend class PortManager; - MidiPort (const std::string& name, Flags); + MidiPort (const std::string& name, PortFlags); private: MidiBuffer* _buffer; bool _has_been_mixed_down; bool _resolve_required; bool _input_active; - - void resolve_notes (void* jack_buffer, MidiBuffer::TimeType when); + bool _always_parse; + + /* Naming this is tricky. AsyncMIDIPort inherits (for now, aug 2013) from + * both MIDI::Port, which has _parser, and this (ARDOUR::MidiPort). We + * need parsing support in this object, independently of what the + * MIDI::Port/AsyncMIDIPort stuff does. Rather than risk errors coming + * from not explicitly naming which _parser we want, we will call this + * _self_parser for now. + * + * Ultimately, MIDI::Port should probably go away or be fully integrated + * into this object, somehow. + */ + + MIDI::Parser _self_parser; + + void resolve_notes (void* buffer, MidiBuffer::TimeType when); }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/midi_ui.h b/libs/ardour/ardour/midi_ui.h index 34e97494be..c15a530057 100644 --- a/libs/ardour/ardour/midi_ui.h +++ b/libs/ardour/ardour/midi_ui.h @@ -26,13 +26,11 @@ #include "pbd/signals.h" #include "pbd/stacktrace.h" -namespace MIDI { - class Port; -} namespace ARDOUR { class Session; +class AsyncMIDIPort; /* this is mostly a placeholder because I suspect that at some point we will want to add more members to accomodate @@ -67,7 +65,7 @@ class MidiControlUI : public AbstractUI<MidiUIRequest> ARDOUR::Session& _session; PBD::ScopedConnection rebind_connection; - bool midi_input_handler (Glib::IOCondition, MIDI::Port*); + bool midi_input_handler (Glib::IOCondition, AsyncMIDIPort*); void reset_ports (); void clear_ports (); diff --git a/libs/ardour/ardour/midiport_manager.h b/libs/ardour/ardour/midiport_manager.h new file mode 100644 index 0000000000..9f93c43d5a --- /dev/null +++ b/libs/ardour/ardour/midiport_manager.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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 __midiport_manager_h__ +#define __midiport_manager_h__ + +#include <list> + +#include <string> + +#include "pbd/rcu.h" + +#include "midi++/types.h" +#include "midi++/port.h" + +#include "ardour/types.h" + +namespace ARDOUR { + +class MidiPort; +class Port; + +class MidiPortManager { + public: + MidiPortManager(); + virtual ~MidiPortManager (); + + /* Ports used for control. These are read/written to outside of the + * process callback (asynchronously with respect to when data + * actually arrives). + * + * More detail: we do actually read/write data for these ports + * inside the process callback, but incoming data is only parsed + * and outgoing data is only generated *outside* the process + * callback. + */ + + MIDI::Port* midi_input_port () const { return _midi_input_port; } + MIDI::Port* midi_output_port () const { return _midi_output_port; } + MIDI::Port* mmc_input_port () const { return _mmc_input_port; } + MIDI::Port* mmc_output_port () const { return _mmc_output_port; } + + /* Ports used for synchronization. These have their I/O handled inside the + * process callback. + */ + + boost::shared_ptr<MidiPort> mtc_input_port() const { return _mtc_input_port; } + boost::shared_ptr<MidiPort> mtc_output_port() const { return _mtc_output_port; } + boost::shared_ptr<MidiPort> midi_clock_input_port() const { return _midi_clock_input_port; } + boost::shared_ptr<MidiPort> midi_clock_output_port() const { return _midi_clock_output_port; } + + void set_midi_port_states (const XMLNodeList&); + std::list<XMLNode*> get_midi_port_states () const; + + PBD::Signal0<void> PortsChanged; + + protected: + /* asynchronously handled ports: MIDI::Port */ + MIDI::Port* _midi_input_port; + MIDI::Port* _midi_output_port; + MIDI::Port* _mmc_input_port; + MIDI::Port* _mmc_output_port; + /* these point to the same objects as the 4 members above, + but cast to their ARDOUR::Port base class + */ + boost::shared_ptr<Port> _midi_in; + boost::shared_ptr<Port> _midi_out; + boost::shared_ptr<Port> _mmc_in; + boost::shared_ptr<Port> _mmc_out; + + /* synchronously handled ports: ARDOUR::MidiPort */ + boost::shared_ptr<MidiPort> _mtc_input_port; + boost::shared_ptr<MidiPort> _mtc_output_port; + boost::shared_ptr<MidiPort> _midi_clock_input_port; + boost::shared_ptr<MidiPort> _midi_clock_output_port; + + void create_ports (); + +}; + +} // namespace MIDI + +#endif // __midi_port_manager_h__ diff --git a/libs/ardour/ardour/port.h b/libs/ardour/ardour/port.h index e225117d94..77bf2b6f71 100644 --- a/libs/ardour/ardour/port.h +++ b/libs/ardour/ardour/port.h @@ -30,6 +30,7 @@ #include "pbd/signals.h" #include "ardour/data_type.h" +#include "ardour/port_engine.h" #include "ardour/types.h" namespace ARDOUR { @@ -40,11 +41,6 @@ class Buffer; class Port : public boost::noncopyable { public: - enum Flags { - IsInput = JackPortIsInput, - IsOutput = JackPortIsOutput, - }; - virtual ~Port (); static void set_connecting_blocked( bool yn ) { @@ -62,7 +58,7 @@ public: int set_name (std::string const &); /** @return flags */ - Flags flags () const { + PortFlags flags () const { return _flags; } @@ -90,24 +86,24 @@ public: virtual int connect (Port *); int disconnect (Port *); - void ensure_jack_monitors_input (bool); - bool jack_monitoring_input () const; + void request_input_monitoring (bool); + void ensure_input_monitoring (bool); + bool monitoring_input () const; int reestablish (); int reconnect (); - void request_jack_monitors_input (bool); bool last_monitor() const { return _last_monitor; } void set_last_monitor (bool yn) { _last_monitor = yn; } - jack_port_t* jack_port() const { return _jack_port; } + PortEngine::PortHandle port_handle() { return _port_handle; } - void get_connected_latency_range (jack_latency_range_t& range, bool playback) const; + void get_connected_latency_range (LatencyRange& range, bool playback) const; - void set_private_latency_range (jack_latency_range_t& range, bool playback); - const jack_latency_range_t& private_latency_range (bool playback) const; + void set_private_latency_range (LatencyRange& range, bool playback); + const LatencyRange& private_latency_range (bool playback) const; - void set_public_latency_range (jack_latency_range_t& range, bool playback) const; - jack_latency_range_t public_latency_range (bool playback) const; + void set_public_latency_range (LatencyRange& range, bool playback) const; + LatencyRange public_latency_range (bool playback) const; virtual void reset (); @@ -122,8 +118,6 @@ public: bool physically_connected () const; - static void set_engine (AudioEngine *); - PBD::Signal1<void,bool> MonitorInputChanged; static PBD::Signal2<void,boost::shared_ptr<Port>,boost::shared_ptr<Port> > PostDisconnect; static PBD::Signal0<void> PortDrop; @@ -141,11 +135,16 @@ public: virtual void increment_port_buffer_offset (pframes_t n); + virtual XMLNode& get_state (void) const; + virtual int set_state (const XMLNode&, int version); + + static std::string state_node_name; + protected: - Port (std::string const &, DataType, Flags); + Port (std::string const &, DataType, PortFlags); - jack_port_t* _jack_port; ///< JACK port + PortEngine::PortHandle _port_handle; static bool _connecting_blocked; static pframes_t _global_port_buffer_offset; /* access only from process() tree */ @@ -153,18 +152,16 @@ protected: framecnt_t _port_buffer_offset; /* access only from process() tree */ - jack_latency_range_t _private_playback_latency; - jack_latency_range_t _private_capture_latency; - - static AudioEngine* _engine; ///< the AudioEngine + LatencyRange _private_playback_latency; + LatencyRange _private_capture_latency; private: std::string _name; ///< port short name - Flags _flags; ///< flags + PortFlags _flags; ///< flags bool _last_monitor; /** ports that we are connected to, kept so that we can - reconnect to JACK when required + reconnect to the backend when required */ std::set<std::string> _connections; diff --git a/libs/ardour/ardour/port_engine.h b/libs/ardour/ardour/port_engine.h new file mode 100644 index 0000000000..71f93ea05e --- /dev/null +++ b/libs/ardour/ardour/port_engine.h @@ -0,0 +1,345 @@ +/* + Copyright (C) 2013 Paul Davis + + 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_port_engine_h__ +#define __libardour_port_engine_h__ + +#include <vector> +#include <string> + +#include <stdint.h> + +#include "ardour/data_type.h" +#include "ardour/types.h" + +namespace ARDOUR { + +class PortManager; + +/** PortEngine is an abstract base class that defines the functionality + * required by Ardour. + * + * A Port is basically an endpoint for a datastream (which can either be + * continuous, like audio, or event-based, like MIDI). Ports have buffers + * associated with them into which data can be written (if they are output + * ports) and from which data can be read (if they input ports). Ports can be + * connected together so that data written to an output port can be read from + * an input port. These connections can be 1:1, 1:N OR N:1. + * + * Ports may be associated with software only, or with hardware. Hardware + * related ports are often referred to as physical, and correspond to some + * relevant physical entity on a hardware device, such as an audio jack or a + * MIDI connector. Physical ports may be potentially asked to monitor their + * inputs, though some implementations may not support this. + * + * Most physical ports will also be considered "terminal", which means that + * data delivered there or read from there will go to or comes from a system + * outside of the PortEngine implementation's control (e.g. the analog domain + * for audio, or external MIDI devices for MIDI). Non-physical ports can also + * be considered "terminal". For example, the output port of a software + * synthesizer is a terminal port, because the data contained in its buffer + * does not and cannot be considered to come from any other port - it is + * synthesized by its owner. + * + * Ports also have latency associated with them. Each port has a playback + * latency and a capture latency: + * + * <b>capture latency</b>: how long since the data read from the buffer of a + * port arrived at at a terminal port. The data will have + * come from the "outside world" if the terminal port is also + * physical, or will have been synthesized by the entity that + * owns the terminal port. + * + * <b>playback latency</b>: how long until the data written to the buffer of + * port will reach a terminal port. + * + * + * For more detailed questions about the PortEngine API, consult the JACK API + * documentation, on which this entire object is based. + */ + +class PortEngine { + public: + PortEngine (PortManager& pm) : manager (pm) {} + virtual ~PortEngine() {} + + /* We use void* here so that the API can be defined for any implementation. + * + * We could theoretically use a template (PortEngine<T>) and define + * PortHandle as T, but this complicates the desired inheritance + * pattern in which FooPortEngine handles things for the Foo API, + * rather than being a derivative of PortEngine<Foo>. + */ + + typedef void* PortHandle; + + /** Return a typeless pointer to an object that may be of interest + * that understands the internals of a particular PortEngine + * implementation. + * + * XXX the existence of this method is a band-aid over some design + * issues and will it will be removed in the future + */ + virtual void* private_handle() const = 0; + + virtual bool connected() const = 0; + + /** Return the name of this process as used by the port manager + * when naming ports. + */ + virtual const std::string& my_name() const = 0; + + /** Return the maximum size of a port name + */ + virtual uint32_t port_name_size() const = 0; + + /** Returns zero if the port referred to by @param port was set to @param + * name. Return non-zero otherwise. + */ + virtual int set_port_name (PortHandle port, const std::string& name) = 0; + /** Return the name of the port referred to by @param port. If the port + * does not exist, return an empty string. + */ + virtual std::string get_port_name (PortHandle) const = 0; + /** Return a reference to a port with the fullname @param name. Return + * a null pointer if no such port exists. + */ + virtual PortHandle* get_port_by_name (const std::string&) const = 0; + + /** Find the set of ports whose names, types and flags match + * specified values, place the names of each port into @param ports, + * and return the count of the number found. + * + * To avoid selecting by name, pass an empty string for @param + * port_name_pattern. + * + * To avoid selecting by type, pass DataType::NIL as @param type. + * + * To avoid selecting by flags, pass PortFlags (0) as @param flags. + */ + virtual int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>& ports) const = 0; + + /** Return the Ardour data type handled by the port referred to by @param + * port. Returns DataType::NIL if the port does not exist. + */ + virtual DataType port_data_type (PortHandle port) const = 0; + + /** Create a new port whose fullname will be the conjuction of my_name(), + * ":" and @param shortname. The port will handle data specified by @param + * type and will have the flags given by @param flags. If successfull, + * return a reference to the port, otherwise return a null pointer. + */ + virtual PortHandle register_port (const std::string& shortname, ARDOUR::DataType type, ARDOUR::PortFlags flags) = 0; + + /* Destroy the port referred to by @param port, including all resources + * associated with it. This will also disconnect @param port from any ports it + * is connected to. + */ + virtual void unregister_port (PortHandle) = 0; + + /* Connection management */ + + /** Ensure that data written to the port named by @param src will be + * readable from the port named by @param dst. Return zero on success, + * non-zero otherwise. + */ + virtual int connect (const std::string& src, const std::string& dst) = 0; + + /** Remove any existing connection between the ports named by @param src and + * @param dst. Return zero on success, non-zero otherwise. + */ + virtual int disconnect (const std::string& src, const std::string& dst) = 0; + + + /** Ensure that data written to the port referenced by @param portwill be + * readable from the port named by @param dst. Return zero on success, + * non-zero otherwise. + */ + virtual int connect (PortHandle src, const std::string& dst) = 0; + /** Remove any existing connection between the port referenced by @param src and + * the port named @param dst. Return zero on success, non-zero otherwise. + */ + virtual int disconnect (PortHandle src, const std::string& dst) = 0; + + /** Remove all connections between the port referred to by @param port and + * any other ports. Return zero on success, non-zero otherwise. + */ + virtual int disconnect_all (PortHandle port) = 0; + + /** Return true if the port referred to by @param port has any connections + * to other ports. Return false otherwise. + */ + virtual bool connected (PortHandle port, bool process_callback_safe = true) = 0; + /** Return true if the port referred to by @param port is connected to + * the port named by @param name. Return false otherwise. + */ + virtual bool connected_to (PortHandle, const std::string& name, bool process_callback_safe = true) = 0; + + /** Return true if the port referred to by @param port has any connections + * to ports marked with the PortFlag IsPhysical. Return false otherwise. + */ + virtual bool physically_connected (PortHandle port, bool process_callback_safe = true) = 0; + + /** Place the names of all ports connected to the port named by @param + * ports into @param names, and return the number of connections. + */ + virtual int get_connections (PortHandle port, std::vector<std::string>& names, bool process_callback_safe = true) = 0; + + /* MIDI */ + + /** Retrieve a MIDI event from the data at @param port_buffer. The event + number to be retrieved is given by @param event_index (a value of zero + indicates that the first event in the port_buffer should be retrieved). + * + * The data associated with the event will be copied into the buffer at + * @param buf and the number of bytes written will be stored in @param + * size. The timestamp of the event (which is always relative to the start + * of the current process cycle, in samples) will be stored in @param + * timestamp + */ + virtual int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index) = 0; + + /** Place a MIDI event consisting of @param size bytes copied from the data + * at @param buf into the port buffer referred to by @param + * port_buffer. The MIDI event will be marked with a time given by @param + * timestamp. Return zero on success, non-zero otherwise. + * + * Events must be added monotonically to a port buffer. An attempt to + * add a non-monotonic event (e.g. out-of-order) will cause this method + * to return a failure status. + */ + virtual int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size) = 0; + + /** Return the number of MIDI events in the data at @param port_buffer + */ + virtual uint32_t get_midi_event_count (void* port_buffer) = 0; + + /** Clear the buffer at @param port_buffer of all MIDI events. + * + * After a call to this method, an immediate, subsequent call to + * get_midi_event_count() with the same @param port_buffer argument must + * return zero. + */ + virtual void midi_clear (void* port_buffer) = 0; + + /* Monitoring */ + + /** Return true if the implementation can offer input monitoring. + * + * Input monitoring involves the (selective) routing of incoming data + * to an outgoing data stream, without the data being passed to the CPU. + * + * Only certain audio hardware can provide this, and only certain audio + * APIs can offer it. + */ + virtual bool can_monitor_input() const = 0; + /** Increment or decrement the number of requests to monitor the input + * of the hardware channel represented by the port referred to by @param + * port. + * + * If the number of requests rises above zero, input monitoring will + * be enabled (if can_monitor_input() returns true for the implementation). + * + * If the number of requests falls to zero, input monitoring will be + * disabled (if can_monitor_input() returns true for the implementation) + */ + virtual int request_input_monitoring (PortHandle port, bool yn) = 0; + /* Force input monitoring of the hardware channel represented by the port + * referred to by @param port to be on or off, depending on the true/false + * status of @param yn. The request count is ignored when using this + * method, so if this is called with yn set to false, input monitoring will + * be disabled regardless of the number of requests to enable it. + */ + virtual int ensure_input_monitoring (PortHandle port, bool yn) = 0; + /** Return true if input monitoring is enabled for the hardware channel + * represented by the port referred to by @param port. Return false + * otherwise. + */ + virtual bool monitoring_input (PortHandle port) = 0; + + /* Latency management + */ + + /** Set the latency range for the port referred to by @param port to @param + * r. The playback range will be set if @param for_playback is true, + * otherwise the capture range will be set. + */ + virtual void set_latency_range (PortHandle port, bool for_playback, LatencyRange r) = 0; + /** Return the latency range for the port referred to by @param port. + * The playback range will be returned if @param for_playback is true, + * otherwise the capture range will be returned. + */ + virtual LatencyRange get_latency_range (PortHandle port, bool for_playback) = 0; + + /* Discovering physical ports */ + + /** Return true if the port referred to by @param port has the IsPhysical + * flag set. Return false otherwise. + */ + virtual bool port_is_physical (PortHandle port) const = 0; + + /** Store into @param names the names of all ports with the IsOutput and + * IsPhysical flag set, that handle data of type @param type. + * + * This can be used to discover outputs associated with hardware devices. + */ + virtual void get_physical_outputs (DataType type, std::vector<std::string>& names) = 0; + /** Store into @param names the names of all ports with the IsInput and + * IsPhysical flags set, that handle data of type @param type. + * + * This can be used to discover inputs associated with hardware devices. + */ + virtual void get_physical_inputs (DataType type, std::vector<std::string>& names) = 0; + /** Return the total count (possibly mixed between different data types) + of the number of ports with the IsPhysical and IsOutput flags set. + */ + virtual ChanCount n_physical_outputs () const = 0; + /** Return the total count (possibly mixed between different data types) + of the number of ports with the IsPhysical and IsInput flags set. + */ + virtual ChanCount n_physical_inputs () const = 0; + + /** Return the address of the memory area where data for the port can be + * written (if the port has the PortFlag IsOutput set) or read (if the port + * has the PortFlag IsInput set). + * + * The return value is untyped because buffers containing different data + * depending on the port type. + */ + virtual void* get_buffer (PortHandle, pframes_t) = 0; + + /* MIDI ports (the ones in libmidi++) need this to be able to correctly + * schedule MIDI events within their buffers. It is a bit odd that we + * expose this here, because it is also exposed by AudioBackend, but they + * only have access to a PortEngine object, not an AudioBackend. + * + * Return the time according to the sample clock in use when the current + * buffer process cycle began. + * + * XXX to be removed after some more design cleanup. + */ + virtual pframes_t sample_time_at_cycle_start () = 0; + + protected: + PortManager& manager; +}; + +} + +#endif /* __libardour_port_engine_h__ */ diff --git a/libs/ardour/ardour/port_manager.h b/libs/ardour/ardour/port_manager.h new file mode 100644 index 0000000000..6d45597a41 --- /dev/null +++ b/libs/ardour/ardour/port_manager.h @@ -0,0 +1,169 @@ +/* + Copyright (C) 2013 Paul Davis + + 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_port_manager_h__ +#define __libardour_port_manager_h__ + +#include <vector> +#include <string> +#include <exception> +#include <map> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include "pbd/rcu.h" + +#include "ardour/chan_count.h" +#include "ardour/midiport_manager.h" +#include "ardour/port.h" +#include "ardour/port_engine.h" + +namespace ARDOUR { + +class PortManager +{ + public: + typedef std::map<std::string,boost::shared_ptr<Port> > Ports; + typedef std::list<boost::shared_ptr<Port> > PortList; + + PortManager (); + virtual ~PortManager() {} + + void set_port_engine (PortEngine& pe); + PortEngine& port_engine() { return *_impl; } + + uint32_t port_name_size() const; + std::string my_name() const; + + /* Port registration */ + + boost::shared_ptr<Port> register_input_port (DataType, const std::string& portname, bool async = false); + boost::shared_ptr<Port> register_output_port (DataType, const std::string& portname, bool async = false); + int unregister_port (boost::shared_ptr<Port>); + + /* Port connectivity */ + + int connect (const std::string& source, const std::string& destination); + int disconnect (const std::string& source, const std::string& destination); + int disconnect (boost::shared_ptr<Port>); + int reestablish_ports (); + int reconnect_ports (); + + bool connected (const std::string&); + bool connected_to (const std::string&, const std::string&); + bool physically_connected (const std::string&); + int get_connections (const std::string&, std::vector<std::string>&); + + /* Naming */ + + boost::shared_ptr<Port> get_port_by_name (const std::string &); + void port_renamed (const std::string&, const std::string&); + std::string make_port_name_relative (const std::string& name) const; + std::string make_port_name_non_relative (const std::string& name) const; + bool port_is_mine (const std::string& fullname) const; + + /* other Port management */ + + bool port_is_physical (const std::string&) const; + void get_physical_outputs (DataType type, std::vector<std::string>&); + void get_physical_inputs (DataType type, std::vector<std::string>&); + ChanCount n_physical_outputs () const; + ChanCount n_physical_inputs () const; + + int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>&); + int get_ports (DataType, PortList&); + + void remove_all_ports (); + + /* per-Port monitoring */ + + bool can_request_input_monitoring () const; + void request_input_monitoring (const std::string&, bool) const; + void ensure_input_monitoring (const std::string&, bool) const; + + class PortRegistrationFailure : public std::exception { + public: + PortRegistrationFailure (std::string const & why = "") + : reason (why) {} + + ~PortRegistrationFailure () throw () {} + + const char *what() const throw () { return reason.c_str(); } + + private: + std::string reason; + }; + + /* the port engine will invoke these callbacks when the time is right */ + + void registration_callback (); + int graph_order_callback (); + void connect_callback (const std::string&, const std::string&, bool connection); + + bool port_remove_in_progress() const { return _port_remove_in_progress; } + + /** Emitted if the backend notifies us of a graph order event */ + PBD::Signal0<void> GraphReordered; + + /** Emitted if a Port is registered or unregistered */ + PBD::Signal0<void> PortRegisteredOrUnregistered; + + /** Emitted if a Port is connected or disconnected. + * The Port parameters are the ports being connected / disconnected, or 0 if they are not known to Ardour. + * The std::string parameters are the (long) port names. + * The bool parameter is true if ports were connected, or false for disconnected. + */ + PBD::Signal5<void, boost::weak_ptr<Port>, std::string, boost::weak_ptr<Port>, std::string, bool> PortConnectedOrDisconnected; + + protected: + boost::shared_ptr<PortEngine> _impl; + SerializedRCUManager<Ports> ports; + bool _port_remove_in_progress; + + boost::shared_ptr<Port> register_port (DataType type, const std::string& portname, bool input, bool async = false); + void port_registration_failure (const std::string& portname); + + /** List of ports to be used between ::cycle_start() and ::cycle_end() + */ + boost::shared_ptr<Ports> _cycle_ports; + + void fade_out (gain_t, gain_t, pframes_t); + void silence (pframes_t nframes); + void check_monitoring (); + /** Signal the start of an audio cycle. + * This MUST be called before any reading/writing for this cycle. + * Realtime safe. + */ + void cycle_start (pframes_t nframes); + + /** Signal the end of an audio cycle. + * This signifies that the cycle began with @ref cycle_start has ended. + * This MUST be called at the end of each cycle. + * Realtime safe. + */ + void cycle_end (pframes_t nframes); +}; + + + +} // namespace + +#endif /* __libardour_port_manager_h__ */ diff --git a/libs/ardour/ardour/public_diskstream.h b/libs/ardour/ardour/public_diskstream.h index da481a6dee..125e1a21ce 100644 --- a/libs/ardour/ardour/public_diskstream.h +++ b/libs/ardour/ardour/public_diskstream.h @@ -33,8 +33,8 @@ public: virtual ~PublicDiskstream() {} virtual boost::shared_ptr<Playlist> playlist () = 0; - virtual void request_jack_monitors_input (bool) = 0; - virtual void ensure_jack_monitors_input (bool) = 0; + virtual void request_input_monitoring (bool) = 0; + virtual void ensure_input_monitoring (bool) = 0; virtual bool destructive () const = 0; virtual std::list<boost::shared_ptr<Source> > & last_capture_sources () = 0; virtual void set_capture_offset () = 0; diff --git a/libs/ardour/ardour/rc_configuration.h b/libs/ardour/ardour/rc_configuration.h index ff1c5f035c..e2f68477a1 100644 --- a/libs/ardour/ardour/rc_configuration.h +++ b/libs/ardour/ardour/rc_configuration.h @@ -52,7 +52,6 @@ class RCConfiguration : public Configuration XMLNode * instant_xml (const std::string& str); XMLNode* control_protocol_state () { return _control_protocol_state; } - std::list<XMLNode*> midi_port_states () { return _midi_port_states; } /* define accessor methods */ @@ -81,11 +80,6 @@ class RCConfiguration : public Configuration #undef CONFIG_VARIABLE_SPECIAL XMLNode* _control_protocol_state; - - /** MIDI port nodes from the RC configuration. We store them so that we can set their - state once the audio engine and hence ports are up. - */ - std::list<XMLNode*> _midi_port_states; }; /* XXX: rename this */ diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 525dfbd849..43660d41cc 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -110,6 +110,8 @@ class IOProcessor; class ImportStatus; class MidiClockTicker; class MidiControlUI; +class MidiPortManager; +class MidiPort; class MidiRegion; class MidiSource; class MidiTrack; @@ -381,9 +383,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi framecnt_t worst_track_latency () const { return _worst_track_latency; } framecnt_t worst_playback_latency () const { return _worst_output_latency + _worst_track_latency; } -#ifdef HAVE_JACK_SESSION - void jack_session_event (jack_session_event_t* event); -#endif int save_state (std::string snapshot_name, bool pending = false, bool switch_to_snapshot = false); int restore_state (std::string snapshot_name); int save_template (std::string template_name); @@ -556,6 +555,11 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi int remove_last_capture (); + /** handlers should return 0 for "everything OK", and any other value for + * "cannot setup audioengine". + */ + static PBD::Signal1<int,uint32_t> AudioEngineSetupRequired; + /** handlers should return -1 for "stop cleanup", 0 for "yes, delete this playlist", 1 for "no, don't delete this playlist". @@ -813,8 +817,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr<SessionPlaylists> playlists; void send_mmc_locate (framepos_t); - int send_full_time_code (framepos_t); - void send_song_position_pointer (framepos_t); + void queue_full_time_code () { _send_timecode_update = true; } + void queue_song_position_pointer () { /* currently does nothing */ } bool step_editing() const { return (_step_editors > 0); } @@ -863,6 +867,27 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr<IO> ltc_input_io() { return _ltc_input; } boost::shared_ptr<IO> ltc_output_io() { return _ltc_output; } + MIDI::Port* midi_input_port () const; + MIDI::Port* midi_output_port () const; + MIDI::Port* mmc_output_port () const; + MIDI::Port* mmc_input_port () const; + + boost::shared_ptr<MidiPort> midi_clock_output_port () const; + boost::shared_ptr<MidiPort> midi_clock_input_port () const; + boost::shared_ptr<MidiPort> mtc_output_port () const; + boost::shared_ptr<MidiPort> mtc_input_port () const; + + MIDI::MachineControl& mmc() { return *_mmc; } + + /* Callbacks specifically related to JACK, and called directly + * from the JACK audio backend. + */ + +#ifdef HAVE_JACK_SESSION + void jack_session_event (jack_session_event_t* event); +#endif + void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); + protected: friend class AudioEngine; void set_block_size (pframes_t nframes); @@ -1046,7 +1071,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::scoped_ptr<SessionDirectory> _session_dir; void hookup_io (); - void when_engine_running (); + int when_engine_running (); void graph_reordered (); /** current snapshot name, without the .ardour suffix */ @@ -1112,8 +1137,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void auto_loop_changed (Location *); void auto_loop_declick_range (Location *, framepos_t &, framepos_t &); - void first_stage_init (std::string path, std::string snapshot_name); - int second_stage_init (); + void pre_engine_init (std::string path); + int post_engine_init (); void remove_empty_sounds (); void setup_midi_control (); @@ -1216,7 +1241,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi framepos_t ltc_timecode_offset; bool ltc_timecode_negative_offset; - jack_latency_range_t ltc_out_latency; + LatencyRange ltc_out_latency; void ltc_tx_initialize(); void ltc_tx_cleanup(); @@ -1261,6 +1286,13 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void engine_halted (); void xrun_recovery (); + /* These are synchronous and so can only be called from within the process + * cycle + */ + + int send_full_time_code (framepos_t, pframes_t nframes); + void send_song_position_pointer (framepos_t); + TempoMap *_tempo_map; void tempo_map_changed (const PBD::PropertyChange&); @@ -1429,9 +1461,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi */ std::list<GQuark> _current_trans_quarks; - void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); - int jack_sync_callback (jack_transport_state_t, jack_position_t*); - void reset_jack_connection (jack_client_t* jack); + int backend_sync_callback (TransportState, framepos_t); + void process_rtop (SessionEvent*); void update_latency (bool playback); @@ -1585,6 +1616,14 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi void reconnect_ltc_input (); void reconnect_ltc_output (); + + /* persistent, non-track related MIDI ports */ + MidiPortManager* _midi_ports; + MIDI::MachineControl* _mmc; + + void setup_ltc (); + void setup_click (); + void setup_bundles (); }; } // namespace ARDOUR diff --git a/libs/ardour/ardour/slave.h b/libs/ardour/ardour/slave.h index 9862824da9..c60acb1df7 100644 --- a/libs/ardour/ardour/slave.h +++ b/libs/ardour/ardour/slave.h @@ -40,14 +40,12 @@ #define PLUSMINUS(A) ( ((A)<0) ? "-" : (((A)>0) ? "+" : "\u00B1") ) #define LEADINGZERO(A) ( (A)<10 ? " " : (A)<100 ? " " : (A)<1000 ? " " : "" ) -namespace MIDI { - class Port; -} - namespace ARDOUR { class TempoMap; class Session; +class AudioEngine; +class MidiPort; /** * @class Slave @@ -252,10 +250,10 @@ class TimecodeSlave : public Slave { class MTC_Slave : public TimecodeSlave { public: - MTC_Slave (Session&, MIDI::Port&); + MTC_Slave (Session&, MidiPort&); ~MTC_Slave (); - void rebind (MIDI::Port&); + void rebind (MidiPort&); bool speed_and_position (double&, framepos_t&); bool locked() const; @@ -273,7 +271,7 @@ class MTC_Slave : public TimecodeSlave { private: Session& session; - MIDI::Port* port; + MidiPort* port; PBD::ScopedConnectionList port_connections; PBD::ScopedConnection config_connection; bool can_notify_on_unknown_rate; @@ -354,7 +352,7 @@ public: std::string approximate_current_delta() const; private: - void parse_ltc(const jack_nframes_t, const jack_default_audio_sample_t * const, const framecnt_t); + void parse_ltc(const pframes_t, const Sample* const, const framecnt_t); void process_ltc(framepos_t const); void init_engine_dll (framepos_t, int32_t); bool detect_discontinuity(LTCFrameExt *, int, bool); @@ -391,7 +389,7 @@ public: PBD::ScopedConnectionList port_connections; PBD::ScopedConnection config_connection; - jack_latency_range_t ltc_slave_latency; + LatencyRange ltc_slave_latency; /* DLL - chase LTC */ int transport_direction; @@ -404,13 +402,13 @@ public: class MIDIClock_Slave : public Slave { public: - MIDIClock_Slave (Session&, MIDI::Port&, int ppqn = 24); + MIDIClock_Slave (Session&, MidiPort&, int ppqn = 24); /// Constructor for unit tests MIDIClock_Slave (ISlaveSessionProxy* session_proxy = 0, int ppqn = 24); ~MIDIClock_Slave (); - void rebind (MIDI::Port&); + void rebind (MidiPort&); bool speed_and_position (double&, framepos_t&); bool locked() const; @@ -426,7 +424,6 @@ class MIDIClock_Slave : public Slave { protected: ISlaveSessionProxy* session; - MIDI::Port* port; PBD::ScopedConnectionList port_connections; /// pulses per quarter note for one MIDI clock frame (default 24) @@ -492,7 +489,7 @@ class MIDIClock_Slave : public Slave { class JACK_Slave : public Slave { public: - JACK_Slave (jack_client_t*); + JACK_Slave (AudioEngine&); ~JACK_Slave (); bool speed_and_position (double& speed, framepos_t& pos); @@ -502,11 +499,10 @@ class JACK_Slave : public Slave bool ok() const; framecnt_t resolution () const { return 1; } bool requires_seekahead () const { return false; } - void reset_client (jack_client_t* jack); bool is_always_synced() const { return true; } private: - jack_client_t* jack; + AudioEngine& engine; double speed; bool _starting; }; diff --git a/libs/ardour/ardour/ticker.h b/libs/ardour/ardour/ticker.h index b6e5376c12..7f0d1987fc 100644 --- a/libs/ardour/ardour/ticker.h +++ b/libs/ardour/ardour/ticker.h @@ -27,17 +27,13 @@ #include "ardour/session_handle.h" -#ifndef TICKER_H_ -#define TICKER_H_ +#ifndef __libardour_ticker_h__ +#define __libardour_ticker_h__ -namespace MIDI { - class Port; -} - -namespace ARDOUR -{ +namespace ARDOUR { class Session; +class MidiPort; class MidiClockTicker : public SessionHandlePtr, boost::noncopyable { @@ -45,7 +41,7 @@ public: MidiClockTicker (); virtual ~MidiClockTicker(); - void tick (const framepos_t& transport_frames); + void tick (const framepos_t& transport_frames, pframes_t nframes); bool has_midi_port() const { return _midi_port != 0; } @@ -58,9 +54,6 @@ public: /// slot for the signal session::TransportStateChange void transport_state_changed(); - /// slot for the signal session::PositionChanged - void position_changed (framepos_t position); - /// slot for the signal session::TransportLooped void transport_looped(); @@ -70,23 +63,25 @@ public: /// pulses per quarter note (default 24) void set_ppqn(int ppqn) { _ppqn = ppqn; } -private: - MIDI::Port* _midi_port; - int _ppqn; - double _last_tick; - - class Position; - boost::scoped_ptr<Position> _pos; - - double one_ppqn_in_frames (framepos_t transport_position); - - void send_midi_clock_event (pframes_t offset); - void send_start_event (pframes_t offset); - void send_continue_event (pframes_t offset); - void send_stop_event (pframes_t offset); - void send_position_event (uint32_t midi_clocks, pframes_t offset); + private: + boost::shared_ptr<MidiPort> _midi_port; + int _ppqn; + double _last_tick; + bool _send_pos; + bool _send_state; + + class Position; + boost::scoped_ptr<Position> _pos; + + double one_ppqn_in_frames (framepos_t transport_position); + + void send_midi_clock_event (pframes_t offset, pframes_t nframes); + void send_start_event (pframes_t offset, pframes_t nframes); + void send_continue_event (pframes_t offset, pframes_t nframes); + void send_stop_event (pframes_t offset, pframes_t nframes); + void send_position_event (uint32_t midi_clocks, pframes_t offset, pframes_t nframes); }; - } + // namespace -#endif /* TICKER_H_ */ +#endif /* __libardour_ticker_h__ */ diff --git a/libs/ardour/ardour/track.h b/libs/ardour/ardour/track.h index 7159261b51..bdf420763b 100644 --- a/libs/ardour/ardour/track.h +++ b/libs/ardour/ardour/track.h @@ -115,8 +115,8 @@ class Track : public Route, public PublicDiskstream /* PublicDiskstream interface */ boost::shared_ptr<Playlist> playlist (); - void request_jack_monitors_input (bool); - void ensure_jack_monitors_input (bool); + void request_input_monitoring (bool); + void ensure_input_monitoring (bool); bool destructive () const; std::list<boost::shared_ptr<Source> > & last_capture_sources (); void set_capture_offset (); diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 2115149872..17bffc20e4 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -28,8 +28,6 @@ #include <stdint.h> #include <inttypes.h> -#include <jack/types.h> -#include <jack/midiport.h> #include "timecode/bbt_time.h" #include "timecode/time.h" @@ -53,12 +51,12 @@ namespace ARDOUR { class Route; class Region; - typedef jack_default_audio_sample_t Sample; - typedef float pan_t; - typedef float gain_t; - typedef uint32_t layer_t; - typedef uint64_t microseconds_t; - typedef jack_nframes_t pframes_t; + typedef float Sample; + typedef float pan_t; + typedef float gain_t; + typedef uint32_t layer_t; + typedef uint64_t microseconds_t; + typedef uint32_t pframes_t; /* Any position measured in audio frames. Assumed to be non-negative but not enforced. @@ -585,6 +583,32 @@ namespace ARDOUR { FadeSymmetric, }; + enum TransportState { + /* these values happen to match the constants used by JACK but + this equality cannot be assumed. + */ + TransportStopped = 0, + TransportRolling = 1, + TransportLooping = 2, + TransportStarting = 3, + }; + + enum PortFlags { + /* these values happen to match the constants used by JACK but + this equality cannot be assumed. + */ + IsInput = 0x1, + IsOutput = 0x2, + IsPhysical = 0x4, + CanMonitor = 0x8, + IsTerminal = 0x10 + }; + + struct LatencyRange { + uint32_t min; //< samples + uint32_t max; //< samples + }; + } // namespace ARDOUR diff --git a/libs/ardour/async_midi_port.cc b/libs/ardour/async_midi_port.cc new file mode 100644 index 0000000000..77ca62961e --- /dev/null +++ b/libs/ardour/async_midi_port.cc @@ -0,0 +1,303 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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. + + $Id$ +*/ + +#include <iostream> + +#include "pbd/error.h" +#include "pbd/stacktrace.h" + +#include "midi++/types.h" + +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" + +using namespace MIDI; +using namespace ARDOUR; +using namespace std; +using namespace PBD; + +namespace Evoral { + template class EventRingBuffer<MIDI::timestamp_t>; +} + +pthread_t AsyncMIDIPort::_process_thread; + +#define port_engine AudioEngine::instance()->port_engine() + +AsyncMIDIPort::AsyncMIDIPort (string const & name, PortFlags flags) + : MidiPort (name, flags) + , MIDI::Port (name, MIDI::Port::Flags (0)) + , _currently_in_cycle (false) + , _last_write_timestamp (0) + , output_fifo (512) + , input_fifo (1024) + , xthread (true) +{ +} + +AsyncMIDIPort::~AsyncMIDIPort () +{ +} + +void +AsyncMIDIPort::flush_output_fifo (pframes_t nframes) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; + size_t written; + + output_fifo.get_read_vector (&vec); + + MidiBuffer& mb (get_midi_buffer (nframes)); + + if (vec.len[0]) { + Evoral::Event<double>* evp = vec.buf[0]; + + for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if (vec.len[1]) { + Evoral::Event<double>* evp = vec.buf[1]; + + for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if ((written = vec.len[0] + vec.len[1]) != 0) { + output_fifo.increment_read_idx (written); + } +} + +void +AsyncMIDIPort::cycle_start (pframes_t nframes) +{ + _currently_in_cycle = true; + MidiPort::cycle_start (nframes); + + /* dump anything waiting in the output FIFO at the start of the port + * buffer + */ + + if (ARDOUR::Port::sends_output()) { + flush_output_fifo (nframes); + } + + /* copy incoming data from the port buffer into the input FIFO + and if necessary wakeup the reader + */ + + if (ARDOUR::Port::receives_input()) { + MidiBuffer& mb (get_midi_buffer (nframes)); + pframes_t when = AudioEngine::instance()->sample_time_at_cycle_start(); + + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + input_fifo.write (when, (Evoral::EventType) 0, (*b).size(), (*b).buffer()); + } + + if (!mb.empty()) { + xthread.wakeup (); + } + } + +} + +void +AsyncMIDIPort::cycle_end (pframes_t nframes) +{ + if (ARDOUR::Port::sends_output()) { + /* move any additional data from output FIFO into the port + buffer. + */ + flush_output_fifo (nframes); + } + + MidiPort::cycle_end (nframes); + + _currently_in_cycle = false; +} + +/** wait for the output FIFO to be emptied by successive process() callbacks. + * + * Cannot be called from a processing thread. + */ +void +AsyncMIDIPort::drain (int check_interval_usecs) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + if (!AudioEngine::instance()->running() || AudioEngine::instance()->session() == 0) { + /* no more process calls - it will never drain */ + return; + } + + + if (is_process_thread()) { + error << "Process thread called MIDI::AsyncMIDIPort::drain() - this cannot work" << endmsg; + return; + } + + while (1) { + output_fifo.get_write_vector (&vec); + if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { + break; + } + usleep (check_interval_usecs); + } +} + +int +AsyncMIDIPort::write (const MIDI::byte * msg, size_t msglen, MIDI::timestamp_t timestamp) +{ + int ret = 0; + + if (!ARDOUR::Port::sends_output()) { + return ret; + } + + if (!is_process_thread()) { + + /* this is the best estimate of "when" this MIDI data is being + * delivered + */ + + _parser->set_timestamp (AudioEngine::instance()->sample_time() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + Glib::Threads::Mutex::Lock lm (output_fifo_lock); + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + output_fifo.get_write_vector (&vec); + + if (vec.len[0] + vec.len[1] < 1) { + error << "no space in FIFO for non-process thread MIDI write" << endmsg; + return 0; + } + + if (vec.len[0]) { + if (!vec.buf[0]->owns_buffer()) { + vec.buf[0]->set_buffer (0, 0, true); + } + vec.buf[0]->set (msg, msglen, timestamp); + } else { + if (!vec.buf[1]->owns_buffer()) { + vec.buf[1]->set_buffer (0, 0, true); + } + vec.buf[1]->set (msg, msglen, timestamp); + } + + output_fifo.increment_write_idx (1); + + ret = msglen; + + } else { + + _parser->set_timestamp (AudioEngine::instance()->sample_time_at_cycle_start() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + if (timestamp >= _cycle_nframes) { + std::cerr << "attempting to write MIDI event of " << msglen << " MIDI::bytes at time " + << timestamp << " of " << _cycle_nframes + << " (this will not work - needs a code fix)" + << std::endl; + } + + /* This is the process thread, which makes checking + * _currently_in_cycle atomic and safe, since it is only + * set from cycle_start() and cycle_end(), also called + * only from the process thread. + */ + + if (_currently_in_cycle) { + + MidiBuffer& mb (get_midi_buffer (_cycle_nframes)); + + if (timestamp == 0) { + timestamp = _last_write_timestamp; + } + + if (mb.push_back (timestamp, msglen, msg)) { + ret = msglen; + _last_write_timestamp = timestamp; + + } else { + cerr << "AsyncMIDIPort (" << ARDOUR::Port::name() << "): write of " << msglen << " @ " << timestamp << " failed\n" << endl; + PBD::stacktrace (cerr, 20); + ret = 0; + } + } else { + cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; + PBD::stacktrace (cerr, 20); + } + } + + return ret; +} + + +int +AsyncMIDIPort::read (MIDI::byte *, size_t) +{ + if (!ARDOUR::Port::receives_input()) { + return 0; + } + + timestamp_t time; + Evoral::EventType type; + uint32_t size; + MIDI::byte buffer[input_fifo.capacity()]; + + while (input_fifo.read (&time, &type, &size, buffer)) { + _parser->set_timestamp (time); + for (uint32_t i = 0; i < size; ++i) { + _parser->scanner (buffer[i]); + } + } + + return 0; +} + +void +AsyncMIDIPort::parse (framecnt_t) +{ + MIDI::byte buf[1]; + + /* see ::read() to realize why buf is not used */ + read (buf, sizeof (buf)); +} + +void +AsyncMIDIPort::set_process_thread (pthread_t thr) +{ + _process_thread = thr; +} + +bool +AsyncMIDIPort::is_process_thread() +{ + return pthread_equal (pthread_self(), _process_thread); +} + diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 622a42edf2..f4d10c2043 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -1752,7 +1752,7 @@ AudioDiskstream::prep_record_enable () if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (!(_session.config.get_auto_input() && rolling)); + (*chan)->source.request_input_monitoring (!(_session.config.get_auto_input() && rolling)); capturing_sources.push_back ((*chan)->write_source); (*chan)->write_source->mark_streaming_write_started (); } @@ -1773,7 +1773,7 @@ AudioDiskstream::prep_record_disable () boost::shared_ptr<ChannelList> c = channels.reader(); if (Config->get_monitoring_model() == HardwareMonitoring) { for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (false); + (*chan)->source.request_input_monitoring (false); } } capturing_sources.clear (); @@ -2039,12 +2039,12 @@ AudioDiskstream::allocate_temporary_buffers () } void -AudioDiskstream::request_jack_monitors_input (bool yn) +AudioDiskstream::request_input_monitoring (bool yn) { boost::shared_ptr<ChannelList> c = channels.reader(); for (ChannelList::iterator chan = c->begin(); chan != c->end(); ++chan) { - (*chan)->source.request_jack_monitors_input (yn); + (*chan)->source.request_input_monitoring (yn); } } @@ -2367,13 +2367,13 @@ AudioDiskstream::ChannelSource::is_physical () const } void -AudioDiskstream::ChannelSource::request_jack_monitors_input (bool yn) const +AudioDiskstream::ChannelSource::request_input_monitoring (bool yn) const { if (name.empty()) { return; } - return AudioEngine::instance()->request_jack_monitors_input (name, yn); + return AudioEngine::instance()->request_input_monitoring (name, yn); } AudioDiskstream::ChannelInfo::ChannelInfo (framecnt_t playback_bufsize, framecnt_t capture_bufsize, framecnt_t speed_size, framecnt_t wrap_size) diff --git a/libs/ardour/audio_port.cc b/libs/ardour/audio_port.cc index 240224ea5e..6a86360b69 100644 --- a/libs/ardour/audio_port.cc +++ b/libs/ardour/audio_port.cc @@ -21,13 +21,17 @@ #include "pbd/stacktrace.h" #include "ardour/audio_buffer.h" +#include "ardour/audioengine.h" #include "ardour/audio_port.h" #include "ardour/data_type.h" +#include "ardour/port_engine.h" using namespace ARDOUR; using namespace std; -AudioPort::AudioPort (const std::string& name, Flags flags) +#define port_engine AudioEngine::instance()->port_engine() + +AudioPort::AudioPort (const std::string& name, PortFlags flags) : Port (name, DataType::AUDIO, flags) , _buffer (new AudioBuffer (0)) { @@ -73,7 +77,7 @@ AudioBuffer& AudioPort::get_audio_buffer (pframes_t nframes) { /* caller must hold process lock */ - _buffer->set_data ((Sample *) jack_port_get_buffer (_jack_port, _cycle_nframes) + + _buffer->set_data ((Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes) + _global_port_buffer_offset + _port_buffer_offset, nframes); return *_buffer; } @@ -82,7 +86,7 @@ Sample* AudioPort::engine_get_whole_audio_buffer () { /* caller must hold process lock */ - return (Sample *) jack_port_get_buffer (_jack_port, _cycle_nframes); + return (Sample *) port_engine.get_buffer (_port_handle, _cycle_nframes); } diff --git a/libs/ardour/audio_track.cc b/libs/ardour/audio_track.cc index 0530dbfce9..a82c109cba 100644 --- a/libs/ardour/audio_track.cc +++ b/libs/ardour/audio_track.cc @@ -96,7 +96,7 @@ AudioTrack::set_diskstream (boost::shared_ptr<Diskstream> ds) } _diskstream->set_record_enabled (false); - _diskstream->request_jack_monitors_input (false); + _diskstream->request_input_monitoring (false); DiskstreamChanged (); /* EMIT SIGNAL */ } diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 5e3e5ba9e2..0ff8cd89ef 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -25,24 +25,30 @@ #include <sstream> #include <glibmm/timer.h> +#include <glibmm/pattern.h> +#include <glibmm/module.h> +#include "pbd/epa.h" +#include "pbd/file_utils.h" #include "pbd/pthread_utils.h" #include "pbd/stacktrace.h" #include "pbd/unknown_type.h" -#include "pbd/epa.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" -#include "midi++/manager.h" +#include "ardour/async_midi_port.h" #include "ardour/audio_port.h" +#include "ardour/audio_backend.h" #include "ardour/audioengine.h" +#include "ardour/backend_search_path.h" #include "ardour/buffer.h" #include "ardour/cycle_timer.h" #include "ardour/internal_send.h" #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" @@ -56,60 +62,50 @@ using namespace PBD; gint AudioEngine::m_meter_exit; AudioEngine* AudioEngine::_instance = 0; -#define GET_PRIVATE_JACK_POINTER(j) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return; } -#define GET_PRIVATE_JACK_POINTER_RET(j,r) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return r; } - -AudioEngine::AudioEngine (string client_name, string session_uuid) - : _jack (0) - , session_remove_pending (false) +AudioEngine::AudioEngine () + : session_remove_pending (false) , session_removal_countdown (-1) , _running (false) - , _has_run (false) - , _buffer_size (0) - , _frame_rate (0) + , _freewheeling (false) , monitor_check_interval (INT32_MAX) , last_monitor_check (0) , _processed_frames (0) - , _freewheeling (false) - , _pre_freewheel_mmc_enabled (false) - , _usecs_per_cycle (0) - , port_remove_in_progress (false) , m_meter_thread (0) , _main_thread (0) - , ports (new Ports) + , _mtdm (0) + , _measuring_latency (false) + , _latency_input_port (0) + , _latency_output_port (0) + , _latency_flush_frames (0) + , _latency_signal_latency (0) { - _instance = this; /* singleton */ - g_atomic_int_set (&m_meter_exit, 0); - - if (connect_to_jack (client_name, session_uuid)) { - throw NoBackendAvailable (); - } - - Port::set_engine (this); + discover_backends (); } AudioEngine::~AudioEngine () { + drop_backend (); + config_connection.disconnect (); { Glib::Threads::Mutex::Lock tm (_process_lock); session_removed.signal (); - - if (_running) { - jack_client_close (_jack); - _jack = 0; - } - stop_metering_thread (); } } -jack_client_t* -AudioEngine::jack() const +AudioEngine* +AudioEngine::create () { - return _jack; + if (_instance) { + return _instance; + } + + _instance = new AudioEngine (); + + return _instance; } void @@ -126,290 +122,7 @@ _thread_init_callback (void * /*arg*/) SessionEvent::create_per_thread_pool (X_("Audioengine"), 512); - MIDI::JackMIDIPort::set_process_thread (pthread_self()); -} - -static void -ardour_jack_error (const char* msg) -{ - error << "JACK: " << msg << endmsg; -} - -void -AudioEngine::set_jack_callbacks () -{ - GET_PRIVATE_JACK_POINTER (_jack); - - if (jack_on_info_shutdown) { - jack_on_info_shutdown (_priv_jack, halted_info, this); - } else { - jack_on_shutdown (_priv_jack, halted, this); - } - - jack_set_thread_init_callback (_priv_jack, _thread_init_callback, this); - jack_set_process_thread (_priv_jack, _process_thread, this); - jack_set_sample_rate_callback (_priv_jack, _sample_rate_callback, this); - jack_set_buffer_size_callback (_priv_jack, _bufsize_callback, this); - jack_set_graph_order_callback (_priv_jack, _graph_order_callback, this); - jack_set_port_registration_callback (_priv_jack, _registration_callback, this); - jack_set_port_connect_callback (_priv_jack, _connect_callback, this); - jack_set_xrun_callback (_priv_jack, _xrun_callback, this); - jack_set_sync_callback (_priv_jack, _jack_sync_callback, this); - jack_set_freewheel_callback (_priv_jack, _freewheel_callback, this); - - if (_session && _session->config.get_jack_time_master()) { - jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); - } - -#ifdef HAVE_JACK_SESSION - if( jack_set_session_callback) - jack_set_session_callback (_priv_jack, _session_callback, this); -#endif - - if (jack_set_latency_callback) { - jack_set_latency_callback (_priv_jack, _latency_callback, this); - } - - jack_set_error_function (ardour_jack_error); -} - -int -AudioEngine::start () -{ - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (!_running) { - - if (!jack_port_type_get_buffer_size) { - warning << _("This version of JACK is old - you should upgrade to a newer version that supports jack_port_type_get_buffer_size()") << endmsg; - } - - if (_session) { - BootMessage (_("Connect session to engine")); - _session->set_frame_rate (jack_get_sample_rate (_priv_jack)); - } - - /* a proxy for whether jack_activate() will definitely call the buffer size - * callback. with older versions of JACK, this function symbol will be null. - * this is reliable, but not clean. - */ - - if (!jack_port_type_get_buffer_size) { - jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); - } - - _processed_frames = 0; - last_monitor_check = 0; - - set_jack_callbacks (); - - if (jack_activate (_priv_jack) == 0) { - _running = true; - _has_run = true; - Running(); /* EMIT SIGNAL */ - } else { - // error << _("cannot activate JACK client") << endmsg; - } - } - - return _running ? 0 : -1; -} - -int -AudioEngine::stop (bool forever) -{ - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (_priv_jack) { - if (forever) { - disconnect_from_jack (); - } else { - jack_deactivate (_priv_jack); - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - Stopped(); /* EMIT SIGNAL */ - } - } - - if (forever) { - stop_metering_thread (); - } - - return _running ? -1 : 0; -} - - -bool -AudioEngine::get_sync_offset (pframes_t& offset) const -{ - -#ifdef HAVE_JACK_VIDEO_SUPPORT - - GET_PRIVATE_JACK_POINTER_RET (_jack, false); - - jack_position_t pos; - - if (_priv_jack) { - (void) jack_transport_query (_priv_jack, &pos); - - if (pos.valid & JackVideoFrameOffset) { - offset = pos.video_offset; - return true; - } - } -#else - /* keep gcc happy */ - offset = 0; -#endif - - return false; -} - -void -AudioEngine::_jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, - jack_position_t* pos, int new_position, void *arg) -{ - static_cast<AudioEngine*> (arg)->jack_timebase_callback (state, nframes, pos, new_position); -} - -void -AudioEngine::jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, - jack_position_t* pos, int new_position) -{ - if (_jack && _session && _session->synced_to_jack()) { - _session->jack_timebase_callback (state, nframes, pos, new_position); - } -} - -int -AudioEngine::_jack_sync_callback (jack_transport_state_t state, jack_position_t* pos, void* arg) -{ - return static_cast<AudioEngine*> (arg)->jack_sync_callback (state, pos); -} - -int -AudioEngine::jack_sync_callback (jack_transport_state_t state, jack_position_t* pos) -{ - if (_jack && _session) { - return _session->jack_sync_callback (state, pos); - } - - return true; -} - -int -AudioEngine::_xrun_callback (void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - if (ae->connected()) { - ae->Xrun (); /* EMIT SIGNAL */ - } - return 0; -} - -#ifdef HAVE_JACK_SESSION -void -AudioEngine::_session_callback (jack_session_event_t *event, void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - if (ae->connected()) { - ae->JackSessionEvent ( event ); /* EMIT SIGNAL */ - } -} -#endif - -int -AudioEngine::_graph_order_callback (void *arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - - if (ae->connected() && !ae->port_remove_in_progress) { - ae->GraphReordered (); /* EMIT SIGNAL */ - } - - return 0; -} - -void* -AudioEngine::_process_thread (void *arg) -{ - return static_cast<AudioEngine *> (arg)->process_thread (); -} - -void -AudioEngine::_freewheel_callback (int onoff, void *arg) -{ - static_cast<AudioEngine*>(arg)->freewheel_callback (onoff); -} - -void -AudioEngine::freewheel_callback (int onoff) -{ - _freewheeling = onoff; - - if (onoff) { - _pre_freewheel_mmc_enabled = MIDI::Manager::instance()->mmc()->send_enabled (); - MIDI::Manager::instance()->mmc()->enable_send (false); - } else { - MIDI::Manager::instance()->mmc()->enable_send (_pre_freewheel_mmc_enabled); - } -} - -void -AudioEngine::_registration_callback (jack_port_id_t /*id*/, int /*reg*/, void* arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - - if (!ae->port_remove_in_progress) { - ae->PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ - } -} - -void -AudioEngine::_latency_callback (jack_latency_callback_mode_t mode, void* arg) -{ - return static_cast<AudioEngine *> (arg)->jack_latency_callback (mode); -} - -void -AudioEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn, void* arg) -{ - AudioEngine* ae = static_cast<AudioEngine*> (arg); - ae->connect_callback (id_a, id_b, conn); -} - -void -AudioEngine::connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn) -{ - if (port_remove_in_progress) { - return; - } - - GET_PRIVATE_JACK_POINTER (_jack); - - jack_port_t* jack_port_a = jack_port_by_id (_priv_jack, id_a); - jack_port_t* jack_port_b = jack_port_by_id (_priv_jack, id_b); - - boost::shared_ptr<Port> port_a; - boost::shared_ptr<Port> port_b; - Ports::iterator x; - boost::shared_ptr<Ports> pr = ports.reader (); - - - x = pr->find (make_port_name_relative (jack_port_name (jack_port_a))); - if (x != pr->end()) { - port_a = x->second; - } - - x = pr->find (make_port_name_relative (jack_port_name (jack_port_b))); - if (x != pr->end()) { - port_b = x->second; - } - - PortConnectedOrDisconnected ( - port_a, jack_port_name (jack_port_a), - port_b, jack_port_name (jack_port_b), - conn == 0 ? false : true - ); /* EMIT SIGNAL */ + AsyncMIDIPort::set_process_thread (pthread_self()); } void @@ -428,29 +141,32 @@ AudioEngine::split_cycle (pframes_t offset) } } -void* -AudioEngine::process_thread () +int +AudioEngine::sample_rate_change (pframes_t nframes) { - /* JACK doesn't do this for us when we use the wait API - */ - - _thread_init_callback (0); + /* check for monitor input change every 1/10th of second */ - _main_thread = new ProcessThread; + monitor_check_interval = nframes / 10; + last_monitor_check = 0; - while (1) { - GET_PRIVATE_JACK_POINTER_RET(_jack,0); + if (_session) { + _session->set_frame_rate (nframes); + } - pframes_t nframes = jack_cycle_wait (_priv_jack); + SampleRateChanged (nframes); /* EMIT SIGNAL */ - if (process_callback (nframes)) { - return 0; - } + return 0; +} - jack_cycle_signal (_priv_jack, 0); - } +int +AudioEngine::buffer_size_change (pframes_t bufsiz) +{ + if (_session) { + _session->set_block_size (bufsiz); + last_monitor_check = 0; + } - return 0; + return 0; } /** Method called by our ::process_thread when there is work to be done. @@ -459,7 +175,6 @@ AudioEngine::process_thread () int AudioEngine::process_callback (pframes_t nframes) { - GET_PRIVATE_JACK_POINTER_RET(_jack,0); Glib::Threads::Mutex::Lock tm (_process_lock, Glib::Threads::TRY_LOCK); PT_TIMING_REF; @@ -482,6 +197,43 @@ AudioEngine::process_callback (pframes_t nframes) return 0; } + bool return_after_remove_check = false; + + 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_after_remove_check = true; + + } 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_after_remove_check = true; + } + if (session_remove_pending) { /* perform the actual session removal */ @@ -489,7 +241,7 @@ AudioEngine::process_callback (pframes_t nframes) if (session_removal_countdown < 0) { /* fade out over 1 second */ - session_removal_countdown = _frame_rate/2; + session_removal_countdown = sample_rate()/2; session_removal_gain = 1.0; session_removal_gain_step = 1.0/session_removal_countdown; @@ -518,11 +270,15 @@ AudioEngine::process_callback (pframes_t nframes) } } + if (return_after_remove_check) { + return 0; + } + if (_session == 0) { if (!_freewheeling) { - MIDI::Manager::instance()->cycle_start(nframes); - MIDI::Manager::instance()->cycle_end(); + PortManager::cycle_start (nframes); + PortManager::cycle_end (nframes); } _processed_frames = next_processed_frames; @@ -533,34 +289,22 @@ AudioEngine::process_callback (pframes_t nframes) /* tell all relevant objects that we're starting a new cycle */ InternalSend::CycleStart (nframes); - Port::set_global_port_buffer_offset (0); - Port::set_cycle_framecnt (nframes); /* tell all Ports that we're starting a new cycle */ - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_start (nframes); - } + PortManager::cycle_start (nframes); /* test if we are freewheeling and there are freewheel signals connected. ardour should act normally even when freewheeling unless /it/ is - exporting + exporting (which is what Freewheel.empty() tests for). */ if (_freewheeling && !Freewheel.empty()) { - Freewheel (nframes); - } else { - MIDI::Manager::instance()->cycle_start(nframes); - if (_session) { _session->process (nframes); } - - MIDI::Manager::instance()->cycle_end(); } if (_freewheeling) { @@ -573,52 +317,18 @@ AudioEngine::process_callback (pframes_t nframes) } if (last_monitor_check + monitor_check_interval < next_processed_frames) { - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - bool x; - - if (i->second->last_monitor() != (x = i->second->jack_monitoring_input ())) { - i->second->set_last_monitor (x); - /* XXX I think this is dangerous, due to - a likely mutex in the signal handlers ... - */ - i->second->MonitorInputChanged (x); /* EMIT SIGNAL */ - } - } + + PortManager::check_monitoring (); last_monitor_check = next_processed_frames; } if (_session->silent()) { - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - if (i->second->sends_output()) { - i->second->get_buffer(nframes).silence(nframes); - } - } + PortManager::silence (nframes); } if (session_remove_pending && session_removal_countdown) { - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - - if (i->second->sends_output()) { - - boost::shared_ptr<AudioPort> ap = boost::dynamic_pointer_cast<AudioPort> (i->second); - if (ap) { - Sample* s = ap->engine_get_whole_audio_buffer (); - gain_t g = session_removal_gain; - - for (pframes_t n = 0; n < nframes; ++n) { - *s++ *= g; - g -= session_removal_gain_step; - } - } - } - } + PortManager::fade_out (session_removal_gain, session_removal_gain_step, nframes); if (session_removal_countdown > nframes) { session_removal_countdown -= nframes; @@ -629,11 +339,7 @@ AudioEngine::process_callback (pframes_t nframes) session_removal_gain -= (nframes * session_removal_gain_step); } - // Finalize ports - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_end (nframes); - } + PortManager::cycle_end (nframes); _processed_frames = next_processed_frames; @@ -642,97 +348,6 @@ AudioEngine::process_callback (pframes_t nframes) return 0; } -int -AudioEngine::_sample_rate_callback (pframes_t nframes, void *arg) -{ - return static_cast<AudioEngine *> (arg)->jack_sample_rate_callback (nframes); -} - -int -AudioEngine::jack_sample_rate_callback (pframes_t nframes) -{ - _frame_rate = nframes; - _usecs_per_cycle = (int) floor ((((double) frames_per_cycle() / nframes)) * 1000000.0); - - /* check for monitor input change every 1/10th of second */ - - monitor_check_interval = nframes / 10; - last_monitor_check = 0; - - if (_session) { - _session->set_frame_rate (nframes); - } - - SampleRateChanged (nframes); /* EMIT SIGNAL */ - - return 0; -} - -void -AudioEngine::jack_latency_callback (jack_latency_callback_mode_t mode) -{ - if (_session) { - _session->update_latency (mode == JackPlaybackLatency); - } -} - -int -AudioEngine::_bufsize_callback (pframes_t nframes, void *arg) -{ - return static_cast<AudioEngine *> (arg)->jack_bufsize_callback (nframes); -} - -int -AudioEngine::jack_bufsize_callback (pframes_t nframes) -{ - /* if the size has not changed, this should be a no-op */ - - if (nframes == _buffer_size) { - return 0; - } - - GET_PRIVATE_JACK_POINTER_RET (_jack, 1); - - _buffer_size = nframes; - _usecs_per_cycle = (int) floor ((((double) nframes / frame_rate())) * 1000000.0); - last_monitor_check = 0; - - if (jack_port_type_get_buffer_size) { - _raw_buffer_sizes[DataType::AUDIO] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_AUDIO_TYPE); - _raw_buffer_sizes[DataType::MIDI] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_MIDI_TYPE); - } else { - - /* Old version of JACK. - - These crude guesses, see below where we try to get the right answers. - - Note that our guess for MIDI deliberatey tries to overestimate - by a little. It would be nicer if we could get the actual - size from a port, but we have to use this estimate in the - event that there are no MIDI ports currently. If there are - the value will be adjusted below. - */ - - _raw_buffer_sizes[DataType::AUDIO] = nframes * sizeof (Sample); - _raw_buffer_sizes[DataType::MIDI] = nframes * 4 - (nframes/2); - } - - { - Glib::Threads::Mutex::Lock lm (_process_lock); - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->reset(); - } - } - - if (_session) { - _session->set_block_size (_buffer_size); - } - - return 0; -} void AudioEngine::stop_metering_thread () @@ -757,8 +372,9 @@ void AudioEngine::meter_thread () { pthread_set_name (X_("meter")); + while (true) { - Glib::usleep (10000); + Glib::usleep (10000); /* 1/100th sec interval */ if (g_atomic_int_get(&m_meter_exit)) { break; } @@ -775,19 +391,9 @@ AudioEngine::set_session (Session *s) if (_session) { - start_metering_thread (); - - pframes_t blocksize = jack_get_buffer_size (_jack); + pframes_t blocksize = samples_per_cycle (); - /* page in as much of the session process code as we - can before we really start running. - */ - - boost::shared_ptr<Ports> p = ports.reader(); - - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_start (blocksize); - } + PortManager::cycle_start (blocksize); _session->process (blocksize); _session->process (blocksize); @@ -798,9 +404,7 @@ AudioEngine::set_session (Session *s) _session->process (blocksize); _session->process (blocksize); - for (Ports::iterator i = p->begin(); i != p->end(); ++i) { - i->second->cycle_end (blocksize); - } + PortManager::cycle_end (blocksize); } } @@ -815,6 +419,7 @@ AudioEngine::remove_session () if (_session) { session_remove_pending = true; + session_removal_countdown = 0; session_removed.wait(_process_lock); } @@ -825,813 +430,622 @@ AudioEngine::remove_session () remove_all_ports (); } + void -AudioEngine::port_registration_failure (const std::string& portname) +AudioEngine::died () { - GET_PRIVATE_JACK_POINTER (_jack); - string full_portname = jack_client_name; - full_portname += ':'; - full_portname += portname; - - - jack_port_t* p = jack_port_by_name (_priv_jack, full_portname.c_str()); - string reason; + /* called from a signal handler for SIGPIPE */ - if (p) { - reason = string_compose (_("a port with the name \"%1\" already exists: check for duplicated track/bus names"), portname); - } else { - reason = string_compose (_("No more JACK ports are available. You will need to stop %1 and restart JACK with more ports if you need this many tracks."), PROGRAM_NAME); - } + stop_metering_thread (); - throw PortRegistrationFailure (string_compose (_("AudioEngine: cannot register port \"%1\": %2"), portname, reason).c_str()); + _running = false; } -boost::shared_ptr<Port> -AudioEngine::register_port (DataType dtype, const string& portname, bool input) +int +AudioEngine::reset_timebase () { - boost::shared_ptr<Port> newport; - - try { - if (dtype == DataType::AUDIO) { - newport.reset (new AudioPort (portname, (input ? Port::IsInput : Port::IsOutput))); - } else if (dtype == DataType::MIDI) { - newport.reset (new MidiPort (portname, (input ? Port::IsInput : Port::IsOutput))); + if (_session) { + if (_session->config.get_jack_time_master()) { + _backend->set_time_master (true); } else { - throw PortRegistrationFailure("unable to create port (unknown type)"); + _backend->set_time_master (false); } - - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - ps->insert (make_pair (make_port_name_relative (portname), newport)); - - /* writer goes out of scope, forces update */ - - return newport; - } - - catch (PortRegistrationFailure& err) { - throw err; - } catch (std::exception& e) { - throw PortRegistrationFailure(string_compose( - _("unable to create port: %1"), e.what()).c_str()); - } catch (...) { - throw PortRegistrationFailure("unable to create port (unknown error)"); } + return 0; } -boost::shared_ptr<Port> -AudioEngine::register_input_port (DataType type, const string& portname) -{ - return register_port (type, portname, true); -} -boost::shared_ptr<Port> -AudioEngine::register_output_port (DataType type, const string& portname) +void +AudioEngine::destroy () { - return register_port (type, portname, false); + delete _instance; + _instance = 0; } int -AudioEngine::unregister_port (boost::shared_ptr<Port> port) +AudioEngine::discover_backends () { - /* caller must hold process lock */ + vector<std::string> backend_modules; - if (!_running) { - /* probably happening when the engine has been halted by JACK, - in which case, there is nothing we can do here. - */ - return 0; - } + _backends.clear (); - { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - Ports::iterator x = ps->find (make_port_name_relative (port->name())); + Glib::PatternSpec so_extension_pattern("*backend.so"); + Glib::PatternSpec dylib_extension_pattern("*backend.dylib"); - if (x != ps->end()) { - ps->erase (x); - } + find_matching_files_in_search_path (backend_search_path (), + so_extension_pattern, backend_modules); - /* writer goes out of scope, forces update */ - } + find_matching_files_in_search_path (backend_search_path (), + dylib_extension_pattern, backend_modules); - ports.flush (); + DEBUG_TRACE (DEBUG::Panning, string_compose (_("looking for backends in %1\n"), backend_search_path().to_string())); - return 0; -} + for (vector<std::string>::iterator i = backend_modules.begin(); i != backend_modules.end(); ++i) { -int -AudioEngine::connect (const string& source, const string& destination) -{ - int ret; + AudioBackendInfo* info; - if (!_running) { - if (!_has_run) { - fatal << _("connect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; + if ((info = backend_discover (*i)) != 0) { + _backends.insert (make_pair (info->name, info)); } } - string s = make_port_name_non_relative (source); - string d = make_port_name_non_relative (destination); - + return _backends.size(); +} - boost::shared_ptr<Port> src = get_port_by_name (s); - boost::shared_ptr<Port> dst = get_port_by_name (d); +AudioBackendInfo* +AudioEngine::backend_discover (const string& path) +{ + Glib::Module module (path); + AudioBackendInfo* info; + void* sym = 0; - if (src) { - ret = src->connect (d); - } else if (dst) { - ret = dst->connect (s); - } else { - /* neither port is known to us, and this API isn't intended for use as a general patch bay */ - ret = -1; + if (!module) { + error << string_compose(_("AudioEngine: cannot load module \"%1\" (%2)"), path, + Glib::Module::get_last_error()) << endmsg; + return 0; } - - if (ret > 0) { - /* already exists - no error, no warning */ - } else if (ret < 0) { - error << string_compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"), - source, s, destination, d) - << endmsg; + + if (!module.get_symbol ("descriptor", sym)) { + error << string_compose(_("AudioEngine: backend at \"%1\" has no descriptor."), path) << endmsg; + error << Glib::Module::get_last_error() << endmsg; + return 0; } - return ret; + module.make_resident (); + + info = (AudioBackendInfo*) sym; + + return info; } -int -AudioEngine::disconnect (const string& source, const string& destination) +vector<const AudioBackendInfo*> +AudioEngine::available_backends() const { - int ret; - - if (!_running) { - if (!_has_run) { - fatal << _("disconnect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; - } + vector<const AudioBackendInfo*> r; + + for (BackendMap::const_iterator i = _backends.begin(); i != _backends.end(); ++i) { + r.push_back (i->second); } - string s = make_port_name_non_relative (source); - string d = make_port_name_non_relative (destination); - - boost::shared_ptr<Port> src = get_port_by_name (s); - boost::shared_ptr<Port> dst = get_port_by_name (d); - - if (src) { - ret = src->disconnect (d); - } else if (dst) { - ret = dst->disconnect (s); - } else { - /* neither port is known to us, and this API isn't intended for use as a general patch bay */ - ret = -1; - } - return ret; + return r; } -int -AudioEngine::disconnect (boost::shared_ptr<Port> port) +string +AudioEngine::current_backend_name() const { - GET_PRIVATE_JACK_POINTER_RET (_jack,-1); - - if (!_running) { - if (!_has_run) { - fatal << _("disconnect called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return -1; - } - } - - return port->disconnect_all (); + if (_backend) { + return _backend->name(); + } + return string(); } -ARDOUR::framecnt_t -AudioEngine::frame_rate () const +void +AudioEngine::drop_backend () { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - if (_frame_rate == 0) { - return (_frame_rate = jack_get_sample_rate (_priv_jack)); - } else { - return _frame_rate; + if (_backend) { + _backend->stop (); + _backend.reset (); } } -size_t -AudioEngine::raw_buffer_size (DataType t) +boost::shared_ptr<AudioBackend> +AudioEngine::set_backend (const std::string& name, const std::string& arg1, const std::string& arg2) { - std::map<DataType,size_t>::const_iterator s = _raw_buffer_sizes.find(t); - return (s != _raw_buffer_sizes.end()) ? s->second : 0; -} + BackendMap::iterator b = _backends.find (name); -ARDOUR::pframes_t -AudioEngine::frames_per_cycle () const -{ - GET_PRIVATE_JACK_POINTER_RET (_jack,0); - if (_buffer_size == 0) { - return jack_get_buffer_size (_jack); - } else { - return _buffer_size; + if (b == _backends.end()) { + return boost::shared_ptr<AudioBackend>(); } -} - -/** @param name Full or short name of port - * @return Corresponding Port or 0. - */ -boost::shared_ptr<Port> -AudioEngine::get_port_by_name (const string& portname) -{ - if (!_running) { - if (!_has_run) { - fatal << _("get_port_by_name() called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - boost::shared_ptr<Port> (); + drop_backend (); + + try { + if (b->second->instantiate (arg1, arg2)) { + throw failed_constructor (); } - } - if (!port_is_mine (portname)) { - /* not an ardour port */ - return boost::shared_ptr<Port> (); - } + _backend = b->second->backend_factory (*this); + _impl = b->second->portengine_factory (*this); - boost::shared_ptr<Ports> pr = ports.reader(); - std::string rel = make_port_name_relative (portname); - Ports::iterator x = pr->find (rel); - - if (x != pr->end()) { - /* its possible that the port was renamed by some 3rd party and - we don't know about it. check for this (the check is quick - and cheap), and if so, rename the port (which will alter - the port map as a side effect). - */ - const std::string check = make_port_name_relative (jack_port_name (x->second->jack_port())); - if (check != rel) { - x->second->set_name (check); - } - return x->second; + } catch (exception& e) { + error << string_compose (_("Could not create backend for %1: %2"), name, e.what()) << endmsg; + return boost::shared_ptr<AudioBackend>(); } - return boost::shared_ptr<Port> (); + return _backend; } -void -AudioEngine::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name) +/* BACKEND PROXY WRAPPERS */ + +int +AudioEngine::start () { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> p = writer.get_copy(); - Ports::iterator x = p->find (old_relative_name); + if (!_backend) { + return -1; + } + + if (_running) { + return 0; + } + + _processed_frames = 0; + last_monitor_check = 0; - if (x != p->end()) { - boost::shared_ptr<Port> port = x->second; - p->erase (x); - p->insert (make_pair (new_relative_name, port)); + if (_backend->start()) { + return -1; } -} -const char ** -AudioEngine::get_ports (const string& port_name_pattern, const string& type_name_pattern, uint32_t flags) -{ - GET_PRIVATE_JACK_POINTER_RET (_jack,0); - if (!_running) { - if (!_has_run) { - fatal << _("get_ports called before engine was started") << endmsg; - /*NOTREACHED*/ - } else { - return 0; + _running = true; + + if (_session) { + _session->set_frame_rate (_backend->sample_rate()); + + if (_session->config.get_jack_time_master()) { + _backend->set_time_master (true); } } - return jack_get_ports (_priv_jack, port_name_pattern.c_str(), type_name_pattern.c_str(), flags); + + start_metering_thread (); + + Running(); /* EMIT SIGNAL */ + + return 0; } -void -AudioEngine::halted_info (jack_status_t code, const char* reason, void *arg) +int +AudioEngine::stop () { - /* called from jack shutdown handler */ - - AudioEngine* ae = static_cast<AudioEngine *> (arg); - bool was_running = ae->_running; - - ae->stop_metering_thread (); + if (!_backend) { + return 0; + } - ae->_running = false; - ae->_buffer_size = 0; - ae->_frame_rate = 0; - ae->_jack = 0; + Glib::Threads::Mutex::Lock lm (_process_lock); - if (was_running) { - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ -#ifdef HAVE_JACK_ON_INFO_SHUTDOWN - switch (code) { - case JackBackendError: - ae->Halted(reason); /* EMIT SIGNAL */ - break; - default: - ae->Halted(""); /* EMIT SIGNAL */ - } -#else - ae->Halted(""); /* EMIT SIGNAL */ -#endif - } + if (_backend->stop ()) { + return -1; + } + + _running = false; + _processed_frames = 0; + _measuring_latency = false; + _latency_output_port = 0; + _latency_input_port = 0; + stop_metering_thread (); + + Port::PortDrop (); + Stopped (); /* EMIT SIGNAL */ + + return 0; } -void -AudioEngine::halted (void *arg) +int +AudioEngine::pause () { - cerr << "HALTED by JACK\n"; + if (!_backend) { + return 0; + } + + if (_backend->pause ()) { + return -1; + } - /* called from jack shutdown handler */ + _running = false; + + Stopped(); /* EMIT SIGNAL */ + return 0; +} - AudioEngine* ae = static_cast<AudioEngine *> (arg); - bool was_running = ae->_running; +int +AudioEngine::freewheel (bool start_stop) +{ + if (!_backend) { + return -1; + } - ae->stop_metering_thread (); + /* _freewheeling will be set when first Freewheel signal occurs */ - ae->_running = false; - ae->_buffer_size = 0; - ae->_frame_rate = 0; - ae->_jack = 0; + return _backend->freewheel (start_stop); +} - if (was_running) { - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - ae->Halted(""); /* EMIT SIGNAL */ +float +AudioEngine::get_cpu_load() const +{ + if (!_backend) { + return 0.0; } + return _backend->cpu_load (); } -void -AudioEngine::died () +bool +AudioEngine::is_realtime() const { - /* called from a signal handler for SIGPIPE */ - - stop_metering_thread (); + if (!_backend) { + return false; + } - _running = false; - _buffer_size = 0; - _frame_rate = 0; - _jack = 0; + return _backend->is_realtime(); } bool -AudioEngine::can_request_hardware_monitoring () +AudioEngine::connected() const { - GET_PRIVATE_JACK_POINTER_RET (_jack,false); - const char ** ports; - - if ((ports = jack_get_ports (_priv_jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortCanMonitor)) == 0) { + if (!_backend) { return false; } - free (ports); - - return true; + return _backend->connected(); } -ChanCount -AudioEngine::n_physical (unsigned long flags) const +void +AudioEngine::transport_start () { - ChanCount c; - - GET_PRIVATE_JACK_POINTER_RET (_jack, c); - - const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); - if (ports == 0) { - return c; - } - - for (uint32_t i = 0; ports[i]; ++i) { - if (!strstr (ports[i], "Midi-Through")) { - DataType t (jack_port_type (jack_port_by_name (_jack, ports[i]))); - c.set (t, c.get (t) + 1); - } + if (!_backend) { + return; } - - free (ports); - - return c; + return _backend->transport_start (); } -ChanCount -AudioEngine::n_physical_inputs () const +void +AudioEngine::transport_stop () { - return n_physical (JackPortIsInput); + if (!_backend) { + return; + } + return _backend->transport_stop (); } -ChanCount -AudioEngine::n_physical_outputs () const +TransportState +AudioEngine::transport_state () { - return n_physical (JackPortIsOutput); + if (!_backend) { + return TransportStopped; + } + return _backend->transport_state (); } void -AudioEngine::get_physical (DataType type, unsigned long flags, vector<string>& phy) +AudioEngine::transport_locate (framepos_t pos) { - GET_PRIVATE_JACK_POINTER (_jack); - const char ** ports; - - if ((ports = jack_get_ports (_priv_jack, NULL, type.to_jack_type(), JackPortIsPhysical | flags)) == 0) { + if (!_backend) { return; } - - if (ports) { - for (uint32_t i = 0; ports[i]; ++i) { - if (strstr (ports[i], "Midi-Through")) { - continue; - } - phy.push_back (ports[i]); - } - free (ports); - } + return _backend->transport_locate (pos); } -/** Get physical ports for which JackPortIsOutput is set; ie those that correspond to - * a physical input connector. - */ -void -AudioEngine::get_physical_inputs (DataType type, vector<string>& ins) +framepos_t +AudioEngine::transport_frame() { - get_physical (type, JackPortIsOutput, ins); + if (!_backend) { + return 0; + } + return _backend->transport_frame (); } -/** Get physical ports for which JackPortIsInput is set; ie those that correspond to - * a physical output connector. - */ -void -AudioEngine::get_physical_outputs (DataType type, vector<string>& outs) +framecnt_t +AudioEngine::sample_rate () const { - get_physical (type, JackPortIsInput, outs); + if (!_backend) { + return 0; + } + return _backend->sample_rate (); } -void -AudioEngine::transport_stop () +pframes_t +AudioEngine::samples_per_cycle () const { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_stop (_priv_jack); + if (!_backend) { + return 0; + } + return _backend->buffer_size (); } -void -AudioEngine::transport_start () +int +AudioEngine::usecs_per_cycle () const { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_start (_priv_jack); + if (!_backend) { + return -1; + } + return _backend->usecs_per_cycle (); } -void -AudioEngine::transport_locate (framepos_t where) +size_t +AudioEngine::raw_buffer_size (DataType t) { - GET_PRIVATE_JACK_POINTER (_jack); - jack_transport_locate (_priv_jack, where); + if (!_backend) { + return -1; + } + return _backend->raw_buffer_size (t); } -AudioEngine::TransportState -AudioEngine::transport_state () +pframes_t +AudioEngine::sample_time () { - GET_PRIVATE_JACK_POINTER_RET (_jack, ((TransportState) JackTransportStopped)); - jack_position_t pos; - return (TransportState) jack_transport_query (_priv_jack, &pos); + if (!_backend) { + return 0; + } + return _backend->sample_time (); } -int -AudioEngine::reset_timebase () +pframes_t +AudioEngine::sample_time_at_cycle_start () { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - if (_session) { - if (_session->config.get_jack_time_master()) { - return jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); - } else { - return jack_release_timebase (_jack); - } + if (!_backend) { + return 0; } - return 0; + return _backend->sample_time_at_cycle_start (); } -int -AudioEngine::freewheel (bool onoff) +pframes_t +AudioEngine::samples_since_cycle_start () { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (onoff != _freewheeling) { - return jack_set_freewheel (_priv_jack, onoff); - - } else { - /* already doing what has been asked for */ - return 0; + if (!_backend) { + return 0; } + return _backend->samples_since_cycle_start (); } -void -AudioEngine::remove_all_ports () +bool +AudioEngine::get_sync_offset (pframes_t& offset) const { - /* make sure that JACK callbacks that will be invoked as we cleanup - * ports know that they have nothing to do. - */ - - port_remove_in_progress = true; - - /* process lock MUST be held by caller - */ - - { - RCUWriter<Ports> writer (ports); - boost::shared_ptr<Ports> ps = writer.get_copy (); - ps->clear (); + if (!_backend) { + return false; } - - /* clear dead wood list in RCU */ - - ports.flush (); - - port_remove_in_progress = false; + return _backend->get_sync_offset (offset); } int -AudioEngine::connect_to_jack (string client_name, string session_uuid) +AudioEngine::create_process_thread (boost::function<void()> func, pthread_t* thr, size_t stacksize) { - EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); - boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; - jack_status_t status; - - /* revert all environment settings back to whatever they were when ardour started - */ - - if (global_epa) { - current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ - global_epa->restore (); - } - - jack_client_name = client_name; /* might be reset below */ -#ifdef HAVE_JACK_SESSION - if (! session_uuid.empty()) - _jack = jack_client_open (jack_client_name.c_str(), JackSessionID, &status, session_uuid.c_str()); - else -#endif - _jack = jack_client_open (jack_client_name.c_str(), JackNullOption, &status, 0); - - if (_jack == NULL) { - // error message is not useful here + if (!_backend) { return -1; } + return _backend->create_process_thread (func, thr, stacksize); +} - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - if (status & JackNameNotUnique) { - jack_client_name = jack_get_client_name (_priv_jack); +int +AudioEngine::set_device_name (const std::string& name) +{ + if (!_backend) { + return -1; } - - return 0; + return _backend->set_device_name (name); } int -AudioEngine::disconnect_from_jack () +AudioEngine::set_sample_rate (float sr) { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - - if (_running) { - stop_metering_thread (); - } - - { - Glib::Threads::Mutex::Lock lm (_process_lock); - jack_client_close (_priv_jack); - _jack = 0; - } - - _buffer_size = 0; - _frame_rate = 0; - _raw_buffer_sizes.clear(); - - if (_running) { - _running = false; - MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ - Stopped(); /* EMIT SIGNAL */ + if (!_backend) { + return -1; } - - return 0; + return _backend->set_sample_rate (sr); } int -AudioEngine::reconnect_to_jack () +AudioEngine::set_buffer_size (uint32_t bufsiz) { - if (_running) { - disconnect_from_jack (); - /* XXX give jackd a chance */ - Glib::usleep (250000); - } - - if (connect_to_jack (jack_client_name, "")) { - error << _("failed to connect to JACK") << endmsg; + if (!_backend) { return -1; } + return _backend->set_buffer_size (bufsiz); +} - Ports::iterator i; - - boost::shared_ptr<Ports> p = ports.reader (); - - for (i = p->begin(); i != p->end(); ++i) { - if (i->second->reestablish ()) { - break; - } +int +AudioEngine::set_sample_format (SampleFormat sf) +{ + if (!_backend) { + return -1; } + return _backend->set_sample_format (sf); +} - if (i != p->end()) { - /* failed */ - remove_all_ports (); +int +AudioEngine::set_interleaved (bool yn) +{ + if (!_backend) { return -1; } + return _backend->set_interleaved (yn); +} - GET_PRIVATE_JACK_POINTER_RET (_jack,-1); - - MIDI::Manager::instance()->reestablish (_priv_jack); - - if (_session) { - _session->reset_jack_connection (_priv_jack); - jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); - _session->set_frame_rate (jack_get_sample_rate (_priv_jack)); +int +AudioEngine::set_input_channels (uint32_t ic) +{ + if (!_backend) { + return -1; } + return _backend->set_input_channels (ic); +} - last_monitor_check = 0; - - set_jack_callbacks (); - - if (jack_activate (_priv_jack) == 0) { - _running = true; - _has_run = true; - } else { +int +AudioEngine::set_output_channels (uint32_t oc) +{ + if (!_backend) { return -1; } + return _backend->set_output_channels (oc); +} - /* re-establish connections */ - - for (i = p->begin(); i != p->end(); ++i) { - i->second->reconnect (); +int +AudioEngine::set_systemic_input_latency (uint32_t il) +{ + if (!_backend) { + return -1; } - - MIDI::Manager::instance()->reconnect (); - - Running (); /* EMIT SIGNAL*/ - - start_metering_thread (); - - return 0; + return _backend->set_systemic_input_latency (il); } int -AudioEngine::request_buffer_size (pframes_t nframes) +AudioEngine::set_systemic_output_latency (uint32_t ol) { - GET_PRIVATE_JACK_POINTER_RET (_jack, -1); - - if (nframes == jack_get_buffer_size (_priv_jack)) { - return 0; + if (!_backend) { + return -1; } - - return jack_set_buffer_size (_priv_jack, nframes); + return _backend->set_systemic_output_latency (ol); } -string -AudioEngine::make_port_name_relative (string portname) const +/* END OF BACKEND PROXY API */ + +void +AudioEngine::thread_init_callback (void* arg) { - string::size_type len; - string::size_type n; + /* make sure that anybody who needs to know about this thread + knows about it. + */ - len = portname.length(); + pthread_set_name (X_("audioengine")); - for (n = 0; n < len; ++n) { - if (portname[n] == ':') { - break; - } - } + PBD::notify_gui_about_thread_creation ("gui", pthread_self(), X_("AudioEngine"), 4096); + PBD::notify_gui_about_thread_creation ("midiui", pthread_self(), X_("AudioEngine"), 128); - if ((n != len) && (portname.substr (0, n) == jack_client_name)) { - return portname.substr (n+1); - } + SessionEvent::create_per_thread_pool (X_("AudioEngine"), 512); - return portname; + AsyncMIDIPort::set_process_thread (pthread_self()); + + if (arg) { + /* the special thread created/managed by the backend */ + AudioEngine::instance()->_main_thread = new ProcessThread; + } } -string -AudioEngine::make_port_name_non_relative (string portname) const +int +AudioEngine::sync_callback (TransportState state, framepos_t position) { - string str; - - if (portname.find_first_of (':') != string::npos) { - return portname; + if (_session) { + return _session->backend_sync_callback (state, position); } + return 0; +} - str = jack_client_name; - str += ':'; - str += portname; - - return str; +void +AudioEngine::freewheel_callback (bool onoff) +{ + _freewheeling = onoff; } -bool -AudioEngine::port_is_mine (const string& portname) const +void +AudioEngine::latency_callback (bool for_playback) { - if (portname.find_first_of (':') != string::npos) { - if (portname.substr (0, jack_client_name.length ()) != jack_client_name) { - return false; - } + if (_session) { + _session->update_latency (for_playback); } - return true; } -bool -AudioEngine::is_realtime () const +void +AudioEngine::update_latencies () { - GET_PRIVATE_JACK_POINTER_RET (_jack,false); - return jack_is_realtime (_priv_jack); + if (_backend) { + _backend->update_latencies (); + } } -int -AudioEngine::create_process_thread (boost::function<void()> f, jack_native_thread_t* thread, size_t stacksize) +void +AudioEngine::halted_callback (const char* why) { - GET_PRIVATE_JACK_POINTER_RET (_jack, 0); - ThreadData* td = new ThreadData (this, f, stacksize); + stop_metering_thread (); + _running = false; - if (jack_client_create_thread (_priv_jack, thread, jack_client_real_time_priority (_priv_jack), - jack_is_realtime (_priv_jack), _start_process_thread, td)) { - return -1; - } - - return 0; + Port::PortDrop (); /* EMIT SIGNAL */ + Halted (why); /* EMIT SIGNAL */ } bool -AudioEngine::stop_process_thread (jack_native_thread_t thread) +AudioEngine::setup_required () const { - /** - * can't use GET_PRIVATE_JACK_POINTER_RET (_jack, 0) here - * because _jack is 0 when this is called. At least for - * Jack 2 _jack arg is not used so it should be OK - */ - -#if defined(USING_JACK2_EXPANSION_OF_JACK_API) || defined(PLATFORM_WINDOWS) - if (jack_client_stop_thread (_jack, thread) != 0) { - error << "AudioEngine: cannot stop process thread" << endmsg; + if (_backends.size() == 1 && _backends.begin()->second->already_configured()) { return false; } -#else - void* status; - pthread_join (thread, &status); -#endif return true; } -void* -AudioEngine::_start_process_thread (void* arg) +MTDM* +AudioEngine::mtdm() { - ThreadData* td = reinterpret_cast<ThreadData*> (arg); - boost::function<void()> f = td->f; - delete td; - - f (); - - return 0; + return _mtdm; } -bool -AudioEngine::port_is_physical (const std::string& portname) const +void +AudioEngine::start_latency_detection () { - GET_PRIVATE_JACK_POINTER_RET(_jack, false); + PortEngine& pe (port_engine()); - jack_port_t *port = jack_port_by_name (_priv_jack, portname.c_str()); + delete _mtdm; + _mtdm = 0; - if (!port) { - return false; - } + /* create the ports we will use to read/write data */ + + if ((_latency_output_port = pe.register_port ("latency_out", DataType::AUDIO, IsOutput)) == 0) { + return; + } + if (pe.connect (_latency_output_port, _latency_output_name)) { + return; + } - return jack_port_flags (port) & JackPortIsPhysical; -} + const string portname ("latency_in"); + if ((_latency_input_port = pe.register_port (portname, DataType::AUDIO, IsInput)) == 0) { + pe.unregister_port (_latency_output_port); + return; + } + if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) { + pe.unregister_port (_latency_output_port); + return; + } -void -AudioEngine::request_jack_monitors_input (const std::string& portname, bool yn) const -{ - GET_PRIVATE_JACK_POINTER(_jack); + LatencyRange lr; + _latency_signal_latency = 0; + lr = pe.get_latency_range (_latency_input_port, false); + _latency_signal_latency = lr.max; + lr = pe.get_latency_range (_latency_output_port, true); + _latency_signal_latency += lr.max; - jack_port_t *port = jack_port_by_name (_priv_jack, portname.c_str()); + cerr << "latency signal pathway = " << _latency_signal_latency << endl; + + /* all created and connected, lets go */ + + _mtdm = new MTDM (sample_rate()); + _measuring_latency = true; + _latency_flush_frames = samples_per_cycle(); - if (!port) { - return; - } - jack_port_request_monitor (port, yn); } void -AudioEngine::update_latencies () +AudioEngine::stop_latency_detection () { - GET_PRIVATE_JACK_POINTER (_jack); - jack_recompute_total_latencies (_priv_jack); + port_engine().unregister_port (_latency_output_port); + port_engine().unregister_port (_latency_input_port); + _measuring_latency = false; } void -AudioEngine::destroy () +AudioEngine::set_latency_output_port (const string& name) { - delete _instance; - _instance = 0; + _latency_output_name = name; } +void +AudioEngine::set_latency_input_port (const string& name) +{ + _latency_input_name = name; +} diff --git a/libs/ardour/backend_search_path.cc b/libs/ardour/backend_search_path.cc new file mode 100644 index 0000000000..4c5ff40e7d --- /dev/null +++ b/libs/ardour/backend_search_path.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <glibmm/miscutils.h> + +#include "ardour/backend_search_path.h" +#include "ardour/directory_names.h" +#include "ardour/filesystem_paths.h" + +namespace { + const char * const backend_env_variable_name = "ARDOUR_BACKEND_PATH"; +} // anonymous + +using namespace PBD; + +namespace ARDOUR { + +Searchpath +backend_search_path () +{ + Searchpath spath(user_config_directory ()); + spath += ardour_dll_directory (); + spath.add_subdirectory_to_paths(backend_dir_name); + + spath += Searchpath(Glib::getenv(backend_env_variable_name)); + return spath; +} + +} // namespace ARDOUR diff --git a/libs/ardour/buffer_set.cc b/libs/ardour/buffer_set.cc index 184e23d1af..e67aee6be7 100644 --- a/libs/ardour/buffer_set.cc +++ b/libs/ardour/buffer_set.cc @@ -90,7 +90,7 @@ BufferSet::clear() /** Set up this BufferSet so that its data structures mirror a PortSet's buffers. * This is quite expensive and not RT-safe, so it should not be called in a process context; - * get_jack_port_addresses() will fill in a structure set up by this method. + * get_backend_port_addresses() will fill in a structure set up by this method. * * XXX: this *is* called in a process context; I'm not sure quite what `should not' means above. */ @@ -114,13 +114,13 @@ BufferSet::attach_buffers (PortSet& ports) _is_mirror = true; } -/** Write the JACK port addresses from a PortSet into our data structures. This +/** Write the backend port addresses from a PortSet into our data structures. This * call assumes that attach_buffers() has already been called for the same PortSet. * Does not allocate, so RT-safe BUT you can only call Port::get_buffer() from * the process() callback tree anyway, so this has to be called in RT context. */ void -BufferSet::get_jack_port_addresses (PortSet& ports, framecnt_t nframes) +BufferSet::get_backend_port_addresses (PortSet& ports, framecnt_t nframes) { assert (_count == ports.count ()); assert (_available == ports.count ()); diff --git a/libs/ardour/bundle.cc b/libs/ardour/bundle.cc index 0ac62d7076..834a98d347 100644 --- a/libs/ardour/bundle.cc +++ b/libs/ardour/bundle.cc @@ -443,27 +443,26 @@ Bundle::connected_to (boost::shared_ptr<Bundle> other, AudioEngine & engine) return true; } -/** This must not be called in code executed as a response to a JACK event, - * as it uses jack_port_get_all_connections(). +/** This must not be called in code executed as a response to a backend event, + * as it uses the backend port_get_all_connections(). * @return true if any of this bundle's channels are connected to anything. */ bool Bundle::connected_to_anything (AudioEngine& engine) { + PortManager& pm (engine); + for (uint32_t i = 0; i < nchannels().n_total(); ++i) { Bundle::PortList const & ports = channel_ports (i); for (uint32_t j = 0; j < ports.size(); ++j) { - /* ports[j] may not be an Ardour port, so use JACK directly + + /* ports[j] may not be an Ardour port, so use the port manager directly rather than doing it with Port. */ - jack_port_t* jp = jack_port_by_name (engine.jack(), ports[j].c_str()); - if (jp) { - const char ** c = jack_port_get_all_connections (engine.jack(), jp); - if (c) { - jack_free (c); - return true; - } + + if (pm.connected (ports[j])) { + return true; } } } diff --git a/libs/ardour/capturing_processor.cc b/libs/ardour/capturing_processor.cc index 4a31d92cc8..ce4a546fb4 100644 --- a/libs/ardour/capturing_processor.cc +++ b/libs/ardour/capturing_processor.cc @@ -28,7 +28,7 @@ namespace ARDOUR { CapturingProcessor::CapturingProcessor (Session & session) : Processor (session, X_("capture point")) - , block_size (session.engine().frames_per_cycle()) + , block_size (AudioEngine::instance()->samples_per_cycle()) { realloc_buffers (); } diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 51115001cb..dc762299e9 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -61,5 +61,6 @@ uint64_t PBD::DEBUG::TempoMap = PBD::new_debug_bit ("tempomap"); uint64_t PBD::DEBUG::OrderKeys = PBD::new_debug_bit ("orderkeys"); uint64_t PBD::DEBUG::Automation = PBD::new_debug_bit ("automation"); uint64_t PBD::DEBUG::WiimoteControl = PBD::new_debug_bit ("wiimotecontrol"); +uint64_t PBD::DEBUG::Ports = PBD::new_debug_bit ("Ports"); diff --git a/libs/ardour/delivery.cc b/libs/ardour/delivery.cc index dfbe4c960a..79c44ce94a 100644 --- a/libs/ardour/delivery.cc +++ b/libs/ardour/delivery.cc @@ -245,7 +245,7 @@ Delivery::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pf processing pathway that wants to use this->output_buffers() for some reason. */ - output_buffers().get_jack_port_addresses (ports, nframes); + output_buffers().get_backend_port_addresses (ports, nframes); // this Delivery processor is not a derived type, and thus we assume // we really can modify the buffers passed in (it is almost certainly diff --git a/libs/ardour/directory_names.cc b/libs/ardour/directory_names.cc index 6fb15eaabd..f01c024435 100644 --- a/libs/ardour/directory_names.cc +++ b/libs/ardour/directory_names.cc @@ -38,6 +38,7 @@ const char* const route_templates_dir_name = X_("route_templates"); const char* const surfaces_dir_name = X_("surfaces"); const char* const ladspa_dir_name = X_("ladspa"); const char* const panner_dir_name = X_("panners"); +const char* const backend_dir_name = X_("backends"); /* these should end up using variants of PROGRAM_NAME */ #ifdef __APPLE__ diff --git a/libs/ardour/export_channel.cc b/libs/ardour/export_channel.cc index 9b3f50e85d..82e5d80244 100644 --- a/libs/ardour/export_channel.cc +++ b/libs/ardour/export_channel.cc @@ -117,7 +117,7 @@ RegionExportChannelFactory::RegionExportChannelFactory (Session * session, Audio : region (region) , track (track) , type (type) - , frames_per_cycle (session->engine().frames_per_cycle ()) + , frames_per_cycle (session->engine().samples_per_cycle ()) , buffers_up_to_date (false) , region_start (region.position()) , position (region_start) diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc index 127546e8fc..2c0c44033d 100644 --- a/libs/ardour/export_graph_builder.cc +++ b/libs/ardour/export_graph_builder.cc @@ -56,7 +56,7 @@ ExportGraphBuilder::ExportGraphBuilder (Session const & session) : session (session) , thread_pool (hardware_concurrency()) { - process_buffer_frames = session.engine().frames_per_cycle(); + process_buffer_frames = session.engine().samples_per_cycle(); } ExportGraphBuilder::~ExportGraphBuilder () @@ -507,7 +507,7 @@ ExportGraphBuilder::ChannelConfig::ChannelConfig (ExportGraphBuilder & parent, F config = new_config; - framecnt_t max_frames = parent.session.engine().frames_per_cycle(); + framecnt_t max_frames = parent.session.engine().samples_per_cycle(); interleaver.reset (new Interleaver<Sample> ()); interleaver->init (new_config.channel_config->get_n_chans(), max_frames); diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 1406bbd43a..8646b819f3 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -35,6 +35,7 @@ #include <unistd.h> #include <fcntl.h> #include <errno.h> +#include <time.h> #ifdef WINDOWS_VST_SUPPORT #include <fst.h> @@ -74,11 +75,11 @@ #include "pbd/basename.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "midi++/mmc.h" #include "ardour/analyser.h" #include "ardour/audio_library.h" +#include "ardour/audio_backend.h" #include "ardour/audioengine.h" #include "ardour/audioplaylist.h" #include "ardour/audioregion.h" @@ -86,6 +87,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/filesystem_paths.h" #include "ardour/midi_region.h" +#include "ardour/midiport_manager.h" #include "ardour/mix.h" #include "ardour/panner_manager.h" #include "ardour/plugin_manager.h" @@ -342,6 +344,8 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir EventTypeMap::instance().new_parameter(EnvelopeAutomation); EventTypeMap::instance().new_parameter(MidiCCAutomation); + ARDOUR::AudioEngine::create (); + libardour_initialized = true; return true; @@ -350,9 +354,6 @@ ARDOUR::init (bool use_windows_vst, bool try_optimization, const char* localedir void ARDOUR::init_post_engine () { - /* the MIDI Manager is needed by the ControlProtocolManager */ - MIDI::Manager::create (AudioEngine::instance()->jack()); - ControlProtocolManager::instance().discover_control_protocols (); XMLNode* node; @@ -550,3 +551,43 @@ ARDOUR::get_available_sync_options () return ret; } + +/** Return a monotonic value for the number of microseconds that have elapsed + * since an arbitrary zero origin. + */ + +#ifdef __MACH__ +/* Thanks Apple for not implementing this basic SUSv2, POSIX.1-2001 function + */ +#include <mach/mach_time.h> +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 +int +clock_gettime (int /*clk_id*/, struct timespec *t) +{ + static bool initialized = false; + static mach_timebase_info_data_t timebase; + if (!initialized) { + mach_timebase_info(&timebase); + initialized = true; + } + uint64_t time; + time = mach_absolute_time(); + double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); + double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); + t->tv_sec = seconds; + t->tv_nsec = nseconds; + return 0; +} +#endif + +microseconds_t +ARDOUR::get_microseconds () +{ + struct timespec ts; + if (clock_gettime (CLOCK_MONOTONIC, &ts) != 0) { + /* EEEK! */ + return 0; + } + return (microseconds_t) ts.tv_sec * 1000000 + (ts.tv_nsec/1000); +} diff --git a/libs/ardour/internal_send.cc b/libs/ardour/internal_send.cc index 029a46ce53..8136985e1f 100644 --- a/libs/ardour/internal_send.cc +++ b/libs/ardour/internal_send.cc @@ -310,7 +310,7 @@ bool InternalSend::configure_io (ChanCount in, ChanCount out) { bool ret = Send::configure_io (in, out); - set_block_size (_session.engine().frames_per_cycle()); + set_block_size (_session.engine().samples_per_cycle()); return ret; } diff --git a/libs/ardour/io.cc b/libs/ardour/io.cc index a9d37d4315..ebd295411e 100644 --- a/libs/ardour/io.cc +++ b/libs/ardour/io.cc @@ -1344,7 +1344,7 @@ IO::bundle_changed (Bundle::Change /*c*/) string IO::build_legal_port_name (DataType type) { - const int name_size = jack_port_name_size(); + const int name_size = AudioEngine::instance()->port_name_size(); int limit; string suffix; @@ -1378,7 +1378,7 @@ IO::build_legal_port_name (DataType type) // allow up to 4 digits for the output port number, plus the slash, suffix and extra space - limit = name_size - _session.engine().client_name().length() - (suffix.length() + 5); + limit = name_size - AudioEngine::instance()->my_name().length() - (suffix.length() + 5); std::vector<char> buf1(name_size+1); std::vector<char> buf2(name_size+1); @@ -1411,7 +1411,7 @@ IO::find_port_hole (const char* base) */ for (n = 1; n < 9999; ++n) { - std::vector<char> buf(jack_port_name_size()); + std::vector<char> buf (AudioEngine::instance()->port_name_size()); PortSet::iterator i = _ports.begin(); snprintf (&buf[0], jack_port_name_size(), _("%s %u"), base, n); @@ -1645,7 +1645,7 @@ IO::process_input (boost::shared_ptr<Processor> proc, framepos_t start_frame, fr return; } - _buffers.get_jack_port_addresses (_ports, nframes); + _buffers.get_backend_port_addresses (_ports, nframes); if (proc) { proc->run (_buffers, start_frame, end_frame, nframes, true); } diff --git a/libs/ardour/jack_slave.cc b/libs/ardour/jack_slave.cc index 4c2da4c6c4..4b2f3b1860 100644 --- a/libs/ardour/jack_slave.cc +++ b/libs/ardour/jack_slave.cc @@ -20,16 +20,14 @@ #include <iostream> #include <cerrno> -#include <jack/jack.h> -#include <jack/transport.h> - +#include "ardour/audioengine.h" #include "ardour/slave.h" using namespace std; using namespace ARDOUR; -JACK_Slave::JACK_Slave (jack_client_t* j) - : jack (j) +JACK_Slave::JACK_Slave (AudioEngine& e) + : engine (e) { double x; framepos_t p; @@ -41,12 +39,6 @@ JACK_Slave::~JACK_Slave () { } -void -JACK_Slave::reset_client (jack_client_t* j) -{ - jack = j; -} - bool JACK_Slave::locked() const { @@ -62,33 +54,26 @@ JACK_Slave::ok() const bool JACK_Slave::speed_and_position (double& sp, framepos_t& position) { - jack_position_t pos; - jack_transport_state_t state; - - state = jack_transport_query (jack, &pos); - - switch (state) { - case JackTransportStopped: + switch (engine.transport_state()) { + case TransportStopped: speed = 0; _starting = false; break; - case JackTransportRolling: + case TransportRolling: speed = 1.0; _starting = false; break; - case JackTransportLooping: + case TransportLooping: speed = 1.0; _starting = false; break; - case JackTransportStarting: + case TransportStarting: _starting = true; // don't adjust speed here, just leave it as it was break; - default: - cerr << "WARNING: Unknown JACK transport state: " << state << endl; } sp = speed; - position = pos.frame; + position = engine.transport_frame(); return true; } diff --git a/libs/ardour/ladspa_plugin.cc b/libs/ardour/ladspa_plugin.cc index 78e16f9941..8b089929b5 100644 --- a/libs/ardour/ladspa_plugin.cc +++ b/libs/ardour/ladspa_plugin.cc @@ -44,8 +44,6 @@ #include "pbd/xml++.h" #include "pbd/stacktrace.h" -#include "midi++/manager.h" - #include "ardour/session.h" #include "ardour/ladspa_plugin.h" #include "ardour/buffer_set.h" @@ -129,7 +127,9 @@ LadspaPlugin::init (string module_path, uint32_t index, framecnt_t rate) port_cnt = parameter_count(); _control_data = new LADSPA_Data[port_cnt]; + memset (_control_data, 0, sizeof (LADSPA_Data) * port_cnt); _shadow_data = new LADSPA_Data[port_cnt]; + memset (_shadow_data, 0, sizeof (LADSPA_Data) * port_cnt); for (i = 0; i < port_cnt; ++i) { if (LADSPA_IS_PORT_CONTROL(port_descriptor (i))) { diff --git a/libs/ardour/ltc_slave.cc b/libs/ardour/ltc_slave.cc index ac93ccdb40..eb168c9554 100644 --- a/libs/ardour/ltc_slave.cc +++ b/libs/ardour/ltc_slave.cc @@ -134,7 +134,7 @@ LTC_Slave::resync_latency() if (!session.deletion_in_progress() && session.ltc_output_io()) { /* check if Port exits */ boost::shared_ptr<Port> ltcport = session.ltc_input_port(); - ltcport->get_connected_latency_range(ltc_slave_latency, false); + ltcport->get_connected_latency_range (ltc_slave_latency, false); } } @@ -150,9 +150,9 @@ LTC_Slave::reset() } void -LTC_Slave::parse_ltc(const jack_nframes_t nframes, const jack_default_audio_sample_t * const in, const ARDOUR::framecnt_t posinfo) +LTC_Slave::parse_ltc(const pframes_t nframes, const Sample* const in, const framecnt_t posinfo) { - jack_nframes_t i; + pframes_t i; unsigned char sound[8192]; if (nframes > 8192) { /* TODO warn once or wrap, loop conversion below @@ -413,22 +413,22 @@ LTC_Slave::init_engine_dll (framepos_t pos, int32_t inc) } /* main entry point from session_process.cc - * called from jack_process callback context - * so it is OK to use jack_port_get_buffer() + * called from process callback context + * so it is OK to use get_buffer() */ bool LTC_Slave::speed_and_position (double& speed, framepos_t& pos) { bool engine_init_called = false; - framepos_t now = session.engine().frame_time_at_cycle_start(); + framepos_t now = session.engine().sample_time_at_cycle_start(); framepos_t sess_pos = session.transport_frame(); // corresponds to now - framecnt_t nframes = session.engine().frames_per_cycle(); + framecnt_t nframes = session.engine().samples_per_cycle(); - jack_default_audio_sample_t *in; + Sample* in; boost::shared_ptr<Port> ltcport = session.ltc_input_port(); - in = (jack_default_audio_sample_t*) jack_port_get_buffer (ltcport->jack_port(), nframes); + in = (Sample*) AudioEngine::instance()->port_engine().get_buffer (ltcport->port_handle(), nframes); frameoffset_t skip = now - (monotonic_cnt + nframes); monotonic_cnt = now; @@ -441,7 +441,7 @@ LTC_Slave::speed_and_position (double& speed, framepos_t& pos) else if (engine_dll_initstate != transport_direction && ltc_speed != 0) { engine_dll_initstate = transport_direction; init_engine_dll(last_ltc_frame + rint(ltc_speed * double(2 * nframes + now - last_timestamp)), - session.engine().frames_per_cycle()); + session.engine().samples_per_cycle()); engine_init_called = true; } @@ -521,8 +521,8 @@ LTC_Slave::speed_and_position (double& speed, framepos_t& pos) t0 = t1; t1 += b * e + e2; e2 += c * e; - speed_flt = (t1 - t0) / double(session.engine().frames_per_cycle()); - DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().frames_per_cycle() )); + speed_flt = (t1 - t0) / double(session.engine().samples_per_cycle()); + DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", t0, t1, e, speed_flt, e2 - session.engine().samples_per_cycle() )); } else { DEBUG_TRACE (DEBUG::LTC, string_compose ("LTC adjusting elapsed (no DLL) from %1 by %2\n", elapsed, (2 * nframes * ltc_speed))); speed_flt = 0; diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index 30134a5dbb..6288616824 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -291,7 +291,7 @@ LV2Plugin::init(const void* c_plugin, framecnt_t rate) _latency_control_port = 0; _next_cycle_start = std::numeric_limits<framepos_t>::max(); _next_cycle_speed = 1.0; - _block_length = _engine.frames_per_cycle(); + _block_length = _engine.samples_per_cycle(); _seq_size = _engine.raw_buffer_size(DataType::MIDI); _state_version = 0; _was_activated = false; @@ -1925,7 +1925,7 @@ LV2Plugin::Impl::designated_input (const char* uri, void** bufptrs[], void** buf return port; } -static bool lv2_filter (const string& str, void * /* arg*/) +static bool lv2_filter (const string& str, void* /*arg*/) { /* Not a dotfile, has a prefix before a period, suffix is "lv2" */ diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc index b47c3ca152..d75b861ea1 100644 --- a/libs/ardour/midi_buffer.cc +++ b/libs/ardour/midi_buffer.cc @@ -22,6 +22,7 @@ #include "pbd/malign.h" #include "pbd/compose.h" #include "pbd/debug.h" +#include "pbd/stacktrace.h" #include "ardour/debug.h" #include "ardour/midi_buffer.h" @@ -133,6 +134,7 @@ MidiBuffer::push_back(const Evoral::MIDIEvent<TimeType>& ev) if (_size + stamp_size + ev.size() >= _capacity) { cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; + PBD::stacktrace (cerr, 20); return false; } @@ -171,7 +173,9 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) #endif if (_size + stamp_size + size >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; + cerr << "MidiBuffer::push_back2 failed (buffer is full; _size = " << _size << " capacity " + << _capacity << " stamp " << stamp_size << " size = " << size << ")" << endl; + PBD::stacktrace (cerr, 20); return false; } @@ -190,55 +194,6 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) return true; } - -/** Push an event into the buffer. - * - * Note that the raw MIDI pointed to by ev will be COPIED and unmodified. - * That is, the caller still owns it, if it needs freeing it's Not My Problem(TM). - * Realtime safe. - * @return false if operation failed (not enough room) - */ -bool -MidiBuffer::push_back(const jack_midi_event_t& ev) -{ - const size_t stamp_size = sizeof(TimeType); - - if (_size + stamp_size + ev.size >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; - return false; - } - - if (!Evoral::midi_event_is_valid(ev.buffer, ev.size)) { - cerr << "WARNING: MidiBuffer ignoring illegal MIDI event" << endl; - return false; - } - -#ifndef NDEBUG - if (DEBUG::MidiIO & PBD::debug_bits) { - DEBUG_STR_DECL(a); - DEBUG_STR_APPEND(a, string_compose ("midibuffer %1 push jack event @ %2 sz %3 ", this, ev.time, ev.size)); - for (size_t i=0; i < ev.size; ++i) { - DEBUG_STR_APPEND(a,hex); - DEBUG_STR_APPEND(a,"0x"); - DEBUG_STR_APPEND(a,(int)ev.buffer[i]); - DEBUG_STR_APPEND(a,' '); - } - DEBUG_STR_APPEND(a,'\n'); - DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str()); - } -#endif - - uint8_t* const write_loc = _data + _size; - *((TimeType*)write_loc) = ev.time; - memcpy(write_loc + stamp_size, ev.buffer, ev.size); - - _size += stamp_size + ev.size; - _silent = false; - - return true; -} - - /** Reserve space for a new event in the buffer. * * This call is for copying MIDI directly into the buffer, the data location diff --git a/libs/ardour/midi_clock_slave.cc b/libs/ardour/midi_clock_slave.cc index 75f3c13f1c..a52854d954 100644 --- a/libs/ardour/midi_clock_slave.cc +++ b/libs/ardour/midi_clock_slave.cc @@ -30,6 +30,8 @@ #include "midi++/port.h" #include "ardour/debug.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/slave.h" #include "ardour/tempo.h" @@ -40,7 +42,7 @@ using namespace ARDOUR; using namespace MIDI; using namespace PBD; -MIDIClock_Slave::MIDIClock_Slave (Session& s, MIDI::Port& p, int ppqn) +MIDIClock_Slave::MIDIClock_Slave (Session& s, MidiPort& p, int ppqn) : ppqn (ppqn) , bandwidth (10.0 / 60.0) // 1 BpM = 1 / 60 Hz { @@ -63,19 +65,18 @@ MIDIClock_Slave::~MIDIClock_Slave() } void -MIDIClock_Slave::rebind (MIDI::Port& p) +MIDIClock_Slave::rebind (MidiPort& port) { - port_connections.drop_connections(); + DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port.name())); - port = &p; + port_connections.drop_connections (); - DEBUG_TRACE (DEBUG::MidiClock, string_compose ("MIDIClock_Slave: connecting to port %1\n", port->name())); + port.self_parser().timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2)); + port.self_parser().start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2)); + port.self_parser().contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2)); + port.self_parser().stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2)); + port.self_parser().position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3)); - port->parser()->timing.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::update_midi_clock, this, _1, _2)); - port->parser()->start.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::start, this, _1, _2)); - port->parser()->contineu.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::contineu, this, _1, _2)); - port->parser()->stop.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::stop, this, _1, _2)); - port->parser()->position.connect_same_thread (port_connections, boost::bind (&MIDIClock_Slave::position, this, _1, _2, 3)); } void diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index cf143bf595..39932db8bf 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -201,8 +201,8 @@ MidiDiskstream::non_realtime_input_change () seek (_session.transport_frame()); } - g_atomic_int_set(&_frames_pending_write, 0); - g_atomic_int_set(&_num_captured_loops, 0); + g_atomic_int_set(const_cast<gint*> (&_frames_pending_write), 0); + g_atomic_int_set(const_cast<gint*> (&_num_captured_loops), 0); } int @@ -375,8 +375,8 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t } _write_source->mark_write_starting_now( capture_start_frame, capture_captured, loop_length); - g_atomic_int_set(&_frames_pending_write, 0); - g_atomic_int_set(&_num_captured_loops, 0); + g_atomic_int_set(const_cast<gint*> (&_frames_pending_write), 0); + g_atomic_int_set(const_cast<gint*> (&_num_captured_loops), 0); was_recording = true; } } @@ -445,7 +445,7 @@ MidiDiskstream::process (BufferSet& bufs, framepos_t transport_frame, pframes_t break; } } - g_atomic_int_add(&_frames_pending_write, nframes); + g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), nframes); if (buf.size() != 0) { Glib::Threads::Mutex::Lock lm (_gui_feed_buffer_mutex, Glib::Threads::TRY_LOCK); @@ -798,7 +798,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) return 0; } - const framecnt_t total = g_atomic_int_get(&_frames_pending_write); + const framecnt_t total = g_atomic_int_get(const_cast<gint*> (&_frames_pending_write)); if (total == 0 || _capture_buf->read_space() == 0 || @@ -833,7 +833,7 @@ MidiDiskstream::do_flush (RunContext /*context*/, bool force_flush) error << string_compose(_("MidiDiskstream %1: cannot write to disk"), id()) << endmsg; return -1; } - g_atomic_int_add(&_frames_pending_write, -to_write); + g_atomic_int_add(const_cast<gint*> (&_frames_pending_write), -to_write); } out: @@ -1043,7 +1043,7 @@ MidiDiskstream::transport_looped (framepos_t) the Source and/or entirely after the capture is finished. */ if (was_recording) { - g_atomic_int_add(&_num_captured_loops, 1); + g_atomic_int_add(const_cast<gint*> (&_num_captured_loops), 1); } } @@ -1110,7 +1110,7 @@ MidiDiskstream::prep_record_enable () boost::shared_ptr<MidiPort> sp = _source_port.lock (); if (sp && Config->get_monitoring_model() == HardwareMonitoring) { - sp->request_jack_monitors_input (!(_session.config.get_auto_input() && rolling)); + sp->request_input_monitoring (!(_session.config.get_auto_input() && rolling)); } return true; @@ -1258,12 +1258,12 @@ MidiDiskstream::allocate_temporary_buffers () } void -MidiDiskstream::ensure_jack_monitors_input (bool yn) +MidiDiskstream::ensure_input_monitoring (bool yn) { boost::shared_ptr<MidiPort> sp = _source_port.lock (); if (sp) { - sp->ensure_jack_monitors_input (yn); + sp->ensure_input_monitoring (yn); } } diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index 72150f8cfc..de2263fad6 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -26,11 +26,14 @@ using namespace ARDOUR; using namespace std; -MidiPort::MidiPort (const std::string& name, Flags flags) +#define port_engine AudioEngine::instance()->port_engine() + +MidiPort::MidiPort (const std::string& name, PortFlags flags) : Port (name, DataType::MIDI, flags) , _has_been_mixed_down (false) , _resolve_required (false) , _input_active (true) + , _always_parse (false) { _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } @@ -43,12 +46,32 @@ MidiPort::~MidiPort() void MidiPort::cycle_start (pframes_t nframes) { + framepos_t now = AudioEngine::instance()->sample_time_at_cycle_start(); + Port::cycle_start (nframes); _buffer->clear (); if (sends_output ()) { - jack_midi_clear_buffer (jack_port_get_buffer (_jack_port, nframes)); + port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes)); + } + + if (_always_parse) { + MidiBuffer& mb (get_midi_buffer (nframes)); + + /* dump incoming MIDI to parser */ + + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + uint8_t* buf = (*b).buffer(); + + _self_parser.set_timestamp (now + (*b).time()); + + uint32_t limit = (*b).size(); + + for (size_t n = 0; n < limit; ++n) { + _self_parser.scanner (buf[n]); + } + } } } @@ -63,31 +86,33 @@ MidiPort::get_midi_buffer (pframes_t nframes) if (_input_active) { - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); - const pframes_t event_count = jack_midi_get_event_count (jack_buffer); - - /* suck all relevant MIDI events from the JACK MIDI port buffer + 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 */ - + for (pframes_t i = 0; i < event_count; ++i) { - jack_midi_event_t ev; + pframes_t timestamp; + size_t size; + uint8_t* buf; - jack_midi_event_get (&ev, jack_buffer, i); + port_engine.midi_event_get (timestamp, size, &buf, buffer, i); - if (ev.buffer[0] == 0xfe) { + if (buf[0] == 0xfe) { /* throw away active sensing */ continue; } /* check that the event is in the acceptable time range */ - if ((ev.time >= (_global_port_buffer_offset + _port_buffer_offset)) && - (ev.time < (_global_port_buffer_offset + _port_buffer_offset + nframes))) { - _buffer->push_back (ev); + if ((timestamp >= (_global_port_buffer_offset + _port_buffer_offset)) && + (timestamp < (_global_port_buffer_offset + _port_buffer_offset + nframes))) { + _buffer->push_back (timestamp, size, buf); } else { - cerr << "Dropping incoming MIDI at time " << ev.time << "; offset=" + cerr << "Dropping incoming MIDI at time " << timestamp << "; offset=" << _global_port_buffer_offset << " limit=" << (_global_port_buffer_offset + _port_buffer_offset + nframes) << "\n"; } @@ -121,7 +146,7 @@ MidiPort::cycle_split () } void -MidiPort::resolve_notes (void* jack_buffer, MidiBuffer::TimeType when) +MidiPort::resolve_notes (void* port_buffer, MidiBuffer::TimeType when) { for (uint8_t channel = 0; channel <= 0xF; channel++) { @@ -132,13 +157,13 @@ MidiPort::resolve_notes (void* jack_buffer, MidiBuffer::TimeType when) * that prioritize sustain over AllNotesOff */ - if (jack_midi_event_write (jack_buffer, when, ev, 3) != 0) { + if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) { cerr << "failed to deliver sustain-zero on channel " << channel << " on port " << name() << endl; } ev[1] = MIDI_CTL_ALL_NOTES_OFF; - if (jack_midi_event_write (jack_buffer, 0, ev, 3) != 0) { + if (port_engine.midi_event_put (port_buffer, 0, ev, 3) != 0) { cerr << "failed to deliver ALL NOTES OFF on channel " << channel << " on port " << name() << endl; } } @@ -149,11 +174,11 @@ MidiPort::flush_buffers (pframes_t nframes) { if (sends_output ()) { - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); - + void* port_buffer = port_engine.get_buffer (_port_handle, nframes); + if (_resolve_required) { /* resolve all notes at the start of the buffer */ - resolve_notes (jack_buffer, 0); + resolve_notes (port_buffer, 0); _resolve_required = false; } @@ -166,7 +191,7 @@ MidiPort::flush_buffers (pframes_t nframes) assert (ev.time() < (nframes + _global_port_buffer_offset + _port_buffer_offset)); if (ev.time() >= _global_port_buffer_offset + _port_buffer_offset) { - if (jack_midi_event_write (jack_buffer, (jack_nframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { + if (port_engine.midi_event_put (port_buffer, (pframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { cerr << "write failed, drop flushed note off on the floor, time " << ev.time() << " > " << _global_port_buffer_offset + _port_buffer_offset << endl; } @@ -202,6 +227,7 @@ MidiPort::reset () { Port::reset (); delete _buffer; + cerr << name() << " new MIDI buffer of size " << AudioEngine::instance()->raw_buffer_size (DataType::MIDI) << endl; _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } @@ -210,3 +236,9 @@ MidiPort::set_input_active (bool yn) { _input_active = yn; } + +void +MidiPort::set_always_parse (bool yn) +{ + _always_parse = yn; +} diff --git a/libs/ardour/midi_ui.cc b/libs/ardour/midi_ui.cc index 5822cc509e..e6d49175bd 100644 --- a/libs/ardour/midi_ui.cc +++ b/libs/ardour/midi_ui.cc @@ -22,11 +22,11 @@ #include "pbd/pthread_utils.h" -#include "midi++/manager.h" -#include "midi++/port.h" - +#include "ardour/async_midi_port.h" #include "ardour/debug.h" #include "ardour/audioengine.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" #include "ardour/midi_ui.h" #include "ardour/session.h" #include "ardour/session_event.h" @@ -48,7 +48,6 @@ MidiControlUI::MidiControlUI (Session& s) : AbstractUI<MidiUIRequest> (X_("midiui")) , _session (s) { - MIDI::Manager::instance()->PortsChanged.connect_same_thread (rebind_connection, boost::bind (&MidiControlUI::change_midi_ports, this)); _instance = this; } @@ -83,20 +82,10 @@ MidiControlUI::do_request (MidiUIRequest* req) } } -void -MidiControlUI::change_midi_ports () -{ - MidiUIRequest* req = get_request (PortChange); - if (req == 0) { - return; - } - send_request (req); -} - bool -MidiControlUI::midi_input_handler (IOCondition ioc, MIDI::Port* port) +MidiControlUI::midi_input_handler (IOCondition ioc, AsyncMIDIPort* port) { - DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on %1\n", port->name())); + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("something happend on %1\n", ((ARDOUR::Port*)port)->name())); if (ioc & ~IO_IN) { return false; @@ -108,8 +97,8 @@ MidiControlUI::midi_input_handler (IOCondition ioc, MIDI::Port* port) CrossThreadChannel::drain (port->selectable()); #endif - DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name())); - framepos_t now = _session.engine().frame_time(); + DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", ((ARDOUR::Port*)port)->name())); + framepos_t now = _session.engine().sample_time(); port->parse (now); } @@ -130,22 +119,19 @@ MidiControlUI::clear_ports () void MidiControlUI::reset_ports () { - clear_ports (); - - boost::shared_ptr<const MIDI::Manager::PortList> plist = MIDI::Manager::instance()->get_midi_ports (); + if (port_sources.empty()) { + AsyncMIDIPort* async = dynamic_cast<AsyncMIDIPort*> (_session.midi_input_port()); - for (MIDI::Manager::PortList::const_iterator i = plist->begin(); i != plist->end(); ++i) { - - if (!(*i)->centrally_parsed()) { - continue; + if (!async) { + return; } int fd; - if ((fd = (*i)->selectable ()) >= 0) { + if ((fd = async->selectable ()) >= 0) { Glib::RefPtr<IOSource> psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR); - - psrc->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), *i)); + + psrc->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), async)); psrc->attach (_main_loop->get_context()); // glibmm hack: for now, store only the GSource* diff --git a/libs/ardour/midiport_manager.cc b/libs/ardour/midiport_manager.cc new file mode 100644 index 0000000000..d17401cee8 --- /dev/null +++ b/libs/ardour/midiport_manager.cc @@ -0,0 +1,170 @@ +/* + Copyright (C) 1998-99 Paul Barton-Davis + 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. + + $Id$ +*/ + +#include "ardour/audioengine.h" +#include "ardour/async_midi_port.h" +#include "ardour/midiport_manager.h" +#include "ardour/rc_configuration.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace std; +using namespace MIDI; +using namespace PBD; + + +MidiPortManager::MidiPortManager () +{ + create_ports (); +} + +MidiPortManager::~MidiPortManager () +{ + if (_midi_in) { + AudioEngine::instance()->unregister_port (_midi_in); + } + if (_midi_in) { + AudioEngine::instance()->unregister_port (_midi_in); + } + if (_mtc_input_port) { + AudioEngine::instance()->unregister_port (_mtc_input_port); + } + if (_mtc_output_port) { + AudioEngine::instance()->unregister_port (_mtc_output_port); + } + if (_midi_clock_input_port) { + AudioEngine::instance()->unregister_port (_midi_clock_input_port); + } + if (_midi_clock_output_port) { + AudioEngine::instance()->unregister_port (_midi_clock_output_port); + } + +} + +void +MidiPortManager::create_ports () +{ + /* this method is idempotent + */ + + if (_midi_in) { + return; + } + + _midi_in = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MIDI control in"), true); + _midi_out = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MIDI control out"), true); + + _mmc_in = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MMC in"), true); + _mmc_out = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MMC out"), true); + + /* XXX nasty type conversion needed because of the mixed inheritance + * required to integrate MIDI::IPMidiPort and ARDOUR::AsyncMIDIPort. + * + * At some point, we'll move IPMidiPort into Ardour and make it + * inherit from ARDOUR::MidiPort not MIDI::Port, and then this + * mess can go away + */ + + _midi_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_in).get(); + _midi_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out).get(); + + _mmc_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_in).get(); + _mmc_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_out).get(); + + /* Now register ports used for sync (MTC and MIDI Clock) + */ + + boost::shared_ptr<ARDOUR::Port> p; + + p = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MTC in")); + _mtc_input_port = boost::dynamic_pointer_cast<MidiPort> (p); + p = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MTC out")); + _mtc_output_port= boost::dynamic_pointer_cast<MidiPort> (p); + + p = AudioEngine::instance()->register_input_port (DataType::MIDI, _("MIDI Clock in")); + _midi_clock_input_port = boost::dynamic_pointer_cast<MidiPort> (p); + p = AudioEngine::instance()->register_output_port (DataType::MIDI, _("MIDI Clock out")); + _midi_clock_output_port= boost::dynamic_pointer_cast<MidiPort> (p); + + /* These ports all need their incoming data handled in + * Port::cycle_start() and so ... + */ + + _mtc_input_port->set_always_parse (true); + _mtc_output_port->set_always_parse (true); + _midi_clock_input_port->set_always_parse (true); + _midi_clock_output_port->set_always_parse (true); +} + +void +MidiPortManager::set_midi_port_states (const XMLNodeList&nodes) +{ + XMLProperty* prop; + typedef map<std::string,boost::shared_ptr<Port> > PortMap; + PortMap ports; + const int version = 0; + + ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port)); + ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port)); + ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port)); + ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port)); + ports.insert (make_pair (_midi_input_port->name(), _midi_in)); + ports.insert (make_pair (_midi_output_port->name(), _midi_out)); + ports.insert (make_pair (_mmc_input_port->name(), _mmc_in)); + ports.insert (make_pair (_mmc_output_port->name(), _mmc_out)); + + for (XMLNodeList::const_iterator n = nodes.begin(); n != nodes.end(); ++n) { + if ((prop = (*n)->property (X_("name"))) == 0) { + continue; + } + + PortMap::iterator p = ports.find (prop->value()); + if (p == ports.end()) { + continue; + } + + p->second->set_state (**n, version); + } +} + +list<XMLNode*> +MidiPortManager::get_midi_port_states () const +{ + typedef map<std::string,boost::shared_ptr<Port> > PortMap; + PortMap ports; + list<XMLNode*> s; + + ports.insert (make_pair (_mtc_input_port->name(), _mtc_input_port)); + ports.insert (make_pair (_mtc_output_port->name(), _mtc_output_port)); + ports.insert (make_pair (_midi_clock_input_port->name(), _midi_clock_input_port)); + ports.insert (make_pair (_midi_clock_output_port->name(), _midi_clock_output_port)); + ports.insert (make_pair (_midi_input_port->name(), _midi_in)); + ports.insert (make_pair (_midi_output_port->name(), _midi_out)); + ports.insert (make_pair (_mmc_input_port->name(), _mmc_in)); + ports.insert (make_pair (_mmc_output_port->name(), _mmc_out)); + + for (PortMap::const_iterator p = ports.begin(); p != ports.end(); ++p) { + s.push_back (&p->second->get_state()); + } + + return s; +} + + diff --git a/libs/ardour/mtc_slave.cc b/libs/ardour/mtc_slave.cc index c41c6d6c94..2c351ab68a 100644 --- a/libs/ardour/mtc_slave.cc +++ b/libs/ardour/mtc_slave.cc @@ -25,11 +25,12 @@ #include "pbd/error.h" #include "pbd/pthread_utils.h" -#include "midi++/port.h" +#include "ardour/audioengine.h" #include "ardour/debug.h" -#include "ardour/slave.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/session.h" -#include "ardour/audioengine.h" +#include "ardour/slave.h" #include <glibmm/timer.h> @@ -50,8 +51,9 @@ using namespace Timecode; */ const int MTC_Slave::frame_tolerance = 2; -MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) +MTC_Slave::MTC_Slave (Session& s, MidiPort& p) : session (s) + , port (&p) { can_notify_on_unknown_rate = true; did_reset_tc_format = false; @@ -72,7 +74,10 @@ MTC_Slave::MTC_Slave (Session& s, MIDI::Port& p) session.config.ParameterChanged.connect_same_thread (config_connection, boost::bind (&MTC_Slave::parameter_changed, this, _1)); parse_timecode_offset(); reset (true); - rebind (p); + + port->self_parser().mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3)); + port->self_parser().mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3)); + port->self_parser().mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1)); } MTC_Slave::~MTC_Slave() @@ -96,15 +101,12 @@ MTC_Slave::~MTC_Slave() } void -MTC_Slave::rebind (MIDI::Port& p) +MTC_Slave::rebind (MidiPort& p) { port_connections.drop_connections (); port = &p; - port->parser()->mtc_time.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_time, this, _1, _2, _3)); - port->parser()->mtc_qtr.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_qtr, this, _1, _2, _3)); - port->parser()->mtc_status.connect_same_thread (port_connections, boost::bind (&MTC_Slave::update_mtc_status, this, _1)); } void @@ -156,7 +158,8 @@ MTC_Slave::outside_window (framepos_t pos) const bool MTC_Slave::locked () const { - return port->parser()->mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0; + DEBUG_TRACE (DEBUG::MTC, string_compose ("locked ? %1 last %2 initstate %3\n", port->self_parser().mtc_locked(), last_inbound_frame, engine_dll_initstate)); + return port->self_parser().mtc_locked() && last_inbound_frame !=0 && engine_dll_initstate !=0; } bool @@ -257,7 +260,6 @@ MTC_Slave::init_mtc_dll(framepos_t tme, double qtr) DEBUG_TRACE (DEBUG::MTC, string_compose ("[re-]init MTC DLL %1 %2 %3\n", t0, t1, e2)); } - /* called from MIDI parser */ void MTC_Slave::update_mtc_qtr (Parser& /*p*/, int which_qtr, framepos_t now) @@ -308,7 +310,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now a locate command via MMC. */ - //DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::update_mtc_time - TID:%1\n", pthread_name())); + DEBUG_TRACE (DEBUG::MTC, string_compose ("MTC::update_mtc_time - TID:%1\n", ::pthread_self())); TimecodeFormat tc_format; bool reset_tc = true; @@ -424,7 +426,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now now, timecode, mtc_frame, was_full, speedup_due_to_tc_mismatch)); if (was_full || outside_window (mtc_frame)) { - DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC or outside window. - TID:%1\n", pthread_name())); + DEBUG_TRACE (DEBUG::MTC, string_compose ("update_mtc_time: full TC %1 or outside window %2\n", was_full, outside_window (mtc_frame))); session.request_locate (mtc_frame, false); session.request_transport_speed (0); update_mtc_status (MIDI::MTC_Stopped); @@ -448,7 +450,7 @@ MTC_Slave::update_mtc_time (const MIDI::byte *msg, bool was_full, framepos_t now DEBUG_TRACE (DEBUG::MTC, string_compose ("new mtc_frame: %1 | MTC-FpT: %2 A3-FpT:%3\n", mtc_frame, (4.0*qtr), session.frames_per_timecode_frame())); - switch (port->parser()->mtc_running()) { + switch (port->self_parser().mtc_running()) { case MTC_Backward: mtc_frame -= mtc_off; qtr *= -1.0; @@ -528,9 +530,10 @@ MTC_Slave::reset_window (framepos_t root) of acceptable MTC frames wide open. otherwise, shrink it down to just 2 video frames ahead of the window root (taking direction into account). */ + framecnt_t const d = (quarter_frame_duration * 4 * frame_tolerance); - switch (port->parser()->mtc_running()) { + switch (port->self_parser().mtc_running()) { case MTC_Forward: window_begin = root; transport_direction = 1; @@ -553,7 +556,7 @@ MTC_Slave::reset_window (framepos_t root) break; } - DEBUG_TRACE (DEBUG::MTC, string_compose ("legal MTC window now %1 .. %2\n", window_begin, window_end)); + DEBUG_TRACE (DEBUG::MTC, string_compose ("reset MTC window @ %3, now %1 .. %2\n", window_begin, window_end, root)); } void @@ -577,11 +580,11 @@ MTC_Slave::init_engine_dll (framepos_t pos, framepos_t inc) } /* main entry point from session_process.cc - * in jack_process callback context */ +xo * in process callback context */ bool MTC_Slave::speed_and_position (double& speed, framepos_t& pos) { - framepos_t now = session.engine().frame_time_at_cycle_start(); + framepos_t now = session.engine().sample_time_at_cycle_start(); framepos_t sess_pos = session.transport_frame(); // corresponds to now //sess_pos -= session.engine().frames_since_cycle_start(); @@ -591,11 +594,21 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) read_current (&last); + DEBUG_TRACE (DEBUG::MTC, string_compose ("speed&pos: timestamp %1 speed %2 initstate %3 dir %4 tpos %5 now %6 last-in %7\n", + last.timestamp, + last.speed, + engine_dll_initstate, + transport_direction, + sess_pos, + now, + last_inbound_frame)); + /* re-init engine DLL here when state changed (direction, first_mtc_timestamp) */ - if (last.timestamp == 0) { engine_dll_initstate = 0; } - else if (engine_dll_initstate != transport_direction && last.speed != 0) { + if (last.timestamp == 0) { + engine_dll_initstate = 0; + } else if (engine_dll_initstate != transport_direction && last.speed != 0) { engine_dll_initstate = transport_direction; - init_engine_dll(last.position, session.engine().frames_per_cycle()); + init_engine_dll(last.position, session.engine().samples_per_cycle()); engine_dll_reinitialized = true; } @@ -627,9 +640,7 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) /* interpolate position according to speed and time since last quarter-frame*/ if (speed_flt == 0.0f) { elapsed = 0; - } - else - { + } else { /* scale elapsed time by the current MTC speed */ elapsed = (framecnt_t) rint (speed_flt * (now - last.timestamp)); if (give_slave_full_control_over_transport_speed() && !engine_dll_reinitialized) { @@ -645,8 +656,8 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) te0 = te1; te1 += be * e + ee2; ee2 += ce * e; - speed_flt = (te1 - te0) / double(session.engine().frames_per_cycle()); - DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().frames_per_cycle() )); + speed_flt = (te1 - te0) / double(session.engine().samples_per_cycle()); + DEBUG_TRACE (DEBUG::MTC, string_compose ("engine DLL t0:%1 t1:%2 err:%3 spd:%4 ddt:%5\n", te0, te1, e, speed_flt, ee2 - session.engine().samples_per_cycle() )); } } @@ -659,14 +670,13 @@ MTC_Slave::speed_and_position (double& speed, framepos_t& pos) */ if (!session.actively_recording() && speed != 0 - && ( (pos < 0) || (labs(pos - sess_pos) > 3 * session.frame_rate()) ) - ) { + && ((pos < 0) || (labs(pos - sess_pos) > 3 * session.frame_rate()))) { engine_dll_initstate = 0; queue_reset (false); } /* provide a .1% deadzone to lock the speed */ - if (fabs(speed - 1.0) <= 0.001) + if (fabs (speed - 1.0) <= 0.001) speed = 1.0; DEBUG_TRACE (DEBUG::MTC, string_compose ("MTCsync spd: %1 pos: %2 | last-pos: %3 elapsed: %4 delta: %5\n", diff --git a/libs/ardour/panner_manager.cc b/libs/ardour/panner_manager.cc index f757279d29..8611c7bc86 100644 --- a/libs/ardour/panner_manager.cc +++ b/libs/ardour/panner_manager.cc @@ -84,6 +84,7 @@ PannerManager::discover_panners () panner_discover (*i); } } + int PannerManager::panner_discover (string path) { diff --git a/libs/ardour/port.cc b/libs/ardour/port.cc index 0f28b31b57..d8a32e3467 100644 --- a/libs/ardour/port.cc +++ b/libs/ardour/port.cc @@ -32,6 +32,7 @@ #include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/port.h" +#include "ardour/port_engine.h" #include "i18n.h" @@ -42,13 +43,19 @@ using namespace PBD; PBD::Signal2<void,boost::shared_ptr<Port>, boost::shared_ptr<Port> > Port::PostDisconnect; PBD::Signal0<void> Port::PortDrop; -AudioEngine* Port::_engine = 0; bool Port::_connecting_blocked = false; pframes_t Port::_global_port_buffer_offset = 0; pframes_t Port::_cycle_nframes = 0; +std::string Port::state_node_name = X_("Port"); + +/* a handy define to shorten what would otherwise be a needlessly verbose + * repeated phrase + */ +#define port_engine AudioEngine::instance()->port_engine() +#define port_manager AudioEngine::instance() /** @param n Port short name */ -Port::Port (std::string const & n, DataType t, Flags f) +Port::Port (std::string const & n, DataType t, PortFlags f) : _port_buffer_offset (0) , _name (n) , _flags (f) @@ -60,21 +67,17 @@ Port::Port (std::string const & n, DataType t, Flags f) _private_capture_latency.max = 0; /* Unfortunately we have to pass the DataType into this constructor so that - we can create the right kind of JACK port; aside from this we'll use the + we can create the right kind of port; aside from this we'll use the virtual function type () to establish type. */ assert (_name.find_first_of (':') == std::string::npos); - if (!_engine->connected()) { + if ((_port_handle = port_engine.register_port (_name, t, _flags)) == 0) { + cerr << "Failed to register port \"" << _name << "\", reason is unknown from here\n"; throw failed_constructor (); } - - if ((_jack_port = jack_port_register (_engine->jack (), _name.c_str (), t.to_jack_type (), _flags, 0)) == 0) { - cerr << "Failed to register JACK port \"" << _name << "\", reason is unknown from here\n"; - throw failed_constructor (); - } - + PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::drop, this)); } @@ -87,11 +90,10 @@ Port::~Port () void Port::drop () { - if (_jack_port) { - if (_engine->jack ()) { - jack_port_unregister (_engine->jack (), _jack_port); - } - _jack_port = 0; + if (_port_handle) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("drop handle for port %1\n", name())); + port_engine.unregister_port (_port_handle); + _port_handle = 0; } } @@ -99,18 +101,18 @@ Port::drop () bool Port::connected () const { - return (jack_port_connected (_jack_port) != 0); + return (port_engine.connected (_port_handle) != 0); } int Port::disconnect_all () { - jack_port_disconnect (_engine->jack(), _jack_port); + port_engine.disconnect_all (_port_handle); _connections.clear (); /* a cheaper, less hacky way to do boost::shared_from_this() ... */ - boost::shared_ptr<Port> pself = _engine->get_port_by_name (name()); + boost::shared_ptr<Port> pself = port_manager->get_port_by_name (name()); PostDisconnect (pself, boost::shared_ptr<Port>()); // emit signal return 0; @@ -122,48 +124,29 @@ Port::disconnect_all () bool Port::connected_to (std::string const & o) const { - if (!_engine->connected()) { - /* in some senses, this answer isn't the right one all the time, - because we know about our connections and will re-establish - them when we reconnect to JACK. - */ + if (!port_engine.connected()) { return false; } - return jack_port_connected_to (_jack_port, - _engine->make_port_name_non_relative(o).c_str ()); + return port_engine.connected_to (_port_handle, AudioEngine::instance()->make_port_name_non_relative (o)); } -/** @param o Filled in with port full names of ports that we are connected to */ int Port::get_connections (std::vector<std::string> & c) const { - int n = 0; - - if (_engine->connected()) { - const char** jc = jack_port_get_connections (_jack_port); - if (jc) { - for (int i = 0; jc[i]; ++i) { - c.push_back (jc[i]); - ++n; - } - - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } - } + if (!port_engine.connected()) { + c.insert (c.end(), _connections.begin(), _connections.end()); + return c.size(); } - return n; + return port_engine.get_connections (_port_handle, c); } int Port::connect (std::string const & other) { - std::string const other_shrt = _engine->make_port_name_non_relative (other); - std::string const this_shrt = _engine->make_port_name_non_relative (_name); + std::string const other_name = AudioEngine::instance()->make_port_name_non_relative (other); + std::string const our_name = AudioEngine::instance()->make_port_name_non_relative (_name); int r = 0; @@ -172,9 +155,11 @@ Port::connect (std::string const & other) } if (sends_output ()) { - r = jack_connect (_engine->jack (), this_shrt.c_str (), other_shrt.c_str ()); + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", our_name, other_name)); + r = port_engine.connect (our_name, other_name); } else { - r = jack_connect (_engine->jack (), other_shrt.c_str (), this_shrt.c_str()); + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", other_name, our_name)); + r = port_engine.connect (other_name, our_name); } if (r == 0) { @@ -187,15 +172,15 @@ Port::connect (std::string const & other) int Port::disconnect (std::string const & other) { - std::string const other_fullname = _engine->make_port_name_non_relative (other); - std::string const this_fullname = _engine->make_port_name_non_relative (_name); + std::string const other_fullname = port_manager->make_port_name_non_relative (other); + std::string const this_fullname = port_manager->make_port_name_non_relative (_name); int r = 0; if (sends_output ()) { - r = jack_disconnect (_engine->jack (), this_fullname.c_str (), other_fullname.c_str ()); + r = port_engine.disconnect (this_fullname, other_fullname); } else { - r = jack_disconnect (_engine->jack (), other_fullname.c_str (), this_fullname.c_str ()); + r = port_engine.disconnect (other_fullname, this_fullname); } if (r == 0) { @@ -204,8 +189,8 @@ Port::disconnect (std::string const & other) /* a cheaper, less hacky way to do boost::shared_from_this() ... */ - boost::shared_ptr<Port> pself = _engine->get_port_by_name (name()); - boost::shared_ptr<Port> pother = _engine->get_port_by_name (other); + boost::shared_ptr<Port> pself = AudioEngine::instance()->get_port_by_name (name()); + boost::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other); if (pself && pother) { /* Disconnecting from another Ardour port: need to allow @@ -238,21 +223,22 @@ Port::disconnect (Port* o) } void -Port::set_engine (AudioEngine* e) +Port::request_input_monitoring (bool yn) { - _engine = e; + port_engine.request_input_monitoring (_port_handle, yn); } void -Port::ensure_jack_monitors_input (bool yn) +Port::ensure_input_monitoring (bool yn) { - jack_port_ensure_monitor (_jack_port, yn); + port_engine.ensure_input_monitoring (_port_handle, yn); } bool -Port::jack_monitoring_input () const +Port::monitoring_input () const { - return jack_port_monitoring_input (_jack_port); + + return port_engine.monitoring_input (_port_handle); } void @@ -274,28 +260,23 @@ Port::increment_port_buffer_offset (pframes_t nframes) } void -Port::set_public_latency_range (jack_latency_range_t& range, bool playback) const +Port::set_public_latency_range (LatencyRange& range, bool playback) const { - /* this sets the visible latency that the rest of JACK sees. because we do latency - compensation, all (most) of our visible port latency values are identical. + /* this sets the visible latency that the rest of the port system + sees. because we do latency compensation, all (most) of our visible + port latency values are identical. */ - if (!jack_port_set_latency_range) { - return; - } - DEBUG_TRACE (DEBUG::Latency, string_compose ("SET PORT %1 %4 PUBLIC latency now [%2 - %3]\n", name(), range.min, range.max, (playback ? "PLAYBACK" : "CAPTURE")));; - jack_port_set_latency_range (_jack_port, - (playback ? JackPlaybackLatency : JackCaptureLatency), - &range); + port_engine.set_latency_range (_port_handle, playback, range); } void -Port::set_private_latency_range (jack_latency_range_t& range, bool playback) +Port::set_private_latency_range (LatencyRange& range, bool playback) { if (playback) { _private_playback_latency = range; @@ -313,12 +294,12 @@ Port::set_private_latency_range (jack_latency_range_t& range, bool playback) _private_capture_latency.max)); } - /* push to public (JACK) location so that everyone else can see it */ + /* push to public (port system) location so that everyone else can see it */ set_public_latency_range (range, playback); } -const jack_latency_range_t& +const LatencyRange& Port::private_latency_range (bool playback) const { if (playback) { @@ -338,14 +319,13 @@ Port::private_latency_range (bool playback) const } } -jack_latency_range_t +LatencyRange Port::public_latency_range (bool /*playback*/) const { - jack_latency_range_t r; + LatencyRange r; + + r = port_engine.get_latency_range (_port_handle, sends_output() ? true : false); - jack_port_get_latency_range (_jack_port, - sends_output() ? JackPlaybackLatency : JackCaptureLatency, - &r); DEBUG_TRACE (DEBUG::Latency, string_compose ( "GET PORT %1: %4 PUBLIC latency range %2 .. %3\n", name(), r.min, r.max, @@ -354,27 +334,15 @@ Port::public_latency_range (bool /*playback*/) const } void -Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) const +Port::get_connected_latency_range (LatencyRange& range, bool playback) const { - if (!jack_port_get_latency_range) { - return; - } - vector<string> connections; - jack_client_t* jack = _engine->jack(); - - if (!jack) { - range.min = 0; - range.max = 0; - PBD::warning << _("get_connected_latency_range() called while disconnected from JACK") << endmsg; - return; - } get_connections (connections); if (!connections.empty()) { - range.min = ~((jack_nframes_t) 0); + range.min = ~((pframes_t) 0); range.max = 0; DEBUG_TRACE (DEBUG::Latency, string_compose ("%1: %2 connections to check for latency range\n", name(), connections.size())); @@ -382,21 +350,18 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c for (vector<string>::const_iterator c = connections.begin(); c != connections.end(); ++c) { - jack_latency_range_t lr; + LatencyRange lr; if (!AudioEngine::instance()->port_is_mine (*c)) { - /* port belongs to some other JACK client, use - * JACK to lookup its latency information. + /* port belongs to some other port-system client, use + * the port engine to lookup its latency information. */ - jack_port_t* remote_port = jack_port_by_name (_engine->jack(), (*c).c_str()); + PortEngine::PortHandle remote_port = port_engine.get_port_by_name (*c); if (remote_port) { - jack_port_get_latency_range ( - remote_port, - (playback ? JackPlaybackLatency : JackCaptureLatency), - &lr); + lr = port_engine.get_latency_range (remote_port, playback); DEBUG_TRACE (DEBUG::Latency, string_compose ( "\t%1 <-> %2 : latter has latency range %3 .. %4\n", @@ -440,15 +405,10 @@ Port::get_connected_latency_range (jack_latency_range_t& range, bool playback) c int Port::reestablish () { - jack_client_t* jack = _engine->jack(); + DEBUG_TRACE (DEBUG::Ports, string_compose ("re-establish %1 port %2\n", type().to_string(), _name)); + _port_handle = port_engine.register_port (_name, type(), _flags); - if (!jack) { - return -1; - } - - _jack_port = jack_port_register (jack, _name.c_str(), type().to_jack_type(), _flags, 0); - - if (_jack_port == 0) { + if (_port_handle == 0) { PBD::error << string_compose (_("could not reregister %1"), _name) << endmsg; return -1; } @@ -464,6 +424,8 @@ Port::reconnect () { /* caller must hold process lock; intended to be used only after reestablish() */ + DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2 destinations\n",name(), _connections.size())); + for (std::set<string>::iterator i = _connections.begin(); i != _connections.end(); ++i) { if (connect (*i)) { return -1; @@ -473,7 +435,7 @@ Port::reconnect () return 0; } -/** @param n Short port name (no JACK client name) */ +/** @param n Short port name (no port-system client name) */ int Port::set_name (std::string const & n) { @@ -481,10 +443,10 @@ Port::set_name (std::string const & n) return 0; } - int const r = jack_port_set_name (_jack_port, n.c_str()); + int const r = port_engine.set_port_name (_port_handle, n); if (r == 0) { - _engine->port_renamed (_name, n); + AudioEngine::instance()->port_renamed (_name, n); _name = n; } @@ -492,38 +454,67 @@ Port::set_name (std::string const & n) return r; } -void -Port::request_jack_monitors_input (bool yn) +bool +Port::physically_connected () const { - jack_port_request_monitor (_jack_port, yn); + return port_engine.physically_connected (_port_handle); } -bool -Port::physically_connected () const +XMLNode& +Port::get_state () const { - const char** jc = jack_port_get_connections (_jack_port); + XMLNode* root = new XMLNode (state_node_name); - if (jc) { - for (int i = 0; jc[i]; ++i) { + root->add_property (X_("name"), AudioEngine::instance()->make_port_name_relative (name())); - jack_port_t* port = jack_port_by_name (_engine->jack(), jc[i]); + if (receives_input()) { + root->add_property (X_("direction"), X_("input")); + } else { + root->add_property (X_("direction"), X_("output")); + } - if (port && (jack_port_flags (port) & JackPortIsPhysical)) { - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } - return true; - } - } - if (jack_free) { - jack_free (jc); - } else { - free (jc); - } + vector<string> c; + + get_connections (c); + + for (vector<string>::const_iterator i = c.begin(); i != c.end(); ++i) { + XMLNode* child = new XMLNode (X_("Connection")); + child->add_property (X_("other"), *i); + root->add_child_nocopy (*child); } - return false; + return *root; } +int +Port::set_state (const XMLNode& node, int) +{ + const XMLProperty* prop; + + if (node.name() != state_node_name) { + return -1; + } + + if ((prop = node.property (X_("name"))) != 0) { + set_name (prop->value()); + } + + const XMLNodeList& children (node.children()); + + _connections.clear (); + + for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { + + if ((*c)->name() != X_("Connection")) { + continue; + } + + if ((prop = (*c)->property (X_("other"))) == 0) { + continue; + } + + _connections.insert (prop->value()); + } + + return 0; +} diff --git a/libs/ardour/port_insert.cc b/libs/ardour/port_insert.cc index b2758b2d57..2519451b89 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> 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; } @@ -72,7 +72,7 @@ PortInsert::start_latency_detection () void PortInsert::stop_latency_detection () { - _latency_flush_frames = signal_latency() + _session.engine().frames_per_cycle(); + _latency_flush_frames = signal_latency() + _session.engine().samples_per_cycle(); _latency_detect = false; } @@ -93,7 +93,7 @@ PortInsert::latency() const */ if (_measured_latency == 0) { - return _session.engine().frames_per_cycle() + _input->latency(); + return _session.engine().samples_per_cycle() + _input->latency(); } else { return _measured_latency; } @@ -240,7 +240,7 @@ PortInsert::signal_latency() const */ if (_measured_latency == 0) { - return _session.engine().frames_per_cycle() + _input->signal_latency(); + return _session.engine().samples_per_cycle() + _input->signal_latency(); } else { return _measured_latency; } diff --git a/libs/ardour/port_manager.cc b/libs/ardour/port_manager.cc new file mode 100644 index 0000000000..1758ea4a3f --- /dev/null +++ b/libs/ardour/port_manager.cc @@ -0,0 +1,659 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 "pbd/error.h" + +#include "ardour/async_midi_port.h" +#include "ardour/debug.h" +#include "ardour/port_manager.h" +#include "ardour/audio_port.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +PortManager::PortManager () + : ports (new Ports) + , _port_remove_in_progress (false) +{ +} + +void +PortManager::remove_all_ports () +{ + /* make sure that JACK callbacks that will be invoked as we cleanup + * ports know that they have nothing to do. + */ + + _port_remove_in_progress = true; + + /* process lock MUST be held by caller + */ + + { + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + ps->clear (); + } + + /* clear dead wood list in RCU */ + + ports.flush (); + + _port_remove_in_progress = false; +} + + +string +PortManager::make_port_name_relative (const string& portname) const +{ + if (!_impl) { + return portname; + } + + string::size_type len; + string::size_type n; + string self = _impl->my_name(); + + len = portname.length(); + + for (n = 0; n < len; ++n) { + if (portname[n] == ':') { + break; + } + } + + if ((n != len) && (portname.substr (0, n) == self)) { + return portname.substr (n+1); + } + + return portname; +} + +string +PortManager::make_port_name_non_relative (const string& portname) const +{ + string str; + + if (portname.find_first_of (':') != string::npos) { + return portname; + } + + str = _impl->my_name(); + str += ':'; + str += portname; + + return str; +} + +bool +PortManager::port_is_mine (const string& portname) const +{ + if (!_impl) { + return true; + } + + string self = _impl->my_name(); + + if (portname.find_first_of (':') != string::npos) { + if (portname.substr (0, self.length ()) != self) { + return false; + } + } + + return true; +} + +bool +PortManager::port_is_physical (const std::string& portname) const +{ + if (!_impl) { + return false; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (portname); + if (!ph) { + return false; + } + + return _impl->port_is_physical (ph); +} + +void +PortManager::get_physical_outputs (DataType type, std::vector<std::string>& s) +{ + if (!_impl) { + return; + } + _impl->get_physical_outputs (type, s); +} + +void +PortManager::get_physical_inputs (DataType type, std::vector<std::string>& s) +{ + if (!_impl) { + return; + } + + _impl->get_physical_inputs (type, s); +} + +ChanCount +PortManager::n_physical_outputs () const +{ + if (!_impl) { + return ChanCount::ZERO; + } + + return _impl->n_physical_outputs (); +} + +ChanCount +PortManager::n_physical_inputs () const +{ + if (!_impl) { + return ChanCount::ZERO; + } + return _impl->n_physical_inputs (); +} + +/** @param name Full or short name of port + * @return Corresponding Port or 0. + */ + +boost::shared_ptr<Port> +PortManager::get_port_by_name (const string& portname) +{ + if (!_impl) { + return boost::shared_ptr<Port>(); + } + + if (!port_is_mine (portname)) { + /* not an ardour port */ + return boost::shared_ptr<Port> (); + } + + boost::shared_ptr<Ports> pr = ports.reader(); + std::string rel = make_port_name_relative (portname); + Ports::iterator x = pr->find (rel); + + if (x != pr->end()) { + /* its possible that the port was renamed by some 3rd party and + we don't know about it. check for this (the check is quick + and cheap), and if so, rename the port (which will alter + the port map as a side effect). + */ + const std::string check = make_port_name_relative (_impl->get_port_name (x->second->port_handle())); + if (check != rel) { + x->second->set_name (check); + } + return x->second; + } + + return boost::shared_ptr<Port> (); +} + +void +PortManager::port_renamed (const std::string& old_relative_name, const std::string& new_relative_name) +{ + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> p = writer.get_copy(); + Ports::iterator x = p->find (old_relative_name); + + if (x != p->end()) { + boost::shared_ptr<Port> port = x->second; + p->erase (x); + p->insert (make_pair (new_relative_name, port)); + } +} + +int +PortManager::get_ports (DataType type, PortList& pl) +{ + boost::shared_ptr<Ports> plist = ports.reader(); + for (Ports::iterator p = plist->begin(); p != plist->end(); ++p) { + if (p->second->type() == type) { + pl.push_back (p->second); + } + } + return pl.size(); +} + +int +PortManager::get_ports (const string& port_name_pattern, DataType type, PortFlags flags, vector<string>& s) +{ + if (!_impl) { + return 0; + } + + return _impl->get_ports (port_name_pattern, type, flags, s); +} + +void +PortManager::port_registration_failure (const std::string& portname) +{ + if (!_impl) { + return; + } + + string full_portname = _impl->my_name(); + full_portname += ':'; + full_portname += portname; + + + PortEngine::PortHandle p = _impl->get_port_by_name (full_portname); + string reason; + + if (p) { + reason = string_compose (_("a port with the name \"%1\" already exists: check for duplicated track/bus names"), portname); + } else { + reason = string_compose (_("No more ports are available. You will need to stop %1 and restart with more ports if you need this many tracks."), PROGRAM_NAME); + } + + throw PortRegistrationFailure (string_compose (_("AudioEngine: cannot register port \"%1\": %2"), portname, reason).c_str()); +} + +boost::shared_ptr<Port> +PortManager::register_port (DataType dtype, const string& portname, bool input, bool async) +{ + boost::shared_ptr<Port> newport; + + try { + if (dtype == DataType::AUDIO) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering AUDIO port %1, input %2\n", + portname, input)); + newport.reset (new AudioPort (portname, (input ? IsInput : IsOutput))); + } else if (dtype == DataType::MIDI) { + if (async) { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering ASYNC MIDI port %1, input %2\n", + portname, input)); + newport.reset (new AsyncMIDIPort (portname, (input ? IsInput : IsOutput))); + } else { + DEBUG_TRACE (DEBUG::Ports, string_compose ("registering MIDI port %1, input %2\n", + portname, input)); + newport.reset (new MidiPort (portname, (input ? IsInput : IsOutput))); + } + } else { + throw PortRegistrationFailure("unable to create port (unknown type)"); + } + + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + ps->insert (make_pair (make_port_name_relative (portname), newport)); + + /* writer goes out of scope, forces update */ + + } + + catch (PortRegistrationFailure& err) { + throw err; + } catch (std::exception& e) { + throw PortRegistrationFailure(string_compose( + _("unable to create port: %1"), e.what()).c_str()); + } catch (...) { + throw PortRegistrationFailure("unable to create port (unknown error)"); + } + + DEBUG_TRACE (DEBUG::Ports, string_compose ("\t%2 port registration success, ports now = %1\n", ports.reader()->size(), this)); + return newport; +} + +boost::shared_ptr<Port> +PortManager::register_input_port (DataType type, const string& portname, bool async) +{ + return register_port (type, portname, true, async); +} + +boost::shared_ptr<Port> +PortManager::register_output_port (DataType type, const string& portname, bool async) +{ + return register_port (type, portname, false, async); +} + +int +PortManager::unregister_port (boost::shared_ptr<Port> port) +{ + /* caller must hold process lock */ + + { + RCUWriter<Ports> writer (ports); + boost::shared_ptr<Ports> ps = writer.get_copy (); + Ports::iterator x = ps->find (make_port_name_relative (port->name())); + + if (x != ps->end()) { + ps->erase (x); + } + + /* writer goes out of scope, forces update */ + } + + ports.flush (); + + return 0; +} + +bool +PortManager::connected (const string& port_name) +{ + if (!_impl) { + return false; + } + + PortEngine::PortHandle handle = _impl->get_port_by_name (port_name); + + if (!handle) { + return false; + } + + return _impl->connected (handle); +} + +int +PortManager::connect (const string& source, const string& destination) +{ + int ret; + + string s = make_port_name_non_relative (source); + string d = make_port_name_non_relative (destination); + + boost::shared_ptr<Port> src = get_port_by_name (s); + boost::shared_ptr<Port> dst = get_port_by_name (d); + + if (src) { + ret = src->connect (d); + } else if (dst) { + ret = dst->connect (s); + } else { + /* neither port is known to us ...hand-off to the PortEngine + */ + if (_impl) { + ret = _impl->connect (s, d); + } else { + ret = -1; + } + } + + if (ret > 0) { + /* already exists - no error, no warning */ + } else if (ret < 0) { + error << string_compose(_("AudioEngine: cannot connect %1 (%2) to %3 (%4)"), + source, s, destination, d) + << endmsg; + } + + return ret; +} + +int +PortManager::disconnect (const string& source, const string& destination) +{ + int ret; + + string s = make_port_name_non_relative (source); + string d = make_port_name_non_relative (destination); + + boost::shared_ptr<Port> src = get_port_by_name (s); + boost::shared_ptr<Port> dst = get_port_by_name (d); + + if (src) { + ret = src->disconnect (d); + } else if (dst) { + ret = dst->disconnect (s); + } else { + /* neither port is known to us ...hand-off to the PortEngine + */ + if (_impl) { + ret = _impl->disconnect (s, d); + } else { + ret = -1; + } + } + return ret; +} + +int +PortManager::disconnect (boost::shared_ptr<Port> port) +{ + return port->disconnect_all (); +} + +int +PortManager::reestablish_ports () +{ + Ports::iterator i; + + boost::shared_ptr<Ports> p = ports.reader (); + + DEBUG_TRACE (DEBUG::Ports, string_compose ("reestablish %1 ports\n", p->size())); + + for (i = p->begin(); i != p->end(); ++i) { + if (i->second->reestablish ()) { + error << string_compose (_("Re-establising port %1 failed"), i->second->name()) << endmsg; + cerr << string_compose (_("Re-establising port %1 failed"), i->second->name()) << endl; + break; + } + } + + if (i != p->end()) { + /* failed */ + remove_all_ports (); + return -1; + } + + return 0; +} + +int +PortManager::reconnect_ports () +{ + boost::shared_ptr<Ports> p = ports.reader (); + + /* re-establish connections */ + + DEBUG_TRACE (DEBUG::Ports, string_compose ("reconnect %1 ports\n", p->size())); + + for (Ports::iterator i = p->begin(); i != p->end(); ++i) { + i->second->reconnect (); + } + + return 0; +} + +void +PortManager::connect_callback (const string& a, const string& b, bool conn) +{ + boost::shared_ptr<Port> port_a; + boost::shared_ptr<Port> port_b; + Ports::iterator x; + boost::shared_ptr<Ports> pr = ports.reader (); + + x = pr->find (make_port_name_relative (a)); + if (x != pr->end()) { + port_a = x->second; + } + + x = pr->find (make_port_name_relative (b)); + if (x != pr->end()) { + port_b = x->second; + } + + PortConnectedOrDisconnected ( + port_a, a, + port_b, b, + conn + ); /* EMIT SIGNAL */ +} + +void +PortManager::registration_callback () +{ + if (!_port_remove_in_progress) { + PortRegisteredOrUnregistered (); /* EMIT SIGNAL */ + } +} + +bool +PortManager::can_request_input_monitoring () const +{ + if (!_impl) { + return false; + } + + return _impl->can_monitor_input (); +} + +void +PortManager::request_input_monitoring (const string& name, bool yn) const +{ + if (!_impl) { + return; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (name); + + if (ph) { + _impl->request_input_monitoring (ph, yn); + } +} + +void +PortManager::ensure_input_monitoring (const string& name, bool yn) const +{ + if (!_impl) { + return; + } + + PortEngine::PortHandle ph = _impl->get_port_by_name (name); + + if (ph) { + _impl->ensure_input_monitoring (ph, yn); + } +} + +uint32_t +PortManager::port_name_size() const +{ + if (!_impl) { + return 0; + } + + return _impl->port_name_size (); +} + +string +PortManager::my_name() const +{ + if (!_impl) { + return string(); + } + + return _impl->my_name(); +} + +int +PortManager::graph_order_callback () +{ + if (!_port_remove_in_progress) { + GraphReordered(); /* EMIT SIGNAL */ + } + + return 0; +} + +void +PortManager::cycle_start (pframes_t nframes) +{ + Port::set_global_port_buffer_offset (0); + Port::set_cycle_framecnt (nframes); + + _cycle_ports = ports.reader (); + + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_start (nframes); + } +} + +void +PortManager::cycle_end (pframes_t nframes) +{ + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->cycle_end (nframes); + } + + for (Ports::iterator p = _cycle_ports->begin(); p != _cycle_ports->end(); ++p) { + p->second->flush_buffers (nframes); + } + + _cycle_ports.reset (); + + /* we are done */ +} + +void +PortManager::silence (pframes_t nframes) +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + if (i->second->sends_output()) { + i->second->get_buffer(nframes).silence(nframes); + } + } +} + +void +PortManager::check_monitoring () +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + + bool x; + + if (i->second->last_monitor() != (x = i->second->monitoring_input ())) { + i->second->set_last_monitor (x); + /* XXX I think this is dangerous, due to + a likely mutex in the signal handlers ... + */ + i->second->MonitorInputChanged (x); /* EMIT SIGNAL */ + } + } +} + +void +PortManager::fade_out (gain_t base_gain, gain_t gain_step, pframes_t nframes) +{ + for (Ports::iterator i = _cycle_ports->begin(); i != _cycle_ports->end(); ++i) { + + if (i->second->sends_output()) { + + boost::shared_ptr<AudioPort> ap = boost::dynamic_pointer_cast<AudioPort> (i->second); + if (ap) { + Sample* s = ap->engine_get_whole_audio_buffer (); + gain_t g = base_gain; + + for (pframes_t n = 0; n < nframes; ++n) { + *s++ *= g; + g -= gain_step; + } + } + } + } +} diff --git a/libs/ardour/rc_configuration.cc b/libs/ardour/rc_configuration.cc index 4733587654..7fe030fb7e 100644 --- a/libs/ardour/rc_configuration.cc +++ b/libs/ardour/rc_configuration.cc @@ -27,11 +27,11 @@ #include "pbd/xml++.h" #include "pbd/file_utils.h" -#include "midi++/manager.h" - +#include "ardour/audioengine.h" #include "ardour/control_protocol_manager.h" #include "ardour/diskstream.h" #include "ardour/filesystem_paths.h" +#include "ardour/port.h" #include "ardour/rc_configuration.h" #include "ardour/session_metadata.h" @@ -63,13 +63,8 @@ RCConfiguration::RCConfiguration () { } - RCConfiguration::~RCConfiguration () { - for (list<XMLNode*>::iterator i = _midi_port_states.begin(); i != _midi_port_states.end(); ++i) { - delete *i; - } - delete _control_protocol_state; } @@ -177,16 +172,6 @@ RCConfiguration::get_state () root = new XMLNode("Ardour"); - MIDI::Manager* mm = MIDI::Manager::instance(); - - if (mm) { - boost::shared_ptr<const MIDI::Manager::PortList> ports = mm->get_midi_ports(); - - for (MIDI::Manager::PortList::const_iterator i = ports->begin(); i != ports->end(); ++i) { - root->add_child_nocopy((*i)->get_state()); - } - } - root->add_child_nocopy (get_variables ()); root->add_child_nocopy (SessionMetadata::Metadata()->get_user_state()); @@ -232,12 +217,6 @@ RCConfiguration::set_state (const XMLNode& root, int version) XMLNodeConstIterator niter; XMLNode *node; - for (list<XMLNode*>::iterator i = _midi_port_states.begin(); i != _midi_port_states.end(); ++i) { - delete *i; - } - - _midi_port_states.clear (); - Stateful::save_extra_xml (root); for (niter = nlist.begin(); niter != nlist.end(); ++niter) { @@ -250,8 +229,6 @@ RCConfiguration::set_state (const XMLNode& root, int version) SessionMetadata::Metadata()->set_state (*node, version); } else if (node->name() == ControlProtocolManager::state_node_name) { _control_protocol_state = new XMLNode (*node); - } else if (node->name() == MIDI::Port::state_node_name) { - _midi_port_states.push_back (new XMLNode (*node)); } } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 232f295df6..026ee4ec5c 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -971,7 +971,7 @@ Route::add_processor (boost::shared_ptr<Processor> processor, boost::shared_ptr< DEBUG_TRACE (DEBUG::Processors, string_compose ( "%1 adding processor %2\n", name(), processor->name())); - if (!_session.engine().connected() || !processor) { + if (!AudioEngine::instance()->connected() || !processor) { return 1; } @@ -3818,13 +3818,13 @@ Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecn universally true, but the alternative is way too corner-case to worry about. */ - jack_latency_range_t all_connections; + LatencyRange all_connections; if (from.empty()) { all_connections.min = 0; all_connections.max = 0; } else { - all_connections.min = ~((jack_nframes_t) 0); + all_connections.min = ~((pframes_t) 0); all_connections.max = 0; /* iterate over all "from" ports and determine the latency range for all of their @@ -3833,7 +3833,7 @@ Route::update_port_latencies (PortSet& from, PortSet& to, bool playback, framecn for (PortSet::iterator p = from.begin(); p != from.end(); ++p) { - jack_latency_range_t range; + LatencyRange range; p->get_connected_latency_range (range, playback); @@ -3898,7 +3898,7 @@ Route::set_public_port_latencies (framecnt_t value, bool playback) const latency compensation into account. */ - jack_latency_range_t range; + LatencyRange range; range.min = value; range.max = value; diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 428a5e5274..135df7c2cd 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -49,6 +49,7 @@ #include "ardour/amp.h" #include "ardour/analyser.h" +#include "ardour/async_midi_port.h" #include "ardour/audio_buffer.h" #include "ardour/audio_diskstream.h" #include "ardour/audio_port.h" @@ -66,6 +67,7 @@ #include "ardour/debug.h" #include "ardour/filename_extensions.h" #include "ardour/graph.h" +#include "ardour/midiport_manager.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/operations.h" @@ -85,12 +87,11 @@ #include "ardour/session_playlists.h" #include "ardour/smf_source.h" #include "ardour/source_factory.h" +#include "ardour/speakers.h" #include "ardour/utils.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" -#include "midi++/manager.h" #include "i18n.h" @@ -106,6 +107,7 @@ using namespace PBD; bool Session::_disable_all_loaded_plugins = false; +PBD::Signal1<int,uint32_t> Session::AudioEngineSetupRequired; PBD::Signal1<void,std::string> Session::Dialog; PBD::Signal0<int> Session::AskAboutPendingState; PBD::Signal2<int, framecnt_t, framecnt_t> Session::AskAboutSampleRateMismatch; @@ -130,70 +132,184 @@ Session::Session (AudioEngine &eng, const string& snapshot_name, BusProfile* bus_profile, string mix_template) - : _engine (eng) + : playlists (new SessionPlaylists) + , _engine (eng) + , process_function (&Session::process_with_events) + , waiting_for_sync_offset (false) + , _base_frame_rate (0) + , _current_frame_rate (0) + , _nominal_frame_rate (0) + , transport_sub_state (0) + , _record_status (Disabled) + , _transport_frame (0) + , _session_range_location (0) + , _slave (0) + , _silent (false) + , _transport_speed (0) + , _default_transport_speed (1.0) + , _last_transport_speed (0) , _target_transport_speed (0.0) + , auto_play_legal (false) + , _last_slave_transport_frame (0) + , maximum_output_latency (0) , _requested_return_frame (-1) + , current_block_size (0) + , _worst_output_latency (0) + , _worst_input_latency (0) + , _worst_track_latency (0) + , _have_captured (false) + , _meter_hold (0) + , _meter_falloff (0) + , _non_soloed_outs_muted (false) + , _listen_cnt (0) + , _solo_isolated_cnt (0) + , _writable (false) + , _was_seamless (Config->get_seamless_loop ()) , _under_nsm_control (false) - , _session_dir (new SessionDirectory(fullpath)) + , delta_accumulator_cnt (0) + , average_slave_delta (1800) // !!! why 1800 ??? + , average_dir (0) + , have_first_delta_accumulator (false) + , _slave_state (Stopped) + , post_export_sync (false) + , post_export_position (0) + , _exporting (false) + , _export_started (false) + , _export_rolling (false) + , _pre_export_mmc_enabled (false) + , _name (snapshot_name) + , _is_new (true) + , _send_qf_mtc (false) + , _pframes_since_last_mtc (0) + , session_midi_feedback (0) + , play_loop (false) + , loop_changing (false) + , last_loopend (0) + , _session_dir (new SessionDirectory (fullpath)) + , _current_snapshot_name (snapshot_name) , state_tree (0) - , _state_of_the_state (Clean) + , state_was_pending (false) + , _state_of_the_state (StateOfTheState(CannotSave|InitialConnecting|Loading)) + , _last_roll_location (0) + , _last_roll_or_reversal_location (0) + , _last_record_location (0) + , pending_locate_roll (false) + , pending_locate_frame (0) + , pending_locate_flush (false) + , pending_abort (false) + , pending_auto_loop (false) , _butler (new Butler (*this)) , _post_transport_work (0) + , cumulative_rf_motion (0) + , rf_scale (1.0) + , _locations (new Locations (*this)) + , step_speed (0) + , outbound_mtc_timecode_frame (0) + , next_quarter_frame_to_send (-1) + , _frames_per_timecode_frame (0) + , _frames_per_hour (0) + , _timecode_frames_per_hour (0) + , last_timecode_valid (false) + , last_timecode_when (0) , _send_timecode_update (false) + , ltc_encoder (0) , ltc_enc_buf(0) + , ltc_buf_off (0) + , ltc_buf_len (0) + , ltc_speed (0) + , ltc_enc_byte (0) + , ltc_enc_pos (0) + , ltc_enc_cnt (0) + , ltc_enc_off (0) + , restarting (false) + , ltc_prev_cycle (0) + , ltc_timecode_offset (0) + , ltc_timecode_negative_offset (false) + , midi_control_ui (0) + , _tempo_map (0) , _all_route_group (new RouteGroup (*this, "all")) , routes (new RouteList) + , _adding_routes_in_progress (false) + , destructive_index (0) + , solo_update_disabled (false) + , default_fade_steepness (0) + , default_fade_msecs (0) , _total_free_4k_blocks (0) , _total_free_4k_blocks_uncertain (false) + , no_questions_about_missing_files (false) + , _playback_load (0) + , _capture_load (0) , _bundles (new BundleList) , _bundle_xml_node (0) , _current_trans (0) + , _clicking (false) , click_data (0) , click_emphasis_data (0) + , click_length (0) + , click_emphasis_length (0) + , _clicks_cleared (0) + , _play_range (false) , main_outs (0) + , first_file_data_format_reset (true) + , first_file_header_format_reset (true) + , have_looped (false) , _have_rec_enabled_track (false) + , _step_editors (0) , _suspend_timecode_transmission (0) + , _speakers (new Speakers) + , ignore_route_processor_changes (false) { - _locations = new Locations (*this); - ltc_encoder = NULL; - - if (how_many_dsp_threads () > 1) { - /* For now, only create the graph if we are using >1 DSP threads, as - it is a bit slower than the old code with 1 thread. - */ - _process_graph.reset (new Graph (*this)); - } - - playlists.reset (new SessionPlaylists); - - _all_route_group->set_active (true, this); - - interpolation.add_channel_to (0, 0); - - if (!eng.connected()) { - throw failed_constructor(); - } - - n_physical_outputs = _engine.n_physical_outputs (); - n_physical_inputs = _engine.n_physical_inputs (); - - first_stage_init (fullpath, snapshot_name); - - _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + uint32_t sr = 0; + pre_engine_init (fullpath); + if (_is_new) { if (create (mix_template, bus_profile)) { destroy (); throw failed_constructor (); } + } else { + if (load_state (_current_snapshot_name)) { + throw failed_constructor (); + } + + /* try to get sample rate from XML state so that we + * can influence the SR if we set up the audio + * engine. + */ + + if (state_tree) { + const XMLProperty* prop; + if ((prop = state_tree->root()->property (X_("sample-rate"))) != 0) { + sr = atoi (prop->value()); + } + } + } + + if (_engine.current_backend() == 0 || _engine.setup_required()) { + boost::optional<int> r = AudioEngineSetupRequired (sr); + if (r.get_value_or (-1) != 0) { + destroy (); + throw failed_constructor(); + } + } + + /* at this point the engine should be connected (i.e. interacting + with a backend device (or psuedo-device) and available to us + for determinining sample rates and other settings. + */ + + if (!_engine.connected()) { + destroy (); + throw failed_constructor(); } - if (second_stage_init ()) { + if (post_engine_init ()) { destroy (); throw failed_constructor (); } - store_recent_sessions(_name, _path); + store_recent_sessions (_name, _path); bool was_dirty = dirty(); @@ -210,6 +326,16 @@ Session::Session (AudioEngine &eng, EndTimeChanged.connect_same_thread (*this, boost::bind (&Session::end_time_changed, this, _1)); _is_new = false; + + /* hook us up to the engine since we are now completely constructed */ + + BootMessage (_("Connect to engine")); + + _engine.set_session (this); + _engine.reset_timebase (); + + BootMessage (_("Session loading complete")); + } Session::~Session () @@ -330,6 +456,8 @@ Session::destroy () /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */ playlists.reset (); + delete _mmc; + delete _midi_ports; delete _locations; DEBUG_TRACE (DEBUG::Destruction, "Session::destroy() done\n"); @@ -340,135 +468,103 @@ Session::destroy () } void -Session::when_engine_running () +Session::setup_ltc () { - string first_physical_output; - - BootMessage (_("Set block size and sample rate")); - - set_block_size (_engine.frames_per_cycle()); - set_frame_rate (_engine.frame_rate()); - - BootMessage (_("Using configuration")); - - boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false)); - boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true)); - - Config->map_parameters (ff); - config.map_parameters (ft); - - /* every time we reconnect, recompute worst case output latencies */ - - _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); - - if (synced_to_jack()) { - _engine.transport_stop (); + XMLNode* child = 0; + + _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); + _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); + + if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) { + _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_input (); } - - if (config.get_jack_time_master()) { - _engine.transport_locate (_transport_frame); + + if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) { + _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); + } else { + { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); + } + reconnect_ltc_output (); } + + /* fix up names of LTC ports because we don't want the normal + * IO style of NAME/TYPE-{in,out}N + */ + + _ltc_input->nth (0)->set_name (_("LTC-in")); + _ltc_output->nth (0)->set_name (_("LTC-out")); +} - _clicking = false; +void +Session::setup_click () +{ + XMLNode* child = 0; - try { - XMLNode* child = 0; + _clicking = false; + _click_io.reset (new ClickIO (*this, "click")); + _click_gain.reset (new Amp (*this)); + _click_gain->activate (); + + if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) { - _ltc_input.reset (new IO (*this, _("LTC In"), IO::Input)); - _ltc_output.reset (new IO (*this, _("LTC Out"), IO::Output)); - - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-In")) != 0) { - _ltc_input->set_state (*(child->children().front()), Stateful::loading_state_version); - } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_input->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_input (); - } + /* existing state for Click */ + int c = 0; - if (state_tree && (child = find_named_node (*state_tree->root(), "LTC-Out")) != 0) { - _ltc_output->set_state (*(child->children().front()), Stateful::loading_state_version); + if (Stateful::loading_state_version < 3000) { + c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); } else { - { - Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); - _ltc_output->ensure_io (ChanCount (DataType::AUDIO, 1), true, this); - } - reconnect_ltc_output (); - } - - /* fix up names of LTC ports because we don't want the normal - * IO style of NAME/TYPE-{in,out}N - */ - - _ltc_input->nth (0)->set_name (_("LTC-in")); - _ltc_output->nth (0)->set_name (_("LTC-out")); - - _click_io.reset (new ClickIO (*this, "click")); - _click_gain.reset (new Amp (*this)); - _click_gain->activate (); - - if (state_tree && (child = find_named_node (*state_tree->root(), "Click")) != 0) { - - /* existing state for Click */ - int c = 0; - - if (Stateful::loading_state_version < 3000) { - c = _click_io->set_state_2X (*child->children().front(), Stateful::loading_state_version, false); - } else { - const XMLNodeList& children (child->children()); - XMLNodeList::const_iterator i = children.begin(); - if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { - ++i; - if (i != children.end()) { - c = _click_gain->set_state (**i, Stateful::loading_state_version); - } + const XMLNodeList& children (child->children()); + XMLNodeList::const_iterator i = children.begin(); + if ((c = _click_io->set_state (**i, Stateful::loading_state_version)) == 0) { + ++i; + if (i != children.end()) { + c = _click_gain->set_state (**i, Stateful::loading_state_version); } } + } - if (c == 0) { - _clicking = Config->get_clicking (); + if (c == 0) { + _clicking = Config->get_clicking (); - } else { + } else { - error << _("could not setup Click I/O") << endmsg; - _clicking = false; - } + error << _("could not setup Click I/O") << endmsg; + _clicking = false; + } - } else { + } else { - /* default state for Click: dual-mono to first 2 physical outputs */ + /* default state for Click: dual-mono to first 2 physical outputs */ - vector<string> outs; - _engine.get_physical_outputs (DataType::AUDIO, outs); + vector<string> outs; + _engine.get_physical_outputs (DataType::AUDIO, outs); - for (uint32_t physport = 0; physport < 2; ++physport) { - if (outs.size() > physport) { - if (_click_io->add_port (outs[physport], this)) { - // relax, even though its an error - } + for (uint32_t physport = 0; physport < 2; ++physport) { + if (outs.size() > physport) { + if (_click_io->add_port (outs[physport], this)) { + // relax, even though its an error } } - - if (_click_io->n_ports () > ChanCount::ZERO) { - _clicking = Config->get_clicking (); - } } - } - catch (failed_constructor& err) { - error << _("cannot setup Click I/O") << endmsg; - } - - BootMessage (_("Compute I/O Latencies")); - - if (_clicking) { - // XXX HOW TO ALERT UI TO THIS ? DO WE NEED TO? + if (_click_io->n_ports () > ChanCount::ZERO) { + _clicking = Config->get_clicking (); + } } +} - BootMessage (_("Set up standard connections")); - +void +Session::setup_bundles () +{ vector<string> inputs[DataType::num_types]; vector<string> outputs[DataType::num_types]; for (uint32_t i = 0; i < DataType::num_types; ++i) { @@ -567,6 +663,37 @@ Session::when_engine_running () add_bundle (c); } +} + +int +Session::when_engine_running () +{ + /* every time we reconnect, recompute worst case output latencies */ + + _engine.Running.connect_same_thread (*this, boost::bind (&Session::initialize_latencies, this)); + + if (synced_to_jack()) { + _engine.transport_stop (); + } + + if (config.get_jack_time_master()) { + _engine.transport_locate (_transport_frame); + } + + + try { + BootMessage (_("Set up LTC")); + setup_ltc (); + BootMessage (_("Set up Click")); + setup_click (); + BootMessage (_("Set up standard connections")); + setup_bundles (); + } + + catch (failed_constructor& err) { + return -1; + } + BootMessage (_("Setup signal flow and plugins")); /* Reset all panners */ @@ -584,7 +711,8 @@ Session::when_engine_running () as it will set states for ports which the ControlProtocolManager creates. */ - MIDI::Manager::instance()->set_port_states (Config->midi_port_states ()); + // XXX set state of MIDI::Port's + // MidiPortManager::instance()->set_port_states (Config->midi_port_states ()); /* And this must be done after the MIDI::Manager::set_port_states as * it will try to make connections whose details are loaded by set_port_states. @@ -609,11 +737,7 @@ Session::when_engine_running () initialize_latencies (); - /* hook us up to the engine */ - - BootMessage (_("Connect to engine")); - _engine.set_session (this); - _engine.reset_timebase (); + return 0; } void @@ -874,7 +998,12 @@ Session::hookup_io () /* Tell all IO objects to connect themselves together */ IO::enable_connecting (); - MIDI::JackMIDIPort::MakeConnections (); + + /* Now tell all "floating" ports to connect to whatever + they should be connected to. + */ + + AudioEngine::instance()->reconnect_ports (); /* Anyone who cares about input state, wake up and do something */ @@ -936,7 +1065,7 @@ Session::set_track_monitor_input_status (bool yn) boost::shared_ptr<AudioTrack> tr = boost::dynamic_pointer_cast<AudioTrack> (*i); if (tr && tr->record_enabled ()) { //cerr << "switching to input = " << !auto_input << __FILE__ << __LINE__ << endl << endl; - tr->request_jack_monitors_input (yn); + tr->request_input_monitoring (yn); } } } @@ -1169,7 +1298,7 @@ Session::enable_record () if (g_atomic_int_compare_and_exchange (&_record_status, rs, Recording)) { _last_record_location = _transport_frame; - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe)); if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) { set_track_monitor_input_status (true); @@ -1190,7 +1319,7 @@ Session::disable_record (bool rt_context, bool force) if ((!Config->get_latched_record_enable () && !play_loop) || force) { g_atomic_int_set (&_record_status, Disabled); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit)); } else { if (rs == Recording) { g_atomic_int_set (&_record_status, Enabled); @@ -1244,7 +1373,7 @@ Session::maybe_enable_record () enable_record (); } } else { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause)); RecordStateChanged (); /* EMIT SIGNAL */ } @@ -1341,6 +1470,7 @@ Session::set_frame_rate (framecnt_t frames_per_second) */ _base_frame_rate = frames_per_second; + _nominal_frame_rate = frames_per_second; sync_time_vars(); diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc index 0db4fc33bb..62eb61ab83 100644 --- a/libs/ardour/session_export.cc +++ b/libs/ardour/session_export.cc @@ -21,7 +21,6 @@ #include "pbd/error.h" #include <glibmm/threads.h> -#include <midi++/manager.h> #include <midi++/mmc.h> #include "ardour/audioengine.h" @@ -93,8 +92,8 @@ Session::pre_export () /* disable MMC output early */ - _pre_export_mmc_enabled = MIDI::Manager::instance()->mmc()->send_enabled (); - MIDI::Manager::instance()->mmc()->enable_send (false); + _pre_export_mmc_enabled = _mmc->send_enabled (); + _mmc->enable_send (false); return 0; } @@ -237,7 +236,7 @@ Session::finalize_audio_export () export_freewheel_connection.disconnect(); - MIDI::Manager::instance()->mmc()->enable_send (_pre_export_mmc_enabled); + _mmc->enable_send (_pre_export_mmc_enabled); /* maybe write CUE/TOC */ diff --git a/libs/ardour/session_jack.cc b/libs/ardour/session_jack.cc new file mode 100644 index 0000000000..af8a93fec3 --- /dev/null +++ b/libs/ardour/session_jack.cc @@ -0,0 +1,185 @@ +/* + Copyright (C) 1999-2013 Paul Davis + + 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. + +*/ + + +#ifdef WAF_BUILD +#include "libardour-config.h" +#endif + +#include <time.h> + +#include <glibmm/miscutils.h> + +#include "jack/jack.h" +#include "jack/session.h" + +#include "ardour/audioengine.h" +#include "ardour/filename_extensions.h" +#include "ardour/session.h" +#include "ardour/session_directory.h" +#include "ardour/tempo.h" + +using namespace ARDOUR; +using std::string; + +#ifdef HAVE_JACK_SESSION +void +Session::jack_session_event (jack_session_event_t* event) +{ + char timebuf[128], *tmp; + time_t n; + struct tm local_time; + + time (&n); + localtime_r (&n, &local_time); + strftime (timebuf, sizeof(timebuf), "JS_%FT%T", &local_time); + + while ((tmp = strchr(timebuf, ':'))) { *tmp = '.'; } + + if (event->type == JackSessionSaveTemplate) + { + if (save_template( timebuf )) { + event->flags = JackSessionSaveError; + } else { + string cmd ("ardour3 -P -U "); + cmd += event->client_uuid; + cmd += " -T "; + cmd += timebuf; + + event->command_line = strdup (cmd.c_str()); + } + } + else + { + if (save_state (timebuf)) { + event->flags = JackSessionSaveError; + } else { + std::string xml_path (_session_dir->root_path()); + std::string legalized_filename = legalize_for_path (timebuf) + statefile_suffix; + xml_path = Glib::build_filename (xml_path, legalized_filename); + + string cmd ("ardour3 -P -U "); + cmd += event->client_uuid; + cmd += " \""; + cmd += xml_path; + cmd += '\"'; + + event->command_line = strdup (cmd.c_str()); + } + } + + /* this won't be called if the port engine in use is not JACK, so we do + not have to worry about the type of PortEngine::private_handle() + */ + + jack_client_t* jack_client = (jack_client_t*) AudioEngine::instance()->port_engine().private_handle(); + + if (jack_client) { + jack_session_reply (jack_client, event); + } + + if (event->type == JackSessionSaveAndQuit) { + Quit (); /* EMIT SIGNAL */ + } + + jack_session_event_free( event ); +} +#endif + +void +Session::jack_timebase_callback (jack_transport_state_t /*state*/, + pframes_t /*nframes*/, + jack_position_t* pos, + int /*new_position*/) +{ + Timecode::BBT_Time bbt; + + /* BBT info */ + + if (_tempo_map) { + + TempoMetric metric (_tempo_map->metric_at (_transport_frame)); + + try { + _tempo_map->bbt_time_rt (_transport_frame, bbt); + + pos->bar = bbt.bars; + pos->beat = bbt.beats; + pos->tick = bbt.ticks; + + // XXX still need to set bar_start_tick + + pos->beats_per_bar = metric.meter().divisions_per_bar(); + pos->beat_type = metric.meter().note_divisor(); + pos->ticks_per_beat = Timecode::BBT_Time::ticks_per_beat; + pos->beats_per_minute = metric.tempo().beats_per_minute(); + + pos->valid = jack_position_bits_t (pos->valid | JackPositionBBT); + + } catch (...) { + /* no message */ + } + } + +#ifdef HAVE_JACK_VIDEO_SUPPORT + //poke audio video ratio so Ardour can track Video Sync + pos->audio_frames_per_video_frame = frame_rate() / timecode_frames_per_second(); + pos->valid = jack_position_bits_t (pos->valid | JackAudioVideoRatio); +#endif + +#if 0 + /* Timecode info */ + + pos->timecode_offset = config.get_timecode_offset(); + t.timecode_frame_rate = timecode_frames_per_second(); + pos->valid = jack_position_bits_t (pos->valid | JackPositionTimecode; + + if (_transport_speed) { + + if (play_loop) { + + Location* location = _locations.auto_loop_location(); + + if (location) { + + t.transport_state = JackTransportLooping; + t.loop_start = location->start(); + t.loop_end = location->end(); + t.valid = jack_transport_bits_t (t.valid | JackTransportLoop); + + } else { + + t.loop_start = 0; + t.loop_end = 0; + t.transport_state = JackTransportRolling; + + } + + } else { + + t.loop_start = 0; + t.loop_end = 0; + t.transport_state = JackTransportRolling; + + } + + } +#endif +} + diff --git a/libs/ardour/session_ltc.cc b/libs/ardour/session_ltc.cc index 2671bfccf4..2ce25b5a48 100644 --- a/libs/ardour/session_ltc.cc +++ b/libs/ardour/session_ltc.cc @@ -55,7 +55,7 @@ using namespace Timecode; * This filter is adaptive so that fast vari-speed signals * will not be affected by it. */ -#define LTC_RISE_TIME(speed) MIN (100, MAX(40, (4000000 / ((speed==0)?1:speed) / engine().frame_rate()))) +#define LTC_RISE_TIME(speed) MIN (100, MAX(40, (4000000 / ((speed==0)?1:speed) / engine().sample_rate()))) #define TV_STANDARD(tcf) \ (timecode_to_frames_per_second(tcf)==25.0 ? LTC_TV_625_50 : \ @@ -564,7 +564,7 @@ Session::ltc_tx_send_time_code_for_cycle (framepos_t start_frame, framepos_t end * To do better than this, resampling (or a rewrite of the * encoder) is required. */ - ltc_speed -= ((ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_frame) / engine().frame_rate(); + ltc_speed -= ((ltc_enc_pos + ltc_enc_cnt - poff) - cycle_start_frame) / engine().sample_rate(); } diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc index 9a7ca5b734..63cba68df1 100644 --- a/libs/ardour/session_midi.cc +++ b/libs/ardour/session_midi.cc @@ -29,7 +29,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "pbd/error.h" #include "pbd/pthread_utils.h" @@ -40,6 +39,7 @@ #include "ardour/audio_track.h" #include "ardour/audioengine.h" #include "ardour/debug.h" +#include "ardour/midi_port.h" #include "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/session.h" @@ -348,7 +348,7 @@ Session::mmc_record_enable (MIDI::MachineControl &mmc, size_t trk, bool enabled) * @param t time to send. */ int -Session::send_full_time_code (framepos_t const t) +Session::send_full_time_code (framepos_t const t, pframes_t nframes) { /* This function could easily send at a given frame offset, but would * that be useful? Does ardour do sub-block accurate locating? [DR] */ @@ -423,10 +423,9 @@ Session::send_full_time_code (framepos_t const t) msg[8] = timecode.frames; // Send message at offset 0, sent time is for the start of this cycle - if (MIDI::Manager::instance()->mtc_output_port()->midimsg (msg, sizeof (msg), 0)) { - error << _("Session: could not send full MIDI time code") << endmsg; - return -1; - } + + MidiBuffer& mb (_midi_ports->mtc_output_port()->get_midi_buffer (nframes)); + mb.push_back (0, sizeof (msg), msg); _pframes_since_last_mtc = 0; return 0; @@ -469,7 +468,7 @@ Session::send_midi_time_code_for_cycle (framepos_t start_frame, framepos_t end_f next_quarter_frame_to_send, quarter_frame_duration)); if (rint(outbound_mtc_timecode_frame + (next_quarter_frame_to_send * quarter_frame_duration)) < _transport_frame) { - send_full_time_code (_transport_frame); + send_full_time_code (_transport_frame, nframes); return 0; } @@ -515,9 +514,10 @@ Session::send_midi_time_code_for_cycle (framepos_t start_frame, framepos_t end_f ARDOUR::pframes_t const out_stamp = (msg_time - start_frame) / _transport_speed; assert (out_stamp < nframes); - if (MIDI::Manager::instance()->mtc_output_port()->midimsg (mtc_msg, 2, out_stamp)) { + MidiBuffer& mb (_midi_ports->mtc_output_port()->get_midi_buffer(nframes)); + if (!mb.push_back (out_stamp, 2, mtc_msg)) { error << string_compose(_("Session: cannot send quarter-frame MTC message (%1)"), strerror (errno)) - << endmsg; + << endmsg; return -1; } @@ -602,3 +602,45 @@ Session::start_midi_thread () return 0; } +MIDI::Port* +Session::midi_input_port () const +{ + return _midi_ports->midi_input_port (); +} +MIDI::Port* +Session::midi_output_port () const +{ + return _midi_ports->midi_output_port (); +} +boost::shared_ptr<MidiPort> +Session::midi_clock_output_port () const +{ + return _midi_ports->midi_clock_output_port (); +} +boost::shared_ptr<MidiPort> +Session::midi_clock_input_port () const +{ + return _midi_ports->midi_clock_input_port (); +} +boost::shared_ptr<MidiPort> +Session::mtc_output_port () const +{ + return _midi_ports->mtc_output_port (); +} +boost::shared_ptr<MidiPort> +Session::mtc_input_port () const +{ + return _midi_ports->mtc_input_port (); +} + +MIDI::Port* +Session::mmc_output_port () const +{ + return _midi_ports->mmc_output_port (); +} + +MIDI::Port* +Session::mmc_input_port () const +{ + return _midi_ports->mmc_input_port (); +} diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index cecbd88f65..3c46a2e0a6 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -40,7 +40,6 @@ #include "ardour/ticker.h" #include "ardour/types.h" -#include "midi++/manager.h" #include "midi++/mmc.h" #include "i18n.h" @@ -85,7 +84,7 @@ Session::process (pframes_t nframes) try { if (!_engine.freewheeling() && Config->get_send_midi_clock() && transport_speed() == 1.0f && midi_clock->has_midi_port()) { - midi_clock->tick (transport_at_start); + midi_clock->tick (transport_at_start, nframes); } } catch (...) { /* don't bother with a message */ @@ -325,7 +324,7 @@ Session::process_with_events (pframes_t nframes) * and prepare for rolling) */ if (_send_timecode_update) { - send_full_time_code (_transport_frame); + send_full_time_code (_transport_frame, nframes); } if (!process_can_proceed()) { diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 9699a7c37f..64f8e387be 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 1999-2002 Paul Davis + Copyright (C) 1999-2013 Paul Davis 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 @@ -59,7 +59,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "evoral/SMF.hpp" @@ -88,6 +87,7 @@ #include "ardour/control_protocol_manager.h" #include "ardour/directory_names.h" #include "ardour/filename_extensions.h" +#include "ardour/graph.h" #include "ardour/location.h" #include "ardour/midi_model.h" #include "ardour/midi_patch_manager.h" @@ -126,112 +126,47 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -/** @param snapshot_name Snapshot name, without the .ardour prefix */ void -Session::first_stage_init (string fullpath, string snapshot_name) +Session::pre_engine_init (string fullpath) { - if (fullpath.length() == 0) { + if (fullpath.empty()) { destroy (); throw failed_constructor(); } - _path = canonical_path (fullpath); + /* discover canonical fullpath */ - if (_path[_path.length()-1] != G_DIR_SEPARATOR) { - _path += G_DIR_SEPARATOR; + char buf[PATH_MAX+1]; + if (!realpath (fullpath.c_str(), buf) && (errno != ENOENT)) { + error << string_compose(_("Could not use path %1 (%2)"), buf, strerror(errno)) << endmsg; + destroy (); + throw failed_constructor(); } - /* these two are just provisional settings. set_state() - will likely override them. - */ - - _name = _current_snapshot_name = snapshot_name; + _path = string(buf); + + /* we require _path to end with a dir separator */ - set_history_depth (Config->get_history_depth()); + if (_path[_path.length()-1] != G_DIR_SEPARATOR) { + _path += G_DIR_SEPARATOR; + } - _current_frame_rate = _engine.frame_rate (); - _nominal_frame_rate = _current_frame_rate; - _base_frame_rate = _current_frame_rate; + /* is it new ? */ - _tempo_map = new TempoMap (_current_frame_rate); - _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1)); + _is_new = !Glib::file_test (_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); + /* finish initialization that can't be done in a normal C++ constructor + definition. + */ - _non_soloed_outs_muted = false; - _listen_cnt = 0; - _solo_isolated_cnt = 0; + timerclear (&last_mmc_step); g_atomic_int_set (&processing_prohibited, 0); - _transport_speed = 0; - _default_transport_speed = 1.0; - _last_transport_speed = 0; - _target_transport_speed = 0; - auto_play_legal = false; - transport_sub_state = 0; - _transport_frame = 0; - _requested_return_frame = -1; - _session_range_location = 0; g_atomic_int_set (&_record_status, Disabled); - loop_changing = false; - play_loop = false; - have_looped = false; - _last_roll_location = 0; - _last_roll_or_reversal_location = 0; - _last_record_location = 0; - pending_locate_frame = 0; - pending_locate_roll = false; - pending_locate_flush = false; - state_was_pending = false; - set_next_event (); - outbound_mtc_timecode_frame = 0; - next_quarter_frame_to_send = -1; - current_block_size = 0; - solo_update_disabled = false; - _have_captured = false; - _worst_output_latency = 0; - _worst_input_latency = 0; - _worst_track_latency = 0; - _state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading); - _was_seamless = Config->get_seamless_loop (); - _slave = 0; - _send_qf_mtc = false; - _pframes_since_last_mtc = 0; g_atomic_int_set (&_playback_load, 100); g_atomic_int_set (&_capture_load, 100); - _play_range = false; - _exporting = false; - pending_abort = false; - _adding_routes_in_progress = false; - destructive_index = 0; - first_file_data_format_reset = true; - first_file_header_format_reset = true; - post_export_sync = false; - midi_control_ui = 0; - _step_editors = 0; - no_questions_about_missing_files = false; - _speakers.reset (new Speakers); - _clicks_cleared = 0; - ignore_route_processor_changes = false; - _pre_export_mmc_enabled = false; - - AudioDiskstream::allocate_working_buffers(); - - /* default short fade = 15ms */ - - SndFileSource::setup_standard_crossfades (*this, frame_rate()); - - last_mmc_step.tv_sec = 0; - last_mmc_step.tv_usec = 0; - step_speed = 0.0; - - /* click sounds are unset by default, which causes us to internal - waveforms for clicks. - */ - - click_length = 0; - click_emphasis_length = 0; - _clicking = false; - - process_function = &Session::process_with_events; + set_next_event (); + _all_route_group->set_active (true, this); + interpolation.add_channel_to (0, 0); if (config.get_use_video_sync()) { waiting_for_sync_offset = true; @@ -239,32 +174,26 @@ Session::first_stage_init (string fullpath, string snapshot_name) waiting_for_sync_offset = false; } - last_timecode_when = 0; - last_timecode_valid = false; - - sync_time_vars (); - last_rr_session_dir = session_dirs.begin(); - refresh_disk_space (); + + set_history_depth (Config->get_history_depth()); + + if (how_many_dsp_threads () > 1) { + /* For now, only create the graph if we are using >1 DSP threads, as + it is a bit slower than the old code with 1 thread. + */ + _process_graph.reset (new Graph (*this)); + } /* default: assume simple stereo speaker configuration */ _speakers->setup_default_speakers (2); - /* slave stuff */ - - average_slave_delta = 1800; // !!! why 1800 ???? - have_first_delta_accumulator = false; - delta_accumulator_cnt = 0; - _slave_state = Stopped; - _solo_cut_control.reset (new ProxyControllable (_("solo cut control (dB)"), PBD::Controllable::GainLike, boost::bind (&RCConfiguration::set_solo_mute_gain, Config, _1), boost::bind (&RCConfiguration::get_solo_mute_gain, Config))); add_controllable (_solo_cut_control); - _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this)); - /* These are all static "per-class" signals */ SourceFactory::SourceCreated.connect_same_thread (*this, boost::bind (&Session::add_source, this, _1)); @@ -277,87 +206,101 @@ Session::first_stage_init (string fullpath, string snapshot_name) Delivery::disable_panners (); IO::disable_connecting (); + + AudioFileSource::set_peak_dir (_session_dir->peak_path()); } int -Session::second_stage_init () +Session::post_engine_init () { - AudioFileSource::set_peak_dir (_session_dir->peak_path()); + BootMessage (_("Set block size and sample rate")); - if (!_is_new) { - if (load_state (_current_snapshot_name)) { - return -1; - } - } + set_block_size (_engine.samples_per_cycle()); + set_frame_rate (_engine.sample_rate()); + + n_physical_outputs = _engine.n_physical_outputs (); + n_physical_inputs = _engine.n_physical_inputs (); + BootMessage (_("Using configuration")); + + _midi_ports = new MidiPortManager; + setup_midi_machine_control (); + if (_butler->start_thread()) { return -1; } - + if (start_midi_thread ()) { return -1; } + + setup_click_sounds (0); + setup_midi_control (); - setup_midi_machine_control (); - - // set_state() will call setup_raid_path(), but if it's a new session we need - // to call setup_raid_path() here. - - if (state_tree) { - if (set_state (*state_tree->root(), Stateful::loading_state_version)) { - return -1; - } - } else { - setup_raid_path(_path); - } + _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this)); + _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this)); - /* we can't save till after ::when_engine_running() is called, - because otherwise we save state with no connections made. - therefore, we reset _state_of_the_state because ::set_state() - will have cleared it. + try { + /* tempo map requires sample rate knowledge */ - we also have to include Loading so that any events that get - generated between here and the end of ::when_engine_running() - will be processed directly rather than queued. - */ + _tempo_map = new TempoMap (_current_frame_rate); + _tempo_map->PropertyChanged.connect_same_thread (*this, boost::bind (&Session::tempo_map_changed, this, _1)); + + /* MidiClock requires a tempo map */ - _state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading); + midi_clock = new MidiClockTicker (); + midi_clock->set_session (this); - _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this)); - _locations->added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1)); - setup_click_sounds (0); - setup_midi_control (); + /* crossfades require sample rate knowledge */ - /* Pay attention ... */ + SndFileSource::setup_standard_crossfades (*this, frame_rate()); + _engine.GraphReordered.connect_same_thread (*this, boost::bind (&Session::graph_reordered, this)); + + AudioDiskstream::allocate_working_buffers(); + refresh_disk_space (); + + /* we're finally ready to call set_state() ... all objects have + * been created, the engine is running. + */ + + if (state_tree) { + if (set_state (*state_tree->root(), Stateful::loading_state_version)) { + return -1; + } + } else { + // set_state() will call setup_raid_path(), but if it's a new session we need + // to call setup_raid_path() here. + setup_raid_path (_path); + } - _engine.Halted.connect_same_thread (*this, boost::bind (&Session::engine_halted, this)); - _engine.Xrun.connect_same_thread (*this, boost::bind (&Session::xrun_recovery, this)); + /* ENGINE */ - midi_clock = new MidiClockTicker (); - midi_clock->set_session (this); + boost::function<void (std::string)> ff (boost::bind (&Session::config_changed, this, _1, false)); + boost::function<void (std::string)> ft (boost::bind (&Session::config_changed, this, _1, true)); + + Config->map_parameters (ff); + config.map_parameters (ft); - try { when_engine_running (); - } - - /* handle this one in a different way than all others, so that its clear what happened */ - catch (AudioEngine::PortRegistrationFailure& err) { + _locations->changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this)); + _locations->added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1)); + + } catch (AudioEngine::PortRegistrationFailure& err) { + /* handle this one in a different way than all others, so that its clear what happened */ error << err.what() << endmsg; return -1; - } - - catch (...) { + } catch (...) { return -1; } BootMessage (_("Reset Remote Controls")); - send_full_time_code (0); + // send_full_time_code (0); _engine.transport_locate (0); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset)); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (Timecode::Time ())); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset)); + _mmc->send (MIDI::MachineControlCommand (Timecode::Time ())); MIDI::Name::MidiPatchManager::instance().set_session (this); @@ -366,18 +309,16 @@ Session::second_stage_init () _state_of_the_state = Clean; - Port::set_connecting_blocked (false); - DirtyChanged (); /* EMIT SIGNAL */ - if (state_was_pending) { - save_state (_current_snapshot_name); + if (_is_new) { + save_state (""); + } else if (state_was_pending) { + save_state (""); remove_pending_capture_state (); state_was_pending = false; } - BootMessage (_("Session loading complete")); - return 0; } @@ -604,8 +545,6 @@ Session::create (const string& session_template, BusProfile* bus_profile) add_monitor_section (); } - save_state (""); - return 0; } @@ -684,62 +623,6 @@ Session::remove_state (string snapshot_name) } } -#ifdef HAVE_JACK_SESSION -void -Session::jack_session_event (jack_session_event_t * event) -{ - char timebuf[128], *tmp; - time_t n; - struct tm local_time; - - time (&n); - localtime_r (&n, &local_time); - strftime (timebuf, sizeof(timebuf), "JS_%FT%T", &local_time); - - while ((tmp = strchr(timebuf, ':'))) { *tmp = '.'; } - - if (event->type == JackSessionSaveTemplate) - { - if (save_template( timebuf )) { - event->flags = JackSessionSaveError; - } else { - string cmd ("ardour3 -P -U "); - cmd += event->client_uuid; - cmd += " -T "; - cmd += timebuf; - - event->command_line = strdup (cmd.c_str()); - } - } - else - { - if (save_state (timebuf)) { - event->flags = JackSessionSaveError; - } else { - std::string xml_path (_session_dir->root_path()); - std::string legalized_filename = legalize_for_path (timebuf) + statefile_suffix; - xml_path = Glib::build_filename (xml_path, legalized_filename); - - string cmd ("ardour3 -P -U "); - cmd += event->client_uuid; - cmd += " \""; - cmd += xml_path; - cmd += '\"'; - - event->command_line = strdup (cmd.c_str()); - } - } - - jack_session_reply (_engine.jack(), event); - - if (event->type == JackSessionSaveAndQuit) { - Quit (); /* EMIT SIGNAL */ - } - - jack_session_event_free( event ); -} -#endif - /** @param snapshot_name Name to save under, without .ardour / .pending prefix */ int Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot) @@ -1034,6 +917,15 @@ Session::state (bool full_state) /* various options */ + list<XMLNode*> midi_port_nodes = _midi_ports->get_midi_port_states(); + if (!midi_port_nodes.empty()) { + XMLNode* midi_port_stuff = new XMLNode ("MIDIPorts"); + for (list<XMLNode*>::const_iterator n = midi_port_nodes.begin(); n != midi_port_nodes.end(); ++n) { + midi_port_stuff->add_child_nocopy (**n); + } + node->add_child_nocopy (*midi_port_stuff); + } + node->add_child_nocopy (config.get_variables ()); node->add_child_nocopy (ARDOUR::SessionMetadata::Metadata()->get_state()); @@ -1239,6 +1131,11 @@ Session::set_state (const XMLNode& node, int version) Evoral::init_event_id_counter (atoi (prop->value())); } + + if ((child = find_named_node (node, "MIDIPorts")) != 0) { + _midi_ports->set_midi_port_states (child->children()); + } + IO::disable_connecting (); Stateful::save_extra_xml (node); @@ -3453,11 +3350,11 @@ Session::config_changed (std::string p, bool ours) } else if (p == "mmc-device-id" || p == "mmc-receive-id" || p == "mmc-receive-device-id") { - MIDI::Manager::instance()->mmc()->set_receive_device_id (Config->get_mmc_receive_device_id()); + _mmc->set_receive_device_id (Config->get_mmc_receive_device_id()); } else if (p == "mmc-send-id" || p == "mmc-send-device-id") { - MIDI::Manager::instance()->mmc()->set_send_device_id (Config->get_mmc_send_device_id()); + _mmc->set_send_device_id (Config->get_mmc_send_device_id()); } else if (p == "midi-control") { @@ -3520,7 +3417,7 @@ Session::config_changed (std::string p, bool ours) } else if (p == "send-mmc") { - MIDI::Manager::instance()->mmc()->enable_send (Config->get_send_mmc ()); + _mmc->enable_send (Config->get_send_mmc ()); } else if (p == "midi-feedback") { @@ -3578,13 +3475,13 @@ Session::config_changed (std::string p, bool ours) } else if (p == "initial-program-change") { - if (MIDI::Manager::instance()->mmc()->output_port() && Config->get_initial_program_change() >= 0) { + if (_mmc->output_port() && Config->get_initial_program_change() >= 0) { MIDI::byte buf[2]; buf[0] = MIDI::program; // channel zero by default buf[1] = (Config->get_initial_program_change() & 0x7f); - MIDI::Manager::instance()->mmc()->output_port()->midimsg (buf, sizeof (buf), 0); + _mmc->output_port()->midimsg (buf, sizeof (buf), 0); } } else if (p == "solo-mute-override") { // catch_up_on_solo_mute_override (); @@ -3648,27 +3545,28 @@ Session::load_diskstreams_2X (XMLNode const & node, int) void Session::setup_midi_machine_control () { - MIDI::MachineControl* mmc = MIDI::Manager::instance()->mmc (); - - mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); - mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); - mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1)); - mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1)); - mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1)); - mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1)); - mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1)); - mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1)); - mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1)); - mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2)); - mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2)); - mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3)); - mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3)); + _mmc = new MIDI::MachineControl; + _mmc->set_ports (_midi_ports->mmc_input_port(), _midi_ports->mmc_output_port()); + + _mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); + _mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1)); + _mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1)); + _mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1)); + _mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1)); + _mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1)); + _mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1)); + _mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1)); + _mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1)); + _mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2)); + _mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2)); + _mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3)); + _mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3)); /* also handle MIDI SPP because its so common */ - mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this)); - mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this)); - mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this)); + _mmc->SPPStart.connect_same_thread (*this, boost::bind (&Session::spp_start, this)); + _mmc->SPPContinue.connect_same_thread (*this, boost::bind (&Session::spp_continue, this)); + _mmc->SPPStop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this)); } boost::shared_ptr<Controllable> diff --git a/libs/ardour/session_time.cc b/libs/ardour/session_time.cc index 18805afa90..0f2186c09b 100644 --- a/libs/ardour/session_time.cc +++ b/libs/ardour/session_time.cc @@ -180,31 +180,30 @@ Session::timecode_time (Timecode::Time &t) } int -Session::jack_sync_callback (jack_transport_state_t state, - jack_position_t* pos) +Session::backend_sync_callback (TransportState state, framepos_t pos) { bool slave = synced_to_jack(); switch (state) { - case JackTransportStopped: - if (slave && _transport_frame != pos->frame && post_transport_work() == 0) { - request_locate (pos->frame, false); + case TransportStopped: + if (slave && _transport_frame != pos && post_transport_work() == 0) { + request_locate (pos, false); // cerr << "SYNC: stopped, locate to " << pos->frame << " from " << _transport_frame << endl; return false; } else { return true; } - case JackTransportStarting: + case TransportStarting: // cerr << "SYNC: starting @ " << pos->frame << " a@ " << _transport_frame << " our work = " << post_transport_work() << " pos matches ? " << (_transport_frame == pos->frame) << endl; if (slave) { - return _transport_frame == pos->frame && post_transport_work() == 0; + return _transport_frame == pos && post_transport_work() == 0; } else { return true; } break; - case JackTransportRolling: + case TransportRolling: // cerr << "SYNC: rolling slave = " << slave << endl; if (slave) { start_transport (); @@ -212,93 +211,13 @@ Session::jack_sync_callback (jack_transport_state_t state, break; default: - error << string_compose (_("Unknown JACK transport state %1 in sync callback"), state) + error << string_compose (_("Unknown transport state %1 in sync callback"), state) << endmsg; } return true; } -void -Session::jack_timebase_callback (jack_transport_state_t /*state*/, - pframes_t /*nframes*/, - jack_position_t* pos, - int /*new_position*/) -{ - Timecode::BBT_Time bbt; - - /* BBT info */ - - if (_tempo_map) { - - TempoMetric metric (_tempo_map->metric_at (_transport_frame)); - - try { - _tempo_map->bbt_time_rt (_transport_frame, bbt); - - pos->bar = bbt.bars; - pos->beat = bbt.beats; - pos->tick = bbt.ticks; - - // XXX still need to set bar_start_tick - - pos->beats_per_bar = metric.meter().divisions_per_bar(); - pos->beat_type = metric.meter().note_divisor(); - pos->ticks_per_beat = Timecode::BBT_Time::ticks_per_beat; - pos->beats_per_minute = metric.tempo().beats_per_minute(); - - pos->valid = jack_position_bits_t (pos->valid | JackPositionBBT); - - } catch (...) { - /* no message */ - } - } - -#ifdef HAVE_JACK_VIDEO_SUPPORT - //poke audio video ratio so Ardour can track Video Sync - pos->audio_frames_per_video_frame = frame_rate() / timecode_frames_per_second(); - pos->valid = jack_position_bits_t (pos->valid | JackAudioVideoRatio); -#endif - -#if 0 - /* Timecode info */ - - pos->timecode_offset = config.get_timecode_offset(); - t.timecode_frame_rate = timecode_frames_per_second(); - pos->valid = jack_position_bits_t (pos->valid | JackPositionTimecode; - - if (_transport_speed) { - - if (play_loop) { - - Location* location = _locations.auto_loop_location(); - - if (location) { - - t.transport_state = JackTransportLooping; - t.loop_start = location->start(); - t.loop_end = location->end(); - t.valid = jack_transport_bits_t (t.valid | JackTransportLoop); - - } else { - - t.loop_start = 0; - t.loop_end = 0; - t.transport_state = JackTransportRolling; - - } - - } else { - - t.loop_start = 0; - t.loop_end = 0; - t.transport_state = JackTransportRolling; - - } - - } -#endif -} ARDOUR::framecnt_t Session::convert_to_frames (AnyTime const & position) diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 51d8d2c369..ab6d68fd67 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -33,7 +33,6 @@ #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/manager.h" #include "ardour/audioengine.h" #include "ardour/auditioner.h" @@ -610,11 +609,23 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) have_looped = false; - if (!_engine.freewheeling()) { - send_full_time_code (_transport_frame); + /* don't bother with this stuff if we're disconnected from the engine, + because there will be no process callbacks to deliver stuff from + */ + + if (_engine.connected() && !_engine.freewheeling()) { + // need to queue this in the next RT cycle + _send_timecode_update = true; if (!dynamic_cast<MTC_Slave*>(_slave)) { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop)); + + /* This (::non_realtime_stop()) gets called by main + process thread, which will lead to confusion + when calling AsyncMIDIPort::write(). + + Something must be done. XXX + */ send_mmc_locate (_transport_frame); } } @@ -1260,7 +1271,7 @@ Session::start_transport () Timecode::Time time; timecode_time_subframes (_transport_frame, time); if (!dynamic_cast<MTC_Slave*>(_slave)) { - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); + _mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay)); } } @@ -1338,8 +1349,9 @@ Session::use_sync_source (Slave* new_slave) _slave = new_slave; DEBUG_TRACE (DEBUG::Slave, string_compose ("set new slave to %1\n", _slave)); - - send_full_time_code (_transport_frame); + + // need to queue this for next process() cycle + _send_timecode_update = true; boost::shared_ptr<RouteList> rl = routes.reader(); for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { @@ -1380,7 +1392,7 @@ Session::switch_to_sync_source (SyncSource src) } try { - new_slave = new MTC_Slave (*this, *MIDI::Manager::instance()->mtc_input_port()); + new_slave = new MTC_Slave (*this, *_midi_ports->mtc_input_port()); } catch (failed_constructor& err) { @@ -1409,7 +1421,7 @@ Session::switch_to_sync_source (SyncSource src) } try { - new_slave = new MIDIClock_Slave (*this, *MIDI::Manager::instance()->midi_clock_input_port(), 24); + new_slave = new MIDIClock_Slave (*this, *_midi_ports->midi_clock_input_port(), 24); } catch (failed_constructor& err) { @@ -1426,7 +1438,7 @@ Session::switch_to_sync_source (SyncSource src) return; } - new_slave = new JACK_Slave (_engine.jack()); + new_slave = new JACK_Slave (*AudioEngine::instance()); break; default: @@ -1616,16 +1628,6 @@ Session::allow_auto_play (bool yn) auto_play_legal = yn; } -void -Session::reset_jack_connection (jack_client_t* jack) -{ - JACK_Slave* js; - - if (_slave && ((js = dynamic_cast<JACK_Slave*> (_slave)) != 0)) { - js->reset_client (jack); - } -} - bool Session::maybe_stop (framepos_t limit) { @@ -1646,7 +1648,7 @@ Session::send_mmc_locate (framepos_t t) if (!_engine.freewheeling()) { Timecode::Time time; timecode_time_subframes (t, time); - MIDI::Manager::instance()->mmc()->send (MIDI::MachineControlCommand (time)); + _mmc->send (MIDI::MachineControlCommand (time)); } } diff --git a/libs/ardour/slave.cc b/libs/ardour/slave.cc index 1cbcba8ee6..e8c63b43c1 100644 --- a/libs/ardour/slave.cc +++ b/libs/ardour/slave.cc @@ -50,13 +50,13 @@ SlaveSessionProxy::transport_frame() const pframes_t SlaveSessionProxy::frames_since_cycle_start() const { - return session.engine().frames_since_cycle_start(); + return session.engine().samples_since_cycle_start(); } framepos_t SlaveSessionProxy::frame_time() const { - return session.engine().frame_time(); + return session.engine().sample_time(); } void diff --git a/libs/ardour/ticker.cc b/libs/ardour/ticker.cc index f32cdf9415..9a29df2641 100644 --- a/libs/ardour/ticker.cc +++ b/libs/ardour/ticker.cc @@ -19,13 +19,12 @@ #include "pbd/compose.h" #include "pbd/stacktrace.h" -#include "midi++/port.h" -#include "midi++/jack_midi_port.h" -#include "midi++/manager.h" - #include "evoral/midi_events.h" +#include "ardour/async_midi_port.h" #include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" #include "ardour/ticker.h" #include "ardour/session.h" #include "ardour/tempo.h" @@ -95,16 +94,16 @@ public: MidiClockTicker::MidiClockTicker () - : _midi_port (0) - , _ppqn (24) + : _ppqn (24) , _last_tick (0.0) + , _send_pos (false) + , _send_state (false) { _pos.reset (new Position()); } MidiClockTicker::~MidiClockTicker() { - _midi_port = 0; _pos.reset (0); } @@ -115,7 +114,6 @@ MidiClockTicker::set_session (Session* s) if (_session) { _session->TransportStateChange.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_state_changed, this)); - _session->PositionChanged.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::position_changed, this, _1)); _session->TransportLooped.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::transport_looped, this)); _session->Located.connect_same_thread (_session_connections, boost::bind (&MidiClockTicker::session_located, this)); @@ -139,41 +137,20 @@ MidiClockTicker::session_located() return; } - if (_pos->speed == 0.0f) { - uint32_t where = llrint (_pos->midi_beats); - send_position_event (where, 0); - } else if (_pos->speed == 1.0f) { -#if 1 - /* Experimental. To really do this and have accuracy, the - stop/locate/continue sequence would need queued to send immediately - before the next midi clock. */ - - send_stop_event (0); - - if (_pos->frame == 0) { - send_start_event (0); - } else { - uint32_t where = llrint (_pos->midi_beats); - send_position_event (where, 0); - send_continue_event (0); - } -#endif - } else { - /* Varispeed not supported */ - } + _send_pos = true; } void MidiClockTicker::session_going_away () { SessionHandlePtr::session_going_away(); - _midi_port = 0; + _midi_port.reset (); } void MidiClockTicker::update_midi_clock_port() { - _midi_port = MIDI::Manager::instance()->midi_clock_output_port(); + _midi_port = _session->midi_clock_output_port(); } void @@ -204,49 +181,12 @@ MidiClockTicker::transport_state_changed() return; } - if (_pos->speed == 1.0f) { - - if (_session->get_play_loop()) { - assert(_session->locations()->auto_loop_location()); - - if (_pos->frame == _session->locations()->auto_loop_location()->start()) { - send_start_event(0); - } else { - send_continue_event(0); - } - - } else if (_pos->frame == 0) { - send_start_event(0); - } else { - send_continue_event(0); - } - - // send_midi_clock_event (0); - - } else if (_pos->speed == 0.0f) { - send_stop_event (0); - send_position_event (llrint (_pos->midi_beats), 0); - } + _send_state = true; // tick (_pos->frame); } void -MidiClockTicker::position_changed (framepos_t) -{ -#if 0 - const double speed = _session->transport_speed(); - DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Transport Position Change: %1, speed: %2\n", position, speed)); - - if (speed == 0.0f && Config->get_send_midi_clock()) { - send_position_event (position, 0); - } - - _last_tick = position; -#endif -} - -void MidiClockTicker::transport_looped() { Location* loop_location = _session->locations()->auto_loop_location(); @@ -270,18 +210,68 @@ MidiClockTicker::transport_looped() } void -MidiClockTicker::tick (const framepos_t& /* transport_frame */) +MidiClockTicker::tick (const framepos_t& /* transport_frame */, pframes_t nframes) { if (!Config->get_send_midi_clock() || _session == 0 || _session->transport_speed() != 1.0f || _midi_port == 0) { return; } - MIDI::JackMIDIPort* mp = dynamic_cast<MIDI::JackMIDIPort*> (_midi_port); - if (! mp) { - return; + if (_send_pos) { + if (_pos->speed == 0.0f) { + uint32_t where = llrint (_pos->midi_beats); + send_position_event (where, 0, nframes); + } else if (_pos->speed == 1.0f) { +#if 1 + /* Experimental. To really do this and have accuracy, the + stop/locate/continue sequence would need queued to send immediately + before the next midi clock. */ + + send_stop_event (0, nframes); + + if (_pos->frame == 0) { + send_start_event (0, nframes); + } else { + uint32_t where = llrint (_pos->midi_beats); + send_position_event (where, 0, nframes); + send_continue_event (0, nframes); + } +#endif + } else { + /* Varispeed not supported */ + } + + _send_pos = true; } - const framepos_t end = _pos->frame + mp->nframes_this_cycle(); + if (_send_state) { + if (_pos->speed == 1.0f) { + if (_session->get_play_loop()) { + assert(_session->locations()->auto_loop_location()); + + if (_pos->frame == _session->locations()->auto_loop_location()->start()) { + send_start_event (0, nframes); + } else { + send_continue_event (0, nframes); + } + + } else if (_pos->frame == 0) { + send_start_event (0, nframes); + } else { + send_continue_event (0, nframes); + } + + // send_midi_clock_event (0); + + } else if (_pos->speed == 0.0f) { + send_stop_event (0, nframes); + send_position_event (llrint (_pos->midi_beats), 0, nframes); + } + + _send_state = false; + } + + + const framepos_t end = _pos->frame + nframes; double iter = _last_tick; while (true) { @@ -291,14 +281,14 @@ MidiClockTicker::tick (const framepos_t& /* transport_frame */) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Tick: iter: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n", - iter, _last_tick, next_tick, next_tick_offset, mp ? mp->nframes_this_cycle() : 0)); + iter, _last_tick, next_tick, next_tick_offset, nframes)); - if (!mp || (next_tick_offset >= mp->nframes_this_cycle())) { + if (next_tick_offset >= nframes) { break; } if (next_tick_offset >= 0) { - send_midi_clock_event (next_tick_offset); + send_midi_clock_event (next_tick_offset, nframes); } iter = next_tick; @@ -308,7 +298,6 @@ MidiClockTicker::tick (const framepos_t& /* transport_frame */) _pos->frame = end; } - double MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position) { @@ -322,7 +311,7 @@ MidiClockTicker::one_ppqn_in_frames (framepos_t transport_position) } void -MidiClockTicker::send_midi_clock_event (pframes_t offset) +MidiClockTicker::send_midi_clock_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -330,12 +319,13 @@ MidiClockTicker::send_midi_clock_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Tick with offset %1\n", offset)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CLOCK }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_CLOCK }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_start_event (pframes_t offset) +MidiClockTicker::send_start_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -343,12 +333,13 @@ MidiClockTicker::send_start_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Start %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_START }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_START }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_continue_event (pframes_t offset) +MidiClockTicker::send_continue_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -356,12 +347,13 @@ MidiClockTicker::send_continue_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Continue %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_CONTINUE }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_CONTINUE }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_stop_event (pframes_t offset) +MidiClockTicker::send_stop_event (pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -369,12 +361,13 @@ MidiClockTicker::send_stop_event (pframes_t offset) DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Stop %1\n", _last_tick)); - static uint8_t _midi_clock_tick[1] = { MIDI_CMD_COMMON_STOP }; - _midi_port->write (_midi_clock_tick, 1, offset); + static uint8_t tick_byte = { MIDI_CMD_COMMON_STOP }; + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 1, &tick_byte); } void -MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset) +MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset, pframes_t nframes) { if (!_midi_port) { return; @@ -391,7 +384,8 @@ MidiClockTicker::send_position_event (uint32_t midi_beats, pframes_t offset) msg[1] = midi_beats & 0x007f; msg[2] = midi_beats >> 7; - _midi_port->midimsg (msg, sizeof (msg), offset); + MidiBuffer& mb (_midi_port->get_midi_buffer (nframes)); + mb.push_back (offset, 3, &msg[0]); DEBUG_TRACE (DEBUG::MidiClock, string_compose ("Song Position Sent: %1\n", midi_beats)); } diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index f02863393e..57239cb841 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -550,15 +550,15 @@ Track::playlist () } void -Track::request_jack_monitors_input (bool m) +Track::request_input_monitoring (bool m) { - _diskstream->request_jack_monitors_input (m); + _diskstream->request_input_monitoring (m); } void -Track::ensure_jack_monitors_input (bool m) +Track::ensure_input_monitoring (bool m) { - _diskstream->ensure_jack_monitors_input (m); + _diskstream->ensure_input_monitoring (m); } bool diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 87dce06a2a..485a2284bc 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -1,10 +1,11 @@ -#!/usr/bin/env python +1#!/usr/bin/env python from waflib.extras import autowaf as autowaf from waflib import Options import os import sys import re import subprocess +import sys # default state file version for this build CURRENT_SESSION_FILE_VERSION = 3001 @@ -22,6 +23,7 @@ path_prefix = 'libs/ardour/' libardour_sources = [ 'amp.cc', 'analyser.cc', + 'async_midi_port.cc', 'audio_buffer.cc', 'audio_diskstream.cc', 'audio_library.cc', @@ -44,6 +46,7 @@ libardour_sources = [ 'automation_control.cc', 'automation_list.cc', 'automation_watch.cc', + 'backend_search_path.cc', 'beats_frames_converter.cc', 'broadcast_info.cc', 'buffer.cc', @@ -128,6 +131,7 @@ libardour_sources = [ 'midi_stretch.cc', 'midi_track.cc', 'midi_ui.cc', + 'midiport_manager.cc', 'mix.cc', 'monitor_processor.cc', 'mtc_slave.cc', @@ -151,6 +155,7 @@ libardour_sources = [ 'plugin_manager.cc', 'port.cc', 'port_insert.cc', + 'port_manager.cc', 'port_set.cc', 'process_thread.cc', 'processor.cc', @@ -178,6 +183,7 @@ libardour_sources = [ 'session_events.cc', 'session_export.cc', 'session_handle.cc', + 'session_jack.cc', 'session_ltc.cc', 'session_metadata.cc', 'session_midi.cc', @@ -259,6 +265,10 @@ def configure(conf): atleast_version='0.1.0') autowaf.check_pkg(conf, 'sigc++-2.0', uselib_store='SIGCPP', atleast_version='2.0') + + if re.search ("linux", sys.platform) != None: + autowaf.check_pkg(conf, 'alsa', uselib_store='ALSA') + if Options.options.lv2: autowaf.check_pkg(conf, 'lv2', uselib_store='LV2', atleast_version='1.0.0', mandatory=True) @@ -303,60 +313,6 @@ def configure(conf): conf.check(header_name='unistd.h', define_name='HAVE_UNISTD',mandatory=False) - conf.check_cc(fragment = ''' -#include <jack/jack.h> -void callback(jack_status_t code, const char* reason, void* arg) { return; } -int main(int argc, char **argv) { - jack_client_t* c; - jack_on_info_shutdown(c, callback, (void*) 0); - return 0; -}''', - uselib= [ 'JACK' ], - msg = 'Checking for jack_on_info_shutdown', - define_name = 'HAVE_JACK_ON_INFO_SHUTDOWN', - okmsg = 'present') - - missing_jack_message = 'missing - a version of JACK that supports jack_port_set_latency_range() is required to compile Ardour3.' - - conf.check_cc(fragment = ''' -#include <jack/jack.h> -int main(int argc, char **argv) { - jack_port_t* p; - jack_latency_range_t r; - jack_port_set_latency_range(p, JackCaptureLatency, &r); - return 0; -}''', - uselib = [ 'JACK' ], - msg = 'Checking for new JACK latency API', - okmsg = 'present', - mandatory = True, - errmsg = missing_jack_message) - - conf.check_cc(fragment = ''' -#include <jack/jack.h> -int main(int argc, char **argv) { - jack_port_type_get_buffer_size((jack_client_t*)0, ""); - return 0; -}''', - uselib = [ 'JACK' ], - msg = 'Checking for new jack_port_type_get_buffer_size', - okmsg = 'present', - mandatory = True, - errmsg = missing_jack_message) - - conf.check_cc(fragment = ''' -#include <jack/transport.h> -int main(int argc, char** argv) { - jack_position_t pos; - pos.valid & JackVideoFrameOffset; - return 0; -}''', - uselib= [ 'JACK' ], - msg = 'Checking for JackVideoFrameOffset', - define_name = 'HAVE_JACK_VIDEO_SUPPORT', - mandatory = False, - okmsg = 'present') - if flac_supported(): conf.define ('HAVE_FLAC', 1) if ogg_supported(): diff --git a/libs/backends/jack/jack_api.cc b/libs/backends/jack/jack_api.cc new file mode 100644 index 0000000000..0136161181 --- /dev/null +++ b/libs/backends/jack/jack_api.cc @@ -0,0 +1,102 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 "jack_connection.h" +#include "jack_audiobackend.h" +#include "jack_portengine.h" + +using namespace ARDOUR; + +static boost::shared_ptr<JACKAudioBackend> backend; +static boost::shared_ptr<JACKPortEngine> port_engine; +static boost::shared_ptr<JackConnection> jack_connection; + +static boost::shared_ptr<AudioBackend> +backend_factory (AudioEngine& ae) +{ + if (!jack_connection) { + return boost::shared_ptr<AudioBackend>(); + } + + if (!backend) { + backend.reset (new JACKAudioBackend (ae, jack_connection)); + } + + return backend; +} + +static boost::shared_ptr<PortEngine> +portengine_factory (PortManager& pm) +{ + if (!jack_connection) { + return boost::shared_ptr<PortEngine>(); + } + + if (!port_engine) { + port_engine.reset (new JACKPortEngine (pm, jack_connection)); + } + + return port_engine; +} + +static int +instantiate (const std::string& arg1, const std::string& arg2) +{ + try { + jack_connection.reset (new JackConnection (arg1, arg2)); + } catch (...) { + return -1; + } + + return 0; +} + +static int +deinstantiate () +{ + port_engine.reset (); + backend.reset (); + jack_connection.reset (); + + return 0; +} + +static bool +already_configured () +{ + return JackConnection::server_running (); +} + +extern "C" { + + + /* functions looked up using dlopen-and-cousins, and so naming scope + * must be non-mangled. + */ + + ARDOUR::AudioBackendInfo descriptor = { + "JACK", + instantiate, + deinstantiate, + backend_factory, + portengine_factory, + already_configured, + }; +} + diff --git a/libs/backends/jack/jack_audiobackend.cc b/libs/backends/jack/jack_audiobackend.cc new file mode 100644 index 0000000000..e004849896 --- /dev/null +++ b/libs/backends/jack/jack_audiobackend.cc @@ -0,0 +1,1002 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <string> +#include <list> +#include <math.h> + +#include <boost/scoped_ptr.hpp> +#include <glibmm/timer.h> +#include <glibmm/spawn.h> + +#include "pbd/error.h" + +#include "jack/jack.h" +#include "jack/thread.h" + +#include "ardour/audioengine.h" +#include "ardour/session.h" +#include "ardour/types.h" + +#include "jack_audiobackend.h" +#include "jack_connection.h" +#include "jack_portengine.h" +#include "jack_utils.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +#define GET_PRIVATE_JACK_POINTER(localvar) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(localvar,r) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return r; } + +JACKAudioBackend::JACKAudioBackend (AudioEngine& e, boost::shared_ptr<JackConnection> jc) + : AudioBackend (e) + , _jack_connection (jc) + , _running (false) + , _freewheeling (false) + , _target_sample_rate (48000) + , _target_buffer_size (1024) + , _target_sample_format (FormatFloat) + , _target_interleaved (false) + , _target_input_channels (-1) + , _target_output_channels (-1) + , _target_systemic_input_latency (0) + , _target_systemic_output_latency (0) + , _current_sample_rate (0) + , _current_buffer_size (0) +{ + _jack_connection->Disconnected.connect_same_thread (disconnect_connection, boost::bind (&JACKAudioBackend::disconnected, this, _1)); +} + +JACKAudioBackend::~JACKAudioBackend() +{ +} + +string +JACKAudioBackend::name() const +{ + return X_("JACK"); +} + +void* +JACKAudioBackend::private_handle() const +{ + return _jack_connection->jack(); +} + +bool +JACKAudioBackend::connected() const +{ + return (private_handle() != 0); +} + +bool +JACKAudioBackend::is_realtime () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,false); + return jack_is_realtime (_priv_jack); +} + +bool +JACKAudioBackend::requires_driver_selection() const +{ + return true; +} + +vector<string> +JACKAudioBackend::enumerate_drivers () const +{ + vector<string> currently_available; + get_jack_audio_driver_names (currently_available); + return currently_available; +} + +int +JACKAudioBackend::set_driver (const std::string& name) +{ + _target_driver = name; + return 0; +} + +vector<AudioBackend::DeviceStatus> +JACKAudioBackend::enumerate_devices () const +{ + vector<string> currently_available = get_jack_device_names_for_audio_driver (_target_driver); + vector<DeviceStatus> statuses; + + if (all_devices.find (_target_driver) == all_devices.end()) { + all_devices.insert (make_pair (_target_driver, std::set<string>())); + } + + /* store every device we've found, by driver name. + * + * This is so we do not confuse ALSA, FFADO, netjack etc. devices + * with each other. + */ + + DeviceList& all (all_devices[_target_driver]); + + for (vector<string>::const_iterator d = currently_available.begin(); d != currently_available.end(); ++d) { + all.insert (*d); + } + + for (DeviceList::const_iterator d = all.begin(); d != all.end(); ++d) { + if (find (currently_available.begin(), currently_available.end(), *d) == currently_available.end()) { + statuses.push_back (DeviceStatus (*d, false)); + } else { + statuses.push_back (DeviceStatus (*d, false)); + } + } + + return statuses; +} + +vector<float> +JACKAudioBackend::available_sample_rates (const string& /*device*/) const +{ + vector<float> f; + + if (connected()) { + f.push_back (sample_rate()); + return f; + } + + /* if JACK is not already running, just list a bunch of reasonable + values and let the future sort it all out. + */ + + f.push_back (8000.0); + f.push_back (16000.0); + f.push_back (24000.0); + f.push_back (32000.0); + f.push_back (44100.0); + f.push_back (48000.0); + f.push_back (88200.0); + f.push_back (96000.0); + f.push_back (192000.0); + f.push_back (384000.0); + + return f; +} + +vector<uint32_t> +JACKAudioBackend::available_buffer_sizes (const string& /*device*/) const +{ + vector<uint32_t> s; + + if (connected()) { + s.push_back (buffer_size()); + return s; + } + + s.push_back (8); + s.push_back (16); + s.push_back (32); + s.push_back (64); + s.push_back (128); + s.push_back (256); + s.push_back (512); + s.push_back (1024); + s.push_back (2048); + s.push_back (4096); + s.push_back (8192); + + return s; +} + +uint32_t +JACKAudioBackend::available_input_channel_count (const string& /*device*/) const +{ + return 128; +} + +uint32_t +JACKAudioBackend::available_output_channel_count (const string& /*device*/) const +{ + return 128; +} + +/* -- parameter setting -- */ + +int +JACKAudioBackend::set_device_name (const string& dev) +{ + if (connected()) { + /* need to stop and restart JACK for this to work, at present */ + return -1; + } + + _target_device = dev; + return 0; +} + +int +JACKAudioBackend::set_sample_rate (float sr) +{ + if (!connected()) { + _target_sample_rate = sr; + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (sr == jack_get_sample_rate (_priv_jack)) { + return 0; + } + + return -1; +} + +int +JACKAudioBackend::set_buffer_size (uint32_t nframes) +{ + if (!connected()) { + _target_buffer_size = nframes; + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (nframes == jack_get_buffer_size (_priv_jack)) { + return 0; + } + + return jack_set_buffer_size (_priv_jack, nframes); +} + +int +JACKAudioBackend::set_sample_format (SampleFormat sf) +{ + /* as far as JACK clients are concerned, the hardware is always + * floating point format. + */ + if (sf == FormatFloat) { + return 0; + } + return -1; +} + +int +JACKAudioBackend::set_interleaved (bool yn) +{ + /* as far as JACK clients are concerned, the hardware is always + * non-interleaved + */ + if (!yn) { + return 0; + } + return -1; +} + +int +JACKAudioBackend::set_input_channels (uint32_t cnt) +{ + if (connected()) { + return -1; + } + + _target_input_channels = cnt; + + return 0; +} + +int +JACKAudioBackend::set_output_channels (uint32_t cnt) +{ + if (connected()) { + return -1; + } + + _target_output_channels = cnt; + + return 0; +} + +int +JACKAudioBackend::set_systemic_input_latency (uint32_t l) +{ + if (connected()) { + return -1; + } + + _target_systemic_input_latency = l; + + return 0; +} + +int +JACKAudioBackend::set_systemic_output_latency (uint32_t l) +{ + if (connected()) { + return -1; + } + + _target_systemic_output_latency = l; + + return 0; +} + +/* --- Parameter retrieval --- */ + +std::string +JACKAudioBackend::device_name () const +{ + if (connected()) { + return "???"; + } + + return _target_device; +} + +float +JACKAudioBackend::sample_rate () const +{ + if (connected()) { + return _current_sample_rate; + } + return _target_sample_rate; +} + +uint32_t +JACKAudioBackend::buffer_size () const +{ + if (connected()) { + return _current_buffer_size; + } + return _target_buffer_size; +} + +SampleFormat +JACKAudioBackend::sample_format () const +{ + return FormatFloat; +} + +bool +JACKAudioBackend::interleaved () const +{ + return false; +} + +uint32_t +JACKAudioBackend::input_channels () const +{ + if (connected()) { + return n_physical (JackPortIsInput).n_audio(); + } + return _target_input_channels; +} + +uint32_t +JACKAudioBackend::output_channels () const +{ + if (connected()) { + return n_physical (JackPortIsOutput).n_audio(); + } + return _target_output_channels; +} + +uint32_t +JACKAudioBackend::systemic_input_latency () const +{ + return _target_systemic_output_latency; +} + +uint32_t +JACKAudioBackend::systemic_output_latency () const +{ + return _target_systemic_output_latency; +} + +size_t +JACKAudioBackend::raw_buffer_size(DataType t) +{ + std::map<DataType,size_t>::const_iterator s = _raw_buffer_sizes.find(t); + return (s != _raw_buffer_sizes.end()) ? s->second : 0; +} + +void +JACKAudioBackend::setup_jack_startup_command () +{ + /* first we map the parameters that have been set onto a + * JackCommandLineOptions object. + */ + + JackCommandLineOptions options; + + get_jack_default_server_path (options.server_path); + options.driver = _target_driver; + options.samplerate = _target_sample_rate; + options.period_size = _target_buffer_size; + options.num_periods = 2; + options.input_device = _target_device; + options.output_device = _target_device; + options.input_latency = _target_systemic_input_latency; + options.output_latency = _target_systemic_output_latency; + options.input_channels = _target_input_channels; + options.output_channels = _target_output_channels; + if (_target_sample_format == FormatInt16) { + options.force16_bit = _target_sample_format; + } + options.realtime = true; + options.ports_max = 2048; + + /* this must always be true for any server instance we start ourselves + */ + + options.temporary = true; + + string cmdline; + + if (!get_jack_command_line_string (options, cmdline)) { + /* error, somehow */ + return; + } + + std::cerr << "JACK command line will be: " << cmdline << std::endl; + + write_jack_config_file (get_jack_server_user_config_file_path(), cmdline); +} + +/* ---- BASIC STATE CONTROL API: start/stop/pause/freewheel --- */ + +int +JACKAudioBackend::start () +{ + if (!connected()) { + + if (!_jack_connection->server_running()) { + setup_jack_startup_command (); + } + + if (_jack_connection->open ()) { + return -1; + } + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + /* get the buffer size and sample rates established */ + + jack_sample_rate_callback (jack_get_sample_rate (_priv_jack)); + jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); + + /* Now that we have buffer size and sample rate established, the engine + can go ahead and do its stuff + */ + + engine.reestablish_ports (); + + if (!jack_port_type_get_buffer_size) { + warning << _("This version of JACK is old - you should upgrade to a newer version that supports jack_port_type_get_buffer_size()") << endmsg; + } + + set_jack_callbacks (); + + if (jack_activate (_priv_jack) == 0) { + _running = true; + } else { + // error << _("cannot activate JACK client") << endmsg; + } + + engine.reconnect_ports (); + + return 0; +} + +int +JACKAudioBackend::stop () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + _jack_connection->close (); + + _current_buffer_size = 0; + _current_sample_rate = 0; + + _raw_buffer_sizes.clear(); + + return 0; +} + +int +JACKAudioBackend::pause () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (_priv_jack) { + jack_deactivate (_priv_jack); + } + + return 0; +} + +int +JACKAudioBackend::freewheel (bool onoff) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (onoff == _freewheeling) { + /* already doing what has been asked for */ + + return 0; + } + + if (jack_set_freewheel (_priv_jack, onoff) == 0) { + _freewheeling = true; + return 0; + } + + return -1; +} + +/* --- TRANSPORT STATE MANAGEMENT --- */ + +void +JACKAudioBackend::transport_stop () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_stop (_priv_jack); +} + +void +JACKAudioBackend::transport_start () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_start (_priv_jack); +} + +void +JACKAudioBackend::transport_locate (framepos_t where) +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_locate (_priv_jack, where); +} + +framepos_t +JACKAudioBackend::transport_frame () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_get_current_transport_frame (_priv_jack); +} + +TransportState +JACKAudioBackend::transport_state () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, ((TransportState) JackTransportStopped)); + jack_position_t pos; + return (TransportState) jack_transport_query (_priv_jack, &pos); +} + +int +JACKAudioBackend::set_time_master (bool yn) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + if (yn) { + return jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); + } else { + return jack_release_timebase (_priv_jack); + } +} + +/* process-time */ + +bool +JACKAudioBackend::get_sync_offset (pframes_t& offset) const +{ + +#ifdef HAVE_JACK_VIDEO_SUPPORT + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + + jack_position_t pos; + + if (_priv_jack) { + (void) jack_transport_query (_priv_jack, &pos); + + if (pos.valid & JackVideoFrameOffset) { + offset = pos.video_offset; + return true; + } + } +#else + /* keep gcc happy */ + offset = 0; +#endif + + return false; +} + +pframes_t +JACKAudioBackend::sample_time () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_frame_time (_priv_jack); +} + +pframes_t +JACKAudioBackend::sample_time_at_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_last_frame_time (_priv_jack); +} + +pframes_t +JACKAudioBackend::samples_since_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_frames_since_cycle_start (_priv_jack); +} + +/* JACK Callbacks */ + +static void +ardour_jack_error (const char* msg) +{ + error << "JACK: " << msg << endmsg; +} + +void +JACKAudioBackend::set_jack_callbacks () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + + jack_set_thread_init_callback (_priv_jack, AudioEngine::thread_init_callback, 0); + + jack_set_process_thread (_priv_jack, _process_thread, this); + jack_set_sample_rate_callback (_priv_jack, _sample_rate_callback, this); + jack_set_buffer_size_callback (_priv_jack, _bufsize_callback, this); + jack_set_xrun_callback (_priv_jack, _xrun_callback, this); + jack_set_sync_callback (_priv_jack, _jack_sync_callback, this); + jack_set_freewheel_callback (_priv_jack, _freewheel_callback, this); + +#ifdef HAVE_JACK_SESSION + if( jack_set_session_callback) + jack_set_session_callback (_priv_jack, _session_callback, this); +#endif + + if (jack_set_latency_callback) { + jack_set_latency_callback (_priv_jack, _latency_callback, this); + } + + jack_set_error_function (ardour_jack_error); +} + +void +JACKAudioBackend::_jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, + jack_position_t* pos, int new_position, void *arg) +{ + static_cast<JACKAudioBackend*> (arg)->jack_timebase_callback (state, nframes, pos, new_position); +} + +void +JACKAudioBackend::jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, + jack_position_t* pos, int new_position) +{ + ARDOUR::Session* session = engine.session(); + + if (session) { + session->jack_timebase_callback (state, nframes, pos, new_position); + } +} + +int +JACKAudioBackend::_jack_sync_callback (jack_transport_state_t state, jack_position_t* pos, void* arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_sync_callback (state, pos); +} + +int +JACKAudioBackend::jack_sync_callback (jack_transport_state_t state, jack_position_t* pos) +{ + TransportState tstate; + + switch (state) { + case JackTransportStopped: + tstate = TransportStopped; + break; + case JackTransportRolling: + tstate = TransportRolling; + break; + case JackTransportLooping: + tstate = TransportLooping; + break; + case JackTransportStarting: + tstate = TransportStarting; + break; + } + + return engine.sync_callback (tstate, pos->frame); + + return true; +} + +int +JACKAudioBackend::_xrun_callback (void *arg) +{ + JACKAudioBackend* ae = static_cast<JACKAudioBackend*> (arg); + if (ae->connected()) { + ae->engine.Xrun (); /* EMIT SIGNAL */ + } + return 0; +} + +#ifdef HAVE_JACK_SESSION +void +JACKAudioBackend::_session_callback (jack_session_event_t *event, void *arg) +{ + JACKAudioBackend* ae = static_cast<JACKAudioBackend*> (arg); + ARDOUR::Session* session = ae->engine.session(); + + if (session) { + session->jack_session_event (event); + } +} +#endif + +void +JACKAudioBackend::_freewheel_callback (int onoff, void *arg) +{ + static_cast<JACKAudioBackend*>(arg)->freewheel_callback (onoff); +} + +void +JACKAudioBackend::freewheel_callback (int onoff) +{ + _freewheeling = onoff; + engine.freewheel_callback (onoff); +} + +void +JACKAudioBackend::_latency_callback (jack_latency_callback_mode_t mode, void* arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_latency_callback (mode); +} + +int +JACKAudioBackend::create_process_thread (boost::function<void()> f, pthread_t* thread, size_t stacksize) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + ThreadData* td = new ThreadData (this, f, stacksize); + + if (jack_client_create_thread (_priv_jack, thread, jack_client_real_time_priority (_priv_jack), + jack_is_realtime (_priv_jack), _start_process_thread, td)) { + return -1; + } + + return 0; +} + +void* +JACKAudioBackend::_start_process_thread (void* arg) +{ + ThreadData* td = reinterpret_cast<ThreadData*> (arg); + boost::function<void()> f = td->f; + delete td; + + f (); + + return 0; +} + +void* +JACKAudioBackend::_process_thread (void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->process_thread (); +} + +void* +JACKAudioBackend::process_thread () +{ + /* JACK doesn't do this for us when we use the wait API + */ + + AudioEngine::thread_init_callback (this); + + while (1) { + GET_PRIVATE_JACK_POINTER_RET(_priv_jack,0); + + pframes_t nframes = jack_cycle_wait (_priv_jack); + + if (engine.process_callback (nframes)) { + return 0; + } + + jack_cycle_signal (_priv_jack, 0); + } + + return 0; +} + +int +JACKAudioBackend::_sample_rate_callback (pframes_t nframes, void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_sample_rate_callback (nframes); +} + +int +JACKAudioBackend::jack_sample_rate_callback (pframes_t nframes) +{ + _current_sample_rate = nframes; + return engine.sample_rate_change (nframes); +} + +void +JACKAudioBackend::jack_latency_callback (jack_latency_callback_mode_t mode) +{ + engine.latency_callback (mode == JackPlaybackLatency); +} + +int +JACKAudioBackend::_bufsize_callback (pframes_t nframes, void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_bufsize_callback (nframes); +} + +int +JACKAudioBackend::jack_bufsize_callback (pframes_t nframes) +{ + /* if the size has not changed, this should be a no-op */ + + if (nframes == _current_buffer_size) { + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 1); + + _current_buffer_size = nframes; + + if (jack_port_type_get_buffer_size) { + _raw_buffer_sizes[DataType::AUDIO] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_AUDIO_TYPE); + _raw_buffer_sizes[DataType::MIDI] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_MIDI_TYPE); + } else { + + /* Old version of JACK. + + These crude guesses, see below where we try to get the right answers. + + Note that our guess for MIDI deliberatey tries to overestimate + by a little. It would be nicer if we could get the actual + size from a port, but we have to use this estimate in the + event that there are no MIDI ports currently. If there are + the value will be adjusted below. + */ + + _raw_buffer_sizes[DataType::AUDIO] = nframes * sizeof (Sample); + _raw_buffer_sizes[DataType::MIDI] = nframes * 4 - (nframes/2); + } + + engine.buffer_size_change (nframes); + + return 0; +} + +void +JACKAudioBackend::disconnected (const char* why) +{ + bool was_running = _running; + + _running = false; + _current_buffer_size = 0; + _current_sample_rate = 0; + + if (was_running) { + engine.halted_callback (why); /* EMIT SIGNAL */ + } +} +float +JACKAudioBackend::cpu_load() const +{ + GET_PRIVATE_JACK_POINTER_RET(_priv_jack,0); + return jack_cpu_load (_priv_jack); +} + +void +JACKAudioBackend::update_latencies () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_recompute_total_latencies (_priv_jack); +} + +ChanCount +JACKAudioBackend::n_physical (unsigned long flags) const +{ + ChanCount c; + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, c); + + const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (!strstr (ports[i], "Midi-Through")) { + DataType t (jack_port_type (jack_port_by_name (_priv_jack, ports[i]))); + c.set (t, c.get (t) + 1); + } + } + + jack_free (ports); + } + + return c; +} + +bool +JACKAudioBackend::can_change_sample_rate_when_running () const +{ + return false; +} + +bool +JACKAudioBackend::can_change_buffer_size_when_running () const +{ + return true; +} + +string +JACKAudioBackend::control_app_name () const +{ + /* Since JACK/ALSA really don't provide particularly integrated support + for the idea of a control app to be used to control a device, + allow the user to take some control themselves if necessary. + */ + + const char* env_value = g_getenv ("ARDOUR_DEVICE_CONTROL_APP"); + string appname; + + if (!env_value) { + if (_target_driver.empty() || _target_device.empty()) { + return appname; + } + + if (_target_driver == "ALSA") { + + if (_target_device == "Hammerfall DSP") { + appname = "hdspconf"; + } else if (_target_device == "M Audio Delta 1010") { + appname = "mudita24"; + } + } + } else { + appname = env_value; + } + + return appname; +} + +void +JACKAudioBackend::launch_control_app () +{ + string appname = control_app_name(); + + if (appname.empty()) { + error << string_compose (_("There is no control application for the device \"%1\""), _target_device) << endmsg; + return; + } + + std::list<string> args; + args.push_back (appname); + Glib::spawn_async ("", args, Glib::SPAWN_SEARCH_PATH); +} diff --git a/libs/backends/jack/jack_audiobackend.h b/libs/backends/jack/jack_audiobackend.h new file mode 100644 index 0000000000..1389e15c4a --- /dev/null +++ b/libs/backends/jack/jack_audiobackend.h @@ -0,0 +1,190 @@ +/* + Copyright (C) 2013 Paul Davis + + 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_jack_audiobackend_h__ +#define __libardour_jack_audiobackend_h__ + +#include <string> +#include <vector> +#include <map> +#include <set> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include <jack/jack.h> +#ifdef HAVE_JACK_SESSION +#include <jack/session.h> +#endif + +#include "ardour/audio_backend.h" + +namespace ARDOUR { + +class JackConnection; + +class JACKAudioBackend : public AudioBackend { + public: + JACKAudioBackend (AudioEngine& e, boost::shared_ptr<JackConnection>); + ~JACKAudioBackend (); + + std::string name() const; + void* private_handle() const; + bool connected() const; + bool is_realtime () const; + + bool requires_driver_selection() const; + std::vector<std::string> enumerate_drivers () const; + int set_driver (const std::string&); + + std::vector<DeviceStatus> enumerate_devices () const; + + std::vector<float> available_sample_rates (const std::string& device) const; + std::vector<uint32_t> available_buffer_sizes (const std::string& device) const; + uint32_t available_input_channel_count (const std::string& device) const; + uint32_t available_output_channel_count (const std::string& device) const; + + bool can_change_sample_rate_when_running() const; + bool can_change_buffer_size_when_running() const; + + int set_device_name (const std::string&); + int set_sample_rate (float); + int set_buffer_size (uint32_t); + int set_sample_format (SampleFormat); + int set_interleaved (bool yn); + int set_input_channels (uint32_t); + int set_output_channels (uint32_t); + int set_systemic_input_latency (uint32_t); + int set_systemic_output_latency (uint32_t); + + std::string device_name () const; + float sample_rate () const; + uint32_t buffer_size () const; + SampleFormat sample_format () const; + bool interleaved () const; + uint32_t input_channels () const; + uint32_t output_channels () const; + uint32_t systemic_input_latency () const; + uint32_t systemic_output_latency () const; + + std::string control_app_name () const; + void launch_control_app (); + + int start (); + int stop (); + int pause (); + int freewheel (bool); + + float cpu_load() const; + + pframes_t sample_time (); + pframes_t sample_time_at_cycle_start (); + pframes_t samples_since_cycle_start (); + + size_t raw_buffer_size (DataType t); + + int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize); + + void transport_start (); + void transport_stop (); + void transport_locate (framepos_t /*pos*/); + TransportState transport_state () const; + framepos_t transport_frame() const; + + int set_time_master (bool /*yn*/); + bool get_sync_offset (pframes_t& /*offset*/) const; + + void update_latencies (); + + static bool already_configured(); + + private: + boost::shared_ptr<JackConnection> _jack_connection; //< shared with JACKPortEngine + bool _running; + bool _freewheeling; + std::map<DataType,size_t> _raw_buffer_sizes; + + static int _xrun_callback (void *arg); + static void* _process_thread (void *arg); + static int _sample_rate_callback (pframes_t nframes, void *arg); + static int _bufsize_callback (pframes_t nframes, void *arg); + static void _jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int, void*); + static int _jack_sync_callback (jack_transport_state_t, jack_position_t*, void *arg); + static void _freewheel_callback (int , void *arg); + static void _latency_callback (jack_latency_callback_mode_t, void*); +#ifdef HAVE_JACK_SESSION + static void _session_callback (jack_session_event_t *event, void *arg); +#endif + + void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); + int jack_sync_callback (jack_transport_state_t, jack_position_t*); + int jack_bufsize_callback (pframes_t); + int jack_sample_rate_callback (pframes_t); + void freewheel_callback (int); + int process_callback (pframes_t nframes); + void jack_latency_callback (jack_latency_callback_mode_t); + void disconnected (const char*); + + void set_jack_callbacks (); + int reconnect_to_jack (); + + struct ThreadData { + JACKAudioBackend* engine; + boost::function<void()> f; + size_t stacksize; + + ThreadData (JACKAudioBackend* e, boost::function<void()> fp, size_t stacksz) + : engine (e) , f (fp) , stacksize (stacksz) {} + }; + + void* process_thread (); + static void* _start_process_thread (void*); + + ChanCount n_physical (unsigned long) const; + + void setup_jack_startup_command (); + + /* pffooo */ + + std::string _target_driver; + std::string _target_device; + float _target_sample_rate; + uint32_t _target_buffer_size; + SampleFormat _target_sample_format; + bool _target_interleaved; + uint32_t _target_input_channels; + uint32_t _target_output_channels; + uint32_t _target_systemic_input_latency; + uint32_t _target_systemic_output_latency; + uint32_t _current_sample_rate; + uint32_t _current_buffer_size; + + typedef std::set<std::string> DeviceList; + typedef std::map<std::string,DeviceList> DriverDeviceMap; + + mutable DriverDeviceMap all_devices; + + PBD::ScopedConnection disconnect_connection; +}; + +} // namespace + +#endif /* __ardour_audiobackend_h__ */ + diff --git a/libs/backends/jack/jack_connection.cc b/libs/backends/jack/jack_connection.cc new file mode 100644 index 0000000000..da0127c584 --- /dev/null +++ b/libs/backends/jack/jack_connection.cc @@ -0,0 +1,164 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <boost/scoped_ptr.hpp> +#include <jack/session.h> + +#include "pbd/epa.h" + +#include "jack_connection.h" +#include "jack_utils.h" + +#define GET_PRIVATE_JACK_POINTER(j) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(j,r) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return r; } + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +static void jack_halted_callback (void* arg) +{ + JackConnection* jc = static_cast<JackConnection*> (arg); + jc->halted_callback (); +} + +static void jack_halted_info_callback (jack_status_t code, const char* reason, void* arg) +{ + JackConnection* jc = static_cast<JackConnection*> (arg); + jc->halted_info_callback (code, reason); +} + + +JackConnection::JackConnection (const std::string& arg1, const std::string& arg2) + : _jack (0) + , _client_name (arg1) + , session_uuid (arg2) +{ +} + +JackConnection::~JackConnection () +{ + close (); +} + +bool +JackConnection::server_running () +{ + EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); + boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; + + /* revert all environment settings back to whatever they were when + * ardour started, because ardour's startup script may have reset + * something in ways that interfere with finding/starting JACK. + */ + + if (global_epa) { + current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ + global_epa->restore (); + } + + jack_status_t status; + jack_client_t* c = jack_client_open ("ardourprobe", JackNoStartServer, &status); + + if (status == 0) { + jack_client_close (c); + return true; + } + + return false; +} + +int +JackConnection::open () +{ + EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); + boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; + jack_status_t status; + + close (); + + /* revert all environment settings back to whatever they were when ardour started + */ + + if (global_epa) { + current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ + global_epa->restore (); + } + + /* ensure that PATH or equivalent includes likely locations of the JACK + * server, in case the user's default does not. + */ + + vector<string> dirs; + get_jack_server_dir_paths (dirs); + set_path_env_for_jack_autostart (dirs); + + if ((_jack = jack_client_open (_client_name.c_str(), JackSessionID, &status, session_uuid.c_str())) == 0) { + return -1; + } + + if (status & JackNameNotUnique) { + _client_name = jack_get_client_name (_jack); + } + + /* attach halted handler */ + + if (jack_on_info_shutdown) { + jack_on_info_shutdown (_jack, jack_halted_info_callback, this); + } else { + jack_on_shutdown (_jack, jack_halted_callback, this); + } + + + Connected(); /* EMIT SIGNAL */ + + return 0; +} + +int +JackConnection::close () +{ + GET_PRIVATE_JACK_POINTER_RET (_jack, -1); + + if (_priv_jack) { + int ret = jack_client_close (_priv_jack); + _jack = 0; + Disconnected (""); /* EMIT SIGNAL */ + return ret; + } + + return 0; +} + +void +JackConnection::halted_callback () +{ + _jack = 0; + Disconnected (""); +} + +void +JackConnection::halted_info_callback (jack_status_t /*status*/, const char* reason) +{ + _jack = 0; + Disconnected (reason); +} + + diff --git a/libs/backends/jack/jack_connection.h b/libs/backends/jack/jack_connection.h new file mode 100644 index 0000000000..cd45f3b9ba --- /dev/null +++ b/libs/backends/jack/jack_connection.h @@ -0,0 +1,40 @@ +#ifndef __libardour_jack_connection_h__ +#define __libardour_jack_connection_h__ + +#include <string> +#include <jack/jack.h> + +#include "pbd/signals.h" + +namespace ARDOUR { + +class JackConnection { + public: + JackConnection (const std::string& client_name, const std::string& session_uuid); + ~JackConnection (); + + const std::string& client_name() const { return _client_name; } + + int open (); + int close (); + bool connected () const { return _jack != 0; } + + jack_client_t* jack() const { return _jack; } + + PBD::Signal0<void> Connected; + PBD::Signal1<void,const char*> Disconnected; + + void halted_callback (); + void halted_info_callback (jack_status_t, const char*); + + static bool server_running(); + + private: + jack_client_t* volatile _jack; + std::string _client_name; + std::string session_uuid; +}; + +} // namespace + +#endif /* __libardour_jack_connection_h__ */ diff --git a/libs/backends/jack/jack_portengine.cc b/libs/backends/jack/jack_portengine.cc new file mode 100644 index 0000000000..bd352a2f11 --- /dev/null +++ b/libs/backends/jack/jack_portengine.cc @@ -0,0 +1,569 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <string.h> +#include <stdint.h> + +#include "pbd/error.h" + +#include "jack_portengine.h" +#include "jack_connection.h" + +#include "ardour/port_manager.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +#define GET_PRIVATE_JACK_POINTER(localvar) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(localvar,r) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return r; } + +static uint32_t +ardour_port_flags_to_jack_flags (PortFlags flags) +{ + uint32_t jack_flags = 0; + + if (flags & IsInput) { + jack_flags |= JackPortIsInput; + } + if (flags & IsOutput) { + jack_flags |= JackPortIsOutput; + } + if (flags & IsTerminal) { + jack_flags |= JackPortIsTerminal; + } + if (flags & IsPhysical) { + jack_flags |= JackPortIsPhysical; + } + if (flags & CanMonitor) { + jack_flags |= JackPortCanMonitor; + } + + return jack_flags; +} + +static DataType +jack_port_type_to_ardour_data_type (const char* jack_type) +{ + if (strcmp (jack_type, JACK_DEFAULT_AUDIO_TYPE) == 0) { + return DataType::AUDIO; + } else if (strcmp (jack_type, JACK_DEFAULT_MIDI_TYPE) == 0) { + return DataType::MIDI; + } + return DataType::NIL; +} + +static const char* +ardour_data_type_to_jack_port_type (DataType d) +{ + switch (d) { + case DataType::AUDIO: + return JACK_DEFAULT_AUDIO_TYPE; + case DataType::MIDI: + return JACK_DEFAULT_MIDI_TYPE; + } + + return ""; +} + +JACKPortEngine::JACKPortEngine (PortManager& pm, boost::shared_ptr<JackConnection> jc) + : PortEngine (pm) + , _jack_connection (jc) +{ + _jack_connection->Connected.connect_same_thread (jack_connection_connection, boost::bind (&JACKPortEngine::connected_to_jack, this)); +} + +JACKPortEngine::~JACKPortEngine () +{ + /* a default destructor would do this, and so would this one, + but we'll make it explicit in case we ever need to debug + the lifetime of the JACKConnection + */ + _jack_connection.reset (); +} + +void +JACKPortEngine::connected_to_jack () +{ + /* register callbacks for stuff that is our responsibility */ + + jack_client_t* client = _jack_connection->jack(); + + if (!client) { + /* how could this happen? it could ... */ + error << _("Already disconnected from JACK before PortEngine could register callbacks") << endmsg; + return; + } + + jack_set_port_registration_callback (client, _registration_callback, this); + jack_set_port_connect_callback (client, _connect_callback, this); + jack_set_graph_order_callback (client, _graph_order_callback, this); +} + +void* +JACKPortEngine::private_handle() const +{ + return _jack_connection->jack(); +} + +bool +JACKPortEngine::connected() const +{ + return _jack_connection->connected(); +} + +int +JACKPortEngine::set_port_name (PortHandle port, const std::string& name) +{ + return jack_port_set_name ((jack_port_t*) port, name.c_str()); +} + +string +JACKPortEngine::get_port_name (PortHandle port) const +{ + return jack_port_name ((jack_port_t*) port); +} + +PortEngine::PortHandle* +JACKPortEngine:: get_port_by_name (const std::string& name) const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return (PortHandle*) jack_port_by_name (_priv_jack, name.c_str()); +} + +void +JACKPortEngine::_registration_callback (jack_port_id_t /*id*/, int /*reg*/, void* arg) +{ + static_cast<JACKPortEngine*> (arg)->manager.registration_callback (); +} + +int +JACKPortEngine::_graph_order_callback (void *arg) +{ + return static_cast<JACKPortEngine*> (arg)->manager.graph_order_callback (); +} + +void +JACKPortEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn, void* arg) +{ + static_cast<JACKPortEngine*> (arg)->connect_callback (id_a, id_b, conn); +} + +void +JACKPortEngine::connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn) +{ + if (manager.port_remove_in_progress()) { + return; + } + + GET_PRIVATE_JACK_POINTER (_priv_jack); + + jack_port_t* a = jack_port_by_id (_priv_jack, id_a); + jack_port_t* b = jack_port_by_id (_priv_jack, id_b); + + manager.connect_callback (jack_port_name (a), jack_port_name (b), conn == 0 ? false : true); +} + +bool +JACKPortEngine::connected (PortHandle port, bool process_callback_safe) +{ + bool ret = false; + + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + ret = true; + } + + jack_free (ports); + + return ret; +} + +bool +JACKPortEngine::connected_to (PortHandle port, const std::string& other, bool process_callback_safe) +{ + bool ret = false; + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + if (other == ports[i]) { + ret = true; + } + } + jack_free (ports); + } + + return ret; +} + +bool +JACKPortEngine::physically_connected (PortHandle p, bool process_callback_safe) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + jack_port_t* port = (jack_port_t*) p; + + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + + jack_port_t* other = jack_port_by_name (_priv_jack, ports[i]); + + if (other && (jack_port_flags (other) & JackPortIsPhysical)) { + return true; + } + } + jack_free (ports); + } + + return false; +} + +int +JACKPortEngine::get_connections (PortHandle port, vector<string>& s, bool process_callback_safe) +{ + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + s.push_back (ports[i]); + } + jack_free (ports); + } + + return s.size(); +} + +DataType +JACKPortEngine::port_data_type (PortHandle p) const +{ + return jack_port_type_to_ardour_data_type (jack_port_type ((jack_port_t*) p)); +} + +const string& +JACKPortEngine::my_name() const +{ + return _jack_connection->client_name(); +} + +bool +JACKPortEngine::port_is_physical (PortHandle ph) const +{ + if (!ph) { + return false; + } + + return jack_port_flags ((jack_port_t*) ph) & JackPortIsPhysical; +} + +int +JACKPortEngine::get_ports (const string& port_name_pattern, DataType type, PortFlags flags, vector<string>& s) const +{ + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,0); + + const char** ports = jack_get_ports (_priv_jack, port_name_pattern.c_str(), + ardour_data_type_to_jack_port_type (type), + ardour_port_flags_to_jack_flags (flags)); + + if (ports == 0) { + return 0; + } + + for (uint32_t i = 0; ports[i]; ++i) { + s.push_back (ports[i]); + } + + jack_free (ports); + + return s.size(); +} + +ChanCount +JACKPortEngine::n_physical (unsigned long flags) const +{ + ChanCount c; + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, c); + + const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (!strstr (ports[i], "Midi-Through")) { + DataType t = port_data_type (jack_port_by_name (_priv_jack, ports[i])); + if (t != DataType::NIL) { + c.set (t, c.get (t) + 1); + } + } + } + + jack_free (ports); + } + + return c; +} + +ChanCount +JACKPortEngine::n_physical_inputs () const +{ + return n_physical (JackPortIsInput); +} + +ChanCount +JACKPortEngine::n_physical_outputs () const +{ + return n_physical (JackPortIsOutput); +} + +void +JACKPortEngine::get_physical (DataType type, unsigned long flags, vector<string>& phy) const +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + const char ** ports; + + if ((ports = jack_get_ports (_priv_jack, NULL, ardour_data_type_to_jack_port_type (type), JackPortIsPhysical | flags)) == 0) { + return; + } + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (strstr (ports[i], "Midi-Through")) { + continue; + } + phy.push_back (ports[i]); + } + jack_free (ports); + } +} + +/** Get physical ports for which JackPortIsOutput is set; ie those that correspond to + * a physical input connector. + */ +void +JACKPortEngine::get_physical_inputs (DataType type, vector<string>& ins) +{ + get_physical (type, JackPortIsOutput, ins); +} + +/** Get physical ports for which JackPortIsInput is set; ie those that correspond to + * a physical output connector. + */ +void +JACKPortEngine::get_physical_outputs (DataType type, vector<string>& outs) +{ + get_physical (type, JackPortIsInput, outs); +} + + +bool +JACKPortEngine::can_monitor_input () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,false); + const char ** ports; + + if ((ports = jack_get_ports (_priv_jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortCanMonitor)) == 0) { + return false; + } + + jack_free (ports); + + return true; +} + +int +JACKPortEngine::request_input_monitoring (PortHandle port, bool yn) +{ + return jack_port_request_monitor ((jack_port_t*) port, yn); +} +int +JACKPortEngine::ensure_input_monitoring (PortHandle port, bool yn) +{ + return jack_port_ensure_monitor ((jack_port_t*) port, yn); +} +bool +JACKPortEngine::monitoring_input (PortHandle port) +{ + return jack_port_monitoring_input ((jack_port_t*) port); +} + + +pframes_t +JACKPortEngine::sample_time_at_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_last_frame_time (_priv_jack); +} + + +PortEngine::PortHandle +JACKPortEngine::register_port (const std::string& shortname, ARDOUR::DataType type, ARDOUR::PortFlags flags) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_port_register (_priv_jack, shortname.c_str(), + ardour_data_type_to_jack_port_type (type), + ardour_port_flags_to_jack_flags (flags), + 0); +} + +void +JACKPortEngine::unregister_port (PortHandle port) +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + (void) jack_port_unregister (_priv_jack, (jack_port_t*) port); +} + +int +JACKPortEngine::connect (PortHandle port, const std::string& other) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_connect (_priv_jack, jack_port_name ((jack_port_t*) port), other.c_str()); +} +int +JACKPortEngine::connect (const std::string& src, const std::string& dst) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + int r = jack_connect (_priv_jack, src.c_str(), dst.c_str()); + return r; +} + +int +JACKPortEngine::disconnect (PortHandle port, const std::string& other) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_disconnect (_priv_jack, jack_port_name ((jack_port_t*) port), other.c_str()); +} + +int +JACKPortEngine::disconnect (const std::string& src, const std::string& dst) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_disconnect (_priv_jack, src.c_str(), dst.c_str()); +} + +int +JACKPortEngine::disconnect_all (PortHandle port) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_port_disconnect (_priv_jack, (jack_port_t*) port); +} + +int +JACKPortEngine::midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index) +{ + jack_midi_event_t ev; + int ret; + + if ((ret = jack_midi_event_get (&ev, port_buffer, event_index)) == 0) { + timestamp = ev.time; + size = ev.size; + *buf = ev.buffer; + } + + return ret; +} + +int +JACKPortEngine::midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size) +{ + return jack_midi_event_write (port_buffer, timestamp, buffer, size); +} + +uint32_t +JACKPortEngine::get_midi_event_count (void* port_buffer) +{ + return jack_midi_get_event_count (port_buffer); +} + +void +JACKPortEngine::midi_clear (void* port_buffer) +{ + jack_midi_clear_buffer (port_buffer); +} + +void +JACKPortEngine::set_latency_range (PortHandle port, bool for_playback, LatencyRange r) +{ + jack_latency_range_t range; + + range.min = r.min; + range.max = r.max; + + jack_port_set_latency_range ((jack_port_t*) port, for_playback ? JackPlaybackLatency : JackCaptureLatency, &range); +} + +LatencyRange +JACKPortEngine::get_latency_range (PortHandle port, bool for_playback) +{ + jack_latency_range_t range; + LatencyRange ret; + + jack_port_get_latency_range ((jack_port_t*) port, for_playback ? JackPlaybackLatency : JackCaptureLatency, &range); + + ret.min = range.min; + ret.max = range.max; + + return ret; +} + +void* +JACKPortEngine::get_buffer (PortHandle port, pframes_t nframes) +{ + return jack_port_get_buffer ((jack_port_t*) port, nframes); +} + +uint32_t +JACKPortEngine::port_name_size() const +{ + return jack_port_name_size (); +} diff --git a/libs/backends/jack/jack_portengine.h b/libs/backends/jack/jack_portengine.h new file mode 100644 index 0000000000..0e1eb48c5c --- /dev/null +++ b/libs/backends/jack/jack_portengine.h @@ -0,0 +1,132 @@ +/* + Copyright (C) 2013 Paul Davis + + 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_jack_portengine_h__ +#define __libardour_jack_portengine_h__ + +#include <string> +#include <vector> + +#include <stdint.h> + +#include <jack/types.h> +#include <jack/midiport.h> + +#include <boost/shared_ptr.hpp> + +#include "pbd/signals.h" + +#include "ardour/port_engine.h" +#include "ardour/types.h" + +namespace ARDOUR { + +class JackConnection; + +class JACKPortEngine : public PortEngine +{ + public: + JACKPortEngine (PortManager&, boost::shared_ptr<JackConnection>); + ~JACKPortEngine(); + + void* private_handle() const; + bool connected() const; + + const std::string& my_name() const; + + uint32_t port_name_size() const; + + int set_port_name (PortHandle, const std::string&); + std::string get_port_name (PortHandle) const; + PortHandle* get_port_by_name (const std::string&) const; + + int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>&) const; + + DataType port_data_type (PortHandle) const; + + PortHandle register_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags); + void unregister_port (PortHandle); + + bool connected (PortHandle, bool process_callback_safe); + bool connected_to (PortHandle, const std::string&, bool process_callback_safe); + bool physically_connected (PortHandle, bool process_callback_safe); + int get_connections (PortHandle, std::vector<std::string>&, bool process_callback_safe); + int connect (PortHandle, const std::string&); + + int disconnect (PortHandle, const std::string&); + int disconnect_all (PortHandle); + int connect (const std::string& src, const std::string& dst); + int disconnect (const std::string& src, const std::string& dst); + + /* MIDI */ + + int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index); + int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size); + uint32_t get_midi_event_count (void* port_buffer); + void midi_clear (void* port_buffer); + + /* Monitoring */ + + bool can_monitor_input() const; + int request_input_monitoring (PortHandle, bool); + int ensure_input_monitoring (PortHandle, bool); + bool monitoring_input (PortHandle); + + /* Latency management + */ + + void set_latency_range (PortHandle, bool for_playback, LatencyRange); + LatencyRange get_latency_range (PortHandle, bool for_playback); + + /* Physical ports */ + + bool port_is_physical (PortHandle) const; + void get_physical_outputs (DataType type, std::vector<std::string>&); + void get_physical_inputs (DataType type, std::vector<std::string>&); + ChanCount n_physical_outputs () const; + ChanCount n_physical_inputs () const; + + /* Getting access to the data buffer for a port */ + + void* get_buffer (PortHandle, pframes_t); + + /* Miscellany */ + + pframes_t sample_time_at_cycle_start (); + + private: + boost::shared_ptr<JackConnection> _jack_connection; + + static int _graph_order_callback (void *arg); + static void _registration_callback (jack_port_id_t, int, void *); + static void _connect_callback (jack_port_id_t, jack_port_id_t, int, void *); + + void connect_callback (jack_port_id_t, jack_port_id_t, int); + + ChanCount n_physical (unsigned long flags) const; + void get_physical (DataType type, unsigned long flags, std::vector<std::string>& phy) const; + + PBD::ScopedConnection jack_connection_connection; + void connected_to_jack (); + +}; + +} // namespace + +#endif /* __libardour_jack_portengine_h__ */ diff --git a/libs/backends/jack/jack_utils.cc b/libs/backends/jack/jack_utils.cc new file mode 100644 index 0000000000..77f3d95aa1 --- /dev/null +++ b/libs/backends/jack/jack_utils.cc @@ -0,0 +1,897 @@ +/* + Copyright (C) 2010 Paul Davis + Copyright (C) 2011 Tim Mayberry + + 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. + +*/ + +#ifdef HAVE_ALSA +#include <alsa/asoundlib.h> +#endif + +#ifdef __APPLE__ +#include <CoreAudio/CoreAudio.h> +#include <CoreFoundation/CFString.h> +#include <sys/param.h> +#include <mach-o/dyld.h> +#endif + +#ifdef HAVE_PORTAUDIO +#include <portaudio.h> +#endif + +#include <jack/jack.h> + +#include <fstream> + +#include <boost/scoped_ptr.hpp> + +#include <glibmm/miscutils.h> + +#include "pbd/epa.h" +#include "pbd/error.h" +#include "pbd/convert.h" +#include "pbd/file_utils.h" +#include "pbd/search_path.h" + +#include "jack_utils.h" + +#ifdef __APPLE +#include <CFBundle.h> +#endif + +#include "i18n.h" + +using namespace std; +using namespace PBD; + +namespace ARDOUR { + // The pretty driver names + const char * const portaudio_driver_name = X_("Portaudio"); + const char * const coreaudio_driver_name = X_("CoreAudio"); + const char * const alsa_driver_name = X_("ALSA"); + const char * const oss_driver_name = X_("OSS"); + const char * const freebob_driver_name = X_("FreeBoB"); + const char * const ffado_driver_name = X_("FFADO"); + const char * const netjack_driver_name = X_("NetJACK"); + const char * const dummy_driver_name = X_("Dummy"); +} + +namespace { + + // The real driver names + const char * const portaudio_driver_command_line_name = X_("portaudio"); + const char * const coreaudio_driver_command_line_name = X_("coreaudio"); + const char * const alsa_driver_command_line_name = X_("alsa"); + const char * const oss_driver_command_line_name = X_("oss"); + const char * const freebob_driver_command_line_name = X_("freebob"); + const char * const ffado_driver_command_line_name = X_("firewire"); + const char * const netjack_driver_command_line_name = X_("netjack"); + const char * const dummy_driver_command_line_name = X_("dummy"); + + // should we provide more "pretty" names like above? + const char * const alsaseq_midi_driver_name = X_("seq"); + const char * const alsaraw_midi_driver_name = X_("raw"); + const char * const winmme_midi_driver_name = X_("winmme"); + const char * const coremidi_midi_driver_name = X_("coremidi"); + + // this should probably be translated + const char * const default_device_name = X_("Default"); +} + +std::string +get_none_string () +{ + return _("None"); +} + +void +ARDOUR::get_jack_audio_driver_names (vector<string>& audio_driver_names) +{ +#ifdef WIN32 + audio_driver_names.push_back (portaudio_driver_name); +#elif __APPLE__ + audio_driver_names.push_back (coreaudio_driver_name); +#else +#ifdef HAVE_ALSA + audio_driver_names.push_back (alsa_driver_name); +#endif + audio_driver_names.push_back (oss_driver_name); + audio_driver_names.push_back (freebob_driver_name); + audio_driver_names.push_back (ffado_driver_name); +#endif + audio_driver_names.push_back (netjack_driver_name); + audio_driver_names.push_back (dummy_driver_name); +} + +void +ARDOUR::get_jack_default_audio_driver_name (string& audio_driver_name) +{ + vector<string> drivers; + get_jack_audio_driver_names (drivers); + audio_driver_name = drivers.front (); +} + +void +ARDOUR::get_jack_sample_rate_strings (vector<string>& samplerates) +{ + // do these really need to be translated? + samplerates.push_back (_("8000Hz")); + samplerates.push_back (_("22050Hz")); + samplerates.push_back (_("44100Hz")); + samplerates.push_back (_("48000Hz")); + samplerates.push_back (_("88200Hz")); + samplerates.push_back (_("96000Hz")); + samplerates.push_back (_("192000Hz")); +} + +string +ARDOUR::get_jack_default_sample_rate () +{ + return _("48000Hz"); +} + +void +ARDOUR::get_jack_period_size_strings (std::vector<std::string>& period_sizes) +{ + period_sizes.push_back ("32"); + period_sizes.push_back ("64"); + period_sizes.push_back ("128"); + period_sizes.push_back ("256"); + period_sizes.push_back ("512"); + period_sizes.push_back ("1024"); + period_sizes.push_back ("2048"); + period_sizes.push_back ("4096"); + period_sizes.push_back ("8192"); +} + +string +ARDOUR::get_jack_default_period_size () +{ + return "1024"; +} + +void +ARDOUR::get_jack_dither_mode_strings (const string& driver, vector<string>& dither_modes) +{ + dither_modes.push_back (get_none_string ()); + + if (driver == alsa_driver_name ) { + dither_modes.push_back (_("Triangular")); + dither_modes.push_back (_("Rectangular")); + dither_modes.push_back (_("Shaped")); + } +} + +string +ARDOUR::get_jack_default_dither_mode (const string& /*driver*/) +{ + return get_none_string (); +} + +string +ARDOUR::get_jack_latency_string (string samplerate, float periods, string period_size) +{ + uint32_t rate = atoi (samplerate); + float psize = atof (period_size); + + char buf[32]; + snprintf (buf, sizeof(buf), "%.1fmsec", (periods * psize) / (rate/1000.0)); + + return buf; +} + +bool +get_jack_command_line_audio_driver_name (const string& driver_name, string& command_line_name) +{ + using namespace ARDOUR; + if (driver_name == portaudio_driver_name) { + command_line_name = portaudio_driver_command_line_name; + return true; + } else if (driver_name == coreaudio_driver_name) { + command_line_name = coreaudio_driver_command_line_name; + return true; + } else if (driver_name == alsa_driver_name) { + command_line_name = alsa_driver_command_line_name; + return true; + } else if (driver_name == oss_driver_name) { + command_line_name = oss_driver_command_line_name; + return true; + } else if (driver_name == freebob_driver_name) { + command_line_name = freebob_driver_command_line_name; + return true; + } else if (driver_name == ffado_driver_name) { + command_line_name = ffado_driver_command_line_name; + return true; + } else if (driver_name == netjack_driver_name) { + command_line_name = netjack_driver_command_line_name; + return true; + } else if (driver_name == dummy_driver_name) { + command_line_name = dummy_driver_command_line_name; + return true; + } + return false; +} + +bool +get_jack_command_line_audio_device_name (const string& driver_name, + const string& device_name, string& command_line_device_name) +{ + using namespace ARDOUR; + device_map_t devices; + + get_jack_device_names_for_audio_driver (driver_name, devices); + + for (device_map_t::const_iterator i = devices.begin (); i != devices.end(); ++i) { + if (i->first == device_name) { + command_line_device_name = i->second; + return true; + } + } + return false; +} + +bool +get_jack_command_line_dither_mode (const string& dither_mode, string& command_line_dither_mode) +{ + using namespace ARDOUR; + + if (dither_mode == _("Triangular")) { + command_line_dither_mode = "triangular"; + return true; + } else if (dither_mode == _("Rectangular")) { + command_line_dither_mode = "rectangular"; + return true; + } else if (dither_mode == _("Shaped")) { + command_line_dither_mode = "shaped"; + return true; + } + + return false; +} + +void +ARDOUR::get_jack_alsa_device_names (device_map_t& devices) +{ +#ifdef HAVE_ALSA + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + string devname; + int cardnum = -1; + int device = -1; + + while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) { + + devname = "hw:"; + devname += PBD::to_string (cardnum, std::dec); + + if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) { + + if (snd_ctl_card_info (handle, info) < 0) { + continue; + } + + string card_name = snd_ctl_card_info_get_name (info); + + /* change devname to use ID, not number */ + + devname = "hw:"; + devname += snd_ctl_card_info_get_id (info); + + while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) { + + /* only detect duplex devices here. more + * complex arrangements are beyond our scope + */ + + snd_pcm_info_set_device (pcminfo, device); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_CAPTURE); + + if (snd_ctl_pcm_info (handle, pcminfo) >= 0) { + + snd_pcm_info_set_device (pcminfo, device); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK); + + if (snd_ctl_pcm_info (handle, pcminfo) >= 0) { + devname += ','; + devname += PBD::to_string (device, std::dec); + devices.insert (std::make_pair (card_name, devname)); + } + } + } + + snd_ctl_close(handle); + } + } +#else + /* silence a compiler unused variable warning */ + (void) devices; +#endif +} + +#ifdef __APPLE__ +static OSStatus +getDeviceUIDFromID( AudioDeviceID id, char *name, size_t nsize) +{ + UInt32 size = sizeof(CFStringRef); + CFStringRef UI; + OSStatus res = AudioDeviceGetProperty(id, 0, false, + kAudioDevicePropertyDeviceUID, &size, &UI); + if (res == noErr) + CFStringGetCString(UI,name,nsize,CFStringGetSystemEncoding()); + CFRelease(UI); + return res; +} +#endif + +void +ARDOUR::get_jack_coreaudio_device_names (device_map_t& devices) +{ +#ifdef __APPLE__ + // Find out how many Core Audio devices are there, if any... + // (code snippet gently "borrowed" from St?hane Letz jackdmp;) + OSStatus err; + Boolean isWritable; + UInt32 outSize = sizeof(isWritable); + + err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &outSize, &isWritable); + if (err == noErr) { + // Calculate the number of device available... + int numCoreDevices = outSize / sizeof(AudioDeviceID); + // Make space for the devices we are about to get... + AudioDeviceID *coreDeviceIDs = new AudioDeviceID [numCoreDevices]; + err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &outSize, (void *) coreDeviceIDs); + if (err == noErr) { + // Look for the CoreAudio device name... + char coreDeviceName[256]; + UInt32 nameSize; + + for (int i = 0; i < numCoreDevices; i++) { + + nameSize = sizeof (coreDeviceName); + + /* enforce duplex devices only */ + + err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], + 0, true, kAudioDevicePropertyStreams, + &outSize, &isWritable); + + if (err != noErr || outSize == 0) { + continue; + } + + err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], + 0, false, kAudioDevicePropertyStreams, + &outSize, &isWritable); + + if (err != noErr || outSize == 0) { + continue; + } + + err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], + 0, true, kAudioDevicePropertyDeviceName, + &outSize, &isWritable); + if (err == noErr) { + err = AudioDeviceGetProperty(coreDeviceIDs[i], + 0, true, kAudioDevicePropertyDeviceName, + &nameSize, (void *) coreDeviceName); + if (err == noErr) { + char drivername[128]; + + // this returns the unique id for the device + // that must be used on the commandline for jack + + if (getDeviceUIDFromID(coreDeviceIDs[i], drivername, sizeof (drivername)) == noErr) { + devices.insert (make_pair (coreDeviceName, drivername)); + } + } + } + } + } + delete [] coreDeviceIDs; + } +#else + /* silence a compiler unused variable warning */ + (void) devices; +#endif +} + +void +ARDOUR::get_jack_portaudio_device_names (device_map_t& devices) +{ +#ifdef HAVE_PORTAUDIO + if (Pa_Initialize() != paNoError) { + return; + } + + for (PaDeviceIndex i = 0; i < Pa_GetDeviceCount (); ++i) { + string api_name; + string readable_name; + string jack_device_name; + const PaDeviceInfo* device_info = Pa_GetDeviceInfo(i); + + if (device_info != NULL) { // it should never be ? + api_name = Pa_GetHostApiInfo (device_info->hostApi)->name; + readable_name = api_name + " " + device_info->name; + jack_device_name = api_name + "::" + device_info->name; + devices.insert (make_pair (readable_name, jack_device_name)); + } + } + Pa_Terminate(); +#else + /* silence a compiler unused variable warning */ + (void) devices; +#endif +} + +void +ARDOUR::get_jack_oss_device_names (device_map_t& devices) +{ + devices.insert (make_pair (default_device_name, default_device_name)); +} + +void +ARDOUR::get_jack_freebob_device_names (device_map_t& devices) +{ + devices.insert (make_pair (default_device_name, default_device_name)); +} + +void +ARDOUR::get_jack_ffado_device_names (device_map_t& devices) +{ + devices.insert (make_pair (default_device_name, default_device_name)); +} + +void +ARDOUR::get_jack_netjack_device_names (device_map_t& devices) +{ + devices.insert (make_pair (default_device_name, default_device_name)); +} + +void +ARDOUR::get_jack_dummy_device_names (device_map_t& devices) +{ + devices.insert (make_pair (default_device_name, default_device_name)); +} + +bool +ARDOUR::get_jack_device_names_for_audio_driver (const string& driver_name, device_map_t& devices) +{ + devices.clear(); + + if (driver_name == portaudio_driver_name) { + get_jack_portaudio_device_names (devices); + } else if (driver_name == coreaudio_driver_name) { + get_jack_coreaudio_device_names (devices); + } else if (driver_name == alsa_driver_name) { + get_jack_alsa_device_names (devices); + } else if (driver_name == oss_driver_name) { + get_jack_oss_device_names (devices); + } else if (driver_name == freebob_driver_name) { + get_jack_freebob_device_names (devices); + } else if (driver_name == ffado_driver_name) { + get_jack_ffado_device_names (devices); + } else if (driver_name == netjack_driver_name) { + get_jack_netjack_device_names (devices); + } else if (driver_name == dummy_driver_name) { + get_jack_dummy_device_names (devices); + } + + return !devices.empty(); +} + + +std::vector<std::string> +ARDOUR::get_jack_device_names_for_audio_driver (const string& driver_name) +{ + std::vector<std::string> readable_names; + device_map_t devices; + + get_jack_device_names_for_audio_driver (driver_name, devices); + + for (device_map_t::const_iterator i = devices.begin (); i != devices.end(); ++i) { + readable_names.push_back (i->first); + } + + return readable_names; +} + +bool +ARDOUR::get_jack_audio_driver_supports_two_devices (const string& driver) +{ + return (driver == alsa_driver_name || driver == oss_driver_name); +} + +bool +ARDOUR::get_jack_audio_driver_supports_latency_adjustment (const string& driver) +{ + return (driver == alsa_driver_name || driver == coreaudio_driver_name || + driver == ffado_driver_name || driver == portaudio_driver_name); +} + +bool +ARDOUR::get_jack_audio_driver_supports_setting_period_count (const string& driver) +{ + return !(driver == dummy_driver_name || driver == coreaudio_driver_name || + driver == portaudio_driver_name); +} + +bool +ARDOUR::get_jack_server_application_names (std::vector<std::string>& server_names) +{ +#ifdef WIN32 + server_names.push_back ("jackd.exe"); +#else + server_names.push_back ("jackd"); + server_names.push_back ("jackdmp"); +#endif + return !server_names.empty(); +} + +void +ARDOUR::set_path_env_for_jack_autostart (const vector<std::string>& dirs) +{ +#ifdef __APPLE__ + // push it back into the environment so that auto-started JACK can find it. + // XXX why can't we just expect OS X users to have PATH set correctly? we can't ... + setenv ("PATH", SearchPath(dirs).to_string().c_str(), 1); +#else + /* silence a compiler unused variable warning */ + (void) dirs; +#endif +} + +bool +ARDOUR::get_jack_server_dir_paths (vector<std::string>& server_dir_paths) +{ +#ifdef __APPLE__ + /* this magic lets us finds the path to the OSX bundle, and then + we infer JACK's location from there + */ + + char execpath[MAXPATHLEN+1]; + uint32_t pathsz = sizeof (execpath); + + _NSGetExecutablePath (execpath, &pathsz); + + server_dir_paths.push_back (Glib::path_get_dirname (execpath)); +#endif + + SearchPath sp(string(g_getenv("PATH"))); + +#ifdef WIN32 + gchar *install_dir = g_win32_get_package_installation_directory_of_module (NULL); + if (install_dir) { + sp.push_back (install_dir); + g_free (install_dir); + } + // don't try and use a system wide JACK install yet. +#else + if (sp.empty()) { + sp.push_back ("/usr/bin"); + sp.push_back ("/bin"); + sp.push_back ("/usr/local/bin"); + sp.push_back ("/opt/local/bin"); + } +#endif + + std::copy (sp.begin(), sp.end(), std::back_inserter(server_dir_paths)); + + return !server_dir_paths.empty(); +} + +bool +ARDOUR::get_jack_server_paths (const vector<std::string>& server_dir_paths, + const vector<string>& server_names, + vector<std::string>& server_paths) +{ + for (vector<string>::const_iterator i = server_names.begin(); i != server_names.end(); ++i) { + Glib::PatternSpec ps (*i); + find_matching_files_in_directories (server_dir_paths, ps, server_paths); + } + return !server_paths.empty(); +} + +bool +ARDOUR::get_jack_server_paths (vector<std::string>& server_paths) +{ + vector<std::string> server_dirs; + + if (!get_jack_server_dir_paths (server_dirs)) { + return false; + } + + vector<string> server_names; + + if (!get_jack_server_application_names (server_names)) { + return false; + } + + if (!get_jack_server_paths (server_dirs, server_names, server_paths)) { + return false; + } + + return !server_paths.empty(); +} + +bool +ARDOUR::get_jack_default_server_path (std::string& server_path) +{ + vector<std::string> server_paths; + + if (!get_jack_server_paths (server_paths)) { + return false; + } + + server_path = server_paths.front (); + return true; +} + +string +quote_string (const string& str) +{ + return "\"" + str + "\""; +} + +ARDOUR::JackCommandLineOptions::JackCommandLineOptions () + : server_path () + , timeout(0) + , no_mlock(false) + , ports_max(128) + , realtime(true) + , priority(0) + , unlock_gui_libs(false) + , verbose(false) + , temporary(true) + , driver() + , input_device() + , output_device() + , num_periods(2) + , period_size(1024) + , samplerate(48000) + , input_latency(0) + , output_latency(0) + , hardware_metering(false) + , hardware_monitoring(false) + , dither_mode() + , force16_bit(false) + , soft_mode(false) + , midi_driver() +{ + +} + +bool +ARDOUR::get_jack_command_line_string (const JackCommandLineOptions& options, string& command_line) +{ + vector<string> args; + + args.push_back (options.server_path); + +#ifdef WIN32 + // must use sync mode on windows + args.push_back ("-S"); + + // this needs to be added now on windows + if (!options.midi_driver.empty () && options.midi_driver != get_none_string ()) { + args.push_back ("-X"); + args.push_back (options.midi_driver); + } +#endif + + if (options.timeout) { + args.push_back ("-t"); + args.push_back (to_string (options.timeout, std::dec)); + } + + if (options.no_mlock) { + args.push_back ("-m"); + } + + args.push_back ("-p"); + args.push_back (to_string(options.ports_max, std::dec)); + + if (options.realtime) { + args.push_back ("-R"); + if (options.priority != 0) { + args.push_back ("-P"); + args.push_back (to_string(options.priority, std::dec)); + } + } else { + args.push_back ("-r"); + } + + if (options.unlock_gui_libs) { + args.push_back ("-u"); + } + + if (options.verbose) { + args.push_back ("-v"); + } + +#ifndef WIN32 + if (options.temporary) { + args.push_back ("-T"); + } +#endif + + string command_line_driver_name; + + if (!get_jack_command_line_audio_driver_name (options.driver, command_line_driver_name)) { + return false; + } + + args.push_back ("-d"); + args.push_back (command_line_driver_name); + + if (options.output_device.empty() && options.input_device.empty()) { + return false; + } + + string command_line_input_device_name; + string command_line_output_device_name; + + if (!get_jack_command_line_audio_device_name (options.driver, + options.input_device, command_line_input_device_name)) { + return false; + } + + if (!get_jack_command_line_audio_device_name (options.driver, + options.output_device, command_line_output_device_name)) { + return false; + } + + if (options.input_device.empty()) { + // playback only + if (options.output_device.empty()) { + return false; + } + args.push_back ("-P"); + } else if (options.output_device.empty()) { + // capture only + if (options.input_device.empty()) { + return false; + } + args.push_back ("-C"); + } else if (options.input_device != options.output_device) { + // capture and playback on two devices if supported + if (get_jack_audio_driver_supports_two_devices (options.driver)) { + args.push_back ("-C"); + args.push_back (command_line_input_device_name); + args.push_back ("-P"); + args.push_back (command_line_output_device_name); + } else { + return false; + } + } + + if (options.input_channels) { + args.push_back ("-i"); + args.push_back (to_string (options.input_channels, std::dec)); + } + + if (options.output_channels) { + args.push_back ("-o"); + args.push_back (to_string (options.output_channels, std::dec)); + } + + if (get_jack_audio_driver_supports_setting_period_count (options.driver)) { + args.push_back ("-n"); + args.push_back (to_string (options.num_periods, std::dec)); + } + + args.push_back ("-r"); + args.push_back (to_string (options.samplerate, std::dec)); + + args.push_back ("-p"); + args.push_back (to_string (options.period_size, std::dec)); + + if (get_jack_audio_driver_supports_latency_adjustment (options.driver)) { + if (options.input_latency) { + args.push_back ("-I"); + args.push_back (to_string (options.input_latency, std::dec)); + } + if (options.output_latency) { + args.push_back ("-O"); + args.push_back (to_string (options.output_latency, std::dec)); + } + } + + if (options.input_device == options.output_device && options.input_device != default_device_name) { + args.push_back ("-d"); + args.push_back (command_line_input_device_name); + } + + if (options.driver == alsa_driver_name) { + if (options.hardware_metering) { + args.push_back ("-M"); + } + if (options.hardware_monitoring) { + args.push_back ("-H"); + } + + string command_line_dither_mode; + if (get_jack_command_line_dither_mode (options.dither_mode, command_line_dither_mode)) { + args.push_back ("-z"); + args.push_back (command_line_dither_mode); + } + if (options.force16_bit) { + args.push_back ("-S"); + } + if (options.soft_mode) { + args.push_back ("-s"); + } + + if (!options.midi_driver.empty() && options.midi_driver != get_none_string ()) { + args.push_back ("-X"); + args.push_back (options.midi_driver); + } + } + + ostringstream oss; + + for (vector<string>::const_iterator i = args.begin(); i != args.end();) { +#ifdef WIN32 + oss << quote_string (*i); +#else + oss << *i; +#endif + if (++i != args.end()) oss << ' '; + } + + command_line = oss.str(); + return true; +} + +string +ARDOUR::get_jack_server_config_file_name () +{ + return ".jackdrc"; +} + +std::string +ARDOUR::get_jack_server_user_config_dir_path () +{ + return Glib::get_home_dir (); +} + +std::string +ARDOUR::get_jack_server_user_config_file_path () +{ + return Glib::build_filename (get_jack_server_user_config_dir_path (), get_jack_server_config_file_name ()); +} + +bool +ARDOUR::write_jack_config_file (const std::string& config_file_path, const string& command_line) +{ + ofstream jackdrc (config_file_path.c_str()); + + if (!jackdrc) { + error << string_compose (_("cannot open JACK rc file %1 to store parameters"), config_file_path) << endmsg; + return false; + } + + jackdrc << command_line << endl; + jackdrc.close (); + return true; +} diff --git a/libs/backends/jack/jack_utils.h b/libs/backends/jack/jack_utils.h new file mode 100644 index 0000000000..7565353198 --- /dev/null +++ b/libs/backends/jack/jack_utils.h @@ -0,0 +1,235 @@ +/* + Copyright (C) 2011 Tim Mayberry + + 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 <stdint.h> + +#include <vector> +#include <map> +#include <string> + +namespace ARDOUR { + + // Names for the drivers on all possible systems + extern const char * const portaudio_driver_name; + extern const char * const coreaudio_driver_name; + extern const char * const alsa_driver_name; + extern const char * const oss_driver_name; + extern const char * const freebob_driver_name; + extern const char * const ffado_driver_name; + extern const char * const netjack_driver_name; + extern const char * const dummy_driver_name; + + /** + * Get a list of possible JACK audio driver names based on platform + */ + void get_jack_audio_driver_names (std::vector<std::string>& driver_names); + + /** + * Get the default JACK audio driver based on platform + */ + void get_jack_default_audio_driver_name (std::string& driver_name); + + /** + * Get a list of possible samplerates supported be JACK + */ + void get_jack_sample_rate_strings (std::vector<std::string>& sample_rates); + + /** + * @return The default samplerate + */ + std::string get_jack_default_sample_rate (); + + /** + * @return true if sample rate string was able to be converted + */ + bool get_jack_sample_rate_value_from_string (const std::string& srs, uint32_t& srv); + + /** + * Get a list of possible period sizes supported be JACK + */ + void get_jack_period_size_strings (std::vector<std::string>& samplerates); + + /** + * @return The default period size + */ + std::string get_jack_default_period_size (); + + /** + * @return true if period size string was able to be converted + */ + bool get_jack_period_size_value_from_string (const std::string& pss, uint32_t& psv); + + /** + * These are driver specific I think, so it may require a driver arg + * in future + */ + void get_jack_dither_mode_strings (const std::string& driver, std::vector<std::string>& dither_modes); + + /** + * @return The default dither mode + */ + std::string get_jack_default_dither_mode (const std::string& driver); + + /** + * @return Estimate of latency + * + * API matches current use in GUI + */ + std::string get_jack_latency_string (std::string samplerate, float periods, std::string period_size); + + /** + * Key being a readable name to display in a GUI + * Value being name used in a jack commandline + */ + typedef std::map<std::string, std::string> device_map_t; + + /** + * Use library specific code to find out what what devices exist for a given + * driver that might work in JACK. There is no easy way to find out what + * modules the JACK server supports so guess based on platform. For instance + * portaudio is cross-platform but we only return devices if built for + * windows etc + */ + void get_jack_alsa_device_names (device_map_t& devices); + void get_jack_portaudio_device_names (device_map_t& devices); + void get_jack_coreaudio_device_names (device_map_t& devices); + void get_jack_oss_device_names (device_map_t& devices); + void get_jack_freebob_device_names (device_map_t& devices); + void get_jack_ffado_device_names (device_map_t& devices); + void get_jack_netjack_device_names (device_map_t& devices); + void get_jack_dummy_device_names (device_map_t& devices); + + /* + * @return true if there were devices found for the driver + * + * @param driver The driver name returned by get_jack_audio_driver_names + * @param devices The map used to insert the drivers into, devices will be cleared before + * adding the available drivers + */ + bool get_jack_device_names_for_audio_driver (const std::string& driver, device_map_t& devices); + + /* + * @return a list of readable device names for a specific driver. + */ + std::vector<std::string> get_jack_device_names_for_audio_driver (const std::string& driver); + + /** + * @return true if the driver supports playback and recording + * on separate devices + */ + bool get_jack_audio_driver_supports_two_devices (const std::string& driver); + + bool get_jack_audio_driver_supports_latency_adjustment (const std::string& driver); + + bool get_jack_audio_driver_supports_setting_period_count (const std::string& driver); + + /** + * The possible names to use to try and find servers, this includes + * any file extensions like .exe on Windows + * + * @return true if the JACK application names for this platform could be guessed + */ + bool get_jack_server_application_names (std::vector<std::string>& server_names); + + /** + * Sets the PATH environment variable to contain directories likely to contain + * JACK servers so that if the JACK server is auto-started it can find the server + * executable. + * + * This is only modifies PATH on the mac at the moment. + */ + void set_path_env_for_jack_autostart (const std::vector<std::string>&); + + /** + * Get absolute paths to directories that might contain JACK servers on the system + * + * @return true if !server_paths.empty() + */ + bool get_jack_server_dir_paths (std::vector<std::string>& server_dir_paths); + + /** + * Get absolute paths to JACK servers on the system + * + * @return true if a server was found + */ + bool get_jack_server_paths (const std::vector<std::string>& server_dir_paths, + const std::vector<std::string>& server_names, + std::vector<std::string>& server_paths); + + + bool get_jack_server_paths (std::vector<std::string>& server_paths); + + /** + * Get absolute path to default JACK server + */ + bool get_jack_default_server_path (std::string& server_path); + + /** + * @return The name of the jack server config file + */ + std::string get_jack_server_config_file_name (); + + std::string get_jack_server_user_config_dir_path (); + + std::string get_jack_server_user_config_file_path (); + + bool write_jack_config_file (const std::string& config_file_path, const std::string& command_line); + + struct JackCommandLineOptions { + + // see implementation for defaults + JackCommandLineOptions (); + + //operator bool + //operator ostream + + std::string server_path; + uint32_t timeout; + bool no_mlock; + uint32_t ports_max; + bool realtime; + uint32_t priority; + bool unlock_gui_libs; + bool verbose; + bool temporary; + bool playback_only; + bool capture_only; + std::string driver; + std::string input_device; + std::string output_device; + uint32_t num_periods; + uint32_t period_size; + uint32_t samplerate; + uint32_t input_channels; + uint32_t output_channels; + uint32_t input_latency; + uint32_t output_latency; + bool hardware_metering; + bool hardware_monitoring; + std::string dither_mode; + bool force16_bit; + bool soft_mode; + std::string midi_driver; + }; + + /** + * @return true if able to build a valid command line based on options + */ + bool get_jack_command_line_string (const JackCommandLineOptions& options, std::string& command_line); +} diff --git a/libs/backends/jack/wscript b/libs/backends/jack/wscript new file mode 100644 index 0000000000..3c47f3a652 --- /dev/null +++ b/libs/backends/jack/wscript @@ -0,0 +1,51 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import os +import sys +import re + +# Library version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +JACKBACKEND_VERSION = '1.0.0' +I18N_PACKAGE = 'jack-backend' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + autowaf.set_options(opt) + +def configure(conf): + autowaf.configure(conf) + +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.source = [ + 'jack_api.cc', + 'jack_connection.cc', + 'jack_audiobackend.cc', + 'jack_portengine.cc', + 'jack_utils.cc' + ] + obj.includes = ['.'] + obj.cxxflags = [ '-fPIC' ] + obj.name = 'jack_audiobackend' + obj.target = 'jack_audiobackend' + obj.uselib = [ 'JACK' ] + obj.use = 'ardour libpbd' + obj.vnum = JACKBACKEND_VERSION + obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'backends') + obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"'] + + + # + # device discovery code in the jack backend needs ALSA + # on Linux. + # + + if re.search ("linux", sys.platform) != None: + obj.uselib += [ 'ALSA' ] + diff --git a/libs/backends/wscript b/libs/backends/wscript new file mode 100644 index 0000000000..b8779b7713 --- /dev/null +++ b/libs/backends/wscript @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import os + +# Mandatory variables +top = '.' +out = 'build' + +backends = [ 'jack' ] + +def options(opt): + autowaf.set_options(opt) + +def sub_config_and_use(conf, name, has_objects = True): + conf.recurse(name) + autowaf.set_local_lib(conf, name, has_objects) + +def configure(conf): + autowaf.set_recursive() + autowaf.configure(conf) + + for i in backends: + sub_config_and_use(conf, i) + +def build(bld): + for i in backends: + bld.recurse(i) diff --git a/libs/midi++2/jack_midi_port.cc b/libs/midi++2/jack_midi_port.cc deleted file mode 100644 index 38ec4d35c7..0000000000 --- a/libs/midi++2/jack_midi_port.cc +++ /dev/null @@ -1,486 +0,0 @@ -/* - Copyright (C) 1998 Paul Barton-Davis - - 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. - - $Id$ -*/ -#include <iostream> -#include <cstdio> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> - -#include <jack/jack.h> -#include <jack/midiport.h> - -#include "pbd/xml++.h" -#include "pbd/error.h" -#include "pbd/failed_constructor.h" -#include "pbd/convert.h" -#include "pbd/strsplit.h" -#include "pbd/stacktrace.h" - -#include "midi++/types.h" -#include "midi++/jack_midi_port.h" -#include "midi++/channel.h" - -using namespace MIDI; -using namespace std; -using namespace PBD; - -namespace Evoral { -template class EventRingBuffer<timestamp_t>; -} - -pthread_t JackMIDIPort::_process_thread; -Signal0<void> JackMIDIPort::JackHalted; -Signal0<void> JackMIDIPort::MakeConnections; - -JackMIDIPort::JackMIDIPort (string const & name, Flags flags, jack_client_t* jack_client) - : Port (name, flags) - , _currently_in_cycle (false) - , _nframes_this_cycle (0) - , _jack_client (jack_client) - , _jack_port (0) - , _last_write_timestamp (0) - , output_fifo (512) - , input_fifo (1024) -#ifndef PLATFORM_WINDOWS - , xthread (true) -#endif -{ - assert (jack_client); - init (name, flags); -} - -JackMIDIPort::JackMIDIPort (const XMLNode& node, jack_client_t* jack_client) - : Port (node) - , _currently_in_cycle (false) - , _nframes_this_cycle (0) - , _jack_client (jack_client) - , _jack_port (0) - , _last_write_timestamp (0) - , output_fifo (512) - , input_fifo (1024) -#ifndef PLATFORM_WINDOWS - , xthread (true) -#endif -{ - assert (jack_client); - - Descriptor desc (node); - init (desc.tag, desc.flags); - set_state (node); -} - -void -JackMIDIPort::init (const string& /*name*/, Flags /*flags*/) -{ - if (!create_port ()) { - _ok = true; - } - - MakeConnections.connect_same_thread (connect_connection, boost::bind (&JackMIDIPort::make_connections, this)); - JackHalted.connect_same_thread (halt_connection, boost::bind (&JackMIDIPort::jack_halted, this)); -} - - -JackMIDIPort::~JackMIDIPort () -{ - if (_jack_port) { - if (_jack_client) { - jack_port_unregister (_jack_client, _jack_port); - _jack_port = 0; - } - } -} - -void -JackMIDIPort::parse (framecnt_t timestamp) -{ - byte buf[512]; - - /* NOTE: parsing is done (if at all) by initiating a read from - the port. Each port implementation calls on the parser - once it has data ready. - */ - - _parser->set_timestamp (timestamp); - - while (1) { - - // cerr << "+++ READ ON " << name() << endl; - - int nread = read (buf, sizeof (buf)); - - // cerr << "-- READ (" << nread << " ON " << name() << endl; - - if (nread > 0) { - if ((size_t) nread < sizeof (buf)) { - break; - } else { - continue; - } - } else if (nread == 0) { - break; - } else if (errno == EAGAIN) { - break; - } else { - fatal << "Error reading from MIDI port " << name() << endmsg; - /*NOTREACHED*/ - } - } -} - -void -JackMIDIPort::cycle_start (pframes_t nframes) -{ - assert (_jack_port); - - _currently_in_cycle = true; - _nframes_this_cycle = nframes; - - assert(_nframes_this_cycle == nframes); - - if (sends_output()) { - void *buffer = jack_port_get_buffer (_jack_port, nframes); - jack_midi_clear_buffer (buffer); - flush (buffer); - } - - if (receives_input()) { - void* jack_buffer = jack_port_get_buffer(_jack_port, nframes); - const pframes_t event_count = jack_midi_get_event_count(jack_buffer); - - jack_midi_event_t ev; - timestamp_t cycle_start_frame = jack_last_frame_time (_jack_client); - - for (pframes_t i = 0; i < event_count; ++i) { - jack_midi_event_get (&ev, jack_buffer, i); - input_fifo.write (cycle_start_frame + ev.time, (Evoral::EventType) 0, ev.size, ev.buffer); - } - - if (event_count) { -#ifndef PLATFORM_WINDOWS - xthread.wakeup (); -#endif - } - } -} - -void -JackMIDIPort::cycle_end () -{ - if (sends_output()) { - flush (jack_port_get_buffer (_jack_port, _nframes_this_cycle)); - } - - _currently_in_cycle = false; - _nframes_this_cycle = 0; -} - -void -JackMIDIPort::jack_halted () -{ - _jack_client = 0; - _jack_port = 0; -} - -void -JackMIDIPort::drain (int check_interval_usecs) -{ - RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; - - if (is_process_thread()) { - error << "Process thread called MIDI::JackMIDIPort::drain() - this cannot work" << endmsg; - return; - } - - while (1) { - output_fifo.get_write_vector (&vec); - if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { - break; - } - g_usleep (check_interval_usecs); - } -} - -int -JackMIDIPort::write (const MIDI::byte * msg, size_t msglen, timestamp_t timestamp) -{ - int ret = 0; - - if (!_jack_client || !_jack_port) { - /* poof ! make it just vanish into thin air, since we are no - longer connected to JACK. - */ - return msglen; - } - - if (!sends_output()) { - return ret; - } - - if (!is_process_thread()) { - - Glib::Threads::Mutex::Lock lm (output_fifo_lock); - RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; - - output_fifo.get_write_vector (&vec); - - if (vec.len[0] + vec.len[1] < 1) { - error << "no space in FIFO for non-process thread MIDI write" << endmsg; - return 0; - } - - if (vec.len[0]) { - if (!vec.buf[0]->owns_buffer()) { - vec.buf[0]->set_buffer (0, 0, true); - } - vec.buf[0]->set (msg, msglen, timestamp); - } else { - if (!vec.buf[1]->owns_buffer()) { - vec.buf[1]->set_buffer (0, 0, true); - } - vec.buf[1]->set (msg, msglen, timestamp); - } - - output_fifo.increment_write_idx (1); - - ret = msglen; - - } else { - - if (timestamp >= _nframes_this_cycle) { - std::cerr << "attempting to write MIDI event of " << msglen << " bytes at time " - << timestamp << " of " << _nframes_this_cycle - << " (this will not work - needs a code fix)" - << std::endl; - } - - if (_currently_in_cycle) { - if (timestamp == 0) { - timestamp = _last_write_timestamp; - } - - if ((ret = jack_midi_event_write (jack_port_get_buffer (_jack_port, _nframes_this_cycle), - timestamp, msg, msglen)) == 0) { - ret = msglen; - _last_write_timestamp = timestamp; - - } else { - cerr << "write of " << msglen << " @ " << timestamp << " failed, port holds " - << jack_midi_get_event_count (jack_port_get_buffer (_jack_port, _nframes_this_cycle)) - << " port is " << _jack_port - << " ntf = " << _nframes_this_cycle - << " buf = " << jack_port_get_buffer (_jack_port, _nframes_this_cycle) - << " ret = " << ret - << endl; - PBD::stacktrace (cerr, 20); - ret = 0; - } - } else { - cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; - PBD::stacktrace (cerr, 20); - } - } - - if (ret > 0 && _parser) { - // ardour doesn't care about this and neither should your app, probably - // output_parser->raw_preparse (*output_parser, msg, ret); - for (int i = 0; i < ret; i++) { - _parser->scanner (msg[i]); - } - // ardour doesn't care about this and neither should your app, probably - // output_parser->raw_postparse (*output_parser, msg, ret); - } - - return ret; -} - -void -JackMIDIPort::flush (void* jack_port_buffer) -{ - RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; - size_t written; - - output_fifo.get_read_vector (&vec); - - if (vec.len[0] + vec.len[1]) { - // cerr << "Flush " << vec.len[0] + vec.len[1] << " events from non-process FIFO\n"; - } - - if (vec.len[0]) { - Evoral::Event<double>* evp = vec.buf[0]; - - for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { - jack_midi_event_write (jack_port_buffer, - (timestamp_t) evp->time(), evp->buffer(), evp->size()); - } - } - - if (vec.len[1]) { - Evoral::Event<double>* evp = vec.buf[1]; - - for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { - jack_midi_event_write (jack_port_buffer, - (timestamp_t) evp->time(), evp->buffer(), evp->size()); - } - } - - if ((written = vec.len[0] + vec.len[1]) != 0) { - output_fifo.increment_read_idx (written); - } -} - -int -JackMIDIPort::read (MIDI::byte *, size_t) -{ - if (!receives_input()) { - return 0; - } - - timestamp_t time; - Evoral::EventType type; - uint32_t size; - vector<MIDI::byte> buffer(input_fifo.capacity()); - - while (input_fifo.read (&time, &type, &size, &buffer[0])) { - _parser->set_timestamp (time); - for (uint32_t i = 0; i < size; ++i) { - _parser->scanner (buffer[i]); - } - } - - return 0; -} - -int -JackMIDIPort::create_port () -{ - _jack_port = jack_port_register(_jack_client, _tagname.c_str(), JACK_DEFAULT_MIDI_TYPE, _flags, 0); - return _jack_port == 0 ? -1 : 0; -} - -XMLNode& -JackMIDIPort::get_state () const -{ - XMLNode& root = Port::get_state (); - -#if 0 - byte device_inquiry[6]; - - device_inquiry[0] = 0xf0; - device_inquiry[0] = 0x7e; - device_inquiry[0] = 0x7f; - device_inquiry[0] = 0x06; - device_inquiry[0] = 0x02; - device_inquiry[0] = 0xf7; - - write (device_inquiry, sizeof (device_inquiry), 0); -#endif - - if (_jack_port) { - - const char** jc = jack_port_get_connections (_jack_port); - string connection_string; - if (jc) { - for (int i = 0; jc[i]; ++i) { - if (i > 0) { - connection_string += ','; - } - connection_string += jc[i]; - } - free (jc); - } - - if (!connection_string.empty()) { - root.add_property ("connections", connection_string); - } - } else { - if (!_connections.empty()) { - root.add_property ("connections", _connections); - } - } - - return root; -} - -void -JackMIDIPort::set_state (const XMLNode& node) -{ - const XMLProperty* prop; - - if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) { - return; - } - - Port::set_state (node); - - if ((prop = node.property ("connections")) != 0) { - _connections = prop->value (); - } -} - -void -JackMIDIPort::make_connections () -{ - if (!_connections.empty()) { - vector<string> ports; - split (_connections, ports, ','); - for (vector<string>::iterator x = ports.begin(); x != ports.end(); ++x) { - if (_jack_client) { - if (receives_input()) { - jack_connect (_jack_client, (*x).c_str(), jack_port_name (_jack_port)); - } else { - jack_connect (_jack_client, jack_port_name (_jack_port), (*x).c_str()); - } - /* ignore failures */ - } - } - } - - connect_connection.disconnect (); -} - -void -JackMIDIPort::set_process_thread (pthread_t thr) -{ - _process_thread = thr; -} - -bool -JackMIDIPort::is_process_thread() -{ - return (pthread_equal(pthread_self(), _process_thread)); -} - -void -JackMIDIPort::reestablish (jack_client_t* jack) -{ - _jack_client = jack; - int const r = create_port (); - - if (r) { - PBD::error << "could not reregister ports for " << name() << endmsg; - } -} - -void -JackMIDIPort::reconnect () -{ - make_connections (); -} diff --git a/libs/midi++2/manager.cc b/libs/midi++2/manager.cc deleted file mode 100644 index 822c74e125..0000000000 --- a/libs/midi++2/manager.cc +++ /dev/null @@ -1,183 +0,0 @@ -/* - Copyright (C) 1998-99 Paul Barton-Davis - 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. - - $Id$ -*/ - -#include <fcntl.h> - -#include <glib.h> - -#include "pbd/error.h" - -#include "midi++/types.h" -#include "midi++/manager.h" -#include "midi++/channel.h" -#include "midi++/port.h" -#include "midi++/jack_midi_port.h" -#include "midi++/mmc.h" - -using namespace std; -using namespace MIDI; -using namespace PBD; - -Manager *Manager::theManager = 0; - -Manager::Manager (jack_client_t* jack) - : _ports (new PortList) -{ - _mmc = new MachineControl (this, jack); - - _mtc_input_port = add_port (new MIDI::JackMIDIPort ("MTC in", Port::IsInput, jack)); - _mtc_output_port = add_port (new MIDI::JackMIDIPort ("MTC out", Port::IsOutput, jack)); - _midi_input_port = add_port (new MIDI::JackMIDIPort ("MIDI control in", Port::IsInput, jack)); - _midi_output_port = add_port (new MIDI::JackMIDIPort ("MIDI control out", Port::IsOutput, jack)); - _midi_clock_input_port = add_port (new MIDI::JackMIDIPort ("MIDI clock in", Port::IsInput, jack)); - _midi_clock_output_port = add_port (new MIDI::JackMIDIPort ("MIDI clock out", Port::IsOutput, jack)); -} - -Manager::~Manager () -{ - delete _mmc; - - /* This will delete our MTC etc. ports */ - - boost::shared_ptr<PortList> pr = _ports.reader (); - for (PortList::iterator p = pr->begin(); p != pr->end(); ++p) { - delete *p; - } - - if (theManager == this) { - theManager = 0; - } -} - -Port * -Manager::add_port (Port* p) -{ - { - RCUWriter<PortList> writer (_ports); - boost::shared_ptr<PortList> pw = writer.get_copy (); - pw->push_back (p); - } - - PortsChanged (); /* EMIT SIGNAL */ - - return p; -} - -void -Manager::remove_port (Port* p) -{ - { - RCUWriter<PortList> writer (_ports); - boost::shared_ptr<PortList> pw = writer.get_copy (); - pw->remove (p); - } - - PortsChanged (); /* EMIT SIGNAL */ -} - -void -Manager::cycle_start (pframes_t nframes) -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - for (PortList::iterator p = pr->begin(); p != pr->end(); ++p) { - (*p)->cycle_start (nframes); - } -} - -void -Manager::cycle_end() -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - for (PortList::iterator p = pr->begin(); p != pr->end(); ++p) { - (*p)->cycle_end (); - } -} - -/** Re-register ports that disappear on JACK shutdown */ -void -Manager::reestablish (jack_client_t* jack) -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) { - JackMIDIPort* pp = dynamic_cast<JackMIDIPort*> (*p); - if (pp) { - pp->reestablish (jack); - } - } -} - -/** Re-connect ports after a reestablish () */ -void -Manager::reconnect () -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) { - JackMIDIPort* pp = dynamic_cast<JackMIDIPort*> (*p); - if (pp) { - pp->reconnect (); - } - } -} - -Port* -Manager::port (string const & n) -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - PortList::const_iterator p = pr->begin(); - while (p != pr->end() && (*p)->name() != n) { - ++p; - } - - if (p == pr->end()) { - return 0; - } - - return *p; -} - -void -Manager::create (jack_client_t* jack) -{ - assert (theManager == 0); - theManager = new Manager (jack); -} - -void -Manager::set_port_states (list<XMLNode*> s) -{ - boost::shared_ptr<PortList> pr = _ports.reader (); - - for (list<XMLNode*>::iterator i = s.begin(); i != s.end(); ++i) { - for (PortList::const_iterator j = pr->begin(); j != pr->end(); ++j) { - (*j)->set_state (**i); - } - } -} - -void -Manager::destroy () -{ - delete theManager; - theManager = 0; -} diff --git a/libs/midi++2/midi++/manager.h b/libs/midi++2/midi++/manager.h deleted file mode 100644 index c37ba392b4..0000000000 --- a/libs/midi++2/midi++/manager.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright (C) 1998 Paul Barton-Davis - - 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 __midi_manager_h__ -#define __midi_manager_h__ - -#include <list> - -#include <string> - -#include "pbd/rcu.h" - -#include "midi++/types.h" -#include "midi++/port.h" - -namespace MIDI { - -class MachineControl; - -class Manager { - public: - ~Manager (); - - /** Signal the start of an audio cycle. - * This MUST be called before any reading/writing for this cycle. - * Realtime safe. - */ - void cycle_start (pframes_t nframes); - - /** Signal the end of an audio cycle. - * This signifies that the cycle began with @ref cycle_start has ended. - * This MUST be called at the end of each cycle. - * Realtime safe. - */ - void cycle_end (); - - MachineControl* mmc () const { return _mmc; } - Port *mtc_input_port() const { return _mtc_input_port; } - Port *mtc_output_port() const { return _mtc_output_port; } - Port *midi_input_port() const { return _midi_input_port; } - Port *midi_output_port() const { return _midi_output_port; } - Port *midi_clock_input_port() const { return _midi_clock_input_port; } - Port *midi_clock_output_port() const { return _midi_clock_output_port; } - - Port* add_port (Port *); - void remove_port (Port *); - - Port* port (std::string const &); - - void set_port_states (std::list<XMLNode*>); - - typedef std::list<Port *> PortList; - - boost::shared_ptr<const PortList> get_midi_ports() const { return _ports.reader (); } - - static void create (jack_client_t* jack); - - static Manager *instance () { - return theManager; - } - static void destroy (); - - void reestablish (jack_client_t *); - void reconnect (); - - PBD::Signal0<void> PortsChanged; - - private: - /* This is a SINGLETON pattern */ - - Manager (jack_client_t *); - static Manager *theManager; - - MIDI::MachineControl* _mmc; - MIDI::Port* _mtc_input_port; - MIDI::Port* _mtc_output_port; - MIDI::Port* _midi_input_port; - MIDI::Port* _midi_output_port; - MIDI::Port* _midi_clock_input_port; - MIDI::Port* _midi_clock_output_port; - - SerializedRCUManager<PortList> _ports; -}; - -} // namespace MIDI - -#endif // __midi_manager_h__ diff --git a/libs/midi++2/midi++/mmc.h b/libs/midi++2/midi++/mmc.h index 18204e3fa8..01f8bf3b8a 100644 --- a/libs/midi++2/midi++/mmc.h +++ b/libs/midi++2/midi++/mmc.h @@ -22,17 +22,22 @@ #include <jack/types.h> #include "timecode/time.h" + #include "pbd/signals.h" #include "pbd/ringbuffer.h" + #include "midi++/types.h" #include "midi++/parser.h" +namespace ARDOUR { + class PortEngine; +} + namespace MIDI { class Port; class Parser; class MachineControlCommand; -class Manager; /** Class to handle incoming and outgoing MIDI machine control messages */ class MachineControl @@ -89,7 +94,9 @@ class MachineControl cmdResume = 0x7F }; - MachineControl (Manager *, jack_client_t *); + MachineControl (); + + void set_ports (MIDI::Port* input, MIDI::Port* output); Port* input_port() { return _input_port; } Port* output_port() { return _output_port; } diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h index f5f343e952..44897f9d8e 100644 --- a/libs/midi++2/midi++/parser.h +++ b/libs/midi++2/midi++/parser.h @@ -41,7 +41,7 @@ typedef PBD::Signal3<void,Parser &, byte *, size_t> Signal; class Parser { public: - Parser (Port &p); + Parser (); ~Parser (); /* sets the time that will be reported for any MTC or MIDI Clock @@ -105,7 +105,6 @@ class Parser { const char *midi_event_type_name (MIDI::eventType); void trace (bool onoff, std::ostream *o, const std::string &prefix = ""); bool tracing() { return trace_stream != 0; } - Port &port() { return _port; } void set_offline (bool); bool offline() const { return _offline; } @@ -136,7 +135,6 @@ class Parser { void reset_mtc_state (); private: - Port&_port; /* tracing */ std::ostream *trace_stream; diff --git a/libs/midi++2/midi++/port.h b/libs/midi++2/midi++/port.h index 153cfb0529..a915320fa3 100644 --- a/libs/midi++2/midi++/port.h +++ b/libs/midi++2/midi++/port.h @@ -55,13 +55,6 @@ class Port { virtual XMLNode& get_state () const; virtual void set_state (const XMLNode&); - // FIXME: make Manager a friend of port so these can be hidden? - - /* Only for use by MidiManager. Don't ever call this. */ - virtual void cycle_start (pframes_t) {} - /* Only for use by MidiManager. Don't ever call this. */ - virtual void cycle_end () {} - /** Write a message to port. * @param msg Raw MIDI message to send * @param msglen Size of @a msg diff --git a/libs/midi++2/mmc.cc b/libs/midi++2/mmc.cc index 1b8de40358..1b9481c9bf 100644 --- a/libs/midi++2/mmc.cc +++ b/libs/midi++2/mmc.cc @@ -22,12 +22,12 @@ #include <map> #include "timecode/time.h" + #include "pbd/error.h" + #include "midi++/mmc.h" #include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/parser.h" -#include "midi++/manager.h" #ifndef __INT_MAX__ // 'ssize_t' won't be defined yet typedef long ssize_t; @@ -199,16 +199,21 @@ static void build_mmc_cmd_map () mmc_cmd_map.insert (newpair); } - -MachineControl::MachineControl (Manager* m, jack_client_t* jack) +MachineControl::MachineControl () { build_mmc_cmd_map (); _receive_device_id = 0x7f; _send_device_id = 0x7f; +} + +void +MachineControl::set_ports (MIDI::Port* ip, MIDI::Port* op) +{ + port_connections.drop_connections (); - _input_port = m->add_port (new JackMIDIPort ("MMC in", Port::IsInput, jack)); - _output_port = m->add_port (new JackMIDIPort ("MMC out", Port::IsOutput, jack)); + _input_port = ip; + _output_port = op; _input_port->parser()->mmc.connect_same_thread (port_connections, boost::bind (&MachineControl::process_mmc_message, this, _1, _2, _3)); _input_port->parser()->start.connect_same_thread (port_connections, boost::bind (&MachineControl::spp_start, this)); diff --git a/libs/midi++2/mtc.cc b/libs/midi++2/mtc.cc index 3c58b6684f..1a477c3b8c 100644 --- a/libs/midi++2/mtc.cc +++ b/libs/midi++2/mtc.cc @@ -240,7 +240,7 @@ Parser::process_mtc_quarter_frame (MIDI::byte *msg) /* time code is looking good */ #ifdef DEBUG_MTC - // cerr << "for quarter frame " << which_quarter_frame << " byte = " << hex << (int) msg[1] << dec << endl; + cerr << "for quarter frame " << which_quarter_frame << " byte = " << hex << (int) msg[1] << dec << endl; #endif switch (which_quarter_frame) { diff --git a/libs/midi++2/parser.cc b/libs/midi++2/parser.cc index 94daef7871..7d31c99781 100644 --- a/libs/midi++2/parser.cc +++ b/libs/midi++2/parser.cc @@ -104,8 +104,7 @@ Parser::midi_event_type_name (eventType t) } } -Parser::Parser (Port &p) - : _port(p) +Parser::Parser () { trace_stream = 0; trace_prefix = ""; diff --git a/libs/midi++2/port.cc b/libs/midi++2/port.cc index 3e7896631a..1480202867 100644 --- a/libs/midi++2/port.cc +++ b/libs/midi++2/port.cc @@ -71,7 +71,7 @@ Port::init (string const & name, Flags flags) _tagname = name; _flags = flags; - _parser = new Parser (*this); + _parser = new Parser (); for (int i = 0; i < 16; i++) { _channel[i] = new Channel (i, *this); diff --git a/libs/midi++2/wscript b/libs/midi++2/wscript index ea8988110d..0abbab7d40 100644 --- a/libs/midi++2/wscript +++ b/libs/midi++2/wscript @@ -26,12 +26,11 @@ out = 'build' path_prefix = 'libs/midi++2/' + libmidi_sources = [ 'midi.cc', 'channel.cc', 'ipmidi_port.cc', - 'jack_midi_port.cc', - 'manager.cc', 'parser.cc', 'port.cc', 'midnam_patch.cc', @@ -68,7 +67,7 @@ def build(bld): obj.cxxflags = [ '-fPIC', '-DWITH_JACK_MIDI' ] # everybody loves JACK obj.export_includes = ['.'] - obj.includes = ['.', '../surfaces/control_protocol'] + obj.includes = ['.', '../surfaces/control_protocol', '../ardour' ] obj.name = 'libmidipp' obj.target = 'midipp' obj.uselib = 'GLIBMM SIGCPP XML JACK OSX' diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc index a8799944b3..e0b06a9369 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -32,13 +32,14 @@ #include "pbd/xml++.h" #include "midi++/port.h" -#include "midi++/manager.h" +#include "ardour/audioengine.h" #include "ardour/filesystem_paths.h" #include "ardour/session.h" #include "ardour/route.h" #include "ardour/midi_ui.h" #include "ardour/rc_configuration.h" +#include "ardour/midiport_manager.h" #include "generic_midi_control_protocol.h" #include "midicontrollable.h" @@ -59,8 +60,8 @@ GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) , _threshold (10) , gui (0) { - _input_port = MIDI::Manager::instance()->midi_input_port (); - _output_port = MIDI::Manager::instance()->midi_output_port (); + _input_port = s.midi_input_port (); + _output_port = s.midi_output_port (); do_feedback = false; _feedback_interval = 10000; // microseconds @@ -338,7 +339,7 @@ GenericMidiControlProtocol::start_learning (Controllable* c) } if (!mc) { - mc = new MIDIControllable (this, *_input_port, *c, false); + mc = new MIDIControllable (this, *_input_port->parser(), *c, false); } { @@ -435,7 +436,7 @@ GenericMidiControlProtocol::create_binding (PBD::Controllable* control, int pos, MIDI::byte value = control_number; // Create a MIDIControllable - MIDIControllable* mc = new MIDIControllable (this, *_input_port, *control, false); + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), *control, false); // Remove any old binding for this midi channel/type/value pair // Note: can't use delete_binding() here because we don't know the specific controllable we want to remove, only the midi information @@ -559,7 +560,7 @@ GenericMidiControlProtocol::set_state (const XMLNode& node, int version) Controllable* c = Controllable::by_id (id); if (c) { - MIDIControllable* mc = new MIDIControllable (this, *_input_port, *c, false); + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), *c, false); if (mc->set_state (**niter, version) == 0) { controllables.push_back (mc); @@ -754,7 +755,7 @@ GenericMidiControlProtocol::create_binding (const XMLNode& node) prop = node.property (X_("uri")); uri = prop->value(); - MIDIControllable* mc = new MIDIControllable (this, *_input_port, momentary); + MIDIControllable* mc = new MIDIControllable (this, *_input_port->parser(), momentary); if (mc->init (uri)) { delete mc; @@ -892,7 +893,7 @@ GenericMidiControlProtocol::create_function (const XMLNode& node) prop = node.property (X_("function")); - MIDIFunction* mf = new MIDIFunction (*_input_port); + MIDIFunction* mf = new MIDIFunction (*_input_port->parser()); if (mf->setup (*this, prop->value(), argument, data, data_size)) { delete mf; @@ -988,7 +989,7 @@ GenericMidiControlProtocol::create_action (const XMLNode& node) prop = node.property (X_("action")); - MIDIAction* ma = new MIDIAction (*_input_port); + MIDIAction* ma = new MIDIAction (*_input_port->parser()); if (ma->init (*this, prop->value(), data, data_size)) { delete ma; diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.h b/libs/surfaces/generic_midi/generic_midi_control_protocol.h index a7c420cc41..a4a51b7f99 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.h +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.h @@ -22,14 +22,11 @@ #include <list> #include <glibmm/threads.h> + #include "ardour/types.h" #include "control_protocol/control_protocol.h" -namespace MIDI { - class Port; -} - namespace PBD { class Controllable; class ControllableDescriptor; @@ -37,6 +34,11 @@ namespace PBD { namespace ARDOUR { class Session; + class MidiPort; +} + +namespace MIDI { + class Port; } class MIDIControllable; @@ -51,8 +53,8 @@ class GenericMidiControlProtocol : public ARDOUR::ControlProtocol { int set_active (bool yn); static bool probe() { return true; } - MIDI::Port* input_port () const { return _input_port; } - MIDI::Port* output_port () const { return _output_port; } + MIDI::Port* input_port () const { return _input_port; } + MIDI::Port* output_port () const { return _output_port; } void set_feedback_interval (ARDOUR::microseconds_t); int set_feedback (bool yn); @@ -97,8 +99,8 @@ class GenericMidiControlProtocol : public ARDOUR::ControlProtocol { } private: - MIDI::Port* _input_port; - MIDI::Port* _output_port; + MIDI::Port* _input_port; + MIDI::Port* _output_port; ARDOUR::microseconds_t _feedback_interval; ARDOUR::microseconds_t last_feedback_time; diff --git a/libs/surfaces/generic_midi/midiaction.cc b/libs/surfaces/generic_midi/midiaction.cc index e1eed1a49e..e537122e9c 100644 --- a/libs/surfaces/generic_midi/midiaction.cc +++ b/libs/surfaces/generic_midi/midiaction.cc @@ -25,7 +25,7 @@ using namespace MIDI; -MIDIAction::MIDIAction (MIDI::Port& p) +MIDIAction::MIDIAction (MIDI::Parser& p) : MIDIInvokable (p) { } diff --git a/libs/surfaces/generic_midi/midiaction.h b/libs/surfaces/generic_midi/midiaction.h index 521f59f26d..e2a29143ad 100644 --- a/libs/surfaces/generic_midi/midiaction.h +++ b/libs/surfaces/generic_midi/midiaction.h @@ -36,8 +36,6 @@ namespace Gtk { } namespace MIDI { - class Channel; - class Port; class Parser; } @@ -46,7 +44,7 @@ class GenericMidiControlProtocol; class MIDIAction : public MIDIInvokable { public: - MIDIAction (MIDI::Port&); + MIDIAction (MIDI::Parser&); virtual ~MIDIAction (); int init (GenericMidiControlProtocol&, const std::string& action_name, MIDI::byte* sysex = 0, size_t ssize = 0); diff --git a/libs/surfaces/generic_midi/midicontrollable.cc b/libs/surfaces/generic_midi/midicontrollable.cc index 4bf6eb0389..a26617fd67 100644 --- a/libs/surfaces/generic_midi/midicontrollable.cc +++ b/libs/surfaces/generic_midi/midicontrollable.cc @@ -31,6 +31,7 @@ #include "midi++/port.h" #include "midi++/channel.h" +#include "ardour/async_midi_port.h" #include "ardour/automation_control.h" #include "ardour/midi_ui.h" #include "ardour/utils.h" @@ -43,11 +44,11 @@ using namespace MIDI; using namespace PBD; using namespace ARDOUR; -MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, bool m) +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, MIDI::Parser& p, bool m) : _surface (s) , controllable (0) , _descriptor (0) - , _port (p) + , _parser (p) , _momentary (m) { _learned = false; /* from URI */ @@ -60,10 +61,10 @@ MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, bool feedback = true; // for now } -MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, Port& p, Controllable& c, bool m) +MIDIControllable::MIDIControllable (GenericMidiControlProtocol* s, MIDI::Parser& p, Controllable& c, bool m) : _surface (s) , _descriptor (0) - , _port (p) + , _parser (p) , _momentary (m) { set_controllable (&c); @@ -150,7 +151,7 @@ void MIDIControllable::learn_about_external_control () { drop_external_control (); - _port.parser()->any.connect_same_thread (midi_learn_connection, boost::bind (&MIDIControllable::midi_receiver, this, _1, _2, _3)); + _parser.any.connect_same_thread (midi_learn_connection, boost::bind (&MIDIControllable::midi_receiver, this, _1, _2, _3)); } void @@ -358,12 +359,6 @@ MIDIControllable::midi_receiver (Parser &, MIDI::byte *msg, size_t /*len*/) return; } - /* if the our port doesn't do input anymore, forget it ... */ - - if (!_port.parser()) { - return; - } - bind_midi ((channel_t) (msg[0] & 0xf), eventType (msg[0] & 0xF0), msg[1]); if (controllable) { @@ -382,49 +377,43 @@ MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) control_channel = chn; control_additional = additional; - if (_port.parser() == 0) { - return; - } - - Parser& p = *_port.parser(); - int chn_i = chn; switch (ev) { case MIDI::off: - p.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); + _parser.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); /* if this is a togglee, connect to noteOn as well, and we'll toggle back and forth between the two. */ if (_momentary) { - p.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); + _parser.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); } _control_description = "MIDI control: NoteOff"; break; case MIDI::on: - p.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); + _parser.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_note_on, this, _1, _2)); if (_momentary) { - p.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); + _parser.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[1], boost::bind (&MIDIControllable::midi_sense_note_off, this, _1, _2)); } _control_description = "MIDI control: NoteOn"; break; case MIDI::controller: - p.channel_controller[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_controller, this, _1, _2)); + _parser.channel_controller[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_controller, this, _1, _2)); snprintf (buf, sizeof (buf), "MIDI control: Controller %d", control_additional); _control_description = buf; break; case MIDI::program: - p.channel_program_change[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_program_change, this, _1, _2)); + _parser.channel_program_change[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_program_change, this, _1, _2)); _control_description = "MIDI control: ProgramChange"; break; case MIDI::pitchbend: - p.channel_pitchbend[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_pitchbend, this, _1, _2)); + _parser.channel_pitchbend[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIControllable::midi_sense_pitchbend, this, _1, _2)); _control_description = "MIDI control: Pitchbend"; break; diff --git a/libs/surfaces/generic_midi/midicontrollable.h b/libs/surfaces/generic_midi/midicontrollable.h index dd9dc988b4..aeed05db4a 100644 --- a/libs/surfaces/generic_midi/midicontrollable.h +++ b/libs/surfaces/generic_midi/midicontrollable.h @@ -36,17 +36,20 @@ namespace PBD { namespace MIDI { class Channel; - class Port; class Parser; } class GenericMidiControlProtocol; +namespace ARDOUR { + class AsyncMIDIPort; +} + class MIDIControllable : public PBD::Stateful { public: - MIDIControllable (GenericMidiControlProtocol *, MIDI::Port&, PBD::Controllable&, bool momentary); - MIDIControllable (GenericMidiControlProtocol *, MIDI::Port&, bool momentary = false); + MIDIControllable (GenericMidiControlProtocol *, MIDI::Parser&, PBD::Controllable&, bool momentary); + MIDIControllable (GenericMidiControlProtocol *, MIDI::Parser&, bool momentary = false); virtual ~MIDIControllable (); int init (const std::string&); @@ -72,7 +75,7 @@ class MIDIControllable : public PBD::Stateful bool learned() const { return _learned; } - MIDI::Port& get_port() const { return _port; } + MIDI::Parser& get_parser() { return _parser; } PBD::Controllable* get_controllable() const { return controllable; } void set_controllable (PBD::Controllable*); const std::string& current_uri() const { return _current_uri; } @@ -98,8 +101,8 @@ class MIDIControllable : public PBD::Stateful GenericMidiControlProtocol* _surface; PBD::Controllable* controllable; PBD::ControllableDescriptor* _descriptor; - std::string _current_uri; - MIDI::Port& _port; + std::string _current_uri; + MIDI::Parser& _parser; bool setting; int last_value; float last_controllable_value; diff --git a/libs/surfaces/generic_midi/midifunction.cc b/libs/surfaces/generic_midi/midifunction.cc index 70e9337861..b0dc95e4fc 100644 --- a/libs/surfaces/generic_midi/midifunction.cc +++ b/libs/surfaces/generic_midi/midifunction.cc @@ -25,7 +25,7 @@ using namespace MIDI; -MIDIFunction::MIDIFunction (MIDI::Port& p) +MIDIFunction::MIDIFunction (MIDI::Parser& p) : MIDIInvokable (p) { } diff --git a/libs/surfaces/generic_midi/midifunction.h b/libs/surfaces/generic_midi/midifunction.h index 8f0b0218d0..88aff0ab0a 100644 --- a/libs/surfaces/generic_midi/midifunction.h +++ b/libs/surfaces/generic_midi/midifunction.h @@ -33,7 +33,6 @@ namespace MIDI { class Channel; - class Port; class Parser; } @@ -64,7 +63,7 @@ class MIDIFunction : public MIDIInvokable TrackSetSoloIsolate, }; - MIDIFunction (MIDI::Port&); + MIDIFunction (MIDI::Parser&); virtual ~MIDIFunction (); int setup (GenericMidiControlProtocol&, const std::string& function_name, const std::string& argument, MIDI::byte* sysex = 0, size_t ssize = 0); diff --git a/libs/surfaces/generic_midi/midiinvokable.cc b/libs/surfaces/generic_midi/midiinvokable.cc index 79835835a4..42c74553d8 100644 --- a/libs/surfaces/generic_midi/midiinvokable.cc +++ b/libs/surfaces/generic_midi/midiinvokable.cc @@ -25,8 +25,8 @@ using namespace MIDI; -MIDIInvokable::MIDIInvokable (MIDI::Port& p) - : _port (p) +MIDIInvokable::MIDIInvokable (MIDI::Parser& p) + : _parser (p) { data_size = 0; data = 0; @@ -127,12 +127,6 @@ MIDIInvokable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) control_channel = chn; control_additional = additional; - if (_port.parser() == 0) { - return; - } - - Parser& p = *_port.parser(); - int chn_i = chn; /* incoming MIDI is parsed by Ardour' MidiUI event loop/thread, and we want our handlers to execute in that context, so we use @@ -141,27 +135,27 @@ MIDIInvokable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) switch (ev) { case MIDI::off: - p.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_note_off, this, _1, _2)); + _parser.channel_note_off[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_note_off, this, _1, _2)); break; case MIDI::on: - p.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_note_on, this, _1, _2)); + _parser.channel_note_on[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_note_on, this, _1, _2)); break; case MIDI::controller: - p.channel_controller[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_controller, this, _1, _2)); + _parser.channel_controller[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_controller, this, _1, _2)); break; case MIDI::program: - p.channel_program_change[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_program_change, this, _1, _2)); + _parser.channel_program_change[chn_i].connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_program_change, this, _1, _2)); break; case MIDI::sysex: - p.sysex.connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_sysex, this, _1, _2, _3)); + _parser.sysex.connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_sysex, this, _1, _2, _3)); break; case MIDI::any: - p.any.connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_any, this, _1, _2, _3)); + _parser.any.connect_same_thread (midi_sense_connection[0], boost::bind (&MIDIInvokable::midi_sense_any, this, _1, _2, _3)); break; default: diff --git a/libs/surfaces/generic_midi/midiinvokable.h b/libs/surfaces/generic_midi/midiinvokable.h index 62cbab238c..f374a126a5 100644 --- a/libs/surfaces/generic_midi/midiinvokable.h +++ b/libs/surfaces/generic_midi/midiinvokable.h @@ -31,7 +31,6 @@ namespace MIDI { class Channel; - class Port; class Parser; } @@ -40,12 +39,12 @@ class GenericMidiControlProtocol; class MIDIInvokable : public PBD::Stateful { public: - MIDIInvokable (MIDI::Port&); + MIDIInvokable (MIDI::Parser&); virtual ~MIDIInvokable (); virtual int init (GenericMidiControlProtocol&, const std::string&, MIDI::byte* data = 0, size_t dsize = 0); - MIDI::Port& get_port() const { return _port; } + MIDI::Parser& get_parser() { return _parser; } void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte); MIDI::channel_t get_control_channel () { return control_channel; } @@ -55,7 +54,7 @@ class MIDIInvokable : public PBD::Stateful protected: GenericMidiControlProtocol* _ui; std::string _invokable_name; - MIDI::Port& _port; + MIDI::Parser& _parser; PBD::ScopedConnection midi_sense_connection[2]; MIDI::eventType control_type; MIDI::byte control_additional; diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc index 95ac0c3025..194e35a26f 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.cc +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -392,7 +392,9 @@ MackieControlProtocol::set_active (bool yn) BaseUI::run (); - create_surfaces (); + if (create_surfaces ()) { + return -1; + } connect_session_signals (); _active = true; update_surfaces (); @@ -605,7 +607,7 @@ MackieControlProtocol::set_profile (const string& profile_name) _device_profile = d->second; } -void +int MackieControlProtocol::set_device (const string& device_name, bool allow_activation) { map<string,DeviceInfo>::iterator d = DeviceInfo::device_info.find (device_name); @@ -614,7 +616,7 @@ MackieControlProtocol::set_device (const string& device_name, bool allow_activat device_name, allow_activation)); if (d == DeviceInfo::device_info.end()) { - return; + return -1; } if (_active) { @@ -628,13 +630,16 @@ MackieControlProtocol::set_device (const string& device_name, bool allow_activat set_active (true); } else { if (_active) { - create_surfaces (); + if (create_surfaces ()) { + return -1; + } switch_banks (0, true); } } + return 0; } -void +int MackieControlProtocol::create_surfaces () { string device_name; @@ -651,7 +656,13 @@ MackieControlProtocol::create_surfaces () for (uint32_t n = 0; n < 1 + _device_info.extenders(); ++n) { - boost::shared_ptr<Surface> surface (new Surface (*this, device_name, n, stype)); + boost::shared_ptr<Surface> surface; + + try { + surface.reset (new Surface (*this, device_name, n, stype)); + } catch (...) { + return -1; + } { Glib::Threads::Mutex::Lock lm (surfaces_lock); @@ -697,6 +708,8 @@ MackieControlProtocol::create_surfaces () g_source_ref (psrc->gobj()); } } + + return 0; } void @@ -1237,7 +1250,7 @@ MackieControlProtocol::midi_input_handler (IOCondition ioc, MIDI::Port* port) } DEBUG_TRACE (DEBUG::MackieControl, string_compose ("data available on %1\n", port->name())); - framepos_t now = session->engine().frame_time(); + framepos_t now = session->engine().sample_time(); port->parse (now); } @@ -1574,14 +1587,17 @@ MackieControlProtocol::set_ipmidi_base (int16_t portnum) } } -void +int MackieControlProtocol::ipmidi_restart () { clear_ports (); clear_surfaces (); - create_surfaces (); + if (create_surfaces ()) { + return -1; + } switch_banks (_current_initial_bank, true); needs_ipmidi_restart = false; + return 0; } void diff --git a/libs/surfaces/mackie/mackie_control_protocol.h b/libs/surfaces/mackie/mackie_control_protocol.h index ffc06a32eb..4669247726 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.h +++ b/libs/surfaces/mackie/mackie_control_protocol.h @@ -120,8 +120,8 @@ class MackieControlProtocol Mackie::DeviceProfile& device_profile() { return _device_profile; } int set_active (bool yn); - void set_device (const std::string&, bool allow_activation = true); - void set_profile (const std::string&); + int set_device (const std::string&, bool allow_activation = true); + void set_profile (const std::string&); FlipMode flip_mode () const { return _flip_mode; } ViewMode view_mode () const { return _view_mode; } @@ -291,7 +291,7 @@ class MackieControlProtocol ARDOUR::RouteNotificationList _last_selected_routes; - void create_surfaces (); + int create_surfaces (); bool periodic(); void build_gui (); bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port); @@ -301,8 +301,8 @@ class MackieControlProtocol void build_button_map (); void gui_track_selection_changed (ARDOUR::RouteNotificationListPtr, bool save_list); void _gui_track_selection_changed (ARDOUR::RouteNotificationList*, bool save_list); - void ipmidi_restart (); - void initialize (); + int ipmidi_restart (); + void initialize (); /* BUTTON HANDLING */ diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc index 0c45a29378..507491d84c 100644 --- a/libs/surfaces/mackie/surface.cc +++ b/libs/surfaces/mackie/surface.cc @@ -24,7 +24,6 @@ #include <cmath> #include "midi++/port.h" -#include "midi++/manager.h" #include "ardour/automation_control.h" #include "ardour/debug.h" @@ -87,7 +86,11 @@ Surface::Surface (MackieControlProtocol& mcp, const std::string& device_name, ui { DEBUG_TRACE (DEBUG::MackieControl, "Surface::Surface init\n"); - _port = new SurfacePort (*this); + try { + _port = new SurfacePort (*this); + } catch (...) { + throw failed_constructor (); + } /* only the first Surface object has global controls */ diff --git a/libs/surfaces/mackie/surface_port.cc b/libs/surfaces/mackie/surface_port.cc index 9f52f0dccd..508f501f59 100644 --- a/libs/surfaces/mackie/surface_port.cc +++ b/libs/surfaces/mackie/surface_port.cc @@ -23,60 +23,48 @@ #include <sigc++/sigc++.h> #include <boost/shared_array.hpp> +#include "pbd/failed_constructor.h" + #include "midi++/types.h" -#include "midi++/port.h" -#include "midi++/jack_midi_port.h" #include "midi++/ipmidi_port.h" -#include "midi++/manager.h" +#include "ardour/async_midi_port.h" #include "ardour/debug.h" #include "ardour/rc_configuration.h" #include "ardour/session.h" #include "ardour/audioengine.h" +#include "ardour/async_midi_port.h" +#include "ardour/midiport_manager.h" #include "controls.h" #include "mackie_control_protocol.h" #include "surface.h" #include "surface_port.h" - #include "i18n.h" using namespace std; using namespace Mackie; using namespace PBD; +using namespace ARDOUR; -/** @param input_port Input MIDI::Port; this object takes responsibility for - * adding & removing it from the MIDI::Manager and destroying it. @param - * output_port Output MIDI::Port; responsibility similarly taken. - */ SurfacePort::SurfacePort (Surface& s) : _surface (&s) - , _input_port (0) - , _output_port (0) { + if (_surface->mcp().device_info().uses_ipmidi()) { _input_port = new MIDI::IPMIDIPort (_surface->mcp().ipmidi_base() +_surface->number()); _output_port = _input_port; } else { - jack_client_t* jack = MackieControlProtocol::instance()->get_session().engine().jack(); - - _input_port = new MIDI::JackMIDIPort (string_compose (_("%1 in"), _surface->name()), MIDI::Port::IsInput, jack); - _output_port =new MIDI::JackMIDIPort (string_compose (_("%1 out"), _surface->name()), MIDI::Port::IsOutput, jack); - - /* MackieControl has its own thread for handling input from the input - * port, and we don't want anything handling output from the output - * port. This stops the Generic MIDI UI event loop in ardour from - * attempting to handle these ports. - */ - - _input_port->set_centrally_parsed (false); - _output_port->set_centrally_parsed (false); - - MIDI::Manager * mm = MIDI::Manager::instance(); - - mm->add_port (_input_port); - mm->add_port (_output_port); + _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, string_compose (_("%1 in"), _surface->name()), true); + _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, string_compose (_("%1 out"), _surface->name()), true); + + if (_async_in == 0 || _async_out == 0) { + throw failed_constructor(); + } + + _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get(); + _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get(); } } @@ -86,17 +74,15 @@ SurfacePort::~SurfacePort() delete _input_port; } else { - MIDI::Manager* mm = MIDI::Manager::instance (); - - if (_input_port) { - mm->remove_port (_input_port); - delete _input_port; + if (_async_in) { + AudioEngine::instance()->unregister_port (_async_in); + _async_in.reset (); } - if (_output_port) { + if (_async_out) { _output_port->drain (10000); - mm->remove_port (_output_port); - delete _output_port; + AudioEngine::instance()->unregister_port (_async_out); + _async_out.reset (); } } } diff --git a/libs/surfaces/mackie/surface_port.h b/libs/surfaces/mackie/surface_port.h index 7dc20a06f2..751ee848d7 100644 --- a/libs/surfaces/mackie/surface_port.h +++ b/libs/surfaces/mackie/surface_port.h @@ -21,16 +21,23 @@ #include <midi++/types.h> #include "pbd/signals.h" + + #include "midi_byte_array.h" #include "types.h" namespace MIDI { - class Port; class Parser; + class Port; } class MackieControlProtocol; +namespace ARDOUR { + class AsyncMIDIPort; + class Port; +} + namespace Mackie { @@ -49,17 +56,17 @@ public: /// an easier way to output bytes via midi int write (const MidiByteArray&); - MIDI::Port& input_port() { return *_input_port; } - const MIDI::Port& input_port() const { return *_input_port; } - MIDI::Port& output_port() { return *_output_port; } - const MIDI::Port& output_port() const { return *_output_port; } + MIDI::Port& input_port() const { return *_input_port; } + MIDI::Port& output_port() const { return *_output_port; } protected: private: - Mackie::Surface* _surface; - MIDI::Port* _input_port; - MIDI::Port* _output_port; + Mackie::Surface* _surface; + MIDI::Port* _input_port; + MIDI::Port* _output_port; + boost::shared_ptr<ARDOUR::Port> _async_in; + boost::shared_ptr<ARDOUR::Port> _async_out; }; std::ostream& operator << (std::ostream& , const SurfacePort& port); diff --git a/midi_maps/Novation_Impulse61.map b/midi_maps/Novation_Impulse61.map new file mode 100644 index 0000000000..516167d071 --- /dev/null +++ b/midi_maps/Novation_Impulse61.map @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ArdourMIDIBindings version="1.0.0" name="Novation Impulse 61"> + +<!-- 2013-09-04 Alexandre Prokoudine: Map file creation --> +<!-- This mapping relies on the Basic MIDI preset in the Impulse 61. --> + +<!-- Transport controls --> + + <Binding channel="1" ctl="112" function="transport-start"/> + <Binding channel="1" ctl="113" function="transport-end"/> + <Binding channel="1" ctl="114" function="transport-stop"/> + <Binding channel="1" ctl="115" function="transport-roll"/> + <Binding channel="1" ctl="116" function="loop-toggle"/> + <Binding channel="1" ctl="117" function="rec-enable"/> + <Binding channel="1" ctl="117" function="rec-disable"/> + +<!-- Gain controls. Faders are mapped to faders. --> + + <Binding channel="1" ctl="41" uri="/route/gain 1"/> + <Binding channel="1" ctl="42" uri="/route/gain 2"/> + <Binding channel="1" ctl="43" uri="/route/gain 3"/> + <Binding channel="1" ctl="44" uri="/route/gain 4"/> + <Binding channel="1" ctl="45" uri="/route/gain 5"/> + <Binding channel="1" ctl="46" uri="/route/gain 6"/> + <Binding channel="1" ctl="47" uri="/route/gain 7"/> + <Binding channel="1" ctl="48" uri="/route/gain 8"/> + <Binding channel="1" ctl="49" uri="/bus/gain master"/> + +<!-- Pan controls. Encoders are mapped to pan direction. --> +<!-- There doesn't seem to be a way to map encoders to pan width. --> + + <Binding channel="1" ctl="21" uri="/route/pandirection 1"/> + <Binding channel="1" ctl="22" uri="/route/pandirection 2"/> + <Binding channel="1" ctl="23" uri="/route/pandirection 3"/> + <Binding channel="1" ctl="24" uri="/route/pandirection 4"/> + <Binding channel="1" ctl="25" uri="/route/pandirection 5"/> + <Binding channel="1" ctl="26" uri="/route/pandirection 6"/> + <Binding channel="1" ctl="27" uri="/route/pandirection 7"/> + <Binding channel="1" ctl="28" uri="/route/pandirection 8"/> + +<!-- Status controls --> +<!-- Status buttons are mapped to mute toggles. --> +<!-- Apparently Solo toggle only works when Automap is used. Sad but true. --> + + <Binding channel="1" ctl="51" uri="/route/mute 1"/> + <Binding channel="1" ctl="52" uri="/route/mute 2"/> + <Binding channel="1" ctl="53" uri="/route/mute 3"/> + <Binding channel="1" ctl="54" uri="/route/mute 4"/> + <Binding channel="1" ctl="55" uri="/route/mute 5"/> + <Binding channel="1" ctl="56" uri="/route/mute 6"/> + <Binding channel="1" ctl="57" uri="/route/mute 7"/> + <Binding channel="1" ctl="58" uri="/route/mute 8"/> + <Binding channel="1" ctl="59" uri="/bus/mute master"/>" + + +</ArdourMIDIBindings> @@ -29,6 +29,7 @@ children = [ 'libs/rubberband', 'libs/surfaces', 'libs/panners', + 'libs/backends', 'libs/timecode', 'libs/ardour', 'libs/gtkmm2ext', |