summaryrefslogtreecommitdiffstats
path: root/src/JackDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/JackDriver.cpp')
-rw-r--r--src/JackDriver.cpp588
1 files changed, 588 insertions, 0 deletions
diff --git a/src/JackDriver.cpp b/src/JackDriver.cpp
new file mode 100644
index 0000000..5daedae
--- /dev/null
+++ b/src/JackDriver.cpp
@@ -0,0 +1,588 @@
+/* This file is part of Patchage.
+ * Copyright 2007-2014 David Robillard <http://drobilla.net>
+ *
+ * Patchage 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 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Patchage 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 details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Patchage. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cassert>
+#include <cstring>
+#include <set>
+#include <string>
+
+#include <boost/format.hpp>
+
+#include <jack/jack.h>
+#include <jack/statistics.h>
+
+#include "JackDriver.hpp"
+#include "Patchage.hpp"
+#include "PatchageCanvas.hpp"
+#include "PatchageEvent.hpp"
+#include "PatchageModule.hpp"
+#include "Queue.hpp"
+#include "patchage_config.h"
+#ifdef HAVE_JACK_METADATA
+#include <jack/metadata.h>
+#include "jackey.h"
+#endif
+
+using std::endl;
+using std::string;
+using boost::format;
+
+JackDriver::JackDriver(Patchage* app)
+ : _app(app)
+ , _client(NULL)
+ , _events(128)
+ , _xruns(0)
+ , _xrun_delay(0)
+ , _is_activated(false)
+{
+ _last_pos.frame = 0;
+ _last_pos.valid = (jack_position_bits_t)0;
+}
+
+JackDriver::~JackDriver()
+{
+ detach();
+}
+
+/** Connect to Jack.
+ */
+void
+JackDriver::attach(bool launch_daemon)
+{
+ // Already connected
+ if (_client)
+ return;
+
+ jack_options_t options = (!launch_daemon) ? JackNoStartServer : JackNullOption;
+ _client = jack_client_open("Patchage", options, NULL);
+ if (_client == NULL) {
+ _app->error_msg("Jack: Unable to create client.");
+ _is_activated = false;
+ } else {
+ jack_client_t* const client = _client;
+
+ jack_on_shutdown(client, jack_shutdown_cb, this);
+ jack_set_client_registration_callback(client, jack_client_registration_cb, this);
+ jack_set_port_registration_callback(client, jack_port_registration_cb, this);
+ jack_set_port_connect_callback(client, jack_port_connect_cb, this);
+ jack_set_xrun_callback(client, jack_xrun_cb, this);
+
+ _buffer_size = jack_get_buffer_size(client);
+
+ if (!jack_activate(client)) {
+ _is_activated = true;
+ signal_attached.emit();
+ std::stringstream ss;
+ _app->info_msg("Jack: Attached.");
+ } else {
+ _app->error_msg("Jack: Client activation failed.");
+ _is_activated = false;
+ }
+ }
+}
+
+void
+JackDriver::detach()
+{
+ Glib::Mutex::Lock lock(_shutdown_mutex);
+ if (_client) {
+ jack_deactivate(_client);
+ jack_client_close(_client);
+ _client = NULL;
+ }
+ _is_activated = false;
+ signal_detached.emit();
+ _app->info_msg("Jack: Detached.");
+}
+
+static bool
+is_jack_port(const PatchagePort* port)
+{
+ return (port->type() == JACK_AUDIO ||
+ port->type() == JACK_MIDI ||
+ port->type() == JACK_OSC ||
+ port->type() == JACK_CV);
+}
+
+/** Destroy all JACK (canvas) ports.
+ */
+void
+JackDriver::destroy_all()
+{
+ if (_app->canvas()) {
+ _app->canvas()->remove_ports(is_jack_port);
+ }
+}
+
+PatchagePort*
+JackDriver::create_port_view(Patchage* patchage,
+ const PortID& id)
+{
+ assert(id.type == PortID::JACK_ID);
+
+ jack_port_t* jack_port = jack_port_by_id(_client, id.id.jack_id);
+ if (!jack_port) {
+ _app->error_msg((format("Jack: Failed to find port with ID `%1%'.")
+ % id).str());;
+ return NULL;
+ }
+
+ const int jack_flags = jack_port_flags(jack_port);
+
+ string module_name, port_name;
+ port_names(id, module_name, port_name);
+
+ ModuleType type = InputOutput;
+ if (_app->conf()->get_module_split(
+ module_name, (jack_flags & JackPortIsTerminal))) {
+ if (jack_flags & JackPortIsInput) {
+ type = Input;
+ } else {
+ type = Output;
+ }
+ }
+
+ PatchageModule* parent = _app->canvas()->find_module(module_name, type);
+ if (!parent) {
+ parent = new PatchageModule(patchage, module_name, type);
+ parent->load_location();
+ patchage->canvas()->add_module(module_name, parent);
+ }
+
+ if (parent->get_port(port_name)) {
+ _app->error_msg((format("Jack: Module `%1%' already has port `%2%'.")
+ % module_name % port_name).str());
+ return NULL;
+ }
+
+ PatchagePort* port = create_port(*parent, jack_port, id);
+ port->show();
+ if (port->is_input()) {
+ parent->set_is_source(false);
+ }
+
+ return port;
+}
+
+#ifdef HAVE_JACK_METADATA
+static std::string
+get_property(jack_uuid_t subject, const char* key)
+{
+ std::string result;
+
+ char* value = NULL;
+ char* datatype = NULL;
+ if (!jack_get_property(subject, key, &value, &datatype)) {
+ result = value;
+ }
+ jack_free(datatype);
+ jack_free(value);
+
+ return result;
+}
+#endif
+
+PatchagePort*
+JackDriver::create_port(PatchageModule& parent, jack_port_t* port, PortID id)
+{
+ if (!port) {
+ return NULL;
+ }
+
+ std::string label;
+ boost::optional<int> order;
+
+#ifdef HAVE_JACK_METADATA
+ const jack_uuid_t uuid = jack_port_uuid(port);
+ if (_app->conf()->get_sort_ports()) {
+ const std::string order_str = get_property(uuid, JACKEY_ORDER);
+ label = get_property(uuid, JACK_METADATA_PRETTY_NAME);
+ if (!order_str.empty()) {
+ order = atoi(order_str.c_str());
+ }
+ }
+#endif
+
+ const char* const type_str = jack_port_type(port);
+ PortType port_type;
+ if (!strcmp(type_str, JACK_DEFAULT_AUDIO_TYPE)) {
+ port_type = JACK_AUDIO;
+#ifdef HAVE_JACK_METADATA
+ if (get_property(uuid, JACKEY_SIGNAL_TYPE) == "CV") {
+ port_type = JACK_CV;
+ }
+#endif
+ } else if (!strcmp(type_str, JACK_DEFAULT_MIDI_TYPE)) {
+ port_type = JACK_MIDI;
+#ifdef HAVE_JACK_METADATA
+ if (get_property(uuid, JACKEY_EVENT_TYPES) == "OSC") {
+ port_type = JACK_OSC;
+ }
+#endif
+ } else {
+ _app->warning_msg((format("Jack: Port `%1%' has unknown type `%2%'.")
+ % jack_port_name(port) % type_str).str());
+ return NULL;
+ }
+
+ PatchagePort* ret(
+ new PatchagePort(parent, port_type, jack_port_short_name(port),
+ label,
+ (jack_port_flags(port) & JackPortIsInput),
+ _app->conf()->get_port_color(port_type),
+ _app->show_human_names(),
+ order));
+
+ if (id.type != PortID::NULL_PORT_ID) {
+ dynamic_cast<PatchageCanvas*>(parent.canvas())->index_port(id, ret);
+ }
+
+ return ret;
+}
+
+void
+JackDriver::shutdown()
+{
+ signal_detached.emit();
+}
+
+/** Refresh all Jack audio ports/connections.
+ * To be called from GTK thread only.
+ */
+void
+JackDriver::refresh()
+{
+ const char** ports;
+ jack_port_t* port;
+
+ // Jack can take _client away from us at any time throughout here :/
+ // Shortest locks possible is the best solution I can figure out
+
+ Glib::Mutex::Lock lock(_shutdown_mutex);
+
+ if (_client == NULL) {
+ shutdown();
+ return;
+ }
+
+ ports = jack_get_ports(_client, NULL, NULL, 0); // get all existing ports
+
+ if (!ports) {
+ return;
+ }
+
+ string client1_name;
+ string port1_name;
+ string client2_name;
+ string port2_name;
+ size_t colon;
+
+ // Add all ports
+ for (int i = 0; ports[i]; ++i) {
+ port = jack_port_by_name(_client, ports[i]);
+
+ client1_name = ports[i];
+ client1_name = client1_name.substr(0, client1_name.find(":"));
+
+ ModuleType type = InputOutput;
+ if (_app->conf()->get_module_split(
+ client1_name,
+ (jack_port_flags(port) & JackPortIsTerminal))) {
+ if (jack_port_flags(port) & JackPortIsInput) {
+ type = Input;
+ } else {
+ type = Output;
+ }
+ }
+
+ PatchageModule* m = _app->canvas()->find_module(client1_name, type);
+
+ if (!m) {
+ m = new PatchageModule(_app, client1_name, type);
+ m->load_location();
+ _app->canvas()->add_module(client1_name, m);
+ }
+
+ if (!m->get_port(jack_port_short_name(port)))
+ create_port(*m, port, PortID());
+ }
+
+ // Add all connections
+ for (int i = 0; ports[i]; ++i) {
+ port = jack_port_by_name(_client, ports[i]);
+ const char** connected_ports = jack_port_get_all_connections(_client, port);
+
+ client1_name = ports[i];
+ colon = client1_name.find(':');
+ port1_name = client1_name.substr(colon + 1);
+ client1_name = client1_name.substr(0, colon);
+
+ const ModuleType port1_type = (jack_port_flags(port) & JackPortIsInput)
+ ? Input : Output;
+
+ PatchageModule* client1_module
+ = _app->canvas()->find_module(client1_name, port1_type);
+
+ if (connected_ports) {
+ for (int j = 0; connected_ports[j]; ++j) {
+
+ client2_name = connected_ports[j];
+ colon = client2_name.find(':');
+ port2_name = client2_name.substr(colon+1);
+ client2_name = client2_name.substr(0, colon);
+
+ const ModuleType port2_type = (port1_type == Input) ? Output : Input;
+
+ PatchageModule* client2_module
+ = _app->canvas()->find_module(client2_name, port2_type);
+
+ Ganv::Port* port1 = client1_module->get_port(port1_name);
+ Ganv::Port* port2 = client2_module->get_port(port2_name);
+
+ if (!port1 || !port2)
+ continue;
+
+ Ganv::Port* src = NULL;
+ Ganv::Port* dst = NULL;
+
+ if (port1->is_output() && port2->is_input()) {
+ src = port1;
+ dst = port2;
+ } else {
+ src = port2;
+ dst = port1;
+ }
+
+ if (src && dst && !_app->canvas()->get_edge(src, dst))
+ _app->canvas()->make_connection(src, dst);
+ }
+
+ jack_free(connected_ports);
+ }
+ }
+
+ jack_free(ports);
+}
+
+bool
+JackDriver::port_names(const PortID& id,
+ string& module_name,
+ string& port_name)
+{
+ jack_port_t* jack_port = NULL;
+
+ if (id.type == PortID::JACK_ID)
+ jack_port = jack_port_by_id(_client, id.id.jack_id);
+
+ if (!jack_port) {
+ module_name.clear();
+ port_name.clear();
+ return false;
+ }
+
+ const string full_name = jack_port_name(jack_port);
+
+ module_name = full_name.substr(0, full_name.find(":"));
+ port_name = full_name.substr(full_name.find(":")+1);
+
+ return true;
+}
+
+/** Connects two Jack audio ports.
+ * To be called from GTK thread only.
+ * \return Whether connection succeeded.
+ */
+bool
+JackDriver::connect(PatchagePort* src_port,
+ PatchagePort* dst_port)
+{
+ if (_client == NULL)
+ return false;
+
+ int result = jack_connect(_client, src_port->full_name().c_str(), dst_port->full_name().c_str());
+
+ if (result == 0)
+ _app->info_msg(string("Jack: Connected ")
+ + src_port->full_name() + " => " + dst_port->full_name());
+ else
+ _app->error_msg(string("Jack: Unable to connect ")
+ + src_port->full_name() + " => " + dst_port->full_name());
+
+ return (!result);
+}
+
+/** Disconnects two Jack audio ports.
+ * To be called from GTK thread only.
+ * \return Whether disconnection succeeded.
+ */
+bool
+JackDriver::disconnect(PatchagePort* const src_port,
+ PatchagePort* const dst_port)
+{
+ if (_client == NULL)
+ return false;
+
+ int result = jack_disconnect(_client, src_port->full_name().c_str(), dst_port->full_name().c_str());
+
+ if (result == 0)
+ _app->info_msg(string("Jack: Disconnected ")
+ + src_port->full_name() + " => " + dst_port->full_name());
+ else
+ _app->error_msg(string("Jack: Unable to disconnect ")
+ + src_port->full_name() + " => " + dst_port->full_name());
+
+ return (!result);
+}
+
+void
+JackDriver::jack_client_registration_cb(const char* name, int registered, void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ assert(me->_client);
+
+ if (registered) {
+ me->_events.push(PatchageEvent(PatchageEvent::CLIENT_CREATION, name));
+ } else {
+ me->_events.push(PatchageEvent(PatchageEvent::CLIENT_DESTRUCTION, name));
+ }
+}
+
+void
+JackDriver::jack_port_registration_cb(jack_port_id_t port_id, int registered, void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ assert(me->_client);
+
+ if (registered) {
+ me->_events.push(PatchageEvent(PatchageEvent::PORT_CREATION, port_id));
+ } else {
+ me->_events.push(PatchageEvent(PatchageEvent::PORT_DESTRUCTION, port_id));
+ }
+}
+
+void
+JackDriver::jack_port_connect_cb(jack_port_id_t src, jack_port_id_t dst, int connect, void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ assert(me->_client);
+
+ if (connect) {
+ me->_events.push(PatchageEvent(PatchageEvent::CONNECTION, src, dst));
+ } else {
+ me->_events.push(PatchageEvent(PatchageEvent::DISCONNECTION, src, dst));
+ }
+}
+
+int
+JackDriver::jack_xrun_cb(void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ assert(me->_client);
+
+ ++me->_xruns;
+ me->_xrun_delay = jack_get_xrun_delayed_usecs(me->_client);
+
+ jack_reset_max_delayed_usecs(me->_client);
+
+ return 0;
+}
+
+void
+JackDriver::jack_shutdown_cb(void* jack_driver)
+{
+ assert(jack_driver);
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ me->_app->info_msg("Jack: Shutdown.");
+ Glib::Mutex::Lock lock(me->_shutdown_mutex);
+ me->_client = NULL;
+ me->_is_activated = false;
+ me->signal_detached.emit();
+}
+
+jack_nframes_t
+JackDriver::buffer_size()
+{
+ if (_is_activated)
+ return _buffer_size;
+ else
+ return jack_get_buffer_size(_client);
+}
+
+void
+JackDriver::reset_xruns()
+{
+ _xruns = 0;
+ _xrun_delay = 0;
+}
+
+float
+JackDriver::get_max_dsp_load()
+{
+ float max_load = 0.0f;
+ if (_client) {
+ const float max_delay = jack_get_max_delayed_usecs(_client);
+ const float rate = sample_rate();
+ const float size = buffer_size();
+ const float period = size / rate * 1000000; // usec
+
+ if (max_delay > period) {
+ max_load = 1.0;
+ jack_reset_max_delayed_usecs(_client);
+ } else {
+ max_load = max_delay / period;
+ }
+ }
+ return max_load;
+}
+
+void
+JackDriver::reset_max_dsp_load()
+{
+ if (_client) {
+ jack_reset_max_delayed_usecs(_client);
+ }
+}
+
+bool
+JackDriver::set_buffer_size(jack_nframes_t size)
+{
+ if (buffer_size() == size) {
+ return true;
+ }
+
+ if (!_client) {
+ _buffer_size = size;
+ return true;
+ }
+
+ if (jack_set_buffer_size(_client, size)) {
+ _app->error_msg("[JACK] Unable to set buffer size");
+ return false;
+ } else {
+ _buffer_size = size;
+ return true;
+ }
+}
+
+void
+JackDriver::process_events(Patchage* app)
+{
+ while (!_events.empty()) {
+ PatchageEvent& ev = _events.front();
+ ev.execute(app);
+ _events.pop();
+ }
+}