diff options
Diffstat (limited to 'src/libs/engine')
184 files changed, 21146 insertions, 0 deletions
diff --git a/src/libs/engine/AlsaMidiDriver.cpp b/src/libs/engine/AlsaMidiDriver.cpp new file mode 100644 index 00000000..decd2471 --- /dev/null +++ b/src/libs/engine/AlsaMidiDriver.cpp @@ -0,0 +1,373 @@ +/* This file is part of Om. Copyright (C) 2006 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 "AlsaMidiDriver.h" +#include <iostream> +#include <cstdlib> +#include <pthread.h> +#include "Om.h" +#include "OmApp.h" +#include "util/types.h" +#include "OmApp.h" +#include "Maid.h" +#include "AudioDriver.h" +#include "PortInfo.h" +#include "MidiMessage.h" +#include "PortBase.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +//// AlsaMidiPort //// + +AlsaMidiPort::AlsaMidiPort(AlsaMidiDriver* driver, PortBase<MidiMessage>* port) +: DriverPort(), + ListNode<AlsaMidiPort*>(this), + m_driver(driver), + m_patch_port(port), + m_port_id(0), + m_midi_pool(new unsigned char*[port->buffer_size()]), + m_events(1024) +{ + assert(port->parent() != NULL); + assert(port->poly() == 1); + + if (port->port_info()->is_input()) { + if ((m_port_id = snd_seq_create_simple_port(driver->seq_handle(), port->path().c_str(), + SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_APPLICATION)) < 0) + { + cerr << "[AlsaMidiPort] Error creating sequencer port." << endl; + exit(EXIT_FAILURE); + } + } else { + if ((m_port_id = snd_seq_create_simple_port(driver->seq_handle(), port->path().c_str(), + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_APPLICATION)) < 0) + { + cerr << "[AlsaMidiPort] Error creating sequencer port." << endl; + exit(EXIT_FAILURE); + } + } + + /* Allocate event pool. This pool is used when preparing a block from the queue + * of Alsa events. The buffer member of the MidiMessage's in the patch port's + * buffer will be set directly to an element in this pool, then next cycle they + * will be overwritten (eliminating the need for any allocation/freeing). */ + for (size_t i=0; i < port->buffer_size(); ++i) + m_midi_pool[i] = new unsigned char[MAX_MIDI_EVENT_SIZE]; + + port->buffer(0)->clear(); + port->fixed_buffers(true); +} + + +AlsaMidiPort::~AlsaMidiPort() +{ + snd_seq_delete_simple_port(m_driver->seq_handle(), m_port_id); + + // Free event pool + for (size_t i=0; i < m_patch_port->buffer_size(); ++i) + delete[] m_midi_pool[i]; + + delete[] m_midi_pool; +} + + +void +AlsaMidiPort::add_to_driver() +{ + m_driver->add_port(this); +} + + +void +AlsaMidiPort::remove_from_driver() +{ + m_driver->remove_port(this); +} + + +void +AlsaMidiPort::set_name(const string& name) +{ + snd_seq_port_info_t* info = NULL; + snd_seq_port_info_malloc(&info); + snd_seq_get_port_info(m_driver->seq_handle(), m_port_id, info); + snd_seq_port_info_set_name(info, name.c_str()); + snd_seq_set_port_info(m_driver->seq_handle(), m_port_id, info); + snd_seq_port_info_free(info); +} + + +void +AlsaMidiPort::event(snd_seq_event_t* const ev) +{ + // Abuse the tick field to hold the timestamp + ev->time.tick = om->audio_driver()->time_stamp(); + + // Fix noteons with velocity 0 (required for DSSI spec) + if (ev->type == SND_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0) + ev->type = SND_SEQ_EVENT_NOTEOFF; + + m_events.push(*ev); +} + + +/** Generates a flat array of MIDI events for patching. + * + * Prepares all events that occurred during the time interval passed + * (which ideally are the events from the previous cycle with an exact + * 1 cycle delay) and creates a flat port buffer for this cycle. + */ +void +AlsaMidiPort::prepare_block(const samplecount block_start, const samplecount block_end) +{ + assert(block_end >= block_start); + + snd_seq_event_t* ev = NULL; + MidiMessage* message = NULL; + size_t num_events = 0; + size_t event_size = 0; // decoded length of Alsa event in bytes + int timestamp = 0; + + while (!m_events.is_empty() && m_events.front().time.tick < block_end) { + assert(num_events < m_patch_port->buffer_size()); + ev = &m_events.front(); + message = &m_patch_port->buffer(0)->data()[num_events]; + + timestamp = ev->time.tick - block_start; + if (timestamp < 0) { + // FIXME: remove this (obviously not realtime safe) + cerr << "[AlsaMidiPort] Missed event by " << -timestamp << " samples!" << endl; + timestamp = 0; + } + assert(timestamp < (int)(block_end - block_start)); + + // Reset decoder so we don't get running status + snd_midi_event_reset_decode(m_driver->event_coder()); + + // FIXME: is this realtime safe? + if ((event_size = snd_midi_event_decode(m_driver->event_coder(), + m_midi_pool[num_events], MAX_MIDI_EVENT_SIZE, ev)) > 0) { + message->size = event_size; + message->time = timestamp; + message->buffer = m_midi_pool[num_events]; + ++num_events; + } else { + cerr << "[AlsaMidiPort] Unable to decode MIDI event" << endl; + } + + m_events.pop(); + } + + m_patch_port->buffer(0)->filled_size(num_events); + m_patch_port->tied_port()->buffer(0)->filled_size(num_events); +} + + + +//// AlsaMidiDriver //// + + +bool AlsaMidiDriver::m_midi_thread_exit_flag = true; + + +AlsaMidiDriver::AlsaMidiDriver() +: m_seq_handle(NULL), + m_event_coder(NULL), + m_is_activated(false) +{ + if (snd_seq_open(&m_seq_handle, "hw", SND_SEQ_OPEN_INPUT, 0) < 0) { + cerr << "[AlsaMidiDriver] Error opening ALSA sequencer." << endl; + exit(EXIT_FAILURE); + } else { + cout << "[AlsaMidiDriver] Successfully opened ALSA sequencer." << endl; + } + + if (snd_midi_event_new(3, &m_event_coder)) { + cerr << "[AlsaMidiDriver] Failed to initialize ALSA MIDI event coder!"; + exit(EXIT_FAILURE); + } else { + snd_midi_event_reset_encode(m_event_coder); + snd_midi_event_reset_decode(m_event_coder); + } + + snd_seq_set_client_name(m_seq_handle, "Om"); +} + + +AlsaMidiDriver::~AlsaMidiDriver() +{ + deactivate(); + snd_midi_event_free(m_event_coder); + snd_seq_close(m_seq_handle); +} + + +/** Launch and start the MIDI thread. + */ +void +AlsaMidiDriver::activate() +{ + // Just exit if already running + if (m_midi_thread_exit_flag == false) + return; + + bool success = false; + m_midi_thread_exit_flag = false; + + //if (om->audio_driver()->is_realtime()) { + pthread_attr_t attr; + pthread_attr_init(&attr); + + if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) { + cerr << "[AlsaMidiDriver] Unable to set realtime scheduling for MIDI thread." << endl; + } + + sched_param param; + param.sched_priority = 10; + + pthread_attr_setstacksize(&attr, 1500000); + + if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) + || pthread_attr_setschedparam(&attr, ¶m)) + cout << "[AlsaMidiDriver] Unable to set SCHED_FIFO priority " + << param.sched_priority << endl; + + if (!pthread_create(&m_process_thread, &attr, process_midi_in, this)) { + cout << "[AlsaMidiDriver] Started realtime MIDI thread (SCHED_FIFO, priority " + << param.sched_priority << ")" << endl; + success = true; + } else { + cerr << "[AlsaMidiDriver] Unable to start realtime MIDI thread." << endl; + } + pthread_attr_destroy(&attr); + //} + + if (!success) { + // FIXME: check for success + pthread_create(&m_process_thread, NULL, process_midi_in, this); + cout << "[AlsaMidiDriver] Started non-realtime MIDI thread." << endl; + } + +#ifdef HAVE_LASH + lash_driver->set_alsa_client_id(snd_seq_client_id(m_seq_handle)); +#endif + + m_is_activated = true; +} + + +/** Terminate the MIDI thread. + */ +void +AlsaMidiDriver::deactivate() +{ + if (m_is_activated) { + m_midi_thread_exit_flag = true; + pthread_cancel(m_process_thread); + pthread_join(m_process_thread, NULL); + m_is_activated = false; + } +} + + +/** Build flat arrays of events for DSSI plugins for each Port. + */ +void +AlsaMidiDriver::prepare_block(const samplecount block_start, const samplecount block_end) +{ + for (List<AlsaMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i) + (*i)->prepare_block(block_start, block_end); +} + + +/** Add an Alsa MIDI port. + * + * Realtime safe, this is to be called at the beginning of a process cycle to + * insert (and actually begin using) a new port. + * + * See create_port() and remove_port(). + */ +void +AlsaMidiDriver::add_port(AlsaMidiPort* port) +{ + if (port->patch_port()->port_info()->is_input()) + m_in_ports.push_back(port); + else + m_out_ports.push_back(port); +} + + +/** Remove an Alsa MIDI port. + * + * Realtime safe. This is to be called at the beginning of a process cycle to + * remove the port from the lists read by the audio thread, so the port + * will no longer be used and can be removed afterwards. + * + * It is the callers responsibility to delete the returned port. + */ +AlsaMidiPort* +AlsaMidiDriver::remove_port(AlsaMidiPort* port) +{ + if (port->patch_port()->port_info()->is_input()) { + for (List<AlsaMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i) + if ((*i) == (AlsaMidiPort*)port) + return m_in_ports.remove(i)->elem(); + } else { + for (List<AlsaMidiPort*>::iterator i = m_out_ports.begin(); i != m_out_ports.end(); ++i) + if ((*i) == port) + return m_out_ports.remove(i)->elem(); + } + + cerr << "[AlsaMidiDriver::remove_input] WARNING: Failed to find Jack port to remove!" << endl; + return NULL; +} + + +/** MIDI thread. + */ +void* +AlsaMidiDriver::process_midi_in(void* alsa_driver) +{ + AlsaMidiDriver* ad = (AlsaMidiDriver*)alsa_driver; + + snd_seq_event_t* ev; + + int npfd = snd_seq_poll_descriptors_count(ad->m_seq_handle, POLLIN); + struct pollfd pfd; + snd_seq_poll_descriptors(ad->m_seq_handle, &pfd, npfd, POLLIN); + + while ( ! m_midi_thread_exit_flag) + if (poll(&pfd, npfd, 100000) > 0) + while (snd_seq_event_input(ad->m_seq_handle, &ev) > 0) + for (List<AlsaMidiPort*>::iterator i = ad->m_in_ports.begin(); i != ad->m_in_ports.end(); ++i) + if ((*i)->port_id() == ev->dest.port) + (*i)->event(ev); + + cout << "[AlsaMidiDriver] Exiting MIDI thread." << endl; + + return NULL; +} + + +} // namespace Om + diff --git a/src/libs/engine/AlsaMidiDriver.h b/src/libs/engine/AlsaMidiDriver.h new file mode 100644 index 00000000..5acbbfbf --- /dev/null +++ b/src/libs/engine/AlsaMidiDriver.h @@ -0,0 +1,127 @@ +/* This file is part of Om. Copyright (C) 2006 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 ALSAMIDIDRIVER_H +#define ALSAMIDIDRIVER_H + +#include <alsa/asoundlib.h> +#include "List.h" +#include "util/Queue.h" +#include "MidiDriver.h" + +namespace Om { + +class Node; +class SetPortValueEvent; +class AlsaMidiDriver; +template <typename T> class PortBase; + +static const int MAX_MIDI_EVENT_SIZE = 3; + + +/** Representation of an ALSA MIDI port. + * + * \ingroup engine + */ +class AlsaMidiPort : public DriverPort, public ListNode<AlsaMidiPort*> +{ +public: + AlsaMidiPort(AlsaMidiDriver* driver, PortBase<MidiMessage>* port); + virtual ~AlsaMidiPort(); + + void event(snd_seq_event_t* const ev); + + void prepare_block(const samplecount block_start, const samplecount block_end); + + void add_to_driver(); + void remove_from_driver(); + void set_name(const string& name); + + int port_id() const { return m_port_id; } + PortBase<MidiMessage>* patch_port() const { return m_patch_port; } + +private: + // Prevent copies (undefined) + AlsaMidiPort(const AlsaMidiPort&); + AlsaMidiPort& operator=(const AlsaMidiPort&); + + AlsaMidiDriver* m_driver; + PortBase<MidiMessage>* m_patch_port; + int m_port_id; + unsigned char** m_midi_pool; ///< Pool of raw MIDI events for MidiMessage::buffer + Queue<snd_seq_event_t> m_events; +}; + + +/** Alsa MIDI driver. + * + * This driver reads Alsa MIDI events and dispatches them to the appropriate + * AlsaMidiPort for processing. + * + * \ingroup engine + */ +class AlsaMidiDriver : public MidiDriver +{ +public: + AlsaMidiDriver(); + ~AlsaMidiDriver(); + + void activate(); + void deactivate(); + + bool is_activated() const { return m_is_activated; } + + void prepare_block(const samplecount block_start, const samplecount block_end); + + DriverPort* create_port(PortBase<MidiMessage>* patch_port) + { return new AlsaMidiPort(this, patch_port); } + + snd_seq_t* seq_handle() const { return m_seq_handle; } + snd_midi_event_t* event_coder() const { return m_event_coder; } + +private: + + // Prevent copies (undefined) + AlsaMidiDriver(const AlsaMidiDriver&); + AlsaMidiDriver& operator=(const AlsaMidiDriver&); + + List<AlsaMidiPort*> m_in_ports; + List<AlsaMidiPort*> m_out_ports; + + friend class AlsaMidiPort; + + // Functions for AlsaMidiPort + void add_port(AlsaMidiPort* port); + AlsaMidiPort* remove_port(AlsaMidiPort* port); + + void add_output(ListNode<AlsaMidiPort*>* port); + ListNode<AlsaMidiPort*>* remove_output(AlsaMidiPort* port); + + // MIDI thread + static void* process_midi_in(void* me); + + snd_seq_t* m_seq_handle; + snd_midi_event_t* m_event_coder; + pthread_t m_process_thread; + bool m_is_activated; + static bool m_midi_thread_exit_flag; +}; + + +} // namespace Om + + +#endif // ALSAMIDIDRIVER_H diff --git a/src/libs/engine/Array.h b/src/libs/engine/Array.h new file mode 100644 index 00000000..6b56ecc5 --- /dev/null +++ b/src/libs/engine/Array.h @@ -0,0 +1,109 @@ +/* This file is part of Om. Copyright (C) 2006 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 ARRAY_H +#define ARRAY_H + +#include "MaidObject.h" +#include <cassert> +#include <cstdlib> +#include "util/types.h" + + +/** An array. + * + * Has a stack-like push_back() too, for find_process_order... + */ +template <class T> +class Array : public MaidObject +{ +public: + Array(size_t size = 0) : m_size(size), m_top(0), m_elems(NULL) { + if (size > 0) + m_elems = new T[size]; + } + + Array(size_t size, T initial_value) : m_size(size), m_top(0), m_elems(NULL) { + if (size > 0) { + m_elems = new T[size]; + for (size_t i=0; i < size; ++i) + m_elems[i] = initial_value; + } + } + + Array(size_t size, const Array<T>* contents) : m_size(size), m_top(size+1) { + m_elems = new T[size]; + if (size <= contents->size()) + memcpy(m_elems, contents->m_elems, size * sizeof(T)); + else + memcpy(m_elems, contents->m_elems, contents->size() * sizeof(T)); + } + + ~Array() { + free(); + } + + void alloc(size_t num_elems) { + assert(num_elems > 0); + + delete[] m_elems; + m_size = num_elems; + m_top = 0; + + m_elems = new T[num_elems]; + } + + void alloc(size_t num_elems, T initial_value) { + assert(num_elems > 0); + + delete[] m_elems; + m_size = num_elems; + m_top = 0; + + m_elems = new T[num_elems]; + for (size_t i=0; i < m_size; ++i) + m_elems[i] = initial_value; + } + + void free() { + delete[] m_elems; + m_size = 0; + m_top = 0; + } + + void push_back(T n) { + assert(m_top < m_size); + m_elems[m_top++] = n; + } + + inline size_t size() const { return m_size; } + + inline T& operator[](size_t i) const { assert(i < m_size); return m_elems[i]; } + + inline T& at(size_t i) const { assert(i < m_size); return m_elems[i]; } + +private: + // Disallow copies (undefined) + Array(const Array& copy); + Array& operator=(const Array& copy); + + size_t m_size; + size_t m_top; // points to empty element above "top" element + T* m_elems; +}; + + +#endif // ARRAY_H diff --git a/src/libs/engine/AudioDriver.h b/src/libs/engine/AudioDriver.h new file mode 100644 index 00000000..056aeab4 --- /dev/null +++ b/src/libs/engine/AudioDriver.h @@ -0,0 +1,50 @@ +/* This file is part of Om. Copyright (C) 2006 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 AUDIODRIVER_H +#define AUDIODRIVER_H + +#include "Driver.h" +#include "util/types.h" +#include "List.h" + +namespace Om { + +class Patch; +class AudioDriver; +template <typename T> class PortBase; + + +/** Audio driver abstract base class. + * + * \ingroup engine + */ +class AudioDriver : public Driver<sample> +{ +public: + + virtual void set_root_patch(Patch* patch) = 0; + virtual Patch* root_patch() = 0; + + virtual samplecount buffer_size() const = 0; + virtual samplecount sample_rate() const = 0; + virtual samplecount time_stamp() const = 0; +}; + + +} // namespace Om + +#endif // AUDIODRIVER_H diff --git a/src/libs/engine/AudioInputNode.cpp b/src/libs/engine/AudioInputNode.cpp new file mode 100644 index 00000000..8a3594cb --- /dev/null +++ b/src/libs/engine/AudioInputNode.cpp @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 "AudioInputNode.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Plugin.h" +#include "PortInfo.h" +#include "Patch.h" + +namespace Om { + + +AudioInputNode::AudioInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<sample>(path, poly, parent, srate, buffer_size) +{ + OutputPort<sample>* internal_port = new OutputPort<sample>(this, "in", 0, m_poly, + new PortInfo("in", AUDIO, OUTPUT), m_buffer_size); + InputPort<sample>* external_port = new InputPort<sample>(parent, m_name, 0, m_poly, + new PortInfo(m_name, AUDIO, INPUT), m_buffer_size); + external_port->tie(internal_port); + m_external_port = external_port; + internal_port->set_value(0, 0); + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("audio_input"); + m_plugin.name("Om Patch Audio Input Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/AudioInputNode.h b/src/libs/engine/AudioInputNode.h new file mode 100644 index 00000000..894ec082 --- /dev/null +++ b/src/libs/engine/AudioInputNode.h @@ -0,0 +1,45 @@ +/* This file is part of Om. Copyright (C) 2006 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 AUDIOINPUTNODE_H +#define AUDIOINPUTNODE_H + +#include <string> +#include "util/types.h" +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +class Patch; +template <typename T> class InputPort; + + +/** Audio input BridgeNode. + * + * \ingroup engine + */ +class AudioInputNode : public BridgeNode<sample> +{ +public: + AudioInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // AUDIOINPUTNODE_H diff --git a/src/libs/engine/AudioOutputNode.cpp b/src/libs/engine/AudioOutputNode.cpp new file mode 100644 index 00000000..d9a925f8 --- /dev/null +++ b/src/libs/engine/AudioOutputNode.cpp @@ -0,0 +1,50 @@ +/* This file is part of Om. Copyright (C) 2006 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 "AudioOutputNode.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Plugin.h" +#include "Patch.h" +#include "PortInfo.h" +#include <cassert> + +namespace Om { + + +AudioOutputNode::AudioOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<sample>(path, poly, parent, srate, buffer_size) +{ + OutputPort<sample>* external_port = new OutputPort<sample>(parent, m_name, 0, m_poly, + new PortInfo(m_name, AUDIO, OUTPUT), m_buffer_size); + InputPort<sample>* internal_port = new InputPort<sample>(this, "out", 0, m_poly, + new PortInfo("out", AUDIO, INPUT), m_buffer_size); + internal_port->tie(external_port); + m_external_port = external_port; + internal_port->set_value(0, 0); + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("audio_output"); + m_plugin.name("Om Patch Audio Output Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/AudioOutputNode.h b/src/libs/engine/AudioOutputNode.h new file mode 100644 index 00000000..6399cfc6 --- /dev/null +++ b/src/libs/engine/AudioOutputNode.h @@ -0,0 +1,44 @@ +/* This file is part of Om. Copyright (C) 2006 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 AUDIOOUTPUTNODE_H +#define AUDIOOUTPUTNODE_H + +#include <string> +#include "util/types.h" +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +template <typename T> class OutputPort; + + +/** Audio output BridgeNode. + * + * \ingroup engine + */ +class AudioOutputNode : public BridgeNode<sample> +{ +public: + AudioOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // AUDIOOUTPUTNODE_H diff --git a/src/libs/engine/BridgeNode.cpp b/src/libs/engine/BridgeNode.cpp new file mode 100644 index 00000000..776bcc92 --- /dev/null +++ b/src/libs/engine/BridgeNode.cpp @@ -0,0 +1,159 @@ +/* This file is part of Om. Copyright (C) 2006 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 "BridgeNode.h" +//#include "ClientBroadcaster.h" +#include "Plugin.h" +#include "Patch.h" +#include "Om.h" +#include "OmApp.h" +#include "Maid.h" +#include "Driver.h" +#include "PortInfo.h" +#include <cassert> + +namespace Om { + +template <typename T> +BridgeNode<T>::BridgeNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: InternalNode(path, + (parent->parent_patch() == NULL || poly != parent->parent_patch()->poly()) ? 1 : poly, + //poly, + parent, srate, buffer_size), + m_driver_port(NULL), + m_listnode(NULL), + m_external_port(NULL) +{ + //cerr << "Creating bridge node " << path << " - polyphony: " << m_poly << endl; + m_listnode = new ListNode<InternalNode*>(this); +} +template +BridgeNode<sample>::BridgeNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +template +BridgeNode<MidiMessage>::BridgeNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + +template <typename T> +BridgeNode<T>::~BridgeNode() +{ + delete m_driver_port; +} +template BridgeNode<sample>::~BridgeNode(); +template BridgeNode<MidiMessage>::~BridgeNode(); + + +template <typename T> +void +BridgeNode<T>::activate() +{ + assert(om->template driver<T>() != NULL); + assert(m_external_port != NULL); // Derived classes must create this + assert(parent_patch() != NULL); + + if (parent_patch()->parent() == NULL && om != NULL) + m_driver_port = om->template driver<T>()->create_port(m_external_port); + + InternalNode::activate(); +} + + +template <typename T> +void +BridgeNode<T>::deactivate() +{ + if (m_is_added) + remove_from_patch(); + + InternalNode::deactivate(); + + if (m_driver_port != NULL) { + delete m_driver_port; + m_driver_port = NULL; + } +} + + +template <typename T> +void +BridgeNode<T>::add_to_patch() +{ + assert(parent_patch() != NULL); + + parent_patch()->add_bridge_node(m_listnode); + + InternalNode::add_to_patch(); + + // Activate driver port now in the audio thread (not before when created, to avoid race issues) + if (m_driver_port != NULL) + m_driver_port->add_to_driver(); +} + + +template <typename T> +void +BridgeNode<T>::remove_from_patch() +{ + assert(parent_patch() != NULL); + + if (m_is_added) { + if (m_driver_port != NULL) + m_driver_port->remove_from_driver(); + ListNode<InternalNode*>* ln = NULL; + ln = parent_patch()->remove_bridge_node(this); + + om->maid()->push(ln); + m_listnode = NULL; + + } + InternalNode::remove_from_patch(); +} + + +template <typename T> +void +BridgeNode<T>::set_path(const Path& new_path) +{ + InternalNode::set_path(new_path); + + m_external_port->set_path(new_path); + + if (m_driver_port != NULL) + m_driver_port->set_name(path().c_str()); +} + + +#if 0 +template <typename T> +void +BridgeNode<T>::send_creation_messages(ClientInterface* client) const +{ + InternalNode::send_creation_messages(client); + om->client_broadcaster()->send_new_port_to(client, m_external_port); + + // Send metadata + for (map<string, string>::const_iterator i = metadata().begin(); i != metadata().end(); ++i) + om->client_broadcaster()->send_metadata_update_to(client, path(), (*i).first, (*i).second); + + // Send control value (if necessary) + //if (m_external_port->port_info()->is_control()) + // om->client_broadcaster()->send_control_change_to(client, path(), + // ((PortBase<sample>*)m_external_port)->buffer(0)->value_at(0)); +} +#endif + + +} // namespace Om + diff --git a/src/libs/engine/BridgeNode.h b/src/libs/engine/BridgeNode.h new file mode 100644 index 00000000..99fad912 --- /dev/null +++ b/src/libs/engine/BridgeNode.h @@ -0,0 +1,90 @@ +/* This file is part of Om. Copyright (C) 2006 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 BRIDGENODE_H +#define BRIDGENODE_H + +#include <string> +#include "InternalNode.h" +#include "PortBase.h" +using std::string; + +template <typename T> class ListNode; + +namespace Om { + +class DriverPort; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A Node to represent input/output Ports on a Patch. + * + * This node acts as both a Node and a Port (it is the only Node type that + * returns non-null for as_port()). The node is a normal Node in a Patch, + * the Port is a Port on that Patch (port->parent == node->parent). + * + * This class handles all DriverPort functionality as well (if this Node + * is on a top level Patch). + * + * Both input and output nodes are handled in this class. + * + * This node will force itself to monophonic (regardless of the poly parameter + * passed to constructor) if the parent of the patch it's representing a port + * on has a different polyphony than the patch (since connecting mismatched + * polyphonies is impossible). + * + * \ingroup engine + */ +template <typename T> +class BridgeNode : public InternalNode +{ +public: + virtual ~BridgeNode(); + + Port* as_port() { return m_external_port; } + + void activate(); + void deactivate(); + void add_to_patch(); + void remove_from_patch(); + //void send_creation_messages(ClientInterface* client) const; + + void set_path(const Path& new_path); + +protected: + // Disallow copies (undefined) + BridgeNode(const BridgeNode&); + BridgeNode& operator=(const BridgeNode&); + + BridgeNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + /** Driver port, used if this is on a top level Patch */ + DriverPort* m_driver_port; + + ListNode<InternalNode*>* m_listnode; + + PortBase<T>* m_external_port; +}; + + +template class BridgeNode<sample>; +template class BridgeNode<MidiMessage>; + +} // namespace Om + +#endif // BRIDGENODE_H diff --git a/src/libs/engine/Buffer.cpp b/src/libs/engine/Buffer.cpp new file mode 100644 index 00000000..963b14a5 --- /dev/null +++ b/src/libs/engine/Buffer.cpp @@ -0,0 +1,297 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Buffer.h" +#include <iostream> +#include <cassert> +#include <stdlib.h> +#include "MidiMessage.h" +using std::cerr; using std::endl; + +/* TODO: Be sure these functions are vectorized by GCC when it's vectorizer + * stops sucking. Probably a good idea to inline them as well */ + +namespace Om { + + +template <typename T> +Buffer<T>::Buffer(size_t size) +: m_size(size), + m_filled_size(0), + m_is_joined(false), + m_state(OK), + m_set_value(0), + m_data(NULL), + m_local_data(NULL) +{ + assert(m_size > 0); + allocate(); +} +template Buffer<sample>::Buffer(size_t size); +template Buffer<MidiMessage>::Buffer(size_t size); + + +/** Allocate and use a locally managed buffer (data). + */ +template<typename T> +void +Buffer<T>::allocate() +{ + assert(!m_is_joined); + assert(m_data == NULL); + assert(m_local_data == NULL); + assert(m_size > 0); + + const int ret = posix_memalign((void**)&m_local_data, 16, m_size * sizeof(T)); + if (ret != 0) { + cerr << "[Buffer] Failed to allocate buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + assert(ret == 0); + assert(m_local_data != NULL); + m_data = m_local_data; + + set(0, 0, m_size-1); +} +template void Buffer<sample>::allocate(); +template void Buffer<MidiMessage>::allocate(); + + +/** Free locally allocated buffer. + */ +template<typename T> +void +Buffer<T>::deallocate() +{ + assert(!m_is_joined); + free(m_local_data); + if (m_data == m_local_data) + m_data = NULL; + m_local_data = NULL; +} +template void Buffer<sample>::deallocate(); +template void Buffer<MidiMessage>::deallocate(); + + +/** Empty (ie zero) the buffer. + */ +template<typename T> +void +Buffer<T>::clear() +{ + set(0, 0, m_size-1); + m_state = OK; + m_filled_size = 0; +} +template void Buffer<sample>::clear(); +template void Buffer<MidiMessage>::clear(); + + +/** Set value of buffer to @a val after @a start_sample. + * + * The Buffer will handle setting the intial portion of the buffer to the + * value on the next cycle automatically (if @a start_sample is > 0), as + * long as pre_process() is called every cycle. + */ +template <typename T> +void +Buffer<T>::set(T val, size_t start_sample) +{ + assert(start_sample < m_size); + + set(val, start_sample, m_size-1); + + if (start_sample > 0) + m_state = HALF_SET_CYCLE_1; + + m_set_value = val; +} +template void Buffer<sample>::set(sample val, size_t start_sample); +template void Buffer<MidiMessage>::set(MidiMessage val, size_t start_sample); + + +/** Set a block of buffer to @a val. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + */ +template <typename T> +void +Buffer<T>::set(T val, size_t start_sample, size_t end_sample) +{ + assert(start_sample >= 0); + assert(end_sample >= start_sample); + assert(end_sample < m_size); + assert(m_data != NULL); + + for (size_t i=start_sample; i <= end_sample; ++i) + m_data[i] = val; +} +template void Buffer<sample>::set(sample val, size_t start_sample, size_t end_sample); +template void Buffer<MidiMessage>::set(MidiMessage val, size_t start_sample, size_t end_sample); + + +/** Scale a block of buffer by @a val. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + */ +template <typename T> +void +Buffer<T>::scale(T val, size_t start_sample, size_t end_sample) +{ + assert(start_sample >= 0); + assert(end_sample >= start_sample); + assert(end_sample < m_size); + assert(m_data != NULL); + + for (size_t i=start_sample; i <= end_sample; ++i) + m_data[i] *= val; +} +template void Buffer<sample>::scale(sample val, size_t start_sample, size_t end_sample); + + +/** Copy a block of @a src into buffer. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + * This function only copies the same range in one buffer to another. + */ +template <typename T> +void +Buffer<T>::copy(const Buffer<T>* src, size_t start_sample, size_t end_sample) +{ + assert(start_sample >= 0); + assert(end_sample >= start_sample); + assert(end_sample < m_size); + assert(src != NULL); + assert(src->data() != NULL); + assert(m_data != NULL); + + const T* const src_data = src->data(); + + for (size_t i=start_sample; i <= end_sample; ++i) + m_data[i] = src_data[i]; +} +template void Buffer<sample>::copy(const Buffer<sample>* const src, size_t start_sample, size_t end_sample); +template void Buffer<MidiMessage>::copy(const Buffer<MidiMessage>* const src, size_t start_sample, size_t end_sample); + + +/** Accumulate a block of @a src into @a dst. + * + * @a start_sample and @a end_sample define the inclusive range to be accumulated. + * This function only adds the same range in one buffer to another. + */ +template <typename T> +void +Buffer<T>::accumulate(const Buffer<T>* const src, size_t start_sample, size_t end_sample) +{ + assert(start_sample >= 0); + assert(end_sample >= start_sample); + assert(end_sample < m_size); + assert(src != NULL); + assert(src->data() != NULL); + assert(m_data != NULL); + + const T* const src_data = src->data(); + + for (size_t i=start_sample; i <= end_sample; ++i) + m_data[i] += src_data[i]; + +} +template void Buffer<sample>::accumulate(const Buffer<sample>* const src, size_t start_sample, size_t end_sample); + + +/** Use another buffer's data instead of the local one. + * + * This buffer will essentially be identical to @a buf after this call. + */ +template<typename T> +void +Buffer<T>::join(Buffer<T>* buf) +{ + assert(buf->size() == m_size); + + m_data = buf->m_data; + m_filled_size = buf->filled_size(); + m_is_joined = true; + + assert(m_filled_size <= m_size); +} +template void Buffer<sample>::join(Buffer<sample>* buf); +template void Buffer<MidiMessage>::join(Buffer<MidiMessage>* buf); + + +template<typename T> +void +Buffer<T>::unjoin() +{ + m_is_joined = false; + m_data = m_local_data; +} +template void Buffer<sample>::unjoin(); +template void Buffer<MidiMessage>::unjoin(); + + +template<> +void +Buffer<sample>::prepare(samplecount nframes) +{ + // FIXME: nframes parameter doesn't actually work, + // writing starts from 0 every time + assert(m_size == 1 || nframes == m_size); + + switch (m_state) { + case HALF_SET_CYCLE_1: + m_state = HALF_SET_CYCLE_2; + break; + case HALF_SET_CYCLE_2: + set(m_set_value, 0, m_size-1); + m_state = OK; + break; + default: + break; + } +} + + +////// DriverBuffer //////// + +template <typename T> +DriverBuffer<T>::DriverBuffer(size_t size) +: Buffer<T>(size) +{ + Buffer<T>::deallocate(); // FIXME: allocate then immediately deallocate, dirty + Buffer<T>::m_data = NULL; +} +template DriverBuffer<sample>::DriverBuffer(size_t size); +template DriverBuffer<MidiMessage>::DriverBuffer(size_t size); + + +/** Set the buffer (data) used. + * + * This is only to be used by Drivers (to provide zero-copy processing). + */ +template<typename T> +void +DriverBuffer<T>::set_data(T* data) +{ + assert(!m_is_joined); + m_data = data; +} +template void DriverBuffer<sample>::set_data(sample* data); +template void DriverBuffer<MidiMessage>::set_data(MidiMessage* data); + + +} // namespace Om diff --git a/src/libs/engine/Buffer.h b/src/libs/engine/Buffer.h new file mode 100644 index 00000000..13d62727 --- /dev/null +++ b/src/libs/engine/Buffer.h @@ -0,0 +1,91 @@ +/* This file is part of Om. Copyright (C) 2006 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 BUFFER_H +#define BUFFER_H + +#include <cstddef> +#include <cassert> +#include "util/types.h" + +namespace Om { + + +template <typename T> +class Buffer +{ +public: + Buffer(size_t size); + + void clear(); + void set(T val, size_t start_sample); + void set(T val, size_t start_sample, size_t end_sample); + void scale(T val, size_t start_sample, size_t end_sample); + void copy(const Buffer<T>* src, size_t start_sample, size_t end_sample); + void accumulate(const Buffer<T>* src, size_t start_sample, size_t end_sample); + + void join(Buffer* buf); + void unjoin(); + + inline T& value_at(size_t offset) { assert(offset < m_size); return m_data[offset]; } + + void prepare(samplecount nframes); + + void filled_size(size_t size) { m_filled_size = size; } + size_t filled_size() const { return m_filled_size; } + bool is_joined() const { return m_is_joined; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + +protected: + enum BufferState { OK, HALF_SET_CYCLE_1, HALF_SET_CYCLE_2 }; + + void allocate(); + void deallocate(); + + size_t m_size; ///< Allocated buffer size + size_t m_filled_size; ///< Usable buffer size (for MIDI ports etc) + bool m_is_joined; ///< Whether or not @ref m_data is shares with another Buffer + BufferState m_state; ///< State of buffer for setting values next cycle + T m_set_value; ///< Value set by @ref set (may need to be set next cycle) + + T* m_data; ///< Buffer to be returned by data() (not equal to m_local_data if joined) + T* m_local_data; ///< Locally allocated buffer +}; + + +/** Less robust Buffer for Driver's use. + * + * Does not allocate an array by default, and allows direct setting of + * data pointer for zero-copy processing. + */ +template <typename T> +class DriverBuffer : public Buffer<T> +{ +public: + DriverBuffer(size_t size); + + void set_data(T* data); + +private: + using Buffer<T>::m_data; + using Buffer<T>::m_is_joined; +}; + + +} // namespace Om + +#endif // BUFFER_H diff --git a/src/libs/engine/ClientBroadcaster.cpp b/src/libs/engine/ClientBroadcaster.cpp new file mode 100644 index 00000000..f8823233 --- /dev/null +++ b/src/libs/engine/ClientBroadcaster.cpp @@ -0,0 +1,331 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ClientBroadcaster.h" +#include <cassert> +#include <iostream> +#include <unistd.h> +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "NodeFactory.h" +#include "util.h" +#include "Patch.h" +#include "Node.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "PortBase.h" +#include "Connection.h" +#include "AudioDriver.h" +#include "ObjectSender.h" +#include "interface/ClientKey.h" +#include "interface/ClientInterface.h" +#include "OSCClient.h" +using std::cout; using std::cerr; using std::endl; +using Om::Shared::ClientInterface; + +namespace Om { + + +/** Register a client to receive messages over the notification band. + */ +void +ClientBroadcaster::register_client(const ClientKey key, CountedPtr<ClientInterface> client) +{ + assert(key.type() == ClientKey::OSC_URL); + assert(key.uri() != ""); + + bool found = false; + for (ClientList::iterator i = _clients.begin(); i != _clients.end(); ++i) + if ((*i).first == key) + found = true; + + if (!found) { + _clients.push_back(pair<ClientKey, CountedPtr<ClientInterface> >(key, client)); + cout << "[ClientBroadcaster] Registered client " << key.uri() + << " (" << _clients.size() << " clients)" << endl; + } else { + cout << "[ClientBroadcaster] Client already registered." << endl; + } +} + + +/** Remove a client from the list of registered clients. + * + * The removed client is returned (not deleted). It is the caller's + * responsibility to delete the returned pointer, if it's not NULL. + * + * @return true if client was found and removed (and refcount decremented). + */ +bool +ClientBroadcaster::unregister_client(const ClientKey& key) +{ + cerr << "FIXME: unregister broken\n"; + return false; + +#if 0 + if (responder == NULL) + return NULL; + + // FIXME: remove filthy cast + const string url = lo_address_get_url(((OSCResponder*)responder)->source()); + ClientInterface* r = NULL; + + for (ClientList::iterator i = _clients.begin(); i != _clients.end(); ++i) { + if ((*i).second->url() == url) { + r = *i; + _clients.erase(i); + break; + } + } + + if (r != NULL) + cout << "[OSC] Unregistered client " << r->url() << " (" << _clients.size() << " clients)" << endl; + else + cerr << "[OSC] ERROR: Unable to find client to unregister!" << endl; + + return r; +#endif +} + + + +/** Looks up the client with the given @a source address (which is used as the + * unique identifier for registered clients). + * + * (A responder is passed to remove the dependency on liblo addresses in request + * events, in anticipation of libom and multiple ways of responding to clients). + */ +CountedPtr<ClientInterface> +ClientBroadcaster::client(const ClientKey& key) +{ + for (ClientList::iterator i = _clients.begin(); i != _clients.end(); ++i) + if ((*i).first == key) + return (*i).second; + + cerr << "[ClientBroadcaster] Failed to find client." << endl; + + return NULL; +} + + +void +ClientBroadcaster::send_error(const string& msg) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->error(msg); +} + + + +void +ClientBroadcaster::send_plugins_to(ClientInterface* client) +{ + om->node_factory()->lock_plugin_list(); + + const list<Plugin*>& plugs = om->node_factory()->plugins(); + const Plugin* plugin; + + lo_timetag tt; + lo_timetag_now(&tt); + lo_bundle b = lo_bundle_new(tt); + lo_message m = lo_message_new(); + list<lo_message> msgs; + + lo_message_add_int32(m, plugs.size()); + lo_bundle_add_message(b, "/om/num_plugins", m); + msgs.push_back(m); + + for (list<Plugin*>::const_iterator j = plugs.begin(); j != plugs.end(); ++j) { + plugin = (*j); + m = lo_message_new(); + + lo_message_add_string(m, plugin->type_string()); + lo_message_add_string(m, plugin->uri().c_str()); + lo_message_add_string(m, plugin->name().c_str()); + lo_bundle_add_message(b, "/om/plugin", m); + msgs.push_back(m); + if (lo_bundle_length(b) > 1024) { + // FIXME FIXME FIXME dirty, dirty cast + lo_send_bundle(((OSCClient*)client)->address(), b); + lo_bundle_free(b); + b = lo_bundle_new(tt); + } + } + + if (lo_bundle_length(b) > 0) { + // FIXME FIXME FIXME dirty, dirty cast + lo_send_bundle(((OSCClient*)client)->address(), b); + lo_bundle_free(b); + } else { + lo_bundle_free(b); + } + for (list<lo_bundle>::const_iterator i = msgs.begin(); i != msgs.end(); ++i) + lo_message_free(*i); + + om->node_factory()->unlock_plugin_list(); +} + + +void +ClientBroadcaster::send_node(const Node* node) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_node((*i).second.get(), node); +} + + +void +ClientBroadcaster::send_port(const Port* port) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_port((*i).second.get(), port); +} + + +void +ClientBroadcaster::send_destroyed(const string& path) +{ + assert(path != "/"); + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->object_destroyed(path); +} + +void +ClientBroadcaster::send_patch_cleared(const string& patch_path) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->patch_cleared(patch_path); +} + +void +ClientBroadcaster::send_connection(const Connection* const c) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->connection(c->src_port()->path(), c->dst_port()->path()); +} + + +void +ClientBroadcaster::send_disconnection(const string& src_port_path, const string& dst_port_path) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->disconnection(src_port_path, dst_port_path); +} + + +void +ClientBroadcaster::send_patch_enable(const string& patch_path) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->patch_enabled(patch_path); +} + + +void +ClientBroadcaster::send_patch_disable(const string& patch_path) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->patch_disabled(patch_path); +} + + +/** Send notification of a metadata update. + * + * Like control changes, does not send update to client that set the metadata, if applicable. + */ +void +ClientBroadcaster::send_metadata_update(const string& node_path, const string& key, const string& value) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->metadata_update(node_path, key, value); +} + + +/** Send notification of a control change. + * + * If responder is specified, the notification will not be send to the address of + * that responder (to avoid sending redundant information back to clients and + * forcing clients to ignore things to avoid feedback loops etc). + */ +void +ClientBroadcaster::send_control_change(const string& port_path, float value) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->control_change(port_path, value); +} + + +void +ClientBroadcaster::send_program_add(const string& node_path, int bank, int program, const string& name) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->program_add(node_path, bank, program, name); +} + + +void +ClientBroadcaster::send_program_remove(const string& node_path, int bank, int program) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->program_remove(node_path, bank, program); +} + + +/** Send a patch. + * + * Sends all objects underneath Patch - contained Nodes, etc. + */ +void +ClientBroadcaster::send_patch(const Patch* const p) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_patch((*i).second.get(), p); +} + + +/** Sends notification of an OmObject's renaming + */ +void +ClientBroadcaster::send_rename(const string& old_path, const string& new_path) +{ + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->object_renamed(old_path, new_path); +} + + +/** Sends all OmObjects known to the engine. + */ +void +ClientBroadcaster::send_all_objects() +{ + cerr << "FIXME: send_all" << endl; + + //for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + // (*i).second->send_all_objects(); +} + +/* +void +ClientBroadcaster::send_node_creation_messages(const Node* const node) +{ + // This is pretty stupid :/ in and out and back again! + for (ClientList::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + node->send_creation_messages((*i).second); +} +*/ + +} // namespace Om diff --git a/src/libs/engine/ClientBroadcaster.h b/src/libs/engine/ClientBroadcaster.h new file mode 100644 index 00000000..b0a60f57 --- /dev/null +++ b/src/libs/engine/ClientBroadcaster.h @@ -0,0 +1,101 @@ +/* This file is part of Om. Copyright (C) 2006 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 CLIENTBROADCASTER_H +#define CLIENTBROADCASTER_H + +#include <string> +#include <iostream> +#include <utility> +#include <list> +#include <lo/lo.h> +#include <pthread.h> +#include "util/types.h" +#include "interface/ClientInterface.h" +#include "util/CountedPtr.h" + +using std::list; using std::string; using std::pair; + +namespace Om { + +class Node; +class Port; +class PortInfo; +class Plugin; +class Patch; +class Connection; +class Responder; +namespace Shared { class ClientKey; } +using Shared::ClientKey; +using Shared::ClientInterface; + +/** Broadcaster for all clients. + * + * This sends messages to all client simultaneously through the opaque + * ClientInterface. The clients may be OSC driver, in process, theoretically + * anything that implements ClientInterface. + * + * This also serves as the database of all registered clients. + * + * \ingroup engine + */ +class ClientBroadcaster +{ +public: + void register_client(const ClientKey key, CountedPtr<ClientInterface> client); + bool unregister_client(const ClientKey& key); + + CountedPtr<ClientInterface> client(const ClientKey& key); + + // Notification band: + + //void send_client_registration(const string& url, int client_id); + + // Error that isn't the direct result of a request + void send_error(const string& msg); + + void send_plugins_to(ClientInterface* client); + + //void send_node_creation_messages(const Node* const node); + + void send_patch(const Patch* const p); + void send_node(const Node* const node); + void send_port(const Port* port); + void send_destroyed(const string& path); + void send_patch_cleared(const string& patch_path); + void send_connection(const Connection* const connection); + void send_disconnection(const string& src_port_path, const string& dst_port_path); + void send_rename(const string& old_path, const string& new_path); + void send_all_objects(); + void send_patch_enable(const string& patch_path); + void send_patch_disable(const string& patch_path); + void send_metadata_update(const string& node_path, const string& key, const string& value); + void send_control_change(const string& port_path, float value); + void send_program_add(const string& node_path, int bank, int program, const string& name); + void send_program_remove(const string& node_path, int bank, int program); + +private: + typedef list<pair<ClientKey, CountedPtr<ClientInterface> > > ClientList; + //list<pair<ClientKey, ClientInterface* const> > _clients; + ClientList _clients; +}; + + + +} // namespace Om + +#endif // CLIENTBROADCASTER_H + diff --git a/src/libs/engine/Connection.cpp b/src/libs/engine/Connection.cpp new file mode 100644 index 00000000..d1592009 --- /dev/null +++ b/src/libs/engine/Connection.cpp @@ -0,0 +1,45 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Connection.h" +#include "util.h" +#include "Node.h" +#include "Om.h" +#include "Port.h" + +namespace Om { + + +/** Constructor for a connection from a node's output port. + * + * This handles both polyphonic and monophonic nodes, transparently to the + * user (InputPort). + */ +Connection::Connection(Port* const src_port, Port* const dst_port) +: m_src_port(src_port), + m_dst_port(dst_port), + m_is_poly_to_mono( (src_port->parent_node()->poly() > dst_port->parent_node()->poly()) ), + m_pending_disconnection(false) +{ + assert(src_port != NULL); + assert(dst_port != NULL); + + assert((src_port->parent_node()->poly() == dst_port->parent_node()->poly()) + || (src_port->parent_node()->poly() == 1 || dst_port->parent_node()->poly() == 1)); +} + +} // namespace Om + diff --git a/src/libs/engine/Connection.h b/src/libs/engine/Connection.h new file mode 100644 index 00000000..769c2047 --- /dev/null +++ b/src/libs/engine/Connection.h @@ -0,0 +1,66 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONNECTION_H +#define CONNECTION_H + +#include <cstdlib> +#include "MaidObject.h" +#include "util/types.h" + +namespace Om { + +class Port; + + +/** Represents a single inbound connection for an InputPort. + * + * This can be a group of ports (ie coming from a polyphonic Node) or + * a single Port. This class exists basically as an abstraction of mixing + * down polyphonic inputs, so InputPort can just deal with mixing down + * multiple connections (oblivious to the polyphonic situation of the + * connection itself). + * + * \ingroup engine + */ +class Connection : public MaidObject +{ +public: + virtual ~Connection() {} + + Port* src_port() const { return m_src_port; } + Port* dst_port() const { return m_dst_port; } + + /** Used by some (recursive) events to prevent double disconnections */ + bool pending_disconnection() { return m_pending_disconnection; } + void pending_disconnection(bool b) { m_pending_disconnection = b; } + +protected: + // Disallow copies (undefined) + Connection(const Connection&); + + Connection(Port* const src_port, Port* const dst_port); + + Port* const m_src_port; + Port* const m_dst_port; + bool m_is_poly_to_mono; + bool m_pending_disconnection; +}; + + +} // namespace Om + +#endif // CONNECTION_H diff --git a/src/libs/engine/ConnectionBase.cpp b/src/libs/engine/ConnectionBase.cpp new file mode 100644 index 00000000..c8936818 --- /dev/null +++ b/src/libs/engine/ConnectionBase.cpp @@ -0,0 +1,96 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ConnectionBase.h" +#include "util.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Node.h" +#include "Om.h" +#include "Port.h" + +namespace Om { + + +/** Constructor for a connection from a node's output port. + * + * This handles both polyphonic and monophonic nodes, transparently to the + * user (InputPort). + */ +template <typename T> +ConnectionBase<T>::ConnectionBase(OutputPort<T>* const src_port, InputPort<T>* const dst_port) +: Connection(src_port, dst_port), + m_local_buffer(NULL), + m_is_poly_to_mono( (src_port->parent_node()->poly() > dst_port->parent_node()->poly()) ), + m_buffer_size(src_port->buffer_size()), + m_pending_disconnection(false) +{ + assert((src_port->parent_node()->poly() == dst_port->parent_node()->poly()) + || (src_port->parent_node()->poly() == 1 || dst_port->parent_node()->poly() == 1)); + + if (m_is_poly_to_mono) // Poly -> Mono connection, need a buffer to mix in to + m_local_buffer = new Buffer<T>(m_buffer_size); +} +template ConnectionBase<sample>::ConnectionBase(OutputPort<sample>* const src_port, InputPort<sample>* const dst_port); +template ConnectionBase<MidiMessage>::ConnectionBase(OutputPort<MidiMessage>* const src_port, InputPort<MidiMessage>* const dst_port); + + +template <typename T> +ConnectionBase<T>::~ConnectionBase() +{ + delete m_local_buffer; +} +template ConnectionBase<sample>::~ConnectionBase(); +template ConnectionBase<MidiMessage>::~ConnectionBase(); + + +template <typename sample> +void +ConnectionBase<sample>::prepare_buffers() +{ + /* Thought: A poly output port can be connected to multiple mono input + * ports, which means this mix down would have to happen many times. + * Adding a method to OutputPort that mixes down all it's outputs into + * a buffer (if it hasn't been done already this cycle) and returns that + * would avoid having to mix multiple times. Probably not a very common + * case, but it would be faster anyway. */ + + if (m_is_poly_to_mono) { + m_local_buffer->copy(src_port()->buffer(0), 0, m_buffer_size-1); + + // Mix all the source's voices down into local buffer starting at the second + // voice (buffer is already set to first voice above) + for (size_t j=1; j < src_port()->poly(); ++j) + m_local_buffer->accumulate(src_port()->buffer(j), 0, m_buffer_size-1); + + // Scale the buffer down. + if (src_port()->poly() > 1) + m_local_buffer->scale(1.0f/(float)src_port()->poly(), 0, m_buffer_size-1); + } +} +template void ConnectionBase<sample>::prepare_buffers(); + + +// FIXME: MIDI mixing not implemented +template <> +void +ConnectionBase<MidiMessage>::prepare_buffers() +{ +} + + +} // namespace Om + diff --git a/src/libs/engine/ConnectionBase.h b/src/libs/engine/ConnectionBase.h new file mode 100644 index 00000000..72acae2e --- /dev/null +++ b/src/libs/engine/ConnectionBase.h @@ -0,0 +1,105 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONNECTIONBASE_H +#define CONNECTIONBASE_H + +#include "util/types.h" +#include "OutputPort.h" +#include "Connection.h" + +namespace Om { + +class MidiMessage; +class Port; +template <typename T> class InputPort; + + +/** A Connection with a type. + * + * \ingroup engine + */ +template <typename T> +class ConnectionBase : public Connection +{ +public: + ConnectionBase(OutputPort<T>* const src_port, InputPort<T>* const dst_port); + virtual ~ConnectionBase(); + + void prepare_buffers(); + + inline OutputPort<T>* src_port() const { return (OutputPort<T>*)m_src_port; } + inline InputPort<T>* dst_port() const { return (InputPort<T>*)m_dst_port; } + + /** Used by some (recursive) events to prevent double disconnections */ + bool pending_disconnection() { return m_pending_disconnection; } + void pending_disconnection(bool b) { m_pending_disconnection = b; } + + /** Get the buffer for a particular voice. + * A ConnectionBase is smart - it knows the destination port respondering the + * buffer, and will return accordingly (ie the same buffer for every voice + * in a mono->poly connection). + */ + inline Buffer<T>* buffer(size_t voice) const; + +private: + // Disallow copies (undefined) + ConnectionBase(const ConnectionBase& copy); + ConnectionBase& operator=(const ConnectionBase&); + + Buffer<T>* m_local_buffer; ///< Only used for poly->mono connections + bool m_is_poly_to_mono; + size_t m_buffer_size; + bool m_pending_disconnection; +}; + + +template <> +inline Buffer<sample>* +ConnectionBase<sample>::buffer(size_t voice) const +{ + PortBase<sample>* const src_port = (PortBase<sample>*)m_src_port; + + if (m_is_poly_to_mono) { + return m_local_buffer; + } else { + if (src_port->poly() == 1) + return src_port->buffer(0); + else + return src_port->buffer(voice); + } +} + + +template <> +inline Buffer<MidiMessage>* +ConnectionBase<MidiMessage>::buffer(size_t voice) const +{ + // No such thing as polyphonic MIDI ports + assert(m_src_port->poly() == 1); + assert(m_dst_port->poly() == 1); + + PortBase<MidiMessage>* const src_port = (PortBase<MidiMessage>*)m_src_port; + return src_port->buffer(0); +} + + +template class ConnectionBase<sample>; +template class ConnectionBase<MidiMessage>; + +} // namespace Om + +#endif // CONNECTIONBASE_H diff --git a/src/libs/engine/ControlInputNode.cpp b/src/libs/engine/ControlInputNode.cpp new file mode 100644 index 00000000..d4d83558 --- /dev/null +++ b/src/libs/engine/ControlInputNode.cpp @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ControlInputNode.h" +#include "util/types.h" +#include "Patch.h" +#include "OutputPort.h" +#include "InputPort.h" +#include "Plugin.h" +#include "PortInfo.h" + +namespace Om { + + +ControlInputNode::ControlInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<sample>(path, poly, parent, srate, buffer_size) +{ + OutputPort<sample>* internal_port = new OutputPort<sample>(this, "in", 0, m_poly, + new PortInfo("in", CONTROL, OUTPUT), 1); + InputPort<sample>* external_port = new InputPort<sample>(parent, m_name, 0, m_poly, + new PortInfo(m_name, CONTROL, INPUT), 1); + external_port->tie(internal_port); + m_external_port = external_port; + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("control_input"); + m_plugin.name("Om Patch Control Input Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/ControlInputNode.h b/src/libs/engine/ControlInputNode.h new file mode 100644 index 00000000..0be82af0 --- /dev/null +++ b/src/libs/engine/ControlInputNode.h @@ -0,0 +1,44 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONTROLINPUTNODE_H +#define CONTROLINPUTNODE_H + +#include <string> +#include "util/types.h" +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +template <typename T> class InputPort; + + +/** Control input BridgeNode. + * + * \ingroup engine + */ +class ControlInputNode : public BridgeNode<sample> +{ +public: + ControlInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // CONTROLINPUTNODE_H diff --git a/src/libs/engine/ControlOutputNode.cpp b/src/libs/engine/ControlOutputNode.cpp new file mode 100644 index 00000000..7e1f3e2a --- /dev/null +++ b/src/libs/engine/ControlOutputNode.cpp @@ -0,0 +1,48 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ControlOutputNode.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "Patch.h" + +namespace Om { + + +ControlOutputNode::ControlOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<sample>(path, poly, parent, srate, buffer_size) +{ + OutputPort<sample>* external_port = new OutputPort<sample>(parent, m_name, 0, m_poly, + new PortInfo(m_name, CONTROL, OUTPUT), 1); + InputPort<sample>* internal_port = new InputPort<sample>(this, "out", 0, m_poly, + new PortInfo("out", CONTROL, INPUT), 1); + internal_port->tie(external_port); + m_external_port = external_port; + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("control_output"); + m_plugin.name("Om Patch Control Output Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/ControlOutputNode.h b/src/libs/engine/ControlOutputNode.h new file mode 100644 index 00000000..64fd9ce6 --- /dev/null +++ b/src/libs/engine/ControlOutputNode.h @@ -0,0 +1,45 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONTROLOUTPUTNODE_H +#define CONTROLOUTPUTNODE_H + +#include <string> +#include "util/types.h" +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +class Port; +template <typename T> class OutputPort; + + +/** Control output BridgeNode. + * + * \ingroup engine + */ +class ControlOutputNode : public BridgeNode<sample> +{ +public: + ControlOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // CONTROLOUTPUTNODE_H diff --git a/src/libs/engine/Controller.h b/src/libs/engine/Controller.h new file mode 100644 index 00000000..facfb4c2 --- /dev/null +++ b/src/libs/engine/Controller.h @@ -0,0 +1,82 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONTROLLER_H +#define CONTROLLER_H + +namespace Om { + + +/** Public interface to Om engine shared library. + * + * This is an interface to all the audio-processing related functionality + * only. OSC communication, LASH session management, etc, are not part of + * this library. + */ +class Controller { +public: + void init(); + void quit(); + + void load_plugins(); + + void activate_engine(); + void deactivate_engine(); + + void create_patch(const char* path, unsigned int polyphony); + void clear_patch(const char* path); + void enable_patch(const char* path); + void disable_patch(const char* path); + void rename(const char* old_path, const char* new_path); + + void create_node(const char* path, + const char* type, + const char* library_name, + const char* library_label, + unsigned int polyphony); + + void destroy(const char* path); + + void connect(const char* src_port_path, const char* dst_port_path); + void disconnect(const char* src_port_path, const char* dst_port_path); + void disconnect_all(const char* path); + + void set_port_value(const char* path, float value); + void set_port_value_voice(const char* path, unsigned int voice); + void set_port_value_slow(const char* path, float value); + + void note_on(const char* node_path, + unsigned char note_num, + unsigned char velocity); + + void note_off(const char* node_path, + unsigned char note_num); + + void all_notes_off(const char* node_path); + void midi_learn(const char* node_path); + + void get_metadata(); + void set_metadata(); + void responder_plugins(); + void responder_all_objects(); + void responder_port_value(); +}; + + +} // namespace Om + +#endif // CONTROLLER_H + diff --git a/src/libs/engine/DSSIPlugin.cpp b/src/libs/engine/DSSIPlugin.cpp new file mode 100644 index 00000000..e8525b7b --- /dev/null +++ b/src/libs/engine/DSSIPlugin.cpp @@ -0,0 +1,340 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DSSIPlugin.h" +#include <map> +#include <set> +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" +#include "interface/ClientInterface.h" +#include "InputPort.h" +#include "PortInfo.h" + +using namespace std; + +namespace Om { + + +DSSIPlugin::DSSIPlugin(const string& name, size_t poly, Patch* parent, DSSI_Descriptor* descriptor, samplerate srate, size_t buffer_size) +: LADSPAPlugin(name, 1, parent, descriptor->LADSPA_Plugin, srate, buffer_size), + m_dssi_descriptor(descriptor), + m_ui_addr(NULL), + m_bank(-1), + m_program(-1), + m_midi_in_port(NULL), + m_alsa_events(new snd_seq_event_t[m_buffer_size]), + m_alsa_encoder(NULL) +{ + if (has_midi_input()) + m_num_ports = descriptor->LADSPA_Plugin->PortCount + 1; + + snd_midi_event_new(3, &m_alsa_encoder); +} + + +DSSIPlugin::~DSSIPlugin() +{ + if (m_ui_addr != NULL) + lo_address_free(m_ui_addr); + + snd_midi_event_free(m_alsa_encoder); + delete [] m_alsa_events; +} + + +/** This needs to be overridden here because LADSPAPlugin::instantiate() + * allocates the port array, and we want to add the MIDI input port to that + * array. + */ +bool +DSSIPlugin::instantiate() +{ + if (!LADSPAPlugin::instantiate()) + return false; + + if (has_midi_input()) { + assert(m_num_ports == m_descriptor->PortCount + 1); + assert(m_ports.size() == m_descriptor->PortCount + 1); + + m_midi_in_port = new InputPort<MidiMessage>(this, "MIDI In", m_num_ports-1, 1, + new PortInfo("MIDI In", MIDI, INPUT), m_buffer_size); + m_ports.at(m_num_ports-1) = m_midi_in_port; + } + + return true; +} + + +void +DSSIPlugin::activate() +{ + LADSPAPlugin::activate(); + + update_programs(false); + set_default_program(); + + snd_midi_event_reset_encode(m_alsa_encoder); +} + + +void +DSSIPlugin::set_ui_url(const string& url) +{ + if (m_ui_addr != NULL) + lo_address_free(m_ui_addr); + + m_ui_url = url; + m_ui_addr = lo_address_new_from_url(url.c_str()); + char* base_path = lo_url_get_path(url.c_str()); + m_ui_base_path = base_path; + free(base_path); + cerr << "Set UI base path to " << m_ui_base_path << endl; +} + + +void +DSSIPlugin::set_control(size_t port_num, sample val) +{ + assert(port_num < m_descriptor->PortCount); + ((PortBase<sample>*)m_ports.at(port_num))->set_value(val, 0); +} + + +void +DSSIPlugin::configure(const string& key, const string& val) +{ + m_dssi_descriptor->configure(m_instances[0], key.c_str(), val.c_str()); + m_configures[key] = val; + update_programs(true); +} + + +void +DSSIPlugin::program(int bank, int program) +{ + if (m_dssi_descriptor->select_program) + m_dssi_descriptor->select_program(m_instances[0], bank, program); + + m_bank = bank; + m_program = program; +} + + +void +DSSIPlugin::convert_events() +{ + assert(has_midi_input()); + assert(m_midi_in_port != NULL); + + Buffer<MidiMessage>& buffer = *m_midi_in_port->buffer(0); + m_encoded_events = 0; + + for (size_t i = 0; i < buffer.filled_size(); ++i) { + snd_midi_event_encode(m_alsa_encoder, buffer.value_at(i).buffer, + buffer.value_at(i).size, + &m_alsa_events[m_encoded_events]); + m_alsa_events[m_encoded_events].time.tick = buffer.value_at(i).time; + if (m_alsa_events[m_encoded_events].type != SND_SEQ_EVENT_NONE) + ++m_encoded_events; + } +} + + +bool +DSSIPlugin::has_midi_input() const +{ + return (m_dssi_descriptor->run_synth || m_dssi_descriptor->run_multiple_synths); +} + + +void +DSSIPlugin::run(size_t nframes) +{ + NodeBase::run(nframes); + + if (m_dssi_descriptor->run_synth) { + convert_events(); + m_dssi_descriptor->run_synth(m_instances[0], nframes, + m_alsa_events, m_encoded_events); + } else if (m_dssi_descriptor->run_multiple_synths) { + convert_events(); + // I hate this stupid function + snd_seq_event_t* events[1] = { m_alsa_events }; + long unsigned events_sizes[1] = { m_encoded_events }; + m_dssi_descriptor->run_multiple_synths(1, m_instances, nframes, + events, events_sizes); + } else { + LADSPAPlugin::run(nframes); + } +} + + +void +DSSIPlugin::send_control(int port_num, float value) +{ + string path = m_ui_base_path + "/control"; + lo_send(m_ui_addr, path.c_str(), "if", port_num, value); +} + + +void +DSSIPlugin::send_program(int bank, int value) +{ + string path = m_ui_base_path + "/program"; + lo_send(m_ui_addr, path.c_str(), "ii", bank, value); +} + + +void +DSSIPlugin::send_configure(const string& key, const string& val) +{ + string path = m_ui_base_path + "/configure"; + lo_send(m_ui_addr, path.c_str(), "ss", key.c_str(), val.c_str()); +} + + +void +DSSIPlugin::send_show() +{ + string path = m_ui_base_path + "/show"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_hide() +{ + string path = m_ui_base_path + "/hide"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_quit() +{ + string path = m_ui_base_path + "/quit"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_update() +{ + // send "configure"s + for (map<string, string>::iterator i = m_configures.begin(); i != m_configures.end(); ++i) + send_configure((*i).first, (*i).second); + + // send "program" + send_program(m_bank, m_program); + + // send "control"s + for (size_t i=0; i < m_ports.size(); ++i) + if (m_ports[i]->port_info()->is_control()) + send_control(m_ports[i]->num(), ((PortBase<sample>*)m_ports[i])->buffer(0)->value_at(0)); + + // send "show" FIXME: not to spec + send_show(); +} + + +bool +DSSIPlugin::update_programs(bool send_events) +{ + // remember all old banks and programs + set<pair<int, int> > to_be_deleted; + map<int, Bank>::const_iterator iter; + Bank::const_iterator iter2; + for (iter = m_banks.begin(); iter != m_banks.end(); ++iter) { + for (iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) { + to_be_deleted.insert(make_pair(iter->first, iter2->first)); + } + } + + // iterate over all programs + if (m_dssi_descriptor->get_program) { + for (int i = 0; true; ++i) { + const DSSI_Program_Descriptor* descriptor = + m_dssi_descriptor->get_program(m_instances[0], i); + if (!descriptor) + break; + + iter = m_banks.find(descriptor->Bank); + if (iter == m_banks.end() || + iter->second.find(descriptor->Program) == iter->second.end() || + iter->second.find(descriptor->Program)->second != descriptor->Name) { + m_banks[descriptor->Bank][descriptor->Program] = descriptor->Name; + if (send_events) { + om->client_broadcaster()->send_program_add(path(), descriptor->Bank, + descriptor->Program, + descriptor->Name); + } + to_be_deleted.erase(make_pair(descriptor->Bank, descriptor->Program)); + } + } + } + + // remove programs that has disappeared from the plugin + set<pair<int, int> >::const_iterator set_iter; + for (set_iter = to_be_deleted.begin(); + set_iter != to_be_deleted.end(); ++set_iter) { + m_banks[set_iter->first].erase(set_iter->second); + if (send_events) + om->client_broadcaster()->send_program_remove(path(), set_iter->first, set_iter->second); + if (m_banks[set_iter->first].size() == 0) + m_banks.erase(set_iter->first); + } + + return true; +} + + +void +DSSIPlugin::set_default_program() +{ + map<int, Bank>::const_iterator iter = m_banks.begin(); + if (iter != m_banks.end()) { + Bank::const_iterator iter2 = iter->second.begin(); + if (iter2 != iter->second.end()) + program(iter->first, iter2->first); + } +} + + +const map<int, DSSIPlugin::Bank>& +DSSIPlugin::get_programs() const +{ + return m_banks; +} + + +/* +void +DSSIPlugin::send_creation_messages(ClientInterface* client) const +{ + LADSPAPlugin::send_creation_messages(client); + + for (map<int, Bank>::const_iterator i = get_programs().begin(); + i != get_programs().end(); ++i) { + + for (Bank::const_iterator j = i->second.begin(); j != i->second.end(); ++j) + client->program_add(path(), + i->first, j->first, j->second); + } +} +*/ + +} // namespace Om diff --git a/src/libs/engine/DSSIPlugin.cpp.orig b/src/libs/engine/DSSIPlugin.cpp.orig new file mode 100644 index 00000000..7c8060f0 --- /dev/null +++ b/src/libs/engine/DSSIPlugin.cpp.orig @@ -0,0 +1,207 @@ +/* This file is part of Om. 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 "DSSIPlugin.h" +#include <map> +#include "Om.h" +#include "OmApp.h" +#include "config.h" +#include "Patch.h" +#include "PortBase.h" +#include "InputPort.h" +#include "PortInfo.h" + +using std::map; + +namespace Om { + + +DSSIPlugin::DSSIPlugin(const string& name, uint poly, Patch* parent, DSSI_Descriptor* descriptor, samplerate srate, size_t buffer_size) +: LADSPAPlugin(name, 1, parent, descriptor->LADSPA_Plugin, srate, buffer_size), + m_ui_addr(NULL), + m_bank(-1), + m_program(-1), + m_dssi_descriptor(descriptor) +{ + m_num_ports = descriptor->LADSPA_Plugin->PortCount + 1; + m_ports.alloc(m_num_ports); + + m_midi_in_port = new InputPort<MidiMessage>(this, "MIDI In", m_num_ports-1, 1, + new PortInfo("MIDI In", MIDI, INPUT), m_buffer_size); + + m_ports.at(m_num_ports-1) = m_midi_in_port; +} + + +DSSIPlugin::~DSSIPlugin() +{ + if (m_ui_addr != NULL) + lo_address_free(m_ui_addr); +} + + +void +DSSIPlugin::activate() +{ + LADSPAPlugin::activate(); +} + + +void +DSSIPlugin::set_ui_url(const string& url) +{ + if (m_ui_addr != NULL) + lo_address_free(m_ui_addr); + + m_ui_url = url; + m_ui_addr = lo_address_new_from_url(url.c_str()); + char* base_path = lo_url_get_path(url.c_str()); + m_ui_base_path = base_path; + free(base_path); + cerr << "Set UI base path to " << m_ui_base_path << endl; +} + + +void +DSSIPlugin::set_control(uint port_num, sample val) +{ + assert(port_num < m_descriptor->PortCount); + ((PortBase<sample>*)m_ports.at(port_num))->set_value(0, val); +} + + +void +DSSIPlugin::configure(const string& key, const string& val) +{ + m_dssi_descriptor->configure(m_instances[0], key.c_str(), val.c_str()); + m_configures[key] = val; +} + + +void +DSSIPlugin::program(int bank, int program) +{ + if (m_dssi_descriptor->select_program) + m_dssi_descriptor->select_program(m_instances[0], bank, program); + + m_bank = bank; + m_program = program; +} + + +void +DSSIPlugin::run(size_t nframes) +{ + NodeBase::run(nframes); + + /*if (m_dssi_descriptor->run_synth) { + m_dssi_descriptor->run_synth(m_instances[0], nframes, + parent_patch()->dssi_events_array(), parent_patch()->dssi_events_size()); + } else if (m_dssi_descriptor->run_multiple_synths) { // I hate this stupid function + snd_seq_event_t* events[1] = { parent_patch()->dssi_events_array() }; + long unsigned events_sizes[1] = { parent_patch()->dssi_events_size() }; + m_dssi_descriptor->run_multiple_synths(1, m_instances, nframes, + events, events_sizes); + } else { + LADSPAPlugin::run(nframes); + } + */ + + if (m_dssi_descriptor->run_synth) { + m_dssi_descriptor->run_synth(m_instances[0], nframes, + (snd_seq_event_t*)m_midi_in_port->buffer(0), m_midi_in_port->get_valid_buffer_size()); + /*} else if (m_dssi_descriptor->run_multiple_synths) { // I hate this stupid function + snd_seq_event_t* events[1] = { parent_patch()->dssi_events_array() }; + long unsigned events_sizes[1] = { parent_patch()->dssi_events_size() }; + m_dssi_descriptor->run_multiple_synths(1, m_instances, nframes, + events, events_sizes);*/ + } else { + LADSPAPlugin::run(nframes); + } + + +} + + +void +DSSIPlugin::send_control(int port_num, float value) +{ + string path = m_ui_base_path + "/control"; + lo_send(m_ui_addr, path.c_str(), "if", port_num, value); +} + + +void +DSSIPlugin::send_program(int bank, int value) +{ + string path = m_ui_base_path + "/program"; + lo_send(m_ui_addr, path.c_str(), "ii", bank, value); +} + + +void +DSSIPlugin::send_configure(const string& key, const string& val) +{ + string path = m_ui_base_path + "/configure"; + lo_send(m_ui_addr, path.c_str(), "ss", key.c_str(), val.c_str()); +} + + +void +DSSIPlugin::send_show() +{ + string path = m_ui_base_path + "/show"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_hide() +{ + string path = m_ui_base_path + "/hide"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_quit() +{ + string path = m_ui_base_path + "/quit"; + lo_send(m_ui_addr, path.c_str(), NULL); +} + + +void +DSSIPlugin::send_update() +{ + // send "configure"s + for (map<string, string>::iterator i = m_configures.begin(); i != m_configures.end(); ++i) + send_configure((*i).first, (*i).second); + + // send "program" + send_program(m_bank, m_program); + + // send "control"s + for (size_t i=0; i < m_ports.size(); ++i) + if (m_ports[i]->port_info()->is_control()) + send_control(m_ports[i]->num(), ((PortBase<sample>*)m_ports[i])->get_value(0, 0)); + + // send "show" FIXME: not to spec + send_show(); +} + + +} // namespace Om diff --git a/src/libs/engine/DSSIPlugin.h b/src/libs/engine/DSSIPlugin.h new file mode 100644 index 00000000..d546a8fe --- /dev/null +++ b/src/libs/engine/DSSIPlugin.h @@ -0,0 +1,109 @@ +/* This file is part of Om. Copyright (C) 2006 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 DSSIPLUGIN_H +#define DSSIPLUGIN_H + +#include <asoundlib.h> +#include <dssi.h> +#include <lo/lo.h> +#include "LADSPAPlugin.h" + +namespace Om { + +class MidiMessage; +template <typename T> class InputPort; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** An instance of a DSSI plugin. + */ +class DSSIPlugin : public LADSPAPlugin +{ +public: + + typedef map<int, string> Bank; + + DSSIPlugin(const string& name, size_t poly, Patch* parent, DSSI_Descriptor* descriptor, samplerate srate, size_t buffer_size); + ~DSSIPlugin(); + + bool instantiate(); + + void activate(); + + void set_ui_url(const string& url); + void send_update(); + + void set_control(size_t port_num, sample val); + void configure(const string& key, const string& val); + void program(int bank, int program); + + void run(size_t nframes); + + bool update_programs(bool send_events); + void set_default_program(); + const map<int, Bank>& get_programs() const; + + //void send_creation_messages(ClientInterface* client) const; + + const Plugin* plugin() const { return m_plugin; } + void plugin(const Plugin* const pi) { m_plugin = pi; } + +private: + // Prevent copies (undefined) + DSSIPlugin(const DSSIPlugin& copy); + DSSIPlugin& operator=(const DSSIPlugin& copy); + + bool has_midi_input() const; + + // DSSI GUI messages + void send_control(int port_num, float value); + void send_program(int bank, int value); + void send_configure(const string& key, const string& val); + void send_show(); + void send_hide(); + void send_quit(); + + // Conversion to ALSA MIDI events + void convert_events(); + + + DSSI_Descriptor* m_dssi_descriptor; + + string m_ui_url; + string m_ui_base_path; + lo_address m_ui_addr; + + // Current values + int m_bank; + int m_program; + map<string, string> m_configures; + map<int, Bank> m_banks; + + InputPort<MidiMessage>* m_midi_in_port; + snd_seq_event_t* m_alsa_events; + unsigned long m_encoded_events; + snd_midi_event_t* m_alsa_encoder; +}; + + +} // namespace Om + + +#endif // DSSIPLUGIN_H + diff --git a/src/libs/engine/DSSIPlugin.h.orig b/src/libs/engine/DSSIPlugin.h.orig new file mode 100644 index 00000000..816c65a7 --- /dev/null +++ b/src/libs/engine/DSSIPlugin.h.orig @@ -0,0 +1,84 @@ +/* This file is part of Om. 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 DSSIPLUGIN_H +#define DSSIPLUGIN_H + +#include <dssi.h> +#include <lo/lo.h> +#include "LADSPAPlugin.h" + +namespace Om { + +class MidiMessage; +template <typename T> class InputPort; + + +/** An instance of a DSSI plugin. + */ +class DSSIPlugin : public LADSPAPlugin +{ +public: + DSSIPlugin(const string& name, uint poly, Patch* parent, DSSI_Descriptor* descriptor, samplerate srate, size_t buffer_size); + ~DSSIPlugin(); + + void activate(); + + void set_ui_url(const string& url); + void send_update(); + + void set_control(uint port_num, sample val); + void configure(const string& key, const string& val); + void program(int bank, int program); + + void run(size_t nframes); + + const Plugin* const plugin() const { return m_plugin; } + void plugin(const Plugin* const pi) { m_plugin = pi; } + +private: + // Prevent copies + DSSIPlugin(const DSSIPlugin& copy) : LADSPAPlugin(copy) { exit(EXIT_FAILURE); } + DSSIPlugin& operator=(const DSSIPlugin& copy) { exit(EXIT_FAILURE); } + + // DSSI GUI messages + void send_control(int port_num, float value); + void send_program(int bank, int value); + void send_configure(const string& key, const string& val); + void send_show(); + void send_hide(); + void send_quit(); + + string m_ui_url; + string m_ui_base_path; + lo_address m_ui_addr; + + // Current values + int m_bank; + int m_program; + map<string, string> m_configures; + + DSSI_Descriptor* m_dssi_descriptor; + + InputPort<MidiMessage>* m_midi_in_port; +}; + + +} // namespace Om + + +#endif // DSSIPLUGIN_H + diff --git a/src/libs/engine/Driver.h b/src/libs/engine/Driver.h new file mode 100644 index 00000000..be882d8d --- /dev/null +++ b/src/libs/engine/Driver.h @@ -0,0 +1,112 @@ +/* This file is part of Om. Copyright (C) 2006 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 + +#include <string> +using std::string; + +namespace Om { + +template <typename T> class PortBase; + + +/** Representation of a system (outside Om, ie hardware) audio port. + * + * This is the class through which the rest of the engine manages everything + * related to driver ports. Derived classes are expected to have a pointer to + * their driver (to be able to perform the operation necessary). + * + * \ingroup engine + */ +class DriverPort { +public: + virtual ~DriverPort() {} + + /** Add this port to driver at the beginning of a process cycle (realtime safe) */ + virtual void add_to_driver() = 0; + + /** Remove this port at the beginning of a process cycle (realtime safe) */ + virtual void remove_from_driver() = 0; + + /** Set the name of the system port */ + virtual void set_name(const string& name) = 0; + +protected: + DriverPort() {} +}; + + +/** Driver abstract base class. + * + * A Driver is, from the perspective of OmObjects (nodes, patches, ports) an + * interface for managing system ports. An implementation of Driver basically + * needs to manage DriverPorts, and handle writing/reading data to/from them. + * + * The template parameter T is the type of data this driver manages (ie the + * data type of the bridge ports it will handle). + * + * \ingroup engine + */ +template <typename T> +class Driver +{ +public: + virtual ~Driver() {} + + virtual void activate() = 0; + virtual void deactivate() = 0; + + virtual bool is_activated() const = 0; + + /** Create a port ready to be inserted with add_input (non realtime). + * + * May return NULL if the Driver can not drive the port for some reason. + */ + virtual DriverPort* create_port(PortBase<T>* patch_port) = 0; +}; + + +#if 0 +/** Dummy audio driver. + * + * Not abstract, all functions are dummies. One of these will be allocated and + * "used" if no working AUDIO driver is loaded. (Doing it this way as opposed to + * just making Driver have dummy functions makes sure any existing Driver + * derived class actually implements the required functions). + * + * \ingroup engine + */ +class DummyDriver : public Driver +{ +public: + ~DummyDriver() {} + + void activate() {} + void deactivate() {} + + void enable() {} + void disable() {} + + DriverPort* create_port(PortBase<sample>* patch_port) { return NULL; } +}; +#endif + + +} // namespace Om + +#endif // DRIVER_H diff --git a/src/libs/engine/Event.cpp b/src/libs/engine/Event.cpp new file mode 100644 index 00000000..7d590e76 --- /dev/null +++ b/src/libs/engine/Event.cpp @@ -0,0 +1,48 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Event.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "AudioDriver.h" + +namespace Om { + +// It'd be nice if this was inlined.. +Event::Event(CountedPtr<Responder> responder) +: m_responder(responder), + m_executed(false) +{ + m_time_stamp = om->audio_driver()->time_stamp(); +} + + +/** Construct an event with no responder. + * + * For internal events only, attempting to access the responder will + * cause a NULL pointer dereference. + */ +Event::Event() +: m_responder(NULL), + m_executed(false) +{ + m_time_stamp = om->audio_driver()->time_stamp(); +} + + +} // namespace Om + diff --git a/src/libs/engine/Event.h b/src/libs/engine/Event.h new file mode 100644 index 00000000..a6d06f83 --- /dev/null +++ b/src/libs/engine/Event.h @@ -0,0 +1,71 @@ +/* This file is part of Om. Copyright (C) 2006 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 EVENT_H +#define EVENT_H + +#include <cassert> +#include "util/CountedPtr.h" +#include "util/types.h" +#include "MaidObject.h" +#include "Responder.h" + +namespace Om { + + + +/** Base class for all events (both realtime and QueuedEvent). + * + * This is for time-critical events like note ons. There is no non-realtime + * pre-execute method as in QueuedEvent's, any lookups etc need to be done in the + * realtime execute() method. + * + * QueuedEvent extends this class with a pre_process() method for any work that needs + * to be done before processing in the realtime audio thread. + * + * \ingroup engine + */ +class Event : public MaidObject +{ +public: + virtual ~Event() {} + + /** Execute event, MUST be realtime safe */ + virtual void execute(samplecount offset) { assert(!m_executed); m_executed = true; } + + /** Perform any actions after execution (ie send OSC response to client). + * No realtime requirement. */ + virtual void post_process() {} + + inline samplecount time_stamp() { return m_time_stamp; } + +protected: + Event(CountedPtr<Responder> responder); + Event(); + + // Prevent copies + Event(const Event&); + Event& operator=(const Event&); + + CountedPtr<Responder> m_responder; + samplecount m_time_stamp; + bool m_executed; +}; + + +} // namespace Om + +#endif // EVENT_H diff --git a/src/libs/engine/EventSource.h b/src/libs/engine/EventSource.h new file mode 100644 index 00000000..b038f427 --- /dev/null +++ b/src/libs/engine/EventSource.h @@ -0,0 +1,52 @@ +/* This file is part of Om. Copyright (C) 2006 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 EVENTSOURCE_H +#define EVENTSOURCE_H + +namespace Om { + +class Event; +class QueuedEvent; + + +/** Source for events to run in the audio thread. + * + * The AudioDriver gets events from an EventSource in the process callback + * (realtime audio thread) and executes them, then they are sent to the + * PostProcessor and finalised (post-processing thread). + */ +class EventSource +{ +public: + + virtual ~EventSource() {} + + virtual Event* pop_earliest_event_before(const samplecount time) = 0; + + virtual void start() = 0; + + virtual void stop() = 0; + +protected: + EventSource() {} +}; + + +} // namespace Om + +#endif // EVENTSOURCE_H + diff --git a/src/libs/engine/InputPort.cpp b/src/libs/engine/InputPort.cpp new file mode 100644 index 00000000..3837da1b --- /dev/null +++ b/src/libs/engine/InputPort.cpp @@ -0,0 +1,352 @@ +/* This file is part of Om. Copyright (C) 2006 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 "InputPort.h" +#include <iostream> +#include <cstdlib> +#include <cassert> +#include "ConnectionBase.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Node.h" +#include "Om.h" +#include "util.h" + +using std::cerr; using std::cout; using std::endl; + + +namespace Om { + + +template <typename T> +InputPort<T>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size) +: PortBase<T>(node, name, index, poly, port_info, buffer_size) +{ + assert(port_info->is_input() && !port_info->is_output()); +} +template InputPort<sample>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); +template InputPort<MidiMessage>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + + +/** Add a connection. Realtime safe. + * + * The buffer of this port will be set directly to the connection's buffer + * if there is only one connection, since no mixing needs to take place. + */ +template<typename T> +void +InputPort<T>::add_connection(ListNode<ConnectionBase<T>*>* const c) +{ + m_connections.push_back(c); + + bool modify_buffers = !m_fixed_buffers; + if (modify_buffers && m_is_tied) + modify_buffers = !m_tied_port->fixed_buffers(); + + if (modify_buffers) { + if (m_connections.size() == 1) { + // Use buffer directly to avoid copying + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->join(c->elem()->buffer(i)); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + assert(m_buffers.at(i)->data() == c->elem()->buffer(i)->data()); + } + } else if (m_connections.size() == 2) { + // Used to directly use single connection buffer, now there's two + // so have to use local ones again and mix down + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->unjoin(); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } + update_buffers(); + } + + assert( ! m_is_tied || m_tied_port != NULL); + assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); +} +template void InputPort<sample>::add_connection(ListNode<ConnectionBase<sample>*>* const c); +template void InputPort<MidiMessage>::add_connection(ListNode<ConnectionBase<MidiMessage>*>* const c); + + +/** Remove a connection. Realtime safe. + */ +template <typename T> +ListNode<ConnectionBase<T>*>* +InputPort<T>::remove_connection(const OutputPort<T>* const src_port) +{ + bool modify_buffers = !m_fixed_buffers; + if (modify_buffers && m_is_tied) + modify_buffers = !m_tied_port->fixed_buffers(); + + typedef typename List<ConnectionBase<T>*>::iterator ConnectionBaseListIterator; + bool found = false; + ListNode<ConnectionBase<T>*>* connection = NULL; + for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i) { + if ((*i)->src_port()->path() == src_port->path()) { + connection = m_connections.remove(i); + found = true; + } + } + + if ( ! found) { + cerr << "WARNING: [InputPort<T>::remove_connection] Connection not found !" << endl; + exit(EXIT_FAILURE); + } else { + if (m_connections.size() == 0) { + for (size_t i=0; i < m_poly; ++i) { + // Use a local buffer + if (modify_buffers && m_buffers.at(i)->is_joined()) + m_buffers.at(i)->unjoin(); + m_buffers.at(i)->clear(); // Write silence + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } else if (modify_buffers && m_connections.size() == 1) { + // Share a buffer + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->join((*m_connections.begin())->buffer(i)); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } + } + + if (modify_buffers) + update_buffers(); + + assert( ! m_is_tied || m_tied_port != NULL); + assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + return connection; +} +template ListNode<ConnectionBase<sample>*>* +InputPort<sample>::remove_connection(const OutputPort<sample>* const src_port); +template ListNode<ConnectionBase<MidiMessage>*>* +InputPort<MidiMessage>::remove_connection(const OutputPort<MidiMessage>* const src_port); + + +/** Update any changed buffers with the plugin this is a port on. + * + * This calls ie the LADSPA connect_port function when buffers have been changed + * due to a connection or disconnection. + */ +template <typename T> +void +InputPort<T>::update_buffers() +{ + for (size_t i=0; i < m_poly; ++i) + InputPort<T>::parent_node()->set_port_buffer(i, m_index, m_buffers.at(i)->data()); +} +template void InputPort<sample>::update_buffers(); +template void InputPort<MidiMessage>::update_buffers(); + + +/** Returns whether this port is connected to the passed port. + */ +template <typename T> +bool +InputPort<T>::is_connected_to(const OutputPort<T>* const port) const +{ + typedef typename List<ConnectionBase<T>*>::const_iterator ConnectionBaseListIterator; + for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i) + if ((*i)->src_port() == port) + return true; + + return false; +} +template bool InputPort<sample>::is_connected_to(const OutputPort<sample>* const port) const; +template bool InputPort<MidiMessage>::is_connected_to(const OutputPort<MidiMessage>* const port) const; + + +/** "Ties" this port to an OutputPort, so they share the same buffer. + * + * This is used by OutputNode and InputNode to provide two different ports + * (internal and external) that share a buffer. + */ +template <typename T> +void +InputPort<T>::tie(OutputPort<T>* const port) +{ + assert((Port*)port != (Port*)this); + assert(port->poly() == this->poly()); + assert(!m_is_tied); + assert(m_tied_port == NULL); + + if (Port::parent_node() != NULL) { + assert(m_poly == port->poly()); + + for (size_t i=0; i < m_poly; ++i) + port->buffer(i)->join(m_buffers.at(i)); + } + m_is_tied = true; + m_tied_port = port; + port->set_tied_port(this); + + assert(m_buffers.at(0)->data() == port->buffer(0)->data()); + + //cerr << "*** Tied " << this->path() << " <-> " << port->path() << endl; +} +template void InputPort<sample>::tie(OutputPort<sample>* const port); +template void InputPort<MidiMessage>::tie(OutputPort<MidiMessage>* const port); + + +/** Prepare buffer for access, mixing if necessary. Realtime safe. + * FIXME: nframes parameter not used, + */ +template<> +void +InputPort<sample>::prepare_buffers(size_t nframes) +{ + assert(!m_is_tied || m_tied_port != NULL); + + typedef List<ConnectionBase<sample>*>::iterator ConnectionBaseListIterator; + bool do_mixdown = true; + + if (m_connections.size() == 0) return; + + for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c) + (*c)->prepare_buffers(); + + // If only one connection, buffer is (maybe) used directly (no copying) + if (m_connections.size() == 1) { + // Buffer changed since connection + if (m_buffers.at(0)->data() != (*m_connections.begin())->buffer(0)->data()) { + if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) { + // can't change buffer, must copy + do_mixdown = true; + } else { + // zero-copy + assert(m_buffers.at(0)->is_joined()); + m_buffers.at(0)->join((*m_connections.begin())->buffer(0)); + do_mixdown = false; + } + } else { + do_mixdown = false; + } + update_buffers(); + } + + if (!do_mixdown) { + assert(m_buffers.at(0)->data() == (*m_connections.begin())->buffer(0)->data()); + return; + } + + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + for (size_t voice=0; voice < m_poly; ++voice) { + m_buffers.at(voice)->copy((*m_connections.begin())->buffer(voice), 0, m_buffer_size-1); + + if (m_connections.size() > 1) { + // Copy first connection + ConnectionBaseListIterator c = m_connections.begin(); + + // Add all other connections + for (++c; c != m_connections.end(); ++c) + m_buffers.at(voice)->accumulate((*c)->buffer(voice), 0, m_buffer_size-1); + } + } +} + + +/** Prepare buffer for access, realtime safe. + * + * MIDI mixing not yet implemented. + */ +template <> +void +InputPort<MidiMessage>::prepare_buffers(size_t nframes) +{ + assert(!m_is_tied || m_tied_port != NULL); + + const size_t num_ins = m_connections.size(); + bool do_mixdown = true; + + assert(num_ins == 0 || num_ins == 1); + + typedef List<ConnectionBase<MidiMessage>*>::iterator ConnectionBaseListIterator; + assert(m_poly == 1); + + for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c) + (*c)->prepare_buffers(); + + + // If only one connection, buffer is used directly (no copying) + if (num_ins == 1) { + // Buffer changed since connection + if (m_buffers.at(0) != (*m_connections.begin())->buffer(0)) { + if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) { + // can't change buffer, must copy + do_mixdown = true; + } else { + // zero-copy + assert(m_buffers.at(0)->is_joined()); + m_buffers.at(0)->join((*m_connections.begin())->buffer(0)); + if (m_is_tied) + m_tied_port->buffer(0)->join(m_buffers.at(0)); + do_mixdown = false; + } + update_buffers(); + } else { + do_mixdown = false; + } + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + assert(!m_is_tied || m_buffers.at(0)->filled_size() == m_tied_port->buffer(0)->filled_size()); + assert(do_mixdown || m_buffers.at(0)->filled_size() == + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + } + + // Get valid buffer size from inbound connections, unless a port on a top-level + // patch (which will be fed by the MidiDriver) + if (m_parent->parent() != NULL) { + if (num_ins == 1) { + m_buffers.at(0)->filled_size( + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + + if (m_is_tied) + m_tied_port->buffer(0)->filled_size(m_buffers.at(0)->filled_size()); + + assert(m_buffers.at(0)->filled_size() == + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + } else { + // Mixing not implemented + m_buffers.at(0)->clear(); + } + } + + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + if (!do_mixdown || m_buffers.at(0)->filled_size() == 0 || num_ins == 0) + return; + + //cerr << path() << " - Copying MIDI buffer" << endl; + + // Be sure buffers are the same as tied port's, if joined + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + if (num_ins > 0) + for (size_t i=0; i < m_buffers.at(0)->filled_size(); ++i) + m_buffers.at(0)[i] = (*m_connections.begin())->buffer(0)[i]; +} + + +} // namespace Om + diff --git a/src/libs/engine/InputPort.h b/src/libs/engine/InputPort.h new file mode 100644 index 00000000..0e5d9d68 --- /dev/null +++ b/src/libs/engine/InputPort.h @@ -0,0 +1,88 @@ +/* This file is part of Om. Copyright (C) 2006 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 INPUTPORT_H +#define INPUTPORT_H + +#include <string> +#include <cstdlib> +#include <cassert> +#include "PortBase.h" +#include "List.h" +#include "MidiMessage.h" +using std::string; + +namespace Om { + +template <typename T> class ConnectionBase; +template <typename T> class OutputPort; +class Node; + + +/** An input port on a Node or Patch. + * + * All ports have a Buffer, but the actual contents (data) of that buffer may be + * set directly to the incoming connection's buffer if there's only one inbound + * connection, to eliminate the need to copy/mix. + * + * If a port has multiple connections, they will be mixed down into the local + * buffer and it will be used. + * + * \ingroup engine + */ +template <typename T> +class InputPort : public PortBase<T> +{ +public: + InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + virtual ~InputPort() {} + + void add_connection(ListNode<ConnectionBase<T>*>* const c); + ListNode<ConnectionBase<T>*>* remove_connection(const OutputPort<T>* const src_port); + + void prepare_buffers(size_t nframes); + + void tie(OutputPort<T>* const port); + + bool is_connected() const { return (m_connections.size() > 0); } + bool is_connected_to(const OutputPort<T>* const port) const; + +private: + // Prevent copies (Undefined) + InputPort<T>(const InputPort<T>& copy); + InputPort<T>& operator=(const InputPort<T>&); + + void update_buffers(); + + List<ConnectionBase<T>*> m_connections; + + // This is just stupid... + using PortBase<T>::m_is_tied; + using PortBase<T>::m_tied_port; + using PortBase<T>::m_buffers; + using PortBase<T>::m_poly; + using PortBase<T>::m_index; + using PortBase<T>::m_buffer_size; + using PortBase<T>::m_fixed_buffers; +}; + + +template class InputPort<sample>; +template class InputPort<MidiMessage>; + +} // namespace Om + +#endif // INPUTPORT_H diff --git a/src/libs/engine/InternalNode.h b/src/libs/engine/InternalNode.h new file mode 100644 index 00000000..fea2d3ad --- /dev/null +++ b/src/libs/engine/InternalNode.h @@ -0,0 +1,70 @@ +/* This file is part of Om. Copyright (C) 2006 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 INTERNALNODE_H +#define INTERNALNODE_H + +#include <cstdlib> +#include "NodeBase.h" +#include "Plugin.h" + +namespace Om { + +class Patch; + + +/** Base class for Internal (builtin) nodes + * + * \ingroup engine + */ +class InternalNode : public NodeBase +{ +public: + InternalNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) + : NodeBase(path, poly, parent, srate, buffer_size), + m_is_added(false) + { + m_plugin.lib_path("/Om"); + } + + virtual ~InternalNode() {} + + virtual void deactivate() { if (m_is_added) remove_from_patch(); NodeBase::deactivate(); } + + virtual void run(size_t nframes) { NodeBase::run(nframes); } + + virtual void add_to_patch() { assert(!m_is_added); m_is_added = true; } + virtual void remove_from_patch() { assert(m_is_added); m_is_added = false; } + + //virtual void send_creation_messages(ClientInterface* client) const + //{ NodeBase::send_creation_messages(client); } + + virtual const Plugin* plugin() const { return &m_plugin; } + virtual void plugin(const Plugin* const) { exit(EXIT_FAILURE); } + +protected: + // Disallow copies (undefined) + InternalNode(const InternalNode&); + InternalNode& operator=(const InternalNode&); + + Plugin m_plugin; + bool m_is_added; +}; + + +} // namespace Om + +#endif // INTERNALNODE_H diff --git a/src/libs/engine/JackAudioDriver.cpp b/src/libs/engine/JackAudioDriver.cpp new file mode 100644 index 00000000..0f5e3b1a --- /dev/null +++ b/src/libs/engine/JackAudioDriver.cpp @@ -0,0 +1,373 @@ +/* This file is part of Om. Copyright (C) 2006 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 "JackAudioDriver.h" +#include "config.h" +#include "tuning.h" +#include <iostream> +#include <cstdlib> +#include "Om.h" +#include "OmApp.h" +#include "util.h" +#include "Event.h" +#include "QueuedEvent.h" +#include "EventSource.h" +#include "PostProcessor.h" +#include "util/Queue.h" +#include "Node.h" +#include "Patch.h" +#include "Port.h" +#include "PortInfo.h" +#include "MidiDriver.h" +#include "List.h" +#include "PortBase.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif + +using std::cout; using std::cerr; using std::endl; + + +namespace Om { + + +//// JackAudioPort //// + +JackAudioPort::JackAudioPort(JackAudioDriver* driver, PortBase<sample>* patch_port) +: DriverPort(), + ListNode<JackAudioPort*>(this), + m_driver(driver), + m_jack_port(NULL), + m_jack_buffer(NULL), + m_patch_port(patch_port) +{ + assert(patch_port->tied_port() != NULL); + assert(patch_port->poly() == 1); + + m_jack_port = jack_port_register(m_driver->jack_client(), + patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE, + (patch_port->port_info()->is_input()) ? JackPortIsInput : JackPortIsOutput, + 0); + + m_jack_buffer = new DriverBuffer<jack_sample_t>(driver->buffer_size()); + + patch_port->fixed_buffers(true); +} + + +JackAudioPort::~JackAudioPort() +{ + jack_port_unregister(m_driver->jack_client(), m_jack_port); +} + + +void +JackAudioPort::add_to_driver() +{ + m_driver->add_port(this); +} + + +void +JackAudioPort::remove_from_driver() +{ + m_driver->remove_port(this); +} + +void +JackAudioPort::prepare_buffer(jack_nframes_t nframes) +{ + // Technically this doesn't need to be done every time for output ports + m_jack_buffer->set_data((jack_default_audio_sample_t*) + jack_port_get_buffer(m_jack_port, nframes)); + + assert(m_patch_port->tied_port() != NULL); + + // FIXME: fixed_buffers switch on/off thing can be removed once this shit + // gets figured out and assertions can go away + m_patch_port->fixed_buffers(false); + m_patch_port->buffer(0)->join(m_jack_buffer); + m_patch_port->tied_port()->buffer(0)->join(m_jack_buffer); + + m_patch_port->fixed_buffers(true); + + assert(m_patch_port->buffer(0)->data() == m_patch_port->tied_port()->buffer(0)->data()); + assert(m_patch_port->buffer(0)->data() == m_jack_buffer->data()); +} + + +//// JackAudioDriver //// + +JackAudioDriver::JackAudioDriver() +: m_client(NULL), + m_buffer_size(0), + m_sample_rate(0), + m_is_activated(false), + m_local_client(true), + m_root_patch(NULL), + m_start_of_current_cycle(0), + m_start_of_last_cycle(0) +{ + m_client = jack_client_new("Om"); + if (m_client == NULL) { + cerr << "[JackAudioDriver] Unable to connect to Jack. Exiting." << endl; + exit(EXIT_FAILURE); + } + + jack_on_shutdown(m_client, shutdown_cb, this); + + m_buffer_size = jack_get_buffer_size(m_client); + m_sample_rate = jack_get_sample_rate(m_client); + + jack_set_sample_rate_callback(m_client, sample_rate_cb, this); + jack_set_buffer_size_callback(m_client, buffer_size_cb, this); +} + +JackAudioDriver::JackAudioDriver(jack_client_t* jack_client) +: m_client(jack_client), + m_buffer_size(jack_get_buffer_size(jack_client)), + m_sample_rate(jack_get_sample_rate(jack_client)), + m_is_activated(false), + m_local_client(false), + m_start_of_current_cycle(0), + m_start_of_last_cycle(0) +{ + jack_on_shutdown(m_client, shutdown_cb, this); + + jack_set_sample_rate_callback(m_client, sample_rate_cb, this); + jack_set_buffer_size_callback(m_client, buffer_size_cb, this); +} + + +JackAudioDriver::~JackAudioDriver() +{ + deactivate(); + + if (m_local_client) + jack_client_close(m_client); +} + + +void +JackAudioDriver::activate() +{ + if (m_is_activated) { + cerr << "[JackAudioDriver] Jack driver already activated." << endl; + return; + } + + jack_set_process_callback(m_client, process_cb, this); + + m_is_activated = true; + + if (jack_activate(m_client)) { + cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl; + exit(EXIT_FAILURE); + } else { + cout << "[JackAudioDriver] Activated Jack client." << endl; +#ifdef HAVE_LASH + lash_driver->set_jack_client_name("Om"); // FIXME: unique name +#endif + } +} + + +void +JackAudioDriver::deactivate() +{ + if (m_is_activated) { + // FIXME + reinterpret_cast<EventSource*>(om->osc_receiver())->stop(); + + jack_deactivate(m_client); + m_is_activated = false; + + for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i) + jack_port_unregister(m_client, (*i)->jack_port()); + + m_ports.clear(); + + cout << "[JackAudioDriver] Deactivated Jack client." << endl; + + om->post_processor()->stop(); + } +} + + +/** Add a Jack port. + * + * Realtime safe, this is to be called at the beginning of a process cycle to + * insert (and actually begin using) a new port. + * + * See create_port() and remove_port(). + */ +void +JackAudioDriver::add_port(JackAudioPort* port) +{ + m_ports.push_back(port); +} + + +/** Remove a Jack port. + * + * Realtime safe. This is to be called at the beginning of a process cycle to + * remove the port from the lists read by the audio thread, so the port + * will no longer be used and can be removed afterwards. + * + * It is the callers responsibility to delete the returned port. + */ +JackAudioPort* +JackAudioDriver::remove_port(JackAudioPort* port) +{ + for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i) + if ((*i) == port) + return m_ports.remove(i)->elem(); + + cerr << "[JackAudioDriver::remove_port] WARNING: Failed to find Jack port to remove!" << endl; + return NULL; +} + + +DriverPort* +JackAudioDriver::create_port(PortBase<sample>* patch_port) +{ + if (patch_port->buffer_size() == m_buffer_size) + return new JackAudioPort(this, patch_port); + else + return NULL; +} + + +/** Process all the pending events for this cycle. + * + * Called from the realtime thread once every process cycle. + */ +void +JackAudioDriver::process_events(jack_nframes_t block_start, jack_nframes_t block_end) +{ + Event* ev = NULL; + + /* Limit the maximum number of queued events to process per cycle. This + * makes the process callback truly realtime-safe by preventing being + * choked by events coming in faster than they can be processed. + * FIXME: run the math on this and figure out a good value */ + const int MAX_SLOW_EVENTS = m_buffer_size / 100; + + int num_events_processed = 0; + int offset = 0; + + // Process the "slow" events first, because it's possible some of the + // RT events depend on them + + // FIXME + while ((ev = reinterpret_cast<EventSource*>(om->osc_receiver()) + ->pop_earliest_event_before(block_end)) != NULL) { + ev->execute(0); // QueuedEvents are not sample accurate + om->post_processor()->push(ev); + if (++num_events_processed > MAX_SLOW_EVENTS) + break; + } + + while (!om->event_queue()->is_empty() + && om->event_queue()->front()->time_stamp() < block_end) { + ev = om->event_queue()->pop(); + offset = ev->time_stamp() - block_start; + if (offset < 0) offset = 0; // this can happen if we miss a cycle + ev->execute(offset); + om->post_processor()->push(ev); + ++num_events_processed; + } + + if (num_events_processed > 0) + om->post_processor()->signal(); +} + + + +/**** Jack Callbacks ****/ + + + +/** Jack process callback, drives entire audio thread. + * + * \callgraph + */ +int +JackAudioDriver::m_process_cb(jack_nframes_t nframes) +{ + // FIXME: support nframes != buffer_size, even though that never damn well happens + assert(nframes == m_buffer_size); + + // Note that jack can elect to not call this function for a cycle, if things aren't + // keeping up + m_start_of_current_cycle = jack_last_frame_time(m_client); + m_start_of_last_cycle = m_start_of_current_cycle - nframes; + + assert(m_start_of_current_cycle - m_start_of_last_cycle == nframes); + + m_transport_state = jack_transport_query(m_client, &m_position); + + process_events(m_start_of_last_cycle, m_start_of_current_cycle); + om->midi_driver()->prepare_block(m_start_of_last_cycle, m_start_of_current_cycle); + + // Set buffers of patch ports to Jack port buffers (zero-copy processing) + for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i) + (*i)->prepare_buffer(nframes); + + // Run root patch + assert(m_root_patch != NULL); + m_root_patch->run(nframes); + + return 0; +} + + +void +JackAudioDriver::m_shutdown_cb() +{ + cout << "[JackAudioDriver] Jack shutdown. Exiting." << endl; + om->quit(); +} + + +int +JackAudioDriver::m_sample_rate_cb(jack_nframes_t nframes) +{ + if (m_is_activated) { + cerr << "[JackAudioDriver] Om does not support changing sample rate on the fly (yet). Aborting." << endl; + exit(EXIT_FAILURE); + } else { + m_sample_rate = nframes; + } + return 0; +} + + +int +JackAudioDriver::m_buffer_size_cb(jack_nframes_t nframes) +{ + if (m_is_activated) { + cerr << "[JackAudioDriver] Om does not support chanding buffer size on the fly (yet). Aborting." << endl; + exit(EXIT_FAILURE); + } else { + m_buffer_size = nframes; + } + return 0; +} + + +} // namespace Om + diff --git a/src/libs/engine/JackAudioDriver.h b/src/libs/engine/JackAudioDriver.h new file mode 100644 index 00000000..c55d392f --- /dev/null +++ b/src/libs/engine/JackAudioDriver.h @@ -0,0 +1,176 @@ +/* This file is part of Om. Copyright (C) 2006 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 JACKAUDIODRIVER_H +#define JACKAUDIODRIVER_H + +#include <jack/jack.h> +#include <jack/transport.h> +#include "List.h" +#include "AudioDriver.h" +#include "Buffer.h" + +namespace Om { + +class Patch; +class Port; +template <typename T> class PortBase; +class JackAudioDriver; +typedef jack_default_audio_sample_t jack_sample_t; + + +/** Used internally by JackAudioDriver to represent a Jack port. + * + * A Jack port always has a one-to-one association with a Patch port. + */ +class JackAudioPort : public DriverPort, public ListNode<JackAudioPort*> +{ +public: + JackAudioPort(JackAudioDriver* driver, PortBase<sample>* patch_port); + ~JackAudioPort(); + + void add_to_driver(); + void remove_from_driver(); + void set_name(const string& name) { jack_port_set_name(m_jack_port, name.c_str()); }; + + void prepare_buffer(jack_nframes_t nframes); + + jack_port_t* jack_port() const { return m_jack_port; } + DriverBuffer<sample>* buffer() const { return m_jack_buffer; } + void jack_buffer(jack_sample_t* s) { m_jack_buffer->set_data(s); } + PortBase<sample>* patch_port() const { return m_patch_port; } + +private: + // Prevent copies (undefined) + JackAudioPort(const JackAudioPort&); + JackAudioPort& operator=(const JackAudioPort&); + + JackAudioDriver* m_driver; + jack_port_t* m_jack_port; + DriverBuffer<sample>* m_jack_buffer; + PortBase<sample>* m_patch_port; +}; + + + +/** The Jack AudioDriver. + * + * The process callback here drives the entire audio thread by "pulling" + * events from queues, processing them, running the patches, and passing + * events along to the PostProcessor. + * + * \ingroup engine + */ +class JackAudioDriver : public AudioDriver +{ +public: + JackAudioDriver(); + JackAudioDriver(jack_client_t *jack_client); + ~JackAudioDriver(); + + void activate(); + void deactivate(); + void enable(); + void disable(); + + void process_events(jack_nframes_t block_start, jack_nframes_t block_end); + + DriverPort* create_port(PortBase<sample>* patch_port); + + Patch* root_patch() { return m_root_patch; } + void set_root_patch(Patch* patch) { m_root_patch = patch; } + + /** Transport state for this frame. + * Intended to only be called from the audio thread. */ + inline const jack_position_t* position() { return &m_position; } + inline const jack_transport_state_t transport_state() { return m_transport_state; } + + bool is_realtime() { return jack_is_realtime(m_client); } + + jack_client_t* jack_client() const { return m_client; } + samplecount buffer_size() const { return m_buffer_size; } + samplecount sample_rate() const { return m_sample_rate; } + bool is_activated() const { return m_is_activated; } + + samplecount time_stamp() const { return jack_frame_time(m_client); } + +private: + // Prevent copies (undefined) + JackAudioDriver(const JackAudioDriver&); + JackAudioDriver& operator=(const JackAudioDriver&); + + friend class JackAudioPort; + + // Functions for JackAudioPort + void add_port(JackAudioPort* port); + JackAudioPort* remove_port(JackAudioPort* port); + + // These are the static versions of the callbacks, they call + // the non-static ones below + inline static int process_cb(jack_nframes_t nframes, void* const jack_driver); + inline static void shutdown_cb(void* const jack_driver); + inline static int buffer_size_cb(jack_nframes_t nframes, void* const jack_driver); + inline static int sample_rate_cb(jack_nframes_t nframes, void* const jack_driver); + + // Non static callbacks + int m_process_cb(jack_nframes_t nframes); + void m_shutdown_cb(); + int m_buffer_size_cb(jack_nframes_t nframes); + int m_sample_rate_cb(jack_nframes_t nframes); + + jack_client_t* m_client; + jack_nframes_t m_buffer_size; + jack_nframes_t m_sample_rate; + bool m_is_activated; + bool m_local_client; ///< Whether m_client should be closed on destruction + jack_position_t m_position; + jack_transport_state_t m_transport_state; + + List<JackAudioPort*> m_ports; + + Patch* m_root_patch; + + jack_nframes_t m_start_of_current_cycle; + jack_nframes_t m_start_of_last_cycle; +}; + + +inline int JackAudioDriver::process_cb(jack_nframes_t nframes, void* jack_driver) +{ + return ((JackAudioDriver*)jack_driver)->m_process_cb(nframes); +} + +inline void JackAudioDriver::shutdown_cb(void* jack_driver) +{ + return ((JackAudioDriver*)jack_driver)->m_shutdown_cb(); +} + + +inline int JackAudioDriver::buffer_size_cb(jack_nframes_t nframes, void* jack_driver) +{ + return ((JackAudioDriver*)jack_driver)->m_buffer_size_cb(nframes); +} + + +inline int JackAudioDriver::sample_rate_cb(jack_nframes_t nframes, void* jack_driver) +{ + return ((JackAudioDriver*)jack_driver)->m_sample_rate_cb(nframes); +} + + +} // namespace Om + +#endif // JACKAUDIODRIVER_H diff --git a/src/libs/engine/JackMidiDriver.cpp b/src/libs/engine/JackMidiDriver.cpp new file mode 100644 index 00000000..40054b8a --- /dev/null +++ b/src/libs/engine/JackMidiDriver.cpp @@ -0,0 +1,217 @@ +/* This file is part of Om. Copyright (C) 2006 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 "JackMidiDriver.h" +#include <iostream> +#include <cstdlib> +#include <pthread.h> +#include "Om.h" +#include "OmApp.h" +#include "util/types.h" +#include "midi.h" +#include "OmApp.h" +#include "Maid.h" +#include "AudioDriver.h" +#include "MidiInputNode.h" +#include "PortInfo.h" +#include "MidiMessage.h" +#include "PortBase.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +//// JackMidiPort //// + +JackMidiPort::JackMidiPort(JackMidiDriver* driver, PortBase<MidiMessage>* patch_port) +: DriverPort(), + ListNode<JackMidiPort*>(this), + m_driver(driver), + m_jack_port(NULL), + m_patch_port(patch_port) +{ + assert(patch_port->poly() == 1); + + m_jack_port = jack_port_register(m_driver->jack_client(), + patch_port->path().c_str(), JACK_DEFAULT_MIDI_TYPE, + (patch_port->port_info()->is_input()) ? JackPortIsInput : JackPortIsOutput, + 0); + + patch_port->buffer(0)->clear(); + patch_port->fixed_buffers(true); +} + + +JackMidiPort::~JackMidiPort() +{ + jack_port_unregister(m_driver->jack_client(), m_jack_port); +} + + +void +JackMidiPort::add_to_driver() +{ + m_driver->add_port(this); +} + + +void +JackMidiPort::remove_from_driver() +{ + m_driver->remove_port(this); +} + + +/** Prepare events for a block. + * + * This is basically trivial (as opposed to AlsaMidiPort) since Jack MIDI + * data is in-band with the audio thread. + * + * Prepares all events that occurred during the time interval passed + * (which ideally are the events from the previous cycle with an exact + * 1 cycle delay). + */ +void +JackMidiPort::prepare_block(const samplecount block_start, const samplecount block_end) +{ + assert(block_end >= block_start); + + const samplecount nframes = block_end - block_start; + void* jack_buffer = jack_port_get_buffer(m_jack_port, nframes); + const jack_nframes_t event_count = jack_midi_port_get_info(jack_buffer, nframes)->event_count; + + assert(event_count < m_patch_port->buffer_size()); + + // Copy events from Jack port buffer into patch port buffer + for (jack_nframes_t i=0; i < event_count; ++i) { + jack_midi_event_t* ev = (jack_midi_event_t*)&m_patch_port->buffer(0)->value_at(i); + jack_midi_event_get(ev, jack_buffer, i, nframes); + + // Convert note ons with velocity 0 to proper note offs + if (ev->buffer[0] == MIDI_CMD_NOTE_ON && ev->buffer[2] == 0) + ev->buffer[0] = MIDI_CMD_NOTE_OFF; + + // MidiMessage and jack_midi_event_t* are the same thing :/ + MidiMessage* const message = &m_patch_port->buffer(0)->data()[i]; + message->time = ev->time; + message->size = ev->size; + message->buffer = ev->buffer; + } + + //cerr << "Jack MIDI got " << event_count << " events." << endl; + + m_patch_port->buffer(0)->filled_size(event_count); + m_patch_port->tied_port()->buffer(0)->filled_size(event_count); +} + + + +//// JackMidiDriver //// + + +bool JackMidiDriver::m_midi_thread_exit_flag = true; + + +JackMidiDriver::JackMidiDriver(jack_client_t* client) +: m_client(client), + m_is_activated(false), + m_is_enabled(false) +{ +} + + +JackMidiDriver::~JackMidiDriver() +{ + deactivate(); +} + + +/** Launch and start the MIDI thread. + */ +void +JackMidiDriver::activate() +{ + m_is_activated = true; +} + + +/** Terminate the MIDI thread. + */ +void +JackMidiDriver::deactivate() +{ + m_is_activated = false; +} + + +/** Build flat arrays of events for DSSI plugins for each Port. + */ +void +JackMidiDriver::prepare_block(const samplecount block_start, const samplecount block_end) +{ + for (List<JackMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i) + (*i)->prepare_block(block_start, block_end); +} + + +/** Add an Jack MIDI port. + * + * Realtime safe, this is to be called at the beginning of a process cycle to + * insert (and actually begin using) a new port. + * + * See create_port() and remove_port(). + */ +void +JackMidiDriver::add_port(JackMidiPort* port) +{ + if (port->patch_port()->port_info()->is_input()) + m_in_ports.push_back(port); + else + m_out_ports.push_back(port); +} + + +/** Remove an Jack MIDI port. + * + * Realtime safe. This is to be called at the beginning of a process cycle to + * remove the port from the lists read by the audio thread, so the port + * will no longer be used and can be removed afterwards. + * + * It is the callers responsibility to delete the returned port. + */ +JackMidiPort* +JackMidiDriver::remove_port(JackMidiPort* port) +{ + if (port->patch_port()->port_info()->is_input()) { + for (List<JackMidiPort*>::iterator i = m_in_ports.begin(); i != m_in_ports.end(); ++i) + if ((*i) == (JackMidiPort*)port) + return m_in_ports.remove(i)->elem(); + } else { + for (List<JackMidiPort*>::iterator i = m_out_ports.begin(); i != m_out_ports.end(); ++i) + if ((*i) == port) + return m_out_ports.remove(i)->elem(); + } + + cerr << "[JackMidiDriver::remove_input] WARNING: Failed to find Jack port to remove!" << endl; + return NULL; +} + + +} // namespace Om + diff --git a/src/libs/engine/JackMidiDriver.h b/src/libs/engine/JackMidiDriver.h new file mode 100644 index 00000000..950d382f --- /dev/null +++ b/src/libs/engine/JackMidiDriver.h @@ -0,0 +1,123 @@ +/* This file is part of Om. Copyright (C) 2006 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 JACKMIDIDRIVER_H +#define JACKMIDIDRIVER_H + +#include <jack/jack.h> +#include <jack/midiport.h> +#include "config.h" +#include "List.h" +#include "util/Queue.h" +#include "MidiDriver.h" + +namespace Om { + +class Node; +class SetPortValueEvent; +class JackMidiDriver; +template <typename T> class PortBase; + + +/** Representation of an JACK MIDI port. + * + * \ingroup engine + */ +class JackMidiPort : public DriverPort, public ListNode<JackMidiPort*> +{ +public: + JackMidiPort(JackMidiDriver* driver, PortBase<MidiMessage>* port); + virtual ~JackMidiPort(); + + void prepare_block(const samplecount block_start, const samplecount block_end); + + void add_to_driver(); + void remove_from_driver(); + void set_name(const string& name) { jack_port_set_name(m_jack_port, name.c_str()); }; + + PortBase<MidiMessage>* patch_port() const { return m_patch_port; } + +private: + // Prevent copies (undefined) + JackMidiPort(const JackMidiPort&); + JackMidiPort& operator=(const JackMidiPort&); + + JackMidiDriver* m_driver; + jack_port_t* m_jack_port; + PortBase<MidiMessage>* m_patch_port; +}; + + +/** Jack MIDI driver. + * + * This driver reads Jack MIDI events and dispatches them to the appropriate + * JackMidiPort for processing. + * + * \ingroup engine + */ +class JackMidiDriver : public MidiDriver +{ +public: + JackMidiDriver(jack_client_t* client); + ~JackMidiDriver(); + + void activate(); + void deactivate(); + void enable() { m_is_enabled = true; } + void disable() { m_is_enabled = false; } + + bool is_activated() const { return m_is_activated; } + bool is_enabled() const { return m_is_enabled; } + + void prepare_block(const samplecount block_start, const samplecount block_end); + + JackMidiPort* create_port(PortBase<MidiMessage>* patch_port) + { return new JackMidiPort(this, patch_port); } + + jack_client_t* jack_client() { return m_client; } + +private: + // Prevent copies (undefined) + JackMidiDriver(const JackMidiDriver&); + JackMidiDriver& operator=(const JackMidiDriver&); + + List<JackMidiPort*> m_in_ports; + List<JackMidiPort*> m_out_ports; + + friend class JackMidiPort; + + // Functions for JackMidiPort + void add_port(JackMidiPort* port); + JackMidiPort* remove_port(JackMidiPort* port); + + void add_output(ListNode<JackMidiPort*>* port); + ListNode<JackMidiPort*>* remove_output(JackMidiPort* port); + + // MIDI thread + static void* process_midi_in(void* me); + + jack_client_t* m_client; + pthread_t m_process_thread; + bool m_is_activated; + bool m_is_enabled; + static bool m_midi_thread_exit_flag; +}; + + +} // namespace Om + + +#endif // JACKMIDIDRIVER_H diff --git a/src/libs/engine/LADSPAPlugin.cpp b/src/libs/engine/LADSPAPlugin.cpp new file mode 100644 index 00000000..ddeb0be4 --- /dev/null +++ b/src/libs/engine/LADSPAPlugin.cpp @@ -0,0 +1,274 @@ +/* This file is part of Om. Copyright (C) 2006 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 "LADSPAPlugin.h" +#include <iostream> +#include <cassert> +#include "float.h" +#include <stdint.h> +#include <cmath> +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" + +namespace Om { + + +/** Partially construct a LADSPAPlugin. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LADSPAPlugin::LADSPAPlugin(const string& path, size_t poly, Patch* parent, const LADSPA_Descriptor* descriptor, samplerate srate, size_t buffer_size) +: NodeBase(path, poly, parent, srate, buffer_size), + m_descriptor(descriptor), + m_instances(NULL) +{ + assert(m_descriptor != NULL); + + // Note that this may be changed by an overriding DSSIPlugin + // ie do not assume m_ports is all LADSPA plugin ports + m_num_ports = m_descriptor->PortCount; +} + + +/** Instantiate self from LADSPA plugin descriptor. + * + * Implemented as a seperate function (rather than in the constructor) to + * allow graceful error-catching of broken plugins. + * + * Returns whether or not plugin was successfully instantiated. If return + * value is false, this object may not be used. + */ +bool +LADSPAPlugin::instantiate() +{ + m_ports.alloc(m_num_ports); + + m_instances = new LADSPA_Handle[m_poly]; + + size_t port_buffer_size = 0; + + for (size_t i=0; i < m_poly; ++i) { + m_instances[i] = m_descriptor->instantiate(m_descriptor, m_srate); + if (m_instances[i] == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + } + + string port_name; + string port_path; + + Port* port = NULL; + + for (size_t j=0; j < m_descriptor->PortCount; ++j) { + port_name = m_descriptor->PortNames[j]; + string::size_type slash_index; + + // Name mangling, to guarantee port names are unique + for (size_t k=0; k < m_descriptor->PortCount; ++k) { + assert(m_descriptor->PortNames[k] != NULL); + if (k != j && port_name == m_descriptor->PortNames[k]) { // clash + if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[j])) + port_name += " (CR)"; + else + port_name += " (AR)"; + } + // Replace all slashes with "-" (so they don't screw up paths) + while ((slash_index = port_name.find("/")) != string::npos) + port_name[slash_index] = '-'; + } + + port_path = path() + "/" + port_name; + + if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[j])) + port_buffer_size = 1; + else if (LADSPA_IS_PORT_AUDIO(m_descriptor->PortDescriptors[j])) + port_buffer_size = m_buffer_size; + + assert (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[j]) + || LADSPA_IS_PORT_OUTPUT(m_descriptor->PortDescriptors[j])); + + if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[j])) { + port = new InputPort<sample>(this, port_name, j, m_poly, + new PortInfo(port_path, m_descriptor->PortDescriptors[j], + m_descriptor->PortRangeHints[j].HintDescriptor), port_buffer_size); + m_ports.at(j) = port; + } else if (LADSPA_IS_PORT_OUTPUT(m_descriptor->PortDescriptors[j])) { + port = new OutputPort<sample>(this, port_name, j, m_poly, + new PortInfo(port_path, m_descriptor->PortDescriptors[j], + m_descriptor->PortRangeHints[j].HintDescriptor), port_buffer_size); + m_ports.at(j) = port; + } + + assert(m_ports.at(j) != NULL); + + PortInfo* pi = port->port_info(); + get_port_vals(j, pi); + + // Set default control val + if (pi->is_control()) + ((PortBase<sample>*)port)->set_value(pi->default_val(), 0); + else + ((PortBase<sample>*)port)->set_value(0.0f, 0); + } + + return true; +} + + +LADSPAPlugin::~LADSPAPlugin() +{ + for (size_t i=0; i < m_poly; ++i) + m_descriptor->cleanup(m_instances[i]); + + delete[] m_instances; +} + + +void +LADSPAPlugin::activate() +{ + NodeBase::activate(); + + PortBase<sample>* port = NULL; + + for (size_t i=0; i < m_poly; ++i) { + for (unsigned long j=0; j < m_descriptor->PortCount; ++j) { + port = static_cast<PortBase<sample>*>(m_ports.at(j)); + set_port_buffer(i, j, ((PortBase<sample>*)m_ports.at(j))->buffer(i)->data()); + if (port->port_info()->is_control()) + port->set_value(port->port_info()->default_val(), 0); + else if (port->port_info()->is_audio()) + port->set_value(0.0f, 0); + } + if (m_descriptor->activate != NULL) + m_descriptor->activate(m_instances[i]); + } +} + + +void +LADSPAPlugin::deactivate() +{ + NodeBase::deactivate(); + + for (size_t i=0; i < m_poly; ++i) + if (m_descriptor->deactivate != NULL) + m_descriptor->deactivate(m_instances[i]); +} + + +void +LADSPAPlugin::run(size_t nframes) +{ + NodeBase::run(nframes); // mixes down input ports + for (size_t i=0; i < m_poly; ++i) + m_descriptor->run(m_instances[i], nframes); +} + + +void +LADSPAPlugin::set_port_buffer(size_t voice, size_t port_num, void* buf) +{ + assert(voice < m_poly); + + // Could be a MIDI port after this + if (port_num < m_descriptor->PortCount) { + m_descriptor->connect_port(m_instances[voice], port_num, (sample*)buf); + } +} + + +// Based on code stolen from jack-rack +void +LADSPAPlugin::get_port_vals(ulong port_index, PortInfo* info) +{ + LADSPA_Data upper = 0.0f; + LADSPA_Data lower = 0.0f; + LADSPA_Data normal = 0.0f; + LADSPA_PortRangeHintDescriptor hint_descriptor = m_descriptor->PortRangeHints[port_index].HintDescriptor; + + /* set upper and lower, possibly adjusted to the sample rate */ + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + upper = m_descriptor->PortRangeHints[port_index].UpperBound * m_srate; + lower = m_descriptor->PortRangeHints[port_index].LowerBound * m_srate; + } else { + upper = m_descriptor->PortRangeHints[port_index].UpperBound; + lower = m_descriptor->PortRangeHints[port_index].LowerBound; + } + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + /* FLT_EPSILON is defined as the different between 1.0 and the minimum + * float greater than 1.0. So, if lower is < FLT_EPSILON, it will be 1.0 + * and the logarithmic control will have a base of 1 and thus not change + */ + if (lower < FLT_EPSILON) lower = FLT_EPSILON; + } + + + if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) { + + if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) { + normal = lower; + } else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) { + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.75 + log(upper) * 0.25); + } else { + normal = lower * 0.75 + upper * 0.25; + } + } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) { + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.5 + log(upper) * 0.5); + } else { + normal = lower * 0.5 + upper * 0.5; + } + } else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) { + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.25 + log(upper) * 0.75); + } else { + normal = lower * 0.25 + upper * 0.75; + } + } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) { + normal = upper; + } else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) { + normal = 0.0; + } else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) { + normal = 1.0; + } else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) { + normal = 100.0; + } else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) { + normal = 440.0; + } + } else { // No default hint + if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { + normal = lower; + } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { + normal = upper; + } + } + + info->min_val(lower); + info->default_val(normal); + info->max_val(upper); +} + + +} // namespace Om + diff --git a/src/libs/engine/LADSPAPlugin.h b/src/libs/engine/LADSPAPlugin.h new file mode 100644 index 00000000..dc1ba5e1 --- /dev/null +++ b/src/libs/engine/LADSPAPlugin.h @@ -0,0 +1,69 @@ +/* This file is part of Om. Copyright (C) 2006 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 LADSPAPLUGIN_H +#define LADSPAPLUGIN_H + +#include <string> +#include <ladspa.h> +#include "util/types.h" +#include "NodeBase.h" +#include "Plugin.h" + +namespace Om { + +class PortInfo; + + +/** An instance of a LADSPA plugin. + * + * \ingroup engine + */ +class LADSPAPlugin : public NodeBase +{ +public: + LADSPAPlugin(const string& name, size_t poly, Patch* parent, const LADSPA_Descriptor* descriptor, samplerate srate, size_t buffer_size); + virtual ~LADSPAPlugin(); + + virtual bool instantiate(); + + void activate(); + void deactivate(); + + void run(size_t nframes); + + void set_port_buffer(size_t voice, size_t port_num, void* buf); + + const Plugin* plugin() const { return m_plugin; } + void plugin(const Plugin* const pi) { m_plugin = pi; } + +protected: + // Prevent copies (undefined) + LADSPAPlugin(const LADSPAPlugin& copy); + LADSPAPlugin& operator=(const LADSPAPlugin&); + + void get_port_vals(ulong port_index, PortInfo* info); + + const LADSPA_Descriptor* m_descriptor; + LADSPA_Handle* m_instances; + + const Plugin* m_plugin; +}; + + +} // namespace Om + +#endif // LADSPAPLUGIN_H diff --git a/src/libs/engine/LV2Plugin.cpp b/src/libs/engine/LV2Plugin.cpp new file mode 100644 index 00000000..1c74bca9 --- /dev/null +++ b/src/libs/engine/LV2Plugin.cpp @@ -0,0 +1,275 @@ +/* This file is part of Om. Copyright (C) 2006 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 "LV2Plugin.h" +#include <iostream> +#include <cassert> +#include "float.h" +#include <stdint.h> +#include <cmath> +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" + +namespace Om { + + +/** Partially construct a LV2Plugin. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LV2Plugin::LV2Plugin(const string& name, + size_t poly, + Patch* parent, + const SLV2Plugin* plugin, + samplerate srate, + size_t buffer_size) +: NodeBase(name, poly, parent, srate, buffer_size), + m_lv2_plugin(plugin), + m_instances(NULL) +{ + assert(m_lv2_plugin); + + // Note that this may be changed by an overriding DSSIPlugin + // ie do not assume m_ports is all LV2 plugin ports + m_num_ports = slv2_plugin_get_num_ports(m_lv2_plugin); +} + + +/** Instantiate self from LV2 plugin descriptor. + * + * Implemented as a seperate function (rather than in the constructor) to + * allow graceful error-catching of broken plugins. + * + * Returns whether or not plugin was successfully instantiated. If return + * value is false, this object may not be used. + */ +bool +LV2Plugin::instantiate() +{ + m_ports.alloc(m_num_ports); + + m_instances = new SLV2Instance*[m_poly]; + + size_t port_buffer_size = 0; + + for (size_t i=0; i < m_poly; ++i) { + m_instances[i] = slv2_plugin_instantiate(m_lv2_plugin, m_srate, NULL); + if (m_instances[i] == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + } + + string port_name; + string port_path; + + Port* port = NULL; + + for (size_t j=0; j < m_num_ports; ++j) { + // LV2 shortnames are guaranteed to be unique + port_name = (char*)slv2_port_get_symbol(m_lv2_plugin, j); + + string::size_type slash_index; + + // Replace all slashes with "-" (so they don't screw up paths) + while ((slash_index = port_name.find("/")) != string::npos) + port_name[slash_index] = '-'; + + port_path = path() + "/" + port_name; + + // Assumes there is only the 4 classes + + SLV2PortClass port_class = slv2_port_get_class(m_lv2_plugin, j); + const bool is_control = (port_class == SLV2_CONTROL_RATE_INPUT + || port_class == SLV2_CONTROL_RATE_OUTPUT); + const bool is_input = (port_class == SLV2_CONTROL_RATE_INPUT + || port_class == SLV2_AUDIO_RATE_INPUT); + + if (is_control) + port_buffer_size = 1; + else + port_buffer_size = m_buffer_size; + + PortType type = is_control ? CONTROL : AUDIO; + PortDirection direction = is_input ? INPUT : OUTPUT; + + if (is_input) { + port = new InputPort<sample>(this, port_name, j, m_poly, + new PortInfo(port_path, type, direction), port_buffer_size); + m_ports.at(j) = port; + } else /* output */ { + port = new OutputPort<sample>(this, port_name, j, m_poly, + new PortInfo(port_path, type, direction), port_buffer_size); + m_ports.at(j) = port; + } + + assert(m_ports.at(j) != NULL); + + PortInfo* pi = port->port_info(); + get_port_vals(j, pi); + + // Set default control val + if (pi->is_control()) + ((PortBase<sample>*)port)->set_value(pi->default_val(), 0); + else + ((PortBase<sample>*)port)->set_value(0.0f, 0); + } + return true; +} + + +LV2Plugin::~LV2Plugin() +{ + for (size_t i=0; i < m_poly; ++i) + slv2_instance_free(m_instances[i]); + + delete[] m_instances; +} + + +void +LV2Plugin::activate() +{ + NodeBase::activate(); + + PortBase<sample>* port = NULL; + + for (size_t i=0; i < m_poly; ++i) { + for (unsigned long j=0; j < m_num_ports; ++j) { + port = static_cast<PortBase<sample>*>(m_ports.at(j)); + set_port_buffer(i, j, ((PortBase<sample>*)m_ports.at(j))->buffer(i)->data()); + if (port->port_info()->is_control()) + port->set_value(port->port_info()->default_val(), 0); + else if (port->port_info()->is_audio()) + port->set_value(0.0f, 0); + } + slv2_instance_activate(m_instances[i]); + } +} + + +void +LV2Plugin::deactivate() +{ + NodeBase::deactivate(); + + for (size_t i=0; i < m_poly; ++i) + slv2_instance_deactivate(m_instances[i]); +} + + +void +LV2Plugin::run(size_t nframes) +{ + NodeBase::run(nframes); // mixes down input ports + for (size_t i=0; i < m_poly; ++i) + slv2_instance_run(m_instances[i], nframes); +} + + +void +LV2Plugin::set_port_buffer(size_t voice, size_t port_num, void* buf) +{ + assert(voice < m_poly); + + // Could be a MIDI port after this + if (port_num < m_num_ports) { + slv2_instance_connect_port(m_instances[voice], port_num, buf); + } +} + + +// Based on code stolen from jack-rack +void +LV2Plugin::get_port_vals(ulong port_index, PortInfo* info) +{ +#if 0 + LV2_Data upper = 0.0f; + LV2_Data lower = 0.0f; + LV2_Data normal = 0.0f; + LV2_PortRangeHintDescriptor hint_descriptor = m_descriptor->PortRangeHints[port_index].HintDescriptor; + + /* set upper and lower, possibly adjusted to the sample rate */ + if (LV2_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + upper = m_descriptor->PortRangeHints[port_index].UpperBound * m_srate; + lower = m_descriptor->PortRangeHints[port_index].LowerBound * m_srate; + } else { + upper = m_descriptor->PortRangeHints[port_index].UpperBound; + lower = m_descriptor->PortRangeHints[port_index].LowerBound; + } + + if (LV2_IS_HINT_LOGARITHMIC(hint_descriptor)) { + /* FLT_EPSILON is defined as the different between 1.0 and the minimum + * float greater than 1.0. So, if lower is < FLT_EPSILON, it will be 1.0 + * and the logarithmic control will have a base of 1 and thus not change + */ + if (lower < FLT_EPSILON) lower = FLT_EPSILON; + } + + + if (LV2_IS_HINT_HAS_DEFAULT(hint_descriptor)) { + + if (LV2_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) { + normal = lower; + } else if (LV2_IS_HINT_DEFAULT_LOW(hint_descriptor)) { + if (LV2_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.75 + log(upper) * 0.25); + } else { + normal = lower * 0.75 + upper * 0.25; + } + } else if (LV2_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) { + if (LV2_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.5 + log(upper) * 0.5); + } else { + normal = lower * 0.5 + upper * 0.5; + } + } else if (LV2_IS_HINT_DEFAULT_HIGH(hint_descriptor)) { + if (LV2_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower) * 0.25 + log(upper) * 0.75); + } else { + normal = lower * 0.25 + upper * 0.75; + } + } else if (LV2_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) { + normal = upper; + } else if (LV2_IS_HINT_DEFAULT_0(hint_descriptor)) { + normal = 0.0; + } else if (LV2_IS_HINT_DEFAULT_1(hint_descriptor)) { + normal = 1.0; + } else if (LV2_IS_HINT_DEFAULT_100(hint_descriptor)) { + normal = 100.0; + } else if (LV2_IS_HINT_DEFAULT_440(hint_descriptor)) { + normal = 440.0; + } + } else { // No default hint + if (LV2_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { + normal = lower; + } else if (LV2_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { + normal = upper; + } + } + + info->min_val(lower); + info->default_val(normal); + info->max_val(upper); +#endif +} + + +} // namespace Om + diff --git a/src/libs/engine/LV2Plugin.h b/src/libs/engine/LV2Plugin.h new file mode 100644 index 00000000..c3d3038a --- /dev/null +++ b/src/libs/engine/LV2Plugin.h @@ -0,0 +1,76 @@ +/* This file is part of Om. Copyright (C) 2006 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 LV2PLUGIN_H +#define LV2PLUGIN_H + +#include <string> +#include <slv2/slv2.h> +#include "util/types.h" +#include "NodeBase.h" +#include "Plugin.h" + +namespace Om { + +class PortInfo; + + +/** An instance of a LV2 plugin. + * + * \ingroup engine + */ +class LV2Plugin : public NodeBase +{ +public: + LV2Plugin(const string& name, + size_t poly, + Patch* parent, + const SLV2Plugin* plugin, + samplerate srate, + size_t buffer_size); + + virtual ~LV2Plugin(); + + virtual bool instantiate(); + + void activate(); + void deactivate(); + + void run(size_t nframes); + + void set_port_buffer(size_t voice, size_t port_num, void* buf); + + const Plugin* plugin() const { return m_om_plugin; } + void plugin(const Plugin* const p) { m_om_plugin = p; } + +protected: + // Prevent copies (undefined) + LV2Plugin(const LV2Plugin& copy); + LV2Plugin& operator=(const LV2Plugin&); + + void get_port_vals(ulong port_index, PortInfo* info); + + const SLV2Plugin* m_lv2_plugin; + SLV2Instance** m_instances; + + const Plugin* m_om_plugin; +}; + + +} // namespace Om + +#endif // LV2PLUGIN_H + diff --git a/src/libs/engine/LashDriver.cpp b/src/libs/engine/LashDriver.cpp new file mode 100644 index 00000000..f42f87f2 --- /dev/null +++ b/src/libs/engine/LashDriver.cpp @@ -0,0 +1,159 @@ +/* This file is part of Om. Copyright (C) 2006 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 <cassert> +#include "OmApp.h" + +using std::cerr; using std::cout; using std::endl; +using std::string; + +namespace Om { + + +LashDriver::LashDriver(OmApp* app, lash_args_t* args) +: m_app(app), + m_client(NULL), + m_alsa_client_id(0) // FIXME: appropriate sentinel? +{ + m_client = lash_init(args, PACKAGE_NAME, + /*LASH_Config_Data_Set|LASH_Config_File*/0, LASH_PROTOCOL(2, 0)); + if (m_client == NULL) { + cerr << "[LashDriver] Failed to connect to LASH. Session management will not function." << endl; + } else { + cout << "[LashDriver] Lash initialised" << endl; + lash_event_t* event = lash_event_new_with_type(LASH_Client_Name); + lash_event_set_string(event, "Om"); + lash_send_event(m_client, event); + } +} + + +/** Set the Jack client name to be sent to LASH. + * The name isn't actually send until restore_finished() is called. + */ +void +LashDriver::set_jack_client_name(const char* name) +{ + m_jack_client_name = name; + lash_jack_client_name(m_client, m_jack_client_name.c_str()); +} + + +/** Set the Alsa client ID to be sent to LASH. + * The name isn't actually send until restore_finished() is called. + */ +void +LashDriver::set_alsa_client_id(int id) +{ + m_alsa_client_id = id; + lash_alsa_client_id(m_client, m_alsa_client_id); +} + + +/** Notify LASH of our port names so it can restore connections. + * The Alsa client ID and Jack client name MUST be set before calling + * this function. + */ +void +LashDriver::restore_finished() +{ + assert(m_jack_client_name != ""); + assert(m_alsa_client_id != 0); + + cerr << "LASH RESTORE FINISHED " << m_jack_client_name << " - " << m_alsa_client_id << endl; + + lash_jack_client_name(m_client, m_jack_client_name.c_str()); + lash_alsa_client_id(m_client, m_alsa_client_id); +} + + +void +LashDriver::process_events() +{ + assert(m_client != NULL); + + 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(); + } else { + cerr << "[LashDriver] WARNING: Unhandled lash event, type " << static_cast<int>(type) << endl; + } +} + + +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); +} + + +} // namespace Om diff --git a/src/libs/engine/LashDriver.h b/src/libs/engine/LashDriver.h new file mode 100644 index 00000000..dedf1c6b --- /dev/null +++ b/src/libs/engine/LashDriver.h @@ -0,0 +1,57 @@ +/* This file is part of Om. Copyright (C) 2006 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 <pthread.h> +#include <string> +using std::string; + +namespace Om { + +class OmApp; + + +/** Handles all support for LASH session management. + */ +class LashDriver +{ +public: + LashDriver(OmApp* app, lash_args_t* args); + + bool enabled() { return (m_client != NULL && lash_enabled(m_client)); } + void process_events(); + void set_jack_client_name(const char* name); + void set_alsa_client_id(int id); + void restore_finished(); + +private: + OmApp* m_app; + lash_client_t* m_client; + + int m_alsa_client_id; + string m_jack_client_name; + + void handle_event(lash_event_t* conf); + void handle_config(lash_config_t* conf); +}; + + +} // namespace Om + +#endif // LASHDRIVER_H diff --git a/src/libs/engine/List.h b/src/libs/engine/List.h new file mode 100644 index 00000000..cc51bc5d --- /dev/null +++ b/src/libs/engine/List.h @@ -0,0 +1,416 @@ +/* This file is part of Om. Copyright (C) 2006 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 LIST_H +#define LIST_H + +#include <cassert> +#include "util/types.h" +#include "MaidObject.h" + + +/** A node in a List. + * + * This class is (unusually) exposed to the user to allow list operations + * to be realtime safe (ie so events can allocate list nodes in other threads + * and then insert them in the realtime thread. + */ +template <typename T> +class ListNode : public MaidObject +{ +public: + ListNode(T elem) : m_elem(elem), m_next(NULL), m_prev(NULL) {} + virtual ~ListNode() {} + + ListNode* next() const { return m_next; } + void next(ListNode* ln) { m_next = ln; } + ListNode* prev() const { return m_prev; } + void prev(ListNode* ln) { m_prev = ln; } + T& elem() { return m_elem;} + const T& elem() const { return m_elem; } + +private: + // Prevent copies (undefined) + ListNode(const ListNode& copy); + ListNode& operator=(const ListNode& copy); + + T m_elem; + ListNode* m_next; + ListNode* m_prev; +}; + + + +/** A realtime safe, (partially) thread safe doubly linked list. + * + * Elements can be added safely while another thread is reading the list. See + * documentation for specific functions for realtime/thread safeness. + */ +template <typename T> +class List : public MaidObject +{ +public: + List() : m_head(NULL), m_tail(NULL), m_size(0), m_end_iter(this), m_const_end_iter(this) + { + m_end_iter.m_listnode = NULL; + m_end_iter.m_next = NULL; + m_const_end_iter.m_listnode = NULL; + m_const_end_iter.m_next = NULL; + } + ~List(); + + void push_back(ListNode<T>* elem); + ListNode<T>* remove(const T elem); + + void clear(); + size_t size() const { return m_size; } + + class iterator; + + /** Realtime safe const iterator for a List. */ + class const_iterator + { + public: + const_iterator(const List<T>* const list); + const_iterator(const iterator& i) + : m_list(i.m_list), m_listnode(i.m_listnode), m_next(i.m_next) {} + + inline const T& operator*(); + inline const_iterator& operator++(); + inline bool operator!=(const const_iterator& iter) const; + inline bool operator!=(const iterator& iter) const; + + friend class List<T>; + + private: + const List<T>* const m_list; + const ListNode<T>* m_listnode; + const ListNode<T>* m_next; // use this instead of m_listnode->next() to allow deleting + }; + + + /** Realtime safe iterator for a List. */ + class iterator + { + public: + iterator(List<T>* const list); + + inline T& operator*(); + inline iterator& operator++(); + inline bool operator!=(const iterator& iter) const; + inline bool operator!=(const const_iterator& iter) const; + + friend class List<T>; + friend class List<T>::const_iterator; + + private: + const List<T>* m_list; + ListNode<T>* m_listnode; + ListNode<T>* m_next; // use this instead of m_listnode->next() to allow deleting + }; + + + ListNode<T>* remove(const iterator iter); + + iterator begin(); + const iterator end() const; + + const_iterator begin() const; + //const_iterator end() const; + +private: + // Prevent copies (undefined) + List(const List& copy); + List& operator=(const List& copy); + + ListNode<T>* m_head; + ListNode<T>* m_tail; + size_t m_size; + iterator m_end_iter; + const_iterator m_const_end_iter; +}; + + + + +template <typename T> +List<T>::~List<T>() +{ + clear(); +} + + +/** Clear the list, deleting all ListNodes contained (but NOT their contents!) + * + * Not realtime safe. + */ +template <typename T> +void +List<T>::clear() +{ + if (m_head == NULL) return; + + ListNode<T>* node = m_head; + ListNode<T>* next = NULL; + + while (node != NULL) { + next = node->next(); + delete node; + node = next; + } + m_tail = m_head = NULL; + m_size = 0; +} + + +/** Add an element to the list. + * + * This method can be called while another thread is reading the list. + * Realtime safe. + */ +template <typename T> +void +List<T>::push_back(ListNode<T>* const ln) +{ + assert(ln != NULL); + + ln->next(NULL); + // FIXME: atomicity? relevant? + if (m_head == NULL) { + ln->prev(NULL); + m_head = m_tail = ln; + } else { + ln->prev(m_tail); + m_tail->next(ln); + m_tail = ln; + } + ++m_size; +} + + +/** Remove an element from the list. + * + * This function is realtime safe - it is the caller's responsibility to + * delete the returned ListNode, or there will be a leak. + */ +template <typename T> +ListNode<T>* +List<T>::remove(const T elem) +{ + // FIXME: atomicity? + ListNode<T>* n = m_head; + while (n != NULL) { + if (n->elem() == elem) + break; + n = n->next(); + } + if (n != NULL) { + if (n == m_head) m_head = m_head->next(); + if (n == m_tail) m_tail = m_tail->prev(); + if (n->prev() != NULL) + n->prev()->next(n->next()); + if (n->next() != NULL) + n->next()->prev(n->prev()); + --m_size; + if (m_size == 0) m_head = m_tail = NULL; // FIXME: Shouldn't be necessary + return n; + } + return NULL; +} + + +/** Remove an element from the list using an iterator. + * + * This function is realtime safe - it is the caller's responsibility to + * delete the returned ListNode, or there will be a leak. + */ +template <typename T> +ListNode<T>* +List<T>::remove(const iterator iter) +{ + ListNode<T>* n = iter.m_listnode; + if (n != NULL) { + if (n == m_head) m_head = m_head->next(); + if (n == m_tail) m_tail = m_tail->prev(); + if (n->prev() != NULL) + n->prev()->next(n->next()); + if (n->next() != NULL) + n->next()->prev(n->prev()); + --m_size; + if (m_size == 0) m_head = m_tail = NULL; // FIXME: Shouldn't be necessary + return n; + } + return NULL; +} + + +//// Iterator stuff //// + +template <typename T> +List<T>::iterator::iterator(List<T>* list) +: m_list(list), + m_listnode(NULL), + m_next(NULL) +{ +} + + +template <typename T> +T& +List<T>::iterator::operator*() +{ + assert(m_listnode != NULL); + return m_listnode->elem(); +} + + +template <typename T> +inline typename List<T>::iterator& +List<T>::iterator::operator++() +{ + assert(m_listnode != NULL); + m_listnode = m_next; + if (m_next != NULL) + m_next = m_next->next(); + else + m_next = NULL; + + return *this; +} + + +template <typename T> +inline bool +List<T>::iterator::operator!=(const iterator& iter) const +{ + return (m_listnode != iter.m_listnode); +} + + +template <typename T> +inline bool +List<T>::iterator::operator!=(const const_iterator& iter) const +{ + return (m_listnode != iter.m_listnode); +} + + +template <typename T> +inline typename List<T>::iterator +List<T>::begin() +{ + typename List<T>::iterator iter(this); + iter.m_listnode = m_head; + if (m_head != NULL) + iter.m_next = m_head->next(); + else + iter.m_next = NULL; + return iter; +} + + +template <typename T> +inline const typename List<T>::iterator +List<T>::end() const +{ + /*typename List<T>::iterator iter(this); + iter.m_listnode = NULL; + iter.m_next = NULL; + return iter;*/ + return m_end_iter; +} + + + +/// const_iterator stuff /// + + +template <typename T> +List<T>::const_iterator::const_iterator(const List<T>* const list) +: m_list(list), + m_listnode(NULL), + m_next(NULL) +{ +} + + +template <typename T> +const T& +List<T>::const_iterator::operator*() +{ + assert(m_listnode != NULL); + return m_listnode->elem(); +} + + +template <typename T> +inline typename List<T>::const_iterator& +List<T>::const_iterator::operator++() +{ + assert(m_listnode != NULL); + m_listnode = m_next; + if (m_next != NULL) + m_next = m_next->next(); + else + m_next = NULL; + + return *this; +} + + +template <typename T> +inline bool +List<T>::const_iterator::operator!=(const const_iterator& iter) const +{ + return (m_listnode != iter.m_listnode); +} + + +template <typename T> +inline bool +List<T>::const_iterator::operator!=(const iterator& iter) const +{ + return (m_listnode != iter.m_listnode); +} + + +template <typename T> +inline typename List<T>::const_iterator +List<T>::begin() const +{ + typename List<T>::const_iterator iter(this); + iter.m_listnode = m_head; + if (m_head != NULL) + iter.m_next = m_head->next(); + else + iter.m_next = NULL; + return iter; +} + +#if 0 +template <typename T> +inline typename List<T>::const_iterator +List<T>::end() const +{ + /*typename List<T>::const_iterator iter(this); + iter.m_listnode = NULL; + iter.m_next = NULL; + return iter;*/ + return m_const_end_iter; +} +#endif + +#endif // LIST_H diff --git a/src/libs/engine/Maid.cpp b/src/libs/engine/Maid.cpp new file mode 100644 index 00000000..3bebdedb --- /dev/null +++ b/src/libs/engine/Maid.cpp @@ -0,0 +1,46 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Maid.h" +#include "MaidObject.h" + + +Maid::Maid(size_t size) +: m_objects(size) +{ +} + + +Maid::~Maid() +{ + cleanup(); +} + + +/** Free all the objects in the queue (passed by push()). + */ +void +Maid::cleanup() +{ + MaidObject* obj = NULL; + + while (!m_objects.is_empty()) { + obj = m_objects.pop(); + delete obj; + } +} + + diff --git a/src/libs/engine/Maid.h b/src/libs/engine/Maid.h new file mode 100644 index 00000000..75412186 --- /dev/null +++ b/src/libs/engine/Maid.h @@ -0,0 +1,65 @@ +/* This file is part of Om. Copyright (C) 2006 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 MAID_H +#define MAID_H + +#include "MaidObject.h" +#include "util/Queue.h" + + +/** Explicitly driven garbage collector. + * + * This is how realtime parts of the code can schedule the deletion of + * objects - push() is realtime safe. + * + * cleanup() is meant to be called periodically to free memory, often + * enough to prevent the queue from overdflowing. This is done by the + * main thread, in OmApp. + * + * \ingroup engine + */ +class Maid +{ +public: + Maid(size_t size); + ~Maid(); + + inline void push(MaidObject* obj); + + void cleanup(); + +private: + // Prevent copies + Maid(const Maid&); + Maid& operator=(const Maid&); + + Queue<MaidObject*> m_objects; +}; + + +/** Push an event to be deleted. Realtime safe. + */ +inline void +Maid::push(MaidObject* obj) +{ + if (obj != NULL) + m_objects.push(obj); +} + + +#endif // MAID_H + diff --git a/src/libs/engine/MaidObject.h b/src/libs/engine/MaidObject.h new file mode 100644 index 00000000..d0060520 --- /dev/null +++ b/src/libs/engine/MaidObject.h @@ -0,0 +1,38 @@ +/* This file is part of Om. Copyright (C) 2006 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 MAIDOBJECT_H +#define MAIDOBJECT_H + + +/** An object that can be passed to the maid for deletion. + * + * \ingroup engine + */ +class MaidObject +{ +public: + MaidObject() {} + virtual ~MaidObject() {} + +private: + // Prevent copies + MaidObject(const MaidObject&); + MaidObject& operator=(const MaidObject&); +}; + + +#endif // MAIDOBJECT_H diff --git a/src/libs/engine/Makefile.am b/src/libs/engine/Makefile.am new file mode 100644 index 00000000..ae0b3e53 --- /dev/null +++ b/src/libs/engine/Makefile.am @@ -0,0 +1,259 @@ +SUBDIRS = tests +DIST_SUBDIRS = events + +AM_CXXFLAGS = @JACK_CFLAGS@ @LOSC_CFLAGS@ @ALSA_CFLAGS@ @LASH_CFLAGS@ @SLV2_CFLAGS@ -I$(top_srcdir)/src/common -I$(top_srcdir)/src/engine/events -fno-exceptions -fno-rtti + +MAINTAINERCLEANFILES = Makefile.in + +common_SOURCES = \ + util.h \ + tuning.h \ + Node.h \ + NodeBase.h \ + NodeBase.cpp \ + InternalNode.h \ + Patch.h \ + Patch.cpp \ + NodeFactory.h \ + NodeFactory.cpp \ + Om.h \ + Om.cpp \ + OmApp.h \ + OmApp.cpp \ + JackAudioDriver.h \ + JackAudioDriver.cpp \ + OSCReceiver.h \ + OSCReceiver.cpp \ + Responder.h \ + OSCResponder.h \ + OSCResponder.cpp \ + ClientKey.h \ + ClientBroadcaster.h \ + ClientBroadcaster.cpp \ + ObjectSender.h \ + ObjectSender.cpp \ + OSCClient.h \ + OSCClient.cpp \ + Buffer.h \ + Buffer.cpp \ + Port.h \ + Port.cpp \ + PortBase.h \ + PortBase.cpp \ + InputPort.h \ + InputPort.cpp \ + OutputPort.h \ + OutputPort.cpp \ + MidiMessage.h \ + MidiNoteNode.h \ + MidiNoteNode.cpp \ + MidiTriggerNode.h \ + MidiTriggerNode.cpp \ + MidiControlNode.h \ + MidiControlNode.cpp \ + BridgeNode.h \ + BridgeNode.cpp \ + ControlInputNode.h \ + ControlInputNode.cpp \ + ControlOutputNode.h \ + ControlOutputNode.cpp \ + AudioInputNode.h \ + AudioInputNode.cpp \ + AudioOutputNode.h \ + AudioOutputNode.cpp \ + MidiInputNode.h \ + MidiInputNode.cpp \ + MidiOutputNode.h \ + MidiOutputNode.cpp \ + Event.h \ + Event.cpp \ + QueuedEvent.h \ + EventSource.h \ + QueuedEventSource.h \ + QueuedEventSource.cpp \ + QueuedEngineInterface.h \ + QueuedEngineInterface.cpp \ + OmObject.h \ + Maid.h \ + Maid.cpp \ + MaidObject.h \ + Tree.h \ + ClientRecord.h \ + PortInfo.h \ + PluginLibrary.h \ + Plugin.h \ + Array.h \ + List.h \ + PostProcessor.h \ + PostProcessor.cpp \ + Connection.h \ + Connection.cpp \ + ConnectionBase.h \ + ConnectionBase.cpp \ + ObjectStore.h \ + ObjectStore.cpp \ + TransportNode.h \ + TransportNode.cpp \ + Driver.h \ + AudioDriver.h \ + MidiDriver.h \ + midi.h \ + ../common/util/Semaphore.h \ + ../common/util/types.h \ + ../common/util/Path.h \ + ../common/util/Queue.h \ + ../common/interface/ClientInterface.h \ + ../common/interface/EngineInterface.h \ + instantiations.cpp + +# Events +common_SOURCES += \ + events/RegisterClientEvent.h \ + events/RegisterClientEvent.cpp \ + events/UnregisterClientEvent.h \ + events/UnregisterClientEvent.cpp \ + events/PingQueuedEvent.h \ + events/ActivateEvent.h \ + events/ActivateEvent.cpp \ + events/DeactivateEvent.h \ + events/DeactivateEvent.cpp \ + events/SetPortValueEvent.h \ + events/SetPortValueEvent.cpp \ + events/SetPortValueQueuedEvent.h \ + events/SetPortValueQueuedEvent.cpp \ + events/NoteOnEvent.h \ + events/NoteOnEvent.cpp \ + events/NoteOffEvent.h \ + events/NoteOffEvent.cpp \ + events/AllNotesOffEvent.h \ + events/AllNotesOffEvent.cpp \ + events/ConnectionEvent.h \ + events/ConnectionEvent.cpp \ + events/DisconnectionEvent.h \ + events/DisconnectionEvent.cpp \ + events/DisconnectNodeEvent.h \ + events/DisconnectNodeEvent.cpp \ + events/DisconnectPortEvent.h \ + events/DisconnectPortEvent.cpp \ + events/DestroyEvent.h \ + events/DestroyEvent.cpp \ + events/AddNodeEvent.h \ + events/AddNodeEvent.cpp \ + events/SetMetadataEvent.h \ + events/SetMetadataEvent.cpp \ + events/RequestMetadataEvent.h \ + events/RequestMetadataEvent.cpp \ + events/RequestPortValueEvent.h \ + events/RequestPortValueEvent.cpp \ + events/RequestAllObjectsEvent.h \ + events/RequestAllObjectsEvent.cpp \ + events/RequestPluginsEvent.h \ + events/RequestPluginsEvent.cpp \ + events/CreatePatchEvent.h \ + events/CreatePatchEvent.cpp \ + events/LoadPluginsEvent.h \ + events/LoadPluginsEvent.cpp \ + events/EnablePatchEvent.h \ + events/EnablePatchEvent.cpp \ + events/DisablePatchEvent.h \ + events/DisablePatchEvent.cpp \ + events/ClearPatchEvent.h \ + events/ClearPatchEvent.cpp \ + events/RenameEvent.h \ + events/RenameEvent.cpp \ + events/MidiLearnEvent.h \ + events/MidiLearnEvent.cpp + +if WITH_JACK_MIDI +common_SOURCES += \ + JackMidiDriver.h \ + JackMidiDriver.cpp +endif + +if WITH_ALSA_MIDI +common_SOURCES += \ + AlsaMidiDriver.h \ + AlsaMidiDriver.cpp +endif + +if WITH_LADSPA +common_SOURCES += \ + LADSPAPlugin.h \ + LADSPAPlugin.cpp +endif + +if WITH_DSSI +common_SOURCES += \ + DSSIPlugin.h \ + DSSIPlugin.cpp \ + events/DSSIConfigureEvent.cpp \ + events/DSSIConfigureEvent.h \ + events/DSSIControlEvent.cpp \ + events/DSSIControlEvent.h \ + events/DSSIProgramEvent.cpp \ + events/DSSIProgramEvent.h \ + events/DSSIUpdateEvent.cpp \ + events/DSSIUpdateEvent.h +endif + +if WITH_LV2 +common_SOURCES += \ + LV2Plugin.h \ + LV2Plugin.cpp +endif + +if WITH_LASH +common_SOURCES += \ + LashDriver.h \ + LashDriver.cpp \ + LashRestoreDoneEvent.h +endif + + +# +# Non-installed library to link stand-alone and in-process build against +# + +noinst_LTLIBRARIES = libom.la +libom_la_SOURCES = $(common_SOURCES) + + + +# +# Stand-alone engine +# +if BUILD_ENGINE + +bin_PROGRAMS = om +om_DEPENDENCIES = libom.la +om_LDADD = @JACK_LIBS@ @LOSC_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @SLV2_LIBS@ -lrt libom.la + +om_SOURCES = \ + main.cpp \ + cmdline.h \ + cmdline.c + +endif # BUILD_ENGINE + + + +# +# Jack internal client +# +if BUILD_IN_PROCESS_ENGINE + + +# FIXME: broken + + +# FIXME: Figure out how to get this properly +omdir = $(prefix)/lib/jack + +om_la_CFLAGS = -fPIC +om_LTLIBRARIES = om.la +om_la_LDFLAGS = -module -avoid-version @JACK_LIBS@ @LOSC_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @SLV2_LIBS@ +om_la_SOURCES = OmInProcess.cpp + +endif # BUILD_IN_PROCESS_ENGINE + + diff --git a/src/libs/engine/MidiControlNode.cpp b/src/libs/engine/MidiControlNode.cpp new file mode 100644 index 00000000..2116c828 --- /dev/null +++ b/src/libs/engine/MidiControlNode.cpp @@ -0,0 +1,133 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiControlNode.h" +#include <math.h> +#include "Om.h" +#include "OmApp.h" +#include "PostProcessor.h" +#include "MidiLearnEvent.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "util.h" +#include "midi.h" + + +namespace Om { + + +MidiControlNode::MidiControlNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: InternalNode(path, 1, parent, srate, buffer_size), + m_learning(false) +{ + m_num_ports = 7; + m_ports.alloc(m_num_ports); + + m_midi_in_port = new InputPort<MidiMessage>(this, "MIDI In", 0, 1, + new PortInfo("MIDI In", MIDI, INPUT), m_buffer_size); + m_ports.at(0) = m_midi_in_port; + + m_param_port = new InputPort<sample>(this, "Controller Number", 1, 1, + new PortInfo("Controller Number", CONTROL, INPUT, INTEGER, 60, 0, 127), 1); + m_ports.at(1) = m_param_port; + m_log_port = new InputPort<sample>(this, "Logarithmic", 2, 1, + new PortInfo("Logarithmic", CONTROL, INPUT, TOGGLE, 0, 0, 1), 1); + m_ports.at(2) = m_log_port; + + m_min_port = new InputPort<sample>(this, "Min", 3, 1, + new PortInfo("Min", CONTROL, INPUT, NONE, 0, 0, 65535), 1); + m_ports.at(3) = m_min_port; + + m_max_port = new InputPort<sample>(this, "Max", 4, 1, + new PortInfo("Max", CONTROL, INPUT, NONE, 1, 0, 65535), 1); + m_ports.at(4) = m_max_port; + + m_audio_port = new OutputPort<sample>(this, "Out (AR)", 5, 1, + new PortInfo("Out (AR)", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(5) = m_audio_port; + + m_control_port = new OutputPort<sample>(this, "Out (CR)", 6, 1, + new PortInfo("Out (CR)", CONTROL, OUTPUT, 0, 0, 1), 1); + m_ports.at(6) = m_control_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("midi_control_in"); + m_plugin.name("Om Control Node (MIDI)"); +} + + +void +MidiControlNode::run(size_t nframes) +{ + InternalNode::run(nframes); + + MidiMessage ev; + + for (size_t i=0; i < m_midi_in_port->buffer(0)->filled_size(); ++i) { + ev = m_midi_in_port->buffer(0)->value_at(i); + + if ((ev.buffer[0] & 0xF0) == MIDI_CMD_CONTROL) + control(ev.buffer[1], ev.buffer[2], ev.time); + } +} + + +void +MidiControlNode::control(uchar control_num, uchar val, samplecount offset) +{ + assert(offset < m_buffer_size); + + sample scaled_value; + + const sample nval = (val / 127.0f); // normalized [0, 1] + + if (m_learning) { + assert(m_learn_event != NULL); + m_param_port->set_value(control_num, offset); + assert(m_param_port->buffer(0)->value_at(0) == control_num); + m_learn_event->set_value(control_num); + m_learn_event->execute(offset); + om->post_processor()->push(m_learn_event); + om->post_processor()->signal(); + m_learning = false; + m_learn_event = NULL; + } + + if (m_log_port->buffer(0)->value_at(0) > 0.0f) { + // haaaaack, stupid negatives and logarithms + sample log_offset = 0; + if (m_min_port->buffer(0)->value_at(0) < 0) + log_offset = fabs(m_min_port->buffer(0)->value_at(0)); + const sample min = log(m_min_port->buffer(0)->value_at(0)+1+log_offset); + const sample max = log(m_max_port->buffer(0)->value_at(0)+1+log_offset); + scaled_value = expf(nval * (max - min) + min) - 1 - log_offset; + } else { + const sample min = m_min_port->buffer(0)->value_at(0); + const sample max = m_max_port->buffer(0)->value_at(0); + scaled_value = ((nval) * (max - min)) + min; + } + + if (control_num == m_param_port->buffer(0)->value_at(0)) { + m_control_port->set_value(scaled_value, offset); + m_audio_port->set_value(scaled_value, offset); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/MidiControlNode.h b/src/libs/engine/MidiControlNode.h new file mode 100644 index 00000000..b40e7631 --- /dev/null +++ b/src/libs/engine/MidiControlNode.h @@ -0,0 +1,72 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDICONTROLNODE_H +#define MIDICONTROLNODE_H + +#include <string> +#include "NodeBase.h" +#include "InternalNode.h" +using std::string; + +namespace Om { + +class MidiLearnResponseEvent; +class MidiMessage; +template <typename T> class InputPort; +template <typename T> class OutputPort; + + +/** MIDI control input node. + * + * Creating one of these nodes is how a user makes "MIDI Bindings". Note that + * this node will always be monophonic, the poly parameter is ignored. + * + * \ingroup engine + */ +class MidiControlNode : public InternalNode +{ +public: + MidiControlNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + void run(size_t nframes); + + void control(uchar control_num, uchar val, samplecount offset); + + void learn(MidiLearnResponseEvent* ev) { m_learning = true; m_learn_event = ev; } + +private: + // Disallow copies (undefined) + MidiControlNode(const MidiControlNode& copy); + MidiControlNode& operator=(const MidiControlNode&); + + bool m_learning; + + InputPort<MidiMessage>* m_midi_in_port; + InputPort<sample>* m_param_port; + InputPort<sample>* m_log_port; + InputPort<sample>* m_min_port; + InputPort<sample>* m_max_port; + OutputPort<sample>* m_control_port; + OutputPort<sample>* m_audio_port; + + MidiLearnResponseEvent* m_learn_event; +}; + + +} // namespace Om + +#endif // MIDICONTROLNODE_H diff --git a/src/libs/engine/MidiDriver.h b/src/libs/engine/MidiDriver.h new file mode 100644 index 00000000..b8fd71ca --- /dev/null +++ b/src/libs/engine/MidiDriver.h @@ -0,0 +1,78 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDIDRIVER_H +#define MIDIDRIVER_H + +#include "Driver.h" +#include <iostream> +using std::cout; using std::endl; + +namespace Om { + +class MidiMessage; + + +/** Midi driver abstract base class. + * + * \ingroup engine + */ +class MidiDriver : public Driver<MidiMessage> +{ +public: + /** Prepare events (however neccessary) for the specified block (realtime safe) */ + virtual void prepare_block(const samplecount block_start, const samplecount block_end) = 0; +}; + + + +/** Dummy MIDIDriver. + * + * Not abstract, all functions are dummies. One of these will be allocated and + * "used" if no working MIDI driver is loaded. (Doing it this way as opposed to + * just making MidiDriver have dummy functions makes sure any existing MidiDriver + * derived class actually implements the required functions). + * + * \ingroup engine + */ +class DummyMidiDriver : public MidiDriver +{ +public: + DummyMidiDriver() { + cout << "[DummyMidiDriver] Started Dummy MIDI driver." << endl; + } + + ~DummyMidiDriver() {} + + void activate() {} + void deactivate() {} + + bool is_activated() const { return false; } + bool is_enabled() const { return false; } + + void enable() {} + void disable() {} + + DriverPort* create_port(PortBase<MidiMessage>* patch_port) { return NULL; } + + void prepare_block(const samplecount block_start, const samplecount block_end) {} +}; + + + +} // namespace Om + +#endif // MIDIDRIVER_H diff --git a/src/libs/engine/MidiInputNode.cpp b/src/libs/engine/MidiInputNode.cpp new file mode 100644 index 00000000..25310807 --- /dev/null +++ b/src/libs/engine/MidiInputNode.cpp @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiInputNode.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Plugin.h" +#include "PortInfo.h" +#include "Patch.h" +#include "MidiMessage.h" + +namespace Om { + + +MidiInputNode::MidiInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<MidiMessage>(path, 1, parent, srate, buffer_size) +{ + OutputPort<MidiMessage>* internal_port = new OutputPort<MidiMessage>(this, "in", 0, m_poly, + new PortInfo("in", MIDI, OUTPUT), m_buffer_size); + InputPort<MidiMessage>* external_port = new InputPort<MidiMessage>(parent, m_name, 0, m_poly, + new PortInfo(m_name, MIDI, INPUT), m_buffer_size); + external_port->tie(internal_port); + m_external_port = external_port; + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("midi_input"); + m_plugin.name("Om Patch MIDI Input Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/MidiInputNode.h b/src/libs/engine/MidiInputNode.h new file mode 100644 index 00000000..0986a267 --- /dev/null +++ b/src/libs/engine/MidiInputNode.h @@ -0,0 +1,43 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDIINPUTNODE_H +#define MIDIINPUTNODE_H + +#include <string> +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +class MidiMessage; + + +/** MIDI input BridgeNode. + * + * \ingroup engine + */ +class MidiInputNode : public BridgeNode<MidiMessage> +{ +public: + MidiInputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // MIDIINPUTNODE_H diff --git a/src/libs/engine/MidiMessage.h b/src/libs/engine/MidiMessage.h new file mode 100644 index 00000000..8dcb3e67 --- /dev/null +++ b/src/libs/engine/MidiMessage.h @@ -0,0 +1,51 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDIMESSAGE_H +#define MIDIMESSAGE_H + +namespace Om { + + +/** Midi Message (the type a MIDI port connects to). + * + * For the time being (ie while it's still possible) this is binary + * compatible with jack_default_midi_event_t. Don't mess that up without + * dealing with all the repercussions (in the MidiDriver's). + * + * Note that with the current implementation one of these is NOT valid + * across process cycles (since the buffer is just a pointer to the bufferr + * given to us by Jack itself. In other words, if one of these needs to be + * stored, it needs to be deep copied. Less copying anyway.. :/ + */ +struct MidiMessage +{ +public: + MidiMessage() : time(0), size(0), buffer(NULL) {} + + // Really just to allow setting to zero for generic algos + MidiMessage(const int& i) : time(0), size(0), buffer(NULL) {} + + samplecount time; + size_t size; + unsigned char* buffer; +}; + + +} // namespace Om + + +#endif // MIDIMESSAGE_H diff --git a/src/libs/engine/MidiNoteNode.cpp b/src/libs/engine/MidiNoteNode.cpp new file mode 100644 index 00000000..9dc32f4a --- /dev/null +++ b/src/libs/engine/MidiNoteNode.cpp @@ -0,0 +1,304 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiNoteNode.h" +#include <cmath> +#include <iostream> +#include "MidiMessage.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "Array.h" +#include "Om.h" +#include "OmApp.h" +#include "AudioDriver.h" +#include "util.h" +#include "midi.h" + +using std::cerr; using std::cout; using std::endl; + + +namespace Om { + + +MidiNoteNode::MidiNoteNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: InternalNode(path, poly, parent, srate, buffer_size), + m_voices(new Voice[poly]), + m_sustain(false) +{ + m_num_ports = 5; + m_ports.alloc(m_num_ports); + + m_midi_in_port = new InputPort<MidiMessage>(this, "MIDI In", 0, 1, + new PortInfo("MIDI In", MIDI, INPUT), m_buffer_size); + m_ports.at(0) = m_midi_in_port; + + m_freq_port = new OutputPort<sample>(this, "Frequency", 1, poly, + new PortInfo("Frequency", AUDIO, OUTPUT, 440, 0, 99999), m_buffer_size); + m_ports.at(1) = m_freq_port; + + m_vel_port = new OutputPort<sample>(this, "Velocity", 2, poly, + new PortInfo("Velocity", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(2) = m_vel_port; + + m_gate_port = new OutputPort<sample>(this, "Gate", 3, poly, + new PortInfo("Gate", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(3) = m_gate_port; + + m_trig_port = new OutputPort<sample>(this, "Trigger", 4, poly, + new PortInfo("Trigger", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(4) = m_trig_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("note_in"); + m_plugin.name("Om Note Node (MIDI, OSC)"); +} + + +MidiNoteNode::~MidiNoteNode() +{ + delete[] m_voices; +} + + +void +MidiNoteNode::run(size_t nframes) +{ + InternalNode::run(nframes); + + MidiMessage ev; + + for (size_t i=0; i < m_midi_in_port->buffer(0)->filled_size(); ++i) { + ev = m_midi_in_port->buffer(0)->value_at(i); + + switch (ev.buffer[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (ev.buffer[2] == 0) + note_off(ev.buffer[1], ev.time); + else + note_on(ev.buffer[1], ev.buffer[2], ev.time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(ev.buffer[1], ev.time); + break; + case MIDI_CMD_CONTROL: + switch (ev.buffer[1]) { + case MIDI_CTL_ALL_NOTES_OFF: + case MIDI_CTL_ALL_SOUNDS_OFF: + all_notes_off(ev.time); + break; + case MIDI_CTL_SUSTAIN: + if (ev.buffer[2] > 63) + sustain_on(); + else + sustain_off(ev.time); + break; + case MIDI_CMD_BENDER: + + break; + } + default: + break; + } + } +} + + +void +MidiNoteNode::note_on(uchar note_num, uchar velocity, samplecount offset) +{ + const jack_nframes_t time_stamp = om->audio_driver()->time_stamp(); + + assert(offset < m_buffer_size); + assert(note_num <= 127); + + Key* key = &m_keys[note_num]; + Voice* voice = NULL; + size_t voice_num = 0; + + // Look for free voices + for (size_t i=0; i < m_poly; ++i) { + if (m_voices[i].state == Voice::Voice::FREE) { + voice = &m_voices[i]; + voice_num = i; + break; + } + } + + // If we didn't find a free one, steal the oldest + if (voice == NULL) { + voice_num = 0; + voice = &m_voices[0]; + jack_nframes_t oldest_time = m_voices[0].time; + for (size_t i=1; i < m_poly; ++i) { + if (m_voices[i].time < oldest_time) { + voice = &m_voices[i]; + voice_num = i; + oldest_time = voice->time; + } + } + } + assert(voice != NULL); + assert(voice == &m_voices[voice_num]); + + //cerr << "[MidiNoteNode] Note on. Key " << (int)note_num << ", Voice " << voice_num << endl; + + // Update stolen key, if applicable + if (voice->state == Voice::Voice::ACTIVE) { + assert(m_keys[voice->note].voice == voice_num); + assert(m_keys[voice->note].state == Key::ON_ASSIGNED); + m_keys[voice->note].state = Key::Key::ON_UNASSIGNED; + //cerr << "[MidiNoteNode] Stole voice " << voice_num << endl; + } + + // Store key information for later reallocation on note off + key->state = Key::Key::ON_ASSIGNED; + key->voice = voice_num; + key->time = time_stamp; + + // Trigger voice + voice->state = Voice::Voice::ACTIVE; + voice->note = note_num; + voice->time = time_stamp; + + assert(m_keys[voice->note].state == Key::Key::ON_ASSIGNED); + assert(m_keys[voice->note].voice == voice_num); + + // one-sample jitter hack to avoid having to deal with trigger sample "next time" + if (offset == (samplecount)(m_buffer_size-1)) + --offset; + + m_freq_port->buffer(voice_num)->set(note_to_freq(note_num), offset); + m_vel_port->buffer(voice_num)->set(velocity/127.0, offset); + m_gate_port->buffer(voice_num)->set(1.0f, offset); + + // trigger (one sample) + m_trig_port->buffer(voice_num)->set(1.0f, offset, offset); + m_trig_port->buffer(voice_num)->set(0.0f, offset+1); + + assert(key->state == Key::Key::ON_ASSIGNED); + assert(voice->state == Voice::Voice::ACTIVE); + assert(key->voice == voice_num); + assert(m_voices[key->voice].note == note_num); +} + + +void +MidiNoteNode::note_off(uchar note_num, samplecount offset) +{ + assert(offset < m_buffer_size); + + Key* key = &m_keys[note_num]; + + if (key->state == Key::ON_ASSIGNED) { + // Assigned key, turn off voice and key + assert(m_voices[key->voice].state == Voice::ACTIVE); + assert(m_voices[key->voice].note == note_num); + key->state = Key::OFF; + + if ( ! m_sustain) + free_voice(key->voice, offset); + else + m_voices[key->voice].state = Voice::HOLDING; + } + + key->state = Key::OFF; +} + + +void +MidiNoteNode::free_voice(size_t voice, samplecount offset) +{ + // Find a key to reassign to the freed voice (the newest, if there is one) + Key* replace_key = NULL; + uchar replace_key_num = 0; + + for (uchar i = 0; i <= 127; ++i) { + if (m_keys[i].state == Key::ON_UNASSIGNED) { + if (replace_key == NULL || m_keys[i].time > replace_key->time) { + replace_key = &m_keys[i]; + replace_key_num = i; + } + } + } + + if (replace_key != NULL) { // Found a key to assign to freed voice + assert(&m_keys[replace_key_num] == replace_key); + assert(replace_key->state == Key::ON_UNASSIGNED); + + // Change the freq but leave the gate high and don't retrigger + m_freq_port->buffer(voice)->set(note_to_freq(replace_key_num), offset); + + replace_key->state = Key::ON_ASSIGNED; + replace_key->voice = voice; + m_keys[m_voices[voice].note].state = Key::ON_UNASSIGNED; + m_voices[voice].note = replace_key_num; + m_voices[voice].state = Voice::ACTIVE; + } else { + // No new note for voice, deactivate (set gate low) + //cerr << "[MidiNoteNode] Note off. Key " << (int)note_num << ", Voice " << voice << " Killed" << endl; + m_gate_port->buffer(voice)->set(0.0f, offset); + m_voices[voice].state = Voice::FREE; + } +} + + +void +MidiNoteNode::all_notes_off(samplecount offset) +{ + //cerr << "Note off starting at sample " << offset << endl; + assert(offset < m_buffer_size); + + // FIXME: set all keys to Key::OFF? + + for (size_t i=0; i < m_poly; ++i) { + m_gate_port->buffer(i)->set(0.0f, offset); + m_voices[i].state = Voice::FREE; + } +} + + +float +MidiNoteNode::note_to_freq(int num) +{ + static const float A4 = 440.0f; + if (num >= 0 && num <= 119) + return A4 * powf(2.0f, (float)(num - 57.0f) / 12.0f); + return 1.0f; // Some LADSPA plugins don't like freq=0 +} + + +void +MidiNoteNode::sustain_on() +{ + m_sustain = true; +} + + +void +MidiNoteNode::sustain_off(samplecount offset) +{ + m_sustain = false; + + for (size_t i=0; i < m_poly; ++i) + if (m_voices[i].state == Voice::HOLDING) + free_voice(i, offset); +} + + +} // namespace Om + diff --git a/src/libs/engine/MidiNoteNode.h b/src/libs/engine/MidiNoteNode.h new file mode 100644 index 00000000..bf302144 --- /dev/null +++ b/src/libs/engine/MidiNoteNode.h @@ -0,0 +1,87 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDINOTENODE_H +#define MIDINOTENODE_H + +#include <string> +#include "InternalNode.h" +#include "util/types.h" + +using std::string; + +namespace Om { + +class MidiMessage; +template <typename T> class InputPort; +template <typename T> class OutputPort; + + +/** MIDI note input node. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class MidiNoteNode : public InternalNode +{ +public: + MidiNoteNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + ~MidiNoteNode(); + + void run(size_t nframes); + + void note_on(uchar note_num, uchar velocity, samplecount offset); + void note_off(uchar note_num, samplecount offset); + void all_notes_off(samplecount offset); + + void sustain_on(); + void sustain_off(samplecount offset); + +private: + + /** Key, one for each key on the keyboard */ + struct Key { + enum State { OFF, ON_ASSIGNED, ON_UNASSIGNED }; + Key() : state(OFF), voice(0), time(0) {} + State state; size_t voice; samplecount time; + }; + + /** Voice, one of these always exists for each voice */ + struct Voice { + enum State { FREE, ACTIVE, HOLDING }; + Voice() : state(FREE), note(0) {} + State state; uchar note; samplecount time; + }; + + float note_to_freq(int num); + void free_voice(size_t voice, samplecount offset); + + Voice* m_voices; + Key m_keys[128]; + bool m_sustain; ///< Whether or not hold pedal is depressed + + InputPort<MidiMessage>* m_midi_in_port; + OutputPort<sample>* m_freq_port; + OutputPort<sample>* m_vel_port; + OutputPort<sample>* m_gate_port; + OutputPort<sample>* m_trig_port; +}; + + +} // namespace Om + +#endif // MIDINOTENODE_H diff --git a/src/libs/engine/MidiOutputNode.cpp b/src/libs/engine/MidiOutputNode.cpp new file mode 100644 index 00000000..33023cc6 --- /dev/null +++ b/src/libs/engine/MidiOutputNode.cpp @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiOutputNode.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Plugin.h" +#include "PortInfo.h" +#include "Patch.h" +#include "MidiMessage.h" + +namespace Om { + + +MidiOutputNode::MidiOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: BridgeNode<MidiMessage>(path, 1, parent, srate, buffer_size) +{ + OutputPort<MidiMessage>* external_port = new OutputPort<MidiMessage>(parent, m_name, 0, m_poly, + new PortInfo(m_name, MIDI, OUTPUT), m_buffer_size); + InputPort<MidiMessage>* internal_port = new InputPort<MidiMessage>(this, "out", 0, m_poly, + new PortInfo("out", MIDI, INPUT), m_buffer_size); + internal_port->tie(external_port); + m_external_port = external_port; + + m_num_ports = 1; + m_ports.alloc(m_num_ports); + m_ports.at(0) = internal_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("midi_output"); + m_plugin.name("Om Patch MIDI Output Node"); +} + + +} // namespace Om + diff --git a/src/libs/engine/MidiOutputNode.h b/src/libs/engine/MidiOutputNode.h new file mode 100644 index 00000000..06d8a892 --- /dev/null +++ b/src/libs/engine/MidiOutputNode.h @@ -0,0 +1,43 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDIOUTPUTNODE_H +#define MIDIOUTPUTNODE_H + +#include <string> +#include "BridgeNode.h" + +using std::string; + +namespace Om { + +class MidiMessage; + + +/** MIDI output BridgeNode. + * + * \ingroup engine + */ +class MidiOutputNode : public BridgeNode<MidiMessage> +{ +public: + MidiOutputNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); +}; + + +} // namespace Om + +#endif // MIDIOUTPUTNODE_H diff --git a/src/libs/engine/MidiTriggerNode.cpp b/src/libs/engine/MidiTriggerNode.cpp new file mode 100644 index 00000000..4fda80e7 --- /dev/null +++ b/src/libs/engine/MidiTriggerNode.cpp @@ -0,0 +1,124 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiTriggerNode.h" +#include <cmath> +#include "InputPort.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "util.h" +#include "midi.h" + +namespace Om { + + +MidiTriggerNode::MidiTriggerNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: InternalNode(path, 1, parent, srate, buffer_size) +{ + m_num_ports = 5; + m_ports.alloc(m_num_ports); + + m_midi_in_port = new InputPort<MidiMessage>(this, "MIDI In", 0, 1, + new PortInfo("MIDI In", MIDI, INPUT), m_buffer_size); + m_ports.at(0) = m_midi_in_port; + + m_note_port = new InputPort<sample>(this, "Note Number", 1, 1, + new PortInfo("Note Number", CONTROL, INPUT, INTEGER, 60, 0, 127), 1); + m_ports.at(1) = m_note_port; + + m_gate_port = new OutputPort<sample>(this, "Gate", 2, 1, + new PortInfo("Gate", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(2) = m_gate_port; + + m_trig_port = new OutputPort<sample>(this, "Trigger", 3, 1, + new PortInfo("Trigger", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(3) = m_trig_port; + + m_vel_port = new OutputPort<sample>(this, "Velocity", 4, poly, + new PortInfo("Velocity", AUDIO, OUTPUT, 0, 0, 1), m_buffer_size); + m_ports.at(4) = m_vel_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("trigger_in"); + m_plugin.name("Om Trigger Node (MIDI, OSC)"); +} + + +void +MidiTriggerNode::run(size_t nframes) +{ + InternalNode::run(nframes); + + MidiMessage ev; + + for (size_t i=0; i < m_midi_in_port->buffer(0)->filled_size(); ++i) { + ev = m_midi_in_port->buffer(0)->value_at(i); + + switch (ev.buffer[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (ev.buffer[2] == 0) + note_off(ev.buffer[1], ev.time); + else + note_on(ev.buffer[1], ev.buffer[2], ev.time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(ev.buffer[1], ev.time); + break; + case MIDI_CMD_CONTROL: + if (ev.buffer[1] == MIDI_CTL_ALL_NOTES_OFF + || ev.buffer[1] == MIDI_CTL_ALL_SOUNDS_OFF) + m_gate_port->buffer(0)->set(0.0f, ev.time); + default: + break; + } + } +} + + +void +MidiTriggerNode::note_on(uchar note_num, uchar velocity, samplecount offset) +{ + //std::cerr << "Note on starting at sample " << offset << std::endl; + assert(offset < m_buffer_size); + + const sample filter_note = m_note_port->buffer(0)->value_at(0); + if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uchar)filter_note)){ + + // See comments in MidiNoteNode::note_on (FIXME) + if (offset == (samplecount)(m_buffer_size-1)) + --offset; + + m_gate_port->buffer(0)->set(1.0f, offset); + m_trig_port->buffer(0)->set(1.0f, offset, offset); + m_trig_port->buffer(0)->set(0.0f, offset+1); + m_vel_port->buffer(0)->set(velocity/127.0f, offset); + } +} + + +void +MidiTriggerNode::note_off(uchar note_num, samplecount offset) +{ + assert(offset < m_buffer_size); + + if (note_num == lrintf(m_note_port->buffer(0)->value_at(0))) + m_gate_port->buffer(0)->set(0.0f, offset); +} + + +} // namespace Om + diff --git a/src/libs/engine/MidiTriggerNode.h b/src/libs/engine/MidiTriggerNode.h new file mode 100644 index 00000000..2f91c631 --- /dev/null +++ b/src/libs/engine/MidiTriggerNode.h @@ -0,0 +1,64 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDITRIGGERNODE_H +#define MIDITRIGGERNODE_H + +#include <string> +#include "InternalNode.h" + +using std::string; + +namespace Om { + +class MidiMessage; +template <typename T> class InputPort; +template <typename T> class OutputPort; + + +/** MIDI trigger input node. + * + * Just has a gate, for drums etc. A control port is used to select + * which note number is responded to. + * + * Note that this node is always monophonic, the poly parameter is ignored. + * (Should that change?) + * + * \ingroup engine + */ +class MidiTriggerNode : public InternalNode +{ +public: + MidiTriggerNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + void run(size_t nframes); + + void note_on(uchar note_num, uchar velocity, samplecount offset); + void note_off(uchar note_num, samplecount offset); + +private: + InputPort<MidiMessage>* m_midi_in_port; + InputPort<sample>* m_note_port; + OutputPort<sample>* m_gate_port; + OutputPort<sample>* m_trig_port; + OutputPort<sample>* m_vel_port; +}; + + +} // namespace Om + +#endif // MIDITRIGGERNODE_H diff --git a/src/libs/engine/Node.h b/src/libs/engine/Node.h new file mode 100644 index 00000000..d169e772 --- /dev/null +++ b/src/libs/engine/Node.h @@ -0,0 +1,120 @@ +/* This file is part of Om. Copyright (C) 2006 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 NODE_H +#define NODE_H + +#include <string> +#include "util/types.h" +#include "OmObject.h" +#include "Array.h" + +using std::string; + +template <typename T> class List; + +namespace Om { + +class Port; +template <typename T> class OutputPort; +class Plugin; +class Patch; +namespace Shared { + class ClientInterface; +} + + +/** A Node (or "module") in a Patch (which is also a Node). + * + * A Node is a unit with input/output ports, a run() method, and some other + * things. + * + * This is a pure abstract base class for any Node, it contains no + * implementation details/data whatsoever. This is the interface you need to + * implement to add a new Node type to Om. + * + * \ingroup engine + */ +class Node : public OmObject +{ +public: + Node(OmObject* parent, const string& name) : OmObject(parent, name) {} + virtual ~Node() {} + + Node* as_node() { return static_cast<Node*>(this); } + + /** Activate this Node. + * + * This function will be called in a non-realtime thread before it is + * inserted in to a patch. Any non-realtime actions that need to be + * done before the Node is ready for use should be done here. + */ + virtual void activate() = 0; + virtual void deactivate() = 0; + virtual bool activated() = 0; + + virtual void run(size_t nframes) = 0; + + virtual void set_port_buffer(size_t voice, size_t port_num, void* buf) = 0; + + // FIXME: Only used by client senders. Remove? + virtual const Array<Port*>& ports() const = 0; + + virtual size_t num_ports() const = 0; + virtual size_t poly() const = 0; + + /** Used by the process order finding algorithm (ie during connections) */ + virtual bool traversed() const = 0; + virtual void traversed(bool b) = 0; + + /** Nodes that are connected to this Node's inputs. + * (This Node depends on them) + */ + virtual List<Node*>* providers() = 0; + virtual void providers(List<Node*>* l) = 0; + + /** Nodes are are connected to this Node's outputs. + * (They depend on this Node) + */ + virtual List<Node*>* dependants() = 0; + virtual void dependants(List<Node*>* l) = 0; + + /** The Patch this Node belongs to. */ + virtual Patch* parent_patch() const = 0; + + /** Information about what 'plugin' this Node is an instance of. + * Not the best name - not all nodes come from plugins (ie Patch) + */ + virtual const Plugin* plugin() const = 0; + virtual void plugin(const Plugin* const pi) = 0; + + /** Add self to a Patch. + * + * This function must be realtime-safe! Any non-realtime actions that + * need to be done before adding to a patch can be done in activate(). + */ + virtual void add_to_patch() = 0; + + virtual void remove_from_patch() = 0; + + /** Send any necessary notification to client on node creation. */ + //virtual void send_creation_messages(Shared::ClientInterface* client) const = 0; +}; + + +} // namespace Om + +#endif // NODE_H diff --git a/src/libs/engine/NodeBase.cpp b/src/libs/engine/NodeBase.cpp new file mode 100644 index 00000000..b1628539 --- /dev/null +++ b/src/libs/engine/NodeBase.cpp @@ -0,0 +1,171 @@ +/* This file is part of Om. Copyright (C) 2006 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 "NodeBase.h" +#include <cassert> +#include <iostream> +#include <stdint.h> +#include "Om.h" +#include "OmApp.h" +#include "util.h" +#include "Array.h" +#include "Plugin.h" +#include "ClientBroadcaster.h" +#include "Port.h" +#include "List.h" +#include "Patch.h" +#include "ObjectStore.h" + +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +NodeBase::NodeBase(const string& name, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: Node(parent, name), + m_poly(poly), + m_srate(srate), + m_buffer_size(buffer_size), + m_activated(false), + m_num_ports(0), + m_traversed(false), + m_providers(new List<Node*>()), + m_dependants(new List<Node*>()) +{ + assert(m_poly > 0); + assert(m_parent == NULL || (m_poly == parent->internal_poly() || m_poly == 1)); +} + + +NodeBase::~NodeBase() +{ + assert(!m_activated); + + delete m_providers; + delete m_dependants; + + for (size_t i=0; i < m_ports.size(); ++i) + delete m_ports.at(i); +} + + +void +NodeBase::activate() +{ + assert(!m_activated); + m_activated = true; +} + + +void +NodeBase::deactivate() +{ + assert(m_activated); + m_activated = false; +} + + +/* +void +NodeBase::send_creation_messages(ClientInterface* client) const +{ + cerr << "FIXME: send_creation\n"; + //om->client_broadcaster()->send_node_to(client, this); +} +*/ + +void +NodeBase::add_to_store() +{ + om->object_store()->add(this); + for (size_t i=0; i < num_ports(); ++i) + om->object_store()->add(m_ports.at(i)); +} + + +void +NodeBase::remove_from_store() +{ + // Remove self + TreeNode<OmObject*>* node = om->object_store()->remove(path()); + if (node != NULL) { + assert(om->object_store()->find(path()) == NULL); + delete node; + } + + // Remove ports + for (size_t i=0; i < m_num_ports; ++i) { + node = om->object_store()->remove(m_ports.at(i)->path()); + if (node != NULL) { + assert(om->object_store()->find(m_ports.at(i)->path()) == NULL); + delete node; + } + } +} + + +/** Runs the Node for the specified number of frames (block size) + */ +void +NodeBase::run(size_t nframes) +{ + assert(m_activated); + // Mix down any ports with multiple inputs + Port* p; + for (size_t i=0; i < m_ports.size(); ++i) { + p = m_ports.at(i); + p->prepare_buffers(nframes); + } +} + + +/** Rename this Node. + * + * This is responsible for updating the ObjectStore so the Node can be + * found at it's new path, as well as all it's children. + */ +void +NodeBase::set_path(const Path& new_path) +{ + const Path old_path = path(); + //cerr << "Renaming " << old_path << " -> " << new_path << endl; + + TreeNode<OmObject*>* treenode = NULL; + + // Reinsert ports + for (size_t i=0; i < m_num_ports; ++i) { + treenode = om->object_store()->remove(old_path +"/"+ m_ports.at(i)->name()); + assert(treenode != NULL); + assert(treenode->node() == m_ports.at(i)); + treenode->key(new_path +"/" + m_ports.at(i)->name()); + om->object_store()->add(treenode); + } + + // Rename and reinsert self + treenode = om->object_store()->remove(old_path); + assert(treenode != NULL); + assert(treenode->node() == this); + OmObject::set_path(new_path); + treenode->key(new_path); + om->object_store()->add(treenode); + + + assert(om->object_store()->find(new_path) == this); +} + + +} // namespace Om + diff --git a/src/libs/engine/NodeBase.h b/src/libs/engine/NodeBase.h new file mode 100644 index 00000000..796abbca --- /dev/null +++ b/src/libs/engine/NodeBase.h @@ -0,0 +1,105 @@ +/* This file is part of Om. Copyright (C) 2006 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 NODEBASE_H +#define NODEBASE_H + +#include <string> +#include <cstdlib> +#include "Node.h" +using std::string; + +namespace Om { + +class Plugin; +class Patch; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** Common implementation stuff for Node. + * + * Pretty much just attributes and getters/setters are here. + * + * \ingroup engine + */ +class NodeBase : public Node +{ +public: + NodeBase(const string& name, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + virtual ~NodeBase(); + + virtual void activate(); + virtual void deactivate(); + bool activated() { return m_activated; } + + virtual void run(size_t nframes); + + virtual void set_port_buffer(size_t voice, size_t port_num, void* buf) {} + + virtual void add_to_patch() {} + virtual void remove_from_patch() {} + + void add_to_store(); + void remove_from_store(); + + //void send_creation_messages(ClientInterface* client) const; + + size_t num_ports() const { return m_num_ports; } + size_t poly() const { return m_poly; } + bool traversed() const { return m_traversed; } + void traversed(bool b) { m_traversed = b; } + + const Array<Port*>& ports() const { return m_ports; } + + virtual List<Node*>* providers() { return m_providers; } + virtual void providers(List<Node*>* l) { m_providers = l; } + + virtual List<Node*>* dependants() { return m_dependants; } + virtual void dependants(List<Node*>* l) { m_dependants = l; } + + Patch* parent_patch() const { return (m_parent == NULL) ? NULL : m_parent->as_patch(); } + + virtual const Plugin* plugin() const { exit(EXIT_FAILURE); } + virtual void plugin(const Plugin* const pi) { exit(EXIT_FAILURE); } + + void set_path(const Path& new_path); + +protected: + // Disallow copies (undefined) + NodeBase(const NodeBase&); + NodeBase& operator=(const NodeBase&); + + size_t m_poly; + + samplerate m_srate; + size_t m_buffer_size; + bool m_activated; + + size_t m_num_ports; // number of ports PER VOICE + Array<Port*> m_ports; + + bool m_traversed; + List<Node*>* m_providers; // Nodes connected to this one's input ports + List<Node*>* m_dependants; // Nodes this one's output ports are connected to +}; + + +} // namespace Om + +#endif // NODEBASE_H diff --git a/src/libs/engine/NodeFactory.cpp b/src/libs/engine/NodeFactory.cpp new file mode 100644 index 00000000..176d3f47 --- /dev/null +++ b/src/libs/engine/NodeFactory.cpp @@ -0,0 +1,707 @@ +/* This file is part of Om. Copyright (C) 2006 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 "NodeFactory.h" +#include "config.h" +#include <cstdlib> +#include <pthread.h> +#include <dirent.h> +#include <float.h> +#include <cmath> +#include <dlfcn.h> +#include "AudioDriver.h" +#include "MidiNoteNode.h" +#include "MidiTriggerNode.h" +#include "MidiControlNode.h" +#include "AudioInputNode.h" +#include "AudioOutputNode.h" +#include "ControlInputNode.h" +#include "ControlOutputNode.h" +#include "MidiInputNode.h" +#include "MidiOutputNode.h" +#include "TransportNode.h" +#include "PluginLibrary.h" +#include "Plugin.h" +#include "Patch.h" +#include "Om.h" +#include "OmApp.h" +#ifdef HAVE_SLV2 +#include "LV2Plugin.h" +#include <slv2/slv2.h> +#endif +#ifdef HAVE_LADSPA +#include "LADSPAPlugin.h" +#endif +#ifdef HAVE_DSSI +#include "DSSIPlugin.h" +#endif + +using std::string; +using std::cerr; using std::cout; using std::endl; + + +namespace Om { + + +/* I am perfectly aware that the vast majority of this class is a + * vomit inducing nightmare at the moment ;) + */ + + + +NodeFactory::NodeFactory() +: m_has_loaded(false) +{ + pthread_mutex_init(&m_plugin_list_mutex, NULL); + + // Add builtin plugin types to m_internal_plugins list + // FIXME: ewwww, definitely a better way to do this! + //Plugin* pi = NULL; + + Patch* parent = new Patch("dummy", 1, NULL, 1, 1, 1); + + Node* n = NULL; + n = new AudioInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new AudioOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new ControlInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new ControlOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiInputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiOutputNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiNoteNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiTriggerNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new MidiControlNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + n = new TransportNode("foo", 1, parent, 1, 1); + m_internal_plugins.push_back(new Plugin(n->plugin())); + delete n; + + + delete parent; +} + + +NodeFactory::~NodeFactory() +{ + for (list<Plugin*>::iterator i = m_plugins.begin(); i != m_plugins.end(); ++i) + delete (*i); + + for (list<PluginLibrary*>::iterator i = m_libraries.begin(); i != m_libraries.end(); ++i) { + (*i)->close(); + delete (*i); + } +} + + +void +NodeFactory::load_plugins() +{ + // Only load if we havn't already, so every client connecting doesn't cause + // this (expensive!) stuff to happen. Not the best solution - would be nice + // if clients could refresh plugins list for whatever reason :/ + if (!m_has_loaded) { + pthread_mutex_lock(&m_plugin_list_mutex); + + m_plugins.clear(); + m_plugins = m_internal_plugins; + +#if HAVE_SLV2 + load_lv2_plugins(); +#endif +#if HAVE_DSSI + load_dssi_plugins(); +#endif +#if HAVE_LADSPA + load_ladspa_plugins(); +#endif + + m_has_loaded = true; + + pthread_mutex_unlock(&m_plugin_list_mutex); + } +} + + +/** Loads a plugin. + * + * Calls the load_*_plugin functions to actually do things, just a wrapper. + */ +Node* +NodeFactory::load_plugin(const Plugin* a_plugin, const string& name, size_t poly, Patch* parent) +{ + assert(parent != NULL); + assert(poly == 1 || poly == parent->internal_poly()); + assert(a_plugin); + + pthread_mutex_lock(&m_plugin_list_mutex); + + Node* r = NULL; + Plugin* plugin = NULL; + + // Attempt to find the plugin in loaded DB + if (a_plugin->type() != Plugin::Internal) { + list<Plugin*>::iterator i; + if (a_plugin->plug_label().length() == 0) { + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if (a_plugin->uri() == (*i)->uri()) { + plugin = *i; + break; + } + } + } else { + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if (a_plugin->uri() == (*i)->uri()) { + plugin = *i; + break; + } + } + } + + if (plugin == NULL) + return NULL; + } + + switch (a_plugin->type()) { +#if HAVE_SLV2 + case Plugin::LV2: + r = load_lv2_plugin(plugin->uri(), name, poly, parent); + break; +#endif +#if HAVE_DSSI + case Plugin::DSSI: + r = load_dssi_plugin(plugin->uri(), name, poly, parent); + break; +#endif +#if HAVE_LADSPA + case Plugin::LADSPA: + r = load_ladspa_plugin(plugin->uri(), name, poly, parent); + break; +#endif + case Plugin::Internal: + r = load_internal_plugin(a_plugin->uri(), name, poly, parent); + break; + default: + cerr << "[NodeFactory] WARNING: Unknown plugin type." << endl; + } + + pthread_mutex_unlock(&m_plugin_list_mutex); + + return r; +} + + +/** Loads an internal plugin. + */ +Node* +NodeFactory::load_internal_plugin(const string& uri, const string& name, size_t poly, Patch* parent) +{ + assert(parent != NULL); + assert(poly == 1 || poly == parent->internal_poly()); + assert(uri.length() > 3); + assert(uri.substr(0, 3) == "om:"); + + string plug_label = uri.substr(3); + + if (plug_label == "midi_input") { + MidiInputNode* tn = new MidiInputNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else if (plug_label == "midi_output") { + MidiOutputNode* tn = new MidiOutputNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else if (plug_label == "audio_input") { + AudioInputNode* in = new AudioInputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return in; + } else if (plug_label == "control_input") { + ControlInputNode* in = new ControlInputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return in; + } else if (plug_label == "audio_output") { + AudioOutputNode* on = new AudioOutputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return on; + } else if (plug_label == "control_output") { + ControlOutputNode* on = new ControlOutputNode(name, poly, parent, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return on; + } else if (plug_label == "note_in" || plug_label == "midi_note_in") { + MidiNoteNode* mn = new MidiNoteNode(name, poly, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "trigger_in" || plug_label == "midi_trigger_in") { + MidiTriggerNode* mn = new MidiTriggerNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "midi_control_in") { + MidiControlNode* mn = new MidiControlNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return mn; + } else if (plug_label == "transport") { + TransportNode* tn = new TransportNode(name, 1, parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + return tn; + } else { + cerr << "Unknown internal plugin type '" << plug_label << "'" << endl; + } + + return NULL; +} + + +#ifdef HAVE_SLV2 + +/** Loads information about all LV2 plugins into internal plugin database. + */ +void +NodeFactory::load_lv2_plugins() +{ + SLV2List plugins = slv2_list_new(); + slv2_list_load_all(plugins); + + //cerr << "[NodeFactory] Found " << slv2_list_get_length(plugins) << " LV2 plugins." << endl; + + for (unsigned long i=0; i < slv2_list_get_length(plugins); ++i) { + + SLV2Plugin* lv2_plug = slv2_list_get_plugin_by_index(plugins, i); + + + //om_plug->library(plugin_library); + + const char* uri = (const char*)slv2_plugin_get_uri(lv2_plug); + //cerr << "LV2 plugin: " << uri << endl; + + bool found = false; + for (list<Plugin*>::const_iterator i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if (!strcmp((*i)->uri().c_str(), uri)) { + cerr << "Warning: Duplicate LV2 plugin (" << uri << ").\nUsing " + << (*i)->lib_path() << " version." << endl; + found = true; + break; + } + } + if (!found) { + //printf("[NodeFactory] Found LV2 plugin %s\n", uri); + Plugin* om_plug = new Plugin(); + om_plug->type(Plugin::LV2); + om_plug->slv2_plugin(lv2_plug); + om_plug->uri(uri); + // FIXME FIXME FIXME temporary hack + om_plug->library(NULL); + om_plug->lib_path("FIXMEpath"); + om_plug->plug_label("FIXMElabel"); + unsigned char* name = slv2_plugin_get_name(lv2_plug); + om_plug->name((char*)name); + free(name); + om_plug->type(Plugin::LV2); + m_plugins.push_back(om_plug); + } + } + + slv2_list_free(plugins); +} + + +/** Loads a LV2 plugin. + * Returns 'poly' independant plugins as a Node* + */ +Node* +NodeFactory::load_lv2_plugin(const string& plug_uri, + const string& node_name, + size_t poly, + Patch* parent) +{ + // Find (Om) Plugin + Plugin* plugin = NULL; + list<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if ((*i)->uri() == plug_uri) break; + } + + Node* n = NULL; + + if (plugin) { + n = new Om::LV2Plugin(node_name, poly, parent, plugin->slv2_plugin(), + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((LV2Plugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + n->plugin(plugin); + } + + return n; +} + +#endif // HAVE_SLV2 + + +#if HAVE_DSSI + +/** Loads information about all DSSI plugins into internal plugin database. + */ +void +NodeFactory::load_dssi_plugins() +{ + // FIXME: too much code duplication with load_ladspa_plugin + + char* env_dssi_path = getenv("DSSI_PATH"); + string dssi_path; + if (!env_dssi_path) { + cerr << "[NodeFactory] DSSI_PATH is empty. Assuming /usr/lib/dssi:/usr/local/lib/dssi:~/.dssi" << endl; + dssi_path = string("/usr/lib/dssi:/usr/local/lib/dssi:").append( + getenv("HOME")).append("/.dssi"); + } else { + dssi_path = env_dssi_path; + } + + DSSI_Descriptor_Function df = NULL; + DSSI_Descriptor* descriptor = NULL; + + string dir; + string full_lib_name; + + // Yep, this should use an sstream alright.. + while (dssi_path != "") { + dir = dssi_path.substr(0, dssi_path.find(':')); + if (dssi_path.find(':') != string::npos) + dssi_path = dssi_path.substr(dssi_path.find(':')+1); + else + dssi_path = ""; + + DIR* pdir = opendir(dir.c_str()); + if (pdir == NULL) { + //cerr << "[NodeFactory] Unreadable directory in DSSI_PATH: " << dir.c_str() << endl; + continue; + } + + struct dirent* pfile; + while ((pfile = readdir(pdir))) { + + if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) + continue; + + full_lib_name = dir +"/"+ pfile->d_name; + + // Load descriptor function + // Loaded with LAZY here, will be loaded with NOW on node loading + void* handle = dlopen(full_lib_name.c_str(), RTLD_LAZY); + if (handle == NULL) + continue; + + df = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); + if (df == NULL) { + // Not a DSSI plugin library + dlclose(handle); + continue; + } + + PluginLibrary* plugin_library = new PluginLibrary(full_lib_name); + m_libraries.push_back(plugin_library); + + const LADSPA_Descriptor* ld = NULL; + + for (unsigned long i=0; (descriptor = (DSSI_Descriptor*)df(i)) != NULL; ++i) { + ld = descriptor->LADSPA_Plugin; + assert(ld != NULL); + Plugin* plugin = new Plugin(); + assert(plugin_library != NULL); + plugin->library(plugin_library); + plugin->lib_path(dir + "/" + pfile->d_name); + plugin->plug_label(ld->Label); + plugin->name(ld->Name); + plugin->type(Plugin::DSSI); + plugin->id(ld->UniqueID); + + bool found = false; + for (list<Plugin*>::const_iterator i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if ((*i)->uri() == plugin->uri()) { + cerr << "Warning: Duplicate DSSI plugin (" << plugin->lib_name() << ":" + << plugin->plug_label() << ")" << " found.\nUsing " << (*i)->lib_path() + << " version." << endl; + found = true; + break; + } + } + if (!found) + m_plugins.push_back(plugin); + else + delete plugin; + } + + df = NULL; + descriptor = NULL; + dlclose(handle); + } + closedir(pdir); + } +} + + +/** Creates a Node by instancing a DSSI plugin. + */ +Node* +NodeFactory::load_dssi_plugin(const string& uri, + const string& name, size_t poly, Patch* parent) +{ + // FIXME: awful code duplication here + + assert(uri != ""); + assert(name != ""); + assert(poly > 0); + + DSSI_Descriptor_Function df = NULL; + const Plugin* plugin = NULL; + Node* n = NULL; + void* handle = NULL; + + // Attempt to find the lib + list<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if (plugin->uri() == uri) break; + } + + assert(plugin->id() != 0); + + if (i == m_plugins.end()) { + cerr << "Did not find DSSI plugin " << uri << " in database." << endl; + return NULL; + } else { + assert(plugin != NULL); + plugin->library()->open(); + handle = plugin->library()->handle(); + assert(handle != NULL); + + // Load descriptor function + dlerror(); + df = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); + if (df == NULL || dlerror() != NULL) { + cerr << "Looks like this isn't a DSSI plugin." << endl; + return NULL; + } + } + + // Attempt to find the plugin in lib + DSSI_Descriptor* descriptor = NULL; + for (unsigned long i=0; (descriptor = (DSSI_Descriptor*)df(i)) != NULL; ++i) { + if (descriptor->LADSPA_Plugin != NULL + && descriptor->LADSPA_Plugin->UniqueID == plugin->id()) { + break; + } + } + + if (descriptor == NULL) { + cerr << "Could not find plugin \"" << plugin->id() << "\" in " << plugin->lib_name() << endl; + return NULL; + } + + n = new DSSIPlugin(name, poly, parent, descriptor, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((DSSIPlugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + + n->plugin(plugin); + + return n; +} +#endif // HAVE_DSSI + + +#ifdef HAVE_LADSPA +/** Loads information about all LADSPA plugins into internal plugin database. + */ +void +NodeFactory::load_ladspa_plugins() +{ + char* env_ladspa_path = getenv("LADSPA_PATH"); + string ladspa_path; + if (!env_ladspa_path) { + cerr << "[NodeFactory] LADSPA_PATH is empty. Assuming /usr/lib/ladspa:/usr/local/lib/ladspa:~/.ladspa" << endl; + ladspa_path = string("/usr/lib/ladspa:/usr/local/lib/ladspa:").append( + getenv("HOME")).append("/.ladspa"); + } else { + ladspa_path = env_ladspa_path; + } + + LADSPA_Descriptor_Function df = NULL; + LADSPA_Descriptor* descriptor = NULL; + + string dir; + string full_lib_name; + + // Yep, this should use an sstream alright.. + while (ladspa_path != "") { + dir = ladspa_path.substr(0, ladspa_path.find(':')); + if (ladspa_path.find(':') != string::npos) + ladspa_path = ladspa_path.substr(ladspa_path.find(':')+1); + else + ladspa_path = ""; + + DIR* pdir = opendir(dir.c_str()); + if (pdir == NULL) { + //cerr << "[NodeFactory] Unreadable directory in LADSPA_PATH: " << dir.c_str() << endl; + continue; + } + + struct dirent* pfile; + while ((pfile = readdir(pdir))) { + + if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) + continue; + + full_lib_name = dir +"/"+ pfile->d_name; + + // Load descriptor function + // Loaded with LAZY here, will be loaded with NOW on node loading + void* handle = dlopen(full_lib_name.c_str(), RTLD_LAZY); + if (handle == NULL) + continue; + + df = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor"); + if (df == NULL) { + dlclose(handle); + continue; + } + + PluginLibrary* plugin_library = new PluginLibrary(full_lib_name); + m_libraries.push_back(plugin_library); + + for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { + Plugin* plugin = new Plugin(); + assert(plugin_library != NULL); + plugin->library(plugin_library); + plugin->lib_path(dir + "/" + pfile->d_name); + plugin->plug_label(descriptor->Label); + plugin->name(descriptor->Name); + plugin->type(Plugin::LADSPA); + plugin->id(descriptor->UniqueID); + + bool found = false; + for (list<Plugin*>::const_iterator i = m_plugins.begin(); i != m_plugins.end(); ++i) { + if ((*i)->uri() == plugin->uri()) { + cerr << "Warning: Duplicate LADSPA plugin " << plugin->uri() + << " found.\nChoosing " << (*i)->lib_path() + << " over " << plugin->lib_path() << endl; + found = true; + break; + } + } + if (!found) + m_plugins.push_back(plugin); + else + delete plugin; + } + + df = NULL; + descriptor = NULL; + dlclose(handle); + } + closedir(pdir); + } +} + + +/** Loads a LADSPA plugin. + * Returns 'poly' independant plugins as a Node* + */ +Node* +NodeFactory::load_ladspa_plugin(const string& uri, + const string& name, size_t poly, Patch* parent) +{ + assert(uri != ""); + assert(name != ""); + assert(poly > 0); + + LADSPA_Descriptor_Function df = NULL; + Plugin* plugin = NULL; + Node* n = NULL; + void* plugin_lib = NULL; + + // Attempt to find the lib + list<Plugin*>::iterator i; + for (i = m_plugins.begin(); i != m_plugins.end(); ++i) { + plugin = (*i); + if (plugin->uri() == uri) break; + } + + assert(plugin->id() != 0); + + if (i == m_plugins.end()) { + cerr << "Did not find LADSPA plugin " << uri << " in database." << endl; + return NULL; + } else { + assert(plugin != NULL); + plugin->library()->open(); + plugin_lib = plugin->library()->handle(); + assert(plugin_lib != NULL); + + // Load descriptor function + dlerror(); + df = (LADSPA_Descriptor_Function)dlsym(plugin_lib, "ladspa_descriptor"); + if (df == NULL || dlerror() != NULL) { + cerr << "Looks like this isn't a LADSPA plugin." << endl; + return NULL; + } + } + + // Attempt to find the plugin in lib + LADSPA_Descriptor* descriptor = NULL; + for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { + if (descriptor->UniqueID == plugin->id()) { + break; + } + } + + if (descriptor == NULL) { + cerr << "Could not find plugin \"" << plugin->id() << "\" in " << plugin->lib_path() << endl; + return NULL; + } + + n = new LADSPAPlugin(name, poly, parent, descriptor, + om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size()); + bool success = ((LADSPAPlugin*)n)->instantiate(); + if (!success) { + delete n; + n = NULL; + } + + n->plugin(plugin); + + return n; +} + + +#endif // HAVE_LADSPA + + +} // namespace Om diff --git a/src/libs/engine/NodeFactory.h b/src/libs/engine/NodeFactory.h new file mode 100644 index 00000000..ed6a35dc --- /dev/null +++ b/src/libs/engine/NodeFactory.h @@ -0,0 +1,93 @@ +/* This file is part of Om. Copyright (C) 2006 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 NODEFACTORY_H +#define NODEFACTORY_H + +#include "config.h" +#include <list> +#include <string> +#include <ladspa.h> +#include <pthread.h> + +using std::string; using std::list; + +namespace Om { + +class Node; +class PortInfo; +class Patch; +class PluginLibrary; +class Plugin; + + +/** Loads plugins and creates Nodes from them. + * + * NodeFactory's responsibility is to get enough information to allow the + * loading of a plugin possible (ie finding/opening shared libraries etc) + * + * The constructor of various Node types (ie LADSPAPlugin) are responsible + * for actually creating a Node instance of the plugin. + * + * \ingroup engine + */ +class NodeFactory +{ +public: + NodeFactory(); + ~NodeFactory(); + + void load_plugins(); + Node* load_plugin(const Plugin* info, const string& name, size_t poly, Patch* parent); + + const list<Plugin*>& plugins() { return m_plugins; } + + void lock_plugin_list() { pthread_mutex_lock(&m_plugin_list_mutex); } + void unlock_plugin_list() { pthread_mutex_unlock(&m_plugin_list_mutex); } + +private: +#ifdef HAVE_LADSPA + void load_ladspa_plugins(); + Node* load_ladspa_plugin(const string& plugin_uri, const string& name, size_t poly, Patch* parent); +#endif + +#ifdef HAVE_SLV2 + void load_lv2_plugins(); + Node* load_lv2_plugin(const string& plugin_uri, const string& name, size_t poly, Patch* parent); +#endif + +#ifdef HAVE_DSSI + void load_dssi_plugins(); + Node* load_dssi_plugin(const string& plugin_uri, const string& name, size_t poly, Patch* parent); +#endif + + Node* load_internal_plugin(const string& plug_label, const string& name, size_t poly, Patch* parent); + + list<PluginLibrary*> m_libraries; + list<Plugin*> m_internal_plugins; + list<Plugin*> m_plugins; + + /** Used to protect the list while load_plugins is building it. */ + pthread_mutex_t m_plugin_list_mutex; + + bool m_has_loaded; +}; + + +} // namespace Om + +#endif // NODEFACTORY_H diff --git a/src/libs/engine/OSCClient.cpp b/src/libs/engine/OSCClient.cpp new file mode 100644 index 00000000..332a2144 --- /dev/null +++ b/src/libs/engine/OSCClient.cpp @@ -0,0 +1,503 @@ +/* This file is part of Om. Copyright (C) 2006 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 "OSCClient.h" +#include <cassert> +#include <iostream> +#include <unistd.h> +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "NodeFactory.h" +#include "util.h" +#include "Patch.h" +#include "Node.h" +#include "PortInfo.h" +#include "Plugin.h" +#include "PortBase.h" +#include "Connection.h" +#include "AudioDriver.h" +#include "interface/ClientInterface.h" +#include "Responder.h" + +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +/*! \page client_osc_namespace Client OSC Namespace Documentation + * + * <p>These are all the messages sent from the engine to the client. Om + * communication takes place over two distinct bands: control band and + * notification band.</p> + * <p>The control band is where clients send commands, and receive a simple + * response, either OK or an error.</p> + * <p>All notifications of engine state (ie new nodes) are sent over the + * notification band <em>which is seperate from the control band</em>. The + * reasoning behind this is that many clients may be connected at the same + * time - a client may receive notifications that are not a direct consequence + * of some message it sent.</p> + * <p>The notification band can be thought of as a stream of events representing + * the changing engine state. For example, It is possible for a client to send + * commands and receive aknowledgements, and not listen to the notification band + * at all; or (in the near future anyway) for a client to use UDP for the control + * band (for speed), and TCP for the notification band (for reliability and + * order guarantees).</p> + * \n\n + */ + + +/* Documentation for namespace portion implemented in Responder.cpp */ + +/** \page client_osc_namespace + * \n + * <h3>Notification Band</h3> + */ + +/** \page client_osc_namespace + * <p> \b /om/response/ok - Respond successfully to a user command + * \arg \b responder-id (int) - Responder ID this is a response to + * </p> \n \n + */ + +/** \page client_osc_namespace + * <p> \b /om/response/error - Respond negatively to a user command + * \arg \b responder-id (int) - Request ID this is a response to + * \arg \b message (string) - Error message (natural language text) + * </p> \n \n + */ + + + +/** \page client_osc_namespace + * \n + * <h3>Notification Band</h3> + */ + +/** \page client_osc_namespace + * <p> \b /om/error - Notification that an error has occurred + * \arg \b message (string) - Error message (natural language text) + * + * \li This is for notification of errors that aren't a direct response to a + * user command, ie "unexpected" errors.</p> \n \n + */ +void +OSCClient::error(const string& msg) +{ + lo_send(_address, "/om/error", "s", msg.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/num_plugins + * \arg \b num (int) - Number of plugins engine has loaded + * \li This is sent before sending the list of plugins, so the client is aware + * of how many plugins (/om/plugin messages) to expect.</p> \n \n + */ + + +/** \page client_osc_namespace + * <p> \b /om/num_plugins + * \arg \b num (int) - Number of plugins engine has loaded + * \li This is sent before sending the list of plugins, so the client is aware + * of how many plugins (/om/plugin messages) to expect.</p> \n \n + */ +void +OSCClient::num_plugins(uint32_t num) +{ + lo_message m = lo_message_new(); + lo_message_add_int32(m, num); + lo_send_message(_address, "/om/num_plugins", m); +} + + +/** \page client_osc_namespace + * <p> \b /om/plugin - Notification of the existance of a plugin + * \arg \b type (string) - Type if plugin ("LADSPA", "DSSI", or "Internal") + * \arg \b uri (string) - URI of the plugin (see engine namespace documentation) \n + * \arg \b lib-name (string) - Name of shared library plugin resides in (ie "cmt.so") + * \arg \b plug-label (string) - Label of the plugin (ie "dahdsr_iaoa") + * \arg \b name (string) - Descriptive human-readable name of plugin (ie "ADSR Envelope") + * </p> \n \n + */ +/* +void +OSCClient::plugins() +{ + om->node_factory()->lock_plugin_list(); + + const list<Plugin*>& plugs = om->node_factory()->plugins(); + const Plugin* plugin; + + lo_timetag tt; + lo_timetag_now(&tt); + lo_bundle b = lo_bundle_new(tt); + lo_message m = lo_message_new(); + list<lo_message> msgs; + + lo_message_add_int32(m, plugs.size()); + lo_bundle_add_message(b, "/om/num_plugins", m); + msgs.push_back(m); + + for (list<Plugin*>::const_iterator j = plugs.begin(); j != plugs.end(); ++j) { + plugin = (*j); + m = lo_message_new(); + + lo_message_add_string(m, plugin->type_string()); + lo_message_add_string(m, plugin->uri().c_str()); + lo_message_add_string(m, plugin->plug_label().c_str()); + lo_message_add_string(m, plugin->name().c_str()); + lo_bundle_add_message(b, "/om/plugin", m); + msgs.push_back(m); + if (lo_bundle_length(b) > 1024) { + lo_send_bundle(_address, b); + lo_bundle_free(b); + b = lo_bundle_new(tt); + } + } + + if (lo_bundle_length(b) > 0) { + lo_send_bundle(_address, b); + lo_bundle_free(b); + } else { + lo_bundle_free(b); + } + for (list<lo_bundle>::const_iterator i = msgs.begin(); i != msgs.end(); ++i) + lo_message_free(*i); + + om->node_factory()->unlock_plugin_list(); +} +*/ + +/** \page client_osc_namespace + * <p> \b /om/new_node - Notification of a new node's creation. + * \arg \b plug-uri (string) - URI of the plugin new node is an instance of + * \arg \b path (string) - Path of the new node + * \arg \b polyphonic (integer-boolean) - Node is polyphonic (1 = yes, 0 = no) + * \arg \b num-ports (integer) - Number of ports (number of new_port messages to expect) + * \li New nodes are sent as a bundle. The first message in the bundle will be + * this one (/om/new_node), followed by a series of /om/new_port commands, + * followed by /om/new_node_end. </p> \n \n + */ +void OSCClient::new_node(const string& plugin_type, + const string& plugin_uri, + const string& node_path, + bool is_polyphonic, + uint32_t num_ports) +{ + lo_send(_address, "/om/new_node", "sssii", plugin_type.c_str(), plugin_uri.c_str(), + node_path.c_str(), is_polyphonic ? 1 : 0, num_ports); +#if 0 + /* + lo_timetag tt; + lo_timetag_now(&tt); + lo_bundle b = lo_bundle_new(tt); + lo_message m = lo_message_new(); + list<lo_message> msgs; + + lo_message_add_string(m, plugin_type.c_str()); + lo_message_add_string(m, plugin_uri.c_str()); + lo_message_add_string(m, node_path.c_str()); + lo_message_add_int32(m, is_polyphonic ? 1 : 0); + lo_message_add_int32(m, num_ports); + + lo_bundle_add_message(b, "/om/new_node", m); + msgs.push_back(m); +*/ + + + /* + const Array<Port*>& ports = node->ports(); + Port* port; + PortInfo* info; + for (size_t j=0; j < ports.size(); ++j) { + port = ports.at(j); + info = port->port_info(); + + assert(port != NULL); + assert(info != NULL); + + m = lo_message_new(); + lo_message_add_string(m, port->path().c_str()); + lo_message_add_string(m, info->type_string().c_str()); + lo_message_add_string(m, info->direction_string().c_str()); + lo_message_add_string(m, info->hint_string().c_str()); + lo_message_add_float(m, info->default_val()); + lo_message_add_float(m, info->min_val()); + lo_message_add_float(m, info->max_val()); + lo_bundle_add_message(b, "/om/new_port", m); + msgs.push_back(m); + + // If the bundle is getting very large, send it and start + // a new one + if (lo_bundle_length(b) > 1024) { + lo_send_bundle(_address, b); + lo_bundle_free(b); + b = lo_bundle_new(tt); + } + } +*/ + /*m = lo_message_new(); + //lo_bundle_add_message(b, "/om/new_node_end", m); + //msgs.push_back(m); + + lo_send_bundle(_address, b); + lo_bundle_free(b); + + for (list<lo_bundle>::const_iterator i = msgs.begin(); i != msgs.end(); ++i) + lo_message_free(*i); + + usleep(100); +*/ + /* + const map<string, string>& data = node->metadata(); + // Send node metadata + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) + metadata_update(node->path(), (*i).first, (*i).second); + + + // Send port metadata + for (size_t j=0; j < ports.size(); ++j) { + port = ports.at(j); + const map<string, string>& data = port->metadata(); + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) + metadata_update(port->path(), (*i).first, (*i).second); + } + + // Send control values + for (size_t i=0; i < node->ports().size(); ++i) { + PortBase<sample>* port = (PortBase<sample>*)node->ports().at(i); + if (port->port_info()->is_input() && port->port_info()->is_control()) + control_change(port->path(), port->buffer(0)->value_at(0)); + } + */ +#endif +} + + + +/** \page client_osc_namespace + * <p> \b /om/new_port - Notification of a new port's creation. + * \arg \b path (string) - Path of new port + * \arg \b data-type (string) - Type of port (CONTROL or AUDIO) + * \arg \b direction ("is-output") (integer) - Direction of data flow (Input = 0, Output = 1) + * + * \li Note that in the event of loading a patch, this message could be + * followed immediately by a control change, meaning the default-value is + * not actually the current value of the port. + * \li The minimum and maximum values are suggestions only, they are not + * enforced in any way, and going outside them is perfectly fine. Also note + * that the port ranges in om_gtk are not these ones! Those ranges are set + * as metadata.</p> \n \n + */ +void +OSCClient::new_port(const string& path, + const string& data_type, + bool is_output) +{ + //PortInfo* info = port->port_info(); + + lo_send(_address, "/om/new_port", "ssi", path.c_str(), data_type.c_str(), is_output); + + // Send metadata + /*const map<string, string>& data = port->metadata(); + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) + metadata_update(port->path(), (*i).first, (*i).second);*/ +} + + +/** \page client_osc_namespace + * <p> \b /om/destroyed - Notification an object has been destroyed + * \arg \b path (string) - Path of object (which no longer exists) </p> \n \n + */ +void +OSCClient::object_destroyed(const string& path) +{ + assert(path != "/"); + + lo_send(_address, "/om/destroyed", "s", path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/patch_cleared - Notification a patch has been cleared (all children destroyed) + * \arg \b path (string) - Path of patch (which is now empty)</p> \n \n + */ +void +OSCClient::patch_cleared(const string& patch_path) +{ + lo_send(_address, "/om/patch_cleared", "s", patch_path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/patch_enabled - Notification a patch's DSP processing has been enabled. + * \arg \b path (string) - Path of enabled patch</p> \n \n + */ +void +OSCClient::patch_enabled(const string& patch_path) +{ + lo_send(_address, "/om/patch_enabled", "s", patch_path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/patch_disabled - Notification a patch's DSP processing has been disabled. + * \arg \b path (string) - Path of disabled patch</p> \n \n + */ +void +OSCClient::patch_disabled(const string& patch_path) +{ + lo_send(_address, "/om/patch_disabled", "s", patch_path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/new_connection - Notification a new connection has been made. + * \arg \b src-path (string) - Path of the source port + * \arg \b dst-path (string) - Path of the destination port</p> \n \n + */ +void +OSCClient::connection(const string& src_port_path, const string& dst_port_path) +{ + lo_send(_address, "/om/new_connection", "ss", src_port_path.c_str(), dst_port_path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/disconnection - Notification a connection has been unmade. + * \arg \b src-path (string) - Path of the source port + * \arg \b dst-path (string) - Path of the destination port</p> \n \n + */ +void +OSCClient::disconnection(const string& src_port_path, const string& dst_port_path) +{ + lo_send(_address, "/om/disconnection", "ss", src_port_path.c_str(), dst_port_path.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/metadata/update - Notification of a piece of metadata. + * \arg \b path (string) - Path of the object associated with metadata (can be a node, patch, or port) + * \arg \b key (string) + * \arg \b value (string)</p> \n \n + */ +void +OSCClient::metadata_update(const string& path, const string& key, const string& value) +{ + lo_send(_address, "/om/metadata/update", "sss", path.c_str(), key.c_str(), value.c_str()); +} + + +/** \page client_osc_namespace + * <p> \b /om/control_change - Notification the value of a port has changed + * \arg \b path (string) - Path of port + * \arg \b value (float) - New value of port + * + * \li This will only send updates for values set by clients of course - not values + * changing because of connections to other ports!</p> \n \n + */ +void +OSCClient::control_change(const string& port_path, float value) +{ + lo_send(_address, "/om/control_change", "sf", port_path.c_str(), value); +} + + +/** \page client_osc_namespace + * <p> \b /om/plugin - Notification of the existance of a plugin + * \arg \b type (string) - Type if plugin ("LADSPA", "DSSI", or "Internal")</p> \n \n + * \arg \b uri (string) - Type if plugin ("LADSPA", "DSSI", or "Internal")</p> \n \n + * \arg \b name (string) - Descriptive human-readable name of plugin (ie "ADSR Envelope") + */ +void +OSCClient::new_plugin(const string& type, const string& uri, const string& name) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, type.c_str()); + lo_message_add_string(m, uri.c_str()); + lo_message_add_string(m, name.c_str()); + lo_send_message(_address, "/om/plugin", m); +} + + +/** \page client_osc_namespace + * <p> \b /om/new_patch - Notification of a new patch + * \arg \b path (string) - Path of new patch + * \arg \b poly (int) - Polyphony of new patch (\em not a boolean like new_node) </p> \n \n + */ +void +OSCClient::new_patch(const string& path, uint32_t poly) +{ + lo_send(_address, "/om/new_patch", "si", path.c_str(), poly); + + /* + if (p->process()) + patch_enabled(p->path()); + + // Send metadata + const map<string, string>& data = p->metadata(); + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) { + metadata_update(p->path(), (*i).first, (*i).second); + } + */ +} + + +/** \page client_osc_namespace + * <p> \b /om/object_renamed - Notification of an object's renaming + * \arg \b old-path (string) - Old path of object + * \arg \b new-path (string) - New path of object </p> \n \n + */ +void +OSCClient::object_renamed(const string& old_path, const string& new_path) +{ + lo_send(_address, "/om/object_renamed", "ss", old_path.c_str(), new_path.c_str()); +} + + +/** Sends all OmObjects known to the engine. + */ +/* +void +OSCClient::all_objects() +{ + for (Tree<OmObject*>::iterator i = om->object_store()->objects().begin(); + i != om->object_store()->objects().end(); ++i) + if ((*i)->as_node() != NULL && (*i)->parent() == NULL) + (*i)->as_node()->send_creation_messages(this); +} +*/ + +/** Sends information about a program associated with a DSSI plugin node. + */ +void +OSCClient::program_add(const string& node_path, uint32_t bank, uint32_t program, const string& name) +{ + lo_send(_address, "/om/program_add", "siis", + node_path.c_str(), bank, program, name.c_str()); +} + + +void +OSCClient::program_remove(const string& node_path, uint32_t bank, uint32_t program) +{ + lo_send(_address, "/om/program_remove", "sii", + node_path.c_str(), bank, program); +} + + +} // namespace Om diff --git a/src/libs/engine/OSCClient.h b/src/libs/engine/OSCClient.h new file mode 100644 index 00000000..db14c696 --- /dev/null +++ b/src/libs/engine/OSCClient.h @@ -0,0 +1,131 @@ +/* This file is part of Om. Copyright (C) 2006 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 OSCCLIENT_H +#define OSCCLIENT_H + +#include <string> +#include <iostream> +#include <list> +#include <lo/lo.h> +#include <pthread.h> +#include "util/types.h" +#include "interface/ClientInterface.h" + +using std::list; using std::string; +using std::cerr; + +namespace Om { + + +/** Implements ClientInterface for OSC clients (sends OSC messages). + * + * \ingroup engine + */ +class OSCClient : public Shared::ClientInterface +{ +public: + OSCClient(const string& url) + : _url(url), + _address(lo_address_new_from_url(url.c_str())) + {} + + virtual ~OSCClient() + { lo_address_free(_address); } + + const string& url() const { return _url; } + const lo_address address() const { return _address; } + + //void plugins(); // FIXME remove + + + + /* *** ClientInterface Implementation Below *** */ + + + //void client_registration(const string& url, int client_id); + + // need a liblo feature to make this possible :/ + void bundle_begin() {} + void bundle_end() {} + + void num_plugins(uint32_t num); + + void error(const string& msg); + + virtual void new_plugin(const string& type, + const string& uri, + const string& name); + + virtual void new_patch(const string& path, uint32_t poly); + + virtual void new_node(const string& plugin_type, + const string& plugin_uri, + const string& node_path, + bool is_polyphonic, + uint32_t num_ports); + + virtual void new_port(const string& path, + const string& data_type, + bool is_output); + + virtual void patch_enabled(const string& path); + + virtual void patch_disabled(const string& path); + + virtual void patch_cleared(const string& path); + + virtual void object_destroyed(const string& path); + + virtual void object_renamed(const string& old_path, + const string& new_path); + + virtual void connection(const string& src_port_path, + const string& dst_port_path); + + virtual void disconnection(const string& src_port_path, + const string& dst_port_path); + + virtual void metadata_update(const string& subject_path, + const string& predicate, + const string& value); + + virtual void control_change(const string& port_path, + float value); + + virtual void program_add(const string& node_path, + uint32_t bank, + uint32_t program, + const string& program_name); + + virtual void program_remove(const string& node_path, + uint32_t bank, + uint32_t program); + +private: + // Prevent copies (undefined) + OSCClient(const OSCClient&); + OSCClient& operator=(const OSCClient&); + + string _url; + lo_address _address; +}; + + +} // namespace Om + +#endif // OSCCLIENT_H + diff --git a/src/libs/engine/OSCReceiver.cpp b/src/libs/engine/OSCReceiver.cpp new file mode 100644 index 00000000..8edab8b1 --- /dev/null +++ b/src/libs/engine/OSCReceiver.cpp @@ -0,0 +1,926 @@ +/* This file is part of Om. Copyright (C) 2006 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 "OSCReceiver.h" +#include <iostream> +#include <cstdlib> +#include <string> +#include <lo/lo.h> +#include "util/types.h" +#include "Om.h" +#include "OmApp.h" +#include "util/Queue.h" +#include "util/CountedPtr.h" +#include "QueuedEventSource.h" +#include "interface/ClientKey.h" +#include "interface/ClientInterface.h" +#include "OSCClient.h" +#include "OSCResponder.h" +#include "ClientBroadcaster.h" +#include "Plugin.h" + +using std::cerr; using std::cout; using std::endl; + +namespace Om { + +using Shared::ClientKey; + + +/*! \page engine_osc_namespace Engine OSC Namespace Documentation + * + * <p>These are the commands the engine recognizes. A client can control every + * aspect of the engine entirely with these commands.</p> + * + * <p>All commands on this page are in the "control band". If a client needs to + * know about the state of the engine, it must listen to the "notification band". + * See the "Client OSC Namespace Documentation" for details. + */ + + +OSCReceiver::OSCReceiver(size_t queue_size, const char* const port) +: QueuedEngineInterface(queue_size), + _port(port), + _is_activated(false), + _st(NULL), + _osc_responder(NULL) +{ + _st = lo_server_thread_new(port, error_cb); + + if (_st == NULL) { + cerr << "[OSC] Could not start OSC server. Aborting." << endl; + exit(EXIT_FAILURE); + } else { + char* lo_url = lo_server_thread_get_url(_st); + cout << "[OSC] Started OSC server at " << lo_url << endl; + free(lo_url); + } + + // For debugging, print all incoming OSC messages + lo_server_thread_add_method(_st, NULL, NULL, generic_cb, NULL); + + // Set response address for this message. + // It's important this is first and returns nonzero. + lo_server_thread_add_method(_st, NULL, NULL, set_response_address_cb, this); + + // Commands + lo_server_thread_add_method(_st, "/om/ping", "i", ping_cb, this); + lo_server_thread_add_method(_st, "/om/ping_slow", "i", ping_slow_cb, this); + lo_server_thread_add_method(_st, "/om/engine/quit", "i", quit_cb, this); + //lo_server_thread_add_method(_st, "/om/engine/register_client", "is", register_client_cb, this); + lo_server_thread_add_method(_st, "/om/engine/register_client", "i", register_client_cb, this); + lo_server_thread_add_method(_st, "/om/engine/unregister_client", "i", unregister_client_cb, this); + lo_server_thread_add_method(_st, "/om/engine/load_plugins", "i", load_plugins_cb, this); + lo_server_thread_add_method(_st, "/om/engine/activate", "i", engine_activate_cb, this); + lo_server_thread_add_method(_st, "/om/engine/deactivate", "i", engine_deactivate_cb, this); + lo_server_thread_add_method(_st, "/om/synth/create_patch", "isi", create_patch_cb, this); + lo_server_thread_add_method(_st, "/om/synth/enable_patch", "is", enable_patch_cb, this); + lo_server_thread_add_method(_st, "/om/synth/disable_patch", "is", disable_patch_cb, this); + lo_server_thread_add_method(_st, "/om/synth/clear_patch", "is", clear_patch_cb, this); + lo_server_thread_add_method(_st, "/om/synth/create_node", "issssi", create_node_cb, this); + lo_server_thread_add_method(_st, "/om/synth/create_node", "isssi", create_node_by_uri_cb, this); + lo_server_thread_add_method(_st, "/om/synth/destroy", "is", destroy_cb, this); + lo_server_thread_add_method(_st, "/om/synth/rename", "iss", rename_cb, this); + lo_server_thread_add_method(_st, "/om/synth/connect", "iss", connect_cb, this); + lo_server_thread_add_method(_st, "/om/synth/disconnect", "iss", disconnect_cb, this); + lo_server_thread_add_method(_st, "/om/synth/disconnect_all", "is", disconnect_all_cb, this); + lo_server_thread_add_method(_st, "/om/synth/set_port_value", "isf", set_port_value_cb, this); + lo_server_thread_add_method(_st, "/om/synth/set_port_value", "isif", set_port_value_voice_cb, this); + lo_server_thread_add_method(_st, "/om/synth/set_port_value_slow", "isf", set_port_value_slow_cb, this); + lo_server_thread_add_method(_st, "/om/synth/note_on", "isii", note_on_cb, this); + lo_server_thread_add_method(_st, "/om/synth/note_off", "isi", note_off_cb, this); + lo_server_thread_add_method(_st, "/om/synth/all_notes_off", "isi", all_notes_off_cb, this); + lo_server_thread_add_method(_st, "/om/synth/midi_learn", "is", midi_learn_cb, this); +#ifdef HAVE_LASH + lo_server_thread_add_method(_st, "/om/lash/restore_finished", "i", lash_restore_done_cb, this); +#endif + + lo_server_thread_add_method(_st, "/om/metadata/request", "isss", metadata_get_cb, this); + lo_server_thread_add_method(_st, "/om/metadata/set", "isss", metadata_set_cb, this); + + // Queries + lo_server_thread_add_method(_st, "/om/request/plugins", "i", request_plugins_cb, this); + lo_server_thread_add_method(_st, "/om/request/all_objects", "i", request_all_objects_cb, this); + lo_server_thread_add_method(_st, "/om/request/port_value", "is", request_port_value_cb, this); + + // DSSI support +#ifdef HAVE_DSSI + // XXX WARNING: notice this is a catch-all + lo_server_thread_add_method(_st, NULL, NULL, dssi_cb, this); +#endif + + lo_server_thread_add_method(_st, NULL, NULL, unknown_cb, NULL); +} + + +OSCReceiver::~OSCReceiver() +{ + deactivate(); + + if (_st != NULL) { + lo_server_thread_free(_st); + _st = NULL; + } +} + + +void +OSCReceiver::start() +{ + QueuedEventSource::start(); + + if (!_is_activated) { + lo_server_thread_start(_st); + _is_activated = true; + } + + /* Waiting on the next liblo release + pthread_t lo_thread = lo_server_thread_get_thread(_st); + + sched_param sp; + sp.sched_priority = 20; + int result = pthread_setschedparam(lo_thread, SCHED_FIFO, &sp); + if (!result) + cout << "[OSC] Set OSC thread to realtime scheduling (SCHED_FIFO, priority " + << sp.sched_priority << ")" << endl; + else + cout << "[OSC] Unable to set OSC thread to realtime scheduling (" + << strerror(result) << endl; + */ +} + + +void +OSCReceiver::stop() +{ + if (_is_activated) { + lo_server_thread_stop(_st); + cout << "[OSCReceiver] Stopped OSC server thread" << endl; + _is_activated = false; + } + QueuedEventSource::stop(); +} + + +/** Create a new responder for this message, if necessary. + * + * This is based on the fact that the current responder is stored in a ref + * counted pointer, and events just take a reference to that. Thus, events + * may delete their responder if we've since switched to a new one, or the + * same one can stay around and serve a series of events. Reference counting + * is pretty sweet, eh? + * + * If this message came from the same source as the last message, no allocation + * of responders or lo_addresses or any of it needs to be done. Unfortunately + * the only way to check is by comparing URLs, because liblo addresses suck. + * + * Really, this entire thing is a basically just a crafty way of partially + * working around the fact that liblo addresses really suck. Oh well. + */ +int +OSCReceiver::set_response_address_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + OSCReceiver* const me = reinterpret_cast<OSCReceiver*>(user_data); + + //cerr << "SET RESPONSE\n"; + + if (argc < 1 || types[0] != 'i') // Not a valid Om message + return 0; // Save liblo the trouble + + //cerr << "** valid msg\n"; + + const int id = argv[0]->i; + + // Need to respond + if (id != -1) { + const lo_address addr = lo_message_get_source(msg); + char* const url = lo_address_get_url(addr); + + //cerr << "** need to respond\n"; + + // Currently have an OSC responder, check if it's still okay + if (me->_responder == me->_osc_responder) { + //cerr << "** osc responder\n"; + + if (!strcmp(url, me->_osc_responder->url())) { + // Nice one, same address + //cerr << "** Using cached response address, hooray" << endl; + } else { + // Shitty deal, make a new one + //cerr << "** Setting response address to " << url << "(2)" << endl; + me->_osc_responder = CountedPtr<OSCResponder>(new OSCResponder(id, url)); + me->set_responder(me->_osc_responder); + // (responder takes ownership of url, no leak) + } + + // Otherwise we have a NULL responder, definitely need to set a new one + } else { + //cerr << "** null responder\n"; + me->_osc_responder = CountedPtr<OSCResponder>(new OSCResponder(id, url)); + me->set_responder(me->_osc_responder); + //cerr << "** Setting response address to " << url << "(2)" << endl; + } + + // Don't respond + } else { + me->disable_responses(); + //cerr << "** Not responding." << endl; + } + + // If this returns 0 no OSC commands will work + return 1; +} + + +void +OSCReceiver::error_cb(int num, const char* msg, const char* path) +{ + cerr << "liblo server error " << num << " in path \"" << "\" - " << msg << endl; +} + + +/** \page engine_osc_namespace + * <p> \b /om/ping - Immediately sends a successful response to the given response id. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCReceiver::m_ping_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + _responder->respond_ok(); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/ping_slow - Sends response after going through the ("slow") event queue. + * \arg \b response-id (integer) + * + * \li See the documentation for /om/synth/set_port_value_slow for an explanation of how + * this differs from /om/ping. This is useful to send after sending a large cluster of + * events as a sentinel and wait on it's response, to know when the events are all + * finished processing.</p> \n \n + */ +int +OSCReceiver::m_ping_slow_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + ping(); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/engine/quit - Terminates the engine. + * \arg \b response-id (integer) + * + * \li Note that there is NO order guarantees with this command at all. You could + * send 10 messages then quit, and the quit reply could come immediately and the + * 10 messages would never get executed. </p> \n \n + */ +int +OSCReceiver::m_quit_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + + quit(); + + return 0; +} + +/** \page engine_osc_namespace + * <p> \b /om/engine/register_client - Registers a new client with the engine + * \arg \b response-id (integer) + * + * The incoming address will be used for the new registered client. If you + * want to register a different specific address, use the URL version. + */ +int +OSCReceiver::m_register_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + lo_address addr = lo_message_get_source(msg); + + char* const url = lo_address_get_url(addr); + CountedPtr<ClientInterface> client(new OSCClient((const char*)url)); + register_client(ClientKey(ClientKey::OSC_URL, (const char*)url), client); + free(url); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/engine/unregister_client - Unregisters a client + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCReceiver::m_unregister_client_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + lo_address addr = lo_message_get_source(msg); + + char* url = lo_address_get_url(addr); + unregister_client(ClientKey(ClientKey::OSC_URL, url)); + free(url); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/engine/load_plugins - Locates all available plugins, making them available for use. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCReceiver::m_load_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + load_plugins(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/engine/activate - Activate the engine (MIDI, audio, everything) + * \arg \b response-id (integer) </p> + * + * \li Note that you <b>must</b> send this message first if you want the engine to do + * anything at all - <em>including respond to your messages!</em> \n \n + */ +int +OSCReceiver::m_engine_activate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + activate(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/engine/deactivate - Deactivate the engine completely. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCReceiver::m_engine_deactivate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + deactivate(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/create_patch - Creates a new, empty, toplevel patch. + * \arg \b response-id (integer) + * \arg \b patch-path (string) - Patch path (complete, ie /master/parent/new_patch) + * \arg \b poly (integer) - Patch's (internal) polyphony </p> \n \n + */ +int +OSCReceiver::m_create_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + const int poly = argv[2]->i; + + create_patch(patch_path, poly); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/rename - Rename an Object (only Nodes, for now) + * \arg \b response-id (integer) + * \arg \b path - Object's path + * \arg \b name - New name for object </p> \n \n + */ +int +OSCReceiver::m_rename_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* object_path = &argv[1]->s; + const char* name = &argv[2]->s; + + rename(object_path, name); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/enable_patch - Enable DSP processing of a patch + * \arg \b response-id (integer) + * \arg \b patch-path - Patch's path + */ +int +OSCReceiver::m_enable_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + + enable_patch(patch_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/disable_patch - Disable DSP processing of a patch + * \arg \b response-id (integer) + * \arg \b patch-path - Patch's path + */ +int +OSCReceiver::m_disable_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + + disable_patch(patch_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/clear_patch - Remove all nodes from a patch + * \arg \b response-id (integer) + * \arg \b patch-path - Patch's path + */ +int +OSCReceiver::m_clear_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + + clear_patch(patch_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/create_node - Add a node into a given patch (load a plugin by URI) + * \arg \b response-id (integer) + * \arg \b node-path (string) - Full path of the new node (ie. /patch2/subpatch/newnode) + * \arg \b type (string) - Plugin type ("Internal", "LV2", "DSSI", "LADSPA") + * \arg \b plug-uri (string) - URI of the plugin to load + * \arg \b poly (integer-boolean) - Whether node is polyphonic (0 = false, 1 = true) </p> \n \n + */ +int +OSCReceiver::m_create_node_by_uri_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* node_path = &argv[1]->s; + const char* type = &argv[2]->s; + const char* plug_uri = &argv[3]->s; + const int poly = argv[4]->i; + + // FIXME: make sure poly is valid + + create_node(node_path, type, plug_uri, (poly == 1)); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/create_node - Add a node into a given patch (load a plugin by libname, label) + * \arg \b response-id (integer) + * \arg \b node-path (string) - Full path of the new node (ie. /patch2/subpatch/newnode) + * \arg \b type (string) - Plugin type ("LADSPA" or "Internal") + * \arg \b lib-name (string) - Name of library where plugin resides (eg "cmt.so") + * \arg \b plug-label (string) - Label (ID) of plugin (eg "sine_fcaa") + * \arg \b poly (integer-boolean) - Whether node is polyphonic (0 = false, 1 = true) + * + * \li This is only here to provide backwards compatibility for old patches that store plugin + * references (particularly LADSPA) as libname, label. + * </p> \n \n + */ +int +OSCReceiver::m_create_node_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + + const char* node_path = &argv[1]->s; + const char* type = &argv[2]->s; + const char* lib_name = &argv[3]->s; + const char* plug_label = &argv[4]->s; + const int poly = argv[5]->i; + */ + cerr << "LOAD NODE BY LIB LABEL\n"; + return 0; + #if 0 + // FIXME Event-ize + + Plugin* plugin = new Plugin(); + plugin->set_type(type); + plugin->lib_name(lib_name); + plugin->plug_label(plug_label); + + if (poly != 0 && poly != 1) { + OSCResponder(addr).respond_error("Invalid poly parameter in create_node"); + return 0; + } + + add_node(node_path, plugin, (poly == 1)); + + return 0; + #endif +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/destroy - Removes (destroys) a Patch or a Node + * \arg \b response-id (integer) + * \arg \b node-path (string) - Full path of the object </p> \n \n + */ +int +OSCReceiver::m_destroy_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* node_path = &argv[1]->s; + + destroy(node_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/connect - Connects two ports (must be in the same patch) + * \arg \b response-id (integer) + * \arg \b src-port-path (string) - Full path of source port + * \arg \b dst-port-path (string) - Full path of destination port </p> \n \n + */ +int +OSCReceiver::m_connect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* src_port_path = &argv[1]->s; + const char* dst_port_path = &argv[2]->s; + + connect(src_port_path, dst_port_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/disconnect - Disconnects two ports. + * \arg \b response-id (integer) + * \arg \b src-port-path (string) - Full path of source port + * \arg \b dst-port-path (string) - Full path of destination port </p> \n \n + */ +int +OSCReceiver::m_disconnect_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* src_port_path = &argv[1]->s; + const char* dst_port_path = &argv[2]->s; + + disconnect(src_port_path, dst_port_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/disconnect_all - Disconnect all connections to/from a node. + * \arg \b response-id (integer) + * \arg \b node-path (string) - Full path of node. </p> \n \n + */ +int +OSCReceiver::m_disconnect_all_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* node_path = &argv[1]->s; + + disconnect_all(node_path); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/set_port_value - Sets the value of a port for all voices (both AR and CR) + * \arg \b response-id (integer) + * \arg \b port-path (string) - Name of port + * \arg \b value (float) - Value to set port to + */ +int +OSCReceiver::m_set_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* port_path = &argv[1]->s; + const float value = argv[2]->f; + + set_port_value(port_path, value); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/set_port_value - Sets the value of a port for a specific voice (both AR and CR) + * \arg \b response-id (integer) + * \arg \b port-path (string) - Name of port + * \arg \b voice (integer) - Voice to set port value for + * \arg \b value (float) - Value to set port to + */ +int +OSCReceiver::m_set_port_value_voice_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* port_path = &argv[1]->s; + const int voice = argv[2]->i; + const float value = argv[3]->f; + + set_port_value(port_path, voice, value); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/set_port_value_slow - Sets the value of a port for all voices (as a QueuedEvent) + * \arg \b response-id (integer) + * \arg \b port-path (string) - Name of port + * \arg \b value (float) - Value to set port to + * + * \li This version exists so you can send it immediately after a QueuedEvent it may depend on (ie a + * node creation) and be sure it happens after the event (a normal set_port_value could beat the + * slow event and arrive out of order). </p> \n \n + */ +int +OSCReceiver::m_set_port_value_slow_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* port_path = &argv[1]->s; + const float value = argv[2]->f; + + set_port_value_queued(port_path, value); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/note_on - Triggers a note-on, just as if it came from MIDI + * \arg \b response-id (integer) + * \arg \b node-path (string) - Patch of Node to trigger (must be a trigger or note node) + * \arg \b note-num (int) - MIDI style note number (0-127) + * \arg \b velocity (int) - MIDI style velocity (0-127)</p> \n \n + */ +int +OSCReceiver::m_note_on_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + + const char* node_path = &argv[1]->s; + const uchar note_num = argv[2]->i; + const uchar velocity = argv[3]->i; + */ + cerr << "FIXME: OSC note on\n"; + //note_on(node_path, note_num, velocity); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/note_off - Triggers a note-off, just as if it came from MIDI + * \arg \b response-id (integer) + * \arg \b node-path (string) - Patch of Node to trigger (must be a trigger or note node) + * \arg \b note-num (int) - MIDI style note number (0-127)</p> \n \n + */ +int +OSCReceiver::m_note_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + + const char* patch_path = &argv[1]->s; + const uchar note_num = argv[2]->i; + */ + cerr << "FIXME: OSC note off\n"; + //note_off(patch_path, note_num); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/all_notes_off - Triggers a note-off for all voices, just as if it came from MIDI + * \arg \b response-id (integer) + * \arg \b patch-path (string) - Patch of patch to send event to </p> \n \n + */ +int +OSCReceiver::m_all_notes_off_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + + const char* patch_path = &argv[1]->s; + */ + cerr << "FIXME: OSC all notes off\n"; + //all_notes_off(patch_path); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/synth/midi_learn - Initiate MIDI learn for a given (MIDI Control) Node + * \arg \b response-id (integer) + * \arg \b node-path (string) - Patch of the Node that should learn the next MIDI event. + * + * \li This of course will only do anything for MIDI control nodes. The node will learn the next MIDI + * event that arrives at it's MIDI input port - no behind the scenes voodoo happens here. It is planned + * that a plugin specification supporting arbitrary OSC commands for plugins will exist one day, and this + * method will go away completely. </p> \n \n + */ +int +OSCReceiver::m_midi_learn_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + + midi_learn(patch_path); + + return 0; +} + + +#ifdef HAVE_LASH +/** \page engine_osc_namespace + * <p> \b /om/lash/restore_done - Notify LASH restoring is finished and connections can be made. + * \arg \b response-id (integer) + */ +int +OSCReceiver::m_lash_restore_done_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + lash_retore_done(); + + return 0; +} +#endif // HAVE_LASH + + +/** \page engine_osc_namespace + * <p> \b /om/metadata/set - Sets a piece of metadata, associated with a synth-space object (node, etc) + * \arg \b response-id (integer) + * \arg \b object-path (string) - Full path of object to associate metadata with + * \arg \b key (string) - Key (index) for new piece of metadata + * \arg \b value (string) - Value of new piece of metadata + */ +int +OSCReceiver::m_metadata_set_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* node_path = &argv[1]->s; + const char* key = &argv[2]->s; + const char* value = &argv[3]->s; + + set_metadata(node_path, key, value); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/metadata/responder - Requests the engine send a piece of metadata, associated with a synth-space object (node, etc) + * \arg \b response-id (integer) + * \arg \b object-path (string) - Full path of object metadata is associated with + * \arg \b key (string) - Key (index) for piece of metadata + * + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCReceiver::m_metadata_get_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + /* + const char* node_path = &argv[1]->s; + const char* key = &argv[2]->s; + */ + cerr << "FIXME: OSC metadata request\n"; + // FIXME: Equivalent? + /* + RequestMetadataEvent* ev = new RequestMetadataEvent( + new OSCResponder(ClientKey(addr)), + node_path, key); + + */ + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/responder/plugins - Requests the engine send a list of all known plugins. + * \arg \b response-id (integer) + * + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCReceiver::m_request_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + cerr << "REQUEST PLUGINS\n"; + + // FIXME + request_plugins(); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/responder/all_objects - Requests the engine send information about \em all objects (patches, nodes, etc) + * \arg \b response-id (integer) + * + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCReceiver::m_request_all_objects_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + request_all_objects(); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /om/responder/port_value - Requests the engine send the value of a port. + * \arg \b response-id (integer) + * \arg \b port-path (string) - Full path of port to send the value of </p> \n \n + * + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCReceiver::m_request_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* port_path = &argv[1]->s; + + request_port_value(port_path); + + return 0; +} + + +#ifdef HAVE_DSSI +int +OSCReceiver::m_dssi_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ +#if 0 + string node_path(path); + + if (node_path.substr(0, 5) != "/dssi") + return 1; + + string command = node_path.substr(node_path.find_last_of("/")+1); + node_path = node_path.substr(5); // chop off leading "/dssi/" + node_path = node_path.substr(0, node_path.find_last_of("/")); // chop off command at end + + //cout << "DSSI: Got message " << command << " for node " << node_path << endl; + + QueuedEvent* ev = NULL; + + if (command == "update" && !strcmp(types, "s")) + ev = new DSSIUpdateEvent(NULL, node_path, &argv[0]->s); + else if (command == "control" && !strcmp(types, "if")) + ev = new DSSIControlEvent(NULL, node_path, argv[0]->i, argv[1]->f); + else if (command == "configure" && ~strcmp(types, "ss")) + ev = new DSSIConfigureEvent(NULL, node_path, &argv[0]->s, &argv[1]->s); + else if (command == "program" && ~strcmp(types, "ii")) + ev = new DSSIProgramEvent(NULL, node_path, argv[0]->i, argv[1]->i); + + if (ev != NULL) + push(ev); + else + cerr << "[OSCReceiver] Unknown DSSI command received: " << path << endl; +#endif + return 0; +} +#endif + + +// Static Callbacks // + + +// Display incoming OSC messages (for debugging purposes) +int +OSCReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + printf("[OSCMsg] %s\n", path); + + for (int i=0; i < argc; ++i) { + printf(" '%c' ", types[i]); + lo_arg_pp(lo_type(types[i]), argv[i]); + printf("\n"); + } + printf("\n"); + + return 1; // not handled +} + + +int +OSCReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + cerr << "Unknown command " << path << " (" << types << "), sending error.\n"; + + string error_msg = "Unknown command: "; + error_msg.append(path).append(" ").append(types); + + om->client_broadcaster()->send_error(error_msg); + + return 0; +} + + +} // namespace Om diff --git a/src/libs/engine/OSCReceiver.h b/src/libs/engine/OSCReceiver.h new file mode 100644 index 00000000..b80c25c3 --- /dev/null +++ b/src/libs/engine/OSCReceiver.h @@ -0,0 +1,124 @@ +/* This file is part of Om. Copyright (C) 2006 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 OSCRECEIVER_H +#define OSCRECEIVER_H + +#include "config.h" +#include <string> +#include <lo/lo.h> +#include "QueuedEngineInterface.h" +#include "OSCResponder.h" +using std::string; + +namespace Om { + +class JackDriver; +class NodeFactory; +class Patch; + + +/* Some boilerplate killing macros... */ +#define LO_HANDLER_ARGS const char* path, const char* types, lo_arg** argv, int argc, lo_message msg + +/* Defines a static handler to be passed to lo_add_method, which is a trivial + * wrapper around a non-static method that does the real work. Makes a whoole + * lot of ugly boiler plate go away */ +#define LO_HANDLER(name) \ +int m_##name##_cb (LO_HANDLER_ARGS);\ +inline static int name##_cb(LO_HANDLER_ARGS, void* osc_receiver)\ +{ return ((OSCReceiver*)osc_receiver)->m_##name##_cb(path, types, argv, argc, msg); } + + + +/** Receives OSC messages from liblo. + * + * This inherits from QueuedEngineInterface and calls it's own functions + * via OSC. It's not actually a directly callable EngineInterface (it's + * callable via OSC...) so it is-implemented-as-a (privately inherits) + * QueuedEngineInterface. + * + * \ingroup engine + */ +class OSCReceiver : private QueuedEngineInterface +{ +public: + OSCReceiver(size_t queue_size, const char* const port); + ~OSCReceiver(); + + void start(); + void stop(); + +private: + // Prevent copies (undefined) + OSCReceiver(const OSCReceiver&); + OSCReceiver& operator=(const OSCReceiver&); + + static void error_cb(int num, const char* msg, const char* path); + static int set_response_address_cb(LO_HANDLER_ARGS, void* osc_receiver); + static int generic_cb(LO_HANDLER_ARGS, void* osc_receiver); + static int unknown_cb(LO_HANDLER_ARGS, void* osc_receiver); + + LO_HANDLER(quit); + LO_HANDLER(ping); + LO_HANDLER(ping_slow); + LO_HANDLER(register_client); + LO_HANDLER(unregister_client); + LO_HANDLER(load_plugins); + LO_HANDLER(engine_activate); + LO_HANDLER(engine_deactivate); + LO_HANDLER(create_patch); + LO_HANDLER(rename); + LO_HANDLER(create_node); + LO_HANDLER(create_node_by_uri); + LO_HANDLER(enable_patch); + LO_HANDLER(disable_patch); + LO_HANDLER(clear_patch); + LO_HANDLER(destroy); + LO_HANDLER(connect); + LO_HANDLER(disconnect); + LO_HANDLER(disconnect_all); + LO_HANDLER(set_port_value); + LO_HANDLER(set_port_value_voice); + LO_HANDLER(set_port_value_slow); + LO_HANDLER(note_on); + LO_HANDLER(note_off); + LO_HANDLER(all_notes_off); + LO_HANDLER(midi_learn); + LO_HANDLER(metadata_get); + LO_HANDLER(metadata_set); + LO_HANDLER(request_plugins); + LO_HANDLER(request_all_objects); + LO_HANDLER(request_port_value); +#ifdef HAVE_DSSI + LO_HANDLER(dssi); +#endif +#ifdef HAVE_LASH + LO_HANDLER(lash_restore_done); +#endif + + const char* const _port; + bool _is_activated; + lo_server_thread _st; + + /** Cached OSC responder (for most recent incoming message) */ + CountedPtr<OSCResponder> _osc_responder; +}; + + +} // namespace Om + +#endif // OSCRECEIVER_H diff --git a/src/libs/engine/OSCResponder.cpp b/src/libs/engine/OSCResponder.cpp new file mode 100644 index 00000000..82e6b55d --- /dev/null +++ b/src/libs/engine/OSCResponder.cpp @@ -0,0 +1,88 @@ +/* This file is part of Om. Copyright (C) 2006 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 "OSCResponder.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" +#include "interface/ClientKey.h" +#include <cstdlib> +#include <iostream> +#include <inttypes.h> +#include <lo/lo.h> + +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +/** Construct an OSCResponder from \a addr. + * Takes ownership of @a url. + */ +OSCResponder::OSCResponder(int32_t id, char* url) +: Responder() +, _id(id) +, _url(url) +, _addr(NULL) +{ + // If ID is -1 this shouldn't have even been created + assert(id != -1); +} + + +OSCResponder::~OSCResponder() +{ + //cerr << "DELETING " << _url << " RESPONDER\n"; + + if (_addr) + lo_address_free(_addr); +} + + +CountedPtr<Shared::ClientInterface> +OSCResponder::find_client() +{ + return om->client_broadcaster()->client(ClientKey(ClientKey::OSC_URL, _url)); +} + + +void +OSCResponder::respond_ok() +{ + _addr = lo_address_new_from_url(_url); + + //cerr << "OK " << _id << endl; + if (lo_send(_addr, "/om/response/ok", "i", _id) < 0) { + cerr << "Unable to send response " << _id << "! (" + << lo_address_errstr(_addr) << ")" << endl; + } +} + + +void +OSCResponder::respond_error(const string& msg) +{ + _addr = lo_address_new_from_url(_url); + + //cerr << "ERR " << _id << endl; + if (lo_send(_addr, "/om/response/error", "is",_id, msg.c_str()) < 0) { + cerr << "Unable to send response " << _id << "! (" + << lo_address_errstr(_addr) << endl; + } +} + +} // namespace OM + diff --git a/src/libs/engine/OSCResponder.h b/src/libs/engine/OSCResponder.h new file mode 100644 index 00000000..f579df98 --- /dev/null +++ b/src/libs/engine/OSCResponder.h @@ -0,0 +1,61 @@ +/* This file is part of Om. Copyright (C) 2006 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 OSCRESPONDER_H +#define OSCRESPONDER_H + +#include <inttypes.h> +#include <memory> +#include <lo/lo.h> +#include "Responder.h" + +namespace Om { + + +/** Responder for (liblo) OSC clients. + * + * OSC Clients use request IDs to be able to associate replies with sent + * events. If the ID is -1, a response will not be sent (and the overhead + * of searching for the client's record will be skipped). Any other integer + * is a valid response ID and will be responded to. + * + * Creation of the lo_address is deferred until needed to avoid bogging down + * the receiving thread as much as possible. + */ +class OSCResponder : public Responder +{ +public: + OSCResponder(int32_t id, char* url); + ~OSCResponder(); + + CountedPtr<Shared::ClientInterface> find_client(); + + void respond_ok(); + void respond_error(const string& msg); + + const char* url() const { return _url; } + +private: + int32_t _id; + char* const _url; + lo_address _addr; +}; + + +} // namespace Om + +#endif // OSCRESPONDER_H + diff --git a/src/libs/engine/ObjectSender.cpp b/src/libs/engine/ObjectSender.cpp new file mode 100644 index 00000000..3db6915d --- /dev/null +++ b/src/libs/engine/ObjectSender.cpp @@ -0,0 +1,207 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ObjectSender.h" +#include "interface/ClientInterface.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "Patch.h" +#include "Node.h" +#include "Port.h" +#include "PortInfo.h" +#include "PortBase.h" +#include "Connection.h" +#include "NodeFactory.h" + +namespace Om { + +/** Send all currently existing objects to client. + */ +void +ObjectSender::send_all(ClientInterface* client) +{ + for (Tree<OmObject*>::iterator i = om->object_store()->objects().begin(); + i != om->object_store()->objects().end(); ++i) + if ((*i)->as_patch() != NULL && (*i)->parent() == NULL) + send_patch(client, (*i)->as_patch()); + //(*i)->as_node()->send_creation_messages(client); + +} + + +void +ObjectSender::send_patch(ClientInterface* client, const Patch* patch) +{ + client->new_patch(patch->path(), patch->internal_poly()); + + for (List<Node*>::const_iterator j = patch->nodes().begin(); + j != patch->nodes().end(); ++j) { + Node* const node = (*j); + Port* const port = node->as_port(); // NULL unless a bridge node + send_node(client, node); + + usleep(100); + + // If this is a bridge (input/output) node, send the patch control value as well + if (port && port->port_info()->is_control()) + client->control_change(port->path(), + ((PortBase<sample>*)port)->buffer(0)->value_at(0)); + } + + for (List<Connection*>::const_iterator j = patch->connections().begin(); + j != patch->connections().end(); ++j) + client->connection((*j)->src_port()->path(), (*j)->dst_port()->path()); + + // Send port information + /*for (size_t i=0; i < m_ports.size(); ++i) { + Port* const port = m_ports.at(i); + + // Send metadata + const map<string, string>& data = port->metadata(); + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) + om->client_broadcaster()->send_metadata_update_to(client, port->path(), (*i).first, (*i).second); + + if (port->port_info()->is_control()) + om->client_broadcaster()->send_control_change_to(client, port->path(), + ((PortBase<sample>*)port)->buffer(0)->value_at(0)); + }*/ +} + + +void +ObjectSender::send_node(ClientInterface* client, const Node* node) +{ + int polyphonic = + (node->poly() > 1 + && node->poly() == node->parent_patch()->internal_poly() + ? 1 : 0); + + assert(node->plugin()->uri().length() > 0); + assert(node->path().length() > 0); + + client->bundle_begin(); + + // FIXME: bundleify + + const Array<Port*>& ports = node->ports(); + + client->new_node(node->plugin()->type_string(), node->plugin()->uri(), + node->path(), polyphonic, ports.size()); + + // Send ports + for (size_t j=0; j < ports.size(); ++j) { + Port* const port = ports.at(j); + PortInfo* const info = port->port_info(); + + assert(port); + assert(info); + + client->new_port(port->path(), info->type_string(), info->is_output()); + + /*m = lo_message_new(); + lo_message_add_string(m, port->path().c_str()); + lo_message_add_string(m, info->type_string().c_str()); + lo_message_add_string(m, info->direction_string().c_str()); + lo_message_add_string(m, info->hint_string().c_str()); + lo_message_add_float(m, info->default_val()); + lo_message_add_float(m, info->min_val()); + lo_message_add_float(m, info->max_val()); + lo_bundle_add_message(b, "/om/new_port", m); + msgs.push_back(m);*/ + + // If the bundle is getting very large, send it and start + // a new one + /*if (lo_bundle_length(b) > 1024) { + lo_send_bundle(_address, b); + lo_bundle_free(b); + b = lo_bundle_new(tt); + }*/ + } + + client->bundle_end(); +} + + +void +ObjectSender::send_port(ClientInterface* client, const Port* port) +{ + PortInfo* info = port->port_info(); + + client->new_port(port->path(), info->type_string(), info->is_output()); + + // Send metadata + const map<string, string>& data = port->metadata(); + for (map<string, string>::const_iterator j = data.begin(); j != data.end(); ++j) + client->metadata_update(port->path(), (*j).first, (*j).second); +} + + +void +ObjectSender::send_plugins(ClientInterface* client) +{ + om->node_factory()->lock_plugin_list(); + + const list<Plugin*>& plugs = om->node_factory()->plugins(); + +/* + lo_timetag tt; + lo_timetag_now(&tt); + lo_bundle b = lo_bundle_new(tt); + lo_message m = lo_message_new(); + list<lo_message> msgs; + + lo_message_add_int32(m, plugs.size()); + lo_bundle_add_message(b, "/om/num_plugins", m); + msgs.push_back(m); +*/ + for (list<Plugin*>::const_iterator j = plugs.begin(); j != plugs.end(); ++j) { + const Plugin* const p = *j; + client->new_plugin(p->type_string(), p->uri(), p->name()); + } +/* + plugin = (*j); + m = lo_message_new(); + + lo_message_add_string(m, plugin->type_string()); + lo_message_add_string(m, plugin->uri().c_str()); + lo_message_add_string(m, plugin->name().c_str()); + lo_bundle_add_message(b, "/om/plugin", m); + msgs.push_back(m); + if (lo_bundle_length(b) > 1024) { + // FIXME FIXME FIXME dirty, dirty cast + lo_send_bundle(((OSCClient*)client)->address(), b); + lo_bundle_free(b); + b = lo_bundle_new(tt); + } + }*/ +/* + if (lo_bundle_length(b) > 0) { + // FIXME FIXME FIXME dirty, dirty cast + lo_send_bundle(((OSCClient*)client)->address(), b); + lo_bundle_free(b); + } else { + lo_bundle_free(b); + } + for (list<lo_bundle>::const_iterator i = msgs.begin(); i != msgs.end(); ++i) + lo_message_free(*i); +*/ + om->node_factory()->unlock_plugin_list(); +} + + +} // namespace Om + diff --git a/src/libs/engine/ObjectSender.h b/src/libs/engine/ObjectSender.h new file mode 100644 index 00000000..f97f1f9e --- /dev/null +++ b/src/libs/engine/ObjectSender.h @@ -0,0 +1,55 @@ +/* This file is part of Om. Copyright (C) 2006 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 OBJECTSENDER_H +#define OBJECTSENDER_H + +namespace Om { + +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + +class Patch; +class Node; +class Port; + + +/** Utility class for sending OmObjects to clients through ClientInterface. + * + * While ClientInterface is the direct low level message-based interface + * (protocol), this is used from the engine to easily send proper Objects + * with these messages (which is done in a few different parts of the code). + * + * Basically a serializer, except to calls on ClientInterface rather than + * eg a byte stream. + */ +class ObjectSender { +public: + + // FIXME: Make all object parameters const + + static void send_all(ClientInterface* client); + static void send_patch(ClientInterface* client, const Patch* patch); + static void send_node(ClientInterface* client, const Node* node); + static void send_port(ClientInterface* client, const Port* port); + static void send_plugins(ClientInterface* client); +}; + +} // namespace Om + +#endif // OBJECTSENDER_H + diff --git a/src/libs/engine/ObjectStore.cpp b/src/libs/engine/ObjectStore.cpp new file mode 100644 index 00000000..a1cf1287 --- /dev/null +++ b/src/libs/engine/ObjectStore.cpp @@ -0,0 +1,109 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ObjectStore.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "Node.h" +#include "Port.h" +#include "List.h" +#include "util/Path.h" +#include "Tree.h" + +namespace Om { + + +/** Find the Patch at the given path. + */ +Patch* +ObjectStore::find_patch(const Path& path) +{ + OmObject* const object = find(path); + return (object == NULL) ? NULL : object->as_patch(); +} + + +/** Find the Node at the given path. + */ +Node* +ObjectStore::find_node(const Path& path) +{ + OmObject* const object = find(path); + return (object == NULL) ? NULL : object->as_node(); +} + + +/** Find the Port at the given path. + */ +Port* +ObjectStore::find_port(const Path& path) +{ + OmObject* const object = find(path); + return (object == NULL) ? NULL : object->as_port(); +} + + +/** Find the Object at the given path. + */ +OmObject* +ObjectStore::find(const Path& path) +{ + return m_objects.find(path); +} + + +/** Add an object to the store. Not realtime safe. + */ +void +ObjectStore::add(OmObject* o) +{ + //cerr << "[ObjectStore] Adding " << o->path() << endl; + m_objects.insert(new TreeNode<OmObject*>(o->path(), o)); +} + + +/** Add an object to the store. Not realtime safe. + */ +void +ObjectStore::add(TreeNode<OmObject*>* tn) +{ + //cerr << "[ObjectStore] Adding " << tn->key() << endl; + m_objects.insert(tn); +} + + +/** Remove a patch from the store. + * + * It it the caller's responsibility to delete the returned ListNode. + * + * @returns TreeNode containing object removed on success, NULL if not found. + */ +TreeNode<OmObject*>* +ObjectStore::remove(const string& path) +{ + TreeNode<OmObject*>* const removed = m_objects.remove(path); + + if (removed == NULL) + cerr << "[ObjectStore] WARNING: Removing " << path << " failed." << endl; + //else + // cerr << "[ObjectStore] Removed " << path << endl; + + return removed; +} + + +} // namespace Om diff --git a/src/libs/engine/ObjectStore.h b/src/libs/engine/ObjectStore.h new file mode 100644 index 00000000..8c71c002 --- /dev/null +++ b/src/libs/engine/ObjectStore.h @@ -0,0 +1,61 @@ +/* This file is part of Om. Copyright (C) 2006 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 OBJECTSTORE_H +#define OBJECTSTORE_H + +#include <string> +#include "util/Path.h" +#include "Tree.h" +using std::string; + +namespace Om { + +class Patch; +class Node; +class Port; +class OmObject; + + +/** Storage for all OmObjects (tree of OmObject's sorted by path). + * + * All looking up in pre_process() methods (and anything else that isn't in-band + * with the audio thread) should use this (to read and modify the OmObject + * tree). + */ +class ObjectStore +{ +public: + Patch* find_patch(const Path& path); + Node* find_node(const Path& path); + Port* find_port(const Path& path); + OmObject* find(const Path& path); + + void add(OmObject* o); + void add(TreeNode<OmObject*>* o); + TreeNode<OmObject*>* remove(const string& key); + + const Tree<OmObject*>& objects() { return m_objects; } + +private: + Tree<OmObject*> m_objects; +}; + + +} // namespace Om + +#endif // OBJECTSTORE diff --git a/src/libs/engine/Om.cpp b/src/libs/engine/Om.cpp new file mode 100644 index 00000000..c04a7464 --- /dev/null +++ b/src/libs/engine/Om.cpp @@ -0,0 +1,36 @@ +/* This file is part of Om. Copyright (C) 2006 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 "OmApp.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif + +/** Om namespace. + * + * Everything internal to the Om engine lives in this namespace. Generally + * none of this code is used by clients - as special (possibly temporary) + * exceptions, some classes in src/common are used by clients but are part + * of the Om namespace. + */ +namespace Om +{ + OmApp* om = 0; +#ifdef HAVE_LASH + LashDriver* lash_driver = 0; +#endif +} diff --git a/src/libs/engine/Om.h b/src/libs/engine/Om.h new file mode 100644 index 00000000..2fe3fd8c --- /dev/null +++ b/src/libs/engine/Om.h @@ -0,0 +1,42 @@ +/* This file is part of Om. Copyright (C) 2006 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 OM_H +#define OM_H + +// FIXME: this dependency has to go +#include "config.h" + + +/* Things all over the place access various bits of Om through this. + * This was a bad design decision, it just evolved this way :/ + * I may refactor it eventually, but it works. + */ + +/** \defgroup engine Engine + */ +namespace Om +{ +#ifdef HAVE_LASH + class LashDriver; + extern LashDriver* lash_driver; +#endif + class OmApp; + extern OmApp* om; +} + +#endif // OM_H + diff --git a/src/libs/engine/OmApp.cpp b/src/libs/engine/OmApp.cpp new file mode 100644 index 00000000..ea7df1df --- /dev/null +++ b/src/libs/engine/OmApp.cpp @@ -0,0 +1,231 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Om.h" +#include "OmApp.h" +#include "config.h" +#include "tuning.h" +#include <sys/mman.h> +#include <iostream> +#include <unistd.h> +#include "Event.h" +#include "util/Queue.h" +#include "JackAudioDriver.h" +#include "NodeFactory.h" +#include "OSCReceiver.h" +#include "ClientBroadcaster.h" +#include "Patch.h" +#include "ObjectStore.h" +#include "MaidObject.h" +#include "Maid.h" +#include "MidiDriver.h" +#include "QueuedEventSource.h" +#include "PostProcessor.h" +#include "CreatePatchEvent.h" +#include "EnablePatchEvent.h" +#ifdef HAVE_JACK_MIDI +#include "JackMidiDriver.h" +#endif +#ifdef HAVE_ALSA_MIDI +#include "AlsaMidiDriver.h" +#endif +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif +using std::cout; using std::cerr; using std::endl; + +namespace Om { + + +OmApp::OmApp(const char* port) +: m_maid(new Maid(maid_queue_size)), + m_audio_driver(new JackAudioDriver()), +#ifdef HAVE_JACK_MIDI + m_midi_driver(new JackMidiDriver(((JackAudioDriver*)m_audio_driver)->jack_client())), +#elif HAVE_ALSA_MIDI + m_midi_driver(new AlsaMidiDriver()), +#else + m_midi_driver(new DummyMidiDriver()), +#endif + m_osc_receiver(new OSCReceiver(pre_processor_queue_size, port)), + m_client_broadcaster(new ClientBroadcaster()), + m_object_store(new ObjectStore()), + m_node_factory(new NodeFactory()), + m_event_queue(new Queue<Event*>(event_queue_size)), +// m_pre_processor(new QueuedEventSource(pre_processor_queue_size)), + m_post_processor(new PostProcessor(post_processor_queue_size)), + m_quit_flag(false), + m_activated(false) +{ + mlock(m_audio_driver, sizeof(JackAudioDriver)); + mlock(m_object_store, sizeof(ObjectStore)); + mlock(m_osc_receiver, sizeof(OSCReceiver)); +#ifdef HAVE_ALSA_MIDI + mlock(m_midi_driver, sizeof(AlsaMidiDriver)); +#else + mlock(m_midi_driver, sizeof(DummyMidiDriver)); +#endif + + m_osc_receiver->start(); + m_post_processor->start(); +} + + +OmApp::OmApp(const char* port, AudioDriver* audio_driver) +: m_maid(new Maid(maid_queue_size)), + m_audio_driver(audio_driver), +#ifdef HAVE_JACK_MIDI + m_midi_driver(new JackMidiDriver(((JackAudioDriver*)m_audio_driver)->jack_client())), +#elif HAVE_ALSA_MIDI + m_midi_driver(new AlsaMidiDriver()), +#else + m_midi_driver(new DummyMidiDriver()), +#endif + m_osc_receiver(new OSCReceiver(pre_processor_queue_size, port)), + m_client_broadcaster(new ClientBroadcaster()), + m_object_store(new ObjectStore()), + m_node_factory(new NodeFactory()), + m_event_queue(new Queue<Event*>(event_queue_size)), + //m_pre_processor(new QueuedEventSource(pre_processor_queue_size)), + m_post_processor(new PostProcessor(post_processor_queue_size)), + m_quit_flag(false), + m_activated(false) +{ + mlock(m_audio_driver, sizeof(JackAudioDriver)); + mlock(m_object_store, sizeof(ObjectStore)); + mlock(m_osc_receiver, sizeof(OSCReceiver)); +#ifdef HAVE_ALSA_MIDI + mlock(m_midi_driver, sizeof(AlsaMidiDriver)); +#else + mlock(m_midi_driver, sizeof(DummyMidiDriver)); +#endif + + m_osc_receiver->start(); + m_post_processor->start(); +} + + +OmApp::~OmApp() +{ + deactivate(); + + for (Tree<OmObject*>::iterator i = m_object_store->objects().begin(); + i != m_object_store->objects().end(); ++i) { + if ((*i)->parent() == NULL) + delete (*i); + } + + delete m_object_store; + delete m_client_broadcaster; + delete m_osc_receiver; + delete m_node_factory; + delete m_midi_driver; + delete m_audio_driver; + + delete m_maid; + + munlockall(); +} + + +/* driver() template specializations. + * Due to the lack of RTTI, this needs to be implemented manually like this. + * If more types/drivers start getting added, it may be worth it to enable + * RTTI and put all the drivers into a map with typeid's as the key. That's + * more elegant and extensible, but this is faster and simpler - for now. + */ +template<> +Driver<MidiMessage>* OmApp::driver<MidiMessage>() { return m_midi_driver; } +template<> +Driver<sample>* OmApp::driver<sample>() { return m_audio_driver; } + + +int +OmApp::main() +{ + // Loop until quit flag is set (by OSCReceiver) + while ( ! m_quit_flag) { + nanosleep(&main_rate, NULL); +#ifdef HAVE_LASH + // Process any pending LASH events + if (lash_driver->enabled()) + lash_driver->process_events(); +#endif + // Run the maid (garbage collector) + m_maid->cleanup(); + } + cout << "[Main] Done main loop." << endl; + + if (m_activated) + deactivate(); + + sleep(1); + cout << "[Main] Om exiting..." << endl; + + return 0; +} + + +void +OmApp::activate() +{ + if (m_activated) + return; + + // Create root patch + CreatePatchEvent create_ev(CountedPtr<Responder>(new Responder()), "/", 1); + create_ev.pre_process(); + create_ev.execute(0); + create_ev.post_process(); + EnablePatchEvent enable_ev(CountedPtr<Responder>(new Responder()), "/"); + enable_ev.pre_process(); + enable_ev.execute(0); + enable_ev.post_process(); + + assert(m_audio_driver->root_patch() != NULL); + + m_audio_driver->activate(); +#ifdef HAVE_ALSA_MIDI + m_midi_driver->activate(); +#endif + m_activated = true; +} + + +void +OmApp::deactivate() +{ + if (!m_activated) + return; + + m_audio_driver->root_patch()->process(false); + + for (Tree<OmObject*>::iterator i = m_object_store->objects().begin(); + i != m_object_store->objects().end(); ++i) + if ((*i)->as_node() != NULL && (*i)->as_node()->parent() == NULL) + (*i)->as_node()->deactivate(); + + if (m_midi_driver != NULL) + m_midi_driver->deactivate(); + + m_osc_receiver->stop(); + m_audio_driver->deactivate(); + + m_activated = false; +} + + +} // namespace Om diff --git a/src/libs/engine/OmApp.h b/src/libs/engine/OmApp.h new file mode 100644 index 00000000..0fe49b81 --- /dev/null +++ b/src/libs/engine/OmApp.h @@ -0,0 +1,99 @@ +/* This file is part of Om. Copyright (C) 2006 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 OMAPP_H +#define OMAPP_H + +template<typename T> class Queue; +class Maid; + +namespace Om { + +class AudioDriver; +class MidiDriver; +class NodeFactory; +class OSCReceiver; +class ClientBroadcaster; +class Patch; +class ObjectStore; +class EventSource; +class PostProcessor; +class Event; +class QueuedEvent; +template <typename T> class Driver; + + +/** The main class for Om, the whole app lives in here + * + * This class should not exist. + * + * \ingroup engine + */ +class OmApp +{ +public: + OmApp(const char* const port); + OmApp(const char* const port, AudioDriver* audio_driver); + ~OmApp(); + + int main(); + + /** Set the quit flag that should kill all threads and exit cleanly. + * Note that it will take some time. */ + void quit() { m_quit_flag = true; } + + void activate(); + void deactivate(); + + Maid* maid() const { return m_maid; } + AudioDriver* audio_driver() const { return m_audio_driver; } + MidiDriver* midi_driver() const { return m_midi_driver; } + OSCReceiver* osc_receiver() const { return m_osc_receiver; } + ClientBroadcaster* client_broadcaster() const { return m_client_broadcaster; } + ObjectStore* object_store() const { return m_object_store; } + NodeFactory* node_factory() const { return m_node_factory; } + Queue<Event*>* event_queue() const { return m_event_queue; } + //EventSource* pre_processor() const { return m_pre_processor; } + PostProcessor* post_processor() const { return m_post_processor; } + + /** Return the active driver for the given (template parameter) type. + * This is a hook for BridgeNode. See OmApp.cpp for specializations. */ + template<typename T> Driver<T>* driver(); + +private: + // Prevent copies + OmApp(const OmApp&); + OmApp& operator=(const OmApp&); + + Maid* m_maid; + AudioDriver* m_audio_driver; + MidiDriver* m_midi_driver; + OSCReceiver* m_osc_receiver; + ClientBroadcaster* m_client_broadcaster; + ObjectStore* m_object_store; + NodeFactory* m_node_factory; + Queue<Event*>* m_event_queue; + //EventSource* m_pre_processor; + PostProcessor* m_post_processor; + + bool m_quit_flag; + bool m_activated; +}; + + +} // namespace Om + +#endif // OMAPP_H diff --git a/src/libs/engine/OmInProcess.cpp b/src/libs/engine/OmInProcess.cpp new file mode 100644 index 00000000..971a1316 --- /dev/null +++ b/src/libs/engine/OmInProcess.cpp @@ -0,0 +1,74 @@ +/* This file is part of Om. Copyright (C) 2006 Mario Lang. + * + * 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 <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include <jack/jack.h> + +#include "Om.h" +#include "OmApp.h" +#include "OSCReceiver.h" +#include "JackAudioDriver.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif + +extern "C" +{ + int jack_initialize(jack_client_t* client, const char* load_init); + void jack_finish(void* arg); +} + + +void* +run_main(void* arg) +{ + Om::om->main(); +#ifdef HAVE_LASH + + delete Om::lash_driver; +#endif + + delete Om::om; + return 0; +} + + +pthread_t main_thread; + + +int +jack_initialize(jack_client_t* client, const char* load_init) +{ + if ((Om::om = new Om::OmApp(load_init, new Om::JackAudioDriver(client))) != NULL) { + pthread_create(&main_thread, NULL, run_main, NULL); + return 0; // Success + } else { + return 1; + } +} + + +void +jack_finish(void* arg) +{ + void* ret; + Om::om->quit(); + pthread_join(main_thread, &ret); +} + diff --git a/src/libs/engine/OmObject.h b/src/libs/engine/OmObject.h new file mode 100644 index 00000000..606a3dba --- /dev/null +++ b/src/libs/engine/OmObject.h @@ -0,0 +1,115 @@ +/* This file is part of Om. Copyright (C) 2006 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 OMOBJECT_H +#define OMOBJECT_H + +#include <string> +#include <map> +#include <cstddef> +#include <cassert> +#include "MaidObject.h" +#include "util/Path.h" + +using std::string; using std::map; + +namespace Om { + +class Patch; +class Node; +class Port; + + +/** An object in the "synth space" of Om - Patch, Node, Port, etc. + * + * Each of these is a MaidObject and so can be deleted in a realtime safe + * way from anywhere, and they all have a map of metadata for clients to store + * arbitrary values in (which the engine puts no significance to whatsoever). + * + * \ingroup engine + */ +class OmObject : public MaidObject +{ +public: + OmObject(OmObject* parent, const string& name) + : m_parent(parent), m_name(name) + { + assert(parent == NULL || m_name.length() > 0); + assert(parent == NULL || m_name.find("/") == string::npos); + //assert(((string)path()).find("//") == string::npos); + } + + virtual ~OmObject() {} + + // Ghetto home-brew RTTI + virtual Patch* as_patch() { return NULL; } + virtual Node* as_node() { return NULL; } + virtual Port* as_port() { return NULL; } + + OmObject* parent() const { return m_parent; } + + inline const string& name() const { return m_name; } + + virtual void set_path(const Path& new_path) { + m_name = new_path.name(); + assert(m_name.find("/") == string::npos); + } + + void set_metadata(const string& key, const string& value) { m_metadata[key] = value; } + const string& get_metadata(const string& key) { + static const string empty_string = ""; + map<string, string>::iterator i = m_metadata.find(key); + if (i != m_metadata.end()) + return (*i).second; + else + return empty_string; + } + + const map<string, string>& metadata() const { return m_metadata; } + + inline const Path path() const { + if (m_parent == NULL) + return Path(string("/").append(m_name)); + else if (m_parent->path() == "/") + return Path(string("/").append(m_name)); + else + return Path(m_parent->path() +"/"+ m_name); + } + + /** Patch and Node override this to recursively add their children. */ + virtual void add_to_store() = 0; + + /** Patch and Node override this to recursively remove their children. */ + virtual void remove_from_store() = 0; + +protected: + OmObject() {} + + OmObject* m_parent; + string m_name; + +private: + // Prevent copies (undefined) + OmObject(const OmObject&); + OmObject& operator=(const OmObject& copy); + + map<string, string> m_metadata; +}; + + +} // namespace Om + +#endif // OMOBJECT_H diff --git a/src/libs/engine/OutputPort.cpp b/src/libs/engine/OutputPort.cpp new file mode 100644 index 00000000..bf971919 --- /dev/null +++ b/src/libs/engine/OutputPort.cpp @@ -0,0 +1,51 @@ +/* This file is part of Om. Copyright (C) 2006 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 "OutputPort.h" +#include "InputPort.h" +#include "PortInfo.h" +#include <cassert> + +namespace Om { + +template<typename T> +OutputPort<T>::OutputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size) +: PortBase<T>(node, name, index, poly, port_info, buffer_size) +{ + assert(port_info->is_output() && !port_info->is_input()); +} +template OutputPort<sample>::OutputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); +template OutputPort<MidiMessage>::OutputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + + +template<typename T> +void +OutputPort<T>::set_tied_port(InputPort<T>* port) +{ + assert(!m_is_tied); + assert(m_tied_port == NULL); + assert(static_cast<PortBase<T>*>(port) != static_cast<PortBase<T>*>(this)); + assert(port != NULL); + + m_is_tied = true; + m_tied_port = (PortBase<T>*)port; +} +template void OutputPort<sample>::set_tied_port(InputPort<sample>* port); +template void OutputPort<MidiMessage>::set_tied_port(InputPort<MidiMessage>* port); + + +} // namespace Om + diff --git a/src/libs/engine/OutputPort.h b/src/libs/engine/OutputPort.h new file mode 100644 index 00000000..90488a7f --- /dev/null +++ b/src/libs/engine/OutputPort.h @@ -0,0 +1,64 @@ +/* This file is part of Om. Copyright (C) 2006 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 OUTPUTPORT_H +#define OUTPUTPORT_H + +#include <string> +#include <cstdlib> +#include "PortBase.h" +#include "util/types.h" + +namespace Om { + +template <typename T> class InputPort; + + +/** An output port. + * + * Output ports always have a locally allocated buffer, and buffer() will + * always return that buffer. (This is very different from InputPort) + * + * This class actually adds no functionality to Port whatsoever right now, + * it will in the future when more advanced port types exist, and it makes + * things clearer throughout the engine. + * + * \ingroup engine + */ +template <typename T> +class OutputPort : public PortBase<T> +{ +public: + OutputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + virtual ~OutputPort() {} + + void set_tied_port(InputPort<T>* port); + +private: + // Prevent copies (undefined) + OutputPort(const OutputPort& copy); + OutputPort<T>& operator=(const OutputPort<T>&); + + using PortBase<T>::m_is_tied; + using PortBase<T>::m_tied_port; +}; + + +template class OutputPort<sample>; + +} // namespace Om + +#endif // OUTPUTPORT_H diff --git a/src/libs/engine/Patch.cpp b/src/libs/engine/Patch.cpp new file mode 100644 index 00000000..be938cae --- /dev/null +++ b/src/libs/engine/Patch.cpp @@ -0,0 +1,356 @@ +/* This file is part of Om. Copyright (C) 2006 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 <cmath> +#include <iostream> +#include "Node.h" +#include "Patch.h" +#include "Plugin.h" +#include "Port.h" +#include "PortInfo.h" +#include "ClientBroadcaster.h" +#include "InternalNode.h" +#include "Connection.h" +#include "Om.h" +#include "OmApp.h" +#include "PortBase.h" +#include "ObjectStore.h" +#include "interface/ClientInterface.h" + +using std::cerr; using std::cout; using std::endl; + +namespace Om { + + +Patch::Patch(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size, size_t internal_poly) +: NodeBase(path, poly, parent, srate, buffer_size), + m_internal_poly(internal_poly), + m_process_order(NULL), + m_process(false) +{ + assert(internal_poly >= 1); + + m_plugin.type(Plugin::Patch); + m_plugin.lib_path(""); + m_plugin.plug_label("om_patch"); + m_plugin.name("Om patch"); + + //std::cerr << "Creating patch " << m_name << ", poly = " << poly + // << ", internal poly = " << internal_poly << std::endl; +} + + +Patch::~Patch() +{ + assert(!m_activated); + + for (List<Connection*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { + delete (*i); + delete m_connections.remove(i); + } + + for (List<Node*>::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) { + assert(!(*i)->activated()); + delete (*i); + delete m_nodes.remove(i); + } + + delete m_process_order; +} + + +void +Patch::activate() +{ + NodeBase::activate(); + + for (List<Node*>::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) + (*i)->activate(); + + assert(m_activated); +} + + +void +Patch::deactivate() +{ + if (m_activated) { + + NodeBase::deactivate(); + + for (List<Node*>::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) { + if ((*i)->activated()) + (*i)->deactivate(); + assert(!(*i)->activated()); + } + } + assert(!m_activated); +} + + +void +Patch::process(bool b) +{ + if (!b) { + // Write output buffers to 0 + for (List<InternalNode*>::iterator i = m_bridge_nodes.begin(); i != m_bridge_nodes.end(); ++i) { + assert((*i)->as_port() != NULL); + if ((*i)->as_port()->port_info()->is_output()) + (*i)->as_port()->clear_buffers(); + } + } + m_process = b; +} + + +/** Run the patch for the specified number of frames. + * + * Calls all Nodes in the order m_process_order specifies. + */ +inline void +Patch::run(size_t nframes) +{ + if (m_process_order == NULL || !m_process) + return; + + // Prepare all ports + for (List<InternalNode*>::iterator i = m_bridge_nodes.begin(); i != m_bridge_nodes.end(); ++i) + (*i)->as_port()->prepare_buffers(nframes); + + // Run all nodes + for (size_t i=0; i < m_process_order->size(); ++i) { + // Could be a gap due to a node removal event (see RemoveNodeEvent.cpp) + // If you're thinking this isn't very nice, you're right. + if (m_process_order->at(i) != NULL) + m_process_order->at(i)->run(nframes); + } +} + + +/** Returns the number of ports. + * + * Needs to override the NodeBase implementation since a Patch's ports are really + * just it's input and output nodes' ports. + */ +size_t +Patch::num_ports() const +{ + return m_bridge_nodes.size(); +} + + +#if 0 +void +Patch::send_creation_messages(ClientInterface* client) const +{ + cerr << "FIXME: creation\n"; + + om->client_broadcaster()->send_patch_to(client, this); + + for (List<Node*>::const_iterator j = m_nodes.begin(); j != m_nodes.end(); ++j) { + Node* node = (*j); + Port* port = node->as_port(); // NULL unless a bridge node + node->send_creation_messages(client); + + usleep(100); + + // If this is a bridge (input/output) node, send the patch control value as well + if (port != NULL && port->port_info()->is_control()) + om->client_broadcaster()->send_control_change_to(client, port->path(), + ((PortBase<sample>*)port)->buffer(0)->value_at(0)); + } + + for (List<Connection*>::const_iterator j = m_connections.begin(); j != m_connections.end(); ++j) { + om->client_broadcaster()->send_connection_to(client, *j); + } + + // Send port information + /*for (size_t i=0; i < m_ports.size(); ++i) { + Port* const port = m_ports.at(i); + + // Send metadata + const map<string, string>& data = port->metadata(); + for (map<string, string>::const_iterator i = data.begin(); i != data.end(); ++i) + om->client_broadcaster()->send_metadata_update_to(client, port->path(), (*i).first, (*i).second); + + if (port->port_info()->is_control()) + om->client_broadcaster()->send_control_change_to(client, port->path(), + ((PortBase<sample>*)port)->buffer(0)->value_at(0)); + }*/ +} +#endif + + +void +Patch::add_to_store() +{ + // Add self and ports + NodeBase::add_to_store(); + + // Add nodes + for (List<Node*>::iterator j = m_nodes.begin(); j != m_nodes.end(); ++j) + (*j)->add_to_store(); +} + + +void +Patch::remove_from_store() +{ + // Remove self and ports + NodeBase::remove_from_store(); + + // Remove nodes + for (List<Node*>::iterator j = m_nodes.begin(); j != m_nodes.end(); ++j) { + (*j)->remove_from_store(); + assert(om->object_store()->find((*j)->path()) == NULL); + } +} + + +// Patch specific stuff + + +void +Patch::add_node(ListNode<Node*>* ln) +{ + assert(ln != NULL); + assert(ln->elem() != NULL); + assert(ln->elem()->parent_patch() == this); + assert(ln->elem()->poly() == m_internal_poly || ln->elem()->poly() == 1); + + m_nodes.push_back(ln); +} + + +ListNode<Node*>* +Patch::remove_node(const string& name) +{ + for (List<Node*>::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) + if ((*i)->name() == name) + return m_nodes.remove(i); + + return NULL; +} + + +/** Remove a connection. Realtime safe. + */ +ListNode<Connection*>* +Patch::remove_connection(const Port* src_port, const Port* dst_port) +{ + bool found = false; + ListNode<Connection*>* connection = NULL; + for (List<Connection*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i) { + if ((*i)->src_port() == src_port && (*i)->dst_port() == dst_port) { + connection = m_connections.remove(i); + found = true; + } + } + + if ( ! found) + cerr << "WARNING: [Patch::remove_connection] Connection not found !" << endl; + + return connection; +} + + +/** Remove a bridge_node. Realtime safe. + */ +ListNode<InternalNode*>* +Patch::remove_bridge_node(const InternalNode* node) +{ + bool found = false; + ListNode<InternalNode*>* bridge_node = NULL; + for (List<InternalNode*>::iterator i = m_bridge_nodes.begin(); i != m_bridge_nodes.end(); ++i) { + if ((*i) == node) { + bridge_node = m_bridge_nodes.remove(i); + found = true; + } + } + + if ( ! found) + cerr << "WARNING: [Patch::remove_bridge_node] InternalNode not found !" << endl; + + return bridge_node; +} + + +/** Find the process order for this Patch. + * + * The process order is a flat list that the patch will execute in order + * when it's run() method is called. Return value is a newly allocated list + * which the caller is reponsible to delete. Note that this function does + * NOT actually set the process order, it is returned so it can be inserted + * at the beginning of an audio cycle (by various Events). + * + * This function is not realtime safe, due to the use of the List<Node*> iterator + */ +Array<Node*>* +Patch::build_process_order() const +{ + Node* node = NULL; + Array<Node*>* const process_order = new Array<Node*>(m_nodes.size()); + + for (List<Node*>::const_iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) + (*i)->traversed(false); + + // Traverse backwards starting at outputs + for (List<InternalNode*>::const_iterator i = m_bridge_nodes.begin(); i != m_bridge_nodes.end(); ++i) { + node = (*i); + assert(node->as_port() != NULL); + + // If the output node has been disconnected and has no connections left, don't traverse + // into it so it's not in the process order (and can be removed w/o flaming segfault death) + if (node->as_port()->port_info()->is_output() && node->providers()->size() > 0) + build_process_order_recursive(node, process_order); + } + + // Add any nodes that weren't hit by the traversal (disjoint nodes) + for (List<Node*>::const_iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) { + node = (*i); + if ( ! node->traversed()) { + process_order->push_back(*i); + node->traversed(true); + } + } + + assert(process_order->size() == m_nodes.size()); + + return process_order; +} + + +/** Rename this Patch. + * + * This is responsible for updating the ObjectStore so the Patch can be + * found at it's new path, as well as all it's children. + */ +void +Patch::set_path(const Path& new_path) +{ + const Path old_path = path(); + + // Update nodes + for (List<Node*>::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i) + (*i)->set_path(new_path.base_path() + (*i)->name()); + + // Update self + NodeBase::set_path(new_path); +} + + +} // namespace Om diff --git a/src/libs/engine/Patch.h b/src/libs/engine/Patch.h new file mode 100644 index 00000000..e2a1ed48 --- /dev/null +++ b/src/libs/engine/Patch.h @@ -0,0 +1,136 @@ +/* This file is part of Om. Copyright (C) 2006 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 PATCH_H +#define PATCH_H + +#include <cstdlib> +#include <string> +#include "NodeBase.h" +#include "Plugin.h" +#include "List.h" + +using std::string; + +template <typename T> class Array; + +namespace Om { + +class Connection; +class InternalNode; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A group of nodes in a graph, possibly polyphonic. + * + * Note that this is also a Node, just one which contains Nodes. + * Therefore infinite subpatching is possible, of polyphonic + * patches of polyphonic nodes etc. etc. + * + * \ingroup engine + */ +class Patch : public NodeBase +{ +public: + Patch(const string& name, size_t poly, Patch* parent, samplerate srate, size_t buffer_size, size_t local_poly); + virtual ~Patch(); + + Patch* as_patch() { return static_cast<Patch*>(this); } + + void activate(); + void deactivate(); + + void run(size_t nframes); + + size_t num_ports() const; + + //void send_creation_messages(ClientInterface* client) const; + + void add_to_store(); + void remove_from_store(); + + void set_path(const Path& new_path); + + // Patch specific stuff not inherited from Node + + void add_node(ListNode<Node*>* tn); + ListNode<Node*>* remove_node(const string& name); + + List<Node*>& nodes() { return m_nodes; } + List<Connection*>& connections() { return m_connections; } + + const List<Node*>& nodes() const { return m_nodes; } + const List<Connection*>& connections() const { return m_connections; } + + void add_bridge_node(ListNode<InternalNode*>* n) { m_bridge_nodes.push_back(n); } + ListNode<InternalNode*>* remove_bridge_node(const InternalNode* n); + + void add_connection(ListNode<Connection*>* c) { m_connections.push_back(c); } + ListNode<Connection*>* remove_connection(const Port* src_port, const Port* dst_port); + + Array<Node*>* process_order() { return m_process_order; } + void process_order(Array<Node*>* po) { m_process_order = po; } + + Array<Node*>* build_process_order() const; + inline void build_process_order_recursive(Node* n, Array<Node*>* order) const; + + /** Whether to run this patch's DSP in the audio thread */ + bool process() const { return m_process; } + void process(bool b); + + size_t internal_poly() const { return m_internal_poly; } + + const Plugin* plugin() const { return &m_plugin; } + void plugin(const Plugin* const) { exit(EXIT_FAILURE); } + +private: + // Prevent copies (undefined) + Patch(const Patch&); + Patch& operator=(const Patch&); + + size_t m_internal_poly; + Array<Node*>* m_process_order; + List<Connection*> m_connections; + List<InternalNode*> m_bridge_nodes; ///< Inputs and outputs + List<Node*> m_nodes; + bool m_process; + + Plugin m_plugin; +}; + + + +/** Private helper for build_process_order */ +inline void +Patch::build_process_order_recursive(Node* n, Array<Node*>* order) const +{ + if (n == NULL || n->traversed()) return; + n->traversed(true); + assert(order != NULL); + + for (List<Node*>::iterator i = n->providers()->begin(); i != n->providers()->end(); ++i) + if ( ! (*i)->traversed() ) + build_process_order_recursive((*i), order); + + order->push_back(n); +} + + +} // namespace Om + +#endif // PATCH_H diff --git a/src/libs/engine/Plugin.h b/src/libs/engine/Plugin.h new file mode 100644 index 00000000..d350b1e8 --- /dev/null +++ b/src/libs/engine/Plugin.h @@ -0,0 +1,149 @@ +/* This file is part of Om. Copyright (C) 2006 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 PLUGIN_H +#define PLUGIN_H + +#include "config.h" + +#include <cstdlib> +#include <dlfcn.h> +#include <string> +#include <iostream> +#ifdef HAVE_SLV2 +#include <slv2/slv2.h> +#endif +using std::string; +using std::cerr; using std::endl; + + +namespace Om { + +class PluginLibrary; + + +/** Representation of a plugin (of various types). + * + * A Node is an instance of this, conceptually. + * FIXME: This whole thing is a filthy mess and needs a rewrite. Probably + * with derived classes for each plugin type. + */ +class Plugin +{ +public: + enum Type { LV2, LADSPA, DSSI, Internal, Patch }; + + Plugin() : m_type(Internal), m_lib_path("/Om"), + m_id(0), m_library(NULL) + { +#ifdef HAVE_SLV2 + m_slv2_plugin = NULL; +#endif + } + + Plugin(const Plugin* const copy) { + // Copying only allowed for Internal plugins. Bit of a hack, but + // allows the PluginInfo to be defined in the Node class which keeps + // things localized and convenient (FIXME?) + if (copy->m_type != Internal) + exit(EXIT_FAILURE); + m_type = copy->m_type; + m_lib_path = copy->m_lib_path; + m_plug_label = copy->m_plug_label; + m_name = copy->m_name; + m_library = copy->m_library; + } + + Type type() const { return m_type; } + void type(Type t) { m_type = t; } + const string& lib_path() const { return m_lib_path; } + void lib_path(const string& s) { m_lib_path = s; } + string lib_name() const { return m_lib_path.substr(m_lib_path.find_last_of("/")); } + const string& plug_label() const { return m_plug_label; } + void plug_label(const string& s) { m_plug_label = s; } + const string& name() const { return m_name; } + void name(const string& s) { m_name = s; } + unsigned long id() const { return m_id; } + void id(unsigned long i) { m_id = i; } + void uri(const string& s) { m_uri = s; } + const string uri() const + { + char id_str[11]; + snprintf(id_str, 11, "%lu", m_id); + + if (m_uri.length() > 0) { + return m_uri; + } else if (m_type == Internal) { + return string("om:") + m_plug_label; + } else if (m_type == LADSPA) { + return string("ladspa:").append(id_str); + } else if (m_type == DSSI) { + return string("dssi:") + lib_name() +":"+ m_plug_label; + } else { + return ""; + } + } + + PluginLibrary* library() const { return m_library; } + void library(PluginLibrary* const library) { m_library = library; } + + const char* type_string() const { + if (m_type == LADSPA) return "LADSPA"; + else if (m_type == LV2) return "LV2"; + else if (m_type == DSSI) return "DSSI"; + else if (m_type == Internal) return "Internal"; + else if (m_type == Patch) return "Patch"; + else return ""; + } + + void set_type(const string& type_string) { + if (type_string == "LADSPA") m_type = LADSPA; + else if (type_string == "LV2") m_type = LV2; + else if (type_string == "DSSI") m_type = DSSI; + else if (type_string == "Internal") m_type = Internal; + else if (type_string == "Patch") m_type = Patch; + } + + // FIXME: ew +#ifdef HAVE_SLV2 + SLV2Plugin* slv2_plugin() { return m_slv2_plugin; } + void slv2_plugin(const SLV2Plugin* p) { m_slv2_plugin = p; } + +#endif +private: + // Disallow copies (undefined) + Plugin(const Plugin&); + Plugin& operator=(const Plugin&); + + Type m_type; + string m_uri; ///< LV2 only + string m_lib_path; ///< LADSPA/DSSI only + string m_plug_label; ///< LADSPA/DSSI only + string m_name; ///< LADSPA/DSSI only + unsigned long m_id; ///< LADSPA/DSSI only + + PluginLibrary* m_library; + +#ifdef HAVE_SLV2 + SLV2Plugin* m_slv2_plugin; +#endif +}; + + +} // namespace Om + +#endif // PLUGIN_H + diff --git a/src/libs/engine/PluginLibrary.h b/src/libs/engine/PluginLibrary.h new file mode 100644 index 00000000..53279d8a --- /dev/null +++ b/src/libs/engine/PluginLibrary.h @@ -0,0 +1,100 @@ +/* This file is part of Om. Copyright (C) 2006 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 PLUGINLIBRARY_H +#define PLUGINLIBRARY_H + +#include <dlfcn.h> +#include <string> +#include <iostream> +using std::string; +using std::cerr; using std::endl; + + +namespace Om { + + +/** Representation of a shared library containing at least one Plugin. + * + * In the NodeFactory, this represents one loaded shared library instance, + * which is what handle() returns. + */ +class PluginLibrary +{ +public: + /** Construct a new PluginLibrary. + * + * Path is assumed to be the path of a valid shared library that can be + * successfully dlopen'ed. + */ + PluginLibrary(const string& path) + : m_path(path), m_handle(NULL) + {} + + ~PluginLibrary() + { + close(); + } + + /** Load and resolve all symbols in this shared library + * (dlopen with RTLD_NOW). + * + * It is okay to call this many times, the library will only be opened + * once. + */ + void open() + { + if (m_handle == NULL) { + dlerror(); + m_handle = dlopen(m_path.c_str(), RTLD_NOW); + if (m_handle == NULL) + cerr << "[PluginLibrary] Warning: Error opening shared library " + << m_path << "(" << dlerror() << ")" << endl; + } + } + + /** Close the dynamic library. + * + * This can be called on an already closed PluginLibrary without problems. + */ + void close() + { + if (m_handle != NULL) { + dlerror(); + if (dlclose(m_handle)) + cerr << "[PluginLibrary] Error closing shared library " << m_path + << "(" << dlerror() << ")" << endl; + } + m_handle = NULL; + } + + void* handle() const { return m_handle; } + +private: + // Disallow copies (undefined) + PluginLibrary(const PluginLibrary&); + PluginLibrary& operator=(const PluginLibrary&); + + string m_path; + void* m_handle; +}; + + +} // namespace Om + +#endif // PLUGINLIBRARY_H + diff --git a/src/libs/engine/Port.cpp b/src/libs/engine/Port.cpp new file mode 100644 index 00000000..987d232d --- /dev/null +++ b/src/libs/engine/Port.cpp @@ -0,0 +1,56 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Port.h" +#include "Node.h" +#include "PortInfo.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" + +namespace Om { + +Port::Port(Node* const node, const string& name, size_t index, size_t poly, PortInfo* port_info) +: OmObject(node, name), + m_index(index), + m_poly(poly), + m_port_info(port_info) +{ + assert(node != NULL); + assert(port_info != NULL); + assert(m_poly > 0); +} + + +void +Port::add_to_store() +{ + om->object_store()->add(this); +} + + +void +Port::remove_from_store() +{ + // Remove self + TreeNode<OmObject*>* node = om->object_store()->remove(path()); + assert(node != NULL); + assert(om->object_store()->find(path()) == NULL); + delete node; +} + + +} // namespace Om diff --git a/src/libs/engine/Port.h b/src/libs/engine/Port.h new file mode 100644 index 00000000..f51d7382 --- /dev/null +++ b/src/libs/engine/Port.h @@ -0,0 +1,80 @@ +/* This file is part of Om. Copyright (C) 2006 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 PORT_H +#define PORT_H + +#include <cstdlib> +#include <string> +#include "util/types.h" +#include "OmObject.h" + +using std::string; + +namespace Om { + +class Node; +class PortInfo; + + +/** A port on a Node. + * + * This is a non-template abstract base class, which basically exists so + * things can pass around Port pointers and not have to worry about type, + * templates, etc. + * + * \ingroup engine + */ +class Port : public OmObject +{ +public: + virtual ~Port() {} + + Port* as_port() { return this; } + + PortInfo* port_info() const { return m_port_info; } + void port_info(PortInfo* pi) { m_port_info = pi; } + + void add_to_store(); + void remove_from_store(); + + /** Called once per process cycle */ + virtual void prepare_buffers(size_t nframes) = 0; + + /** Empty buffer contents completely (ie silence) */ + virtual void clear_buffers() = 0; + + Node* parent_node() const { return m_parent->as_node(); } + bool is_sample() const { return false; } + size_t num() const { return m_index; } + size_t poly() const { return m_poly; } + +protected: + Port(Node* const node, const string& name, size_t index, size_t poly, PortInfo* port_info); + + // Prevent copies (undefined) + Port(const Port&); + Port& operator=(const Port&); + + size_t m_index; + size_t m_poly; + PortInfo* m_port_info; +}; + + +} // namespace Om + +#endif // PORT_H diff --git a/src/libs/engine/PortBase.cpp b/src/libs/engine/PortBase.cpp new file mode 100644 index 00000000..3d8dbc67 --- /dev/null +++ b/src/libs/engine/PortBase.cpp @@ -0,0 +1,133 @@ +/* This file is part of Om. Copyright (C) 2006 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 "PortBase.h" +#include <cmath> +#include <cstdlib> +#include <iostream> +#include <cassert> +#include <sys/mman.h> +#include "util.h" +#include "Node.h" +#include "PortInfo.h" +#include "MidiMessage.h" + +namespace Om { + + +/** Constructor for a Port. + */ +template <typename T> +PortBase<T>::PortBase(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size) +: Port(node, name, index, poly, port_info), + m_buffer_size(buffer_size), + m_fixed_buffers(false), + m_is_tied(false), + m_tied_port(NULL) +{ + allocate_buffers(); + clear_buffers(); + + assert(m_buffers.size() > 0); +} +template +PortBase<sample>::PortBase(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); +template +PortBase<MidiMessage>::PortBase(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + + +template <typename T> +PortBase<T>::~PortBase() +{ + for (size_t i=0; i < m_poly; ++i) + delete m_buffers.at(i); + + delete m_port_info; +} +template PortBase<sample>::~PortBase(); +template PortBase<MidiMessage>::~PortBase(); + + +/** Set the port's value for all voices. + */ +template<> +void +PortBase<sample>::set_value(sample val, size_t offset) +{ + if (m_port_info->is_control()) + offset = 0; + assert(offset < m_buffer_size); + + for (size_t v=0; v < m_poly; ++v) + m_buffers.at(v)->set(val, offset); +} + +/** Set the port's value for a specific voice. + */ +template<> +void +PortBase<sample>::set_value(size_t voice, sample val, size_t offset) +{ + if (m_port_info->is_control()) + offset = 0; + assert(offset < m_buffer_size); + + m_buffers.at(voice)->set(val, offset); +} + + +template <typename T> +void +PortBase<T>::allocate_buffers() +{ + m_buffers.alloc(m_poly); + + for (size_t i=0; i < m_poly; ++i) + m_buffers.at(i) = new Buffer<T>(m_buffer_size); +} +template void PortBase<sample>::allocate_buffers(); +template void PortBase<MidiMessage>::allocate_buffers(); + + +template<> +void +PortBase<sample>::prepare_buffers(size_t nframes) +{ + for (size_t i=0; i < m_poly; ++i) + m_buffers.at(i)->prepare(nframes); +} + + +template<> +void +PortBase<MidiMessage>::prepare_buffers(size_t nframes) +{ +} + + +template<typename T> +void +PortBase<T>::clear_buffers() +{ + for (size_t i=0; i < m_poly; ++i) + m_buffers.at(i)->clear(); +} +template void PortBase<sample>::clear_buffers(); +template void PortBase<MidiMessage>::clear_buffers(); + + +} // namespace Om + diff --git a/src/libs/engine/PortBase.h b/src/libs/engine/PortBase.h new file mode 100644 index 00000000..9962538e --- /dev/null +++ b/src/libs/engine/PortBase.h @@ -0,0 +1,87 @@ +/* This file is part of Om. Copyright (C) 2006 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 PORTBASE_H +#define PORTBASE_H + +#include <string> +#include "util/types.h" +#include "Array.h" +#include "Port.h" +#include "Buffer.h" + +using std::string; + +namespace Om { + +class MidiMessage; +class Node; + + +/** A port (with a type). + * + * This is basically just a buffer and a bunch of flags and helper methods. + * All the interesting functionality of ports is in InputPort. + * + * \ingroup engine + */ +template <typename T> +class PortBase : public Port +{ +public: + virtual ~PortBase(); + + void set_value(size_t voice, T val, size_t offset); + void set_value(T val, size_t offset); + + Buffer<T>* buffer(size_t voice) const { return m_buffers.at(voice); } + + virtual void prepare_buffers(size_t nframes); + virtual void clear_buffers(); + + PortBase* tied_port() const { return m_tied_port; } + void untie() { m_is_tied = false; m_tied_port = NULL; } + + size_t buffer_size() const { return m_buffer_size; } + + /** Used by drivers to prevent port from changing buffers */ + void fixed_buffers(bool b) { m_fixed_buffers = b; } + bool fixed_buffers() { return m_fixed_buffers; } + +protected: + PortBase(Node* const node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + + // Prevent copies (undefined) + PortBase(const PortBase<T>& copy); + PortBase& operator=(const Port&); + + void allocate_buffers(); + + size_t m_buffer_size; + bool m_fixed_buffers; + bool m_is_tied; + PortBase* m_tied_port; + + Array<Buffer<T>*> m_buffers; +}; + + +template class PortBase<sample>; +template class PortBase<MidiMessage>; + +} // namespace Om + +#endif // PORTBASE_H diff --git a/src/libs/engine/PortInfo.h b/src/libs/engine/PortInfo.h new file mode 100644 index 00000000..3fa4215a --- /dev/null +++ b/src/libs/engine/PortInfo.h @@ -0,0 +1,153 @@ +/* This file is part of Om. Copyright (C) 2006 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 PORTINFO_H +#define PORTINFO_H + +#include <cstdlib> +#include <string> +#include <ladspa.h> + +using std::string; + +namespace Om { + + +enum PortType { CONTROL, AUDIO, MIDI }; +enum PortDirection { INPUT = 0, OUTPUT = 1 }; // FIXME: dupe of ClientInterface::PortDirection +enum PortHint { NONE, INTEGER, TOGGLE, LOGARITHMIC }; + + +/** Information about a Port. + * + * I'm not sure this actually has a reason for existing anymore. This is the + * model for a Port, but no other OmObjects need a model, soo.... + * + * \ingroup engine + */ +class PortInfo +{ +public: + PortInfo(const string& port_name, PortType type, PortDirection dir, PortHint hint, float default_val, float min, float max) + : m_name(port_name), + m_type(type), + m_direction(dir), + m_hint(hint), + m_default_val(default_val), + m_min_val(min), + m_max_val(max) + {} + + PortInfo(const string& port_name, PortType type, PortDirection dir, float default_val, float min, float max) + : m_name(port_name), + m_type(type), + m_direction(dir), + m_hint(NONE), + m_default_val(default_val), + m_min_val(min), + m_max_val(max) + {} + + PortInfo(const string& port_name, PortType type, PortDirection dir, PortHint hint = NONE) + : m_name(port_name), + m_type(type), + m_direction(dir), + m_hint(hint), + m_default_val(0.0f), + m_min_val(0.0f), + m_max_val(1.0f) + {} + + PortInfo(const string& port_name, LADSPA_PortDescriptor d, LADSPA_PortRangeHintDescriptor hint) + : m_name(port_name), + m_default_val(1.0f), + m_min_val(0.0f), + m_max_val(1.0f) + { + if (LADSPA_IS_PORT_AUDIO(d)) m_type = AUDIO; + else if (LADSPA_IS_PORT_CONTROL(d)) m_type = CONTROL; + else exit(EXIT_FAILURE); + + if (LADSPA_IS_PORT_INPUT(d)) m_direction = INPUT; + else if (LADSPA_IS_PORT_OUTPUT(d)) m_direction = OUTPUT; + else exit(EXIT_FAILURE); + + if (LADSPA_IS_HINT_TOGGLED(hint)) { + m_hint = TOGGLE; m_min_val = 0; m_max_val = 1; m_default_val = 0; + } else if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + m_hint = LOGARITHMIC; + else if (LADSPA_IS_HINT_INTEGER(hint)) + m_hint = INTEGER; + else + m_hint = NONE; + } + + PortType type() const { return m_type; } + void type(PortType t) { m_type = t; } + float min_val() const { return m_min_val; } + void min_val(float f) { m_min_val = f; } + float default_val() const { return m_default_val; } + void default_val(float f) { m_default_val = f; } + float max_val() const { return m_max_val; } + void max_val(float f) { m_max_val = f; } + PortDirection direction() const { return m_direction; } + + string type_string() { + switch (m_type) { + case CONTROL: return "CONTROL"; break; + case AUDIO: return "AUDIO"; break; + case MIDI: return "MIDI"; break; + default: return "UNKNOWN"; + } + } + string direction_string() { if (m_direction == INPUT) return "INPUT"; else return "OUTPUT"; } + string hint_string() { if (m_hint == INTEGER) return "INTEGER"; + else if (m_hint == LOGARITHMIC) return "LOGARITHMIC"; + else if (m_hint == TOGGLE) return "TOGGLE"; + else return "NONE"; } + + bool is_control() const { return (m_type == CONTROL); } + bool is_audio() const { return (m_type == AUDIO); } + bool is_midi() const { return (m_type == MIDI); } + bool is_input() const { return (m_direction == INPUT); } + bool is_output() const { return (m_direction == OUTPUT); } + + bool is_logarithmic() const { return (m_hint == LOGARITHMIC); } + bool is_integer() const { return (m_hint == INTEGER); } + bool is_toggle() const { return (m_hint == TOGGLE); } + + const string& name() const { return m_name; } + void name(const string& n) { m_name = n; } + +private: + // Prevent copies (undefined) + PortInfo(const PortInfo&); + PortInfo& operator=(const PortInfo&); + + string m_name; + PortType m_type; + PortDirection m_direction; + PortHint m_hint; + float m_default_val; + float m_min_val; + float m_max_val; +}; + + +} // namespace Om + +#endif // PORTINFO_H diff --git a/src/libs/engine/PostProcessor.cpp b/src/libs/engine/PostProcessor.cpp new file mode 100644 index 00000000..65b92015 --- /dev/null +++ b/src/libs/engine/PostProcessor.cpp @@ -0,0 +1,122 @@ +/* This file is part of Om. Copyright (C) 2006 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 "PostProcessor.h" +#include <cassert> +#include <iostream> +#include <pthread.h> +#include "Om.h" +#include "OmApp.h" +#include "Event.h" +#include "util/Queue.h" +#include "Maid.h" + + +using std::cerr; using std::cout; using std::endl; + +namespace Om { + +bool PostProcessor::m_process_thread_exit_flag = false; + + +PostProcessor::PostProcessor(size_t queue_size) +: m_events(queue_size), + m_thread_exists(false), + m_semaphore(0) +{ +} + + +PostProcessor::~PostProcessor() +{ + stop(); +} + + +/** Start the process thread. + */ +void +PostProcessor::start() +{ + cout << "[PostProcessor] Starting." << endl; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1500000); + + pthread_create(&m_process_thread, &attr, process_events, this); + m_thread_exists = true; +} + + +/** Stop the process thread. + */ +void +PostProcessor::stop() +{ + if (m_thread_exists) { + m_process_thread_exit_flag = true; + pthread_cancel(m_process_thread); + pthread_join(m_process_thread, NULL); + m_thread_exists = false; + } +} + + +/** Signal the PostProcessor to process all pending events. + */ +void +PostProcessor::signal() +{ + m_semaphore.post(); +} + + +void* +PostProcessor::process_events(void* osc_processer) +{ + PostProcessor* me = (PostProcessor*)osc_processer; + return me->m_process_events(); +} + + +/** OSC message processing thread. + */ +void* +PostProcessor::m_process_events() +{ + Event* ev = NULL; + + while (true) { + m_semaphore.wait(); + + if (m_process_thread_exit_flag) + break; + + while (!m_events.is_empty()) { + ev = m_events.pop(); + assert(ev != NULL); + ev->post_process(); + om->maid()->push(ev); + } + } + + cout << "[PostProcessor] Exiting post processor thread." << endl; + + return NULL; +} + +} // namespace Om diff --git a/src/libs/engine/PostProcessor.h b/src/libs/engine/PostProcessor.h new file mode 100644 index 00000000..843569d8 --- /dev/null +++ b/src/libs/engine/PostProcessor.h @@ -0,0 +1,78 @@ +/* This file is part of Om. Copyright (C) 2006 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 POSTPROCESSOR_H +#define POSTPROCESSOR_H + +#include <pthread.h> +#include "util/types.h" +#include "util/Queue.h" +#include "util/Semaphore.h" + +namespace Om { + +class Event; + + +/** Processor for Events after leaving the audio thread. + * + * The audio thread pushes events to this when it is done with them (which + * is realtime-safe), which signals the processing thread through a semaphore + * to handle the event and pass it on to the Maid. + * + * \ingroup engine + */ +class PostProcessor +{ +public: + PostProcessor(size_t queue_size); + ~PostProcessor(); + + void start(); + void stop(); + + inline void push(Event* const ev); + void signal(); + +private: + // Prevent copies + PostProcessor(const PostProcessor&); + PostProcessor& operator=(const PostProcessor&); + + Queue<Event*> m_events; + + static void* process_events(void* me); + void* m_process_events(); + + pthread_t m_process_thread; + bool m_thread_exists; + static bool m_process_thread_exit_flag; + Semaphore m_semaphore; +}; + + +/** Push an event on to the process queue, realtime-safe, not thread-safe. + */ +inline void +PostProcessor::push(Event* const ev) +{ + m_events.push(ev); +} + + +} // namespace Om + +#endif // POSTPROCESSOR_H diff --git a/src/libs/engine/QueuedEngineInterface.cpp b/src/libs/engine/QueuedEngineInterface.cpp new file mode 100644 index 00000000..0734eb7a --- /dev/null +++ b/src/libs/engine/QueuedEngineInterface.cpp @@ -0,0 +1,299 @@ +/* This file is part of Om. Copyright (C) 2006 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 "QueuedEngineInterface.h" +#include "QueuedEventSource.h" +#include "events.h" +#include "Om.h" +#include "util/Queue.h" +#include "OmApp.h" + +namespace Om { + +QueuedEngineInterface::QueuedEngineInterface(size_t queue_size) +: QueuedEventSource(queue_size) +, _responder(CountedPtr<Responder>(new Responder())) // NULL responder +{ +} + + +/** Set the Responder to send responses to commands with, once the commands + * are preprocessed and ready to be executed (or not). + * + * Ownership of @a responder is taken. + */ +void +QueuedEngineInterface::set_responder(CountedPtr<Responder> responder) +{ + //cerr << "SET\n"; + _responder = responder; +} + + +void +QueuedEngineInterface::disable_responses() +{ + static CountedPtr<Responder> null_responder(new Responder()); + //cerr << "DISABLE\n"; + set_responder(null_responder); +} + + +/* *** EngineInterface implementation below here *** */ + + +void +QueuedEngineInterface::register_client(ClientKey key, CountedPtr<ClientInterface> client) +{ + RegisterClientEvent* ev = new RegisterClientEvent(_responder, key, client); + push(ev); +} + + +void +QueuedEngineInterface::unregister_client(ClientKey key) +{ + UnregisterClientEvent* ev = new UnregisterClientEvent(_responder, key); + push(ev); +} + + + +// Engine commands +void +QueuedEngineInterface::load_plugins() +{ + LoadPluginsEvent* ev = new LoadPluginsEvent(_responder); + push(ev); + +} + + +void +QueuedEngineInterface::activate() +{ + ActivateEvent* ev = new ActivateEvent(_responder); + push(ev); +} + + +void +QueuedEngineInterface::deactivate() +{ + DeactivateEvent* ev = new DeactivateEvent(_responder); + push(ev); +} + + +void +QueuedEngineInterface::quit() +{ + _responder->respond_ok(); + om->quit(); +} + + + +// Object commands + +void +QueuedEngineInterface::create_patch(const string& path, + uint32_t poly) +{ + CreatePatchEvent* ev = new CreatePatchEvent(_responder, path, poly); + push(ev); + +} + + +void +QueuedEngineInterface::create_node(const string& path, + const string& plugin_type, + const string& plugin_uri, + bool polyphonic) +{ + // FIXME: ew + + Plugin* plugin = new Plugin(); + plugin->set_type(plugin_type); + plugin->uri(plugin_uri); + + AddNodeEvent* ev = new AddNodeEvent(_responder, + path, plugin, polyphonic); + push(ev); +} + + +void +QueuedEngineInterface::rename(const string& old_path, + const string& new_name) +{ + RenameEvent* ev = new RenameEvent(_responder, old_path, new_name); + push(ev); +} + + +void +QueuedEngineInterface::destroy(const string& path) +{ + DestroyEvent* ev = new DestroyEvent(_responder, path); + push(ev); +} + + +void +QueuedEngineInterface::clear_patch(const string& patch_path) +{ +} + + +void +QueuedEngineInterface::enable_patch(const string& patch_path) +{ + EnablePatchEvent* ev = new EnablePatchEvent(_responder, patch_path); + push(ev); +} + + +void +QueuedEngineInterface::disable_patch(const string& patch_path) +{ + DisablePatchEvent* ev = new DisablePatchEvent(_responder, patch_path); + push(ev); +} + + +void +QueuedEngineInterface::connect(const string& src_port_path, + const string& dst_port_path) +{ + ConnectionEvent* ev = new ConnectionEvent(_responder, src_port_path, dst_port_path); + push(ev); + +} + + +void +QueuedEngineInterface::disconnect(const string& src_port_path, + const string& dst_port_path) +{ + DisconnectionEvent* ev = new DisconnectionEvent(_responder, src_port_path, dst_port_path); + push(ev); +} + + +void +QueuedEngineInterface::disconnect_all(const string& node_path) +{ + DisconnectNodeEvent* ev = new DisconnectNodeEvent(_responder, node_path); + push(ev); +} + + +void +QueuedEngineInterface::set_port_value(const string& port_path, + float value) +{ + SetPortValueEvent* ev = new SetPortValueEvent(_responder, port_path, value); + om->event_queue()->push(ev); +} + + +void +QueuedEngineInterface::set_port_value(const string& port_path, + uint32_t voice, + float value) +{ + SetPortValueEvent* ev = new SetPortValueEvent(_responder, voice, port_path, value); + om->event_queue()->push(ev); +} + + +void +QueuedEngineInterface::set_port_value_queued(const string& port_path, + float value) +{ + SetPortValueQueuedEvent* ev = new SetPortValueQueuedEvent(_responder, port_path, value); + push(ev); +} + + +void +QueuedEngineInterface::set_program(const string& node_path, + uint32_t bank, + uint32_t program) +{ + push(new DSSIProgramEvent(_responder, node_path, bank, program)); +} + + +void +QueuedEngineInterface::midi_learn(const string& node_path) +{ + MidiLearnEvent* ev = new MidiLearnEvent(_responder, node_path); + push(ev); +} + + +void +QueuedEngineInterface::set_metadata(const string& path, + const string& predicate, + const string& value) +{ + SetMetadataEvent* ev = new SetMetadataEvent(_responder, + path, predicate, value); + + push(ev); +} + + +// Requests // + +void +QueuedEngineInterface::ping() +{ + PingQueuedEvent* ev = new PingQueuedEvent(_responder); + push(ev); +} + + +void +QueuedEngineInterface::request_port_value(const string& port_path) +{ + RequestPortValueEvent* ev = new RequestPortValueEvent(_responder, port_path); + push(ev); +} + + +void +QueuedEngineInterface::request_plugins() +{ + RequestPluginsEvent* ev = new RequestPluginsEvent(_responder); + push(ev); +} + + +void +QueuedEngineInterface::request_all_objects() +{ + RequestAllObjectsEvent* ev = new RequestAllObjectsEvent(_responder); + push(ev); +} + + +} // namespace Om + + diff --git a/src/libs/engine/QueuedEngineInterface.h b/src/libs/engine/QueuedEngineInterface.h new file mode 100644 index 00000000..cc9c575f --- /dev/null +++ b/src/libs/engine/QueuedEngineInterface.h @@ -0,0 +1,145 @@ +/* This file is part of Om. Copyright (C) 2006 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 QUEUEDENGINEINTERFACE_H +#define QUEUEDENGINEINTERFACE_H + +#include <inttypes.h> +#include <string> +#include <memory> +#include "util/CountedPtr.h" +#include "interface/ClientInterface.h" +#include "interface/ClientKey.h" +#include "QueuedEventSource.h" +#include "Responder.h" +using std::string; + +namespace Om { + +using Shared::ClientKey; +using Shared::ClientInterface; + + +/** A queued (preprocessed) event source / interface. + * + * This is the bridge between the EngineInterface presented to the client, and + * the EventSource that needs to be presented to the AudioDriver. + * + * This is sort of a state machine, \ref set_responder sets the Responder that + * will be used to send the response from all future function calls. Stateless + * protocols like UDP/OSC can use this to set a different response address for + * each event (eg incoming UDP port), but engine/client interfaces that don't + * need to change an 'address' constantly can just set it once on initialisation. + * Blocking control interfaces can be made by setting a Responder which signals + * the caller when the 'response' is 'sent'. + * + * If you do not register a responder, you have no way of knowing if your calls + * are successful. + * + * FIXME: this isn't really "queued" entirely, since some events aren't queued + * events and get pushed directly into the realtime event queue. Should that + * be separated into a different interface/client? + */ +class QueuedEngineInterface : public Om::QueuedEventSource +{ +public: + QueuedEngineInterface(size_t queue_size); + virtual ~QueuedEngineInterface() {} + + virtual void set_responder(CountedPtr<Responder> responder); + virtual void disable_responses(); + + // Client registration + virtual void register_client(ClientKey key, CountedPtr<ClientInterface> client); + virtual void unregister_client(ClientKey key); + + + // Engine commands + virtual void load_plugins(); + virtual void activate(); + virtual void deactivate(); + virtual void quit(); + + // Object commands + + virtual void create_patch(const string& path, + uint32_t poly); + + virtual void create_node(const string& path, + const string& plugin_type, + const string& plugin_uri, + bool polyphonic); + + virtual void rename(const string& old_path, + const string& new_name); + + virtual void destroy(const string& path); + + virtual void clear_patch(const string& patch_path); + + virtual void enable_patch(const string& patch_path); + + virtual void disable_patch(const string& patch_path); + + virtual void connect(const string& src_port_path, + const string& dst_port_path); + + virtual void disconnect(const string& src_port_path, + const string& dst_port_path); + + virtual void disconnect_all(const string& node_path); + + virtual void set_port_value(const string& port_path, + float val); + + virtual void set_port_value(const string& port_path, + uint32_t voice, + float val); + + virtual void set_port_value_queued(const string& port_path, + float val); + + virtual void set_program(const string& node_path, + uint32_t bank, + uint32_t program); + + virtual void midi_learn(const string& node_path); + + virtual void set_metadata(const string& path, + const string& predicate, + const string& value); + + // Requests // + + virtual void ping(); + + virtual void request_port_value(const string& port_path); + + virtual void request_plugins(); + + virtual void request_all_objects(); + +protected: + + /** Where responses to current messages will go. */ + CountedPtr<Responder> _responder; +}; + + +} // namespace Om + +#endif // QUEUEDENGINEINTERFACE_H + diff --git a/src/libs/engine/QueuedEvent.h b/src/libs/engine/QueuedEvent.h new file mode 100644 index 00000000..16560aaa --- /dev/null +++ b/src/libs/engine/QueuedEvent.h @@ -0,0 +1,86 @@ +/* This file is part of Om. Copyright (C) 2006 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 QUEUEDEVENT_H +#define QUEUEDEVENT_H + +#include "Event.h" + +namespace Om { + +class Responder; +class QueuedEventSource; + + +/** An Event with a not-time-critical preprocessing stage. + * + * These events are events that aren't able to be executed immediately by the + * Jack thread (because they allocate memory or whatever). They are pushed + * on to the QueuedEventQueue where they are preprocessed then pushed on + * to the realtime Event Queue when they are ready. + * + * Lookups for these events should go in the pre_process() method, since they are + * not time critical and shouldn't waste time in the audio thread doing + * lookups they can do beforehand. (This applies for any expensive operation that + * could be done before the execute() method). + * + * \ingroup engine + */ +class QueuedEvent : public Event +{ +public: + /** Process this event into a realtime-suitable event. + */ + virtual void pre_process() { + assert(m_pre_processed == false); + m_pre_processed = true; + } + + virtual void execute(samplecount offset) { + assert(m_pre_processed); + Event::execute(offset); + } + + virtual void post_process() {} + + /** If this event blocks the prepare phase of other slow events */ + bool is_blocking() { return m_blocking; } + + bool is_prepared() { return m_pre_processed; } + +protected: + // Prevent copies + QueuedEvent(const QueuedEvent& copy); + QueuedEvent& operator=(const QueuedEvent&); + + QueuedEvent(CountedPtr<Responder> responder, bool blocking = false, QueuedEventSource* source=NULL) + : Event(responder), m_pre_processed(false), m_blocking(blocking), m_source(source) + {} + + // NULL event base (for internal events) + QueuedEvent() + : Event(), m_pre_processed(false), m_blocking(false), m_source(NULL) + {} + + bool m_pre_processed; + bool m_blocking; + QueuedEventSource* m_source; +}; + + +} // namespace Om + +#endif // QUEUEDEVENT_H diff --git a/src/libs/engine/QueuedEventSource.cpp b/src/libs/engine/QueuedEventSource.cpp new file mode 100644 index 00000000..953eb5b1 --- /dev/null +++ b/src/libs/engine/QueuedEventSource.cpp @@ -0,0 +1,201 @@ +/* This file is part of Om. Copyright (C) 2006 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 "QueuedEventSource.h" +#include "QueuedEvent.h" +#include <sys/mman.h> +#include <iostream> +using std::cout; using std::cerr; using std::endl; + + +namespace Om { + + +QueuedEventSource::QueuedEventSource(size_t size) +: m_front(0), + m_back(0), + m_prepared_back(0), + m_size(size+1), + m_thread_exists(false), + m_prepare_thread_exit_flag(false), + m_semaphore(0) +{ + m_events = (QueuedEvent**)calloc(m_size, sizeof(QueuedEvent*)); + + pthread_mutex_init(&m_blocking_mutex, NULL); + pthread_cond_init(&m_blocking_cond, NULL); + + mlock(m_events, m_size * sizeof(QueuedEvent*)); +} + + +QueuedEventSource::~QueuedEventSource() +{ + stop(); + + free(m_events); + pthread_mutex_destroy(&m_blocking_mutex); + pthread_cond_destroy(&m_blocking_cond); +} + + +/** Start the prepare thread. + */ +void +QueuedEventSource::start() +{ + if (m_thread_exists) { + cerr << "[QueuedEventSource] Thread already launched?" << endl; + return; + } else { + cout << "[QueuedEventSource] Launching thread." << endl; + } + + m_prepare_thread_exit_flag = false; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1500000); + + pthread_create(&m_prepare_thread, &attr, &QueuedEventSource::prepare_loop, this); + pthread_attr_destroy(&attr); + + m_thread_exists = true; +} + + +/** Destroy the prepare thread. + */ +void +QueuedEventSource::stop() +{ + if (m_thread_exists) { + m_prepare_thread_exit_flag = true; + pthread_cancel(m_prepare_thread); + pthread_join(m_prepare_thread, NULL); + m_thread_exists = false; + cout << "[QueuedEventSource] Stopped thread." << endl; + } +} + + +/** Push an unprepared event onto the queue. + */ +void +QueuedEventSource::push(QueuedEvent* const ev) +{ + assert(!ev->is_prepared()); + + if (m_events[m_back] != NULL) { + cerr << "[QueuedEventSource] Error: Queue is full! Event is lost, please report!" << endl; + delete ev; + } else { + m_events[m_back] = ev; + m_back = (m_back + 1) % m_size; + m_semaphore.post(); + } +} + + +/** Pops the prepared event at the front of the queue, if it exists. + * + * This method will only pop events that have been prepared, and are + * stamped before the time passed. In other words, it may return NULL + * even if there are events pending in the queue. The events returned are + * actually QueuedEvent*s, but after this they are "normal" events and the + * engine deals with them just like a realtime in-band event. + */ +Event* +QueuedEventSource::pop_earliest_event_before(const samplecount time) +{ + QueuedEvent* front_event = m_events[m_front]; + + // Pop + if (front_event != NULL && front_event->time_stamp() < time && front_event->is_prepared()) { + m_events[m_front] = NULL; + m_front = (m_front + 1) % m_size; + return front_event; + } else { + return NULL; + } +} + + +// Private // + + + +/** Signal that the blocking event is finished. + * + * When this is called preparing will resume. This will be called by + * blocking events in their post_process() method. + */ +void +QueuedEventSource::unblock() +{ + /* FIXME: Make this a semaphore, and have events signal at the end of their + * execute() methods so the preprocessor can start preparing events immediately + * instead of waiting for the postprocessor to get around to finalizing the event? */ + pthread_mutex_lock(&m_blocking_mutex); + pthread_cond_signal(&m_blocking_cond); + pthread_mutex_unlock(&m_blocking_mutex); +} + + +void* +QueuedEventSource::m_prepare_loop() +{ + QueuedEvent* ev = NULL; + + while (true) { + m_semaphore.wait(); + + if (m_prepare_thread_exit_flag) + break; // exit signalled + + ev = m_events[m_prepared_back]; + assert(ev != NULL); + + if (ev == NULL) { + cerr << "[QueuedEventSource] ERROR: Signalled, but event is NULL." << endl; + continue; + } + + assert(ev != NULL); + assert(!ev->is_prepared()); + + if (ev->is_blocking()) + pthread_mutex_lock(&m_blocking_mutex); + + ev->pre_process(); + + m_prepared_back = (m_prepared_back+1) % m_size; + + // If a blocking event, wait for event to finish passing through + // the audio cycle before preparing the next event + if (ev->is_blocking()) { + pthread_cond_wait(&m_blocking_cond, &m_blocking_mutex); + pthread_mutex_unlock(&m_blocking_mutex); + } + } + + cout << "[QueuedEventSource] Exiting slow event queue thread." << endl; + return NULL; +} + + +} // namespace Om + diff --git a/src/libs/engine/QueuedEventSource.h b/src/libs/engine/QueuedEventSource.h new file mode 100644 index 00000000..002fc642 --- /dev/null +++ b/src/libs/engine/QueuedEventSource.h @@ -0,0 +1,83 @@ +/* This file is part of Om. Copyright (C) 2006 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 QUEUEDEVENTSOURCE_H +#define QUEUEDEVENTSOURCE_H + +#include <cstdlib> +#include <pthread.h> +#include "util/types.h" +#include "util/Semaphore.h" +#include "EventSource.h" + +namespace Om { + +class Event; +class QueuedEvent; + + +/** Queue of events that need processing before reaching the audio thread. + * + * Implemented as a deque (ringbuffer) in a circular array. Pushing and + * popping are threadsafe, as long as a single thread pushes and a single + * thread pops (ie this data structure is threadsafe, but the push and pop + * methods themselves are not). + */ +class QueuedEventSource : public EventSource +{ +public: + QueuedEventSource(size_t size); + ~QueuedEventSource(); + + Event* pop_earliest_event_before(const samplecount time); + + void unblock(); + + void start(); + void stop(); + +protected: + void push(QueuedEvent* const ev); + +private: + // Prevent copies (undefined) + QueuedEventSource(const QueuedEventSource&); + QueuedEventSource& operator=(const QueuedEventSource&); + + // Note that it's crucially important which functions access which of these + // variables, to maintain threadsafeness. + + size_t m_front; ///< Front of queue + size_t m_back; ///< Back of entire queue (1 past index of back element) + size_t m_prepared_back; ///< Back of prepared section (1 past index of back prepared element) + const size_t m_size; + QueuedEvent** m_events; + + bool m_thread_exists; + bool m_prepare_thread_exit_flag; + pthread_t m_prepare_thread; + Semaphore m_semaphore; ///< Counting semaphor for driving prepare thread + pthread_mutex_t m_blocking_mutex; + pthread_cond_t m_blocking_cond; + + static void* prepare_loop(void* q) { return ((QueuedEventSource*)q)->m_prepare_loop(); } + void* m_prepare_loop(); +}; + + +} // namespace Om + +#endif // QUEUEDEVENTSOURCE_H diff --git a/src/libs/engine/Responder.h b/src/libs/engine/Responder.h new file mode 100644 index 00000000..cf16a20a --- /dev/null +++ b/src/libs/engine/Responder.h @@ -0,0 +1,63 @@ +/* This file is part of Om. Copyright (C) 2006 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 RESPONDER_H +#define RESPONDER_H + +#include <inttypes.h> +#include <string> +#include "util/CountedPtr.h" +#include "interface/ClientInterface.h" +using std::string; + +namespace Om { + +using Shared::ClientInterface; + + +/** Class to handle responding to clients. + * + * This is an abstract base class to fully abstract the details of client + * communication from the internals of the engine. + * + * Note that this class only handles sending responses to commands from + * clients, (ie OK or an error), <b>not</b> notifications (ie new node, + * disconnection) - that's what ClientInterface is for. If a command is + * a request, \ref find_client can find the corresponding ClientInterface + * for this client to send the reply to. + * + * ClientInterface and Responder are seperate because responding might not + * actually get exposed to the client interface (eg in simulated blocking + * interfaces that wait for responses before returning). + */ +class Responder +{ +public: + Responder() {} + virtual ~Responder() {} + + virtual CountedPtr<Shared::ClientInterface> find_client() + { return CountedPtr<Shared::ClientInterface>(NULL); } + + virtual void respond_ok() {} + virtual void respond_error(const string& msg) {} +}; + + +} // namespace Om + +#endif // RESPONDER_H + diff --git a/src/libs/engine/TransportNode.cpp b/src/libs/engine/TransportNode.cpp new file mode 100644 index 00000000..f5183d56 --- /dev/null +++ b/src/libs/engine/TransportNode.cpp @@ -0,0 +1,155 @@ +/* This file is part of Om. Copyright (C) 2006 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 "TransportNode.h" +#include <jack/transport.h> +#include "OutputPort.h" +#include "Plugin.h" +#include "JackAudioDriver.h" +#include "Port.h" +#include "util.h" +#include "Om.h" +#include "OmApp.h" +#include "PortInfo.h" + +namespace Om { + + +TransportNode::TransportNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size) +: InternalNode(path, 1, parent, srate, buffer_size) +{ + m_num_ports = 10; + m_ports.alloc(m_num_ports); + + OutputPort<sample>* spb_port = new OutputPort<sample>(this, "Seconds per Beat", 0, 1, + new PortInfo("Seconds per Beat", CONTROL, OUTPUT, 0, 0, 1), 1); + m_ports.at(0) = spb_port; + + OutputPort<sample>* bpb_port = new OutputPort<sample>(this, "Beats per Bar", 1, 1, + new PortInfo("Beats per Bar", CONTROL, OUTPUT, 0, 0, 1), 1); + m_ports.at(1) = bpb_port; + + OutputPort<sample>* bar_port = new OutputPort<sample>(this, "Bar", 3, 1, + new PortInfo("Bar", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(2) = bar_port; + + OutputPort<sample>* beat_port = new OutputPort<sample>(this, "Beat", 3, 1, + new PortInfo("Beat", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(3) = beat_port; + + OutputPort<sample>* frame_port = new OutputPort<sample>(this, "Frame", 3, 1, + new PortInfo("Frame", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(4) = frame_port; + + OutputPort<sample>* hour_port = new OutputPort<sample>(this, "Hour", 3, 1, + new PortInfo("Hour", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(5) = hour_port; + + OutputPort<sample>* minute_port = new OutputPort<sample>(this, "Minute", 3, 1, + new PortInfo("Minute", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(6) = minute_port; + + OutputPort<sample>* second_port = new OutputPort<sample>(this, "Second", 3, 1, + new PortInfo("Second", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(7) = second_port; + + OutputPort<sample>* trg_port = new OutputPort<sample>(this, "Beat Tick", 2, 1, + new PortInfo("Beat Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(8) = trg_port; + + OutputPort<sample>* bar_trig_port = new OutputPort<sample>(this, "Bar Tick", 3, 1, + new PortInfo("Bar Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); + m_ports.at(9) = bar_trig_port; + + m_plugin.type(Plugin::Internal); + m_plugin.plug_label("transport"); + m_plugin.name("Om Transport Node (BROKEN)"); +} + + +void +TransportNode::run(size_t nframes) +{ + NodeBase::run(nframes); +#if 0 + + // FIXME: this will die horribly with any driver other than jack (in theory) + const jack_position_t* const position = ((JackAudioDriver*)om->audio_driver())->position(); + jack_transport_state_t state = ((JackAudioDriver*)om->audio_driver())->transport_state(); + double bpm = position->beats_per_minute; + float bpb = position->beats_per_bar; + float spb = 60.0 / bpm; + + //cerr << "bpm = " << bpm << endl; + //cerr << "spb = " << spb << endl; + + if (position->valid & JackPositionBBT) { + cerr << "bar: " << position->bar << endl; + cerr << "beat: " << position->beat << endl; + cerr << "tick: " << position->tick << endl; + } else { + cerr << "No BBT" << endl; + } + + if (position->valid & JackBBTFrameOffset) { + cerr << "bbt_offset: " << position->bbt_offset << endl; + } else { + cerr << "No BBT offset" << endl; + } + + if (position->valid & JackPositionTimecode) { + double time = position->frame_time; + cerr << "Seconds: " << time << " : " << endl; + /*time /= 60.0; + cerr << "Minutes: " << time << " : "; + time /= 60.0; + cerr << "Hours: " << time << " : ";*/ + } else { + cerr << "No timecode." << endl; + } + + + ((OutputPort<sample>*)m_ports.at(0))->buffer(0)->set(spb, 0, 0); + ((OutputPort<sample>*)m_ports.at(1))->buffer(0)->set(bpb, 0, 0); + + // fill the trigger buffers with zeros + ((OutputPort<sample>*)m_ports.at(2))->buffer(0)->set(0.0f, 0, nframes - 1); + ((OutputPort<sample>*)m_ports.at(3))->buffer(0)->set(0.0f, 0, nframes - 1); + + // if the transport is rolling, add triggers at the right frame positions + if ((position->valid & JackTransportBBT) && (state == JackTransportRolling)) { + double frames_per_beat = position->frame_rate * spb; + double first_beat = (1.0f - position->tick / position->ticks_per_beat) * frames_per_beat; + int first_beat_no = position->beat; + if (first_beat >= frames_per_beat) { + first_beat -= frames_per_beat; + --first_beat_no; + } + for ( ; first_beat < nframes; first_beat += frames_per_beat) { + ((OutputPort<sample>*)m_ports.at(2))->buffer(0)->set(1.0f, size_t(first_beat)); + if (first_beat_no % int(bpb) == 0) { + ((OutputPort<sample>*)m_ports.at(3))->buffer(0)->set(1.0f, size_t(first_beat)); + ++first_beat_no; + } + } + } + #endif +} + + +} // namespace Om + diff --git a/src/libs/engine/TransportNode.h b/src/libs/engine/TransportNode.h new file mode 100644 index 00000000..15b1059c --- /dev/null +++ b/src/libs/engine/TransportNode.h @@ -0,0 +1,48 @@ +/* This file is part of Om. Copyright (C) 2006 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 TRANSPORTNODE_H +#define TRANSPORTNODE_H + + +#include <string> +#include <jack/transport.h> +#include "InternalNode.h" + +namespace Om { + +using std::string; + + +/** Transport Node, brings timing information into patches. + * + * This node uses the Jack transport API to get information about BPM, time + * signature, etc.. all sample accurate. Using this you can do + * tempo-synced effects or even synthesis, etc. + */ +class TransportNode : public InternalNode +{ +public: + TransportNode(const string& path, size_t poly, Patch* parent, samplerate srate, size_t buffer_size); + + void run(size_t nframes); +}; + + +} // namespace Om + +#endif // TRANSPORTNODE_H diff --git a/src/libs/engine/Tree.h b/src/libs/engine/Tree.h new file mode 100644 index 00000000..033d48b7 --- /dev/null +++ b/src/libs/engine/Tree.h @@ -0,0 +1,155 @@ +/* This file is part of Om. Copyright (C) 2006 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 NODETREE_H +#define NODETREE_H + +#include <string> +#include <cassert> +#include "MaidObject.h" +using std::string; + +template<typename T> class Tree; + + +/** A node in a Tree. + */ +template <typename T> +class TreeNode : public MaidObject +{ +public: + TreeNode(const string& key) + : m_parent(NULL), m_left_child(NULL), m_right_child(NULL), + m_key(key), m_node(NULL) {} + + TreeNode(const string& key, T n) + : m_parent(NULL), m_left_child(NULL), m_right_child(NULL), + m_key(key), m_node(n) {} + + ~TreeNode() { + assert(m_parent == NULL || m_parent->left_child() != this); + assert(m_parent == NULL || m_parent->right_child() != this); + assert(m_left_child == NULL || m_left_child->parent() != this); + assert(m_right_child == NULL || m_right_child->parent() != this); + m_parent = m_left_child = m_right_child = NULL; + } + + string key() const { return m_key; } + void key(const string& key) { m_key = key; } + TreeNode<T>* parent() const { return m_parent; } + void parent(TreeNode<T>* n) { m_parent = n; } + TreeNode<T>* left_child() const { return m_left_child; } + void left_child(TreeNode<T>* n) { m_left_child = n; } + TreeNode<T>* right_child() const { return m_right_child; } + void right_child(TreeNode<T>* n) { m_right_child = n; } + + bool is_leaf() { return (m_left_child == NULL && m_right_child == NULL); } + bool is_left_child() { return (m_parent != NULL && m_parent->left_child() == this); } + bool is_right_child() { return (m_parent != NULL && m_parent->right_child() == this); } + + T node() { return m_node; } + + friend class Tree<T>; + +protected: + // Prevent copies (undefined) + TreeNode(const TreeNode&); + TreeNode& operator=(const TreeNode&); + + TreeNode<T>* m_parent; + TreeNode<T>* m_left_child; + TreeNode<T>* m_right_child; + string m_key; + T m_node; +}; + + +/** The tree all objects are stored in. + * + * Textbook naive (unbalanced) Binary Search Tree. Slightly different + * from a usual BST implementation in that the "Node" classes (TreeNode) are + * exposed to the user. This is so QueuedEvent's can create the TreeNode in + * another thread, and the realtime jack thread can insert them (without having + * to allocating a TreeNode which is a no-no). + * + * It's also a more annoying implementation because there's no leaf type (since + * a leaf object would have to be deleted on insert). + * + * Tree<T>::iterator is not realtime safe, but the insert/remove/find methods + * of Tree<T> do not use them. + */ +template <typename T> +class Tree +{ +public: + Tree<T>() : m_root(0), m_size(0) {} + ~Tree<T>(); + + void insert(TreeNode<T>* const n); + TreeNode<T>* remove(const string& key); + T find(const string& key) const; + TreeNode<T>* find_treenode(const string& key) const; + + size_t size() const { return m_size; } + + /** NON realtime safe iterator for a Tree<T>. */ + class iterator + { + public: + iterator(const Tree<T>* tree, size_t size); + ~iterator(); + + T operator*() const; + iterator& operator++(); + bool operator!=(const iterator& iter) const; + + friend class Tree<T>; + + iterator(const iterator& copy); + iterator& operator=(const iterator& copy); + + private: + int m_depth; + size_t m_size; + TreeNode<T>** m_stack; + const Tree<T>* m_tree; + }; + + iterator begin() const; + iterator end() const; + +private: + // Prevent copies (undefined) + Tree<T>(const Tree<T>&); + Tree<T>& operator=(const Tree<T>&); + + void m_set_all_traversed_recursive(TreeNode<T>* root, bool b); + + TreeNode<T>* m_find_smallest(TreeNode<T>* root); + TreeNode<T>* m_find_largest(TreeNode<T>* root); + + TreeNode<T>* m_root; + size_t m_size; +}; + + +/* This needs to be done so the templates are defined and can get instantiated + * automatically by the compilter. + */ +//#include "TreeImplementation.h" + + +#endif // NODETREE_H diff --git a/src/libs/engine/TreeImplementation.h b/src/libs/engine/TreeImplementation.h new file mode 100644 index 00000000..a61ec407 --- /dev/null +++ b/src/libs/engine/TreeImplementation.h @@ -0,0 +1,410 @@ +/* This file is part of Om. Copyright (C) 2006 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 "Tree.h" +#include <cstdlib> +#include <iostream> +#include <cassert> +using std::cerr; using std::endl; + + +/* FIXME: this is all in horrible need of a rewrite. */ + + +/** Destroy the tree. + * + * Note that this does not delete any TreeNodes still inside the tree, + * that is the user's responsibility. + */ +template <typename T> +Tree<T>::~Tree() +{ +} + + +/** Insert a node into the tree. Realtime safe. + * + * @a n will be inserted using the key() field for searches. + * n->key() must not be the empty string. + */ +template<typename T> +void +Tree<T>::insert(TreeNode<T>* const n) +{ + assert(n != NULL); + assert(n->left_child() == NULL); + assert(n->right_child() == NULL); + assert(n->parent() == NULL); + assert(n->key().length() > 0); + assert(find_treenode(n->key()) == NULL); + + if (m_root == NULL) { + m_root = n; + } else { + bool left = false; // which child to insert at + bool right = false; + TreeNode<T>* i = m_root; + while (true) { + assert(i != NULL); + if (n->key() <= i->key()) { + if (i->left_child() == NULL) { + left = true; + break; + } else { + i = i->left_child(); + } + } else { + if (i->right_child() == NULL) { + right = true; + break; + } else { + i = i->right_child(); + } + } + } + + assert(i != NULL); + assert(left || right); + assert( ! (left && right) ); + + if (left) { + assert(i->left_child() == NULL); + i->left_child(n); + } else if (right) { + assert(i->right_child() == NULL); + i->right_child(n); + } + n->parent(i); + } + ++m_size; +} + + +/** Remove a node from the tree. + * + * Realtime safe, caller is responsible to delete returned value. + * + * @return NULL if object with @a key is not in tree. + */ +template<typename T> +TreeNode<T>* +Tree<T>::remove(const string& key) +{ + TreeNode<T>* node = find_treenode(key); + TreeNode<T>* n = node; + TreeNode<T>* swap = NULL; + T temp_node; + string temp_key; + + if (node == NULL) + return NULL; + + // Node is not even in tree + if (node->parent() == NULL && m_root != node) + return NULL; + // FIXME: What if the node is in a different tree? Check for this? + +#ifndef NDEBUG + const T& remove_node = node->node(); // for error checking +#endif // NDEBUG + + // n has two children + if (n->left_child() != NULL && n->right_child() != NULL) { + if (rand()%2) + swap = m_find_largest(n->left_child()); + else + swap = m_find_smallest(n->right_child()); + + // Swap node's elements + temp_node = swap->m_node; + swap->m_node = n->m_node; + n->m_node = temp_node; + + // Swap node's keys + temp_key = swap->m_key; + swap->m_key = n->m_key; + n->m_key = temp_key; + + n = swap; + assert(n != NULL); + } + + // be sure we swapped correctly (ie right node is getting removed) + assert(n->node() == remove_node); + + // n now has at most one child + assert(n->left_child() == NULL || n->right_child() == NULL); + + if (n->is_leaf()) { + if (n->is_left_child()) + n->parent()->left_child(NULL); + else if (n->is_right_child()) + n->parent()->right_child(NULL); + + if (m_root == n) m_root = NULL; + } else { // has a single child + TreeNode<T>* child = NULL; + if (n->left_child() != NULL) + child = n->left_child(); + else if (n->right_child() != NULL) + child = n->right_child(); + else + exit(EXIT_FAILURE); + + assert(child != n); + assert(child != NULL); + assert(n->parent() != n); + + if (n->is_left_child()) { + assert(n->parent() != child); + n->parent()->left_child(child); + child->parent(n->parent()); + } else if (n->is_right_child()) { + assert(n->parent() != child); + n->parent()->right_child(child); + child->parent(n->parent()); + } else { + child->parent(NULL); + } + if (m_root == n) m_root = child; + } + + // Be sure node is cut off completely + assert(n != NULL); + assert(n->parent() == NULL || n->parent()->left_child() != n); + assert(n->parent() == NULL || n->parent()->right_child() != n); + assert(n->left_child() == NULL || n->left_child()->parent() != n); + assert(n->right_child() == NULL || n->right_child()->parent() != n); + assert(m_root != n); + + n->parent(NULL); + n->left_child(NULL); + n->right_child(NULL); + + --m_size; + + if (m_size == 0) m_root = NULL; + + // Be sure right node is being removed + assert(n->node() == remove_node); + + return n; +} + + +template<typename T> +T +Tree<T>::find(const string& name) const +{ + TreeNode<T>* tn = find_treenode(name); + + return (tn == NULL) ? NULL : tn->node(); +} + + +template<typename T> +TreeNode<T>* +Tree<T>::find_treenode(const string& name) const +{ + TreeNode<T>* i = m_root; + int cmp = 0; + + while (i != NULL) { + cmp = name.compare(i->key()); + if (cmp < 0) + i = i->left_child(); + else if (cmp > 0) + i = i->right_child(); + else + break; + } + + return i; +} + + +/// Private /// +template<typename T> +void +Tree<T>::m_set_all_traversed_recursive(TreeNode<T>* root, bool b) +{ + assert(root != NULL); + + // Preorder traversal + root->node()->traversed(b); + if (root->left_child() != NULL) + m_set_all_traversed_recursive(root->left_child(), b); + if (root->right_child() != NULL) + m_set_all_traversed_recursive(root->right_child(), b); +} + + +/** Finds the smallest (key) node in the subtree rooted at "root" + */ +template<typename T> +TreeNode<T>* +Tree<T>::m_find_smallest(TreeNode<T>* root) +{ + TreeNode<T>* r = root; + + while (r->left_child() != NULL) + r = r->left_child(); + + return r; +} + + +/** Finds the largest (key) node in the subtree rooted at "root". + */ +template<typename T> +TreeNode<T>* +Tree<T>::m_find_largest(TreeNode<T>* root) +{ + TreeNode<T>* r = root; + + while (r->right_child() != NULL) + r = r->right_child(); + + return r; + +} + + + +//// Iterator Stuff //// + + + +template<typename T> +Tree<T>::iterator::iterator(const Tree *tree, size_t size) +: m_depth(-1), + m_size(size), + m_stack(NULL), + m_tree(tree) +{ + if (size > 0) + m_stack = new TreeNode<T>*[size]; +} + + +template<typename T> +Tree<T>::iterator::~iterator() +{ + delete[] m_stack; +} + + +/* FIXME: Make these next two not memcpy (possibly have to force a single + * iterator existing at any given time) for speed. + */ + +// Copy constructor (for the typical for loop usage) +template<typename T> +Tree<T>::iterator::iterator(const Tree<T>::iterator& copy) +: m_depth(copy.m_depth), + m_size(copy.m_size), + m_tree(copy.m_tree) +{ + if (m_size > 0) { + m_stack = new TreeNode<T>*[m_size]; + memcpy(m_stack, copy.m_stack, m_size * sizeof(TreeNode<T>*)); + } +} + + +// Assignment operator +template<typename T> +typename Tree<T>::iterator& +Tree<T>::iterator::operator=(const Tree<T>::iterator& copy) { + m_depth = copy.m_depth; + m_size = copy.m_size; + m_tree = copy.m_tree; + + if (m_size > 0) { + m_stack = new TreeNode<T>*[m_size]; + memcpy(m_stack, copy.m_stack, m_size * sizeof(TreeNode<T>*)); + } + return *this; +} + + +template<typename T> +T +Tree<T>::iterator::operator*() const +{ + assert(m_depth >= 0); + return m_stack[m_depth]->node(); +} + + +template<typename T> +typename Tree<T>::iterator& +Tree<T>::iterator::operator++() +{ + assert(m_depth >= 0); + + TreeNode<T>* tn = m_stack[m_depth]; + --m_depth; + + tn = tn->right_child(); + while (tn != NULL) { + ++m_depth; + m_stack[m_depth] = tn; + tn = tn->left_child(); + } + + return *this; +} + + +template<typename T> +bool +Tree<T>::iterator::operator!=(const Tree<T>::iterator& iter) const +{ + // (DeMorgan's Law) + return (m_tree != iter.m_tree || m_depth != iter.m_depth); +} + + +template<typename T> +typename Tree<T>::iterator +Tree<T>::begin() const +{ + typename Tree<T>::iterator iter(this, m_size); + iter.m_depth = -1; + + TreeNode<T> *ptr = m_root; + while (ptr != NULL) { + iter.m_depth++; + iter.m_stack[iter.m_depth] = ptr; + ptr = ptr->left_child(); + } + + return iter; +} + + +template<typename T> +typename Tree<T>::iterator +Tree<T>::end() const +{ + typename Tree<T>::iterator iter(this, 0); + iter.m_depth = -1; + + return iter; +} + + diff --git a/src/libs/engine/cmdline.c b/src/libs/engine/cmdline.c new file mode 100644 index 00000000..6b7ddf6a --- /dev/null +++ b/src/libs/engine/cmdline.c @@ -0,0 +1,150 @@ +/* + File autogenerated by gengetopt version 2.10 + generated with the following command: + gengetopt + + The developers of gengetopt consider the fixed text that goes in all + gengetopt output files to be in the public domain: + we make no copyright claims on it. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* If we use autoconf. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "getopt.h" + +#include "cmdline.h" + +void +cmdline_parser_print_version (void) +{ + printf ("%s %s\n", CMDLINE_PARSER_PACKAGE, CMDLINE_PARSER_VERSION); +} + +void +cmdline_parser_print_help (void) +{ + cmdline_parser_print_version (); + printf("\n" + "Usage: %s [OPTIONS]...\n", CMDLINE_PARSER_PACKAGE); + printf(" -h --help Print help and exit\n"); + printf(" -V --version Print version and exit\n"); + printf(" -pSTRING --port=STRING OSC port to listen on (default='16180')\n"); + printf(" -i --in-jackd Run engine as in-process JACK client (default=off)\n"); +} + + +static char *gengetopt_strdup (const char *s); + +/* gengetopt_strdup() */ +/* strdup.c replacement of strdup, which is not standard */ +char * +gengetopt_strdup (const char *s) +{ + char *result = (char*)malloc(strlen(s) + 1); + if (result == (char*)0) + return (char*)0; + strcpy(result, s); + return result; +} + +int +cmdline_parser (int argc, char * const *argv, struct gengetopt_args_info *args_info) +{ + int c; /* Character of the parsed option. */ + int missing_required_options = 0; + + args_info->help_given = 0 ; + args_info->version_given = 0 ; + args_info->port_given = 0 ; + args_info->in_jackd_given = 0 ; +#define clear_args() { \ + args_info->port_arg = gengetopt_strdup("16180") ;\ + args_info->in_jackd_flag = 0;\ +} + + clear_args(); + + optarg = 0; + optind = 1; + opterr = 1; + optopt = '?'; + + while (1) + { + int option_index = 0; + char *stop_char; + + static struct option long_options[] = { + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { "port", 1, NULL, 'p' }, + { "in-jackd", 0, NULL, 'i' }, + { NULL, 0, NULL, 0 } + }; + + stop_char = 0; + c = getopt_long (argc, argv, "hVp:i", long_options, &option_index); + + if (c == -1) break; /* Exit from `while (1)' loop. */ + + switch (c) + { + case 'h': /* Print help and exit. */ + clear_args (); + cmdline_parser_print_help (); + exit (EXIT_SUCCESS); + + case 'V': /* Print version and exit. */ + clear_args (); + cmdline_parser_print_version (); + exit (EXIT_SUCCESS); + + case 'p': /* OSC port to listen on. */ + if (args_info->port_given) + { + fprintf (stderr, "%s: `--port' (`-p') option given more than once\n", CMDLINE_PARSER_PACKAGE); + clear_args (); + exit (EXIT_FAILURE); + } + args_info->port_given = 1; + args_info->port_arg = gengetopt_strdup (optarg); + break; + + case 'i': /* Run engine as in-process JACK client. */ + if (args_info->in_jackd_given) + { + fprintf (stderr, "%s: `--in-jackd' (`-i') option given more than once\n", CMDLINE_PARSER_PACKAGE); + clear_args (); + exit (EXIT_FAILURE); + } + args_info->in_jackd_given = 1; + args_info->in_jackd_flag = !(args_info->in_jackd_flag); + break; + + + case 0: /* Long option with no short option */ + + case '?': /* Invalid option. */ + /* `getopt_long' already printed an error message. */ + exit (EXIT_FAILURE); + + default: /* bug: option not considered. */ + fprintf (stderr, "%s: option unknown: %c\n", CMDLINE_PARSER_PACKAGE, c); + abort (); + } /* switch */ + } /* while */ + + + if ( missing_required_options ) + exit (EXIT_FAILURE); + + return 0; +} diff --git a/src/libs/engine/cmdline.ggo b/src/libs/engine/cmdline.ggo new file mode 100644 index 00000000..8c006ca7 --- /dev/null +++ b/src/libs/engine/cmdline.ggo @@ -0,0 +1,7 @@ +# Process this file with gengetopt -u to generate the necessary code (in cmdline.h, cmdline.c) + +package "Om - An OSC controlled realtime modular synthesizer" + +option "port" p "OSC port to listen on" string default="16180" no +option "in-jackd" i "Run engine as in-process JACK client" flag off + diff --git a/src/libs/engine/cmdline.h b/src/libs/engine/cmdline.h new file mode 100644 index 00000000..fe36a969 --- /dev/null +++ b/src/libs/engine/cmdline.h @@ -0,0 +1,45 @@ +/* cmdline.h */ + +/* File autogenerated by gengetopt version 2.10 */ + +#ifndef CMDLINE_H +#define CMDLINE_H + +/* If we use autoconf. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef CMDLINE_PARSER_PACKAGE +#define CMDLINE_PARSER_PACKAGE "Om - An OSC controlled realtime modular synthesizer" +#endif + +#ifndef CMDLINE_PARSER_VERSION +#define CMDLINE_PARSER_VERSION VERSION +#endif + +struct gengetopt_args_info +{ + char * port_arg; /* OSC port to listen on (default='16180'). */ + int in_jackd_flag; /* Run engine as in-process JACK client (default=off). */ + + int help_given ; /* Whether help was given. */ + int version_given ; /* Whether version was given. */ + int port_given ; /* Whether port was given. */ + int in_jackd_given ; /* Whether in-jackd was given. */ + +} ; + +int cmdline_parser (int argc, char * const *argv, struct gengetopt_args_info *args_info); + +void cmdline_parser_print_help(void); +void cmdline_parser_print_version(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* CMDLINE_H */ diff --git a/src/libs/engine/events.h b/src/libs/engine/events.h new file mode 100644 index 00000000..f1bc5f58 --- /dev/null +++ b/src/libs/engine/events.h @@ -0,0 +1,62 @@ +/* This file is part of Om. Copyright (C) 2006 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 EVENTS_H +#define EVENTS_H + +#include "config.h" + +#include "ActivateEvent.h" +#include "DeactivateEvent.h" +#include "EnablePatchEvent.h" +#include "DisablePatchEvent.h" +#include "ClearPatchEvent.h" +#include "SetPortValueEvent.h" +#include "SetPortValueQueuedEvent.h" +#include "ConnectionEvent.h" +#include "DisconnectionEvent.h" +#include "AddNodeEvent.h" +#include "CreatePatchEvent.h" +#include "DestroyEvent.h" +#include "SetMetadataEvent.h" +#include "RequestMetadataEvent.h" +#include "RequestPortValueEvent.h" +#include "RequestAllObjectsEvent.h" +#include "RequestPluginsEvent.h" +#include "LoadPluginsEvent.h" +#include "NoteOnEvent.h" +#include "NoteOffEvent.h" +#include "AllNotesOffEvent.h" +#include "DisconnectNodeEvent.h" +#include "RegisterClientEvent.h" +#include "UnregisterClientEvent.h" +#include "RenameEvent.h" +#include "PingQueuedEvent.h" +#include "MidiLearnEvent.h" + +#ifdef HAVE_LASH +#include "LashRestoreDoneEvent.h" +#endif + +#ifdef HAVE_DSSI +#include "DSSIUpdateEvent.h" +#include "DSSIControlEvent.h" +#include "DSSIConfigureEvent.h" +#include "DSSIProgramEvent.h" +#endif + +#endif // EVENTS_H + diff --git a/src/libs/engine/events/ActivateEvent.cpp b/src/libs/engine/events/ActivateEvent.cpp new file mode 100644 index 00000000..671b26d5 --- /dev/null +++ b/src/libs/engine/events/ActivateEvent.cpp @@ -0,0 +1,52 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ActivateEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" + +namespace Om { + + +ActivateEvent::ActivateEvent(CountedPtr<Responder> responder) +: QueuedEvent(responder) +{ +} + + +void +ActivateEvent::pre_process() +{ + QueuedEvent::pre_process(); + + if (om != NULL) + om->activate(); +} + + +void +ActivateEvent::post_process() +{ + if (om != NULL) + m_responder->respond_ok(); + else + m_responder->respond_error("Not ready to activate yet."); +} + + +} // namespace Om + diff --git a/src/libs/engine/events/ActivateEvent.h b/src/libs/engine/events/ActivateEvent.h new file mode 100644 index 00000000..280b5523 --- /dev/null +++ b/src/libs/engine/events/ActivateEvent.h @@ -0,0 +1,41 @@ +/* This file is part of Om. Copyright (C) 2006 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 ACTIVATEEVENT_H +#define ACTIVATEEVENT_H + +#include "QueuedEvent.h" + +namespace Om { + + +/** Activates the engine. + * + * \ingroup engine + */ +class ActivateEvent : public QueuedEvent +{ +public: + ActivateEvent(CountedPtr<Responder> responder); + + void pre_process(); + void post_process(); +}; + + +} // namespace Om + +#endif // ACTIVATEEVENT_H diff --git a/src/libs/engine/events/AddNodeEvent.cpp b/src/libs/engine/events/AddNodeEvent.cpp new file mode 100644 index 00000000..2b31ef4a --- /dev/null +++ b/src/libs/engine/events/AddNodeEvent.cpp @@ -0,0 +1,128 @@ +/* This file is part of Om. Copyright (C) 2006 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 "AddNodeEvent.h" +#include "Responder.h" +#include "Patch.h" +#include "Node.h" +#include "Tree.h" +#include "Plugin.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "NodeFactory.h" +#include "ClientBroadcaster.h" +#include "Maid.h" +#include "util/Path.h" +#include "ObjectStore.h" +#include "util/Path.h" +#include "Port.h" + +namespace Om { + + +AddNodeEvent::AddNodeEvent(CountedPtr<Responder> responder, const string& path, Plugin* plugin, bool poly) +: QueuedEvent(responder), + m_path(path), + m_plugin(plugin), + m_poly(poly), + m_patch(NULL), + m_node(NULL), + m_process_order(NULL), + m_node_already_exists(false) +{ +} + + +AddNodeEvent::~AddNodeEvent() +{ + delete m_plugin; +} + + +void +AddNodeEvent::pre_process() +{ + if (om->object_store()->find(m_path) != NULL) { + m_node_already_exists = true; + QueuedEvent::pre_process(); + return; + } + + m_patch = om->object_store()->find_patch(m_path.parent()); + + if (m_patch != NULL) { + if (m_poly) + m_node = om->node_factory()->load_plugin(m_plugin, m_path.name(), m_patch->internal_poly(), m_patch); + else + m_node = om->node_factory()->load_plugin(m_plugin, m_path.name(), 1, m_patch); + + if (m_node != NULL) { + m_node->activate(); + + // This can be done here because the audio thread doesn't touch the + // node tree - just the process order array + m_patch->add_node(new ListNode<Node*>(m_node)); + m_node->add_to_store(); + + if (m_patch->process()) + m_process_order = m_patch->build_process_order(); + } + } + QueuedEvent::pre_process(); +} + + +void +AddNodeEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_node != NULL) { + m_node->add_to_patch(); + + if (m_patch->process_order() != NULL) + om->maid()->push(m_patch->process_order()); + m_patch->process_order(m_process_order); + } +} + + +void +AddNodeEvent::post_process() +{ + string msg; + if (m_node_already_exists) { + msg = string("Could not create node - ").append(m_path);// + " already exists."; + m_responder->respond_error(msg); + } else if (m_patch == NULL) { + msg = "Could not find patch '" + m_path.parent() +"' for add_node."; + m_responder->respond_error(msg); + } else if (m_node == NULL) { + msg = "Unable to load node "; + msg.append(m_path).append(" (you're missing the plugin \"").append( + m_plugin->lib_name()).append(":").append(m_plugin->plug_label()).append("\")");; + m_responder->respond_error(msg); + } else { + m_responder->respond_ok(); + //om->client_broadcaster()->send_node_creation_messages(m_node); + om->client_broadcaster()->send_node(m_node); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/AddNodeEvent.h b/src/libs/engine/events/AddNodeEvent.h new file mode 100644 index 00000000..fe0236ba --- /dev/null +++ b/src/libs/engine/events/AddNodeEvent.h @@ -0,0 +1,63 @@ +/* This file is part of Om. Copyright (C) 2006 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 ADDNODEEVENT_H +#define ADDNODEEVENT_H + +#include "QueuedEvent.h" +#include "util/Path.h" +#include <string> +using std::string; + +template <typename T> class Array; +template<typename T> class TreeNode; + +namespace Om { + +class Patch; +class Node; +class Plugin; + + +/** An event to load a Node and insert it into a Patch. + * + * \ingroup engine + */ +class AddNodeEvent : public QueuedEvent +{ +public: + AddNodeEvent(CountedPtr<Responder> responder, const string& path, Plugin* plugin, bool poly); + ~AddNodeEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_patch_name; + Path m_path; + Plugin* m_plugin; + bool m_poly; + Patch* m_patch; + Node* m_node; + Array<Node*>* m_process_order; // Patch's new process order + bool m_node_already_exists; +}; + + +} // namespace Om + +#endif // ADDNODEEVENT_H diff --git a/src/libs/engine/events/AllNotesOffEvent.cpp b/src/libs/engine/events/AllNotesOffEvent.cpp new file mode 100644 index 00000000..aa3a00f1 --- /dev/null +++ b/src/libs/engine/events/AllNotesOffEvent.cpp @@ -0,0 +1,67 @@ +/* This file is part of Om. Copyright (C) 2006 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 "AllNotesOffEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" + +namespace Om { + + +/** Note off with patch explicitly passed - triggered by MIDI. + */ +AllNotesOffEvent::AllNotesOffEvent(CountedPtr<Responder> responder, Patch* patch) +: Event(responder), + m_patch(patch) +{ +} + + +/** Note off event with lookup - triggered by OSC. + */ +AllNotesOffEvent::AllNotesOffEvent(CountedPtr<Responder> responder, const string& patch_path) +: Event(responder), + m_patch(NULL), + m_patch_path(patch_path) +{ +} + + +void +AllNotesOffEvent::execute(samplecount offset) +{ + if (m_patch == NULL && m_patch_path != "") + m_patch = om->object_store()->find_patch(m_patch_path); + + //if (m_patch != NULL) + // for (List<MidiInNode*>::iterator j = m_patch->midi_in_nodes().begin(); j != m_patch->midi_in_nodes().end(); ++j) + // (*j)->all_notes_off(offset); +} + + +void +AllNotesOffEvent::post_process() +{ + if (m_patch != NULL) + m_responder->respond_ok(); +} + + +} // namespace Om + + diff --git a/src/libs/engine/events/AllNotesOffEvent.h b/src/libs/engine/events/AllNotesOffEvent.h new file mode 100644 index 00000000..ea23301b --- /dev/null +++ b/src/libs/engine/events/AllNotesOffEvent.h @@ -0,0 +1,50 @@ +/* This file is part of Om. Copyright (C) 2006 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 ALLNOTESOFFEVENT_H +#define ALLNOTESOFFEVENT_H + +#include "Event.h" +#include <string> +using std::string; + +namespace Om { + +class Patch; + + +/** A note off event for all active voices. + * + * \ingroup engine + */ +class AllNotesOffEvent : public Event +{ +public: + AllNotesOffEvent(CountedPtr<Responder> responder, Patch* patch); + AllNotesOffEvent(CountedPtr<Responder> responder, const string& patch_path); + + void execute(samplecount offset); + void post_process(); + +private: + Patch* m_patch; + string m_patch_path; +}; + + +} // namespace Om + +#endif // ALLNOTESOFFEVENT_H diff --git a/src/libs/engine/events/ClearPatchEvent.cpp b/src/libs/engine/events/ClearPatchEvent.cpp new file mode 100644 index 00000000..8b8fc223 --- /dev/null +++ b/src/libs/engine/events/ClearPatchEvent.cpp @@ -0,0 +1,114 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ClearPatchEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "util.h" +#include "ObjectStore.h" +#include "Port.h" +#include "Maid.h" +#include "Node.h" +#include "Connection.h" +#include "QueuedEventSource.h" + +namespace Om { + + +ClearPatchEvent::ClearPatchEvent(CountedPtr<Responder> responder, const string& patch_path) +: QueuedEvent(responder, true), + m_patch_path(patch_path), + m_patch(NULL), + m_process(false) +{ +} + + +void +ClearPatchEvent::pre_process() +{ + m_patch = om->object_store()->find_patch(m_patch_path); + + if (m_patch != NULL) { + + m_process = m_patch->process(); + + for (List<Node*>::const_iterator i = m_patch->nodes().begin(); i != m_patch->nodes().end(); ++i) + (*i)->remove_from_store(); + } + + QueuedEvent::pre_process(); +} + + +void +ClearPatchEvent::execute(samplecount offset) +{ + if (m_patch != NULL) { + m_patch->process(false); + + for (List<Node*>::const_iterator i = m_patch->nodes().begin(); i != m_patch->nodes().end(); ++i) + (*i)->remove_from_patch(); + + if (m_patch->process_order() != NULL) { + om->maid()->push(m_patch->process_order()); + m_patch->process_order(NULL); + } + } + + QueuedEvent::execute(offset); +} + + +void +ClearPatchEvent::post_process() +{ + if (m_patch != NULL) { + // Delete all nodes + for (List<Node*>::iterator i = m_patch->nodes().begin(); i != m_patch->nodes().end(); ++i) { + (*i)->deactivate(); + delete *i; + } + m_patch->nodes().clear(); + + // Delete all connections + for (List<Connection*>::iterator i = m_patch->connections().begin(); i != m_patch->connections().end(); ++i) + delete *i; + m_patch->connections().clear(); + + // Restore patch's run state + m_patch->process(m_process); + + // Make sure everything's sane + assert(m_patch->nodes().size() == 0); + assert(m_patch->connections().size() == 0); + + // Reply + m_responder->respond_ok(); + om->client_broadcaster()->send_patch_cleared(m_patch_path); + } else { + m_responder->respond_error(string("Patch ") + m_patch_path + " not found"); + } + + m_source->unblock(); +} + + +} // namespace Om + diff --git a/src/libs/engine/events/ClearPatchEvent.h b/src/libs/engine/events/ClearPatchEvent.h new file mode 100644 index 00000000..c6e531a8 --- /dev/null +++ b/src/libs/engine/events/ClearPatchEvent.h @@ -0,0 +1,54 @@ +/* This file is part of Om. Copyright (C) 2006 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 CLEARPATCHEVENT_H +#define CLEARPATCHEVENT_H + +#include <string> +#include "QueuedEvent.h" +#include "Array.h" + +using std::string; + +namespace Om { + +class Patch; + + +/** Delete all nodes from a patch. + * + * \ingroup engine + */ +class ClearPatchEvent : public QueuedEvent +{ +public: + ClearPatchEvent(CountedPtr<Responder> responder, const string& patch_path); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_patch_path; + Patch* m_patch; + bool m_process; +}; + + +} // namespace Om + + +#endif // CLEARPATCHEVENT_H diff --git a/src/libs/engine/events/ConnectionEvent.cpp b/src/libs/engine/events/ConnectionEvent.cpp new file mode 100644 index 00000000..fe3b991e --- /dev/null +++ b/src/libs/engine/events/ConnectionEvent.cpp @@ -0,0 +1,240 @@ +/* This file is part of Om. Copyright (C) 2006 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 "ConnectionEvent.h" +#include <string> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ConnectionBase.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "Port.h" +#include "PortInfo.h" +#include "Maid.h" +#include "ObjectStore.h" +#include "util/Path.h" + +using std::string; +namespace Om { + + +//// ConnectionEvent //// + + +ConnectionEvent::ConnectionEvent(CountedPtr<Responder> responder, const string& src_port_path, const string& dst_port_path) +: QueuedEvent(responder), + m_src_port_path(src_port_path), + m_dst_port_path(dst_port_path), + m_patch(NULL), + m_src_port(NULL), + m_dst_port(NULL), + m_typed_event(NULL), + m_error(NO_ERROR) +{ +} + + +ConnectionEvent::~ConnectionEvent() +{ + delete m_typed_event; +} + + +void +ConnectionEvent::pre_process() +{ + if (m_src_port_path.parent().parent() != m_dst_port_path.parent().parent()) { + m_error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + /*m_patch = om->object_store()->find_patch(m_src_port_path.parent().parent()); + + if (m_patch == NULL) { + m_error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + }*/ + + Port* port1 = om->object_store()->find_port(m_src_port_path); + Port* port2 = om->object_store()->find_port(m_dst_port_path); + + if (port1 == NULL || port2 == NULL) { + m_error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (port1->port_info()->type() != port2->port_info()->type()) { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + if (port1->port_info()->is_output() && port2->port_info()->is_input()) { + m_src_port = port1; + m_dst_port = port2; + } else if (port2->port_info()->is_output() && port1->port_info()->is_input()) { + m_src_port = port2; + m_dst_port = port1; + } else { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + // Create the typed event to actually do the work + const PortType type = port1->port_info()->type(); + if (type == AUDIO || type == CONTROL) { + m_typed_event = new TypedConnectionEvent<sample>(m_responder, + (OutputPort<sample>*)m_src_port, (InputPort<sample>*)m_dst_port); + } else if (type == MIDI) { + m_typed_event = new TypedConnectionEvent<MidiMessage>(m_responder, + (OutputPort<MidiMessage>*)m_src_port, (InputPort<MidiMessage>*)m_dst_port); + } else { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + m_typed_event->pre_process(); + + QueuedEvent::pre_process(); +} + + +void +ConnectionEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_error == NO_ERROR) + m_typed_event->execute(offset); +} + + +void +ConnectionEvent::post_process() +{ + if (m_error == NO_ERROR) { + m_typed_event->post_process(); + } else { + // FIXME: better error messages + string msg = "Unable to make connection "; + msg.append(m_src_port_path + " -> " + m_dst_port_path); + m_responder->respond_error(msg); + } +} + + + +//// TypedConnectionEvent //// + + +template <typename T> +TypedConnectionEvent<T>::TypedConnectionEvent(CountedPtr<Responder> responder, OutputPort<T>* src_port, InputPort<T>* dst_port) +: QueuedEvent(responder), + m_src_port(src_port), + m_dst_port(dst_port), + m_patch(NULL), + m_process_order(NULL), + m_connection(NULL), + m_port_listnode(NULL), + m_succeeded(true) +{ + assert(src_port != NULL); + assert(dst_port != NULL); +} + +template <typename T> +TypedConnectionEvent<T>::~TypedConnectionEvent() +{ + // FIXME: haaaack, prevent a double delete + // this class is unusable by anything other than ConnectionEvent because of this + //m_responder = NULL; +} + + +template <typename T> +void +TypedConnectionEvent<T>::pre_process() +{ + Node* const src_node = m_src_port->parent_node(); + Node* const dst_node = m_dst_port->parent_node(); + + m_patch = src_node->parent_patch(); + + if (src_node == NULL || dst_node == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + if (src_node->parent() != m_patch || dst_node->parent() != m_patch) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + m_connection = new ConnectionBase<T>(m_src_port, m_dst_port); + m_port_listnode = new ListNode<ConnectionBase<T>*>(m_connection); + m_patch_listnode = new ListNode<Connection*>(m_connection); + + dst_node->providers()->push_back(new ListNode<Node*>(src_node)); + src_node->dependants()->push_back(new ListNode<Node*>(dst_node)); + + if (m_patch->process()) + m_process_order = m_patch->build_process_order(); +} + + +template <typename T> +void +TypedConnectionEvent<T>::execute(samplecount offset) +{ + if (m_succeeded) { + // These must be inserted here, since they're actually used by the audio thread + m_dst_port->add_connection(m_port_listnode); + m_patch->add_connection(m_patch_listnode); + if (m_patch->process_order() != NULL) + om->maid()->push(m_patch->process_order()); + m_patch->process_order(m_process_order); + } +} + + +template <typename T> +void +TypedConnectionEvent<T>::post_process() +{ + if (m_succeeded) { + assert(m_connection != NULL); + + m_responder->respond_ok(); + + om->client_broadcaster()->send_connection(m_connection); + } else { + m_responder->respond_error("Unable to make connection."); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/ConnectionEvent.h b/src/libs/engine/events/ConnectionEvent.h new file mode 100644 index 00000000..8aaf2292 --- /dev/null +++ b/src/libs/engine/events/ConnectionEvent.h @@ -0,0 +1,107 @@ +/* This file is part of Om. Copyright (C) 2006 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 CONNECTIONEVENT_H +#define CONNECTIONEVENT_H + +#include <string> +#include "QueuedEvent.h" +#include "util/Path.h" +#include "util/types.h" +using std::string; + +template <typename T> class ListNode; +template <typename T> class Array; + +namespace Om { + +class Patch; +class Node; +class Connection; +class MidiMessage; +class Port; +template <typename T> class ConnectionBase; +template <typename T> class InputPort; +template <typename T> class OutputPort; +template <typename T> class TypedConnectionEvent; // helper, defined below + + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class ConnectionEvent : public QueuedEvent +{ +public: + ConnectionEvent(CountedPtr<Responder> responder, const string& src_port_path, const string& dst_port_path); + ~ConnectionEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + + enum ErrorType { NO_ERROR, PARENT_PATCH_DIFFERENT, PORT_NOT_FOUND, TYPE_MISMATCH }; + + Path m_src_port_path; + Path m_dst_port_path; + + Patch* m_patch; + Port* m_src_port; + Port* m_dst_port; + + QueuedEvent* m_typed_event; + + ErrorType m_error; +}; + + +/** Templated ConnectionEvent. + * + * Intended to be called from ConnectionEvent so callers (ie OSCReceiver) + * can use ConnectionEvent without knowing anything about types (which + * they can't, since all they have is Port paths). + */ +template <typename T> +class TypedConnectionEvent : public QueuedEvent +{ +public: + TypedConnectionEvent(CountedPtr<Responder> responder, OutputPort<T>* src_port, InputPort<T>* dst_port); + ~TypedConnectionEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + OutputPort<T>* m_src_port; + InputPort<T>* m_dst_port; + + Patch* m_patch; + Array<Node*>* m_process_order; ///< New process order for Patch + ConnectionBase<T>* m_connection; + ListNode<Connection*>* m_patch_listnode; + ListNode<ConnectionBase<T>*>* m_port_listnode; + + bool m_succeeded; +}; + + + +} // namespace Om + +#endif // CONNECTIONEVENT_H diff --git a/src/libs/engine/events/CreatePatchEvent.cpp b/src/libs/engine/events/CreatePatchEvent.cpp new file mode 100644 index 00000000..9f0ae7f2 --- /dev/null +++ b/src/libs/engine/events/CreatePatchEvent.cpp @@ -0,0 +1,150 @@ +/* This file is part of Om. Copyright (C) 2006 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 "CreatePatchEvent.h" +#include "Responder.h" +#include "Patch.h" +#include "Node.h" +#include "Tree.h" +#include "Plugin.h" +#include "Om.h" +#include "OmApp.h" +#include "Maid.h" +#include "ClientBroadcaster.h" +#include "AudioDriver.h" +#include "util/Path.h" +#include "ObjectStore.h" + +namespace Om { + + +CreatePatchEvent::CreatePatchEvent(CountedPtr<Responder> responder, const string& path, int poly) +: QueuedEvent(responder), + m_path(path), + m_patch(NULL), + m_parent(NULL), + m_process_order(NULL), + m_poly(poly), + m_error(NO_ERROR) +{ +} + + +void +CreatePatchEvent::pre_process() +{ + if (om->object_store()->find(m_path) != NULL) { + m_error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + if (m_poly < 1) { + m_error = INVALID_POLY; + QueuedEvent::pre_process(); + return; + } + + if (m_path != "/") { + m_parent = om->object_store()->find_patch(m_path.parent()); + if (m_parent == NULL) { + m_error = PARENT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + } + + size_t poly = 1; + if (m_parent != NULL && m_poly > 1 && m_poly == static_cast<int>(m_parent->internal_poly())) + poly = m_poly; + + m_patch = new Patch(m_path.name(), poly, m_parent, om->audio_driver()->sample_rate(), om->audio_driver()->buffer_size(), m_poly); + + if (m_parent != NULL) { + m_parent->add_node(new ListNode<Node*>(m_patch->as_node())); + + if (m_parent->process()) + m_process_order = m_parent->build_process_order(); + } + + m_patch->activate(); + + // Insert into ObjectStore + m_patch->add_to_store(); + + QueuedEvent::pre_process(); +} + + +void +CreatePatchEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_patch != NULL) { + if (m_parent == NULL) { + assert(m_path == "/"); + assert(m_patch->parent_patch() == NULL); + om->audio_driver()->set_root_patch(m_patch); + } else { + assert(m_parent != NULL); + assert(m_path != "/"); + + m_patch->add_to_patch(); + + if (m_parent->process_order() != NULL) + om->maid()->push(m_parent->process_order()); + m_parent->process_order(m_process_order); + } + } +} + + +void +CreatePatchEvent::post_process() +{ + if (m_responder.get()) { + if (m_error == NO_ERROR) { + + m_responder->respond_ok(); + + // Don't want to send nodes that have been added since prepare() + //om->client_broadcaster()->send_node_creation_messages(m_patch); + + // Patches are always empty on creation, so this is fine + om->client_broadcaster()->send_patch(m_patch); + + } else if (m_error == OBJECT_EXISTS) { + string msg = "Unable to create patch: "; + msg += m_path += " already exists."; + m_responder->respond_error(msg); + } else if (m_error == PARENT_NOT_FOUND) { + string msg = "Unable to create patch: Parent "; + msg += m_path.parent() += " not found."; + m_responder->respond_error(msg); + } else if (m_error == INVALID_POLY) { + string msg = "Unable to create patch "; + msg.append(m_path).append(": ").append("Invalid polyphony respondered."); + m_responder->respond_error(msg); + } else { + m_responder->respond_error("Unable to load patch."); + } + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/CreatePatchEvent.h b/src/libs/engine/events/CreatePatchEvent.h new file mode 100644 index 00000000..507d03c7 --- /dev/null +++ b/src/libs/engine/events/CreatePatchEvent.h @@ -0,0 +1,64 @@ +/* This file is part of Om. Copyright (C) 2006 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 CREATEPATCHEVENT_H +#define CREATEPATCHEVENT_H + +#include "util/Path.h" +#include "QueuedEvent.h" +#include <string> +using std::string; + +template<typename T> class Array; +template<typename T> class TreeNode; + +namespace Om { + +class Patch; +class Node; +class Plugin; + + +/** Creates a new Patch. + * + * \ingroup engine + */ +class CreatePatchEvent : public QueuedEvent +{ +public: + CreatePatchEvent(CountedPtr<Responder> responder, const string& path, int poly); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + enum ErrorType { NO_ERROR, OBJECT_EXISTS, PARENT_NOT_FOUND, INVALID_POLY }; + + Path m_path; + Patch* m_patch; + Patch* m_parent; + Array<Node*>* m_process_order; + TreeNode<Node*>* m_patch_treenode; + int m_poly; + ErrorType m_error; +}; + + +} // namespace Om + + +#endif // CREATEPATCHEVENT_H diff --git a/src/libs/engine/events/DSSIConfigureEvent.cpp b/src/libs/engine/events/DSSIConfigureEvent.cpp new file mode 100644 index 00000000..2ade4671 --- /dev/null +++ b/src/libs/engine/events/DSSIConfigureEvent.cpp @@ -0,0 +1,73 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DSSIConfigureEvent.h" +#include "Om.h" +#include "OmApp.h" +#include "Node.h" +#include "ClientBroadcaster.h" +#include "Plugin.h" +#include "ObjectStore.h" + +namespace Om { + + +DSSIConfigureEvent::DSSIConfigureEvent(CountedPtr<Responder> responder, const string& node_path, const string& key, const string& val) +: QueuedEvent(responder), + m_node_path(node_path), + m_key(key), + m_val(val), + m_node(NULL) +{ +} + + +void +DSSIConfigureEvent::pre_process() +{ + Node* node = om->object_store()->find_node(m_node_path); + + if (node != NULL && node->plugin()->type() == Plugin::DSSI) { + m_node = (DSSIPlugin*)node; + m_node->configure(m_key, m_val); + } + + QueuedEvent::pre_process(); +} + + +void +DSSIConfigureEvent::execute(samplecount offset) +{ + // Nothing. +} + + +void +DSSIConfigureEvent::post_process() +{ + if (m_node == NULL) { + cerr << "Unable to find DSSI node " << m_node_path << endl; + } else { + string key = "dssi-configure--"; + key += m_key; + om->client_broadcaster()->send_metadata_update(m_node_path, key, m_val); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DSSIConfigureEvent.h b/src/libs/engine/events/DSSIConfigureEvent.h new file mode 100644 index 00000000..00b4a134 --- /dev/null +++ b/src/libs/engine/events/DSSIConfigureEvent.h @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 DSSICONFIGUREEVENT_H +#define DSSICONFIGUREEVENT_H + +#include "QueuedEvent.h" +#include "DSSIPlugin.h" + +namespace Om { + + +/** Change of a 'configure' key/value pair for a DSSI plugin. + * + * \ingroup engine + */ +class DSSIConfigureEvent : public QueuedEvent +{ +public: + DSSIConfigureEvent(CountedPtr<Responder> responder, const string& node_path, const string& key, const string& val); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_node_path; + string m_key; + string m_val; + DSSIPlugin* m_node; +}; + + +} // namespace Om + +#endif // DSSICONFIGUREEVENT_H diff --git a/src/libs/engine/events/DSSIControlEvent.cpp b/src/libs/engine/events/DSSIControlEvent.cpp new file mode 100644 index 00000000..ea3e70ac --- /dev/null +++ b/src/libs/engine/events/DSSIControlEvent.cpp @@ -0,0 +1,68 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DSSIControlEvent.h" +#include "Om.h" +#include "OmApp.h" +#include "Node.h" +#include "Plugin.h" +#include "ObjectStore.h" + +namespace Om { + + +DSSIControlEvent::DSSIControlEvent(CountedPtr<Responder> responder, const string& node_path, int port_num, sample val) +: QueuedEvent(responder), + m_node_path(node_path), + m_port_num(port_num), + m_val(val), + m_node(NULL) +{ +} + + +void +DSSIControlEvent::pre_process() +{ + Node* node = om->object_store()->find_node(m_node_path); + + if (node->plugin()->type() != Plugin::DSSI) + m_node = NULL; + else + m_node = (DSSIPlugin*)node; + + QueuedEvent::pre_process(); +} + + +void +DSSIControlEvent::execute(samplecount offset) +{ + if (m_node != NULL) + m_node->set_control(m_port_num, m_val); +} + + +void +DSSIControlEvent::post_process() +{ + if (m_node == NULL) + std::cerr << "Unable to find DSSI node " << m_node_path << std::endl; +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DSSIControlEvent.h b/src/libs/engine/events/DSSIControlEvent.h new file mode 100644 index 00000000..30a5279e --- /dev/null +++ b/src/libs/engine/events/DSSIControlEvent.h @@ -0,0 +1,51 @@ +/* This file is part of Om. Copyright (C) 2006 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 DSSICONTROLEVENT_H +#define DSSICONTROLEVENT_H + +#include "QueuedEvent.h" +#include "DSSIPlugin.h" + +namespace Om { + + +/** A control change event for a DSSI plugin. + * + * This does essentially the same thing as a SetPortValueEvent. + * + * \ingroup engine + */ +class DSSIControlEvent : public QueuedEvent +{ +public: + DSSIControlEvent(CountedPtr<Responder> responder, const string& node_path, int port_num, sample val); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_node_path; + int m_port_num; + float m_val; + DSSIPlugin* m_node; +}; + + +} // namespace Om + +#endif // DSSICONTROLEVENT_H diff --git a/src/libs/engine/events/DSSIProgramEvent.cpp b/src/libs/engine/events/DSSIProgramEvent.cpp new file mode 100644 index 00000000..eb68ef77 --- /dev/null +++ b/src/libs/engine/events/DSSIProgramEvent.cpp @@ -0,0 +1,77 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DSSIProgramEvent.h" +#include <cstdio> +#include <iostream> +#include "Om.h" +#include "OmApp.h" +#include "Node.h" +#include "ClientBroadcaster.h" +#include "Plugin.h" +#include "ObjectStore.h" +using std::cout; using std::cerr; using std::endl; + + +namespace Om { + + +DSSIProgramEvent::DSSIProgramEvent(CountedPtr<Responder> responder, const string& node_path, int bank, int program) +: QueuedEvent(responder), + m_node_path(node_path), + m_bank(bank), + m_program(program), + m_node(NULL) +{ +} + + +void +DSSIProgramEvent::pre_process() +{ + Node* node = om->object_store()->find_node(m_node_path); + + if (node != NULL && node->plugin()->type() == Plugin::DSSI) + m_node = (DSSIPlugin*)node; + + QueuedEvent::pre_process(); +} + + +void +DSSIProgramEvent::execute(samplecount offset) +{ + if (m_node != NULL) + m_node->program(m_bank, m_program); +} + + +void +DSSIProgramEvent::post_process() +{ + if (m_node == NULL) { + cerr << "Unable to find DSSI node " << m_node_path << endl; + } else { + // sends program as metadata in the form bank/program + char* temp_buf = new char[16]; + snprintf(temp_buf, 16, "%d/%d", m_bank, m_program); + om->client_broadcaster()->send_metadata_update(m_node_path, "dssi-program", temp_buf); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DSSIProgramEvent.h b/src/libs/engine/events/DSSIProgramEvent.h new file mode 100644 index 00000000..152f3cb1 --- /dev/null +++ b/src/libs/engine/events/DSSIProgramEvent.h @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 DSSIPROGRAMEVENT_H +#define DSSIPROGRAMEVENT_H + +#include "QueuedEvent.h" +#include "DSSIPlugin.h" + +namespace Om { + + +/** A program change for a DSSI plugin. + * + * \ingroup engine + */ +class DSSIProgramEvent : public QueuedEvent +{ +public: + DSSIProgramEvent(CountedPtr<Responder> responder, const string& node_path, int bank, int program); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_node_path; + int m_bank; + int m_program; + DSSIPlugin* m_node; +}; + + +} // namespace Om + +#endif // DSSIPROGRAMEVENT_H diff --git a/src/libs/engine/events/DSSIUpdateEvent.cpp b/src/libs/engine/events/DSSIUpdateEvent.cpp new file mode 100644 index 00000000..5650dd63 --- /dev/null +++ b/src/libs/engine/events/DSSIUpdateEvent.cpp @@ -0,0 +1,80 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DSSIUpdateEvent.h" +#include <iostream> +#include "Node.h" +#include "ObjectStore.h" +#include "Om.h" +#include "OmApp.h" +#include "DSSIPlugin.h" +#include "Plugin.h" + +using std::cerr; using std::endl; + +namespace Om { + + +DSSIUpdateEvent::DSSIUpdateEvent(CountedPtr<Responder> responder, const string& path, const string& url) +: QueuedEvent(responder), + m_path(path), + m_url(url), + m_node(NULL) +{ +} + + +void +DSSIUpdateEvent::pre_process() +{ + Node* node = om->object_store()->find_node(m_path); + + if (node == NULL || node->plugin()->type() != Plugin::DSSI) { + m_node = NULL; + QueuedEvent::pre_process(); + return; + } else { + m_node = (DSSIPlugin*)node; + } + + QueuedEvent::pre_process(); +} + + +void +DSSIUpdateEvent::execute(samplecount offset) +{ + if (m_node != NULL) { + m_node->set_ui_url(m_url); + } + + QueuedEvent::execute(offset); +} + + +void +DSSIUpdateEvent::post_process() +{ + cerr << "DSSI update event: " << m_url << endl; + + if (m_node != NULL) { + m_node->send_update(); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DSSIUpdateEvent.h b/src/libs/engine/events/DSSIUpdateEvent.h new file mode 100644 index 00000000..cdd8851e --- /dev/null +++ b/src/libs/engine/events/DSSIUpdateEvent.h @@ -0,0 +1,54 @@ +/* This file is part of Om. Copyright (C) 2006 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 DSSIUPDATEEVENT_H +#define DSSIUPDATEEVENT_H + +#include "QueuedEvent.h" +#include <string> + +using std::string; + +namespace Om { + +class DSSIPlugin; + + +/** A DSSI "update" responder for a DSSI plugin/node. + * + * This sends all information about the plugin to the UI (over OSC). + * + * \ingroup engine + */ +class DSSIUpdateEvent : public QueuedEvent +{ +public: + DSSIUpdateEvent(CountedPtr<Responder> responder, const string& path, const string& url); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_path; + string m_url; + DSSIPlugin* m_node; +}; + + +} // namespace Om + +#endif // DSSIUPDATEEVENT_H diff --git a/src/libs/engine/events/DeactivateEvent.cpp b/src/libs/engine/events/DeactivateEvent.cpp new file mode 100644 index 00000000..48bff55a --- /dev/null +++ b/src/libs/engine/events/DeactivateEvent.cpp @@ -0,0 +1,54 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DeactivateEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" + +namespace Om { + + +DeactivateEvent::DeactivateEvent(CountedPtr<Responder> responder) +: QueuedEvent(responder) +{ +} + + +void +DeactivateEvent::pre_process() +{ + QueuedEvent::pre_process(); +} + + +void +DeactivateEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); +} + + +void +DeactivateEvent::post_process() +{ + m_responder->respond_ok(); + om->deactivate(); +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DeactivateEvent.h b/src/libs/engine/events/DeactivateEvent.h new file mode 100644 index 00000000..8401f332 --- /dev/null +++ b/src/libs/engine/events/DeactivateEvent.h @@ -0,0 +1,42 @@ +/* This file is part of Om. Copyright (C) 2006 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 DEACTIVATEEVENT_H +#define DEACTIVATEEVENT_H + +#include "QueuedEvent.h" + +namespace Om { + + +/** Deactivates the engine. + * + * \ingroup engine + */ +class DeactivateEvent : public QueuedEvent +{ +public: + DeactivateEvent(CountedPtr<Responder> responder); + + void pre_process(); + void execute(samplecount offset); + void post_process(); +}; + + +} // namespace Om + +#endif // DEACTIVATEEVENT_H diff --git a/src/libs/engine/events/DestroyEvent.cpp b/src/libs/engine/events/DestroyEvent.cpp new file mode 100644 index 00000000..3988195a --- /dev/null +++ b/src/libs/engine/events/DestroyEvent.cpp @@ -0,0 +1,168 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DestroyEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "Tree.h" +#include "Node.h" +#include "Plugin.h" +#include "InternalNode.h" +#include "DisconnectNodeEvent.h" +#include "DisconnectPortEvent.h" +#include "ClientBroadcaster.h" +#include "Maid.h" +#include "ObjectStore.h" +#include "util/Path.h" +#include "QueuedEventSource.h" +#include "Port.h" + +namespace Om { + + +DestroyEvent::DestroyEvent(CountedPtr<Responder> responder, const string& path, bool lock_mutex) +: QueuedEvent(responder, true), + m_path(path), + m_node(NULL), + m_patch_listnode(NULL), + m_store_treenode(NULL), + m_process_order(NULL), + m_disconnect_event(NULL), + m_parent_disconnect_event(NULL) +{ +} + + +DestroyEvent::DestroyEvent(CountedPtr<Responder> responder, Node* node, bool lock_mutex) +: QueuedEvent(responder, true), + m_path(node->path()), + m_node(node), + m_patch_listnode(NULL), + m_store_treenode(NULL), + m_process_order(NULL), + m_disconnect_event(NULL), + m_parent_disconnect_event(NULL) +{ +} + + +DestroyEvent::~DestroyEvent() +{ + delete m_disconnect_event; + delete m_parent_disconnect_event; +} + + +void +DestroyEvent::pre_process() +{ + if (m_node == NULL) { + OmObject* const obj = om->object_store()->find_node(m_path); + + if (obj != NULL && obj->as_node() != NULL) + m_node = obj->as_node(); + } + + if (m_node != NULL && m_path != "/") { + assert(m_node->parent_patch() != NULL); + m_patch_listnode = m_node->parent_patch()->remove_node(m_path.name()); + if (m_patch_listnode != NULL) { + assert(m_patch_listnode->elem() == m_node); + + m_node->remove_from_store(); + + if (m_node->providers()->size() != 0 || m_node->dependants()->size() != 0) { + m_disconnect_event = new DisconnectNodeEvent(m_node); + m_disconnect_event->pre_process(); + } + + // Create a recursive disconnect event for the parent port, if a bridge node + Port* parent_port = m_patch_listnode->elem()->as_port(); + if (parent_port != NULL) { // Bridge node + m_parent_disconnect_event = new DisconnectPortEvent(parent_port); + m_parent_disconnect_event->pre_process(); + } + + if (m_node->parent_patch()->process()) { + m_process_order = m_node->parent_patch()->build_process_order(); + // Remove node to be removed from the process order so it isn't executed by + // Patch::run and can safely be destroyed + //for (size_t i=0; i < m_process_order->size(); ++i) + // if (m_process_order->at(i) == m_node) + // m_process_order->at(i) = NULL; // ew, gap + +#ifdef DEBUG + // Be sure node is removed from process order, so it can be destroyed + for (size_t i=0; i < m_process_order->size(); ++i) + assert(m_process_order->at(i) != m_node); +#endif + } + } + } + + QueuedEvent::pre_process(); +} + + +void +DestroyEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_patch_listnode != NULL) { + m_node->remove_from_patch(); + + if (m_disconnect_event != NULL) + m_disconnect_event->execute(offset); + if (m_parent_disconnect_event != NULL) + m_parent_disconnect_event->execute(offset); + + if (m_node->parent_patch()->process_order() != NULL) + om->maid()->push(m_node->parent_patch()->process_order()); + m_node->parent_patch()->process_order(m_process_order); + } +} + + +void +DestroyEvent::post_process() +{ + m_source->unblock(); + + if (m_node == NULL) { + if (m_path == "/") + m_responder->respond_error("You can not destroy the root patch (/)"); + else + m_responder->respond_error("Could not find node to destroy"); + } else if (m_patch_listnode != NULL) { + m_node->deactivate(); + m_responder->respond_ok(); + if (m_disconnect_event != NULL) + m_disconnect_event->post_process(); + if (m_parent_disconnect_event != NULL) + m_parent_disconnect_event->post_process(); + om->client_broadcaster()->send_destroyed(m_path); + om->maid()->push(m_patch_listnode); + om->maid()->push(m_node); + } else { + m_responder->respond_error("Unable to destroy object"); + } +} + + +} // namespace Om diff --git a/src/libs/engine/events/DestroyEvent.h b/src/libs/engine/events/DestroyEvent.h new file mode 100644 index 00000000..fc579bf4 --- /dev/null +++ b/src/libs/engine/events/DestroyEvent.h @@ -0,0 +1,68 @@ +/* This file is part of Om. Copyright (C) 2006 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 DESTROYEVENT_H +#define DESTROYEVENT_H + +#include "util/Path.h" +#include "QueuedEvent.h" +#include <string> + +using std::string; + +template<typename T> class Array; +template<typename T> class ListNode; +template<typename T> class TreeNode; + +namespace Om { + +class OmObject; +class Patch; +class Node; +class Plugin; +class DisconnectNodeEvent; +class DisconnectPortEvent; + + +/** An event to remove and delete a Node. + * + * \ingroup engine + */ +class DestroyEvent : public QueuedEvent +{ +public: + DestroyEvent(CountedPtr<Responder> responder, const string& path, bool lock_mutex = true); + DestroyEvent(CountedPtr<Responder> responder, Node* node, bool lock_mutex = true); + ~DestroyEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + Path m_path; + Node* m_node; + ListNode<Node*>* m_patch_listnode; + TreeNode<OmObject*>* m_store_treenode; + Array<Node*>* m_process_order; // Patch's new process order + DisconnectNodeEvent* m_disconnect_event; + DisconnectPortEvent* m_parent_disconnect_event; // used for input/output nodes +}; + + +} // namespace Om + +#endif // DESTROYEVENT_H diff --git a/src/libs/engine/events/DisablePatchEvent.cpp b/src/libs/engine/events/DisablePatchEvent.cpp new file mode 100644 index 00000000..a772e6e9 --- /dev/null +++ b/src/libs/engine/events/DisablePatchEvent.cpp @@ -0,0 +1,70 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DisablePatchEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "util.h" +#include "ObjectStore.h" +#include "Port.h" + +namespace Om { + + +DisablePatchEvent::DisablePatchEvent(CountedPtr<Responder> responder, const string& patch_path) +: QueuedEvent(responder), + m_patch_path(patch_path), + m_patch(NULL) +{ +} + + +void +DisablePatchEvent::pre_process() +{ + m_patch = om->object_store()->find_patch(m_patch_path); + + QueuedEvent::pre_process(); +} + + +void +DisablePatchEvent::execute(samplecount offset) +{ + if (m_patch != NULL) + m_patch->process(false); + + QueuedEvent::execute(offset); +} + + +void +DisablePatchEvent::post_process() +{ + if (m_patch != NULL) { + m_responder->respond_ok(); + om->client_broadcaster()->send_patch_disable(m_patch_path); + } else { + m_responder->respond_error(string("Patch ") + m_patch_path + " not found"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DisablePatchEvent.h b/src/libs/engine/events/DisablePatchEvent.h new file mode 100644 index 00000000..f38f14af --- /dev/null +++ b/src/libs/engine/events/DisablePatchEvent.h @@ -0,0 +1,52 @@ +/* This file is part of Om. Copyright (C) 2006 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 DISABLEPATCHEVENT_H +#define DISABLEPATCHEVENT_H + +#include <string> +#include "QueuedEvent.h" + +using std::string; + +namespace Om { + +class Patch; + + +/** Disables a Patch's DSP processing. + * + * \ingroup engine + */ +class DisablePatchEvent : public QueuedEvent +{ +public: + DisablePatchEvent(CountedPtr<Responder> responder, const string& patch_path); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_patch_path; + Patch* m_patch; +}; + + +} // namespace Om + + +#endif // DISABLEPATCHEVENT_H diff --git a/src/libs/engine/events/DisconnectNodeEvent.cpp b/src/libs/engine/events/DisconnectNodeEvent.cpp new file mode 100644 index 00000000..17367dd4 --- /dev/null +++ b/src/libs/engine/events/DisconnectNodeEvent.cpp @@ -0,0 +1,140 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DisconnectNodeEvent.h" +#include <iostream> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Maid.h" +#include "List.h" +#include "Node.h" +#include "ConnectionBase.h" +#include "DisconnectionEvent.h" +#include "Port.h" +#include "Array.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "util.h" +#include "ObjectStore.h" +#include "util/Path.h" + +using std::cerr; using std::endl; + +namespace Om { + + +DisconnectNodeEvent::DisconnectNodeEvent(CountedPtr<Responder> responder, const string& node_path) +: QueuedEvent(responder), + m_node_path(node_path), + m_patch(NULL), + m_node(NULL), + m_succeeded(true), + m_lookup(true) +{ +} + + +/** Internal version, disconnects parent port as well (in the case of InputNode, etc). + */ +DisconnectNodeEvent::DisconnectNodeEvent(Node* node) +: QueuedEvent(), + m_node_path(""), + m_patch(node->parent_patch()), + m_node(node), + m_succeeded(true), + m_lookup(false) +{ +} + + +DisconnectNodeEvent::~DisconnectNodeEvent() +{ + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + delete (*i); +} + + +void +DisconnectNodeEvent::pre_process() +{ + typedef List<Connection*>::const_iterator ConnectionListIterator; + + // cerr << "Preparing disconnection event...\n"; + + if (m_lookup) { + m_patch = om->object_store()->find_patch(m_node_path.parent()); + + if (m_patch == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + m_node = om->object_store()->find_node(m_node_path); + + if (m_node == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + } + + Connection* c = NULL; + for (ConnectionListIterator i = m_patch->connections().begin(); i != m_patch->connections().end(); ++i) { + c = (*i); + if ((c->src_port()->parent_node() == m_node || c->dst_port()->parent_node() == m_node) && !c->pending_disconnection()) { + DisconnectionEvent* ev = new DisconnectionEvent(CountedPtr<Responder>(new Responder()), c->src_port(), c->dst_port()); + ev->pre_process(); + m_disconnection_events.push_back(new ListNode<DisconnectionEvent*>(ev)); + c->pending_disconnection(true); + } + } + + m_succeeded = true; + QueuedEvent::pre_process(); +} + + +void +DisconnectNodeEvent::execute(samplecount offset) +{ + if (m_succeeded) { + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + (*i)->execute(offset); + } + + QueuedEvent::execute(offset); +} + + +void +DisconnectNodeEvent::post_process() +{ + if (m_succeeded) { + m_responder->respond_ok(); + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + (*i)->post_process(); + } else { + m_responder->respond_error("Unable to disconnect all ports."); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DisconnectNodeEvent.h b/src/libs/engine/events/DisconnectNodeEvent.h new file mode 100644 index 00000000..a82fbaec --- /dev/null +++ b/src/libs/engine/events/DisconnectNodeEvent.h @@ -0,0 +1,68 @@ +/* This file is part of Om. Copyright (C) 2006 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 DISCONNECTNODEEVENT_H +#define DISCONNECTNODEEVENT_H + +#include <string> +#include "util/Path.h" +#include "QueuedEvent.h" +#include "List.h" +using std::string; + +namespace Om { + +class DisconnectionEvent; +class Patch; +class Node; +class Connection; +template <typename T> class ConnectionBase; +class Port; +template <typename T> class InputPort; +template <typename T> class OutputPort; + + +/** An event to disconnect all connections to a Node. + * + * \ingroup engine + */ +class DisconnectNodeEvent : public QueuedEvent +{ +public: + DisconnectNodeEvent(CountedPtr<Responder> responder, const string& node_path); + DisconnectNodeEvent(Node* node); + ~DisconnectNodeEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + Path m_node_path; + Patch* m_patch; + Node* m_node; + List<DisconnectionEvent*> m_disconnection_events; + + bool m_succeeded; + bool m_lookup; + bool m_disconnect_parent; +}; + + +} // namespace Om + + +#endif // DISCONNECTNODEEVENT_H diff --git a/src/libs/engine/events/DisconnectPortEvent.cpp b/src/libs/engine/events/DisconnectPortEvent.cpp new file mode 100644 index 00000000..a4c213d5 --- /dev/null +++ b/src/libs/engine/events/DisconnectPortEvent.cpp @@ -0,0 +1,145 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DisconnectPortEvent.h" +#include <iostream> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Maid.h" +#include "List.h" +#include "Node.h" +#include "Connection.h" +#include "DisconnectionEvent.h" +#include "Port.h" +#include "Array.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "util.h" +#include "ObjectStore.h" +#include "util/Path.h" + +using std::cerr; using std::endl; + +namespace Om { + + +DisconnectPortEvent::DisconnectPortEvent(CountedPtr<Responder> responder, const string& port_path) +: QueuedEvent(responder), + m_port_path(port_path), + m_patch(NULL), + m_port(NULL), + m_process_order(NULL), + m_succeeded(true), + m_lookup(true) +{ +} + + +DisconnectPortEvent::DisconnectPortEvent(Port* port) +: QueuedEvent(), + m_port_path(""), + m_patch((port->parent_node() == NULL) ? NULL : port->parent_node()->parent_patch()), + m_port(port), + m_process_order(NULL), + m_succeeded(true), + m_lookup(false) +{ + //cerr << "DisconnectPortEvent()\n"; +} + + +DisconnectPortEvent::~DisconnectPortEvent() +{ + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + delete (*i); +} + + +void +DisconnectPortEvent::pre_process() +{ + // cerr << "Preparing disconnection event...\n"; + + if (m_lookup) { + m_patch = om->object_store()->find_patch(m_port_path.parent().parent()); + + if (m_patch == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + m_port = om->object_store()->find_port(m_port_path); + + if (m_port == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + } + + if (m_patch == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + Connection* c = NULL; + for (List<Connection*>::const_iterator i = m_patch->connections().begin(); i != m_patch->connections().end(); ++i) { + c = (*i); + if ((c->src_port() == m_port || c->dst_port() == m_port) && !c->pending_disconnection()) { + DisconnectionEvent* ev = new DisconnectionEvent(CountedPtr<Responder>(new Responder()), c->src_port(), c->dst_port()); + ev->pre_process(); + m_disconnection_events.push_back(new ListNode<DisconnectionEvent*>(ev)); + c->pending_disconnection(true); + } + } + + m_succeeded = true; + QueuedEvent::pre_process(); +} + + +void +DisconnectPortEvent::execute(samplecount offset) +{ + if (m_succeeded) { + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + (*i)->execute(offset); + } + + QueuedEvent::execute(offset); +} + + +void +DisconnectPortEvent::post_process() +{ + if (m_succeeded) { + m_responder->respond_ok(); + for (List<DisconnectionEvent*>::iterator i = m_disconnection_events.begin(); i != m_disconnection_events.end(); ++i) + (*i)->post_process(); + } else { + m_responder->respond_error("Unable to disconnect port."); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DisconnectPortEvent.h b/src/libs/engine/events/DisconnectPortEvent.h new file mode 100644 index 00000000..e8de4120 --- /dev/null +++ b/src/libs/engine/events/DisconnectPortEvent.h @@ -0,0 +1,70 @@ +/* This file is part of Om. Copyright (C) 2006 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 DISCONNECTPORTEVENT_H +#define DISCONNECTPORTEVENT_H + +#include <string> +#include "util/Path.h" +#include "QueuedEvent.h" +#include "List.h" + +template <typename T> class Array; + +namespace Om { + + +class Patch; +class Node; +class Connection; +class Port; +class DisconnectionEvent; + +using std::string; + + +/** An event to disconnect all connections to a Port. + * + * \ingroup engine + */ +class DisconnectPortEvent : public QueuedEvent +{ +public: + DisconnectPortEvent(CountedPtr<Responder> responder, const string& port_path); + DisconnectPortEvent(Port* port); + ~DisconnectPortEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + Path m_port_path; + Patch* m_patch; + Port* m_port; + List<DisconnectionEvent*> m_disconnection_events; + + Array<Node*>* m_process_order; // Patch's new process order + + bool m_succeeded; + bool m_lookup; +}; + + +} // namespace Om + + +#endif // DISCONNECTPORTEVENT_H diff --git a/src/libs/engine/events/DisconnectionEvent.cpp b/src/libs/engine/events/DisconnectionEvent.cpp new file mode 100644 index 00000000..e7d06eab --- /dev/null +++ b/src/libs/engine/events/DisconnectionEvent.cpp @@ -0,0 +1,295 @@ +/* This file is part of Om. Copyright (C) 2006 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 "DisconnectionEvent.h" +#include <string> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ConnectionBase.h" +#include "InputPort.h" +#include "OutputPort.h" +#include "Patch.h" +#include "ClientBroadcaster.h" +#include "Port.h" +#include "PortInfo.h" +#include "Maid.h" +#include "ObjectStore.h" +#include "util/Path.h" + +using std::string; +namespace Om { + + +//// DisconnectionEvent //// + + +DisconnectionEvent::DisconnectionEvent(CountedPtr<Responder> responder, const string& src_port_path, const string& dst_port_path) +: QueuedEvent(responder), + m_src_port_path(src_port_path), + m_dst_port_path(dst_port_path), + m_patch(NULL), + m_src_port(NULL), + m_dst_port(NULL), + m_lookup(true), + m_typed_event(NULL), + m_error(NO_ERROR) +{ +} + + +DisconnectionEvent::DisconnectionEvent(CountedPtr<Responder> responder, Port* const src_port, Port* const dst_port) +: QueuedEvent(responder), + m_src_port_path(src_port->path()), + m_dst_port_path(dst_port->path()), + m_patch(src_port->parent_node()->parent_patch()), + m_src_port(src_port), + m_dst_port(dst_port), + m_lookup(false), + m_typed_event(NULL), + m_error(NO_ERROR) +{ + assert(src_port->port_info()->is_output()); + assert(dst_port->port_info()->is_input()); + assert(src_port->port_info()->type() == dst_port->port_info()->type()); + assert(src_port->parent_node()->parent_patch() + == dst_port->parent_node()->parent_patch()); +} + +DisconnectionEvent::~DisconnectionEvent() +{ + delete m_typed_event; +} + + +void +DisconnectionEvent::pre_process() +{ + if (m_lookup) { + if (m_src_port_path.parent().parent() != m_dst_port_path.parent().parent()) { + m_error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + /*m_patch = om->object_store()->find_patch(m_src_port_path.parent().parent()); + + if (m_patch == NULL) { + m_error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + }*/ + + Port* port1 = om->object_store()->find_port(m_src_port_path); + Port* port2 = om->object_store()->find_port(m_dst_port_path); + + if (port1 == NULL || port2 == NULL) { + m_error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (port1->port_info()->type() != port2->port_info()->type()) { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + if (port1->port_info()->is_output() && port2->port_info()->is_input()) { + m_src_port = port1; + m_dst_port = port2; + } else if (port2->port_info()->is_output() && port1->port_info()->is_input()) { + m_src_port = port2; + m_dst_port = port1; + } else { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + } + + // Create the typed event to actually do the work + const PortType type = m_src_port->port_info()->type(); + if (type == AUDIO || type == CONTROL) { + m_typed_event = new TypedDisconnectionEvent<sample>(m_responder, + (OutputPort<sample>*)m_src_port, (InputPort<sample>*)m_dst_port); + } else if (type == MIDI) { + m_typed_event = new TypedDisconnectionEvent<MidiMessage>(m_responder, + (OutputPort<MidiMessage>*)m_src_port, (InputPort<MidiMessage>*)m_dst_port); + } else { + m_error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + m_typed_event->pre_process(); + + QueuedEvent::pre_process(); +} + + +void +DisconnectionEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_error == NO_ERROR) + m_typed_event->execute(offset); +} + + +void +DisconnectionEvent::post_process() +{ + if (m_error == NO_ERROR) { + m_typed_event->post_process(); + } else { + // FIXME: better error messages + string msg = "Unable to make connection "; + msg.append(m_src_port_path + " -> " + m_dst_port_path); + m_responder->respond_error(msg); + } +} + + + +//// TypedDisconnectionEvent //// + + +template <typename T> +TypedDisconnectionEvent<T>::TypedDisconnectionEvent(CountedPtr<Responder> responder, OutputPort<T>* src_port, InputPort<T>* dst_port) +: QueuedEvent(responder), + m_src_port(src_port), + m_dst_port(dst_port), + m_patch(NULL), + m_process_order(NULL), + m_succeeded(true) +{ + assert(src_port != NULL); + assert(dst_port != NULL); +} + +template <typename T> +TypedDisconnectionEvent<T>::~TypedDisconnectionEvent() +{ + // FIXME: haaaack, prevent a double delete + // this class is unusable by anything other than DisconnectionEvent because of this + //m_responder = NULL; +} + + +template <typename T> +void +TypedDisconnectionEvent<T>::pre_process() +{ + if (!m_dst_port->is_connected_to(m_src_port)) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + Node* const src_node = m_src_port->parent_node(); + Node* const dst_node = m_dst_port->parent_node(); + + m_patch = src_node->parent_patch(); + + if (src_node == NULL || dst_node == NULL) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + if (src_node->parent() != m_patch || dst_node->parent() != m_patch) { + m_succeeded = false; + QueuedEvent::pre_process(); + return; + } + + bool removed = false; + + for (List<Node*>::iterator i = dst_node->providers()->begin(); i != dst_node->providers()->end(); ++i) + if ((*i) == src_node) { + delete dst_node->providers()->remove(i); + removed = true; + break; + } + assert(removed); + removed = false; + + for (List<Node*>::iterator i = src_node->dependants()->begin(); i != src_node->dependants()->end(); ++i) + if ((*i) == dst_node) { + delete src_node->dependants()->remove(i); + removed = true; + break; + } + assert(removed); + + if (m_patch->process()) + m_process_order = m_patch->build_process_order(); + + m_succeeded = true; + QueuedEvent::pre_process(); +} + + +template <typename T> +void +TypedDisconnectionEvent<T>::execute(samplecount offset) +{ + if (m_succeeded) { + + ListNode<ConnectionBase<T>*>* const port_connection + = m_dst_port->remove_connection(m_src_port); + + if (port_connection != NULL) { + ListNode<Connection*>* const patch_connection + = m_patch->remove_connection(m_src_port, m_dst_port); + + assert((Connection*)port_connection->elem() == patch_connection->elem()); + + // Clean up both the list node and the connection itself... + om->maid()->push(port_connection); + om->maid()->push(patch_connection); + om->maid()->push(port_connection->elem()); + + if (m_patch->process_order() != NULL) + om->maid()->push(m_patch->process_order()); + m_patch->process_order(m_process_order); + } else { + m_succeeded = false; // Ports weren't connected + } + } + QueuedEvent::execute(offset); +} + + +template <typename T> +void +TypedDisconnectionEvent<T>::post_process() +{ + if (m_succeeded) { + + m_responder->respond_ok(); + + om->client_broadcaster()->send_disconnection(m_src_port->path(), m_dst_port->path()); + } else { + m_responder->respond_error("Unable to disconnect ports."); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/DisconnectionEvent.h b/src/libs/engine/events/DisconnectionEvent.h new file mode 100644 index 00000000..bbb70f3c --- /dev/null +++ b/src/libs/engine/events/DisconnectionEvent.h @@ -0,0 +1,106 @@ +/* This file is part of Om. Copyright (C) 2006 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 DISCONNECTIONEVENT_H +#define DISCONNECTIONEVENT_H + +#include <string> +#include "util/Path.h" +#include "QueuedEvent.h" +#include "util/types.h" +using std::string; + +template <typename T> class ListNode; +template <typename T> class Array; + +namespace Om { + +class Patch; +class Node; +class Connection; +class MidiMessage; +class Port; +template <typename T> class ConnectionBase; +template <typename T> class InputPort; +template <typename T> class OutputPort; +template <typename T> class TypedDisconnectionEvent; // helper, defined below + + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class DisconnectionEvent : public QueuedEvent +{ +public: + DisconnectionEvent(CountedPtr<Responder> responder, const string& src_port_path, const string& dst_port_path); + DisconnectionEvent(CountedPtr<Responder> responder, Port* const src_port, Port* const dst_port); + ~DisconnectionEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + + enum ErrorType { NO_ERROR, PARENT_PATCH_DIFFERENT, PORT_NOT_FOUND, TYPE_MISMATCH }; + + Path m_src_port_path; + Path m_dst_port_path; + + Patch* m_patch; + Port* m_src_port; + Port* m_dst_port; + + bool m_lookup; + QueuedEvent* m_typed_event; + + ErrorType m_error; +}; + + +/** Templated DisconnectionEvent. + * + * Intended to be called from DisconnectionEvent so callers (ie OSCReceiver) + * can use DisconnectionEvent without knowing anything about types (which + * they can't, since all they have is Port paths). + */ +template <typename T> +class TypedDisconnectionEvent : public QueuedEvent +{ +public: + TypedDisconnectionEvent(CountedPtr<Responder> responder, OutputPort<T>* src_port, InputPort<T>* dst_port); + ~TypedDisconnectionEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + OutputPort<T>* m_src_port; + InputPort<T>* m_dst_port; + + Patch* m_patch; + Array<Node*>* m_process_order; ///< New process order for Patch + + bool m_succeeded; +}; + + + +} // namespace Om + +#endif // DISCONNECTIONEVENT_H diff --git a/src/libs/engine/events/EnablePatchEvent.cpp b/src/libs/engine/events/EnablePatchEvent.cpp new file mode 100644 index 00000000..6af3b844 --- /dev/null +++ b/src/libs/engine/events/EnablePatchEvent.cpp @@ -0,0 +1,82 @@ +/* This file is part of Om. Copyright (C) 2006 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 "EnablePatchEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "Patch.h" +#include "util.h" +#include "ClientBroadcaster.h" +#include "ObjectStore.h" + +namespace Om { + + +EnablePatchEvent::EnablePatchEvent(CountedPtr<Responder> responder, const string& patch_path) +: QueuedEvent(responder), + m_patch_path(patch_path), + m_patch(NULL), + m_process_order(NULL) +{ +} + + +void +EnablePatchEvent::pre_process() +{ + m_patch = om->object_store()->find_patch(m_patch_path); + + if (m_patch != NULL) { + /* Any event that requires a new process order will set the patch's + * process order to NULL if it is executed when the patch is not + * active. So, if the PO is NULL, calculate it here */ + if (m_patch->process_order() == NULL) + m_process_order = m_patch->build_process_order(); + } + + QueuedEvent::pre_process(); +} + + +void +EnablePatchEvent::execute(samplecount offset) +{ + if (m_patch != NULL) { + m_patch->process(true); + + if (m_patch->process_order() == NULL) + m_patch->process_order(m_process_order); + } + + QueuedEvent::execute(offset); +} + + +void +EnablePatchEvent::post_process() +{ + if (m_patch != NULL) { + m_responder->respond_ok(); + om->client_broadcaster()->send_patch_enable(m_patch_path); + } else { + m_responder->respond_error(string("Patch ") + m_patch_path + " not found"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/EnablePatchEvent.h b/src/libs/engine/events/EnablePatchEvent.h new file mode 100644 index 00000000..f3d22e69 --- /dev/null +++ b/src/libs/engine/events/EnablePatchEvent.h @@ -0,0 +1,56 @@ +/* This file is part of Om. Copyright (C) 2006 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 ENABLEPATCHEVENT_H +#define ENABLEPATCHEVENT_H + +#include <string> +#include "QueuedEvent.h" + +using std::string; + +template <typename T> class Array; + +namespace Om { + +class Patch; +class Node; + + +/** Enables a patch's DSP processing. + * + * \ingroup engine + */ +class EnablePatchEvent : public QueuedEvent +{ +public: + EnablePatchEvent(CountedPtr<Responder> responder, const string& patch_path); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_patch_path; + Patch* m_patch; + Array<Node*>* m_process_order; // Patch's new process order +}; + + +} // namespace Om + + +#endif // ENABLEPATCHEVENT_H diff --git a/src/libs/engine/events/LashRestoreDoneEvent.h b/src/libs/engine/events/LashRestoreDoneEvent.h new file mode 100644 index 00000000..dfc5b120 --- /dev/null +++ b/src/libs/engine/events/LashRestoreDoneEvent.h @@ -0,0 +1,54 @@ +/* This file is part of Om. Copyright (C) 2006 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 LASHRESTOREDONEEVENT_H +#define LASHRESTOREDONEEVENT_H + +#include "QueuedEvent.h" +#include "LashDriver.h" +#include "util/types.h" + +#include <iostream> +using std::cerr; + +namespace Om { + +class Port; + + +/** Notification from a client that the LASH session is finished restoring. + * + * This is a bit of a hack, but needed to defer notifying LASH of our + * existance until all ports are created. + * + * \ingroup engine + */ +class LashRestoreDoneEvent : public QueuedEvent +{ +public: + LashRestoreDoneEvent(CountedPtr<Responder> responder) : QueuedEvent(responder) {} + + void post_process() + { + m_responder->respond_ok(); + lash_driver->restore_finished(); + } +}; + + +} // namespace Om + +#endif // LASHRESTOREDONEEVENT_H diff --git a/src/libs/engine/events/LoadPluginsEvent.cpp b/src/libs/engine/events/LoadPluginsEvent.cpp new file mode 100644 index 00000000..656e9010 --- /dev/null +++ b/src/libs/engine/events/LoadPluginsEvent.cpp @@ -0,0 +1,44 @@ +/* This file is part of Om. Copyright (C) 2006 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 "LoadPluginsEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "NodeFactory.h" + +namespace Om { + + +LoadPluginsEvent::LoadPluginsEvent(CountedPtr<Responder> responder) +: QueuedEvent(responder) +{ +} + + +void +LoadPluginsEvent::post_process() +{ + // Why is this done here and not in pre_process()??? + om->node_factory()->load_plugins(); + m_responder->respond_ok(); + + //cerr << "Load plugins post finished\n"; +} + + +} // namespace Om + diff --git a/src/libs/engine/events/LoadPluginsEvent.h b/src/libs/engine/events/LoadPluginsEvent.h new file mode 100644 index 00000000..b69dbb38 --- /dev/null +++ b/src/libs/engine/events/LoadPluginsEvent.h @@ -0,0 +1,40 @@ +/* This file is part of Om. Copyright (C) 2006 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 LOADPLUGINSEVENT_H +#define LOADPLUGINSEVENT_H + +#include "QueuedEvent.h" + +namespace Om { + + +/** Loads all plugins into the internal plugin database (in NodeFactory). + * + * \ingroup engine + */ +class LoadPluginsEvent : public QueuedEvent +{ +public: + LoadPluginsEvent(CountedPtr<Responder> responder); + + void post_process(); +}; + + +} // namespace Om + +#endif // LOADPLUGINSEVENT_H diff --git a/src/libs/engine/events/Makefile.am b/src/libs/engine/events/Makefile.am new file mode 100644 index 00000000..5b29e12b --- /dev/null +++ b/src/libs/engine/events/Makefile.am @@ -0,0 +1,67 @@ +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = \ + events/RegisterClientEvent.h \ + events/RegisterClientEvent.cpp \ + events/UnregisterClientEvent.h \ + events/UnregisterClientEvent.cpp \ + events/PingQueuedEvent.h \ + events/ActivateEvent.h \ + events/ActivateEvent.cpp \ + events/DeactivateEvent.h \ + events/DeactivateEvent.cpp \ + events/SetPortValueEvent.h \ + events/SetPortValueEvent.cpp \ + events/SetPortValueQueuedEvent.h \ + events/SetPortValueQueuedEvent.cpp \ + events/NoteOnEvent.h \ + events/NoteOnEvent.cpp \ + events/NoteOffEvent.h \ + events/NoteOffEvent.cpp \ + events/AllNotesOffEvent.h \ + events/AllNotesOffEvent.cpp \ + events/ConnectionEvent.h \ + events/ConnectionEvent.cpp \ + events/DisconnectionEvent.h \ + events/DisconnectionEvent.cpp \ + events/DisconnectNodeEvent.h \ + events/DisconnectNodeEvent.cpp \ + events/DisconnectPortEvent.h \ + events/DisconnectPortEvent.cpp \ + events/DestroyEvent.h \ + events/DestroyEvent.cpp \ + events/AddNodeEvent.h \ + events/AddNodeEvent.cpp \ + events/SetMetadataEvent.h \ + events/SetMetadataEvent.cpp \ + events/RequestMetadataEvent.h \ + events/RequestMetadataEvent.cpp \ + events/RequestPortValueEvent.h \ + events/RequestPortValueEvent.cpp \ + events/RequestAllObjectsEvent.h \ + events/RequestAllObjectsEvent.cpp \ + events/RequestPluginsEvent.h \ + events/RequestPluginsEvent.cpp \ + events/CreatePatchEvent.h \ + events/CreatePatchEvent.cpp \ + events/LoadPluginsEvent.h \ + events/LoadPluginsEvent.cpp \ + events/EnablePatchEvent.h \ + events/EnablePatchEvent.cpp \ + events/DisablePatchEvent.h \ + events/DisablePatchEvent.cpp \ + events/ClearPatchEvent.h \ + events/ClearPatchEvent.cpp \ + events/RenameEvent.h \ + events/RenameEvent.cpp \ + events/MidiLearnEvent.h \ + events/MidiLearnEvent.cpp \ + DSSIConfigureEvent.cpp \ + DSSIConfigureEvent.h \ + DSSIControlEvent.cpp \ + DSSIControlEvent.h \ + DSSIProgramEvent.cpp \ + DSSIProgramEvent.h \ + DSSIUpdateEvent.cpp \ + DSSIUpdateEvent.h + diff --git a/src/libs/engine/events/MidiLearnEvent.cpp b/src/libs/engine/events/MidiLearnEvent.cpp new file mode 100644 index 00000000..63ad82fc --- /dev/null +++ b/src/libs/engine/events/MidiLearnEvent.cpp @@ -0,0 +1,88 @@ +/* This file is part of Om. Copyright (C) 2006 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 "MidiLearnEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "Node.h" +#include "MidiControlNode.h" +#include "ClientBroadcaster.h" + +namespace Om { + + +// MidiLearnResponseEvent + +void +MidiLearnResponseEvent::post_process() +{ + om->client_broadcaster()->send_control_change(m_port_path, m_value); +} + + + +// MidiLearnEvent + +MidiLearnEvent::MidiLearnEvent(CountedPtr<Responder> responder, const string& node_path) +: QueuedEvent(responder), + m_node_path(node_path), + m_node(NULL), + m_response_event(NULL) +{ +} + + +void +MidiLearnEvent::pre_process() +{ + m_node = om->object_store()->find_node(m_node_path); + m_response_event = new MidiLearnResponseEvent(m_node_path + "/Controller Number"); + + QueuedEvent::pre_process(); +} + + +void +MidiLearnEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + // FIXME: this isn't very good at all. + if (m_node != NULL && m_node->plugin()->type() == Plugin::Internal + && m_node->plugin()->plug_label() == "midi_control_in") { + ((MidiControlNode*)m_node)->learn(m_response_event); + } +} + + +void +MidiLearnEvent::post_process() +{ + if (m_node != NULL) { + m_responder->respond_ok(); + } else { + string msg = "Did not find node '"; + msg.append(m_node_path).append("' for MIDI learn."); + m_responder->respond_error(msg); + } +} + + +} // namespace Om + + diff --git a/src/libs/engine/events/MidiLearnEvent.h b/src/libs/engine/events/MidiLearnEvent.h new file mode 100644 index 00000000..793e675a --- /dev/null +++ b/src/libs/engine/events/MidiLearnEvent.h @@ -0,0 +1,84 @@ +/* This file is part of Om. Copyright (C) 2006 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 MIDILEARNEVENT_H +#define MIDILEARNEVENT_H + +#include "QueuedEvent.h" +#include "MidiControlNode.h" +#include "util/types.h" +#include <string> +using std::string; + +namespace Om { + +class Node; +class ControlChangeEvent; + + +/** Response event for a MIDI learn. + * + * This is a trivial event that sends a control change in it's post_process + * method (used by MidiLearnEvent to notify clients when the learn happens) + */ +class MidiLearnResponseEvent : public Event +{ +public: + MidiLearnResponseEvent(const string& port_path) + : Event(CountedPtr<Responder>(NULL)), + m_port_path(port_path), + m_value(0.0f) + {} + + void set_value(sample val) { m_value = val; } + void post_process(); + +private: + string m_port_path; + sample m_value; +}; + + + +/** A MIDI learn event. + * + * This creates a MidiLearnResponseEvent and passes it to the learning node, which + * will push it to the post-processor once the learn happens in order to reply + * to the client with the new port (learned controller) value. + * + * \ingroup engine + */ +class MidiLearnEvent : public QueuedEvent +{ +public: + MidiLearnEvent(CountedPtr<Responder> responder, const string& node_path); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_node_path; + Node* m_node; + + /// Event to respond with when learned + MidiLearnResponseEvent* m_response_event; +}; + + +} // namespace Om + +#endif // MIDILEARNEVENT_H diff --git a/src/libs/engine/events/NoteOffEvent.cpp b/src/libs/engine/events/NoteOffEvent.cpp new file mode 100644 index 00000000..fde2e520 --- /dev/null +++ b/src/libs/engine/events/NoteOffEvent.cpp @@ -0,0 +1,78 @@ +/* This file is part of Om. Copyright (C) 2006 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 "NoteOffEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "Node.h" +#include "MidiNoteNode.h" +#include "MidiTriggerNode.h" + +namespace Om { + + +/** Note off with patch explicitly passed - triggered by MIDI. + */ +NoteOffEvent::NoteOffEvent(CountedPtr<Responder> responder, Node* node, uchar note_num) +: Event(responder), + m_node(node), + m_note_num(note_num) +{ +} + + +/** Note off event with lookup - triggered by OSC. + */ +NoteOffEvent::NoteOffEvent(CountedPtr<Responder> responder, const string& node_path, uchar note_num) +: Event(responder), + m_node(NULL), + m_node_path(node_path), + m_note_num(note_num) +{ +} + + +void +NoteOffEvent::execute(samplecount offset) +{ + if (m_node == NULL && m_node_path != "") + m_node = om->object_store()->find_node(m_node_path); + + // FIXME: this isn't very good at all. + if (m_node != NULL && m_node->plugin()->type() == Plugin::Internal) { + if (m_node->plugin()->plug_label() == "note_in") + ((MidiNoteNode*)m_node)->note_off(m_note_num, offset); + else if (m_node->plugin()->plug_label() == "trigger_in") + ((MidiTriggerNode*)m_node)->note_off(m_note_num, offset); + } +} + + +void +NoteOffEvent::post_process() +{ + if (m_node != NULL) + m_responder->respond_ok(); + else + m_responder->respond_error("Did not find node for note_off"); +} + + +} // namespace Om + + diff --git a/src/libs/engine/events/NoteOffEvent.h b/src/libs/engine/events/NoteOffEvent.h new file mode 100644 index 00000000..edd0b373 --- /dev/null +++ b/src/libs/engine/events/NoteOffEvent.h @@ -0,0 +1,52 @@ +/* This file is part of Om. Copyright (C) 2006 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 NOTEOFFEVENT_H +#define NOTEOFFEVENT_H + +#include "Event.h" +#include "util/types.h" +#include <string> +using std::string; + +namespace Om { + +class Node; + + +/** A note off event. + * + * \ingroup engine + */ +class NoteOffEvent : public Event +{ +public: + NoteOffEvent(CountedPtr<Responder> responder, Node* node, uchar note_num); + NoteOffEvent(CountedPtr<Responder> responder, const string& node_path, uchar note_num); + + void execute(samplecount offset); + void post_process(); + +private: + Node* m_node; + string m_node_path; + uchar m_note_num; +}; + + +} // namespace Om + +#endif // NOTEOFFEVENT_H diff --git a/src/libs/engine/events/NoteOnEvent.cpp b/src/libs/engine/events/NoteOnEvent.cpp new file mode 100644 index 00000000..9688af79 --- /dev/null +++ b/src/libs/engine/events/NoteOnEvent.cpp @@ -0,0 +1,89 @@ +/* This file is part of Om. Copyright (C) 2006 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 "NoteOnEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectStore.h" +#include "Node.h" +#include "MidiNoteNode.h" +#include "MidiTriggerNode.h" +#include "Plugin.h" + +namespace Om { + + +/** Note on with Patch explicitly passed. + * + * Used to be triggered by MIDI. Not used anymore. + */ +NoteOnEvent::NoteOnEvent(CountedPtr<Responder> responder, Node* patch, uchar note_num, uchar velocity) +: Event(responder), + m_node(patch), + m_note_num(note_num), + m_velocity(velocity), + m_is_osc_triggered(false) +{ +} + + +/** Note on with Node lookup. + * + * Triggered by OSC. + */ +NoteOnEvent::NoteOnEvent(CountedPtr<Responder> responder, const string& node_path, uchar note_num, uchar velocity) +: Event(responder), + m_node(NULL), + m_node_path(node_path), + m_note_num(note_num), + m_velocity(velocity), + m_is_osc_triggered(true) +{ +} + + +void +NoteOnEvent::execute(samplecount offset) +{ + // Lookup if neccessary + if (m_is_osc_triggered) + m_node = om->object_store()->find_node(m_node_path); + + // FIXME: this isn't very good at all. + if (m_node != NULL && m_node->plugin()->type() == Plugin::Internal) { + if (m_node->plugin()->plug_label() == "note_in") + ((MidiNoteNode*)m_node)->note_on(m_note_num, m_velocity, offset); + else if (m_node->plugin()->plug_label() == "trigger_in") + ((MidiTriggerNode*)m_node)->note_on(m_note_num, m_velocity, offset); + } +} + + +void +NoteOnEvent::post_process() +{ + if (m_is_osc_triggered) { + if (m_node != NULL) + m_responder->respond_ok(); + else + m_responder->respond_error("Did not find node for note_on"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/NoteOnEvent.h b/src/libs/engine/events/NoteOnEvent.h new file mode 100644 index 00000000..1e09e450 --- /dev/null +++ b/src/libs/engine/events/NoteOnEvent.h @@ -0,0 +1,54 @@ +/* This file is part of Om. Copyright (C) 2006 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 NOTEONEVENT_H +#define NOTEONEVENT_H + +#include "Event.h" +#include "util/types.h" +#include <string> +using std::string; + +namespace Om { + +class Node; + + +/** A note on event. + * + * \ingroup engine + */ +class NoteOnEvent : public Event +{ +public: + NoteOnEvent(CountedPtr<Responder> responder, Node* patch, uchar note_num, uchar velocity); + NoteOnEvent(CountedPtr<Responder> responder, const string& node_path, uchar note_num, uchar velocity); + + void execute(samplecount offset); + void post_process(); + +private: + Node* m_node; + string m_node_path; + uchar m_note_num; + uchar m_velocity; + bool m_is_osc_triggered; +}; + + +} // namespace Om + +#endif // NOTEONEVENT_H diff --git a/src/libs/engine/events/PingQueuedEvent.h b/src/libs/engine/events/PingQueuedEvent.h new file mode 100644 index 00000000..cfba7058 --- /dev/null +++ b/src/libs/engine/events/PingQueuedEvent.h @@ -0,0 +1,44 @@ +/* This file is part of Om. Copyright (C) 2006 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 PINGQUEUEDEVENT_H +#define PINGQUEUEDEVENT_H + +#include "QueuedEvent.h" +#include "util/types.h" +#include "Responder.h" + +namespace Om { + +class Port; + + +/** A "blocking" ping that travels through the event queue before responding. + * + * \ingroup engine + */ +class PingQueuedEvent : public QueuedEvent +{ +public: + PingQueuedEvent(CountedPtr<Responder> responder) : QueuedEvent(responder) {} + + void post_process() { m_responder->respond_ok(); } +}; + + +} // namespace Om + +#endif // PINGQUEUEDEVENT_H diff --git a/src/libs/engine/events/RegisterClientEvent.cpp b/src/libs/engine/events/RegisterClientEvent.cpp new file mode 100644 index 00000000..ca7dd1f6 --- /dev/null +++ b/src/libs/engine/events/RegisterClientEvent.cpp @@ -0,0 +1,53 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RegisterClientEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" + +namespace Om { + + +RegisterClientEvent::RegisterClientEvent(CountedPtr<Responder> responder, + ClientKey key, + CountedPtr<ClientInterface> client) +: QueuedEvent(responder) +, _key(key) +, _client(client) +{ +} + + +void +RegisterClientEvent::pre_process() +{ + om->client_broadcaster()->register_client(_key, _client); + + QueuedEvent::pre_process(); +} + + +void +RegisterClientEvent::post_process() +{ + m_responder->respond_ok(); +} + + +} // namespace Om + diff --git a/src/libs/engine/events/RegisterClientEvent.h b/src/libs/engine/events/RegisterClientEvent.h new file mode 100644 index 00000000..f4b6b60f --- /dev/null +++ b/src/libs/engine/events/RegisterClientEvent.h @@ -0,0 +1,53 @@ +/* This file is part of Om. Copyright (C) 2006 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 REGISTERCLIENTEVENT_H +#define REGISTERCLIENTEVENT_H + +#include "QueuedEvent.h" +#include "interface/ClientKey.h" +#include "interface/ClientInterface.h" +#include <string> +using std::string; +using Om::Shared::ClientInterface; +using Om::Shared::ClientKey; + +namespace Om { + + +/** Registers a new client with the OSC system, so it can receive updates. + * + * \ingroup engine + */ +class RegisterClientEvent : public QueuedEvent +{ +public: + RegisterClientEvent(CountedPtr<Responder> responder, + ClientKey key, + CountedPtr<ClientInterface> client); + + void pre_process(); + void post_process(); + +private: + ClientKey _key; + CountedPtr<ClientInterface> _client; +}; + + +} // namespace Om + +#endif // REGISTERCLIENTEVENT_H diff --git a/src/libs/engine/events/RenameEvent.cpp b/src/libs/engine/events/RenameEvent.cpp new file mode 100644 index 00000000..e2e98dd0 --- /dev/null +++ b/src/libs/engine/events/RenameEvent.cpp @@ -0,0 +1,123 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RenameEvent.h" +#include "Responder.h" +#include "Patch.h" +#include "Node.h" +#include "Tree.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" +#include "util/Path.h" +#include "ObjectStore.h" + +namespace Om { + + +RenameEvent::RenameEvent(CountedPtr<Responder> responder, const string& path, const string& name) +: QueuedEvent(responder), + m_old_path(path), + m_name(name), + m_new_path(m_old_path.parent().base_path() + name), + m_parent_patch(NULL), + m_store_treenode(NULL), + m_error(NO_ERROR) +{ + /* + if (m_old_path.parent() == "/") + m_new_path = string("/") + m_name; + else + m_new_path = m_old_path.parent() +"/"+ m_name;*/ +} + + +RenameEvent::~RenameEvent() +{ +} + + +void +RenameEvent::pre_process() +{ + if (m_name.find("/") != string::npos) { + m_error = INVALID_NAME; + QueuedEvent::pre_process(); + return; + } + + if (om->object_store()->find(m_new_path)) { + m_error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + OmObject* obj = om->object_store()->find(m_old_path); + + if (obj == NULL) { + m_error = OBJECT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + // Renaming only works for Nodes and Patches (which are Nodes) + if (obj->as_node() == NULL) { + m_error = OBJECT_NOT_RENAMABLE; + QueuedEvent::pre_process(); + return; + } + + if (obj != NULL) { + obj->set_path(m_new_path); + assert(obj->path() == m_new_path); + } + + QueuedEvent::pre_process(); +} + + +void +RenameEvent::execute(samplecount offset) +{ + //cout << "Executing rename event..."; + QueuedEvent::execute(offset); +} + + +void +RenameEvent::post_process() +{ + string msg = "Unable to rename object - "; + + if (m_error == NO_ERROR) { + m_responder->respond_ok(); + om->client_broadcaster()->send_rename(m_old_path, m_new_path); + } else { + if (m_error == OBJECT_EXISTS) + msg.append("Object already exists at ").append(m_new_path); + else if (m_error == OBJECT_NOT_FOUND) + msg.append("Could not find object ").append(m_old_path); + else if (m_error == OBJECT_NOT_RENAMABLE) + msg.append(m_old_path).append(" is not renamable"); + else if (m_error == INVALID_NAME) + msg.append(m_name).append(" is not a valid name"); + + m_responder->respond_error(msg); + } +} + + +} // namespace Om diff --git a/src/libs/engine/events/RenameEvent.h b/src/libs/engine/events/RenameEvent.h new file mode 100644 index 00000000..c1c9fe91 --- /dev/null +++ b/src/libs/engine/events/RenameEvent.h @@ -0,0 +1,66 @@ +/* This file is part of Om. Copyright (C) 2006 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 RENAMEEVENT_H +#define RENAMEEVENT_H + +#include "QueuedEvent.h" +#include "util/Path.h" +#include <string> +using std::string; + +template<typename T> class TreeNode; +template<typename T> class ListNode; + +namespace Om { + +class OmObject; +class Patch; +class Node; +class Plugin; +class DisconnectNodeEvent; +class DisconnectPortEvent; + + +/** An event to change the name of an OmObject. + * + * \ingroup engine + */ +class RenameEvent : public QueuedEvent +{ +public: + RenameEvent(CountedPtr<Responder> responder, const string& path, const string& name); + ~RenameEvent(); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + enum ErrorType { NO_ERROR, OBJECT_NOT_FOUND, OBJECT_EXISTS, OBJECT_NOT_RENAMABLE, INVALID_NAME }; + + Path m_old_path; + string m_name; + Path m_new_path; + Patch* m_parent_patch; + TreeNode<OmObject*>* m_store_treenode; + ErrorType m_error; +}; + + +} // namespace Om + +#endif // RENAMEEVENT_H diff --git a/src/libs/engine/events/RequestAllObjectsEvent.cpp b/src/libs/engine/events/RequestAllObjectsEvent.cpp new file mode 100644 index 00000000..ef40a0de --- /dev/null +++ b/src/libs/engine/events/RequestAllObjectsEvent.cpp @@ -0,0 +1,55 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RequestAllObjectsEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ObjectSender.h" + +namespace Om { + + +RequestAllObjectsEvent::RequestAllObjectsEvent(CountedPtr<Responder> responder) +: QueuedEvent(responder), + m_client(CountedPtr<ClientInterface>(NULL)) +{ +} + + +void +RequestAllObjectsEvent::pre_process() +{ + m_client = m_responder->find_client(); + + QueuedEvent::pre_process(); +} + + +void +RequestAllObjectsEvent::post_process() +{ + if (m_client) { + m_responder->respond_ok(); + ObjectSender::send_all(m_client.get()); + } else { + m_responder->respond_error("Invalid URL"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/RequestAllObjectsEvent.h b/src/libs/engine/events/RequestAllObjectsEvent.h new file mode 100644 index 00000000..df63a577 --- /dev/null +++ b/src/libs/engine/events/RequestAllObjectsEvent.h @@ -0,0 +1,50 @@ +/* This file is part of Om. Copyright (C) 2006 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 REQUESTALLOBJECTSEVENT_H +#define REQUESTALLOBJECTSEVENT_H + +#include <string> +#include "QueuedEvent.h" +using std::string; + +namespace Om { + +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A request from a client to send notification of all objects (ie refresh). + * + * \ingroup engine + */ +class RequestAllObjectsEvent : public QueuedEvent +{ +public: + RequestAllObjectsEvent(CountedPtr<Responder> responder); + + void pre_process(); + void post_process(); + +private: + CountedPtr<ClientInterface> m_client; +}; + + +} // namespace Om + +#endif // REQUESTALLOBJECTSEVENT_H diff --git a/src/libs/engine/events/RequestMetadataEvent.cpp b/src/libs/engine/events/RequestMetadataEvent.cpp new file mode 100644 index 00000000..10e1007c --- /dev/null +++ b/src/libs/engine/events/RequestMetadataEvent.cpp @@ -0,0 +1,80 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RequestMetadataEvent.h" +#include <string> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "OmObject.h" +#include "ObjectStore.h" +#include "interface/ClientInterface.h" +#include "ClientBroadcaster.h" +using std::string; + +namespace Om { + + +RequestMetadataEvent::RequestMetadataEvent(CountedPtr<Responder> responder, const string& node_path, const string& key) +: QueuedEvent(responder), + m_path(node_path), + m_key(key), + m_value(""), + m_object(NULL), + m_client(CountedPtr<ClientInterface>(NULL)) +{ +} + + +void +RequestMetadataEvent::pre_process() +{ + m_client = m_responder->find_client(); + + if (m_client) { + m_object = om->object_store()->find(m_path); + if (m_object == NULL) { + QueuedEvent::pre_process(); + return; + } + } + + m_value = m_object->get_metadata(m_key); + + QueuedEvent::pre_process(); +} + + +void +RequestMetadataEvent::post_process() +{ + if (m_client) { + if (m_value == "") { + string msg = "Unable to find object "; + msg += m_path; + m_responder->respond_error(msg); + } else { + m_responder->respond_ok(); + m_client->metadata_update(m_path, m_key, m_value); + } + } else { + m_responder->respond_error("Unknown client"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/RequestMetadataEvent.h b/src/libs/engine/events/RequestMetadataEvent.h new file mode 100644 index 00000000..e4243eb3 --- /dev/null +++ b/src/libs/engine/events/RequestMetadataEvent.h @@ -0,0 +1,56 @@ +/* This file is part of Om. Copyright (C) 2006 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 REQUESTMETADATAEVENT_H +#define REQUESTMETADATAEVENT_H + +#include <string> +#include "QueuedEvent.h" + +using std::string; + +namespace Om { + +class OmObject; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A request from a client for a piece of metadata. + * + * \ingroup engine + */ +class RequestMetadataEvent : public QueuedEvent +{ +public: + RequestMetadataEvent(CountedPtr<Responder> responder, const string& path, const string& key); + + void pre_process(); + void post_process(); + +private: + string m_path; + string m_key; + string m_value; + OmObject* m_object; + CountedPtr<ClientInterface> m_client; +}; + + +} // namespace Om + +#endif // REQUESTMETADATAEVENT_H diff --git a/src/libs/engine/events/RequestPluginsEvent.cpp b/src/libs/engine/events/RequestPluginsEvent.cpp new file mode 100644 index 00000000..89dbefd5 --- /dev/null +++ b/src/libs/engine/events/RequestPluginsEvent.cpp @@ -0,0 +1,55 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RequestPluginsEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" + +namespace Om { + + +RequestPluginsEvent::RequestPluginsEvent(CountedPtr<Responder> responder) +: QueuedEvent(responder), + m_client(CountedPtr<ClientInterface>(NULL)) +{ +} + + +void +RequestPluginsEvent::pre_process() +{ + m_client = m_responder->find_client(); + + QueuedEvent::pre_process(); +} + + +void +RequestPluginsEvent::post_process() +{ + if (m_client) { + om->client_broadcaster()->send_plugins_to(m_client.get()); + m_responder->respond_ok(); + } else { + m_responder->respond_error("Invalid URL"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/RequestPluginsEvent.h b/src/libs/engine/events/RequestPluginsEvent.h new file mode 100644 index 00000000..83447fb3 --- /dev/null +++ b/src/libs/engine/events/RequestPluginsEvent.h @@ -0,0 +1,51 @@ +/* This file is part of Om. Copyright (C) 2006 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 REQUESTPLUGINSEVENT_H +#define REQUESTPLUGINSEVENT_H + +#include <string> +#include "QueuedEvent.h" +using std::string; + +namespace Om { + +class Responder; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A request from a client to send notification of all objects (ie refresh). + * + * \ingroup engine + */ +class RequestPluginsEvent : public QueuedEvent +{ +public: + RequestPluginsEvent(CountedPtr<Responder> responder); + + void pre_process(); + void post_process(); + +private: + CountedPtr<ClientInterface> m_client; +}; + + +} // namespace Om + +#endif // REQUESTPLUGINSEVENT_H diff --git a/src/libs/engine/events/RequestPortValueEvent.cpp b/src/libs/engine/events/RequestPortValueEvent.cpp new file mode 100644 index 00000000..e4d3985e --- /dev/null +++ b/src/libs/engine/events/RequestPortValueEvent.cpp @@ -0,0 +1,81 @@ +/* This file is part of Om. Copyright (C) 2006 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 "RequestPortValueEvent.h" +#include <string> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "interface/ClientInterface.h" +#include "PortBase.h" +#include "PortInfo.h" +#include "ObjectStore.h" +#include "ClientBroadcaster.h" + +using std::string; + +namespace Om { + + +RequestPortValueEvent::RequestPortValueEvent(CountedPtr<Responder> responder, const string& port_path) +: QueuedEvent(responder), + m_port_path(port_path), + m_port(NULL), + m_value(0.0), + m_client(CountedPtr<ClientInterface>(NULL)) +{ +} + + +void +RequestPortValueEvent::pre_process() +{ + m_client = m_responder->find_client(); + m_port = om->object_store()->find_port(m_port_path); + + QueuedEvent::pre_process(); +} + + +void +RequestPortValueEvent::execute(samplecount offset) +{ + if (m_port != NULL && m_port->port_info()->is_audio() || m_port->port_info()->is_control()) + m_value = ((PortBase<sample>*)m_port)->buffer(0)->value_at(offset); + else + m_port = NULL; // triggers error response + + QueuedEvent::execute(offset); +} + + +void +RequestPortValueEvent::post_process() +{ + string msg; + if (m_port) { + m_responder->respond_error("Unable to find port for get_value responder."); + } else if (m_client) { + m_responder->respond_ok(); + m_client->control_change(m_port_path, m_value); + } else { + m_responder->respond_error("Invalid URL"); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/RequestPortValueEvent.h b/src/libs/engine/events/RequestPortValueEvent.h new file mode 100644 index 00000000..d970ee9c --- /dev/null +++ b/src/libs/engine/events/RequestPortValueEvent.h @@ -0,0 +1,56 @@ +/* This file is part of Om. Copyright (C) 2006 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 REQUESTPORTVALUEEVENT_H +#define REQUESTPORTVALUEEVENT_H + +#include <string> +#include "QueuedEvent.h" +#include "util/types.h" + +using std::string; + +namespace Om { + +class Port; +namespace Shared { class ClientInterface; } +using Shared::ClientInterface; + + +/** A request from a client to send the value of a port. + * + * \ingroup engine + */ +class RequestPortValueEvent : public QueuedEvent +{ +public: + RequestPortValueEvent(CountedPtr<Responder> responder, const string& port_path); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_port_path; + Port* m_port; + sample m_value; + CountedPtr<ClientInterface> m_client; +}; + + +} // namespace Om + +#endif // REQUESTPORTVALUEEVENT_H diff --git a/src/libs/engine/events/SetMetadataEvent.cpp b/src/libs/engine/events/SetMetadataEvent.cpp new file mode 100644 index 00000000..614f7ca9 --- /dev/null +++ b/src/libs/engine/events/SetMetadataEvent.cpp @@ -0,0 +1,79 @@ +/* This file is part of Om. Copyright (C) 2006 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 "SetMetadataEvent.h" +#include <string> +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" +#include "OmObject.h" +#include "ObjectStore.h" + +using std::string; + +namespace Om { + + +SetMetadataEvent::SetMetadataEvent(CountedPtr<Responder> responder, const string& path, const string& key, const string& value) +: QueuedEvent(responder), + m_path(path), + m_key(key), + m_value(value), + m_object(NULL) +{ +} + + +void +SetMetadataEvent::pre_process() +{ + m_object = om->object_store()->find(m_path); + if (m_object == NULL) { + QueuedEvent::pre_process(); + return; + } + + m_object->set_metadata(m_key, m_value); + + QueuedEvent::pre_process(); +} + + +void +SetMetadataEvent::execute(samplecount offset) +{ + // Do nothing + + QueuedEvent::execute(offset); +} + + +void +SetMetadataEvent::post_process() +{ + if (m_object == NULL) { + string msg = "Unable to find object "; + msg += m_path; + m_responder->respond_error(msg); + } else { + m_responder->respond_ok(); + om->client_broadcaster()->send_metadata_update(m_path, m_key, m_value); + } +} + + +} // namespace Om diff --git a/src/libs/engine/events/SetMetadataEvent.h b/src/libs/engine/events/SetMetadataEvent.h new file mode 100644 index 00000000..ef471033 --- /dev/null +++ b/src/libs/engine/events/SetMetadataEvent.h @@ -0,0 +1,53 @@ +/* This file is part of Om. Copyright (C) 2006 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 SETMETADATAEVENT_H +#define SETMETADATAEVENT_H + +#include <string> +#include "QueuedEvent.h" + +using std::string; + +namespace Om { + +class OmObject; + + +/** An event to set a piece of metadata for an OmObject. + * + * \ingroup engine + */ +class SetMetadataEvent : public QueuedEvent +{ +public: + SetMetadataEvent(CountedPtr<Responder> responder, const string& path, const string& key, const string& value); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + string m_path; + string m_key; + string m_value; + OmObject* m_object; +}; + + +} // namespace Om + +#endif // SETMETADATAEVENT_H diff --git a/src/libs/engine/events/SetPortValueEvent.cpp b/src/libs/engine/events/SetPortValueEvent.cpp new file mode 100644 index 00000000..964cd9ca --- /dev/null +++ b/src/libs/engine/events/SetPortValueEvent.cpp @@ -0,0 +1,104 @@ +/* This file is part of Om. Copyright (C) 2006 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 "SetPortValueEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "PortBase.h" +#include "PortInfo.h" +#include "ClientBroadcaster.h" +#include "Node.h" +#include "ObjectStore.h" + +namespace Om { + + +/** Voice-specific control setting + */ +SetPortValueEvent::SetPortValueEvent(CountedPtr<Responder> responder, size_t voice_num, const string& port_path, sample val) +: Event(responder), + m_voice_num(voice_num), + m_port_path(port_path), + m_val(val), + m_port(NULL), + m_error(NO_ERROR) +{ +} + + +SetPortValueEvent::SetPortValueEvent(CountedPtr<Responder> responder, const string& port_path, sample val) +: Event(responder), + m_voice_num(-1), + m_port_path(port_path), + m_val(val), + m_port(NULL), + m_error(NO_ERROR) +{ +} + + +void +SetPortValueEvent::execute(samplecount offset) +{ + if (m_port == NULL) + m_port = om->object_store()->find_port(m_port_path); + + if (m_port == NULL) { + m_error = PORT_NOT_FOUND; + } else if (!m_port->port_info()->is_audio() && !m_port->port_info()->is_control()) { + m_error = TYPE_MISMATCH; + } else { + if (m_voice_num == -1) + ((PortBase<sample>*)m_port)->set_value(m_val, offset); + else + ((PortBase<sample>*)m_port)->set_value(m_voice_num, m_val, offset); + //((PortBase<sample>*)m_port)->buffer(m_voice_num)->set(m_val, offset); // FIXME: check range + } +} + + +void +SetPortValueEvent::post_process() +{ + if (m_error == NO_ERROR) { + assert(m_port != NULL); + + m_responder->respond_ok(); + om->client_broadcaster()->send_control_change(m_port_path, m_val); + + // Send patch port control change, if this is a bridge port + Port* parent_port = m_port->parent_node()->as_port(); + if (parent_port != NULL) { + assert(parent_port->port_info()->is_control() || parent_port->port_info()->is_audio()); + om->client_broadcaster()->send_control_change(parent_port->path(), m_val); + } + + } else if (m_error == PORT_NOT_FOUND) { + string msg = "Unable to find port "; + msg.append(m_port_path).append(" for set_port_value"); + m_responder->respond_error(msg); + + } else if (m_error == TYPE_MISMATCH) { + string msg = "Attempt to set "; + msg.append(m_port_path).append(" to incompatible type"); + m_responder->respond_error(msg); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/SetPortValueEvent.h b/src/libs/engine/events/SetPortValueEvent.h new file mode 100644 index 00000000..0a9614b9 --- /dev/null +++ b/src/libs/engine/events/SetPortValueEvent.h @@ -0,0 +1,56 @@ +/* This file is part of Om. Copyright (C) 2006 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 SETPORTVALUEEVENT_H +#define SETPORTVALUEEVENT_H + +#include <string> +#include "Event.h" +#include "util/types.h" +using std::string; + +namespace Om { + +class Port; + + +/** An event to change the value of a port. + * + * \ingroup engine + */ +class SetPortValueEvent : public Event +{ +public: + SetPortValueEvent(CountedPtr<Responder> responder, const string& port_path, sample val); + SetPortValueEvent(CountedPtr<Responder> responder, size_t voice_num, const string& port_path, sample val); + + void execute(samplecount offset); + void post_process(); + +private: + enum ErrorType { NO_ERROR, PORT_NOT_FOUND, TYPE_MISMATCH }; + + int m_voice_num; + string m_port_path; + float m_val; + Port* m_port; + ErrorType m_error; +}; + + +} // namespace Om + +#endif // SETPORTVALUEEVENT_H diff --git a/src/libs/engine/events/SetPortValueQueuedEvent.cpp b/src/libs/engine/events/SetPortValueQueuedEvent.cpp new file mode 100644 index 00000000..e73c0486 --- /dev/null +++ b/src/libs/engine/events/SetPortValueQueuedEvent.cpp @@ -0,0 +1,116 @@ +/* This file is part of Om. Copyright (C) 2006 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 "SetPortValueQueuedEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "PortBase.h" +#include "PortInfo.h" +#include "ClientBroadcaster.h" +#include "Plugin.h" +#include "Node.h" +#include "ObjectStore.h" + +namespace Om { + + +/** Voice-specific control setting + */ +SetPortValueQueuedEvent::SetPortValueQueuedEvent(CountedPtr<Responder> responder, size_t voice_num, const string& port_path, sample val) +: QueuedEvent(responder), + m_voice_num(voice_num), + m_port_path(port_path), + m_val(val), + m_port(NULL), + m_error(NO_ERROR) +{ +} + + +SetPortValueQueuedEvent::SetPortValueQueuedEvent(CountedPtr<Responder> responder, const string& port_path, sample val) +: QueuedEvent(responder), + m_voice_num(-1), + m_port_path(port_path), + m_val(val), + m_port(NULL), + m_error(NO_ERROR) +{ +} + + +void +SetPortValueQueuedEvent::pre_process() +{ + if (m_port == NULL) + m_port = om->object_store()->find_port(m_port_path); + + if (m_port == NULL) { + m_error = PORT_NOT_FOUND; + } else if (!m_port->port_info()->is_audio() && !m_port->port_info()->is_control()) { + m_error = TYPE_MISMATCH; + } + + QueuedEvent::pre_process(); +} + + +void +SetPortValueQueuedEvent::execute(samplecount offset) +{ + QueuedEvent::execute(offset); + + if (m_error == NO_ERROR) { + assert(m_port != NULL); + if (m_voice_num == -1) + ((PortBase<sample>*)m_port)->set_value(m_val, offset); + else + ((PortBase<sample>*)m_port)->buffer(m_voice_num)->set(m_val, offset); // FIXME: check range + } +} + + +void +SetPortValueQueuedEvent::post_process() +{ + if (m_error == NO_ERROR) { + assert(m_port != NULL); + + m_responder->respond_ok(); + om->client_broadcaster()->send_control_change(m_port_path, m_val); + + // Send patch port control change, if this is a bridge port + Port* parent_port = m_port->parent_node()->as_port(); + if (parent_port != NULL) { + assert(parent_port->port_info()->is_control() || parent_port->port_info()->is_audio()); + om->client_broadcaster()->send_control_change(parent_port->path(), m_val); + } + + } else if (m_error == PORT_NOT_FOUND) { + string msg = "Unable to find port "; + msg.append(m_port_path).append(" for set_port_value_slow"); + m_responder->respond_error(msg); + + } else if (m_error == TYPE_MISMATCH) { + string msg = "Attempt to set "; + msg.append(m_port_path).append(" to incompatible type"); + m_responder->respond_error(msg); + } +} + + +} // namespace Om + diff --git a/src/libs/engine/events/SetPortValueQueuedEvent.h b/src/libs/engine/events/SetPortValueQueuedEvent.h new file mode 100644 index 00000000..b92beacf --- /dev/null +++ b/src/libs/engine/events/SetPortValueQueuedEvent.h @@ -0,0 +1,57 @@ +/* This file is part of Om. Copyright (C) 2006 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 SETPORTVALUEQUEUEDEVENT_H +#define SETPORTVALUEQUEUEDEVENT_H + +#include "QueuedEvent.h" +#include "util/types.h" +#include <string> +using std::string; + +namespace Om { + +class Port; + + +/** An event to change the value of a port. + * + * \ingroup engine + */ +class SetPortValueQueuedEvent : public QueuedEvent +{ +public: + SetPortValueQueuedEvent(CountedPtr<Responder> responder, const string& port_path, sample val); + SetPortValueQueuedEvent(CountedPtr<Responder> responder, size_t voice_num, const string& port_path, sample val); + + void pre_process(); + void execute(samplecount offset); + void post_process(); + +private: + enum ErrorType { NO_ERROR, PORT_NOT_FOUND, TYPE_MISMATCH }; + + int m_voice_num; + string m_port_path; + float m_val; + Port* m_port; + ErrorType m_error; +}; + + +} // namespace Om + +#endif // SETPORTVALUEQUEUEDEVENT_H diff --git a/src/libs/engine/events/UnregisterClientEvent.cpp b/src/libs/engine/events/UnregisterClientEvent.cpp new file mode 100644 index 00000000..ef5a9979 --- /dev/null +++ b/src/libs/engine/events/UnregisterClientEvent.cpp @@ -0,0 +1,45 @@ +/* This file is part of Om. Copyright (C) 2006 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 "UnregisterClientEvent.h" +#include "Responder.h" +#include "Om.h" +#include "OmApp.h" +#include "ClientBroadcaster.h" +#include "interface/ClientInterface.h" + +namespace Om { + + +UnregisterClientEvent::UnregisterClientEvent(CountedPtr<Responder> responder, ClientKey key) +: QueuedEvent(responder) +, _key(key) +{ +} + + +void +UnregisterClientEvent::post_process() +{ + if (om->client_broadcaster()->unregister_client(_key)) + m_responder->respond_ok(); + else + m_responder->respond_error("Unable to unregister client"); +} + + +} // namespace Om + diff --git a/src/libs/engine/events/UnregisterClientEvent.h b/src/libs/engine/events/UnregisterClientEvent.h new file mode 100644 index 00000000..fdbdb314 --- /dev/null +++ b/src/libs/engine/events/UnregisterClientEvent.h @@ -0,0 +1,53 @@ +/* This file is part of Om. Copyright (C) 2006 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 UNREGISTERCLIENTEVENT_H +#define UNREGISTERCLIENTEVENT_H + +#include "QueuedEvent.h" +#include "interface/ClientKey.h" +#include <string> +using std::string; + +namespace Om { + +namespace Shared { + class ClientInterface; + class ClientKey; +} +using Shared::ClientInterface; +using Shared::ClientKey; + + +/** Unregisters an OSC client so it no longer receives notifications. + * + * \ingroup engine + */ +class UnregisterClientEvent : public QueuedEvent +{ +public: + UnregisterClientEvent(CountedPtr<Responder> responder, ClientKey key); + + void post_process(); + +private: + ClientKey _key; +}; + + +} // namespace Om + +#endif // UNREGISTERCLIENTEVENT_H diff --git a/src/libs/engine/instantiations.cpp b/src/libs/engine/instantiations.cpp new file mode 100644 index 00000000..e2b0088d --- /dev/null +++ b/src/libs/engine/instantiations.cpp @@ -0,0 +1,49 @@ +/* This file is part of Om. Copyright (C) 2006 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 + */ + +/** @file + * Explicit template instantiations. + * + * Need to do this to avoid undefined references, because GCC doesn't seem to + * know how to recursively instantiate templates. Cleaner to do it all here + * than pollute everything with it. :/ + */ + +#include "Tree.h" +#include "TreeImplementation.h" +#include "OmObject.h" +#include "Node.h" + + +/* Tree */ +template class Tree<Om::Node*>; +template class TreeNode<Om::Node*>; + +template Tree<Om::OmObject*>::Tree(); +template Tree<Om::OmObject*>::~Tree(); +template void Tree<Om::OmObject*>::insert(TreeNode<Om::OmObject*>* const n); +template TreeNode<Om::OmObject*>* Tree<Om::OmObject*>::remove(const string& key); +template Om::OmObject* Tree<Om::OmObject*>::find(const string& key) const; +template TreeNode<Om::OmObject*>* Tree<Om::OmObject*>::find_treenode(const string& key) const; + +template Tree<Om::OmObject*>::iterator Tree<Om::OmObject*>::begin() const; +template Tree<Om::OmObject*>::iterator Tree<Om::OmObject*>::end() const; + +template Tree<Om::OmObject*>::iterator::~iterator(); +template Om::OmObject* Tree<Om::OmObject*>::iterator::operator*() const; +template Tree<Om::OmObject*>::iterator& Tree<Om::OmObject*>::iterator::operator++(); +template bool Tree<Om::OmObject*>::iterator::operator!=(const iterator& iter) const; + diff --git a/src/libs/engine/main.cpp b/src/libs/engine/main.cpp new file mode 100644 index 00000000..a69c7e76 --- /dev/null +++ b/src/libs/engine/main.cpp @@ -0,0 +1,153 @@ +/* This file is part of Om. Copyright (C) 2006 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 <iostream> +#include <cstddef> +#include <signal.h> +#include "config.h" +#include "util.h" +#include "cmdline.h" +#include "Om.h" +#include "OmApp.h" +#ifdef HAVE_LASH +#include "LashDriver.h" +#endif +#ifdef BUILD_IN_PROCESS_ENGINE +#include <jack/jack.h> +#include <jack/intclient.h> +#endif + +using std::cout; using std::endl; using std::cerr; + + +void +catch_int(int) +{ + signal(SIGINT, catch_int); + signal(SIGTERM, catch_int); + + std::cout << "[Main] Om interrupted." << std::endl; + Om::om->quit(); +} + + +#ifdef BUILD_IN_PROCESS_ENGINE + +jack_client_t* jack_client; +jack_intclient_t jack_intclient; + + +void +unload_in_process_engine(int) +{ + jack_status_t status; + int ret = EXIT_SUCCESS; + + cout << "Unloading..."; + status = jack_internal_client_unload(jack_client, jack_intclient); + if (status & JackFailure) { + cout << "failed" << endl; + ret = EXIT_FAILURE; + } else { + cout << "done" << endl; + } + jack_client_close(jack_client); + exit(ret); +} + + +int +load_in_process_engine(const char* port) +{ + int ret = EXIT_SUCCESS; + + jack_status_t status; + + if ((jack_client = jack_client_open("om_load", JackNoStartServer, + &status)) != NULL) { + jack_intclient = + jack_internal_client_load(jack_client, "Om", + (jack_options_t)(JackLoadName|JackLoadInit), + &status, "om", port); + if (status == 0) { + cout << "Engine loaded" << endl; + signal(SIGINT, unload_in_process_engine); + signal(SIGTERM, unload_in_process_engine); + + while (1) { + sleep(1); + } + } else if (status & JackFailure) { + cerr << "Could not load om.so" << endl; + ret = EXIT_FAILURE; + } + + jack_client_close(jack_client); + } else { + cerr << "jack_client_open failed" << endl; + ret = EXIT_FAILURE; + } +} + +#endif // BUILD_IN_PROCESS_ENGINE + + +int +main(int argc, char** argv) +{ +#ifdef HAVE_LASH + lash_args_t* lash_args = lash_extract_args(&argc, &argv); +#endif + + int ret = EXIT_SUCCESS; + + /* Parse command line options */ + gengetopt_args_info args_info; + if (cmdline_parser (argc, argv, &args_info) != 0) + return EXIT_FAILURE; + + + if (args_info.in_jackd_flag) { +#ifdef BUILD_IN_PROCESS_ENGINE + ret = load_in_process_engine(args_info.port_arg); +#else + cerr << "In-process Jack client support not enabled in this build." << endl; + ret = EXIT_FAILURE; +#endif // JACK_IN_PROCESS_ENGINE + } else { + signal(SIGINT, catch_int); + signal(SIGTERM, catch_int); + + Om::set_denormal_flags(); + + Om::om = new Om::OmApp(args_info.port_arg); + +#ifdef HAVE_LASH + Om::lash_driver = new Om::LashDriver(Om::om, lash_args); +#endif + + Om::om->main(); + +#ifdef HAVE_LASH + delete Om::lash_driver; +#endif + + delete Om::om; + } + + return ret; +} + diff --git a/src/libs/engine/midi.h b/src/libs/engine/midi.h new file mode 100644 index 00000000..6575ede7 --- /dev/null +++ b/src/libs/engine/midi.h @@ -0,0 +1,135 @@ +/* Definitions to ease working with raw MIDI. + * + * Stolen from Alsa's asounddef.h + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef MIDI_H +#define MIDI_H + + +/** + * \defgroup midi MIDI Definitions + * MIDI command and controller number definitions. + * \{ + */ + +// Commands: + +#define MIDI_CMD_NOTE_OFF 0x80 /**< note off */ +#define MIDI_CMD_NOTE_ON 0x90 /**< note on */ +#define MIDI_CMD_NOTE_PRESSURE 0xA0 /**< key pressure */ +#define MIDI_CMD_CONTROL 0xB0 /**< control change */ +#define MIDI_CMD_PGM_CHANGE 0xC0 /**< program change */ +#define MIDI_CMD_CHANNEL_PRESSURE 0xD0 /**< channel pressure */ +#define MIDI_CMD_BENDER 0xE0 /**< pitch bender */ + +#define MIDI_CMD_COMMON_SYSEX 0xF0 /**< sysex (system exclusive) begin */ +#define MIDI_CMD_COMMON_MTC_QUARTER 0xF1 /**< MTC quarter frame */ +#define MIDI_CMD_COMMON_SONG_POS 0xF2 /**< song position */ +#define MIDI_CMD_COMMON_SONG_SELECT 0xF3 /**< song select */ +#define MIDI_CMD_COMMON_TUNE_REQUEST 0xF6 /**< tune request */ +#define MIDI_CMD_COMMON_SYSEX_END 0xF7 /**< end of sysex */ +#define MIDI_CMD_COMMON_CLOCK 0xF8 /**< clock */ +#define MIDI_CMD_COMMON_START 0xFA /**< start */ +#define MIDI_CMD_COMMON_CONTINUE 0xFB /**< continue */ +#define MIDI_CMD_COMMON_STOP 0xFC /**< stop */ +#define MIDI_CMD_COMMON_SENSING 0xFE /**< active sensing */ +#define MIDI_CMD_COMMON_RESET 0xFF /**< reset */ + + +// Controllers: + +#define MIDI_CTL_MSB_BANK 0x00 /**< Bank selection */ +#define MIDI_CTL_MSB_MODWHEEL 0x01 /**< Modulation */ +#define MIDI_CTL_MSB_BREATH 0x02 /**< Breath */ +#define MIDI_CTL_MSB_FOOT 0x04 /**< Foot */ +#define MIDI_CTL_MSB_PORTAMENTO_TIME 0x05 /**< Portamento time */ +#define MIDI_CTL_MSB_DATA_ENTRY 0x06 /**< Data entry */ +#define MIDI_CTL_MSB_MAIN_VOLUME 0x07 /**< Main volume */ +#define MIDI_CTL_MSB_BALANCE 0x08 /**< Balance */ +#define MIDI_CTL_MSB_PAN 0x0A /**< Panpot */ +#define MIDI_CTL_MSB_EXPRESSION 0x0B /**< Expression */ +#define MIDI_CTL_MSB_EFFECT1 0x0C /**< Effect1 */ +#define MIDI_CTL_MSB_EFFECT2 0x0D /**< Effect2 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE1 0x10 /**< General purpose 1 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE2 0x11 /**< General purpose 2 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE3 0x12 /**< General purpose 3 */ +#define MIDI_CTL_MSB_GENERAL_PURPOSE4 0x13 /**< General purpose 4 */ +#define MIDI_CTL_LSB_BANK 0x20 /**< Bank selection */ +#define MIDI_CTL_LSB_MODWHEEL 0x21 /**< Modulation */ +#define MIDI_CTL_LSB_BREATH 0x22 /**< Breath */ +#define MIDI_CTL_LSB_FOOT 0x24 /**< Foot */ +#define MIDI_CTL_LSB_PORTAMENTO_TIME 0x25 /**< Portamento time */ +#define MIDI_CTL_LSB_DATA_ENTRY 0x26 /**< Data entry */ +#define MIDI_CTL_LSB_MAIN_VOLUME 0x27 /**< Main volume */ +#define MIDI_CTL_LSB_BALANCE 0x28 /**< Balance */ +#define MIDI_CTL_LSB_PAN 0x2A /**< Panpot */ +#define MIDI_CTL_LSB_EXPRESSION 0x2B /**< Expression */ +#define MIDI_CTL_LSB_EFFECT1 0x2C /**< Effect1 */ +#define MIDI_CTL_LSB_EFFECT2 0x2D /**< Effect2 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE1 0x30 /**< General purpose 1 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE2 0x31 /**< General purpose 2 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE3 0x32 /**< General purpose 3 */ +#define MIDI_CTL_LSB_GENERAL_PURPOSE4 0x33 /**< General purpose 4 */ +#define MIDI_CTL_SUSTAIN 0x40 /**< Sustain pedal */ +#define MIDI_CTL_PORTAMENTO 0x41 /**< Portamento */ +#define MIDI_CTL_SOSTENUTO 0x42 /**< Sostenuto */ +#define MIDI_CTL_SUSTENUTO 0x42 /**< Sostenuto (a typo in the older version) */ +#define MIDI_CTL_SOFT_PEDAL 0x43 /**< Soft pedal */ +#define MIDI_CTL_LEGATO_FOOTSWITCH 0x44 /**< Legato foot switch */ +#define MIDI_CTL_HOLD2 0x45 /**< Hold2 */ +#define MIDI_CTL_SC1_SOUND_VARIATION 0x46 /**< SC1 Sound Variation */ +#define MIDI_CTL_SC2_TIMBRE 0x47 /**< SC2 Timbre */ +#define MIDI_CTL_SC3_RELEASE_TIME 0x48 /**< SC3 Release Time */ +#define MIDI_CTL_SC4_ATTACK_TIME 0x49 /**< SC4 Attack Time */ +#define MIDI_CTL_SC5_BRIGHTNESS 0x4A /**< SC5 Brightness */ +#define MIDI_CTL_SC6 0x4B /**< SC6 */ +#define MIDI_CTL_SC7 0x4C /**< SC7 */ +#define MIDI_CTL_SC8 0x4D /**< SC8 */ +#define MIDI_CTL_SC9 0x4E /**< SC9 */ +#define MIDI_CTL_SC10 0x4F /**< SC10 */ +#define MIDI_CTL_GENERAL_PURPOSE5 0x50 /**< General purpose 5 */ +#define MIDI_CTL_GENERAL_PURPOSE6 0x51 /**< General purpose 6 */ +#define MIDI_CTL_GENERAL_PURPOSE7 0x52 /**< General purpose 7 */ +#define MIDI_CTL_GENERAL_PURPOSE8 0x53 /**< General purpose 8 */ +#define MIDI_CTL_PORTAMENTO_CONTROL 0x54 /**< Portamento control */ +#define MIDI_CTL_E1_REVERB_DEPTH 0x5B /**< E1 Reverb Depth */ +#define MIDI_CTL_E2_TREMOLO_DEPTH 0x5C /**< E2 Tremolo Depth */ +#define MIDI_CTL_E3_CHORUS_DEPTH 0x5D /**< E3 Chorus Depth */ +#define MIDI_CTL_E4_DETUNE_DEPTH 0x5E /**< E4 Detune Depth */ +#define MIDI_CTL_E5_PHASER_DEPTH 0x5F /**< E5 Phaser Depth */ +#define MIDI_CTL_DATA_INCREMENT 0x60 /**< Data Increment */ +#define MIDI_CTL_DATA_DECREMENT 0x61 /**< Data Decrement */ +#define MIDI_CTL_NONREG_PARM_NUM_LSB 0x62 /**< Non-registered parameter number */ +#define MIDI_CTL_NONREG_PARM_NUM_MSB 0x63 /**< Non-registered parameter number */ +#define MIDI_CTL_REGIST_PARM_NUM_LSB 0x64 /**< Registered parameter number */ +#define MIDI_CTL_REGIST_PARM_NUM_MSB 0x65 /**< Registered parameter number */ +#define MIDI_CTL_ALL_SOUNDS_OFF 0x78 /**< All sounds off */ +#define MIDI_CTL_RESET_CONTROLLERS 0x79 /**< Reset Controllers */ +#define MIDI_CTL_LOCAL_CONTROL_SWITCH 0x7A /**< Local control switch */ +#define MIDI_CTL_ALL_NOTES_OFF 0x7B /**< All notes off */ +#define MIDI_CTL_OMNI_OFF 0x7C /**< Omni off */ +#define MIDI_CTL_OMNI_ON 0x7D /**< Omni on */ +#define MIDI_CTL_MONO1 0x7E /**< Mono1 */ +#define MIDI_CTL_MONO2 0x7F /**< Mono2 */ +//@} + + +/** \} */ + +#endif /* MIDI_H */ diff --git a/src/libs/engine/tests/Makefile.am b/src/libs/engine/tests/Makefile.am new file mode 100644 index 00000000..54f6ad1f --- /dev/null +++ b/src/libs/engine/tests/Makefile.am @@ -0,0 +1,27 @@ +if BUILD_UNIT_TESTS + +AM_CXXFLAGS = @JACK_CFLAGS@ @LOSC_CFLAGS@ @ALSA_CFLAGS@ -I../../common +common_ldadd = @JACK_LIBS@ @LOSC_LIBS@ @ALSA_LIBS@ -lrt +node_tree_test_LDADD = $(common_ldadd) +queue_test_LDADD = $(common_ldadd) + +bin_PROGRAMS = node_tree_test queue_test list_test path_test + +list_test_SOURCES = \ + ../List.h \ + list_test.cpp + +path_test_SOURCES = \ + ../../common/Path.h \ + path_test.cpp + +node_tree_test_SOURCES = \ + node_tree_test.cpp \ + ../Tree.h \ + ../TreeImplementation.h + +queue_test_SOURCES = \ + queue_test.cpp \ + ../../common/Queue.h + +endif # BUILD_UNIT_TESTS diff --git a/src/libs/engine/tests/list_test.cpp b/src/libs/engine/tests/list_test.cpp new file mode 100644 index 00000000..d2f91c9d --- /dev/null +++ b/src/libs/engine/tests/list_test.cpp @@ -0,0 +1,93 @@ +#include "../List.h" +#include <iostream> +#include <cstddef> + +using std::cout; using std::endl; + + +int main() +{ + List<int> l; + + l.push_back(new ListNode<int>(1)); + l.push_back(new ListNode<int>(2)); + l.push_back(new ListNode<int>(3)); + l.push_back(new ListNode<int>(4)); + l.push_back(new ListNode<int>(5)); + l.push_back(new ListNode<int>(6)); + l.push_back(new ListNode<int>(7)); + l.push_back(new ListNode<int>(8)); + + cout << "List:" << endl; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + if ((*i) == 4) + l.remove(i); + } + + std::cerr << "Removed 4 (by iterator)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + l.remove(1); + + std::cerr << "Removed 1 (head) (by value)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + if ((*i) == 2) + l.remove(i); + } + + std::cerr << "Removed 2 (head) (by iterator)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + l.remove(5); + + std::cerr << "Removed 5 (by value)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + l.remove(8); + + std::cerr << "Removed 8 (tail) (by value)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + if ((*i) == 7) + l.remove(i); + } + + std::cerr << "Removed 7 (tail) (by iterator)...\n"; + for (List<int>::iterator i = l.begin(); i != l.end(); ++i) { + cout << *i << endl; + } + cout << endl; + + List<int> r; + r.push_back(new ListNode<int>(9)); + r.remove(9); + std::cerr << "Should not see ANY numbers:\n"; + for (List<int>::iterator i = r.begin(); i != r.end(); ++i) { + cout << *i << endl; + } + return 0; +} diff --git a/src/libs/engine/tests/node_tree_test.cpp b/src/libs/engine/tests/node_tree_test.cpp new file mode 100644 index 00000000..2bce9b97 --- /dev/null +++ b/src/libs/engine/tests/node_tree_test.cpp @@ -0,0 +1,94 @@ +#include <cstdlib> +#include <iostream> +#include <vector> +#include "../Tree.h" +#include "../TreeImplementation.h" + +using std::vector; +using std::cout; using std::cerr; using std::endl; + +static const uint NUM_NODES = 20; + +int +main() +{ + cout << "\n\n\n\n"; + + Tree<string> tree; + vector<string> names; + vector<bool> in_tree; // arrays + uint num_in_tree = 0; + + + string name; + + for (uint i=0; i < NUM_NODES; ++i) { + name = (char)(i+'A'); + TreeNode<string>* n = new TreeNode<string>(name, name); + tree.insert(n); + names.push_back(name); + in_tree.push_back(true); + ++num_in_tree; + } + + cout << "Added " << NUM_NODES << " nodes." << endl; + cout << "Tree size: " << tree.size() << endl << endl; + + cout << "Tree contents: " << endl; + for (Tree<string>::iterator i = tree.begin(); i != tree.end(); ++i) { + cout << (*i) << ", "; + } + cout << endl; + + + while (true) { + bool insert; + int num = rand() % NUM_NODES; + + if (num_in_tree == 0) + insert = true; + else if (num_in_tree == NUM_NODES) + insert = false; + else { + while (true) { + insert = rand() % 2; + num = rand() % NUM_NODES; + if ((insert && !in_tree[num]) || (!insert && in_tree[num])) + break; + } + } + + string name = names[num]; + + if (insert) { + assert(in_tree[num] == false); + cout << "\nInserting '" << name << "'" << endl; + tree.insert(new TreeNode<string>(name, name)); + in_tree[num] = true; + ++num_in_tree; + cout << "Tree size: " << tree.size() << endl; + assert(num_in_tree == tree.size()); + } else { + assert(in_tree[num] == true); + cout << "\nRemoving '" << name << "'" << endl; + TreeNode<string>* removed = tree.remove(name); + assert(removed != NULL); + assert(removed->node() == name); + assert(removed->key() == name); + delete removed; + in_tree[num] = false; + assert(names[num] == name); + --num_in_tree; + cout << "Tree size: " << tree.size() << endl; + assert(num_in_tree == tree.size()); + } + assert(num_in_tree == tree.size()); + cout << "Tree contents: " << endl; + for (Tree<string>::iterator i = tree.begin(); i != tree.end(); ++i) { + cout << (*i) << ", "; + } + cout << endl; + } + + return 0; +} diff --git a/src/libs/engine/tests/old_node_tree_test.cpp b/src/libs/engine/tests/old_node_tree_test.cpp new file mode 100644 index 00000000..3b5ed485 --- /dev/null +++ b/src/libs/engine/tests/old_node_tree_test.cpp @@ -0,0 +1,72 @@ +#include <iostream> +#include "../NodeTree.h" +#include "../NodeBase.h" + +using std::cout; using std::cerr; using std::endl; + +int main() +{ + cout << "\n\n\n\n"; + + NodeBase* n = NULL; + TreeNode* tn = NULL; + TreeNode* baz = NULL; + TreeNode* quuux = NULL; + TreeNode* bar = NULL; + + NodeTree tree; + n = new NodeBase("foo", 0, 0, 0); + tn = new TreeNode(n); + tree.insert(tn); + n = new NodeBase("bar", 0, 0, 0); + bar = new TreeNode(n); + tree.insert(bar); + n = new NodeBase("baz", 0, 0, 0); + baz = new TreeNode(n); + tree.insert(baz); + n = new NodeBase("quux", 0, 0, 0); + tn = new TreeNode(n); + tree.insert(tn); + n = new NodeBase("quuux", 0, 0, 0); + quuux = new TreeNode(n); + tree.insert(quuux); + n = new NodeBase("quuuux", 0, 0, 0); + tn = new TreeNode(n); + tree.insert(tn); + + + cout << "Added 6 nodes." << endl; + cout << "Tree size: " << tree.size() << endl << endl; + + cout << "Iterating: " << endl; + for (NodeTree::iterator i = tree.begin(); i != tree.end(); ++i) { + cout << (*i)->name() << endl; + } + + cout << endl << "Search 'foo' - " << tree.find("foo")->name() << endl; + cout << endl << "Search 'bar' - " << tree.find("bar")->name() << endl; + cout << endl << "Search 'baz' - " << tree.find("baz")->name() << endl; + cout << endl << "Search 'quux' - " << tree.find("quux")->name() << endl; + cout << endl << "Search 'quuux' - " << tree.find("quuux")->name() << endl; + cout << endl << "Search 'quuuux' - " << tree.find("quuuux")->name() << endl; + cout << endl << "Search 'dave' - " << tree.find("dave") << endl; + cout << endl << "Search 'fo' - " << tree.find("fo") << endl << endl; + + cout << "Removing 'baz'." << endl; + tree.remove(baz); + + cout << "Iterating: " << endl; + for (NodeTree::iterator i = tree.begin(); i != tree.end(); ++i) { + cout << (*i)->name() << endl; + } + + cout << "Removing 'bar' (the root): " << endl; + tree.remove(bar); + + cout << "Iterating: " << endl; + for (NodeTree::iterator i = tree.begin(); i != tree.end(); ++i) { + cout << (*i)->name() << endl; + } + + return 0; +} diff --git a/src/libs/engine/tests/queue_test.cpp b/src/libs/engine/tests/queue_test.cpp new file mode 100644 index 00000000..3381a329 --- /dev/null +++ b/src/libs/engine/tests/queue_test.cpp @@ -0,0 +1,47 @@ +#include <iostream> +#include <string> +#include "../../common/Queue.h" + +using std::string; using std::cerr; using std::cout; using std::endl; + + +int main() +{ + Queue<int> q(10); + + cout << "New queue. Should be empty: " << q.is_empty() << endl; + cout << "Capacity: " << q.capacity() << endl; + cout << "Fill: " << q.fill() << endl; + + for (uint i=0; i < 5; ++i) { + q.push(i); + assert(!q.is_full()); + q.pop(); + } + cout << "Pushed and popped 5 elements. Queue should be empty: " << q.is_empty() << endl; + cout << "Fill: " << q.fill() << endl; + + for (uint i=10; i < 20; ++i) { + q.push(i); + } + cout << "Pushed 10 elements. Queue should be full: " << q.is_full() << endl; + cout << "Fill: " << q.fill() << endl; + + cout << "The digits 10->19 should print: " << endl; + while (!q.is_empty()) { + int foo = q.pop(); + cout << "Popped: " << foo << endl; + } + cout << "Queue should be empty: " << q.is_empty() << endl; + cout << "Fill: " << q.fill() << endl; + + cout << "Attempting to add eleven elements to queue of size 10. Only first 10 should succeed:" << endl; + for (uint i=20; i <= 39; ++i) { + cout << i; + cout << " - Fill: " << q.fill(); + cout << ", is full: " << q.is_full(); + cout << ", succeeded: " << q.push(i) << endl; + } + + return 0; +} diff --git a/src/libs/engine/tuning.h b/src/libs/engine/tuning.h new file mode 100644 index 00000000..aeec81da --- /dev/null +++ b/src/libs/engine/tuning.h @@ -0,0 +1,39 @@ +/* This file is part of Om. Copyright (C) 2006 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 TUNING_H +#define TUNING_H + +#include <stddef.h> +#include <time.h> + +namespace Om { + +// FIXME: put this in a Config class + +static const size_t event_queue_size = 1024; +static const size_t pre_processor_queue_size = 1024; +static const size_t post_processor_queue_size = 1024; +static const size_t maid_queue_size = 1024; + +// This controls both the LASH event processing rate and the Maid cleanup rate +// (both of which are driven from the main thread) +static const timespec main_rate = { 0, 500000000 }; // 1/2 second + + +} // namespace Om + +#endif // TUNING_H diff --git a/src/libs/engine/util.h b/src/libs/engine/util.h new file mode 100644 index 00000000..9cf4bf7f --- /dev/null +++ b/src/libs/engine/util.h @@ -0,0 +1,73 @@ +/* This file is part of Om. Copyright (C) 2006 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 UTIL_H +#define UTIL_H + +#include <iostream> +#include <cstdlib> + +#include <fenv.h> +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +using std::cerr; using std::endl; + +namespace Om { + +/** Set flags to disable denormal processing. + */ +inline void +set_denormal_flags() +{ +#ifdef __SSE__ + unsigned long a, b, c, d; + + asm("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (1)); + if (d & 1<<25) { /* It has SSE support */ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + + asm("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (0)); + if (b == 0x756e6547) { /* It's an Intel */ + int stepping, model, family, extfamily; + + family = (a >> 8) & 0xf; + extfamily = (a >> 20) & 0xff; + model = (a >> 4) & 0xf; + stepping = a & 0xf; + if (family == 15 && extfamily == 0 && model == 0 && stepping < 7) { + return; + } + } + asm("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (1)); + if (d & 1<<26) { /* bit 26, SSE2 support */ + _mm_setcsr(_mm_getcsr() | 0x40); + //cerr << "Set SSE denormal fix flag." << endl; + } + } else { + cerr << "This code has been built with SSE support, but your processor does" + << " not support the SSE instruction set." << endl << "Exiting." << endl; + exit(EXIT_FAILURE); + } +#endif + // Set 387 control register via standard C99 interface + fesetround(FE_TOWARDZERO); +} + +} // namespace Om + +#endif // UTIL_H |