diff options
author | David Robillard <d@drobilla.net> | 2006-06-09 02:18:57 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2006-06-09 02:18:57 +0000 |
commit | 2f0e092964f800c0ff82cbd08bf2ee05db814e7b (patch) | |
tree | d35a09dc50535aae9292f8ca3262adc5526fe2a6 /src | |
download | patchage-2f0e092964f800c0ff82cbd08bf2ee05db814e7b.tar.gz patchage-2f0e092964f800c0ff82cbd08bf2ee05db814e7b.tar.bz2 patchage-2f0e092964f800c0ff82cbd08bf2ee05db814e7b.zip |
Added patchage
git-svn-id: http://svn.drobilla.net/lad/patchage@5 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src')
-rw-r--r-- | src/AlsaDriver.cpp | 484 | ||||
-rw-r--r-- | src/AlsaDriver.h | 72 | ||||
-rw-r--r-- | src/Driver.h | 55 | ||||
-rw-r--r-- | src/JackDriver.cpp | 258 | ||||
-rw-r--r-- | src/JackDriver.h | 68 | ||||
-rw-r--r-- | src/LashDriver.cpp | 146 | ||||
-rw-r--r-- | src/LashDriver.h | 50 | ||||
-rw-r--r-- | src/Makefile.am | 27 | ||||
-rw-r--r-- | src/Patchage.cpp | 411 | ||||
-rw-r--r-- | src/Patchage.h | 116 | ||||
-rw-r--r-- | src/PatchageFlowCanvas.cpp | 117 | ||||
-rw-r--r-- | src/PatchageFlowCanvas.h | 49 | ||||
-rw-r--r-- | src/PatchageModule.h | 120 | ||||
-rw-r--r-- | src/PatchagePort.h | 66 | ||||
-rw-r--r-- | src/StateManager.cpp | 246 | ||||
-rw-r--r-- | src/StateManager.h | 75 | ||||
-rw-r--r-- | src/main.cpp | 49 | ||||
-rw-r--r-- | src/patchage.glade | 607 | ||||
-rw-r--r-- | src/patchage.gladep | 9 |
19 files changed, 3025 insertions, 0 deletions
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp new file mode 100644 index 0000000..2f4d52e --- /dev/null +++ b/src/AlsaDriver.cpp @@ -0,0 +1,484 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string> +#include <iostream> +#include <cassert> +#include <sys/poll.h> +#include <errno.h> +#include "PatchageFlowCanvas.h" +#include "AlsaDriver.h" +#include "Patchage.h" +#include "PatchageModule.h" +#include "PatchagePort.h" + +using std::cerr; +using std::string; + + +using namespace LibFlowCanvas; + +AlsaDriver::AlsaDriver(Patchage* app) +: m_app(app), + m_canvas(app->canvas()), + m_seq(NULL) +{ +} + + +AlsaDriver::~AlsaDriver() +{ + detach(); +} + + +/** Attach to ALSA. + * @a launch_daemon is ignored, as ALSA has no daemon to launch/connect to. + */ +void +AlsaDriver::attach(bool launch_daemon) +{ + cout << "Connecting to Alsa... "; + cout.flush(); + + int ret = snd_seq_open(&m_seq, "default", + SND_SEQ_OPEN_DUPLEX, + SND_SEQ_NONBLOCK); + if (ret) { + cout << "Failed" << endl; + m_seq = NULL; + } else { + cout << "Connected" << endl; + + snd_seq_set_client_name(m_seq, "Patchage"); + + ret = pthread_create(&m_refresh_thread, NULL, &AlsaDriver::refresh_main, this); + if (ret) + cerr << "Couldn't start refresh thread" << endl; + } +} + + +void +AlsaDriver::detach() +{ + if (m_seq != NULL) { + pthread_cancel(m_refresh_thread); + pthread_join(m_refresh_thread, NULL); + snd_seq_close(m_seq); + m_seq = NULL; + cout << "Disconnected from Alsa" << endl; + } +} + + +/** Refresh all Alsa Midi ports and connections. + */ +void +AlsaDriver::refresh() +{ + if (!is_attached()) + return; + + assert(m_seq); + + refresh_ports(); + refresh_connections(); + + undirty(); +} + + +/** Refresh all Alsa Midi ports. + */ +void +AlsaDriver::refresh_ports() +{ + assert(is_attached()); + assert(m_seq); + + snd_seq_client_info_t* cinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_client_info_set_client(cinfo, -1); + + snd_seq_port_info_t * pinfo; + snd_seq_port_info_alloca(&pinfo); + + string client_name; + string port_name; + bool is_input = false; + bool is_duplex = false; + bool is_application = true; + + while (snd_seq_query_next_client (m_seq, cinfo) >= 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + + client_name = snd_seq_client_info_get_name(cinfo); + + while (snd_seq_query_next_port(m_seq, pinfo) >= 0) { + int caps = snd_seq_port_info_get_capability(pinfo); + int type = snd_seq_port_info_get_type(pinfo); + + // Skip ports we shouldn't show + if (caps & SND_SEQ_PORT_CAP_NO_EXPORT) + continue; + else if ( !( (caps & SND_SEQ_PORT_CAP_READ) + || (caps & SND_SEQ_PORT_CAP_WRITE) + || (caps & SND_SEQ_PORT_CAP_DUPLEX))) + continue; + else if ((snd_seq_client_info_get_type(cinfo) != SND_SEQ_USER_CLIENT) + && ((type == SND_SEQ_PORT_SYSTEM_TIMER + || type == SND_SEQ_PORT_SYSTEM_ANNOUNCE))) + continue; + + const snd_seq_addr_t addr = *snd_seq_port_info_get_addr(pinfo); + + is_duplex = false; + + // FIXME: Should be CAP_SUBS_READ etc? + if ((caps & SND_SEQ_PORT_CAP_READ) && (caps & SND_SEQ_PORT_CAP_WRITE)) + is_duplex = true; + else if (caps & SND_SEQ_PORT_CAP_READ) + is_input = false; + else if (caps & SND_SEQ_PORT_CAP_WRITE) + is_input = true; + + is_application = (type & SND_SEQ_PORT_TYPE_APPLICATION); + port_name = snd_seq_port_info_get_name(pinfo); + PatchageModule* m = NULL; + + //cout << client_name << " : " << port_name << " is_application = " << is_application + // << " is_duplex = " << is_duplex << endl; + + bool split = m_app->state_manager()->get_module_split(client_name, !is_application); + + // Application input/output ports go on the same module + if (!split) { + m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput); + if (m == NULL) { + m = new PatchageModule(m_app, client_name, InputOutput); + m->load_location(); + m->store_location(); + m_canvas->add_module(m); + } + + if (!is_duplex) { + m->add_patchage_port(port_name, is_input, ALSA_MIDI, addr); + } else { + m->add_patchage_port(port_name, true, ALSA_MIDI, addr); + m->add_patchage_port(port_name, false, ALSA_MIDI, addr); + } + } else { // non-application input/output ports (hw interface, etc) go on separate modules + PatchageModule* m = NULL; + ModuleType type = InputOutput; + + // The 'application' hint isn't always set by clients, so this bit + // is pretty nasty... + + if (!is_duplex) { // just one port to add + if (is_input) type = Input; + else type = Output; + + // See if an InputOutput module exists (maybe with Jack ports on it) + m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput); + + if (m == NULL) + m = (PatchageModule*)m_canvas->find_module(client_name, type); + if (m == NULL) { + m = new PatchageModule(m_app, client_name, type); + m->load_location(); + m->store_location(); + m_canvas->add_module(m); + } + m->add_patchage_port(port_name, is_input, ALSA_MIDI, addr); + } else { // two ports to add + type = Input; + + // See if an InputOutput module exists (maybe with Jack ports on it) + m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput); + + if (m == NULL) + m = (PatchageModule*)m_canvas->find_module(client_name, type); + if (m == NULL) { + m = new PatchageModule(m_app, client_name, type); + m->load_location(); + m->store_location(); + m_canvas->add_module(m); + } + m->add_patchage_port(port_name, true, ALSA_MIDI, addr); + + type = Output; + + // See if an InputOutput module exists (maybe with Jack ports on it) + m = (PatchageModule*)m_canvas->find_module(client_name, InputOutput); + + if (m == NULL) + m = (PatchageModule*)m_canvas->find_module(client_name, type); + if (m == NULL) { + m = new PatchageModule(m_app, client_name, type); + m->load_location(); + m->store_location(); + m_canvas->add_module(m); + } + m->add_patchage_port(port_name, false, ALSA_MIDI, addr); + } + } + } + } +} + + +/** Refresh all Alsa Midi connections. + */ +void +AlsaDriver::refresh_connections() +{ + assert(is_attached()); + assert(m_seq); + + PatchageModule* m = NULL; + PatchagePort* p = NULL; + + for (ModuleMap::iterator i = m_canvas->modules().begin(); + i != m_canvas->modules().end(); ++i) { + m = (PatchageModule*)((*i).second); + for (PortList::iterator j = m->ports().begin(); j != m->ports().end(); ++j) { + p = (PatchagePort*)(*j); + if (p->type() == ALSA_MIDI) + add_connections(p); + } + } +} + + +/** Add all connections for the given port. + */ +void +AlsaDriver::add_connections(PatchagePort* port) +{ + assert(is_attached()); + assert(m_seq); + + const snd_seq_addr_t* addr = port->alsa_addr(); + PatchagePort* connected_port = NULL; + + // Fix a problem with duplex->duplex connections (would show up twice) + // No sense doing them all twice anyway.. + if (port->is_input()) + return; + + snd_seq_query_subscribe_t* subsinfo; + snd_seq_query_subscribe_alloca(&subsinfo); + snd_seq_query_subscribe_set_root(subsinfo, addr); + snd_seq_query_subscribe_set_index(subsinfo, 0); + + while(!snd_seq_query_port_subscribers(m_seq, subsinfo)) { + const snd_seq_addr_t* connected_addr = snd_seq_query_subscribe_get_addr(subsinfo); + + connected_port = m_canvas->find_port(connected_addr, !port->is_input()); + + if (connected_port != NULL) { + m_canvas->add_connection(port, connected_port); + } + + snd_seq_query_subscribe_set_index(subsinfo, snd_seq_query_subscribe_get_index(subsinfo) + 1); + } + +} + + +/** Connects two Alsa Midi ports. + * + * \return Whether connection succeeded. + */ +bool +AlsaDriver::connect(const PatchagePort* const src_port, const PatchagePort* const dst_port) +{ + const snd_seq_addr_t* src = src_port->alsa_addr(); + const snd_seq_addr_t* dst = dst_port->alsa_addr(); + + bool result = false; + + if (src && dst) { + snd_seq_port_subscribe_t* subs; + snd_seq_port_subscribe_malloc(&subs); + snd_seq_port_subscribe_set_sender(subs, src); + snd_seq_port_subscribe_set_dest(subs, dst); + snd_seq_port_subscribe_set_exclusive(subs, 0); + snd_seq_port_subscribe_set_time_update(subs, 0); + snd_seq_port_subscribe_set_time_real(subs, 0); + + // Already connected (shouldn't happen) + if (!snd_seq_get_port_subscription(m_seq, subs)) { + cerr << "Error: Attempt to subscribe Alsa ports that are already subscribed." << endl; + result = false; + } + + int ret = snd_seq_subscribe_port(m_seq, subs); + if (ret < 0) { + cerr << "Alsa subscription failed: " << snd_strerror(ret) << endl; + result = false; + } + } + + return (!result); +} + + +/** Disconnects two Alsa Midi ports. + * + * \return Whether disconnection succeeded. + */ +bool +AlsaDriver::disconnect(const PatchagePort* const src_port, const PatchagePort* const dst_port) +{ + const snd_seq_addr_t* src = src_port->alsa_addr(); + const snd_seq_addr_t* dst = dst_port->alsa_addr(); + + bool result = false; + + snd_seq_port_subscribe_t* subs; + snd_seq_port_subscribe_malloc(&subs); + snd_seq_port_subscribe_set_sender(subs, src); + snd_seq_port_subscribe_set_dest(subs, dst); + snd_seq_port_subscribe_set_exclusive(subs, 0); + snd_seq_port_subscribe_set_time_update(subs, 0); + snd_seq_port_subscribe_set_time_real(subs, 0); + + // Not connected (shouldn't happen) + if (snd_seq_get_port_subscription(m_seq, subs) != 0) { + cerr << "Error: Attempt to unsubscribe Alsa ports that are not subscribed." << endl; + result = false; + } + + int ret = snd_seq_unsubscribe_port(m_seq, subs); + if (ret < 0) { + cerr << "Alsa unsubscription failed: " << snd_strerror(ret) << endl; + result = false; + } + + return (!result); +} + + +bool +AlsaDriver::create_refresh_port() +{ + // Mostly lifted from alsa-patch-bay, (C) 2002 Robert Ham, released under GPL + + int ret; + snd_seq_port_info_t* port_info; + + snd_seq_port_info_alloca(&port_info); + snd_seq_port_info_set_name(port_info, "System Announcement Reciever"); + snd_seq_port_info_set_capability(port_info, + SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE|SND_SEQ_PORT_CAP_NO_EXPORT); + + snd_seq_port_info_set_type(port_info, SND_SEQ_PORT_TYPE_APPLICATION); + + ret = snd_seq_create_port(m_seq, port_info); + if (ret) { + cerr << "Error creating alsa port: " << snd_strerror(ret) << endl; + return false; + } + + // Subscribe the port to the system announcer + ret = snd_seq_connect_from(m_seq, + snd_seq_port_info_get_port(port_info), + SND_SEQ_CLIENT_SYSTEM, + SND_SEQ_PORT_SYSTEM_ANNOUNCE); + + if (ret) { + cerr << "Could not connect to system announcer port: " << snd_strerror(ret) << endl; + return false; + } + + return true; +} + + +void* +AlsaDriver::refresh_main(void* me) +{ + AlsaDriver* ad = (AlsaDriver*)me; + ad->m_refresh_main(); + return NULL; +} + + +void +AlsaDriver::m_refresh_main() +{ + // "Heavily influenced" from alsa-patch-bay + // (C) 2002 Robert Ham, released under GPL + + int ret; + int nfds = snd_seq_poll_descriptors_count(m_seq, POLLIN); + struct pollfd* pfds = new struct pollfd[nfds]; + unsigned short* revents = new unsigned short[nfds]; + + if (!create_refresh_port()) { + cerr << "Could not create Alsa listen port. Auto refreshing will not work." << endl; + return; + } + + snd_seq_poll_descriptors(m_seq, pfds, nfds, POLLIN); + + while (true) { + ret = poll(pfds, nfds, -1); + if (ret == -1) { + if (errno == EINTR) + continue; + + cerr << "Error polling Alsa sequencer: " << strerror(errno) << endl; + continue; + } + + ret = snd_seq_poll_descriptors_revents(m_seq, pfds, nfds, revents); + if (ret) { + cerr << "Error getting Alsa sequencer poll events: " + << snd_strerror(ret) << endl; + continue; + } + + for (int i = 0; i < nfds; ++i) { + if (revents[i] > 0) { + snd_seq_event_t* ev; + snd_seq_event_input(m_seq, &ev); + + if (ev == NULL) + continue; + + switch (ev->type) { + case SND_SEQ_EVENT_RESET: + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_CHANGE: + case SND_SEQ_EVENT_PORT_START: + case SND_SEQ_EVENT_PORT_EXIT: + case SND_SEQ_EVENT_PORT_CHANGE: + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + m_is_dirty = true; + break; + default: + break; + } + } + } + } +} diff --git a/src/AlsaDriver.h b/src/AlsaDriver.h new file mode 100644 index 0000000..d6e910f --- /dev/null +++ b/src/AlsaDriver.h @@ -0,0 +1,72 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef ALSADRIVER_H +#define ALSADRIVER_H + +#include <iostream> +#include <alsa/asoundlib.h> +#include <pthread.h> +#include <queue> +#include <string> +#include "Driver.h" +class Patchage; +class PatchagePort; +class PatchageFlowCanvas; + +using std::queue; using std::string; + + +/** Handles all externally driven functionality, registering ports etc. + */ +class AlsaDriver : public Driver +{ +public: + AlsaDriver(Patchage* app); + ~AlsaDriver(); + + void attach(bool launch_daemon = false); + void detach(); + + bool is_attached() const { return (m_seq != NULL); } + + void refresh(); + + bool connect(const PatchagePort* const src_port, const PatchagePort* const dst_port); + bool disconnect(const PatchagePort* const src_port, const PatchagePort* const dst_port); + + PatchageFlowCanvas* canvas() { return m_canvas; } + +private: + void refresh_ports(); + void refresh_connections(); + + void add_connections(PatchagePort* port); + + bool create_refresh_port(); + static void* refresh_main(void* me); + void m_refresh_main(); + + Patchage* m_app; + PatchageFlowCanvas* m_canvas; + + snd_seq_t* m_seq; + + pthread_t m_refresh_thread; +}; + + +#endif // ALSADRIVER_H diff --git a/src/Driver.h b/src/Driver.h new file mode 100644 index 0000000..43b919a --- /dev/null +++ b/src/Driver.h @@ -0,0 +1,55 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DRIVER_H +#define DRIVER_H + +class PatchagePort; + +/** Trival driver base class */ +class Driver { +public: + virtual ~Driver() {} + + virtual void attach(bool launch_daemon) = 0; + virtual void detach() = 0; + virtual bool is_attached() const = 0; + + virtual void refresh() = 0; + + virtual bool connect(const PatchagePort* src_port, + const PatchagePort* dst_port) + { return false; } + + virtual bool disconnect(const PatchagePort* src_port, + const PatchagePort* dst_port) + { return false; } + + /** Returns whether or not a refresh is required. */ + bool is_dirty() const { return m_is_dirty; } + + /** Clear 'dirty' status after a refresh. */ + void undirty() { m_is_dirty = false; } + +protected: + Driver() : m_is_dirty(false) {} + + bool m_is_dirty; +}; + + +#endif // DRIVER_H + diff --git a/src/JackDriver.cpp b/src/JackDriver.cpp new file mode 100644 index 0000000..5bb084e --- /dev/null +++ b/src/JackDriver.cpp @@ -0,0 +1,258 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <cassert> +#include <cstring> +#include <string> +#include <iostream> +#include <jack/jack.h> +#include "PatchageFlowCanvas.h" +#include "JackDriver.h" +#include "Patchage.h" +#include "PatchageModule.h" + +using std::cerr; using std::endl; using std::cout; +using std::string; + +using namespace LibFlowCanvas; + + +JackDriver::JackDriver(Patchage* app) +: m_app(app), + m_canvas(app->canvas()), + m_client(NULL) +{ +} + + +JackDriver::~JackDriver() +{ + detach(); +} + + +/** Connect to Jack. + */ +void +JackDriver::attach(bool launch_daemon) +{ + cout << "Connecting to Jack... "; + cout.flush(); + + if (m_client != NULL) { + cout << "already connected. " << endl; + return; + } + + jack_options_t options = (!launch_daemon) ? JackNoStartServer : JackNullOption; + m_client = jack_client_open("Patchage", options, NULL); + if (m_client == NULL) { + cout << "Failed" << endl; + } else { + jack_set_port_registration_callback(m_client, jack_port_registration_cb, this); + jack_set_graph_order_callback(m_client, jack_graph_order_cb, this); + jack_on_shutdown(m_client, jack_shutdown_cb, this); + + jack_activate(m_client); + + cout << "Connected" << endl; + + m_canvas->destroy(); + + m_is_dirty = true; + } +} + + +void +JackDriver::detach() +{ + if (m_client != NULL) { + jack_deactivate(m_client); + jack_client_close(m_client); + m_client = NULL; + cout << "Disconnected from Jack" << endl; + } +} + + +/** Refresh all Jack audio ports/connections. + * To be called from GTK thread only. + */ +void +JackDriver::refresh() +{ + if (m_client == NULL) + return; + + const char** ports; + jack_port_t* port; + + ports = jack_get_ports(m_client, NULL, NULL, 0); // get all existing ports + + string client1_name; + string port1_name; + string client2_name; + string port2_name; + PatchageModule* m = NULL; + + // Add all ports + if (ports != NULL) + for (int i=0; ports[i]; ++i) { + port = jack_port_by_name(m_client, ports[i]); + client1_name = ports[i]; + client1_name = client1_name.substr(0, client1_name.find(":")); + + ModuleType type = InputOutput; + //if (m_app->state_manager()->get_module_split(client1_name) + // || jack_port_flags(port) & JackPortIsTerminal) { + if (m_app->state_manager()->get_module_split(client1_name, + (jack_port_flags(port) & JackPortIsTerminal))) { + if (jack_port_flags(port) & JackPortIsInput) { + type = Input; + } else { + type = Output; + } + } + + m = (PatchageModule*)m_canvas->find_module(client1_name, type); + + if (m == NULL) { + m = new PatchageModule(m_app, client1_name, type); + m->load_location(); + m->store_location(); + m_canvas->add_module(m); + } + + // FIXME: leak? jack docs don't say + const char* const type_str = jack_port_type(port); + PortType port_type = JACK_AUDIO; + if (!strcmp(type_str, "8 bit raw midi")) + port_type = JACK_MIDI; + + m->add_patchage_port(jack_port_short_name(port), + (jack_port_flags(port) & JackPortIsInput), + port_type); + } + + // Add all connections + if (ports != NULL) { + for (int i=0; ports[i]; ++i) { + port = jack_port_by_name(m_client, ports[i]); + const char** connected_ports = jack_port_get_all_connections(m_client, port); + + if (connected_ports != NULL) + for (int j=0; connected_ports[j]; ++j) { + client1_name = ports[i]; + port1_name = client1_name.substr(client1_name.find(':')+1); + client1_name = client1_name.substr(0, client1_name.find(':')); + + client2_name = connected_ports[j]; + port2_name = client2_name.substr(client2_name.find(':')+1); + client2_name = client2_name.substr(0, client2_name.find(':')); + + m_canvas->add_connection(client1_name, port1_name, client2_name, port2_name); + } + free(connected_ports); + } + free(ports); + } + + undirty(); +} + + +/** Connects two Jack audio ports. + * To be called from GTK thread only. + * \return Whether connection succeeded. + */ +bool +JackDriver::connect(const PatchagePort* const src_port, const PatchagePort* const dst_port) +{ + if (m_client == NULL) + return false; + + int result = jack_connect(m_client, src_port->full_name().c_str(), dst_port->full_name().c_str()); + + string msg; + + if (result == 0) { + msg = "Successfully connected jack ports"; + } else { + msg = "Unable to connect "; + msg += src_port->full_name() + " -> " + dst_port->full_name(); + } + m_app->status_message(msg); + + return (!result); +} + + +/** Disconnects two Jack audio ports. + * To be called from GTK thread only. + * \return Whether disconnection succeeded. + */ +bool +JackDriver::disconnect(const PatchagePort* const src_port, const PatchagePort* const dst_port) +{ + if (m_client == NULL) + return false; + + int result = jack_disconnect(m_client, src_port->full_name().c_str(), dst_port->full_name().c_str()); + + string msg; + + if (result == 0) { + msg = "Successfully disconnected jack ports"; + } else { + msg = "Unable to disconnect "; + msg += src_port->full_name() + " -> " + dst_port->full_name(); + } + m_app->status_message(msg); + + return (!result); +} + + +void +JackDriver::jack_port_registration_cb(jack_port_id_t port_id, int registered, void* me) +{ + assert(me != NULL); + assert(((JackDriver*)me)->m_client != NULL); + ((JackDriver*)me)->m_is_dirty = true; +} + + +int +JackDriver::jack_graph_order_cb(void* me) +{ + assert(me != NULL); + assert(((JackDriver*)me)->m_client != NULL); + ((JackDriver*)me)->m_is_dirty = true; + + return 0; +} + + +void +JackDriver::jack_shutdown_cb(void* me) +{ + assert(me != NULL); + ((JackDriver*)me)->m_client = NULL; + ((JackDriver*)me)->m_is_dirty = true; +} + + diff --git a/src/JackDriver.h b/src/JackDriver.h new file mode 100644 index 0000000..6b69900 --- /dev/null +++ b/src/JackDriver.h @@ -0,0 +1,68 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef JACKDRIVER_H +#define JACKDRIVER_H + +#include <iostream> +#include <jack/jack.h> +#include <string> +#include "Driver.h" +class Patchage; +class PatchageFlowCanvas; +class PatchagePort; + +using std::string; + + +/** Handles all externally driven functionality, registering ports etc. + * + * Jack callbacks and connect methods and things like that live here. + * Right now just for jack ports, but that will change... + */ +class JackDriver : public Driver +{ +public: + JackDriver(Patchage* app); + ~JackDriver(); + + void attach(bool launch_daemon); + void detach(); + + bool is_attached() const { return (m_client != NULL); } + void refresh(); + + bool connect(const PatchagePort* const src_port, const PatchagePort* const dst_port); + bool disconnect(const PatchagePort* const src_port, const PatchagePort* const dst_port); + /*bool connect(const string& src_module_name, const string& src_port_name, + const string& dst_module_name, const string& dest_port_name); + + bool disconnect(const string& src_module_name, const string& src_port_name, + const string& dst_module_name, const string& dest_port_name);*/ + +private: + Patchage* m_app; + PatchageFlowCanvas* m_canvas; + + jack_client_t* m_client; + + static void jack_port_registration_cb(jack_port_id_t port_id, int registered, void* controller); + static int jack_graph_order_cb(void* controller); + static void jack_shutdown_cb(void* controller); +}; + + +#endif // JACKDRIVER_H diff --git a/src/LashDriver.cpp b/src/LashDriver.cpp new file mode 100644 index 0000000..c4a89b2 --- /dev/null +++ b/src/LashDriver.cpp @@ -0,0 +1,146 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "LashDriver.h" +#include "config.h" +#include <iostream> +#include <string> +#include "Patchage.h" +#include "StateManager.h" + +using std::cerr; using std::cout; using std::endl; +using std::string; + + +LashDriver::LashDriver(Patchage* app, int argc, char** argv) +: m_app(app), + m_client(NULL), + m_args(NULL) +{ + m_args = lash_extract_args(&argc, &argv); +} + + +LashDriver::~LashDriver() +{ + if (m_args) + lash_args_destroy(m_args); +} + + +void +LashDriver::attach(bool launch_daemon) +{ + cout << "Connecting to Lash... "; + cout.flush(); + + if (m_client != NULL) { + cout << "already connected." << endl; + return; + } + + int lash_flags = LASH_Config_File; + if (launch_daemon) + lash_flags |= LASH_No_Start_Server; + m_client = lash_init(m_args, PACKAGE_NAME, lash_flags, LASH_PROTOCOL(2, 0)); + if (m_client == NULL) { + cout << "Failed. Session management will not occur." << endl; + } else { + lash_event_t* event = lash_event_new_with_type(LASH_Client_Name); + lash_event_set_string(event, "Patchage"); + lash_send_event(m_client, event); + cout << "Connected" << endl; + } +} + + +void +LashDriver::detach() +{ + // FIXME: send some notification that we're gone?? + m_client = NULL; + cout << "Disconnected from Lash" << endl; +} + + +void +LashDriver::process_events() +{ + lash_event_t* ev = NULL; + lash_config_t* conf = NULL; + + // Process events + while ((ev = lash_get_event(m_client)) != NULL) { + handle_event(ev); + lash_event_destroy(ev); + } + + // Process configs + while ((conf = lash_get_config(m_client)) != NULL) { + handle_config(conf); + lash_config_destroy(conf); + } +} + + +void +LashDriver::handle_event(lash_event_t* ev) +{ + LASH_Event_Type type = lash_event_get_type(ev); + const char* c_str = lash_event_get_string(ev); + string str = (c_str == NULL) ? "" : c_str; + + //cout << "[LashDriver] LASH Event. Type = " << type << ", string = " << str << "**********" << endl; + + if (type == LASH_Save_File) { + //cout << "[LashDriver] LASH Save File - " << str << endl; + m_app->store_window_location(); + m_app->state_manager()->save(str.append("/locations")); + lash_send_event(m_client, lash_event_new_with_type(LASH_Save_File)); + } else if (type == LASH_Restore_File) { + //cout << "[LashDriver] LASH Restore File - " << str << endl; + m_app->state_manager()->load(str.append("/locations")); + m_app->update_state(); + lash_send_event(m_client, lash_event_new_with_type(LASH_Restore_File)); + } else if (type == LASH_Save_Data_Set) { + //cout << "[LashDriver] LASH Save Data Set - " << endl; + + // Tell LASH we're done + lash_send_event(m_client, lash_event_new_with_type(LASH_Save_Data_Set)); + } else if (type == LASH_Quit) { + //stop_thread(); + m_client = NULL; + m_app->quit(); + } +} + + +void +LashDriver::handle_config(lash_config_t* conf) +{ + const char* key = NULL; + const void* val = NULL; + size_t val_size = 0; + + //cout << "[LashDriver] LASH Config. Key = " << key << endl; + + key = lash_config_get_key(conf); + val = lash_config_get_value(conf); + val_size = lash_config_get_value_size(conf); +} + + + diff --git a/src/LashDriver.h b/src/LashDriver.h new file mode 100644 index 0000000..742c249 --- /dev/null +++ b/src/LashDriver.h @@ -0,0 +1,50 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LASHDRIVER_H +#define LASHDRIVER_H + +#include <lash/lash.h> +#include "Driver.h" + +class Patchage; + +class LashDriver : public Driver +{ +public: + LashDriver(Patchage* app, int argc, char** argv); + ~LashDriver(); + + void attach(bool launch_daemon); + void detach(); + + bool is_attached() const { return lash_enabled(m_client); } + + void refresh() {} + + void process_events(); + +private: + Patchage* m_app; + lash_client_t* m_client; + lash_args_t* m_args; + + void handle_event(lash_event_t* conf); + void handle_config(lash_config_t* conf); +}; + + +#endif // LASHDRIVER_H diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..6e6eb5f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,27 @@ +AM_CXXFLAGS = -DPKGDATADIR=\"$(pkgdatadir)\" @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @JACK_CFLAGS@ @ALSA_CFLAGS@ @LASH_CFLAGS@ @FLOWCANVAS_CFLAGS@ +patchage_LDADD = @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @JACK_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @FLOWCANVAS_LIBS@ + +EXTRA_DIST = patchage.gladep + +sharefilesdir = $(pkgdatadir) +dist_sharefiles_DATA = patchage.glade + +bin_PROGRAMS = patchage +patchage_SOURCES = \ + main.cpp \ + Patchage.h \ + Patchage.cpp \ + StateManager.h \ + StateManager.cpp \ + PatchageModule.h \ + PatchagePort.h \ + JackDriver.h \ + JackDriver.cpp \ + AlsaDriver.h \ + AlsaDriver.cpp \ + PatchageFlowCanvas.h \ + PatchageFlowCanvas.cpp + +if WITH_LASH +patchage_SOURCES += LashDriver.h LashDriver.cpp +endif diff --git a/src/Patchage.cpp b/src/Patchage.cpp new file mode 100644 index 0000000..38d17f7 --- /dev/null +++ b/src/Patchage.cpp @@ -0,0 +1,411 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Patchage.h" +#include "config.h" +#include <libgnomecanvasmm.h> +#include <libglademm/xml.h> +#include <fstream> +#include <pthread.h> +#include "StateManager.h" +#include "PatchageFlowCanvas.h" +#include "AlsaDriver.h" +#include "JackDriver.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif + + +Patchage::Patchage(int argc, char** argv) +: +#ifdef HAVE_LASH + m_lash_driver(NULL), +#endif +#ifdef HAVE_ALSA + m_alsa_driver(NULL), +#endif + m_canvas(NULL), + m_jack_driver(NULL), + m_state_manager(NULL), + m_refresh(false) +{ + m_settings_filename = getenv("HOME"); + m_settings_filename += "/.patchagerc"; + + m_state_manager = new StateManager(); + m_canvas = new PatchageFlowCanvas(this, 1600*2, 1200*2); + m_jack_driver = new JackDriver(this); +#ifdef HAVE_ALSA + m_alsa_driver = new AlsaDriver(this); +#endif + + m_state_manager->load(m_settings_filename); + +#ifdef HAVE_LASH + m_lash_driver = new LashDriver(this, argc, argv); +#endif + + Glib::RefPtr<Gnome::Glade::Xml> refXml; + + // Check for the .glade file in current directory + string glade_filename = "./patchage.glade"; + ifstream fs(glade_filename.c_str()); + if (fs.fail()) { // didn't find it, check PKGDATADIR + fs.clear(); + glade_filename = PKGDATADIR; + glade_filename += "/patchage.glade"; + + fs.open(glade_filename.c_str()); + if (fs.fail()) { + cerr << "Unable to find patchage.glade in current directory or " << PKGDATADIR << "." << endl; + exit(EXIT_FAILURE); + } + fs.close(); + } + + try { + refXml = Gnome::Glade::Xml::create(glade_filename); + } catch(const Gnome::Glade::XmlError& ex) { + std::cerr << ex.what() << std::endl; + throw; + } + + refXml->get_widget("patchage_win", m_main_window); + refXml->get_widget("about_win", m_about_window); + refXml->get_widget("about_project_label", m_about_project_label); + refXml->get_widget("launch_jack_menuitem", m_menu_jack_launch); + refXml->get_widget("connect_to_jack_menuitem", m_menu_jack_connect); + refXml->get_widget("disconnect_from_jack_menuitem", m_menu_jack_disconnect); +#ifdef HAVE_LASH + refXml->get_widget("launch_lash_menuitem", m_menu_lash_launch); + refXml->get_widget("connect_to_lash_menuitem", m_menu_lash_connect); + refXml->get_widget("disconnect_from_lash_menuitem", m_menu_lash_disconnect); +#endif +#ifdef HAVE_ALSA + refXml->get_widget("connect_to_alsa_menuitem", m_menu_alsa_connect); + refXml->get_widget("disconnect_from_alsa_menuitem", m_menu_alsa_disconnect); +#endif + refXml->get_widget("file_save_menuitem", m_menu_file_save); + refXml->get_widget("file_quit_menuitem", m_menu_file_quit); + refXml->get_widget("view_refresh_menuitem", m_menu_view_refresh); + refXml->get_widget("help_about_menuitem", m_menu_help_about); + refXml->get_widget("canvas_scrolledwindow", m_canvas_scrolledwindow); + refXml->get_widget("zoom_scale", m_zoom_slider); + refXml->get_widget("about_close_button", m_about_close_button); + refXml->get_widget("status_lab", m_status_label); + + m_main_window->resize( + static_cast<int>(m_state_manager->get_window_size().x), + static_cast<int>(m_state_manager->get_window_size().y)); + + m_main_window->move( + static_cast<int>(m_state_manager->get_window_location().x), + static_cast<int>(m_state_manager->get_window_location().y)); + + m_canvas_scrolledwindow->add(*m_canvas); + //m_canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(m_canvas, &FlowCanvas::scroll_event_handler)); + m_canvas->scroll_to(static_cast<int>(m_canvas->width()/2 - 320), + static_cast<int>(m_canvas->height()/2 - 240)); // FIXME: hardcoded + m_canvas->show(); + + // Idle callback, check if we need to refresh (every 250msec) + Glib::signal_timeout().connect(sigc::mem_fun(this, &Patchage::idle_callback), 250); + + m_zoom_slider->signal_value_changed().connect( sigc::mem_fun(this, &Patchage::zoom_changed)); + m_menu_jack_launch->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_jack_launch)); + m_menu_jack_connect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_jack_connect)); + m_menu_jack_disconnect->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_jack_disconnect)); +#ifdef HAVE_LASH + m_menu_lash_launch->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_lash_launch)); + m_menu_lash_connect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_lash_connect)); + m_menu_lash_disconnect->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_lash_disconnect)); +#endif + m_menu_alsa_connect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_alsa_connect)); + m_menu_alsa_disconnect->signal_activate().connect(sigc::mem_fun(this, &Patchage::menu_alsa_disconnect)); + m_menu_file_save->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_file_save)); + m_menu_file_quit->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_file_quit)); + m_menu_view_refresh->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_view_refresh)); + m_menu_help_about->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_help_about)); + m_about_close_button->signal_clicked().connect( sigc::mem_fun(this, &Patchage::close_about)); + + //_about_project_label->use_markup(true); + m_about_project_label->set_markup("<span size=\"xx-large\" weight=\"bold\">Patchage " PACKAGE_VERSION "</span>"); +} + + +Patchage::~Patchage() +{ +#ifdef HAVE_LASH + delete m_lash_driver; +#endif + delete m_jack_driver; + delete m_alsa_driver; + delete m_canvas; + delete m_state_manager; +} + + +void +Patchage::attach() +{ + m_jack_driver->attach(false); + +#ifdef HAVE_LASH + m_lash_driver->attach(false); +#endif +#ifdef HAVE_ALSA + m_alsa_driver->attach(); +#endif + + update_menu_items(); + menu_view_refresh(); +} + + +bool +Patchage::idle_callback() +{ + // FIXME: no need to destroy the whole canvas every time + if (m_refresh || (m_jack_driver && m_jack_driver->is_dirty()) +#ifdef HAVE_ALSA + || (m_alsa_driver && m_alsa_driver->is_dirty()) +#endif + ) { + m_canvas->destroy(); + m_jack_driver->refresh(); +#ifdef HAVE_ALSA + m_alsa_driver->refresh(); +#endif + update_menu_items(); + m_refresh = false; + } + +#ifdef HAVE_LASH + if (m_lash_driver->is_attached()) + m_lash_driver->process_events(); +#endif + + return true; +} + + +void +Patchage::zoom_changed() +{ + const float z = m_zoom_slider->get_value(); + + m_canvas->zoom(z); + m_state_manager->set_zoom(z); +} + + +void +Patchage::update_state() +{ + for (ModuleMap::iterator i = m_canvas->modules().begin(); i != m_canvas->modules().end(); ++i) + (*i).second->load_location(); + + cerr << "[Patchage] Resizing window: (" << m_state_manager->get_window_size().x + << "," << m_state_manager->get_window_size().y << ")" << endl; + + m_main_window->resize( + static_cast<int>(m_state_manager->get_window_size().x), + static_cast<int>(m_state_manager->get_window_size().y)); + + cerr << "[Patchage] Moving window: (" << m_state_manager->get_window_location().x + << "," << m_state_manager->get_window_location().y << ")" << endl; + m_main_window->move( + static_cast<int>(m_state_manager->get_window_location().x), + static_cast<int>(m_state_manager->get_window_location().y)); +} + + +void +Patchage::status_message(const string& msg) +{ + m_status_label->set_text(msg); +} + + + + +/** Update the sensitivity status of menus to reflect the present. + * + * (eg. disable "Connect to Jack" when Patchage is already connected to Jack) + */ +void +Patchage::update_menu_items() +{ + // Update Jack menu items + const bool jack_attached = m_jack_driver->is_attached(); + m_menu_jack_launch->set_sensitive(!jack_attached); + m_menu_jack_connect->set_sensitive(!jack_attached); + m_menu_jack_disconnect->set_sensitive(jack_attached); + + // Update Lash menu items +#ifdef HAVE_LASH + const bool lash_attached = m_lash_driver->is_attached(); + m_menu_lash_launch->set_sensitive(!lash_attached); + m_menu_lash_connect->set_sensitive(!lash_attached); + m_menu_lash_disconnect->set_sensitive(lash_attached); +#endif + +#ifdef HAVE_ALSA + // Update Alsa menu items + const bool alsa_attached = m_alsa_driver->is_attached(); + m_menu_alsa_connect->set_sensitive(!alsa_attached); + m_menu_alsa_disconnect->set_sensitive(alsa_attached); +#endif +} + + +void +Patchage::menu_jack_launch() +{ + m_jack_driver->attach(true); + update_menu_items(); +} + + +void +Patchage::menu_jack_connect() +{ + m_jack_driver->attach(false); + update_menu_items(); +} + + +void +Patchage::menu_jack_disconnect() +{ + m_jack_driver->detach(); + menu_view_refresh(); + update_menu_items(); +} + +#ifdef HAVE_LASH +void +Patchage::menu_lash_launch() +{ + m_lash_driver->attach(true); + update_menu_items(); +} + + +void +Patchage::menu_lash_connect() +{ + m_lash_driver->attach(false); + update_menu_items(); +} + + +void +Patchage::menu_lash_disconnect() +{ + m_lash_driver->detach(); + update_menu_items(); +} +#endif + +#ifdef HAVE_ALSA +void +Patchage::menu_alsa_connect() +{ + m_alsa_driver->attach(false); + update_menu_items(); +} + + +void +Patchage::menu_alsa_disconnect() +{ + m_alsa_driver->detach(); + menu_view_refresh(); + update_menu_items(); +} +#endif + +void +Patchage::menu_file_save() +{ + store_window_location(); + m_state_manager->save(m_settings_filename); +} + + +void +Patchage::menu_file_quit() +{ +#ifdef HAVE_ALSA + m_alsa_driver->detach(); +#endif + m_jack_driver->detach(); + m_main_window->hide(); +} + + +void +Patchage::menu_view_refresh() +{ + assert(m_canvas); + + // FIXME: rebuilding the entire canvas each time is garbage + m_canvas->destroy(); + + if (m_jack_driver) + m_jack_driver->refresh(); + +#ifdef HAVE_ALSA + if (m_alsa_driver) + m_alsa_driver->refresh(); +#endif +} + + +void +Patchage::menu_help_about() +{ + m_about_window->show(); +} + + +void +Patchage::close_about() +{ + m_about_window->hide(); +} + + +/** Update the stored window location and size in the StateManager (in memory). + */ +void +Patchage::store_window_location() +{ + int loc_x, loc_y, size_x, size_y; + m_main_window->get_position(loc_x, loc_y); + m_main_window->get_size(size_x, size_y); + Coord window_location; + window_location.x = loc_x; + window_location.y = loc_y; + Coord window_size; + window_size.x = size_x; + window_size.y = size_y; + m_state_manager->set_window_location(window_location); + m_state_manager->set_window_size(window_size); +} + + diff --git a/src/Patchage.h b/src/Patchage.h new file mode 100644 index 0000000..f686e49 --- /dev/null +++ b/src/Patchage.h @@ -0,0 +1,116 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PATCHAGE_H +#define PATCHAGE_H + +#include "config.h" +#include <string> +#include <libgnomecanvasmm.h> + +using namespace std; + +class PatchageFlowCanvas; +class JackDriver; +class AlsaDriver; +class LashDriver; +class StateManager; + + +class Patchage +{ +public: + Patchage(int argc, char** argv); + ~Patchage(); + + PatchageFlowCanvas* canvas() { return m_canvas; } + StateManager* state_manager() { return m_state_manager; } + Gtk::Window* window() { return m_main_window; } + JackDriver* jack_driver() { return m_jack_driver; } +#ifdef HAVE_ALSA + AlsaDriver* alsa_driver() { return m_alsa_driver; } +#endif +#ifdef HAVE_LASH + LashDriver* lash_driver() { return m_lash_driver; } +#endif + + void attach(); + void quit() { m_main_window->hide(); } + + void update_state(); + void store_window_location(); + + void status_message(const string& msg); + inline void queue_refresh() { m_refresh = true; } + +protected: + void update_menu_items(); + + void menu_jack_launch(); + void menu_jack_connect(); + void menu_jack_disconnect(); + void menu_file_save(); + void menu_file_quit(); + void menu_view_refresh(); + void menu_help_about(); + void close_about(); + void zoom_changed(); + bool idle_callback(); + +#ifdef HAVE_LASH + LashDriver* m_lash_driver; + Gtk::MenuItem* m_menu_lash_launch; + Gtk::MenuItem* m_menu_lash_connect; + Gtk::MenuItem* m_menu_lash_disconnect; + void menu_lash_launch(); + void menu_lash_connect(); + void menu_lash_disconnect(); +#endif + +#ifdef HAVE_ALSA + AlsaDriver* m_alsa_driver; + Gtk::MenuItem* m_menu_alsa_connect; + Gtk::MenuItem* m_menu_alsa_disconnect; + void menu_alsa_connect(); + void menu_alsa_disconnect(); +#endif + + PatchageFlowCanvas* m_canvas; + JackDriver* m_jack_driver; + StateManager* m_state_manager; + + Gtk::Main* m_gtk_main; + + string m_settings_filename; + bool m_refresh; + + Gtk::Window* m_main_window; + Gtk::Window* m_about_window; + Gtk::Label* m_about_project_label; + Gtk::MenuItem* m_menu_jack_launch; + Gtk::MenuItem* m_menu_jack_connect; + Gtk::MenuItem* m_menu_jack_disconnect; + Gtk::MenuItem* m_menu_file_save; + Gtk::MenuItem* m_menu_file_quit; + Gtk::MenuItem* m_menu_view_refresh; + Gtk::MenuItem* m_menu_help_about; + Gtk::ScrolledWindow* m_canvas_scrolledwindow; + Gtk::HScale* m_zoom_slider; + Gtk::Button* m_about_close_button; + Gtk::Label* m_status_label; +}; + +#endif // PATCHAGE_H diff --git a/src/PatchageFlowCanvas.cpp b/src/PatchageFlowCanvas.cpp new file mode 100644 index 0000000..2b9b19d --- /dev/null +++ b/src/PatchageFlowCanvas.cpp @@ -0,0 +1,117 @@ +/* This file is part of Patchage. Copyright (C) 2004 Dave Robillard. + * + * 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 2 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "PatchageFlowCanvas.h" +#include "Patchage.h" +#include "JackDriver.h" +#include "AlsaDriver.h" +#include "PatchageModule.h" +#include "PatchagePort.h" + + +PatchageFlowCanvas::PatchageFlowCanvas(Patchage* app, int width, int height) +: FlowCanvas(width, height), + m_app(app) +{ +} + + +PatchageModule* +PatchageFlowCanvas::find_module(const string& name, ModuleType type) +{ + PatchageModule* pm = NULL; + + for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m) { + pm = (PatchageModule*)(*m).second; + if (pm->name() == name && pm->type() == type) { + return pm; + } + } + + return NULL; +} + + +PatchagePort* +PatchageFlowCanvas::find_port(const snd_seq_addr_t* alsa_addr, bool is_input) +{ + PatchagePort* pp = NULL; + for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m) { + for (PortList::iterator p = (*m).second->ports().begin(); p != (*m).second->ports().end(); ++p) { + pp = (PatchagePort*)(*p); + if (pp->type() == ALSA_MIDI && pp->alsa_addr() + && pp->alsa_addr()->client == alsa_addr->client + && pp->alsa_addr()->port == alsa_addr->port) + if (is_input == pp->is_input()) + return pp; + } + } + + return NULL; +} + + +void +PatchageFlowCanvas::connect(const Port* port1, const Port* port2) +{ + PatchagePort* p1 = (PatchagePort*)port1; + PatchagePort* p2 = (PatchagePort*)port2; + + if (p1->type() == JACK_AUDIO && p2->type() == JACK_AUDIO + || (p1->type() == JACK_MIDI && p2->type() == JACK_MIDI)) + /*m_app->jack_driver()->connect(p1->module()->name(), p1->name(), + p2->module()->name(), p2->name());*/ + m_app->jack_driver()->connect(p1, p2); + else if (p1->type() == ALSA_MIDI && p2->type() == ALSA_MIDI) + m_app->alsa_driver()->connect(p1, p2); + else + m_app->status_message("Cannot make connection, incompatible port types."); +} + + +void +PatchageFlowCanvas::disconnect(const Port* port1, const Port* port2) +{ + PatchagePort* input = NULL; + PatchagePort* output = NULL; + + if (port1->is_input() && !port2->is_input()) { + input = (PatchagePort*)port1; + output = (PatchagePort*)port2; + } else if (port2->is_input() && !port1->is_input()) { + input = (PatchagePort*)port2; + output = (PatchagePort*)port1; + } else { + m_app->status_message("Attempt to disconnect two input (or output) ports?? Please report bug."); + return; + } + + if (input->type() == JACK_AUDIO && output->type() == JACK_AUDIO + || input->type() == JACK_MIDI && output->type() == JACK_MIDI) + m_app->jack_driver()->disconnect(output, input); + else if (input->type() == ALSA_MIDI && output->type() == ALSA_MIDI) + m_app->alsa_driver()->disconnect(output, input); + else + m_app->status_message("Attempt to disconnect Jack audio port from Alsa Midi port?? Please report bug."); +} + + +void +PatchageFlowCanvas::status_message(const string& msg) +{ + m_app->status_message(msg); +} + diff --git a/src/PatchageFlowCanvas.h b/src/PatchageFlowCanvas.h new file mode 100644 index 0000000..2636920 --- /dev/null +++ b/src/PatchageFlowCanvas.h @@ -0,0 +1,49 @@ +/* This file is part of Patchage. Copyright (C) 2004 Dave Robillard. + * + * 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 2 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PATCHAGEPATCHBAYAREA_H +#define PATCHAGEPATCHBAYAREA_H + +#include <string> +#include <alsa/asoundlib.h> +#include <flowcanvas/FlowCanvas.h> +#include "StateManager.h" +class Patchage; +class PatchageModule; +class PatchagePort; + +using std::string; +using namespace LibFlowCanvas; + +class PatchageFlowCanvas : public FlowCanvas +{ +public: + PatchageFlowCanvas(Patchage* m_app, int width, int height); + + PatchageModule* find_module(const string& name, ModuleType type); + PatchagePort* find_port(const snd_seq_addr_t* alsa_addr, bool is_input); + + void connect(const Port* port1, const Port* port2); + void disconnect(const Port* port1, const Port* port2); + + void status_message(const string& msg); + +private: + Patchage* m_app; +}; + + +#endif // PATCHAGEPATCHBAYAREA_H diff --git a/src/PatchageModule.h b/src/PatchageModule.h new file mode 100644 index 0000000..b511eec --- /dev/null +++ b/src/PatchageModule.h @@ -0,0 +1,120 @@ +/* This file is part of Patchage. Copyright (C) 2004 Dave Robillard. + * + * 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 2 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef PATCHAGEMODULE_H +#define PATCHAGEMODULE_H + +#include <string> +#include <libgnomecanvasmm.h> +#include <alsa/asoundlib.h> +#include <flowcanvas/FlowCanvas.h> +#include <flowcanvas/Module.h> +#include "PatchageFlowCanvas.h" +#include "StateManager.h" +#include "PatchagePort.h" + +using std::string; using std::list; + +using namespace LibFlowCanvas; + +class PatchageModule : public Module +{ +public: + PatchageModule(Patchage* app, const string& title, ModuleType type, double x=0, double y=0) + : Module(app->canvas(), title, x, y), + m_app(app), + m_type(type) + { + Gtk::Menu::MenuList& items = m_menu.items(); + if (type == InputOutput) { + items.push_back(Gtk::Menu_Helpers::MenuElem("Split", + sigc::mem_fun(this, &PatchageModule::split))); + } else { + items.push_back(Gtk::Menu_Helpers::MenuElem("Join", + sigc::mem_fun(this, &PatchageModule::join))); + } + items.push_back(Gtk::Menu_Helpers::MenuElem("Disconnect All", + sigc::mem_fun(this, &PatchageModule::menu_disconnect_all))); + } + + virtual ~PatchageModule() { } + + virtual void add_patchage_port(const string& port_name, bool is_input, PortType type) + { + PatchagePort* port = new PatchagePort(this, type, port_name, is_input, + m_app->state_manager()->get_port_color(type)); + + Module::add_port(port, true); + } + + virtual void add_patchage_port(const string& port_name, bool is_input, PortType type, const snd_seq_addr_t addr) + { + PatchagePort* port = new PatchagePort(this, type, port_name, is_input, + m_app->state_manager()->get_port_color(type)); + + port->alsa_addr(addr); + + Module::add_port(port, true); + } + + + virtual void load_location() { + Coord loc = m_app->state_manager()->get_module_location(m_name, m_type); + + //cerr << "******" << m_name << " MOVING TO (" << loc.x << "," << loc.y << ")" << endl; + + if (loc.x != -1) + move_to(loc.x, loc.y); + else + move_to((m_canvas->width()/2) - 100 + rand() % 400, + (m_canvas->height()/2) - 100 + rand() % 400); + } + + void split() { + assert(m_type == InputOutput); + m_app->state_manager()->set_module_split(m_name, true); + m_app->queue_refresh(); + } + + void join() { + assert(m_type != InputOutput); + m_app->state_manager()->set_module_split(m_name, false); + m_app->queue_refresh(); + } + + virtual void store_location() { + Coord loc = { property_x().get_value(), property_y().get_value() }; + m_app->state_manager()->set_module_location(m_name, m_type, loc); + } + + virtual void show_dialog() {} + virtual void on_right_click(GdkEventButton* ev) { m_menu.popup(ev->button, ev->time); } + virtual void menu_disconnect_all() { + for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p) + (*p)->disconnect_all(); + } + + ModuleType type() { return m_type; } + +protected: + Patchage* m_app; + Gtk::Menu m_menu; + ModuleType m_type; +}; + + +#endif // PATCHAGEMODULE_H diff --git a/src/PatchagePort.h b/src/PatchagePort.h new file mode 100644 index 0000000..601bce9 --- /dev/null +++ b/src/PatchagePort.h @@ -0,0 +1,66 @@ +/* This file is part of Om. Copyright (C) 2004 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef PATCHAGEPORT_H +#define PATCHAGEPORT_H + +#include <string> +#include <list> +#include <alsa/asoundlib.h> +#include <flowcanvas/Port.h> +#include <flowcanvas/Module.h> + +using namespace LibFlowCanvas; +using std::string; using std::list; + +enum PortType { JACK_AUDIO, JACK_MIDI, ALSA_MIDI }; + + +/** A Port on a PatchageModule + * + * \ingroup OmGtk + */ +class PatchagePort : public LibFlowCanvas::Port +{ +public: + PatchagePort(LibFlowCanvas::Module* module, PortType type, const string& name, bool is_input, int color) + : Port(module, name, is_input, color), + m_type(type) + { + m_alsa_addr.client = '\0'; + m_alsa_addr.port = '\0'; + } + + virtual ~PatchagePort() {} + + // FIXME: This driver specific crap really needs to go + void alsa_addr(const snd_seq_addr_t addr) { m_alsa_addr = addr; } + const snd_seq_addr_t* alsa_addr() const + { return (m_type == ALSA_MIDI) ? &m_alsa_addr : NULL; } + + /** Returns the full name of this port, as "modulename:portname" */ + string full_name() const { return m_module->name() + ":" + m_name; } + + PortType type() const { return m_type; } + +private: + snd_seq_addr_t m_alsa_addr; + PortType m_type; +}; + + +#endif // PATCHAGEPORT_H diff --git a/src/StateManager.cpp b/src/StateManager.cpp new file mode 100644 index 0000000..50429e1 --- /dev/null +++ b/src/StateManager.cpp @@ -0,0 +1,246 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "StateManager.h" +#include <stdlib.h> +#include <iostream> +#include <fstream> +#include "Patchage.h" + +using std::map; using std::list; +using std::cerr; using std::cout; using std::endl; + + +StateManager::StateManager() +{ + m_window_location.x = 0; + m_window_location.y = 0; + m_window_size.x = 640; + m_window_size.y = 480; + m_zoom = 1.0; +} + + +Coord +StateManager::get_module_location(const string& name, ModuleType type) +{ + for (std::list<ModuleLocation>::iterator i = m_module_locations.begin(); i != m_module_locations.end(); ++i) { + if ((*i).name == name && (*i).type == type) + return (*i).loc; + } + + // -1 used as a flag value + Coord r = { -1, -1 }; + return r; +} + + +void +StateManager::set_module_location(const string& name, ModuleType type, Coord loc) +{ + for (std::list<ModuleLocation>::iterator i = m_module_locations.begin(); i != m_module_locations.end(); ++i) { + if ((*i).name == name && (*i).type == type) { + (*i).loc = loc; + return; + } + } + + // If we get here, module isn't in list yet + ModuleLocation ml = { name, type, loc }; + m_module_locations.push_back(ml); +} + + +/** Returns whether or not this module should be split. + * + * If nothing is known about the given module, @a default_val is returned (this is + * to allow driver's to request terminal ports get split by default). + */ +bool +StateManager::get_module_split(const string& name, bool default_val) const +{ + map<string, bool>::const_iterator i = m_module_splits.find(name); + if (i == m_module_splits.end()) + return default_val; + else + return (*i).second; +} + + +void +StateManager::set_module_split(const string& name, bool split) +{ + m_module_splits[name] = split; +} + + +void +StateManager::load(const string& filename) +{ + m_module_locations.clear(); + + cerr << "Loading configuration file " << filename << endl; + + std::ifstream is; + is.open(filename.c_str(), std::ios::in); + + if ( ! is.good()) { + std::cerr << "Unable to load file " << filename << "!" << endl; + return; + } + + string s; + + is >> s; + if (s != "window_location") throw "Corrupt settings file."; + is >> s; + m_window_location.x = atoi(s.c_str()); + is >> s; + m_window_location.y = atoi(s.c_str()); + + is >> s; + if (s != "window_size") throw "Corrupt settings file."; + is >> s; + m_window_size.x = atoi(s.c_str()); + is >> s; + m_window_size.y = atoi(s.c_str()); + + is >> s; + if (s != "zoom_level") throw "Corrupt settings file."; + is >> s; + m_zoom = atof(s.c_str()); + + ModuleLocation ml; + while (1) { + is >> s; + if (is.eof()) break; + + // Old versions didn't quote, so need to support both :/ + if (s[0] == '\"') { + if (s.length() > 1 && s[s.length()-1] == '\"') { + ml.name = s.substr(1, s.length()-2); + } else { + ml.name = s.substr(1); + is >> s; + while (s[s.length()-1] != '\"') { + ml.name.append(" ").append(s); + is >> s; + } + ml.name.append(" ").append(s.substr(0, s.length()-1)); + } + } else { + ml.name = s; + } + + is >> s; + if (s == "input") ml.type = Input; + else if (s == "output") ml.type = Output; + else if (s == "inputoutput") ml.type = InputOutput; + else throw "Corrupt settings file."; + + is >> s; + ml.loc.x = atoi(s.c_str()); + is >> s; + ml.loc.y = atoi(s.c_str()); + + m_module_locations.push_back(ml); + } + + is.close(); +} + + +void +StateManager::save(const string& filename) +{ + std::ofstream os; + os.open(filename.c_str(), std::ios::out); + + os << "window_location " << m_window_location.x << " " << m_window_location.y << std::endl; + os << "window_size " << m_window_size.x << " " << m_window_size.y << std::endl; + os << "zoom_level " << m_zoom << std::endl; + + ModuleLocation ml; + for (std::list<ModuleLocation>::iterator i = m_module_locations.begin(); i != m_module_locations.end(); ++i) { + ml = *i; + os << "\"" << ml.name << "\""; + + if (ml.type == Input) os << " input "; + else if (ml.type == Output) os << " output "; + else if (ml.type == InputOutput) os << " inputoutput "; + else throw; + + os << ml.loc.x << " " << ml.loc.y << std::endl; + } + + os.close(); +} + + +Coord +StateManager::get_window_location() +{ + return m_window_location; +} + + +void +StateManager::set_window_location(Coord loc) +{ + m_window_location = loc; +} + + +Coord +StateManager::get_window_size() +{ + return m_window_size; +} + + +void +StateManager::set_window_size(Coord size) +{ + m_window_size = size; +} + + +float +StateManager::get_zoom() +{ + return m_zoom; +} + + +void +StateManager::set_zoom(float zoom) +{ + m_zoom = zoom; +} + + +int +StateManager::get_port_color(PortType type) +{ + if (type == JACK_AUDIO) + return 0x305171FF; + else if (type == JACK_MIDI) + return 0x663939FF; + else if (type == ALSA_MIDI) + return 0x307130FF; + else + return 0xFF0000FF; +} diff --git a/src/StateManager.h b/src/StateManager.h new file mode 100644 index 0000000..b584308 --- /dev/null +++ b/src/StateManager.h @@ -0,0 +1,75 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef STATEMANAGER_H +#define STATEMANAGER_H + +#include <string> +#include <list> +#include <map> +#include <iostream> +#include "PatchagePort.h" + +using std::string; using std::list; using std::map; + + +enum ModuleType { Input, Output, InputOutput }; +struct Coord { double x; double y; }; + +// This should probably be moved out in to a seperate class/file.... +typedef struct ModuleLocation +{ + string name; + ModuleType type; // for distinguishing terminal modules (input or output) + Coord loc; +}; + + +class StateManager +{ +public: + StateManager(); + + void load(const string& filename); + void save(const string& filename); + + Coord get_module_location(const string& name, ModuleType type); + void set_module_location(const string& name, ModuleType type, Coord loc); + + void set_module_split(const string& name, bool split); + bool get_module_split(const string& name, bool default_val) const; + + Coord get_window_location(); + void set_window_location(Coord loc); + + Coord get_window_size(); + void set_window_size(Coord loc); + + float get_zoom(); + void set_zoom(float zoom); + + int get_port_color(PortType type); + +private: + list<ModuleLocation> m_module_locations; + map<string,bool> m_module_splits; + Coord m_window_location; + Coord m_window_size; + float m_zoom; +}; + + +#endif // STATEMANAGER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5646b1c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,49 @@ +/* This file is part of Patchage. Copyright (C) 2005 Dave Robillard. + * + * Om 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. + * + * Om 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 this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include <iostream> +#include <libgnomecanvasmm.h> + +#include "Patchage.h" +#include "JackDriver.h" + +#ifdef HAVE_LASH +#include <lash/lash.h> +#endif // HAVE_LASH + +int main(int argc, char** argv) +{ + try { + + Gnome::Canvas::init(); + Gtk::Main app(argc, argv); + + Patchage patchage(argc, argv); + patchage.attach(); + + app.run(*patchage.window()); + + } catch (std::string msg) { + std::cerr << "Caught exception, aborting. Error message was: " << msg << std::endl; + return 1; + } + + return 0; +} + + diff --git a/src/patchage.glade b/src/patchage.glade new file mode 100644 index 0000000..322a714 --- /dev/null +++ b/src/patchage.glade @@ -0,0 +1,607 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkWindow" id="patchage_win"> + <property name="border_width">1</property> + <property name="visible">True</property> + <property name="title" translatable="yes">Patchage</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="default_width">640</property> + <property name="default_height">480</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus_on_map">True</property> + <property name="urgency_hint">False</property> + + <child> + <widget class="GtkVBox" id="main_vbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkMenuBar" id="menubar"> + <property name="visible">True</property> + <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property> + <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property> + + <child> + <widget class="GtkMenuItem" id="file_menu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_File</property> + <property name="use_underline">True</property> + + <child> + <widget class="GtkMenu" id="file_menu_menu"> + + <child> + <widget class="GtkImageMenuItem" id="launch_jack_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Launch Jack</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_launch_jack_menuitem_activate" last_modification_time="Wed, 05 Apr 2006 04:52:35 GMT"/> + <accelerator key="J" modifiers="GDK_CONTROL_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image143"> + <property name="visible">True</property> + <property name="stock">gtk-execute</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="connect_to_jack_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Connect to _Jack</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_connect_to_jack_menuitem_activate" last_modification_time="Wed, 05 Apr 2006 04:27:40 GMT"/> + <accelerator key="J" modifiers="GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image144"> + <property name="visible">True</property> + <property name="stock">gtk-connect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="disconnect_from_jack_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Disconnect from Jack</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_disconnect_from_jack1_activate" last_modification_time="Sun, 21 May 2006 23:48:26 GMT"/> + <accelerator key="J" modifiers="GDK_SHIFT_MASK | GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image145"> + <property name="visible">True</property> + <property name="stock">gtk-disconnect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkSeparatorMenuItem" id="separator1"> + <property name="visible">True</property> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="launch_lash_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Launch Lash</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_launch_lash1_activate" last_modification_time="Mon, 22 May 2006 01:14:33 GMT"/> + <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image146"> + <property name="visible">True</property> + <property name="stock">gtk-execute</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="connect_to_lash_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Connect to _Lash</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_connect_to_lash1_activate" last_modification_time="Mon, 22 May 2006 01:14:33 GMT"/> + <accelerator key="L" modifiers="GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image147"> + <property name="visible">True</property> + <property name="stock">gtk-connect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="disconnect_from_lash_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Disconnect from Lash</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_disconnect_from_lash1_activate" last_modification_time="Mon, 22 May 2006 01:14:33 GMT"/> + <accelerator key="L" modifiers="GDK_SHIFT_MASK | GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image148"> + <property name="visible">True</property> + <property name="stock">gtk-disconnect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkSeparatorMenuItem" id="separator4"> + <property name="visible">True</property> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="connect_to_alsa_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Connect to _Alsa</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_connect_to_alsa_menuitem_activate" last_modification_time="Mon, 22 May 2006 00:10:31 GMT"/> + <accelerator key="A" modifiers="GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image149"> + <property name="visible">True</property> + <property name="stock">gtk-connect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="disconnect_from_alsa_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Disconnect from Alsa</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_disconnect_from_alsa_menuitem_activate" last_modification_time="Mon, 22 May 2006 00:10:31 GMT"/> + <accelerator key="A" modifiers="GDK_SHIFT_MASK | GDK_MOD1_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image150"> + <property name="visible">True</property> + <property name="stock">gtk-disconnect</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkSeparatorMenuItem" id="separator3"> + <property name="visible">True</property> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="file_save_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Store Positions</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_save_settings1_activate" last_modification_time="Mon, 13 Sep 2004 02:42:14 GMT"/> + <accelerator key="S" modifiers="GDK_CONTROL_MASK" signal="activate"/> + + <child internal-child="image"> + <widget class="GtkImage" id="image151"> + <property name="visible">True</property> + <property name="stock">gtk-save</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkSeparatorMenuItem" id="separator2"> + <property name="visible">True</property> + </widget> + </child> + + <child> + <widget class="GtkImageMenuItem" id="file_quit_menuitem"> + <property name="visible">True</property> + <property name="label">gtk-quit</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_quit1_activate" last_modification_time="Sat, 11 Sep 2004 20:05:11 GMT"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="view_menu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_View</property> + <property name="use_underline">True</property> + + <child> + <widget class="GtkMenu" id="view_menu_menu"> + + <child> + <widget class="GtkImageMenuItem" id="view_refresh_menuitem"> + <property name="visible">True</property> + <property name="label">gtk-refresh</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_item1_activate" last_modification_time="Sat, 11 Sep 2004 20:05:50 GMT"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="help_menu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Help</property> + <property name="use_underline">True</property> + + <child> + <widget class="GtkMenu" id="help_menu_menu"> + + <child> + <widget class="GtkImageMenuItem" id="help_about_menuitem"> + <property name="visible">True</property> + <property name="label">gtk-about</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_about1_activate" last_modification_time="Sat, 11 Sep 2004 20:05:11 GMT"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkScrolledWindow" id="canvas_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkLabel" id="status_lab"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">2</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkSeparatorToolItem" id="separatortoolitem1"> + <property name="visible">True</property> + <property name="draw">True</property> + <property name="visible_horizontal">False</property> + <property name="visible_vertical">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Zoom: </property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHScale" id="zoom_scale"> + <property name="width_request">150</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="draw_value">True</property> + <property name="value_pos">GTK_POS_RIGHT</property> + <property name="digits">1</property> + <property name="update_policy">GTK_UPDATE_CONTINUOUS</property> + <property name="inverted">False</property> + <property name="adjustment">1 0.1 2 0 0 0</property> + </widget> + <packing> + <property name="padding">5</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">2</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + </child> +</widget> + +<widget class="GtkWindow" id="about_win"> + <property name="width_request">320</property> + <property name="height_request">200</property> + <property name="title" translatable="yes">About Patchage</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="modal">False</property> + <property name="resizable">False</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">True</property> + <property name="skip_pager_hint">True</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus_on_map">True</property> + <property name="urgency_hint">False</property> + + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkLabel" id="about_project_label"> + <property name="visible">True</property> + <property name="label" translatable="yes"><span size="xx-large" weight="bold">Patchage</span></property> + <property name="use_underline">False</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">A modular patch bay for: + +Jack Audio Connection Kit +ALSA Sequencer</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Copyright © 2005 Dave Robillard</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">10</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property> + <property name="spacing">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHButtonBox" id="hbuttonbox2"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkButton" id="about_close_button"> + <property name="width_request">66</property> + <property name="height_request">26</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-close</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/src/patchage.gladep b/src/patchage.gladep new file mode 100644 index 0000000..8d205c3 --- /dev/null +++ b/src/patchage.gladep @@ -0,0 +1,9 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd"> + +<glade-project> + <name>Patchage</name> + <program_name>patchage</program_name> + <language>C++</language> + <gnome_support>FALSE</gnome_support> +</glade-project> |