diff options
Diffstat (limited to 'src/engine')
165 files changed, 19040 insertions, 0 deletions
diff --git a/src/engine/AudioBuffer.cpp b/src/engine/AudioBuffer.cpp new file mode 100644 index 00000000..098103c0 --- /dev/null +++ b/src/engine/AudioBuffer.cpp @@ -0,0 +1,299 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cassert> +#include <stdlib.h> +#include "AudioBuffer.hpp" + +using namespace std; + +/* 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 Ingen { + + +AudioBuffer::AudioBuffer(size_t size) + : Buffer((size == 1) ? DataType::CONTROL : DataType::AUDIO, size) + , _data(NULL) + , _local_data(NULL) + , _size(size) + , _filled_size(0) + , _state(OK) + , _set_value(0) + , _set_time(0) +{ + assert(_size > 0); + allocate(); + assert(data()); +} + + +void +AudioBuffer::resize(size_t size) +{ + _size = size; + + Sample* const old_data = _data; + + const bool using_local_data = (_data == _local_data); + + deallocate(); + + const int ret = posix_memalign((void**)&_local_data, 16, _size * sizeof(Sample)); + if (ret != 0) { + cerr << "[Buffer] Failed to allocate buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + assert(ret == 0); + assert(_local_data); + + if (using_local_data) + _data = _local_data; + else + _data = old_data; + + set_block(0, 0, _size-1); +} + + +/** Allocate and use a locally managed buffer (data). + */ +void +AudioBuffer::allocate() +{ + assert(!_joined_buf); + assert(_local_data == NULL); + assert(_size > 0); + + const int ret = posix_memalign((void**)&_local_data, 16, _size * sizeof(Sample)); + if (ret != 0) { + cerr << "[Buffer] Failed to allocate buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + assert(ret == 0); + assert(_local_data); + + _data = _local_data; + + set_block(0, 0, _size-1); +} + + +/** Free locally allocated buffer. + */ +void +AudioBuffer::deallocate() +{ + assert(!_joined_buf); + free(_local_data); + _local_data = NULL; + _data = NULL; +} + + +/** Empty (ie zero) the buffer. + */ +void +AudioBuffer::clear() +{ + set_block(0, 0, _size-1); + _state = OK; + _filled_size = 0; +} + + +/** 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. + */ +void +AudioBuffer::set_value(Sample val, FrameTime cycle_start, FrameTime time) +{ + if (_size == 1) + time = cycle_start; + + FrameTime offset = time - cycle_start; + assert(offset <= _size); + + if (offset < _size) { + set_block(val, offset, _size - 1); + + if (offset > 0) + _state = HALF_SET_CYCLE_1; + } // else trigger at very end of block + + _set_time = time; + _set_value = val; +} + + +/** Set a block of buffer to @a val. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + */ +void +AudioBuffer::set_block(Sample val, size_t start_offset, size_t end_offset) +{ + assert(end_offset >= start_offset); + assert(end_offset < _size); + + Sample* const buf = data(); + assert(buf); + + for (size_t i = start_offset; i <= end_offset; ++i) + buf[i] = val; +} + + +/** Scale a block of buffer by @a val. + * + * @a start_sample and @a end_sample define the inclusive range to be set. + */ +void +AudioBuffer::scale(Sample val, size_t start_sample, size_t end_sample) +{ + assert(end_sample >= start_sample); + assert(end_sample < _size); + + Sample* const buf = data(); + assert(buf); + + for (size_t i=start_sample; i <= end_sample; ++i) + buf[i] *= val; +} + + +/** 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. + */ +void +AudioBuffer::copy(const Buffer* src, size_t start_sample, size_t end_sample) +{ + assert(end_sample >= start_sample); + assert(end_sample < _size); + assert(src); + assert(src->type() == DataType::CONTROL || DataType::AUDIO); + + Sample* const buf = data(); + assert(buf); + + const Sample* const src_buf = ((AudioBuffer*)src)->data(); + assert(src_buf); + + for (size_t i=start_sample; i <= end_sample; ++i) + buf[i] = src_buf[i]; +} + + +/** 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. + */ +void +AudioBuffer::accumulate(const AudioBuffer* const src, size_t start_sample, size_t end_sample) +{ + assert(end_sample >= start_sample); + assert(end_sample < _size); + assert(src); + + Sample* const buf = data(); + assert(buf); + + const Sample* const src_buf = src->data(); + assert(src_buf); + + for (size_t i=start_sample; i <= end_sample; ++i) + buf[i] += src_buf[i]; + +} + + +/** Use another buffer's data instead of the local one. + * + * This buffer will essentially be identical to @a buf after this call. + */ +bool +AudioBuffer::join(Buffer* buf) +{ + AudioBuffer* abuf = dynamic_cast<AudioBuffer*>(buf); + if (!abuf) + return false; + + assert(abuf->size() >= _size); + + _joined_buf = abuf; + _filled_size = abuf->filled_size(); + + assert(_filled_size <= _size); + + return true; +} + + +void +AudioBuffer::unjoin() +{ + _joined_buf = NULL; + _data = _local_data; +} + + +void +AudioBuffer::prepare_read(FrameTime start, SampleCount nframes) +{ + // FIXME: nframes parameter doesn't actually work, + // writing starts from 0 every time + assert(_size == 1 || nframes == _size); + + switch (_state) { + case HALF_SET_CYCLE_1: + if (start > _set_time) + _state = HALF_SET_CYCLE_2; + break; + case HALF_SET_CYCLE_2: + set_block(_set_value, 0, _size-1); + _state = OK; + break; + default: + break; + } +} + + +/** Set the buffer (data) used. + * + * This is only to be used by Drivers (to provide zero-copy processing). + */ +void +AudioBuffer::set_data(Sample* buf) +{ + assert(buf); + assert(!_joined_buf); + _data = buf; +} + + +} // namespace Ingen diff --git a/src/engine/AudioBuffer.hpp b/src/engine/AudioBuffer.hpp new file mode 100644 index 00000000..513c188f --- /dev/null +++ b/src/engine/AudioBuffer.hpp @@ -0,0 +1,84 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUDIOBUFFER_H +#define AUDIOBUFFER_H + +#include <cstddef> +#include <cassert> +#include <boost/utility.hpp> +#include "types.hpp" +#include "Buffer.hpp" + +namespace Ingen { + + +class AudioBuffer : public Buffer +{ +public: + AudioBuffer(size_t capacity); + + void clear(); + void set_value(Sample val, FrameTime cycle_start, FrameTime time); + void set_block(Sample val, size_t start_offset, size_t end_offset); + void scale(Sample val, size_t start_sample, size_t end_sample); + void copy(const Buffer* src, size_t start_sample, size_t end_sample); + void accumulate(const AudioBuffer* src, size_t start_sample, size_t end_sample); + + bool join(Buffer* buf); + void unjoin(); + + /** For driver use only!! */ + void set_data(Sample* data); + + inline const void* raw_data() const { return _data; } + inline void* raw_data() { return _data; } + + inline Sample* data() const { return _data; } + + inline Sample& value_at(size_t offset) const + { assert(offset < _size); return data()[offset]; } + + void prepare_read(FrameTime start, SampleCount nframes); + void prepare_write(FrameTime start, SampleCount nframes) {} + + void rewind() const {} + void resize(size_t size); + + void filled_size(size_t size) { _filled_size = size; } + size_t filled_size() const { return _filled_size; } + size_t size() const { return _size; } + +private: + enum State { OK, HALF_SET_CYCLE_1, HALF_SET_CYCLE_2 }; + + void allocate(); + void deallocate(); + + Sample* _data; ///< Used data pointer (probably same as _local_data) + Sample* _local_data; ///< Locally allocated buffer (possibly unused if joined or set_data used) + size_t _size; ///< Allocated buffer size + size_t _filled_size; ///< Usable buffer size (for MIDI ports etc) + State _state; ///< State of buffer for setting values next cycle + Sample _set_value; ///< Value set by @ref set (may need to be set next cycle) + FrameTime _set_time; ///< Time _set_value was set (to reset next cycle) +}; + + +} // namespace Ingen + +#endif // AUDIOBUFFER_H diff --git a/src/engine/AudioDriver.hpp b/src/engine/AudioDriver.hpp new file mode 100644 index 00000000..6b5ea8dc --- /dev/null +++ b/src/engine/AudioDriver.hpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUDIODRIVER_H +#define AUDIODRIVER_H + +#include <raul/List.hpp> +#include <raul/Path.hpp> +#include "Driver.hpp" +#include "types.hpp" +#include "interface/DataType.hpp" + +namespace Ingen { + +class PatchImpl; +class AudioDriver; +class PortImpl; +class ProcessContext; + + +/** Audio driver abstract base class. + * + * \ingroup engine + */ +class AudioDriver : public Driver +{ +public: + AudioDriver() : Driver(DataType::AUDIO) {} + + virtual void set_root_patch(PatchImpl* patch) = 0; + virtual PatchImpl* root_patch() = 0; + + virtual void add_port(DriverPort* port) = 0; + virtual DriverPort* remove_port(const Raul::Path& path) = 0; + + virtual SampleCount buffer_size() const = 0; + virtual SampleCount sample_rate() const = 0; + virtual SampleCount frame_time() const = 0; + + virtual bool is_realtime() const = 0; + + virtual ProcessContext& context() = 0; +}; + + +} // namespace Ingen + +#endif // AUDIODRIVER_H diff --git a/src/engine/Buffer.cpp b/src/engine/Buffer.cpp new file mode 100644 index 00000000..d019146c --- /dev/null +++ b/src/engine/Buffer.cpp @@ -0,0 +1,38 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" + +namespace Ingen { + + +Buffer* +Buffer::create(DataType type, size_t size) +{ + if (type.is_control()) + return new AudioBuffer(1); + else if (type.is_audio()) + return new AudioBuffer(size); + else if (type.is_event()) + return new EventBuffer(size); + else + throw; +} + + +} // namespace Ingen diff --git a/src/engine/Buffer.hpp b/src/engine/Buffer.hpp new file mode 100644 index 00000000..e388e2e8 --- /dev/null +++ b/src/engine/Buffer.hpp @@ -0,0 +1,76 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUFFER_H +#define BUFFER_H + +#include <cstddef> +#include <cassert> +#include <boost/utility.hpp> +#include <raul/Deletable.hpp> +#include "types.hpp" +#include "interface/DataType.hpp" + +namespace Ingen { + + +class Buffer : public boost::noncopyable, public Raul::Deletable +{ +public: + Buffer(Shared::DataType type, size_t size) + : _type(type) + , _size(size) + , _joined_buf(NULL) + {} + + static Buffer* create(Shared::DataType type, size_t size); + + /** Clear contents and reset state */ + virtual void clear() = 0; + + virtual void* raw_data() = 0; + virtual const void* raw_data() const = 0; + + /** Rewind (ie reset read pointer), but leave contents unchanged */ + virtual void rewind() const = 0; + + virtual void prepare_read(FrameTime start, SampleCount nframes) = 0; + virtual void prepare_write(FrameTime start, SampleCount nframes) = 0; + + bool is_joined() const { return (_joined_buf != NULL); } + Buffer* joined_buffer() const { return _joined_buf; } + + virtual bool join(Buffer* buf) = 0; + virtual void unjoin() = 0; + + virtual void copy(const Buffer* src, size_t start_sample, size_t end_sample) = 0; + + virtual void resize(size_t size) { _size = size; } + + Shared::DataType type() const { return _type; } + size_t size() const { return _size; } + +protected: + Shared::DataType _type; + size_t _size; + Buffer* _joined_buf; +}; + + +} // namespace Ingen + +#endif // BUFFER_H diff --git a/src/engine/ClientBroadcaster.cpp b/src/engine/ClientBroadcaster.cpp new file mode 100644 index 00000000..d754f072 --- /dev/null +++ b/src/engine/ClientBroadcaster.cpp @@ -0,0 +1,271 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <iostream> +#include <unistd.h> +#include "interface/ClientInterface.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" +#include "NodeFactory.hpp" +#include "util.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "AudioDriver.hpp" +#include "ObjectSender.hpp" +#include "OSCClientSender.hpp" + +using namespace std; +using Ingen::Shared::ClientInterface; + +namespace Ingen { + + +/** Register a client to receive messages over the notification band. + */ +void +ClientBroadcaster::register_client(const string& uri, ClientInterface* client) +{ + Clients::iterator i = _clients.find(uri); + + if (i == _clients.end()) { + _clients[uri] = client; + cout << "[ClientBroadcaster] Registered client: " << uri << endl; + } else { + cout << "[ClientBroadcaster] Client already registered: " << uri << endl; + } +} + + +/** Remove a client from the list of registered clients. + * + * @return true if client was found and removed. + */ +bool +ClientBroadcaster::unregister_client(const string& uri) +{ + size_t erased = _clients.erase(uri); + + if (erased > 0) + cout << "Unregistered client: " << uri << endl; + else + cout << "Failed to find client to unregister: " << uri << endl; + + return (erased > 0); +} + + + +/** 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). + */ +ClientInterface* +ClientBroadcaster::client(const string& uri) +{ + Clients::iterator i = _clients.find(uri); + if (i != _clients.end()) { + return (*i).second; + } else { + return NULL; + } +} + + +void +ClientBroadcaster::bundle_begin() +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->bundle_begin(); +} + + +void +ClientBroadcaster::bundle_end() +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->bundle_end(); +} + + +void +ClientBroadcaster::send_error(const string& msg) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->error(msg); +} + +void +ClientBroadcaster::send_plugins_to(ClientInterface* client, const NodeFactory::Plugins& plugins) +{ + client->transfer_begin(); + + for (NodeFactory::Plugins::const_iterator i = plugins.begin(); i != plugins.end(); ++i) { + const PluginImpl* const plugin = i->second; + client->new_plugin(plugin->uri(), plugin->type_uri(), plugin->symbol(), plugin->name()); + } + + client->transfer_end(); +} + + +void +ClientBroadcaster::send_plugins(const NodeFactory::Plugins& plugins) +{ + for (Clients::const_iterator c = _clients.begin(); c != _clients.end(); ++c) + send_plugins_to((*c).second, plugins); +} + + +void +ClientBroadcaster::send_node(const NodeImpl* node, bool recursive) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_node((*i).second, node, recursive); +} + + +void +ClientBroadcaster::send_port(const PortImpl* port) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_port((*i).second, port); +} + + +void +ClientBroadcaster::send_destroyed(const string& path) +{ + assert(path != "/"); + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->destroy(path); +} + + +void +ClientBroadcaster::send_patch_cleared(const string& patch_path) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->patch_cleared(patch_path); +} + +void +ClientBroadcaster::send_connection(const SharedPtr<const ConnectionImpl> c) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->connect(c->src_port()->path(), c->dst_port()->path()); +} + + +void +ClientBroadcaster::send_disconnection(const string& src_port_path, const string& dst_port_path) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->disconnect(src_port_path, dst_port_path); +} + + +/** Send notification of a variable update. + * + * Like control changes, does not send update to client that set the variable, if applicable. + */ +void +ClientBroadcaster::send_variable_change(const string& node_path, const string& key, const Atom& value) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->set_variable(node_path, key, value); +} + + +/** Send notification of a property update. + * + * Like control changes, does not send update to client that set the property, if applicable. + */ +void +ClientBroadcaster::send_property_change(const string& node_path, const string& key, const Atom& value) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->set_property(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_port_value(const string& port_path, const Raul::Atom& value) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->set_port_value(port_path, value); +} + + +void +ClientBroadcaster::send_port_activity(const string& port_path) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->port_activity(port_path); +} + + +void +ClientBroadcaster::send_program_add(const string& node_path, int bank, int program, const string& name) +{ + for (Clients::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 (Clients::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 PatchImpl* p, bool recursive) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + ObjectSender::send_patch((*i).second, p, recursive); +} + + +/** Sends notification of an GraphObject's renaming + */ +void +ClientBroadcaster::send_rename(const string& old_path, const string& new_path) +{ + for (Clients::const_iterator i = _clients.begin(); i != _clients.end(); ++i) + (*i).second->object_renamed(old_path, new_path); +} + + +} // namespace Ingen diff --git a/src/engine/ClientBroadcaster.hpp b/src/engine/ClientBroadcaster.hpp new file mode 100644 index 00000000..b0963610 --- /dev/null +++ b/src/engine/ClientBroadcaster.hpp @@ -0,0 +1,97 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CLIENTBROADCASTER_H +#define CLIENTBROADCASTER_H + +#include <string> +#include <list> +#include <map> +#include <lo/lo.h> +#include <pthread.h> +#include <raul/SharedPtr.hpp> +#include "interface/ClientInterface.hpp" +#include "types.hpp" +#include "NodeFactory.hpp" + +using std::string; + +namespace Ingen { + +class NodeImpl; +class PortImpl; +class PluginImpl; +class PatchImpl; +class ConnectionImpl; +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 string& uri, ClientInterface* client); + bool unregister_client(const string& uri); + + ClientInterface* client(const string& uri); + + //void send_client_registration(const string& url, int client_id); + + void bundle_begin(); + void bundle_end(); + + // Error that isn't the direct result of a request + void send_error(const string& msg); + + void send_plugins(const NodeFactory::Plugins& plugin_list); + void send_patch(const PatchImpl* p, bool recursive); + void send_node(const NodeImpl* node, bool recursive); + void send_port(const PortImpl* port); + void send_destroyed(const string& path); + void send_patch_cleared(const string& patch_path); + void send_connection(const SharedPtr<const ConnectionImpl> 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_variable_change(const string& node_path, const string& key, const Raul::Atom& value); + void send_property_change(const string& node_path, const string& key, const Raul::Atom& value); + void send_port_value(const string& port_path, const Raul::Atom& value); + void send_port_activity(const string& port_path); + 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); + + void send_plugins_to(ClientInterface*, const NodeFactory::Plugins& plugin_list); + +private: + typedef std::map<string, ClientInterface*> Clients; + Clients _clients; +}; + + + +} // namespace Ingen + +#endif // CLIENTBROADCASTER_H + diff --git a/src/engine/CompiledPatch.hpp b/src/engine/CompiledPatch.hpp new file mode 100644 index 00000000..9ca1d6e9 --- /dev/null +++ b/src/engine/CompiledPatch.hpp @@ -0,0 +1,83 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef COMPILED_PATCH_HPP +#define COMPILED_PATCH_HPP + +#include <iostream> +#include <vector> +#include <raul/List.hpp> +#include <raul/Deletable.hpp> +#include <boost/utility.hpp> + +using Raul::List; + +using namespace std; + +namespace Ingen { + + +/** All information required about a node to execute it in an audio thread. + */ +struct CompiledNode { + CompiledNode(NodeImpl* n, size_t np, List<NodeImpl*>* d) + : _node(n), _n_providers(np) + { + // Copy to a vector for maximum iteration speed and cache optimization + // (Need to take a copy anyway) + + _dependants.reserve(d->size()); + for (List<NodeImpl*>::iterator i = d->begin(); i != d->end(); ++i) + _dependants.push_back(*i); + } + + NodeImpl* node() const { return _node; } + size_t n_providers() const { return _n_providers; } + const vector<NodeImpl*>& dependants() const { return _dependants; } + +private: + NodeImpl* _node; + size_t _n_providers; ///< Number of input ready signals to trigger run + vector<NodeImpl*> _dependants; ///< Nodes this one's output ports are connected to +}; + + +/** A patch and a set of connections, "compiled" into a flat structure with + * the correct order so the audio thread(s) can execute it without + * threading problems (since the preprocessor thread fiddles with other + * things). + * + * Currently objects still have some 'heavyweight' connection state, but + * eventually this should be the only place a particular set of connections + * in a patch is stored, so various "connection presets" can be switched + * in a realtime safe way. + * + * The nodes contained here are sorted in the order they must be executed. + * The parallel processing algorithm guarantees no node will be executed + * before it's providers, using this order as well as semaphores. + */ +struct CompiledPatch : public std::vector<CompiledNode> + , public Raul::Deletable + , public boost::noncopyable { + /*CompiledPatch() : std::vector<CompiledNode>() {} + CompiledPatch(size_t reserve) : std::vector<CompiledNode>(reserve) {}*/ +}; + + +} // namespace Ingen + +#endif // COMPILED_PATCH_HPP diff --git a/src/engine/ConnectionImpl.cpp b/src/engine/ConnectionImpl.cpp new file mode 100644 index 00000000..d226b4dd --- /dev/null +++ b/src/engine/ConnectionImpl.cpp @@ -0,0 +1,185 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <algorithm> +#include <raul/Maid.hpp> +#include "util.hpp" +#include "ConnectionImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" + +#include <iostream> +using namespace std; + +namespace Ingen { + + +/** Constructor for a connection from a node's output port. + * + * This handles both polyphonic and monophonic nodes, transparently to the + * user (InputPort). + */ +ConnectionImpl::ConnectionImpl(PortImpl* src_port, PortImpl* dst_port) + : _src_port(src_port) + , _dst_port(dst_port) + , _local_buffer(NULL) + , _buffer_size(dst_port->buffer_size()) + /*, _must_mix( (src_port->poly() != dst_port->poly()) + || (src_port->buffer(0)->size() < dst_port->buffer(0)->size()) )*/ + , _must_mix( (src_port->polyphonic() && (! dst_port->polyphonic())) + || (src_port->poly() != dst_port->poly() ) + || (src_port->buffer(0)->size() < dst_port->buffer(0)->size()) ) + , _pending_disconnection(false) +{ + assert(src_port); + assert(dst_port); + assert(src_port != dst_port); + assert(src_port->path() != dst_port->path()); + assert(src_port->type() == dst_port->type() + || ( (src_port->type() == DataType::CONTROL || src_port->type() == DataType::AUDIO) + && (dst_port->type() == DataType::CONTROL || dst_port->type() == DataType::AUDIO) )); + + /*assert((src_port->parent_node()->poly() == dst_port->parent_node()->poly()) + || (src_port->parent_node()->poly() == 1 || dst_port->parent_node()->poly() == 1));*/ + + if (type() == DataType::EVENT) + _must_mix = false; // FIXME: kludge + + if (_must_mix) + _local_buffer = Buffer::create(dst_port->type(), dst_port->buffer(0)->size()); + + /* FIXME: 1->1 connections with a destination with fixed buffers copies unecessarily */ + //cerr << src_port->path() << " -> " << dst_port->path() << " must mix: " << _must_mix << endl; +} + + +ConnectionImpl::~ConnectionImpl() +{ + delete _local_buffer; +} + + +void +ConnectionImpl::set_buffer_size(size_t size) +{ + if (_must_mix) { + assert(_local_buffer); + delete _local_buffer; + + _local_buffer = Buffer::create(_dst_port->type(), _dst_port->buffer(0)->size()); + } + + _buffer_size = size; +} + + +void +ConnectionImpl::prepare_poly(uint32_t poly) +{ + _src_port->prepare_poly(poly); + + if (type() == DataType::CONTROL || type() == DataType::AUDIO) + _must_mix = (poly > 1) && ( + (_src_port->poly() != _dst_port->poly()) + || (_src_port->polyphonic() && !_dst_port->polyphonic()) + || (_src_port->parent()->polyphonic() && !_dst_port->parent()->polyphonic()) ); + + /*cerr << src_port()->path() << " * " << src_port()->poly() + << " -> " << dst_port()->path() << " * " << dst_port()->poly() + << "\t\tmust mix: " << _must_mix << " at poly " << poly << endl;*/ + + if (_must_mix && ! _local_buffer) + _local_buffer = Buffer::create(_dst_port->type(), _dst_port->buffer(0)->size()); +} + + +void +ConnectionImpl::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + _src_port->apply_poly(maid, poly); + if (poly == 1 && _local_buffer && !_must_mix) { + maid.push(_local_buffer); + _local_buffer = NULL; + } +} + + +void +ConnectionImpl::process(ProcessContext& context) +{ + // FIXME: nframes parameter not used + assert(_buffer_size == 1 || _buffer_size == context.nframes()); + + /* 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. */ + + /*cerr << src_port()->path() << " * " << src_port()->poly() + << " -> " << dst_port()->path() << " * " << dst_port()->poly() + << "\t\tmust mix: " << _must_mix << endl;*/ + + if (_must_mix && (type() == DataType::CONTROL || type() == DataType::AUDIO)) { + + const AudioBuffer* const src_buffer = (AudioBuffer*)src_port()->buffer(0); + AudioBuffer* mix_buf = (AudioBuffer*)_local_buffer; + + assert(mix_buf); + + const size_t copy_size = std::min(src_buffer->size(), mix_buf->size()); + + // Copy src buffer to start of mix buffer + mix_buf->copy((AudioBuffer*)src_port()->buffer(0), 0, copy_size-1); + + // Write last value of src buffer to remainder of dst buffer, if necessary + if (copy_size < mix_buf->size()) + mix_buf->set_block(src_buffer->value_at(copy_size-1), copy_size, mix_buf->size()-1); + + // Accumulate the source's voices into local buffer starting at the second + // voice (buffer is already set to first voice above) + for (uint32_t j=1; j < src_port()->poly(); ++j) { + mix_buf->accumulate((AudioBuffer*)src_port()->buffer(j), 0, copy_size-1); + } + + // Find the summed value and write it to the remainder of dst buffer + if (copy_size < mix_buf->size()) { + float src_value = src_buffer->value_at(copy_size-1); + for (uint32_t j=1; j < src_port()->poly(); ++j) + src_value += ((AudioBuffer*)src_port()->buffer(j))->value_at(copy_size-1); + + mix_buf->set_block(src_value, copy_size, mix_buf->size()-1); + } + + // Scale the buffer down. + if (src_port()->poly() > 1) + mix_buf->scale(1.0f/(float)src_port()->poly(), 0, _buffer_size-1); + + } else if (_must_mix && type() == DataType::EVENT) { + + std::cerr << "WARNING: No event mixing." << std::endl; + + } + +} + + +} // namespace Ingen + diff --git a/src/engine/ConnectionImpl.hpp b/src/engine/ConnectionImpl.hpp new file mode 100644 index 00000000..b3da1b54 --- /dev/null +++ b/src/engine/ConnectionImpl.hpp @@ -0,0 +1,101 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CONNECTIONIMPL_H +#define CONNECTIONIMPL_H + +#include <cstdlib> +#include <boost/utility.hpp> +#include <raul/Deletable.hpp> +#include "interface/DataType.hpp" +#include "interface/Connection.hpp" +#include "PortImpl.hpp" +#include "types.hpp" + +namespace Ingen { + +class PortImpl; +class Buffer; + + +/** 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 ConnectionImpl : public Raul::Deletable, public Shared::Connection +{ +public: + ConnectionImpl(PortImpl* src_port, PortImpl* dst_port); + virtual ~ConnectionImpl(); + + PortImpl* src_port() const { return _src_port; } + PortImpl* dst_port() const { return _dst_port; } + + const Raul::Path src_port_path() const { return _src_port->path(); } + const Raul::Path dst_port_path() const { return _dst_port->path(); } + + /** Used by some (recursive) events to prevent double disconnections */ + bool pending_disconnection() { return _pending_disconnection; } + void pending_disconnection(bool b) { _pending_disconnection = b; } + + void process(ProcessContext& context); + + /** Get the buffer for a particular voice. + * A Connection is smart - it knows the destination port requesting the + * buffer, and will return accordingly (ie the same buffer for every voice + * in a mono->poly connection). + */ + inline Buffer* buffer(size_t voice) const; + + void set_buffer_size(size_t size); + void prepare_poly(uint32_t poly); + void apply_poly(Raul::Maid& maid, uint32_t poly); + + DataType type() const { return _src_port->type(); } + +protected: + PortImpl* const _src_port; + PortImpl* const _dst_port; + Buffer* _local_buffer; + size_t _buffer_size; + bool _must_mix; + bool _pending_disconnection; +}; + + +inline Buffer* +ConnectionImpl::buffer(size_t voice) const +{ + if (_must_mix) { + return _local_buffer; + } else if ( ! _src_port->polyphonic()) { + return _src_port->buffer(0); + } else { + return _src_port->buffer(voice); + } +} + + +} // namespace Ingen + +#endif // CONNECTIONIMPL_H diff --git a/src/engine/Context.hpp b/src/engine/Context.hpp new file mode 100644 index 00000000..2e000fb1 --- /dev/null +++ b/src/engine/Context.hpp @@ -0,0 +1,51 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CONTEXT_H +#define CONTEXT_H + +namespace Ingen { + +class Engine; + +class Context +{ +public: + enum ID { + AUDIO, + MESSAGE + }; + + Context(Engine& engine, ID id) + : _id(id) + , _engine(engine) + {} + + virtual ~Context() {} + + inline Engine& engine() const { return _engine; } + +protected: + ID _id; ///< Fast ID for this context + Engine& _engine; ///< Engine we're running in +}; + + +} // namespace Ingen + +#endif // CONTEXT_H + diff --git a/src/engine/Driver.hpp b/src/engine/Driver.hpp new file mode 100644 index 00000000..590b66b5 --- /dev/null +++ b/src/engine/Driver.hpp @@ -0,0 +1,98 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DRIVER_H +#define DRIVER_H + +#include <string> +#include <boost/utility.hpp> +#include <raul/Path.hpp> +#include "interface/DataType.hpp" +#include "DuplexPort.hpp" + +namespace Ingen { + +class DuplexPort; + + +/** Representation of a "system" (eg outside Ingen) 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 : boost::noncopyable { +public: + virtual ~DriverPort() {} + + /** Set the name of the system port */ + virtual void set_name(const std::string& name) = 0; + + bool is_input() const { return _patch_port->is_input(); } + DuplexPort* patch_port() const { return _patch_port; } + +protected: + /** is_input from the perspective outside of ingen */ + DriverPort(DuplexPort* port) : _patch_port(port) {} + + DuplexPort* _patch_port; +}; + + +/** Driver abstract base class. + * + * A Driver is, from the perspective of GraphObjects (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. + * + * \ingroup engine + */ +class Driver : boost::noncopyable +{ +public: + Driver(DataType type) + : _type(type) + {} + + 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(DuplexPort* patch_port) = 0; + + virtual DriverPort* driver_port(const Raul::Path& path) = 0; + + virtual void add_port(DriverPort* port) = 0; + virtual DriverPort* remove_port(const Raul::Path& path) = 0; + +protected: + DataType _type; +}; + + +} // namespace Ingen + +#endif // DRIVER_H diff --git a/src/engine/DuplexPort.cpp b/src/engine/DuplexPort.cpp new file mode 100644 index 00000000..31bfbe09 --- /dev/null +++ b/src/engine/DuplexPort.cpp @@ -0,0 +1,108 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cstdlib> +#include <cassert> +#include "util.hpp" +#include "DuplexPort.hpp" +#include "ConnectionImpl.hpp" +#include "OutputPort.hpp" +#include "NodeImpl.hpp" +#include "ProcessContext.hpp" +#include "EventBuffer.hpp" + +using namespace std; + +namespace Ingen { + + +DuplexPort::DuplexPort(NodeImpl* parent, const string& name, uint32_t index, uint32_t poly, DataType type, const Atom& value, size_t buffer_size, bool is_output) + : PortImpl(parent, name, index, poly, type, value, buffer_size) + , InputPort(parent, name, index, poly, type, value, buffer_size) + , OutputPort(parent, name, index, poly, type, value, buffer_size) + , _is_output(is_output) +{ + assert(PortImpl::_parent == parent); +} + + +void +DuplexPort::pre_process(ProcessContext& context) +{ + // <BrainHurt> + + /*cerr << endl << "{ duplex pre" << endl; + cerr << path() << " duplex pre: fixed buffers: " << fixed_buffers() << endl; + cerr << path() << " duplex pre: buffer: " << buffer(0) << endl; + cerr << path() << " duplex pre: is_output: " << _is_output << " { " << endl;*/ + + /*if (type() == DataType::EVENT) + for (uint32_t i=0; i < _poly; ++i) + cerr << path() << " (" << buffer(i) << ") # events: " + << ((EventBuffer*)buffer(i))->event_count() + << ", joined: " << _buffers->at(i)->is_joined() << endl;*/ + + if (_is_output) { + + for (uint32_t i=0; i < _poly; ++i) + if (!_buffers->at(i)->is_joined()) + _buffers->at(i)->prepare_write(context.start(), context.nframes()); + + } else { + + for (uint32_t i=0; i < _poly; ++i) + _buffers->at(i)->prepare_read(context.start(), context.nframes()); + + broadcast(context); + } + + //cerr << "} duplex pre " << path() << endl; + + // </BrainHurt> +} + + +void +DuplexPort::post_process(ProcessContext& context) +{ + // <BrainHurt> + + /*cerr << endl << "{ duplex post" << endl; + cerr << path() << " duplex post: fixed buffers: " << fixed_buffers() << endl; + cerr << path() << " duplex post: buffer: " << buffer(0) << endl; + cerr << path() << " duplex post: is_output: " << _is_output << " { " << endl; + + if (type() == DataType::EVENT) + for (uint32_t i=0; i < _poly; ++i) + cerr << path() << " (" << buffer(i) << ") # events: " + << ((EventBuffer*)buffer(i))->event_count() + << ", joined: " << _buffers->at(i)->is_joined() << endl;*/ + + if (_is_output) { + InputPort::pre_process(context); // Mix down inputs + broadcast(context); + } + + //cerr << "} duplex post " << path() << endl; + + // </BrainHurt> +} + + +} // namespace Ingen + diff --git a/src/engine/DuplexPort.hpp b/src/engine/DuplexPort.hpp new file mode 100644 index 00000000..43d202a9 --- /dev/null +++ b/src/engine/DuplexPort.hpp @@ -0,0 +1,68 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DUPLEXPORT_H +#define DUPLEXPORT_H + +#include <string> +#include <raul/Array.hpp> +#include "types.hpp" +#include "Buffer.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" + +namespace Ingen { + +class NodeImpl; + + +/** A duplex port (which is both an InputPort and an OutputPort) + * + * This is used for Patch ports, since they need to appear as both an input + * and an output port based on context. Eg. a patch output appears as an + * input inside the patch, so nodes inside the patch can feed it data. + * + * \ingroup engine + */ +class DuplexPort : public InputPort, public OutputPort +{ +public: + DuplexPort(NodeImpl* parent, + const std::string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size, + bool is_output); + + virtual ~DuplexPort() {} + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + bool is_input() const { return !_is_output; } + bool is_output() const { return _is_output; } + +protected: + bool _is_output; +}; + + +} // namespace Ingen + +#endif // DUPLEXPORT_H diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp new file mode 100644 index 00000000..a08a37ca --- /dev/null +++ b/src/engine/Engine.cpp @@ -0,0 +1,317 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <sys/mman.h> +#include <iostream> +#include <unistd.h> +#include <raul/Deletable.hpp> +#include <raul/Maid.hpp> +#include <raul/SharedPtr.hpp> +#include "Engine.hpp" +#include CONFIG_H_PATH +#include "tuning.hpp" +#include "Event.hpp" +#include "common/interface/EventType.hpp" +#include "shared/Store.hpp" +#include "JackAudioDriver.hpp" +#include "NodeFactory.hpp" +#include "ClientBroadcaster.hpp" +#include "PatchImpl.hpp" +#include "EngineStore.hpp" +#include "MidiDriver.hpp" +#include "OSCDriver.hpp" +#include "QueuedEventSource.hpp" +#include "PostProcessor.hpp" +#include "CreatePatchEvent.hpp" +#include "EnablePatchEvent.hpp" +#include "OSCEngineReceiver.hpp" +#ifdef HAVE_SOUP +#include "HTTPEngineReceiver.hpp" +#endif +#include "PostProcessor.hpp" +#include "ProcessSlave.hpp" +#include "ProcessContext.hpp" +#include "MessageContext.hpp" +#include "ThreadManager.hpp" +#ifdef HAVE_JACK_MIDI +#include "JackMidiDriver.hpp" +#endif +using namespace std; + +namespace Ingen { + + +Engine::Engine(Ingen::Shared::World* world) + : _world(world) + , _midi_driver(NULL) + , _osc_driver(NULL) + , _maid(new Raul::Maid(maid_queue_size)) + , _post_processor(new PostProcessor(*this, /**_maid, */post_processor_queue_size)) + , _broadcaster(new ClientBroadcaster()) + , _node_factory(new NodeFactory(world)) + , _message_context(new MessageContext(*this)) + , _quit_flag(false) + , _activated(false) +{ + if (world->store) { + assert(PtrCast<EngineStore>(world->store)); + } else { + world->store = SharedPtr<Store>(new EngineStore()); + } +} + + +Engine::~Engine() +{ + deactivate(); + + for (EngineStore::iterator i = engine_store()->begin(); + i != engine_store()->end(); ++i) { + if ( ! PtrCast<GraphObjectImpl>(i->second)->parent() ) + i->second.reset(); + } + + delete _broadcaster; + delete _node_factory; + delete _osc_driver; + delete _post_processor; + //delete _lash_driver; + + delete _maid; + + munlockall(); +} + + +SharedPtr<EngineStore> +Engine::engine_store() const +{ + return PtrCast<EngineStore>(_world->store); +} + + +Driver* +Engine::driver(DataType type, EventType event_type) +{ + if (type == DataType::AUDIO) { + return _audio_driver.get(); + } else if (type == DataType::EVENT) { + if (event_type == EventType::MIDI) { + return _midi_driver; + } else if (event_type == EventType::OSC) { + return _osc_driver; + } + } + + return NULL; +} + + +int +Engine::main() +{ + Thread::get().set_context(THREAD_POST_PROCESS); + + // Loop until quit flag is set (by OSCReceiver) + while ( ! _quit_flag) { + nanosleep(&main_rate, NULL); + main_iteration(); + } + cout << "[Main] Done main loop." << endl; + + deactivate(); + + return 0; +} + + +/** Run one iteration of the main loop. + * + * NOT realtime safe (this is where deletion actually occurs) + */ +bool +Engine::main_iteration() +{ +/*#ifdef HAVE_LASH + // Process any pending LASH events + if (lash_driver->enabled()) + lash_driver->process_events(); +#endif*/ + // Run the maid (garbage collector) + _post_processor->process(); + _maid->cleanup(); + + return !_quit_flag; +} + + +void +Engine::start_jack_driver() +{ + if ( ! _audio_driver) + _audio_driver = SharedPtr<AudioDriver>(new JackAudioDriver(*this)); + else + cerr << "[Engine::start_jack_driver] Audio driver already running" << endl; +} + + +void +Engine::start_osc_driver(int port) +{ + if (_event_source) { + cerr << "WARNING: Replacing event source" << endl; + _event_source.reset(); + } + + _event_source = SharedPtr<EventSource>(new OSCEngineReceiver( + *this, pre_processor_queue_size, port)); +} + + +void +Engine::start_http_driver(int port) +{ +#ifdef HAVE_SOUP + // FIXE: leak + HTTPEngineReceiver* server = new HTTPEngineReceiver(*this, port); + server->activate(); +#endif +} + + +SharedPtr<QueuedEngineInterface> +Engine::new_queued_interface() +{ + if (_event_source) { + cerr << "WARNING: Replacing event source" << endl; + _event_source.reset(); + } + + SharedPtr<QueuedEngineInterface> result(new QueuedEngineInterface( + *this, Ingen::event_queue_size, Ingen::event_queue_size)); + + _event_source = result; + + return result; +} + +/* +void +Engine::set_event_source(SharedPtr<EventSource> source) +{ + if (_event_source) + cerr << "Warning: Dropped event source (engine interface)" << endl; + + _event_source = source; +} +*/ + + +bool +Engine::activate(size_t parallelism) +{ + if (_activated) + return false; + + assert(_audio_driver); + assert(_event_source); + +#ifdef HAVE_JACK_MIDI + _midi_driver = new JackMidiDriver(((JackAudioDriver*)_audio_driver.get())->jack_client()); +#else + _midi_driver = new DummyMidiDriver(); +#endif + + _event_source->activate(); + + // Create root patch + + PatchImpl* root_patch = new PatchImpl(*this, "", 1, NULL, + _audio_driver->sample_rate(), _audio_driver->buffer_size(), 1); + root_patch->activate(); + _world->store->add(root_patch); + root_patch->compiled_patch(root_patch->compile()); + + assert(_audio_driver->root_patch() == NULL); + _audio_driver->set_root_patch(root_patch); + + _audio_driver->activate(); + + _process_slaves.clear(); + _process_slaves.reserve(parallelism); + for (size_t i=0; i < parallelism - 1; ++i) + _process_slaves.push_back(new ProcessSlave(*this, _audio_driver->is_realtime())); + + root_patch->enable(); + + //_post_processor->start(); + + _activated = true; + + return true; +} + + +void +Engine::deactivate() +{ + if (!_activated) + return; + + _event_source->deactivate(); + + /*for (Tree<GraphObject*>::iterator i = _engine_store->objects().begin(); + i != _engine_store->objects().end(); ++i) + if ((*i)->as_node() != NULL && (*i)->as_node()->parent() == NULL) + (*i)->as_node()->deactivate();*/ + + if (_midi_driver != NULL) { + _midi_driver->deactivate(); + delete _midi_driver; + _midi_driver = NULL; + } + + _audio_driver->deactivate(); + + _audio_driver->root_patch()->deactivate(); + + for (size_t i=0; i < _process_slaves.size(); ++i) { + delete _process_slaves[i]; + } + + _process_slaves.clear(); + + // Finalize any lingering events (unlikely) + _post_processor->process(); + + _audio_driver.reset(); + _event_source.reset(); + + _activated = false; +} + + +void +Engine::process_events(ProcessContext& context) +{ + if (_event_source) + _event_source->process(*_post_processor, context); +} + + +} // namespace Ingen diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp new file mode 100644 index 00000000..fce826db --- /dev/null +++ b/src/engine/Engine.hpp @@ -0,0 +1,130 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ENGINE_H +#define ENGINE_H + +#include CONFIG_H_PATH +#include <cassert> +#include <vector> +#include <boost/utility.hpp> +#include <raul/SharedPtr.hpp> +#include "module/global.hpp" +#include "interface/DataType.hpp" +#include "interface/EventType.hpp" + +template<typename T> class Queue; + +namespace Raul { class Maid; } + +namespace Ingen { + +class AudioDriver; +class MidiDriver; +class OSCDriver; +class NodeFactory; +class ClientBroadcaster; +class EngineStore; +class EventSource; +class PostProcessor; +class Event; +class QueuedEvent; +class QueuedEngineInterface; +class Driver; +class ProcessSlave; +class ProcessContext; +class MessageContext; + + +/** The main class for the Engine. + * + * This is a (GoF) facade for the engine. Pointers to all components are + * available for more advanced control than this facade allows. + * + * \ingroup engine + */ +class Engine : boost::noncopyable +{ +public: + Engine(Ingen::Shared::World* world); + + virtual ~Engine(); + + virtual int main(); + virtual bool main_iteration(); + + /** Set the quit flag that should kill all threads and exit cleanly. + * Note that it will take some time. */ + virtual void quit() { _quit_flag = true; } + + virtual void start_jack_driver(); + virtual void start_osc_driver(int port); + virtual void start_http_driver(int port); + + virtual SharedPtr<QueuedEngineInterface> new_queued_interface(); + + virtual bool activate(size_t parallelism); + virtual void deactivate(); + + void process_events(ProcessContext& context); + + virtual bool activated() { return _activated; } + + Raul::Maid* maid() const { return _maid; } + EventSource* event_source() const { return _event_source.get(); } + AudioDriver* audio_driver() const { return _audio_driver.get(); } + MidiDriver* midi_driver() const { return _midi_driver; } + OSCDriver* osc_driver() const { return _osc_driver; } + PostProcessor* post_processor() const { return _post_processor; } + ClientBroadcaster* broadcaster() const { return _broadcaster; } + NodeFactory* node_factory() const { return _node_factory; } + MessageContext* message_context() const { return _message_context; } + + SharedPtr<EngineStore> engine_store() const; + + /** Return the active driver for the given type */ + Driver* driver(DataType type, EventType event_type); + + Ingen::Shared::World* world() { return _world; } + + typedef std::vector<ProcessSlave*> ProcessSlaves; + inline const ProcessSlaves& process_slaves() const { return _process_slaves; } + inline ProcessSlaves& process_slaves() { return _process_slaves; } + +private: + ProcessSlaves _process_slaves; + + Ingen::Shared::World* _world; + + SharedPtr<EventSource> _event_source; + SharedPtr<AudioDriver> _audio_driver; + MidiDriver* _midi_driver; + OSCDriver* _osc_driver; + Raul::Maid* _maid; + PostProcessor* _post_processor; + ClientBroadcaster* _broadcaster; + NodeFactory* _node_factory; + MessageContext* _message_context; + + bool _quit_flag; + bool _activated; +}; + + +} // namespace Ingen + +#endif // ENGINE_H diff --git a/src/engine/EngineStore.cpp b/src/engine/EngineStore.cpp new file mode 100644 index 00000000..9fcd3806 --- /dev/null +++ b/src/engine/EngineStore.cpp @@ -0,0 +1,182 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <utility> +#include <vector> +#include <raul/List.hpp> +#include <raul/PathTable.hpp> +#include <raul/TableImpl.hpp> +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "ThreadManager.hpp" + +using namespace std; +using namespace Raul; + +namespace Ingen { + + +/** Find the Patch at the given path. + */ +PatchImpl* +EngineStore::find_patch(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<PatchImpl*>(object); +} + + +/** Find the Node at the given path. + */ +NodeImpl* +EngineStore::find_node(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<NodeImpl*>(object); +} + + +/** Find the Port at the given path. + */ +PortImpl* +EngineStore::find_port(const Path& path) +{ + GraphObjectImpl* const object = find_object(path); + return dynamic_cast<PortImpl*>(object); +} + + +/** Find the Object at the given path. + */ +GraphObjectImpl* +EngineStore::find_object(const Path& path) +{ + iterator i = find(path); + return ((i == end()) ? NULL : dynamic_cast<GraphObjectImpl*>(i->second.get())); +} + + +/** Add an object to the store. Not realtime safe. + */ +void +EngineStore::add(GraphObject* obj) +{ + GraphObjectImpl* o = dynamic_cast<GraphObjectImpl*>(obj); + assert(o); + + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + Store::add(obj); +} + + +/** Add a family of objects to the store. Not realtime safe. + */ +void +EngineStore::add(const Objects& table) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + //cerr << "[EngineStore] Adding " << o[0].second->path() << endl; + cram(table); + + /*cerr << "[EngineStore] Adding Table:" << endl; + for (const_iterator i = table.begin(); i != table.end(); ++i) { + cerr << i->first << " = " << i->second->path() << endl; + }*/ +} + + +/** Remove an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * including the object itself, in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove(const Path& path) +{ + return remove(find(path)); +} + + +/** Remove an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * including the object itself, in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove(iterator object) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + if (object != end()) { + iterator descendants_end = find_descendants_end(object); + //cout << "[EngineStore] Removing " << object->first << " {" << endl; + SharedPtr<Objects> removed = yank(object, descendants_end); + /*for (iterator i = removed->begin(); i != removed->end(); ++i) { + cout << "\t" << i->first << endl; + } + cout << "}" << endl;*/ + + return removed; + + } else { + cerr << "[EngineStore] WARNING: Removing " << object->first << " failed." << endl; + return SharedPtr<EngineStore>(); + } +} + + +/** Remove all children of an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove_children(const Path& path) +{ + return remove_children(find(path)); +} + + +/** Remove all children of an object from the store. + * + * Returned is a vector containing all descendants of the object removed + * in lexicographically sorted order by Path. + */ +SharedPtr<EngineStore::Objects> +EngineStore::remove_children(iterator object) +{ + if (object != end()) { + iterator descendants_end = find_descendants_end(object); + if (descendants_end != object) { + iterator first_child = object; + ++first_child; + return yank(first_child, descendants_end); + } + } else { + cerr << "[EngineStore] WARNING: Removing children of " << object->first << " failed." << endl; + return SharedPtr<EngineStore::Objects>(); + } + + return SharedPtr<EngineStore::Objects>(); +} + + +} // namespace Ingen diff --git a/src/engine/EngineStore.hpp b/src/engine/EngineStore.hpp new file mode 100644 index 00000000..ad656aeb --- /dev/null +++ b/src/engine/EngineStore.hpp @@ -0,0 +1,68 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OBJECTSTORE_H +#define OBJECTSTORE_H + +#include <string> +#include <raul/PathTable.hpp> +#include <raul/SharedPtr.hpp> +#include "shared/Store.hpp" + +using std::string; +using namespace Raul; + +namespace Ingen { + +namespace Shared { class GraphObject; } + +class PatchImpl; +class NodeImpl; +class PortImpl; +class GraphObjectImpl; + + +/** Storage for all GraphObjects (tree of GraphObject'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 GraphObject + * tree). + * + * Searching with find*() is fast (O(log(n)) binary search on contiguous + * memory) and realtime safe, but modification (add or remove) are neither. + */ +class EngineStore : public Shared::Store +{ +public: + PatchImpl* find_patch(const Path& path); + NodeImpl* find_node(const Path& path); + PortImpl* find_port(const Path& path); + GraphObjectImpl* find_object(const Path& path); + + void add(Shared::GraphObject* o); + void add(const Objects& family); + + SharedPtr<Objects> remove(const Path& path); + SharedPtr<Objects> remove(Objects::iterator i); + SharedPtr<Objects> remove_children(const Path& path); + SharedPtr<Objects> remove_children(Objects::iterator i); +}; + + +} // namespace Ingen + +#endif // OBJECTSTORE diff --git a/src/engine/Event.cpp b/src/engine/Event.cpp new file mode 100644 index 00000000..8e5c33da --- /dev/null +++ b/src/engine/Event.cpp @@ -0,0 +1,49 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "Event.hpp" +#include "ThreadManager.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { + + +void +Event::execute(ProcessContext& context) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + assert(!_executed); + assert(_time <= context.end()); + + // Missed the event, jitter, damnit. + if (_time < context.start()) + _time = context.start(); + + _executed = true; +} + + +void +Event::post_process() +{ + // FIXME: Not true witn monolithic GUI/engine + //assert(ThreadManager::current_thread_id() == THREAD_POST_PROCESS); +} + + +} // namespace Ingen + diff --git a/src/engine/Event.hpp b/src/engine/Event.hpp new file mode 100644 index 00000000..5860da2a --- /dev/null +++ b/src/engine/Event.hpp @@ -0,0 +1,75 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVENT_H +#define EVENT_H + +#include <cassert> +#include <raul/SharedPtr.hpp> +#include <raul/Deletable.hpp> +#include "types.hpp" + +namespace Ingen { + +class Engine; +class Responder; +class ProcessContext; + + +/** 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 Raul::Deletable +{ +public: + virtual ~Event() {} + + /** Execute this event in the audio thread (MUST be realtime safe). */ + virtual void execute(ProcessContext& context); + + /** Perform any actions after execution (ie send replies to commands) + * (no realtime requirements). */ + virtual void post_process(); + + inline SampleCount time() const { return _time; } + +protected: + Event(Engine& engine, SharedPtr<Responder> responder, FrameTime time) + : _engine(engine) + , _responder(responder) + , _time(time) + , _executed(false) + {} + + Engine& _engine; + SharedPtr<Responder> _responder; + FrameTime _time; + bool _executed; +}; + + +} // namespace Ingen + +#endif // EVENT_H diff --git a/src/engine/EventBuffer.cpp b/src/engine/EventBuffer.cpp new file mode 100644 index 00000000..e8584597 --- /dev/null +++ b/src/engine/EventBuffer.cpp @@ -0,0 +1,318 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define __STDC_LIMIT_MACROS 1 +#include <stdint.h> +#include <iostream> +#include "EventBuffer.hpp" +#include "lv2ext/lv2_event.h" +#include "lv2ext/lv2_event_helpers.h" + +using namespace std; + +namespace Ingen { + + +/** Allocate a new event buffer. + * \a capacity is in bytes (not number of events). + */ +EventBuffer::EventBuffer(size_t capacity) + : Buffer(DataType(DataType::EVENT), capacity) + , _latest_frames(0) + , _latest_subframes(0) +{ + if (capacity > UINT32_MAX) { + cerr << "Event buffer size " << capacity << " too large, aborting." << endl; + throw std::bad_alloc(); + } + + int ret = posix_memalign((void**)&_local_buf, 16, sizeof(LV2_Event_Buffer) + capacity); + if (ret) { + cerr << "Failed to allocate event buffer. Aborting." << endl; + exit(EXIT_FAILURE); + } + + _local_buf->event_count = 0; + _local_buf->capacity = (uint32_t)capacity; + _local_buf->size = 0; + _local_buf->data = reinterpret_cast<uint8_t*>(_local_buf + 1); + _buf = _local_buf; + + reset(0); + + //cerr << "Creating MIDI Buffer " << _buf << ", capacity = " << _buf->capacity << endl; +} + + +EventBuffer::~EventBuffer() +{ + free(_local_buf); +} + + +/** Use another buffer's data instead of the local one. + * + * This buffer will essentially be identical to @a buf after this call. + */ +bool +EventBuffer::join(Buffer* buf) +{ + EventBuffer* mbuf = dynamic_cast<EventBuffer*>(buf); + if (mbuf) { + _buf = mbuf->local_data(); + _joined_buf = mbuf; + _iter = mbuf->_iter; + _iter.buf = _buf; + return false; + } else { + return false; + } + + //assert(mbuf->size() == _size); + + _joined_buf = mbuf; + + return true; +} + + +void +EventBuffer::unjoin() +{ + _joined_buf = NULL; + _buf = _local_buf; + reset(_this_nframes); +} + + +void +EventBuffer::prepare_read(FrameTime start, SampleCount nframes) +{ + //cerr << "\t" << this << " prepare_read: " << event_count() << endl; + rewind(); + _this_nframes = nframes; +} + + +void +EventBuffer::prepare_write(FrameTime start, SampleCount nframes) +{ + //cerr << "\t" << this << " prepare_write: " << event_count() << endl; + reset(nframes); +} + +/** FIXME: parameters ignored */ +void +EventBuffer::copy(const Buffer* src_buf, size_t start_sample, size_t end_sample) +{ + const EventBuffer* src = dynamic_cast<const EventBuffer*>(src_buf); + assert(src); + assert(_buf->capacity >= src->_buf->capacity); + + clear(); + src->rewind(); + + memcpy(_buf, src->_buf, src->_buf->size); +} + + +/** Increment the read position by one event. + * + * \return true if increment was successful, or false if end of buffer reached. + */ +bool +EventBuffer::increment() const +{ + if (lv2_event_is_valid(&_iter)) { + lv2_event_increment(&_iter); + return true; + } else { + return false; + } +} + + +/** + * \return true iff the cursor is valid (ie get_event is safe) + */ +bool +EventBuffer::is_valid() const +{ + return lv2_event_is_valid(&_iter); +} + + +/** Append an event to the buffer. + * + * \a timestamp must be >= the latest event in the buffer, + * and < this_nframes() + * + * \return true on success + */ +bool +EventBuffer::append(uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size, + const uint8_t* data) +{ +#ifndef NDEBUG + if (lv2_event_is_valid(&_iter)) { + LV2_Event* last_event = lv2_event_get(&_iter, NULL); + assert(last_event->frames < frames + || (last_event->frames == frames && last_event->subframes <= subframes)); + } +#endif + + /*cout << "Appending event type " << type << ", size " << size + << " @ " << frames << "." << subframes << endl;*/ + + const bool ret = lv2_event_write(&_iter, frames, subframes, type, size, data); + + if (!ret) + cerr << "ERROR: Failed to write event." << endl; + + _latest_frames = frames; + _latest_subframes = subframes; + + return ret; +} + + +/** Append a buffer of events to the buffer. + * + * \a timestamp must be >= the latest event in the buffer, + * and < this_nframes() + * + * \return true on success + */ +bool +EventBuffer::append(const LV2_Event_Buffer* buf) +{ + uint8_t** data; + bool ret = true; + + LV2_Event_Iterator iter; + for (lv2_event_begin(&iter, _buf); lv2_event_is_valid(&iter); lv2_event_increment(&iter)) { + LV2_Event* ev = lv2_event_get(&iter, data); + +#ifndef NDEBUG + assert((ev->frames > _latest_frames) + || (ev->frames == _latest_frames + && ev->subframes >= _latest_subframes)); +#endif + + if (!(ret = append(ev->frames, ev->subframes, ev->type, ev->size, *data))) { + cerr << "ERROR: Failed to write event." << endl; + break; + } + + _latest_frames = ev->frames; + _latest_subframes = ev->subframes; + } + + return ret; +} + + +/** Read an event from the current position in the buffer + * + * \return true if read was successful, or false if end of buffer reached + */ +bool +EventBuffer::get_event(uint32_t* frames, + uint32_t* subframes, + uint16_t* type, + uint16_t* size, + uint8_t** data) const +{ + if (lv2_event_is_valid(&_iter)) { + LV2_Event* ev = lv2_event_get(&_iter, data); + *frames = ev->frames; + *subframes = ev->subframes; + *type = ev->type; + *size = ev->size; + return true; + } else { + return false; + } +} + + +/** Clear, and merge \a a and \a b into this buffer. + * + * FIXME: This is slow. + * + * \return true if complete merge was successful + */ +bool +EventBuffer::merge(const EventBuffer& a, const EventBuffer& b) +{ + // Die if a merge isn't necessary as it's expensive + assert(a.size() > 0 && b.size() > 0); + + reset(_this_nframes); + + a.rewind(); + b.rewind(); + +#if 0 + uint32_t a_frames; + uint32_t a_subframes; + uint16_t a_type; + uint16_t a_size; + uint8_t* a_data; + + uint32_t b_frames; + uint32_t b_subframes; + uint16_t b_type; + uint16_t b_size; + uint8_t* b_data; +#endif + + cout << "FIXME: merge" << endl; +#if 0 + a.get_event(&a_frames, &a_subframes, &a_type, &a_size, &a_data); + b.get_event(&b_frames, &b_subframes, &b_type, &b_size, &b_data); + + while (true) { + if (a_data && (!b_data || (a_time < b_time))) { + append(a_time, a_size, a_data); + if (a.increment()) + a.get_event(&a_time, &a_size, &a_data); + else + a_data = NULL; + } else if (b_data) { + append(b_time, b_size, b_data); + if (b.increment()) + b.get_event(&b_time, &b_size, &b_data); + else + b_data = NULL; + } else { + break; + } + } + + _latest_stamp = max(a_time, b_time); +#endif + + return true; +} + + +} // namespace Ingen + diff --git a/src/engine/EventBuffer.hpp b/src/engine/EventBuffer.hpp new file mode 100644 index 00000000..0c80d452 --- /dev/null +++ b/src/engine/EventBuffer.hpp @@ -0,0 +1,104 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVENTBUFFER_H +#define EVENTBUFFER_H + +#include <iostream> +#include <lv2ext/lv2_event.h> +#include <lv2ext/lv2_event_helpers.h> +#include "Buffer.hpp" +#include "interface/DataType.hpp" + +namespace Ingen { + + +class EventBuffer : public Buffer { +public: + EventBuffer(size_t capacity); + + ~EventBuffer(); + + void prepare_read(FrameTime start, SampleCount nframes); + void prepare_write(FrameTime start, SampleCount nframes); + + bool join(Buffer* buf); + void unjoin(); + + inline uint32_t this_nframes() const { return _this_nframes; } + inline uint32_t event_count() const { return _buf->event_count; } + + inline void* raw_data() { return _buf; } + inline const void* raw_data() const { return _buf; } + + inline LV2_Event_Buffer* local_data() { return _local_buf; } + inline const LV2_Event_Buffer* local_data() const { return _local_buf; } + + inline LV2_Event_Buffer* data() { return _buf; } + inline const LV2_Event_Buffer* data() const { return _buf; } + + void copy(const Buffer* src, size_t start_sample, size_t end_sample); + + inline void rewind() const { lv2_event_begin(&_iter, _buf); } + inline void clear() { reset(_this_nframes); } + inline void reset(SampleCount nframes) { + //std::cerr << this << " reset" << std::endl; + _this_nframes = nframes; + _latest_frames = 0; + _latest_subframes = 0; + _buf->event_count = 0; + _buf->size = 0; + rewind(); + } + + bool increment() const; + bool is_valid() const; + + uint32_t latest_frames() const { return _latest_frames; } + uint32_t latest_subframes() const { return _latest_subframes; } + + bool get_event(uint32_t* frames, + uint32_t* subframes, + uint16_t* type, + uint16_t* size, + uint8_t** data) const; + + bool append(uint32_t frames, + uint32_t subframes, + uint16_t type, + uint16_t size, + const uint8_t* data); + + bool append(const LV2_Event_Buffer* buf); + + bool merge(const EventBuffer& a, const EventBuffer& b); + +private: + LV2_Event_Buffer* _buf; ///< Contents (maybe belong to _joined_buf) + LV2_Event_Buffer* _local_buf; ///< Local contents + + mutable LV2_Event_Iterator _iter; ///< Iterator into _buf + + uint32_t _latest_frames; ///< Latest time of all events (frames) + uint32_t _latest_subframes; ///< Latest time of all events (subframes) + uint32_t _this_nframes; ///< Current cycle nframes +}; + + +} // namespace Ingen + +#endif // EVENTBUFFER_H diff --git a/src/engine/EventSink.cpp b/src/engine/EventSink.cpp new file mode 100644 index 00000000..6c775d4c --- /dev/null +++ b/src/engine/EventSink.cpp @@ -0,0 +1,74 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include "events/SendPortValueEvent.hpp" +#include "EventSink.hpp" +#include "PortImpl.hpp" + +using namespace std; + +namespace Ingen { + +#if 0 +void +EventSink::control_change(Port* port, FrameTime time, float val) +{ + //cerr << "CONTROL CHANGE: " << port->path() << " == " << val << endl; + SendPortValueEvent ev(_engine, time, port, false, 0, val); + _events.write(sizeof(ev), (uchar*)&ev); +} +#endif + +/** \a size is not size_t because an event will never be even remotely close + * to UINT32_MAX in size, so uint32_t saves wasted space on 64-bit. + */ +bool +EventSink::write(uint32_t size, const Event* ev) +{ + if (size > _events.write_space()) + return false; + + _events.write(sizeof(uint32_t), (uint8_t*)&size); + _events.write(size, (uint8_t*)ev); + + return true; +} + + +/** Read the next event into event_buffer. + * + * \a event_buffer can be casted to Event* and virtual methods called. + */ +bool +EventSink::read(uint32_t event_buffer_size, uint8_t* event_buffer) +{ + uint32_t read_size; + bool success = _events.full_read(sizeof(uint32_t), (uint8_t*)&read_size); + if (!success) + return false; + + assert(read_size <= event_buffer_size); + + if (read_size > 0) + return _events.full_read(read_size, event_buffer); + else + return false; +} + + +} // namespace Ingen diff --git a/src/engine/EventSink.hpp b/src/engine/EventSink.hpp new file mode 100644 index 00000000..9e937d1a --- /dev/null +++ b/src/engine/EventSink.hpp @@ -0,0 +1,64 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVENTSINK_H +#define EVENTSINK_H + +#include <list> +#include <utility> +#include <raul/RingBuffer.hpp> +#include "events/SendPortValueEvent.hpp" +#include "types.hpp" + +namespace Ingen { + +class PortImpl; +class Engine; +class SendPortValueEvent; + + +/** Sink for events generated in the audio thread. + * + * Implemented as a flat ringbuffer of events, which are constructed directly + * in the ringbuffer rather than allocated on the heap (in order to make + * writing realtime safe). + * + * \ingroup engine + */ +class EventSink +{ +public: + EventSink(Engine& engine, size_t capacity) : _engine(engine), _events(capacity) {} + + /* FIXME: Figure out variable sized event queues and make this a generic + * interface (ie don't add a method for every event type, crap..) */ + + bool write(uint32_t size, const Event* ev); + + bool read(uint32_t event_buffer_size, uint8_t* event_buffer); + +private: + Engine& _engine; + Raul::RingBuffer<uchar> _events; +}; + + + +} // namespace Ingen + +#endif // EVENTSINK_H + diff --git a/src/engine/EventSource.hpp b/src/engine/EventSource.hpp new file mode 100644 index 00000000..68532d16 --- /dev/null +++ b/src/engine/EventSource.hpp @@ -0,0 +1,60 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVENTSOURCE_H +#define EVENTSOURCE_H + +#include "types.hpp" + +namespace Ingen { + +class Event; +class QueuedEvent; +class PostProcessor; + + +/** 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). + * + * There are two distinct classes of events - "queued" and "stamped". Queued + * events are events that require non-realtime pre-processing before being + * executed in the process thread. Stamped events are timestamped realtime + * events that require no pre-processing and can be executed immediately + * (with sample accuracy). + */ +class EventSource +{ +public: + virtual ~EventSource() {} + + virtual void activate() = 0; + virtual void deactivate() = 0; + + virtual void process(PostProcessor& dest, ProcessContext& context) = 0; + +protected: + size_t _capacity; +}; + + +} // namespace Ingen + +#endif // EVENTSOURCE_H + diff --git a/src/engine/GraphObjectImpl.cpp b/src/engine/GraphObjectImpl.cpp new file mode 100644 index 00000000..0e1abc57 --- /dev/null +++ b/src/engine/GraphObjectImpl.cpp @@ -0,0 +1,39 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "GraphObjectImpl.hpp" +#include "PatchImpl.hpp" +#include "EngineStore.hpp" + +namespace Ingen { + + +PatchImpl* +GraphObjectImpl::parent_patch() const +{ + return dynamic_cast<PatchImpl*>((NodeImpl*)_parent); +} + + +SharedPtr<GraphObject> +GraphObjectImpl::find_child(const string& name) const +{ + throw; +} + + +} // namespace Ingen diff --git a/src/engine/GraphObjectImpl.hpp b/src/engine/GraphObjectImpl.hpp new file mode 100644 index 00000000..9b1f675d --- /dev/null +++ b/src/engine/GraphObjectImpl.hpp @@ -0,0 +1,132 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GRAPHOBJECTIMPL_H +#define GRAPHOBJECTIMPL_H + +#include <string> +#include <map> +#include <cstddef> +#include <cassert> +#include <raul/Deletable.hpp> +#include <raul/Path.hpp> +#include <raul/Atom.hpp> +#include "interface/GraphObject.hpp" +#include "types.hpp" + +using Raul::Atom; +using Raul::Path; +using Raul::Symbol; + +namespace Raul { class Maid; } + +namespace Ingen { + +class PatchImpl; +class ProcessContext; + + +/** An object on the audio graph - Patch, Node, Port, etc. + * + * Each of these is a Raul::Deletable and so can be deleted in a realtime safe + * way from anywhere, and they all have a map of variable for clients to store + * arbitrary values in (which the engine puts no significance to whatsoever). + * + * \ingroup engine + */ +class GraphObjectImpl : virtual public Ingen::Shared::GraphObject +{ +public: + virtual ~GraphObjectImpl() {} + + bool polyphonic() const { return _polyphonic; } + virtual bool set_polyphonic(Raul::Maid& maid, bool p) { _polyphonic = p; return true; } + + GraphObject* graph_parent() const { return _parent; } + + inline GraphObjectImpl* parent() const { return _parent; } + const Symbol symbol() const { return _name; } + + virtual void process(ProcessContext& context) = 0; + + /** Rename */ + virtual void set_path(const Path& new_path) { + assert(new_path.parent() == path().parent()); + _name = new_path.name(); + assert(_name.find("/") == std::string::npos); + } + + void set_variable(const std::string& key, const Atom& value) + { _variables[key] = value; } + + void set_property(const std::string& key, const Atom& value) + { _properties[key] = value; } + + const Atom& get_variable(const std::string& key) { + static Atom null_atom; + Variables::iterator i = _variables.find(key); + return (i != _variables.end()) ? (*i).second : null_atom; + } + + const Atom& get_property(const std::string& key) { + static Atom null_atom; + Properties::iterator i = _properties.find(key); + return (i != _properties.end()) ? (*i).second : null_atom; + } + + const Variables& variables() const { return _variables; } + const Properties& properties() const { return _properties; } + Variables& variables() { return _variables; } + Properties& properties() { return _properties; } + + /** The Patch this object is a child of. */ + virtual PatchImpl* parent_patch() const; + + /** Path is dynamically generated from parent to ease renaming */ + const Path path() const { + if (_parent == NULL) + return Path(std::string("/").append(_name)); + else if (_parent->path() == "/") + return Path(std::string("/").append(_name)); + else + return Path(_parent->path() +"/"+ _name); + } + + SharedPtr<GraphObject> find_child(const std::string& name) const; + +protected: + GraphObjectImpl(GraphObjectImpl* parent, const std::string& name, bool polyphonic=false) + : _parent(parent), _name(name), _polyphonic(polyphonic) + { + assert(parent == NULL || _name.length() > 0); + assert(_name.find("/") == std::string::npos); + assert(path().find("//") == std::string::npos); + } + + GraphObjectImpl* _parent; + std::string _name; + bool _polyphonic; + +private: + Variables _variables; + Properties _properties; +}; + + +} // namespace Ingen + +#endif // GRAPHOBJECTIMPL_H diff --git a/src/engine/HTTPEngineReceiver.cpp b/src/engine/HTTPEngineReceiver.cpp new file mode 100644 index 00000000..1b21e184 --- /dev/null +++ b/src/engine/HTTPEngineReceiver.cpp @@ -0,0 +1,207 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cstdlib> +#include <cstdio> +#include <string> +#include <boost/format.hpp> +#include "types.hpp" +#include <raul/SharedPtr.hpp> +#include <raul/AtomLiblo.hpp> +#include "interface/ClientInterface.hpp" +#include "module/Module.hpp" +#include "serialisation/serialisation.hpp" +#include "serialisation/Serialiser.hpp" +#include "serialisation/Parser.hpp" +#include "engine/ThreadManager.hpp" +#include "HTTPEngineReceiver.hpp" +#include "QueuedEventSource.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" + +using namespace std; +using namespace Ingen::Shared; + +namespace Ingen { + + +HTTPEngineReceiver::HTTPEngineReceiver(Engine& engine, uint16_t port) + : QueuedEngineInterface(engine, 2, 2) + , _server(soup_server_new(SOUP_SERVER_PORT, port, NULL)) +{ + _receive_thread = new ReceiveThread(*this); + + soup_server_add_handler(_server, NULL, message_callback, this, NULL); + + cout << "Started HTTP server on port " << soup_server_get_port(_server) << endl; + Thread::set_name("HTTP receiver"); + + if (!engine.world()->serialisation_module) + engine.world()->serialisation_module = Ingen::Shared::load_module("ingen_serialisation"); + + if (engine.world()->serialisation_module) { + if (!engine.world()->serialiser) + engine.world()->serialiser = SharedPtr<Serialiser>( + Ingen::Serialisation::new_serialiser(engine.world(), engine.engine_store())); + + if (!engine.world()->parser) + engine.world()->parser = SharedPtr<Parser>( + Ingen::Serialisation::new_parser()); + } else { + cerr << "WARNING: Failed to load ingen_serialisation module, HTTP disabled." << endl; + } +} + + +HTTPEngineReceiver::~HTTPEngineReceiver() +{ + deactivate(); + + if (_server != NULL) { + soup_server_quit(_server); + _server = NULL; + } +} + + +void +HTTPEngineReceiver::activate() +{ + QueuedEventSource::activate(); + _receive_thread->set_name("HTTP Receiver"); + _receive_thread->start(); +} + + +void +HTTPEngineReceiver::deactivate() +{ + cout << "[HTTPEngineReceiver] Stopped HTTP listening thread" << endl; + _receive_thread->stop(); + QueuedEventSource::deactivate(); +} + + +void +HTTPEngineReceiver::message_callback(SoupServer* server, SoupMessage* msg, const char* path, + GHashTable *query, SoupClientContext* client, void* data) +{ + HTTPEngineReceiver* me = (HTTPEngineReceiver*)data; + + SharedPtr<Store> store = me->_engine.world()->store; + if (!store) { + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + if (!Path::is_valid(path)) { + soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); + const string& err = (boost::format("Bad path: %1%") % path).str(); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY, + err.c_str(), err.length()); + return; + } + + if (msg->method == SOUP_METHOD_GET) { + Glib::RWLock::ReaderLock lock(store->lock()); + + // Find object + Store::const_iterator start = store->find(path); + if (start == store->end()) { + soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); + const string& err = (boost::format("No such object: %1%") % path).str(); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY, + err.c_str(), err.length()); + return; + } + + // Get serialiser + SharedPtr<Serialiser> serialiser = me->_engine.world()->serialiser; + if (!serialiser) { + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, + "No serialiser available\n", 24); + return; + } + +#if 0 + SoupMessageHeaders* in_head = msg->request_headers; + const char* str = soup_message_headers_get(in_head, "Accept"); + cout << "Accept: " << str << endl; +#endif + + // Serialise object + const string response = serialiser->to_string(start->second, + "http://example.org", GraphObject::Variables()); + +#if 0 + FILE* xhtml_file = fopen("/home/dave/ingen_ui.xhtml", "r"); + string response; + while (!feof(xhtml_file)) { + int c = fgetc(xhtml_file); + if (c != EOF) + response += (char)c; + } + fclose(xhtml_file); +#endif + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY, + response.c_str(), response.length()); + + } else if (msg->method == SOUP_METHOD_PUT) { + Glib::RWLock::WriterLock lock(store->lock()); + + // Be sure object doesn't exist + Store::const_iterator start = store->find(path); + if (start != store->end()) { + soup_message_set_status (msg, SOUP_STATUS_CONFLICT); + return; + } + + // Get parser + SharedPtr<Parser> parser = me->_engine.world()->parser; + if (!parser) { + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + //cout << "POST: " << msg->request_body->data << endl; + + // Load object + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + } else if (msg->method == SOUP_METHOD_POST) { + //cout << "PUT: " << msg->request_body->data << endl; + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + } else { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + } +} + + +/** Override the semaphore driven _run method of QueuedEngineInterface + * to wait on HTTP requests and process them immediately in this thread. + */ +void +HTTPEngineReceiver::ReceiveThread::_run() +{ + soup_server_run(_receiver._server); +} + + +} // namespace Ingen diff --git a/src/engine/HTTPEngineReceiver.hpp b/src/engine/HTTPEngineReceiver.hpp new file mode 100644 index 00000000..34c425b2 --- /dev/null +++ b/src/engine/HTTPEngineReceiver.hpp @@ -0,0 +1,59 @@ +/* This file is part of Ingen. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef HTTPENGINERECEIVER_H +#define HTTPENGINERECEIVER_H + +#include CONFIG_H_PATH +#include <string> +#include <stdint.h> +#include <libsoup/soup.h> +#include <raul/SharedPtr.hpp> +#include "QueuedEngineInterface.hpp" + +namespace Ingen { + +class HTTPEngineReceiver : public QueuedEngineInterface +{ +public: + HTTPEngineReceiver(Engine& engine, uint16_t port); + ~HTTPEngineReceiver(); + + void activate(); + void deactivate(); + +private: + struct ReceiveThread : public Raul::Thread { + ReceiveThread(HTTPEngineReceiver& receiver) : _receiver(receiver) {} + virtual void _run(); + private: + HTTPEngineReceiver& _receiver; + }; + + friend class ReceiveThread; + + static void message_callback(SoupServer* server, SoupMessage* msg, const char* path, + GHashTable *query, SoupClientContext* client, void* data); + + ReceiveThread* _receive_thread; + SoupServer* _server; +}; + + +} // namespace Ingen + +#endif // HTTPENGINERECEIVER_H diff --git a/src/engine/InputPort.cpp b/src/engine/InputPort.cpp new file mode 100644 index 00000000..cf5501ca --- /dev/null +++ b/src/engine/InputPort.cpp @@ -0,0 +1,297 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "InputPort.hpp" +#include <iostream> +#include <cstdlib> +#include <cassert> +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "ConnectionImpl.hpp" +#include "OutputPort.hpp" +#include "NodeImpl.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { + + +InputPort::InputPort(NodeImpl* parent, + const string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size) + : PortImpl(parent, name, index, poly, type, value, buffer_size) +{ +} + + +void +InputPort::set_buffer_size(size_t size) +{ + PortImpl::set_buffer_size(size); + assert(_buffer_size = size); + + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + ((ConnectionImpl*)c->get())->set_buffer_size(size); + +} + + +bool +InputPort::prepare_poly(uint32_t poly) +{ + PortImpl::prepare_poly(poly); + + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + ((ConnectionImpl*)c->get())->prepare_poly(poly); + + connect_buffers(); + return true; +} + + +bool +InputPort::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic || !_parent->polyphonic()) + return true; + + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + ((ConnectionImpl*)c->get())->apply_poly(maid, poly); + + PortImpl::apply_poly(maid, poly); + assert(this->poly() == poly); + + if (_connections.size() == 1) { + ConnectionImpl* c = _connections.begin()->get(); + for (uint32_t i=0; i < _poly; ++i) + _buffers->at(i)->join(c->buffer(i)); + } + + connect_buffers(); + + return true; +} + + +/** 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. + */ +void +InputPort::add_connection(Connections::Node* const c) +{ + _connections.push_back(c); + + bool modify_buffers = !_fixed_buffers; + + if (modify_buffers) { + if (_connections.size() == 1) { + // Use buffer directly to avoid copying + for (uint32_t i=0; i < _poly; ++i) { + _buffers->at(i)->join(c->elem()->buffer(i)); + } + } else if (_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 (uint32_t i=0; i < _poly; ++i) { + _buffers->at(i)->unjoin(); + } + } + PortImpl::connect_buffers(); + } + + // Automatically broadcast connected control inputs + if (_type == DataType::CONTROL) + _broadcast = true; +} + + +/** Remove a connection. Realtime safe. + */ +InputPort::Connections::Node* +InputPort::remove_connection(const OutputPort* src_port) +{ + bool modify_buffers = !_fixed_buffers; + + bool found = false; + Connections::Node* connection = NULL; + for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i) { + if ((*i)->src_port()->path() == src_port->path()) { + connection = _connections.erase(i); + found = true; + } + } + + if ( ! found) { + cerr << "WARNING: [InputPort::remove_connection] Connection not found !" << endl; + exit(EXIT_FAILURE); + } else { + if (_connections.size() == 0) { + for (uint32_t i=0; i < _poly; ++i) { + // Use a local buffer + if (modify_buffers) + _buffers->at(i)->unjoin(); + _buffers->at(i)->clear(); // Write silence + } + } else if (modify_buffers && _connections.size() == 1) { + // Share a buffer + for (uint32_t i=0; i < _poly; ++i) { + _buffers->at(i)->join((*_connections.begin())->buffer(i)); + } + } + } + + if (modify_buffers) + PortImpl::connect_buffers(); + + // Turn off broadcasting if we're not connected any more (FIXME: not quite right..) + if (_type == DataType::CONTROL && _connections.size() == 0) + _broadcast = false; + + return connection; +} + + +/** Returns whether this port is connected to the passed port. + */ +/*bool +InputPort::is_connected_to(const OutputPort* port) const +{ + for (Connections::const_iterator i = _connections.begin(); i != _connections.end(); ++i) + if ((*i)->src_port() == port) + return true; + + return false; +}*/ + + +/** Prepare buffer for access, mixing if necessary. Realtime safe. + * FIXME: nframes parameter not used, + */ +void +InputPort::pre_process(ProcessContext& context) +{ + // If value has been set (e.g. events pushed) by the user, + // don't do anything this cycle to avoid smashing the value + if (_set_by_user) + return; + + bool do_mixdown = true; + + if (_connections.size() == 0) { + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->prepare_read(context.start(), context.nframes()); + return; + } + + for (Connections::iterator c = _connections.begin(); c != _connections.end(); ++c) + (*c)->process(context); + + if ( ! _fixed_buffers) { + // If only one connection, try to use buffer directly (zero copy) + if (_connections.size() == 1) { + for (uint32_t i=0; i < _poly; ++i) { + //cerr << path() << " joining to " << (*_connections.begin())->buffer(i) << endl; + _buffers->at(i)->join((*_connections.begin())->buffer(i)); + } + do_mixdown = false; + } + connect_buffers(); + } else { + do_mixdown = true; + } + + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->prepare_read(context.start(), context.nframes()); + + /*cerr << path() << " poly = " << _poly << ", mixdown: " << do_mixdown + << ", fixed buffers: " << _fixed_buffers << ", joined: " << _buffers->at(0)->is_joined() + << " to " << _buffers->at(0)->joined_buffer() << endl;*/ + + /*if (type() == DataType::EVENT) + for (uint32_t i=0; i < _poly; ++i) + cerr << path() << " (" << buffer(i) << ") # events: " + << ((EventBuffer*)buffer(i))->event_count() + << ", joined: " << _buffers->at(i)->is_joined() << endl;*/ + + if (!do_mixdown) { + /*#ifndef NDEBUG + for (uint32_t i=0; i < _poly; ++i) + assert(buffer(i) == (*_connections.begin())->buffer(i)); + #endif*/ + return; + } + + if (_type == DataType::CONTROL || _type == DataType::AUDIO) { + for (uint32_t voice=0; voice < _poly; ++voice) { + // Copy first connection + buffer(voice)->copy( + (*_connections.begin())->buffer(voice), 0, _buffer_size-1); + + // Accumulate the rest + if (_connections.size() > 1) { + + Connections::iterator c = _connections.begin(); + + for (++c; c != _connections.end(); ++c) + ((AudioBuffer*)buffer(voice))->accumulate( + ((AudioBuffer*)(*c)->buffer(voice)), 0, _buffer_size-1); + } + } + } else { + assert(_poly == 1); + + // FIXME + if (_connections.size() > 1) + cerr << "WARNING: MIDI mixing not implemented, only first connection used." << endl; + + // Copy first connection + _buffers->at(0)->copy( + (*_connections.begin())->buffer(0), 0, _buffer_size-1); + } +} + + +void +InputPort::post_process(ProcessContext& context) +{ + broadcast(context); + + // Prepare for next cycle + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->prepare_write(context.start(), context.nframes()); + + _set_by_user = false; + + /*if (_broadcast && (_type == DataType::CONTROL)) { + const Sample value = ((AudioBuffer*)(*_buffers)[0])->value_at(0); + + cerr << path() << " input post: buffer: " << buffer(0) << " value = " + << value << " (last " << _last_broadcasted_value << ")" <<endl; + }*/ +} + + +} // namespace Ingen + diff --git a/src/engine/InputPort.hpp b/src/engine/InputPort.hpp new file mode 100644 index 00000000..2617882f --- /dev/null +++ b/src/engine/InputPort.hpp @@ -0,0 +1,88 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INPUTPORT_H +#define INPUTPORT_H + +#include <string> +#include <cstdlib> +#include <cassert> +#include <raul/List.hpp> +#include <raul/SharedPtr.hpp> +#include "PortImpl.hpp" +using std::string; + +namespace Ingen { + +class ConnectionImpl; +class OutputPort; +class NodeImpl; + + +/** 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 + */ +class InputPort : virtual public PortImpl +{ +public: + InputPort(NodeImpl* parent, + const string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size); + + virtual ~InputPort() {} + + typedef Raul::List< SharedPtr<ConnectionImpl> > Connections; + + void add_connection(Connections::Node* c); + Connections::Node* remove_connection(const OutputPort* src_port); + + const Connections& connections() { return _connections; } + + bool prepare_poly(uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + bool is_connected() const { return (_connections.size() > 0); } + //bool is_connected_to(const OutputPort* port) const; + + bool is_input() const { return true; } + bool is_output() const { return false; } + + virtual void set_buffer_size(size_t size); + +private: + Connections _connections; +}; + + +} // namespace Ingen + +#endif // INPUTPORT_H diff --git a/src/engine/InternalPlugin.cpp b/src/engine/InternalPlugin.cpp new file mode 100644 index 00000000..1c6a92a5 --- /dev/null +++ b/src/engine/InternalPlugin.cpp @@ -0,0 +1,55 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include "InternalPlugin.hpp" +#include "MidiNoteNode.hpp" +#include "MidiTriggerNode.hpp" +#include "MidiControlNode.hpp" +#include "TransportNode.hpp" +#include "Engine.hpp" +#include "AudioDriver.hpp" + +namespace Ingen { + + +NodeImpl* +InternalPlugin::instantiate(const string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine) +{ + assert(_type == Internal); + + SampleCount srate = engine.audio_driver()->sample_rate(); + SampleCount buffer_size = engine.audio_driver()->buffer_size(); + + if (_uri == NS_INGEN "note_node") { + return new MidiNoteNode(name, polyphonic, parent, srate, buffer_size); + } else if (_uri == NS_INGEN "trigger_node") { + return new MidiTriggerNode(name, polyphonic, parent, srate, buffer_size); + } else if (_uri == NS_INGEN "control_node") { + return new MidiControlNode(name, polyphonic, parent, srate, buffer_size); + } else if (_uri == NS_INGEN "transport_node") { + return new TransportNode(name, polyphonic, parent, srate, buffer_size); + } else { + return NULL; + } +} + + +} // namespace Ingen diff --git a/src/engine/InternalPlugin.hpp b/src/engine/InternalPlugin.hpp new file mode 100644 index 00000000..c04c9015 --- /dev/null +++ b/src/engine/InternalPlugin.hpp @@ -0,0 +1,74 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INTERNALPLUGIN_H +#define INTERNALPLUGIN_H + +#include CONFIG_H_PATH + +#ifndef HAVE_SLV2 +#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report." +#endif + +#include <cstdlib> +#include <glibmm/module.h> +#include <boost/utility.hpp> +#include <dlfcn.h> +#include <string> +#include <iostream> +#include <slv2/slv2.h> +#include "types.hpp" +#include "PluginImpl.hpp" + +#define NS_INGEN "http://drobilla.net/ns/ingen#" + +namespace Ingen { + +class NodeImpl; + + +/** Implementation of an Internal plugin. + */ +class InternalPlugin : public PluginImpl +{ +public: + InternalPlugin(const std::string& uri, + const std::string& symbol, + const std::string& name) + : PluginImpl(Plugin::Internal, uri) + , _symbol(symbol) + , _name(name) + {} + + NodeImpl* instantiate(const std::string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine); + + const string symbol() const { return _symbol; } + const string name() const { return _name; } + +private: + const string _symbol; + const string _name; +}; + + +} // namespace Ingen + +#endif // INTERNALPLUGIN_H + diff --git a/src/engine/JackAudioDriver.cpp b/src/engine/JackAudioDriver.cpp new file mode 100644 index 00000000..eff04653 --- /dev/null +++ b/src/engine/JackAudioDriver.cpp @@ -0,0 +1,384 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JackAudioDriver.hpp" +#include CONFIG_H_PATH +#include "tuning.hpp" +#include <iostream> +#include <cstdlib> +#include <raul/List.hpp> +#include "Engine.hpp" +#include "util.hpp" +#include "Event.hpp" +#include "ThreadManager.hpp" +#include "QueuedEvent.hpp" +#include "EventSource.hpp" +#include "PostProcessor.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "MidiDriver.hpp" +#include "DuplexPort.hpp" +#include "EventSource.hpp" +#include "AudioBuffer.hpp" +#include "ProcessSlave.hpp" + +using namespace std; + +namespace Ingen { + + +//// JackAudioPort //// + +JackAudioPort::JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , Raul::List<JackAudioPort*>::Node(this) + , _driver(driver) + , _jack_port(NULL) + , _jack_buffer(NULL) +{ + assert(patch_port->poly() == 1); + + _jack_port = jack_port_register(_driver->jack_client(), + patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE, + (patch_port->is_input()) ? JackPortIsInput : JackPortIsOutput, + 0); + + if (_jack_port == NULL) { + cerr << "[JackAudioPort] ERROR: Failed to register port " << patch_port->path() << endl; + throw JackAudioDriver::PortRegistrationFailedException(); + } + + patch_port->buffer(0)->clear(); + patch_port->fixed_buffers(true); +} + + +JackAudioPort::~JackAudioPort() +{ + jack_port_unregister(_driver->jack_client(), _jack_port); +} + + +void +JackAudioPort::prepare_buffer(jack_nframes_t nframes) +{ + jack_sample_t* jack_buf = (jack_sample_t*)jack_port_get_buffer(_jack_port, nframes); + + AudioBuffer* patch_buf = (AudioBuffer*)_patch_port->buffer(0); + + //cerr << "[JACK] " << _patch_port->path() << " buffer: " << patch_buf << endl; + + if (jack_buf != _jack_buffer) { + patch_buf->set_data(jack_buf); + _jack_buffer = jack_buf; + } + + assert(patch_buf->data() == jack_buf); +} + + +//// JackAudioDriver //// + +JackAudioDriver::JackAudioDriver(Engine& engine, + std::string server_name, + jack_client_t* jack_client) + : _engine(engine) + , _jack_thread(NULL) + , _client(jack_client) + , _buffer_size(jack_client ? jack_get_buffer_size(jack_client) : 0) + , _sample_rate(jack_client ? jack_get_sample_rate(jack_client) : 0) + , _is_activated(false) + , _local_client(true) // FIXME + , _process_context(engine) + , _root_patch(NULL) +{ + if (!_client) { + // Try supplied server name + if (server_name != "") { + _client = jack_client_open("Ingen", JackServerName, NULL, server_name.c_str()); + if (_client) + cerr << "[JackAudioDriver] Connected to JACK server '" << + server_name << "'" << endl; + } + + // Either server name not specified, or supplied server name does not exist + // Connect to default server + if (!_client) { + _client = jack_client_open("Ingen", JackNullOption, NULL); + + if (_client) + cerr << "[JackAudioDriver] Connected to default JACK server." << endl; + } + + // Still failed + if (!_client) { + cerr << "[JackAudioDriver] Unable to connect to Jack. Exiting." << endl; + exit(EXIT_FAILURE); + } + + _buffer_size = jack_get_buffer_size(_client); + _sample_rate = jack_get_sample_rate(_client); + } + + jack_on_shutdown(_client, shutdown_cb, this); + + jack_set_thread_init_callback(_client, thread_init_cb, this); + jack_set_sample_rate_callback(_client, sample_rate_cb, this); + jack_set_buffer_size_callback(_client, buffer_size_cb, this); +} + + +JackAudioDriver::~JackAudioDriver() +{ + deactivate(); + + if (_local_client) + jack_client_close(_client); +} + + +void +JackAudioDriver::activate() +{ + if (_is_activated) { + cerr << "[JackAudioDriver] Jack driver already activated." << endl; + return; + } + + jack_set_process_callback(_client, process_cb, this); + + _is_activated = true; + + if (jack_activate(_client)) { + cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl; + exit(EXIT_FAILURE); + } else { + cout << "[JackAudioDriver] Activated Jack client." << endl; +/*#ifdef HAVE_LASH + _engine.lash_driver()->set_jack_client_name(jack_client_get_name(_client)); +#endif*/ + } +} + + +void +JackAudioDriver::deactivate() +{ + if (_is_activated) { + + //for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + // jack_port_unregister(_client, (*i)->jack_port()); + + jack_deactivate(_client); + _is_activated = false; + + _ports.clear(); + + cout << "[JackAudioDriver] Deactivated Jack client." << endl; + + //_engine.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(DriverPort* port) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + assert(dynamic_cast<JackAudioPort*>(port)); + _ports.push_back((JackAudioPort*)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. + */ +DriverPort* +JackAudioDriver::remove_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return _ports.erase(i)->elem(); // FIXME: LEAK + + cerr << "[JackAudioDriver::remove_port] WARNING: Unable to find Jack port " << path << endl; + return NULL; +} + + +DriverPort* +JackAudioDriver::port(const Path& path) +{ + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + + +DriverPort* +JackAudioDriver::create_port(DuplexPort* patch_port) +{ + try { + if (patch_port->buffer_size() == _buffer_size) + return new JackAudioPort(this, patch_port); + else + return NULL; + } catch (...) { + return NULL; + } +} + + +DriverPort* +JackAudioDriver::driver_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + + +/**** Jack Callbacks ****/ + + + +/** Jack process callback, drives entire audio thread. + * + * \callgraph + */ +int +JackAudioDriver::_process_cb(jack_nframes_t nframes) +{ + if (nframes == 0 || ! _is_activated) + return 0; + + // FIXME: all of this time stuff is screwy + + // FIXME: support nframes != buffer_size, even though that never damn well happens + assert(nframes == _buffer_size); + + // Jack can elect to not call this function for a cycle, if overloaded + // FIXME: this doesn't make sense, and the start time isn't used anyway + const jack_nframes_t start_of_current_cycle = jack_last_frame_time(_client); + + const jack_nframes_t end_of_current_cycle = start_of_current_cycle + nframes; +#ifndef NDEBUG + // FIXME: support changing cycle length + const jack_nframes_t start_of_last_cycle = start_of_current_cycle - nframes; + assert(start_of_current_cycle - start_of_last_cycle == nframes); +#endif + + _transport_state = jack_transport_query(_client, &_position); + + _process_context.set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle); + + for (Engine::ProcessSlaves::iterator i = _engine.process_slaves().begin(); + i != _engine.process_slaves().end(); ++i) { + (*i)->context().set_time_slice(nframes, start_of_current_cycle, end_of_current_cycle); + } + + // Process events that came in during the last cycle + // (Aiming for jitter-free 1 block event latency, ideally) + _engine.process_events(_process_context); + + // Set buffers of patch ports to Jack port buffers (zero-copy processing) + for (Raul::List<JackAudioPort*>::iterator i = _ports.begin(); i != _ports.end(); ++i) { + assert(*i); + (*i)->prepare_buffer(nframes); + } + + assert(_engine.midi_driver()); + _engine.midi_driver()->pre_process(_process_context); + + // Run root patch + if (_root_patch) + _root_patch->process(_process_context); + + _engine.midi_driver()->post_process(_process_context); + + _engine.post_processor()->set_end_time(_process_context.end()); + + return 0; +} + + +void +JackAudioDriver::_thread_init_cb() +{ + // Initialize thread specific data + _jack_thread = Thread::create_for_this_thread("Jack"); + assert(&Thread::get() == _jack_thread); + _jack_thread->set_context(THREAD_PROCESS); + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); +} + +void +JackAudioDriver::_shutdown_cb() +{ + cout << "[JackAudioDriver] Jack shutdown. Exiting." << endl; + _engine.quit(); + _jack_thread = NULL; +} + + +int +JackAudioDriver::_sample_rate_cb(jack_nframes_t nframes) +{ + if (_is_activated) { + cerr << "[JackAudioDriver] On-the-fly sample rate changing not supported (yet). Aborting." << endl; + exit(EXIT_FAILURE); + } else { + _sample_rate = nframes; + } + return 0; +} + + +int +JackAudioDriver::_buffer_size_cb(jack_nframes_t nframes) +{ + if (_root_patch) { + _root_patch->set_buffer_size(nframes); + _buffer_size = nframes; + } + return 0; +} + + +} // namespace Ingen + diff --git a/src/engine/JackAudioDriver.hpp b/src/engine/JackAudioDriver.hpp new file mode 100644 index 00000000..3beb775a --- /dev/null +++ b/src/engine/JackAudioDriver.hpp @@ -0,0 +1,185 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef JACKAUDIODRIVER_H +#define JACKAUDIODRIVER_H + +#include <jack/jack.h> +#include <jack/transport.h> +#include <raul/Thread.hpp> +#include <raul/Path.hpp> +#include <raul/List.hpp> +#include "AudioDriver.hpp" +#include "Buffer.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { + +class Engine; +class PatchImpl; +class PortImpl; +class DuplexPort; +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 Raul::List<JackAudioPort*>::Node +{ +public: + JackAudioPort(JackAudioDriver* driver, DuplexPort* patch_port); + ~JackAudioPort(); + + void set_name(const std::string& name) { jack_port_set_name(_jack_port, name.c_str()); }; + + void prepare_buffer(jack_nframes_t nframes); + + jack_port_t* jack_port() const { return _jack_port; } + +private: + JackAudioDriver* _driver; + jack_port_t* _jack_port; + jack_sample_t* _jack_buffer; ///< Cached for output ports +}; + + + +/** 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(Engine& engine, + std::string server_name = "", + jack_client_t* jack_client = 0); + + ~JackAudioDriver(); + + void activate(); + void deactivate(); + void enable(); + void disable(); + + DriverPort* port(const Raul::Path& path); + DriverPort* create_port(DuplexPort* patch_port); + + void add_port(DriverPort* port); + DriverPort* remove_port(const Raul::Path& path); + + DriverPort* driver_port(const Raul::Path& path); + + PatchImpl* root_patch() { return _root_patch; } + void set_root_patch(PatchImpl* patch) { _root_patch = patch; } + + ProcessContext& context() { return _process_context; } + + /** Transport state for this frame. + * Intended to only be called from the audio thread. */ + inline const jack_position_t* position() { return &_position; } + inline jack_transport_state_t transport_state() { return _transport_state; } + + bool is_realtime() const { return jack_is_realtime(_client); } + + jack_client_t* jack_client() const { return _client; } + SampleCount buffer_size() const { return _buffer_size; } + SampleCount sample_rate() const { return _sample_rate; } + bool is_activated() const { return _is_activated; } + + inline SampleCount frame_time() const { return jack_frame_time(_client); } + + class PortRegistrationFailedException : public std::exception {}; + +private: + friend class JackAudioPort; + + // These are the static versions of the callbacks, they call + // the non-static ones below + inline static void thread_init_cb(void* const jack_driver); + inline static void shutdown_cb(void* const jack_driver); + inline static int process_cb(jack_nframes_t nframes, 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 + void _thread_init_cb(); + void _shutdown_cb(); + int _process_cb(jack_nframes_t nframes); + int _buffer_size_cb(jack_nframes_t nframes); + int _sample_rate_cb(jack_nframes_t nframes); + + Engine& _engine; + Raul::Thread* _jack_thread; + jack_client_t* _client; + jack_nframes_t _buffer_size; + jack_nframes_t _sample_rate; + bool _is_activated; + bool _local_client; ///< Whether _client should be closed on destruction + jack_position_t _position; + jack_transport_state_t _transport_state; + + Raul::List<JackAudioPort*> _ports; + ProcessContext _process_context; + + PatchImpl* _root_patch; +}; + + +inline int JackAudioDriver::process_cb(jack_nframes_t nframes, void* jack_driver) +{ + assert(jack_driver); + return ((JackAudioDriver*)jack_driver)->_process_cb(nframes); +} + +inline void JackAudioDriver::thread_init_cb(void* jack_driver) +{ + assert(jack_driver); + return ((JackAudioDriver*)jack_driver)->_thread_init_cb(); +} + +inline void JackAudioDriver::shutdown_cb(void* jack_driver) +{ + assert(jack_driver); + return ((JackAudioDriver*)jack_driver)->_shutdown_cb(); +} + + +inline int JackAudioDriver::buffer_size_cb(jack_nframes_t nframes, void* jack_driver) +{ + assert(jack_driver); + return ((JackAudioDriver*)jack_driver)->_buffer_size_cb(nframes); +} + + +inline int JackAudioDriver::sample_rate_cb(jack_nframes_t nframes, void* jack_driver) +{ + assert(jack_driver); + return ((JackAudioDriver*)jack_driver)->_sample_rate_cb(nframes); +} + + +} // namespace Ingen + +#endif // JACKAUDIODRIVER_H diff --git a/src/engine/JackMidiDriver.cpp b/src/engine/JackMidiDriver.cpp new file mode 100644 index 00000000..9e236541 --- /dev/null +++ b/src/engine/JackMidiDriver.cpp @@ -0,0 +1,267 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +() */ + +#include <iostream> +#include <cstdlib> +#include <pthread.h> +#include <raul/Maid.hpp> +#include <raul/midi_events.h> +#include "types.hpp" +#include "JackMidiDriver.hpp" +#include "JackAudioDriver.hpp" +#include "ThreadManager.hpp" +#include "AudioDriver.hpp" +#include "EventBuffer.hpp" +#include "DuplexPort.hpp" +#include "ProcessContext.hpp" +#include "jack_compat.h" +using namespace std; + +namespace Ingen { + + +//// JackMidiPort //// + +JackMidiPort::JackMidiPort(JackMidiDriver* driver, DuplexPort* patch_port) + : DriverPort(patch_port) + , Raul::List<JackMidiPort*>::Node(this) + , _driver(driver) + , _jack_port(NULL) +{ + assert(patch_port->poly() == 1); + + _jack_port = jack_port_register(_driver->jack_client(), + patch_port->path().c_str(), JACK_DEFAULT_MIDI_TYPE, + (patch_port->is_input()) ? JackPortIsInput : JackPortIsOutput, + 0); + + if (_jack_port == NULL) { + cerr << "[JackMidiPort] ERROR: Failed to register port " << patch_port->path() << endl; + throw JackAudioDriver::PortRegistrationFailedException(); + } + + patch_port->buffer(0)->clear(); +} + + +JackMidiPort::~JackMidiPort() +{ + jack_port_unregister(_driver->jack_client(), _jack_port); +} + + +/** Prepare input for a block before a cycle is run, in the audio thread. + * + * This is simple since Jack MIDI is in-band with audio. + */ +void +JackMidiPort::pre_process(ProcessContext& context) +{ + if ( ! is_input() ) + return; + + assert(_patch_port->poly() == 1); + + EventBuffer* patch_buf = dynamic_cast<EventBuffer*>(_patch_port->buffer(0)); + assert(patch_buf); + + void* jack_buffer = jack_port_get_buffer(_jack_port, context.nframes()); + const jack_nframes_t event_count = jack_midi_get_event_count(jack_buffer); + + patch_buf->prepare_write(context.start(), context.nframes()); + + // 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_get(&ev, jack_buffer, i); + + // FIXME: type is hardcoded for now, we should get it from + // the type map instead + const bool success = patch_buf->append(ev.time, 0, 1, ev.size, ev.buffer); + if (!success) + cerr << "WARNING: Failed to write MIDI to port buffer, event(s) lost!" << endl; + } + + //if (event_count) + // cerr << "Jack MIDI got " << event_count << " events." << endl; +} + + +/** Prepare output for a block after a cycle is run, in the audio thread. + * + * This is simple since Jack MIDI is in-band with audio. + */ +void +JackMidiPort::post_process(ProcessContext& context) +{ + if (is_input()) + return; + + EventBuffer* patch_buf = dynamic_cast<EventBuffer*>(_patch_port->buffer(0)); + void* jack_buf = jack_port_get_buffer(_jack_port, context.nframes()); + + assert(_patch_port->poly() == 1); + assert(patch_buf); + + patch_buf->prepare_read(context.start(), context.nframes()); + jack_midi_clear_buffer(jack_buf); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* data = NULL; + + // Copy events from Jack port buffer into patch port buffer + for (patch_buf->rewind(); patch_buf->is_valid(); patch_buf->increment()) { + patch_buf->get_event(&frames, &subframes, &type, &size, &data); + jack_midi_event_write(jack_buf, frames, data, size); + } + + //if (event_count) + // cerr << "Jack MIDI wrote " << event_count << " events." << endl; +} + + + +//// JackMidiDriver //// + + +bool JackMidiDriver::_midi_thread_exit_flag = true; + + +JackMidiDriver::JackMidiDriver(jack_client_t* client) + : _client(client) + , _is_activated(false) + , _is_enabled(false) +{ +} + + +JackMidiDriver::~JackMidiDriver() +{ + deactivate(); +} + + +/** Launch and start the MIDI thread. + */ +void +JackMidiDriver::activate() +{ + _is_activated = true; +} + + +/** Terminate the MIDI thread. + */ +void +JackMidiDriver::deactivate() +{ + _is_activated = false; +} + + +/** Build flat arrays of events to be used as input for the given cycle. + */ +void +JackMidiDriver::pre_process(ProcessContext& context) +{ + for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i) + (*i)->pre_process(context); +} + + +/** Write the output from any (top-level, exported) MIDI output ports to Jack ports. + */ +void +JackMidiDriver::post_process(ProcessContext& context) +{ + for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i) + (*i)->post_process(context); +} + + +/** 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 new_port() and remove_port(). + */ +void +JackMidiDriver::add_port(DriverPort* port) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + assert(dynamic_cast<JackMidiPort*>(port)); + + if (port->is_input()) + _in_ports.push_back((JackMidiPort*)port); + else + _out_ports.push_back((JackMidiPort*)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. + */ +DriverPort* +JackMidiDriver::remove_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + // FIXME: duplex? + + for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return _in_ports.erase(i)->elem(); // FIXME: LEAK + + for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return _out_ports.erase(i)->elem(); // FIXME: LEAK + + cerr << "[JackMidiDriver::remove_input] WARNING: Unable to find Jack port " << path << endl; + return NULL; +} + + +DriverPort* +JackMidiDriver::driver_port(const Path& path) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + // FIXME: duplex? + + for (Raul::List<JackMidiPort*>::iterator i = _in_ports.begin(); i != _in_ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + for (Raul::List<JackMidiPort*>::iterator i = _out_ports.begin(); i != _out_ports.end(); ++i) + if ((*i)->patch_port()->path() == path) + return (*i); + + return NULL; +} + + +} // namespace Ingen + diff --git a/src/engine/JackMidiDriver.hpp b/src/engine/JackMidiDriver.hpp new file mode 100644 index 00000000..7889c4fa --- /dev/null +++ b/src/engine/JackMidiDriver.hpp @@ -0,0 +1,113 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef JACKMIDIDRIVER_H +#define JACKMIDIDRIVER_H + +#include <jack/jack.h> +#include <jack/midiport.h> +#include <raul/List.hpp> +#include CONFIG_H_PATH +#include "MidiDriver.hpp" + +namespace Ingen { + +class NodeImpl; +class SetPortValueEvent; +class JackMidiDriver; +class DuplexPort; + + +/** Representation of an JACK MIDI port. + * + * \ingroup engine + */ +class JackMidiPort : public DriverPort, public Raul::List<JackMidiPort*>::Node +{ +public: + JackMidiPort(JackMidiDriver* driver, DuplexPort* port); + virtual ~JackMidiPort(); + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + void set_name(const std::string& name) { jack_port_set_name(_jack_port, name.c_str()); }; + +private: + JackMidiDriver* _driver; + jack_port_t* _jack_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() { _is_enabled = true; } + void disable() { _is_enabled = false; } + + bool is_activated() const { return _is_activated; } + bool is_enabled() const { return _is_enabled; } + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + JackMidiPort* create_port(DuplexPort* patch_port) + { return new JackMidiPort(this, patch_port); } + + void add_port(DriverPort* port); + DriverPort* remove_port(const Raul::Path& path); + + DriverPort* driver_port(const Raul::Path& path); + + jack_client_t* jack_client() { return _client; } + +private: + Raul::List<JackMidiPort*> _in_ports; + Raul::List<JackMidiPort*> _out_ports; + + friend class JackMidiPort; + + void add_output(Raul::List<JackMidiPort*>::Node* port); + Raul::List<JackMidiPort*>::Node* remove_output(JackMidiPort* port); + + // MIDI thread + static void* process_midi_in(void* me); + + jack_client_t* _client; + pthread_t _process_thread; + bool _is_activated; + bool _is_enabled; + static bool _midi_thread_exit_flag; +}; + + +} // namespace Ingen + + +#endif // JACKMIDIDRIVER_H diff --git a/src/engine/LADSPANode.cpp b/src/engine/LADSPANode.cpp new file mode 100644 index 00000000..22f5bbf3 --- /dev/null +++ b/src/engine/LADSPANode.cpp @@ -0,0 +1,373 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cassert> +#include <cmath> +#include <stdint.h> +#include <raul/Maid.hpp> +#include <boost/optional.hpp> +#include "LADSPANode.hpp" +#include "AudioBuffer.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "PluginImpl.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { + + +/** Partially construct a LADSPANode. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LADSPANode::LADSPANode(PluginImpl* plugin, const string& path, bool polyphonic, PatchImpl* parent, const LADSPA_Descriptor* descriptor, SampleRate srate, size_t buffer_size) + : NodeBase(plugin, path, polyphonic, parent, srate, buffer_size) + , _descriptor(descriptor) + , _instances(NULL) + , _prepared_instances(NULL) +{ + assert(_descriptor != NULL); +} + + +LADSPANode::~LADSPANode() +{ + for (uint32_t i=0; i < _polyphony; ++i) + _descriptor->cleanup((*_instances)[i]); + + delete _instances; +} + + +bool +LADSPANode::prepare_poly(uint32_t poly) +{ + NodeBase::prepare_poly(poly); + + if ( (!_polyphonic) + || (_prepared_instances && poly <= _prepared_instances->size()) ) { + return true; + } + + _prepared_instances = new Raul::Array<LADSPA_Handle>(poly, *_instances); + for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) { + _prepared_instances->at(i) = _descriptor->instantiate(_descriptor, _srate); + if (_prepared_instances->at(i) == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + + if (_activated && _descriptor->activate) + _descriptor->activate(_prepared_instances->at(i)); + } + + return true; +} + + +bool +LADSPANode::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + return true; + + if (_prepared_instances) { + assert(poly <= _prepared_instances->size()); + maid.push(_instances); + _instances = _prepared_instances; + _prepared_instances = NULL; + } + + assert(poly <= _instances->size()); + _polyphony = poly; + + return NodeBase::apply_poly(maid, poly); +} + + +static string +nameify_if_invalid(const string& name) +{ + if (Path::is_valid_name(name)) { + return name; + } else { + const string new_name = Path::nameify(name); + assert(Path::is_valid_name(new_name)); + if (new_name != name) + cerr << "Symbol '" << new_name << "' generated from LADSPA name '" << name << endl; + return new_name; + } +} + + +/** 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 +LADSPANode::instantiate() +{ + if (!_ports) + _ports = new Raul::Array<PortImpl*>(_descriptor->PortCount); + + _instances = new Raul::Array<LADSPA_Handle>(_polyphony, NULL); + + size_t port_buffer_size = 0; + + for (uint32_t i=0; i < _polyphony; ++i) { + (*_instances)[i] = _descriptor->instantiate(_descriptor, _srate); + if ((*_instances)[i] == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + } + + string port_name; + string port_path; + + PortImpl* port = NULL; + + for (size_t j=0; j < _descriptor->PortCount; ++j) { + port_name = nameify_if_invalid(_descriptor->PortNames[j]); + + string::size_type slash_index; + + // Name mangling, to guarantee port names are unique + for (size_t k=0; k < _descriptor->PortCount; ++k) { + assert(_descriptor->PortNames[k] != NULL); + if (k != j && port_name == _descriptor->PortNames[k]) { // clash + if (LADSPA_IS_PORT_CONTROL(_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] = '-'; + } + + /*if (_descriptor->PortNames[j] != port_name) + cerr << "NOTICE: Translated LADSPA port name: " << + _descriptor->PortNames[j] << " -> " << port_name << endl;*/ + + port_path = path() + "/" + port_name; + + DataType type = DataType::AUDIO; + port_buffer_size = _buffer_size; + + if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j])) { + type = DataType::CONTROL; + port_buffer_size = 1; + } else { + assert(LADSPA_IS_PORT_AUDIO(_descriptor->PortDescriptors[j])); + } + assert (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j]) + || LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j])); + + boost::optional<Sample> default_val, min, max; + get_port_limits(j, default_val, min, max); + + const float value = default_val ? default_val.get() : 0.0f; + + if (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])) { + port = new InputPort(this, port_name, j, _polyphony, type, value, port_buffer_size); + _ports->at(j) = port; + } else if (LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j])) { + port = new OutputPort(this, port_name, j, _polyphony, type, value, port_buffer_size); + _ports->at(j) = port; + } + + assert(port); + assert(_ports->at(j) == port); + + // Work around broke-ass crackhead plugins + if (default_val && default_val.get() < min.get()) { + cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID + << ": Port default < minimum. Minimum adjusted." << endl; + min = default_val; + } + + if (default_val && default_val.get() > max.get()) { + cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID + << ": Maximum adjusted." << endl; + max = default_val; + } + + // Set initial/default value + if (port->buffer_size() == 1) { + for (uint32_t i=0; i < _polyphony; ++i) + ((AudioBuffer*)port->buffer(i))->set_value(value, 0, 0); + } + + if (port->is_input() && port->buffer_size() == 1) { + if (min) + port->set_variable("ingen:minimum", min.get()); + if (max) + port->set_variable("ingen:maximum", max.get()); + if (default_val) + port->set_variable("ingen:default", default_val.get()); + } + } + + return true; +} + + +void +LADSPANode::activate() +{ + NodeBase::activate(); + + for (uint32_t i=0; i < _polyphony; ++i) { + for (unsigned long j=0; j < _descriptor->PortCount; ++j) { + set_port_buffer(i, j, _ports->at(j)->buffer(i)); + /* if (port->type() == DataType::FLOAT && port->buffer_size() == 1) + port->set_value(0.0f, 0); // FIXME + else if (port->type() == DataType::FLOAT && port->buffer_size() > 1) + port->set_value(0.0f, 0);*/ + } + if (_descriptor->activate != NULL) + _descriptor->activate((*_instances)[i]); + } +} + + +void +LADSPANode::deactivate() +{ + NodeBase::deactivate(); + + for (uint32_t i=0; i < _polyphony; ++i) + if (_descriptor->deactivate != NULL) + _descriptor->deactivate((*_instances)[i]); +} + + +void +LADSPANode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); + + for (uint32_t i=0; i < _polyphony; ++i) + _descriptor->run((*_instances)[i], context.nframes()); + + NodeBase::post_process(context); +} + + +void +LADSPANode::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) +{ + assert(voice < _polyphony); + + AudioBuffer* audio_buffer = dynamic_cast<AudioBuffer*>(buf); + assert(audio_buffer); + + if (port_num < _descriptor->PortCount) + _descriptor->connect_port((*_instances)[voice], port_num, + audio_buffer->data()); +} + + +void +LADSPANode::get_port_limits(unsigned long port_index, + boost::optional<Sample>& normal, + boost::optional<Sample>& lower, + boost::optional<Sample>& upper) +{ + LADSPA_PortRangeHintDescriptor hint_descriptor = _descriptor->PortRangeHints[port_index].HintDescriptor; + + /* set upper and lower, possibly adjusted to the sample rate */ + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { + upper = _descriptor->PortRangeHints[port_index].UpperBound * _srate; + lower = _descriptor->PortRangeHints[port_index].LowerBound * _srate; + } else { + upper = _descriptor->PortRangeHints[port_index].UpperBound; + lower = _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.get() < 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)) { + assert(lower); + assert(upper); + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower.get()) * 0.75 + log(upper.get()) * 0.25); + } else { + normal = lower.get() * 0.75 + upper.get() * 0.25; + } + } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) { + assert(lower); + assert(upper); + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower.get()) * 0.5 + log(upper.get()) * 0.5); + } else { + normal = lower.get() * 0.5 + upper.get() * 0.5; + } + } else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) { + assert(lower); + assert(upper); + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + normal = exp(log(lower.get()) * 0.25 + log(upper.get()) * 0.75); + } else { + normal = lower.get() * 0.25 + upper.get() * 0.75; + } + } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) { + assert(upper); + normal = upper.get(); + } 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)) { + assert(lower); + normal = lower.get(); + } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { + assert(upper); + normal = upper.get(); + } + } +} + + +} // namespace Ingen + diff --git a/src/engine/LADSPANode.hpp b/src/engine/LADSPANode.hpp new file mode 100644 index 00000000..53d7ad9e --- /dev/null +++ b/src/engine/LADSPANode.hpp @@ -0,0 +1,74 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LADSPANODE_H +#define LADSPANODE_H + +#include <string> +#include <ladspa.h> +#include <boost/optional.hpp> +#include "types.hpp" +#include "NodeBase.hpp" +#include "PluginImpl.hpp" + +namespace Ingen { + + +/** An instance of a LADSPA plugin. + * + * \ingroup engine + */ +class LADSPANode : public NodeBase +{ +public: + LADSPANode(PluginImpl* plugin, + const string& name, + bool polyphonic, + PatchImpl* parent, + const LADSPA_Descriptor* descriptor, + SampleRate srate, + size_t buffer_size); + + ~LADSPANode(); + + bool instantiate(); + + bool prepare_poly(uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void activate(); + void deactivate(); + + void process(ProcessContext& context); + + void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf); + +protected: + void get_port_limits(unsigned long port_index, + boost::optional<Sample>& default_value, + boost::optional<Sample>& lower_bound, + boost::optional<Sample>& upper_bound); + + const LADSPA_Descriptor* _descriptor; + Raul::Array<LADSPA_Handle>* _instances; + Raul::Array<LADSPA_Handle>* _prepared_instances; +}; + + +} // namespace Ingen + +#endif // LADSPANODE_H diff --git a/src/engine/LADSPAPlugin.cpp b/src/engine/LADSPAPlugin.cpp new file mode 100644 index 00000000..4a0b5c14 --- /dev/null +++ b/src/engine/LADSPAPlugin.cpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <ladspa.h> +#include <iostream> +#include "LADSPAPlugin.hpp" +#include "LADSPANode.hpp" +#include "NodeImpl.hpp" +#include "Engine.hpp" +#include "AudioDriver.hpp" + +using namespace std; + +namespace Ingen { + + +NodeImpl* +LADSPAPlugin::instantiate(const string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine) +{ + assert(_id != 0); + + SampleCount srate = engine.audio_driver()->sample_rate(); + SampleCount buffer_size = engine.audio_driver()->buffer_size(); + + LADSPA_Descriptor_Function df = NULL; + LADSPANode* n = NULL; + + load(); // FIXME: unload at some point + assert(_module); + assert(*_module); + + if (!_module->get_symbol("ladspa_descriptor", (void*&)df)) { + cerr << "Looks like this isn't a LADSPA plugin." << endl; + return NULL; + } + + // Attempt to find the plugin in library + LADSPA_Descriptor* descriptor = NULL; + for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { + if (descriptor->UniqueID == _id) { + break; + } + } + + if (descriptor != NULL) { + n = new LADSPANode(this, name, polyphonic, parent, descriptor, srate, buffer_size); + + if ( ! n->instantiate() ) { + delete n; + n = NULL; + } + + } else { + cerr << "Could not find plugin \"" << _id << "\" in " << _library_path << endl; + } + + return n; +} + + +} // namespace Ingen diff --git a/src/engine/LADSPAPlugin.hpp b/src/engine/LADSPAPlugin.hpp new file mode 100644 index 00000000..2414be7c --- /dev/null +++ b/src/engine/LADSPAPlugin.hpp @@ -0,0 +1,78 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LADSPAPLUGIN_H +#define LADSPAPLUGIN_H + +#include CONFIG_H_PATH + +#include <cstdlib> +#include <glibmm/module.h> +#include <boost/utility.hpp> +#include <dlfcn.h> +#include <string> +#include <iostream> +#include <raul/Path.hpp> +#include "types.hpp" +#include "PluginImpl.hpp" + +namespace Ingen { + +class NodeImpl; + + +/** Implementation of a LADSPA plugin (loaded shared library). + */ +class LADSPAPlugin : public PluginImpl +{ +public: + LADSPAPlugin(const std::string& library_path, + const std::string& uri, + unsigned long id, + const string& label, + const string& name) + : PluginImpl(Plugin::LADSPA, uri, library_path) + , _id(id) + , _label(label) + , _name(name) + {} + + NodeImpl* instantiate(const std::string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine); + + const std::string& label() const { return _label; } + unsigned long id() const { return _id; } + const string symbol() const { return Raul::Path::nameify(_label); } + const string name() const { return _name; } + + const string library_name() const { + return _library_path.substr(_library_path.find_last_of("/")+1); + } + +private: + const unsigned long _id; + const std::string _label; + const std::string _name; +}; + + +} // namespace Ingen + +#endif // LADSPAPLUGIN_H + diff --git a/src/engine/LV2Info.cpp b/src/engine/LV2Info.cpp new file mode 100644 index 00000000..43dd014b --- /dev/null +++ b/src/engine/LV2Info.cpp @@ -0,0 +1,70 @@ +/* This file is part of Ingen. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define __STDC_LIMIT_MACROS 1 +#include <cassert> +#include <iostream> +#include <stdint.h> +#include "LV2Info.hpp" +#include <module/World.hpp> + +using namespace std; + +namespace Ingen { + +LV2Info::LV2Info(Ingen::Shared::World* world) + : input_class(slv2_value_new_uri(world->slv2_world, SLV2_PORT_CLASS_INPUT)) + , output_class(slv2_value_new_uri(world->slv2_world, SLV2_PORT_CLASS_OUTPUT)) + , control_class(slv2_value_new_uri(world->slv2_world, SLV2_PORT_CLASS_CONTROL)) + , audio_class(slv2_value_new_uri(world->slv2_world, SLV2_PORT_CLASS_AUDIO)) + , event_class(slv2_value_new_uri(world->slv2_world, SLV2_PORT_CLASS_EVENT)) + , _world(world) +{ + assert(world); + + LV2_Event_Feature* ev_data = (LV2_Event_Feature*)malloc(sizeof(LV2_Event_Feature)); + ev_data->lv2_event_ref = &LV2Info::event_ref; + ev_data->lv2_event_unref = &LV2Info::event_ref; + ev_data->callback_data = this; + LV2_Feature* ev_feature = (LV2_Feature*)malloc(sizeof(LV2_Event_Feature)); + ev_feature->URI = LV2_EVENT_URI; + ev_feature->data = ev_data; + + world->lv2_features->add_feature(LV2_EVENT_URI, ev_feature, ev_data); +} + + +LV2Info::~LV2Info() +{ + slv2_value_free(input_class); + slv2_value_free(output_class); + slv2_value_free(control_class); + slv2_value_free(audio_class); + slv2_value_free(event_class); +} + + +uint32_t +LV2Info::event_ref(LV2_Event_Callback_Data callback_data, + LV2_Event* event) +{ + return 0; +} + + + +} // namespace Ingen diff --git a/src/engine/LV2Info.hpp b/src/engine/LV2Info.hpp new file mode 100644 index 00000000..f4859ac7 --- /dev/null +++ b/src/engine/LV2Info.hpp @@ -0,0 +1,66 @@ +/* This file is part of Ingen. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LV2INFO_H +#define LV2INFO_H + +#include CONFIG_H_PATH +#ifndef HAVE_SLV2 +#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report." +#endif + +#include <map> +#include <string> +#include <slv2/slv2.h> +#include "module/global.hpp" +#include "module/World.hpp" +#include "shared/LV2URIMap.hpp" +#include "lv2ext/lv2_uri_map.h" +#include "lv2ext/lv2_event.h" + +namespace Ingen { + + +/** Stuff that may need to be passed to an LV2 plugin (i.e. LV2 features). + */ +class LV2Info : public Shared::LV2URIMap { +public: + LV2Info(Ingen::Shared::World* world); + ~LV2Info(); + + SLV2Value input_class; + SLV2Value output_class; + SLV2Value control_class; + SLV2Value audio_class; + SLV2Value event_class; + + Ingen::Shared::World& world() { return *_world; } + SLV2World lv2_world() { return _world->slv2_world; } + + static uint32_t event_ref(LV2_Event_Callback_Data callback_data, + LV2_Event* event); + + LV2_Feature** lv2_features() const { return _world->lv2_features->lv2_features(); } + +private: + Ingen::Shared::World* _world; +}; + + +} // namespace Ingen + +#endif // LV2INFO_H diff --git a/src/engine/LV2Node.cpp b/src/engine/LV2Node.cpp new file mode 100644 index 00000000..a06cc55a --- /dev/null +++ b/src/engine/LV2Node.cpp @@ -0,0 +1,305 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cassert> +#include <float.h> +#include <stdint.h> +#include <cmath> +#include <raul/Maid.hpp> +#include "AudioBuffer.hpp" +#include "InputPort.hpp" +#include "LV2Node.hpp" +#include "LV2Plugin.hpp" +#include "EventBuffer.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" +#include "lv2_contexts.h" + +using namespace std; + +namespace Ingen { + + +/** Partially construct a LV2Node. + * + * Object is not usable until instantiate() is called with success. + * (It _will_ crash!) + */ +LV2Node::LV2Node(LV2Plugin* plugin, + const string& name, + bool polyphonic, + PatchImpl* parent, + SampleRate srate, + size_t buffer_size) + : NodeBase(plugin, name, polyphonic, parent, srate, buffer_size) + , _lv2_plugin(plugin) + , _instances(NULL) + , _prepared_instances(NULL) + , _message_run(NULL) +{ + assert(_lv2_plugin); +} + + +LV2Node::~LV2Node() +{ + for (uint32_t i=0; i < _polyphony; ++i) + slv2_instance_free((*_instances)[i]); + + delete _instances; +} + + +bool +LV2Node::prepare_poly(uint32_t poly) +{ + NodeBase::prepare_poly(poly); + + if ( (!_polyphonic) + || (_prepared_instances && poly <= _prepared_instances->size()) ) { + return true; + } + + _prepared_instances = new Raul::Array<SLV2Instance>(poly, *_instances); + for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) { + // FIXME: features array (in NodeFactory) must be passed! + _prepared_instances->at(i) = slv2_plugin_instantiate( + _lv2_plugin->slv2_plugin(), _srate, NULL); + + if (_prepared_instances->at(i) == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + + if (_activated) + slv2_instance_activate(_prepared_instances->at(i)); + } + + return true; +} + + +bool +LV2Node::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + return true; + + if (_prepared_instances) { + assert(poly <= _prepared_instances->size()); + maid.push(_instances); + _instances = _prepared_instances; + _prepared_instances = NULL; + } + + assert(poly <= _instances->size()); + _polyphony = poly; + + return NodeBase::apply_poly(maid, poly); +} + + +/** 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 +LV2Node::instantiate() +{ + SharedPtr<LV2Info> info = _lv2_plugin->lv2_info(); + SLV2Plugin plug = _lv2_plugin->slv2_plugin(); + + uint32_t num_ports = slv2_plugin_get_num_ports(plug); + assert(num_ports > 0); + + _ports = new Raul::Array<PortImpl*>(num_ports, NULL); + _instances = new Raul::Array<SLV2Instance>(_polyphony, NULL); + + uint32_t port_buffer_size = 0; + + for (uint32_t i=0; i < _polyphony; ++i) { + (*_instances)[i] = slv2_plugin_instantiate(plug, _srate, info->lv2_features()); + if ((*_instances)[i] == NULL) { + cerr << "Failed to instantiate plugin!" << endl; + return false; + } + + const void* ctx_ext = slv2_instance_get_extension_data( + (*_instances)[i], LV2_CONTEXT_MESSAGE); + + if (ctx_ext) { + cerr << "HAS CONTEXT EXTENSION" << endl; + if (_message_run == NULL) + _message_run = new MessageRunFuncs(_polyphony, NULL); + LV2MessageContext* mc = (LV2MessageContext*)ctx_ext; + (*_message_run)[i] = mc->message_run; + } + } + + string port_name; + string port_path; + + PortImpl* port = NULL; + + float* def_values = new float[num_ports]; + slv2_plugin_get_port_ranges_float(plug, 0, 0, def_values); + + for (uint32_t j=0; j < num_ports; ++j) { + SLV2Port id = slv2_plugin_get_port_by_index(plug, j); + + // LV2 shortnames are guaranteed to be unique, valid C identifiers + port_name = slv2_value_as_string(slv2_port_get_symbol(plug, id)); + + assert(port_name.find("/") == string::npos); + + port_path = path() + "/" + port_name; + + DataType data_type = DataType::UNKNOWN; + if (slv2_port_is_a(plug, id, info->control_class)) { + data_type = DataType::CONTROL; + port_buffer_size = 1; + } else if (slv2_port_is_a(plug, id, info->audio_class)) { + data_type = DataType::AUDIO; + port_buffer_size = _buffer_size; + } else if (slv2_port_is_a(plug, id, info->event_class)) { + data_type = DataType::EVENT; + port_buffer_size = _buffer_size; + } + + enum { UNKNOWN, INPUT, OUTPUT } direction = UNKNOWN; + if (slv2_port_is_a(plug, id, info->input_class)) { + direction = INPUT; + } else if (slv2_port_is_a(plug, id, info->output_class)) { + direction = OUTPUT; + } + + if (data_type == DataType::UNKNOWN || direction == UNKNOWN) { + delete _ports; + _ports = NULL; + delete _instances; + _instances = NULL; + return false; + } + + // FIXME: need nice type preserving SLV2Value -> Raul::Atom conversion + float def = isnan(def_values[j]) ? 0.0f : def_values[j]; + Atom defatm = def; + + if (direction == INPUT) + port = new InputPort(this, port_name, j, _polyphony, data_type, defatm, port_buffer_size); + else + port = new OutputPort(this, port_name, j, _polyphony, data_type, defatm, port_buffer_size); + + if (direction == INPUT && data_type == DataType::CONTROL) + ((AudioBuffer*)port->buffer(0))->set_value(def, 0, 0); + + SLV2Value pred = slv2_value_new_uri(info->lv2_world(), + "http://lv2plug.in/ns/dev/contexts#context"); + SLV2Values contexts = slv2_port_get_value(plug, id, pred); + for (uint32_t i = 0; i < slv2_values_size(contexts); ++i) { + SLV2Value c = slv2_values_get_at(contexts, i); + const char* context = slv2_value_as_string(c); + if (!strcmp("http://lv2plug.in/ns/dev/contexts#MessageContext", context)) { + cout << "MESSAGE CONTEXT!" << endl; + port->set_context(Context::MESSAGE); + } else { + cout << "UNKNOWN CONTEXT: " + << slv2_value_as_string(slv2_values_get_at(contexts, i)) + << endl; + } + } + + _ports->at(j) = port; + } + + delete [] def_values; + + return true; +} + + +void +LV2Node::activate() +{ + NodeBase::activate(); + + for (uint32_t i=0; i < _polyphony; ++i) { + for (unsigned long j=0; j < num_ports(); ++j) { + PortImpl* const port = _ports->at(j); + + set_port_buffer(i, j, port->buffer(i)); + + if (port->type() == DataType::CONTROL) { + ((AudioBuffer*)port->buffer(i))->set_value(port->value().get_float(), 0, 0); + } else if (port->type() == DataType::AUDIO) { + ((AudioBuffer*)port->buffer(i))->set_value(0.0f, 0, 0); + } + } + slv2_instance_activate((*_instances)[i]); + } +} + + +void +LV2Node::deactivate() +{ + NodeBase::deactivate(); + + for (uint32_t i=0; i < _polyphony; ++i) + slv2_instance_deactivate((*_instances)[i]); +} + + +void +LV2Node::message_process(MessageContext& context, uint32_t* output) +{ + // FIXME: voice + if (_message_run) + (*_message_run)[0]((*_instances)[0], output); + + /* MESSAGE PROCESS */ +} + + +void +LV2Node::process(ProcessContext& context) +{ + NodeBase::pre_process(context); + + for (uint32_t i=0; i < _polyphony; ++i) + slv2_instance_run((*_instances)[i], context.nframes()); + + NodeBase::post_process(context); +} + + +void +LV2Node::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) +{ + assert(voice < _polyphony); + + slv2_instance_connect_port((*_instances)[voice], port_num, buf->raw_data()); +} + + +} // namespace Ingen + diff --git a/src/engine/LV2Node.hpp b/src/engine/LV2Node.hpp new file mode 100644 index 00000000..3187005c --- /dev/null +++ b/src/engine/LV2Node.hpp @@ -0,0 +1,75 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LV2NODE_H +#define LV2NODE_H + +#include <string> +#include <slv2/slv2.h> +#include "types.hpp" +#include "NodeBase.hpp" + +namespace Ingen { + +class LV2Plugin; + + +/** An instance of a LV2 plugin. + * + * \ingroup engine + */ +class LV2Node : public NodeBase +{ +public: + LV2Node(LV2Plugin* plugin, + const string& name, + bool polyphonic, + PatchImpl* parent, + SampleRate srate, + size_t buffer_size); + + ~LV2Node(); + + bool instantiate(); + + bool prepare_poly(uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void activate(); + void deactivate(); + + void message_process(MessageContext& context, uint32_t* output); + + void process(ProcessContext& context); + + void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf); + +protected: + LV2Plugin* _lv2_plugin; + Raul::Array<SLV2Instance>* _instances; + Raul::Array<SLV2Instance>* _prepared_instances; + + typedef bool (*MessageRunFunc)(LV2_Handle, uint32_t*); + typedef Raul::Array<MessageRunFunc> MessageRunFuncs; + MessageRunFuncs* _message_run; +}; + + +} // namespace Ingen + +#endif // LV2NODE_H + diff --git a/src/engine/LV2Plugin.cpp b/src/engine/LV2Plugin.cpp new file mode 100644 index 00000000..90a3a6b8 --- /dev/null +++ b/src/engine/LV2Plugin.cpp @@ -0,0 +1,94 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <glibmm.h> +#include <redlandmm/World.hpp> +#include "LV2Plugin.hpp" +#include "LV2Node.hpp" +#include "NodeImpl.hpp" +#include "Engine.hpp" +#include "AudioDriver.hpp" + +namespace Ingen { + + +const string +LV2Plugin::symbol() const +{ + string working = _uri; + if (working[working.length()-1] == '/') + working = working.substr(0, working.length()-1); + + while (working.length() > 0) { + size_t last_slash = working.find_last_of("/"); + const string symbol = working.substr(last_slash+1); + if ( (symbol[0] >= 'a' && symbol[0] <= 'z') + || (symbol[0] >= 'A' && symbol[0] <= 'Z') ) + return Path::nameify(symbol); + else + working = working.substr(0, last_slash); + } + + return "lv2_symbol"; +} + + +const string +LV2Plugin::name() const +{ + if (_name) + return slv2_value_as_string(_name); + else + return "(no name)"; +} + + +NodeImpl* +LV2Plugin::instantiate(const string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine) +{ + SampleCount srate = engine.audio_driver()->sample_rate(); + SampleCount buffer_size = engine.audio_driver()->buffer_size(); + + load(); // FIXME: unload at some point + + Glib::Mutex::Lock lock(engine.world()->rdf_world->mutex()); + LV2Node* n = new LV2Node(this, name, polyphonic, parent, srate, buffer_size); + + if ( ! n->instantiate() ) { + delete n; + n = NULL; + } + + return n; +} + + +void +LV2Plugin::slv2_plugin(SLV2Plugin p) +{ + _slv2_plugin = p; + if (_name) + slv2_value_free(_name); + _name = slv2_plugin_get_name(_slv2_plugin); +} + + +} // namespace Ingen diff --git a/src/engine/LV2Plugin.hpp b/src/engine/LV2Plugin.hpp new file mode 100644 index 00000000..c9bc4ff9 --- /dev/null +++ b/src/engine/LV2Plugin.hpp @@ -0,0 +1,84 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LV2PLUGIN_H +#define LV2PLUGIN_H + +#include CONFIG_H_PATH + +#ifndef HAVE_SLV2 +#error "This file requires SLV2, but HAVE_SLV2 is not defined. Please report." +#endif + +#include <cstdlib> +#include <glibmm/module.h> +#include <boost/utility.hpp> +#include <dlfcn.h> +#include <string> +#include <iostream> +#include <slv2/slv2.h> +#include <raul/SharedPtr.hpp> +#include "types.hpp" +#include "PluginImpl.hpp" +#include "LV2Info.hpp" + +using std::string; +using Ingen::Shared::Plugin; + + +namespace Ingen { + +class PatchImpl; +class NodeImpl; + + +/** Implementation of an LV2 plugin (loaded shared library). + */ +class LV2Plugin : public PluginImpl +{ +public: + LV2Plugin(SharedPtr<LV2Info> lv2_info, const string& uri) + : PluginImpl(Plugin::LV2, uri) + , _name(NULL) + , _slv2_plugin(NULL) + , _lv2_info(lv2_info) + {} + + NodeImpl* instantiate(const string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine); + + const string symbol() const; + const string name() const; + + SharedPtr<LV2Info> lv2_info() const { return _lv2_info; } + + SLV2Plugin slv2_plugin() const { return _slv2_plugin; } + void slv2_plugin(SLV2Plugin p); + +private: + SLV2Value _name; + SLV2Plugin _slv2_plugin; + SharedPtr<LV2Info> _lv2_info; +}; + + +} // namespace Ingen + +#endif // LV2PLUGIN_H + diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am new file mode 100644 index 00000000..3c464de3 --- /dev/null +++ b/src/engine/Makefile.am @@ -0,0 +1,205 @@ +SUBDIRS = events + +MAINTAINERCLEANFILES = Makefile.in + +moduledir = $(libdir)/ingen + +module_LTLIBRARIES = libingen_engine.la +libingen_engine_la_CXXFLAGS = \ + @GLIBMM_CFLAGS@ \ + @INGEN_CFLAGS@ \ + @JACK_CFLAGS@ \ + @LIBLO_CFLAGS@ \ + @RAUL_CFLAGS@ \ + @REDLANDMM_CFLAGS@ \ + @SLV2_CFLAGS@ \ + @SOUP_CFLAGS@ + +libingen_engine_la_LDFLAGS = -no-undefined -module -avoid-version +libingen_engine_la_LIBADD = \ + ../shared/libingen_shared.la \ + ../module/libingen_module.la \ + @GLIBMM_LIBS@ \ + @JACK_LIBS@ \ + @LIBLO_LIBS@ \ + @RAUL_LIBS@ \ + @REDLANDMM_LIBS@ \ + @SLV2_LIBS@ \ + @SOUP_LIBS@ + +AM_CFLAGS=-std=c99 + +libingen_engine_la_SOURCES = \ + AudioBuffer.cpp \ + AudioBuffer.hpp \ + AudioDriver.hpp \ + Buffer.cpp \ + Buffer.hpp \ + ClientBroadcaster.cpp \ + ClientBroadcaster.hpp \ + CompiledPatch.hpp \ + ConnectionImpl.cpp \ + ConnectionImpl.hpp \ + Driver.hpp \ + DuplexPort.cpp \ + DuplexPort.hpp \ + Engine.cpp \ + Engine.hpp \ + EngineStore.cpp \ + EngineStore.hpp \ + Event.cpp \ + Event.hpp \ + EventBuffer.cpp \ + EventBuffer.hpp \ + EventSink.cpp \ + EventSink.hpp \ + EventSource.hpp \ + GraphObjectImpl.cpp \ + GraphObjectImpl.hpp \ + InputPort.cpp \ + InputPort.hpp \ + InternalPlugin.cpp \ + InternalPlugin.hpp \ + JackAudioDriver.cpp \ + JackAudioDriver.hpp \ + JackMidiDriver.cpp \ + JackMidiDriver.hpp \ + LADSPAPlugin.cpp \ + LADSPAPlugin.hpp \ + LV2Info.cpp \ + LV2Info.hpp \ + LV2Plugin.cpp \ + LV2Plugin.hpp \ + MessageContext.cpp \ + MessageContext.hpp \ + MidiControlNode.cpp \ + MidiControlNode.hpp \ + MidiDriver.hpp \ + MidiNoteNode.cpp \ + MidiNoteNode.hpp \ + MidiTriggerNode.cpp \ + MidiTriggerNode.hpp \ + NodeBase.cpp \ + NodeBase.hpp \ + NodeFactory.cpp \ + NodeFactory.hpp \ + NodeImpl.hpp \ + OSCClientSender.cpp \ + OSCClientSender.hpp \ + OSCDriver.hpp \ + OSCEngineReceiver.cpp \ + OSCEngineReceiver.hpp \ + ObjectSender.cpp \ + ObjectSender.hpp \ + OutputPort.cpp \ + OutputPort.hpp \ + PatchImpl.cpp \ + PatchImpl.hpp \ + PatchPlugin.hpp \ + PluginImpl.cpp \ + PluginImpl.hpp \ + PortImpl.cpp \ + PortImpl.hpp \ + PostProcessor.cpp \ + PostProcessor.hpp \ + ProcessContext.hpp \ + ProcessSlave.cpp \ + ProcessSlave.hpp \ + QueuedEngineInterface.cpp \ + QueuedEngineInterface.hpp \ + QueuedEvent.cpp \ + QueuedEvent.hpp \ + QueuedEventSource.cpp \ + QueuedEventSource.hpp \ + Responder.hpp \ + ThreadManager.hpp \ + TransportNode.cpp \ + TransportNode.hpp \ + engine.cpp \ + engine.hpp \ + events.hpp \ + events/AllNotesOffEvent.cpp \ + events/AllNotesOffEvent.hpp \ + events/ClearPatchEvent.cpp \ + events/ClearPatchEvent.hpp \ + events/ConnectionEvent.cpp \ + events/ConnectionEvent.hpp \ + events/CreateNodeEvent.cpp \ + events/CreateNodeEvent.hpp \ + events/CreatePatchEvent.cpp \ + events/CreatePatchEvent.hpp \ + events/CreatePortEvent.cpp \ + events/CreatePortEvent.hpp \ + events/DeactivateEvent.cpp \ + events/DeactivateEvent.hpp \ + events/DestroyEvent.cpp \ + events/DestroyEvent.hpp \ + events/DisconnectAllEvent.cpp \ + events/DisconnectAllEvent.hpp \ + events/DisconnectionEvent.cpp \ + events/DisconnectionEvent.hpp \ + events/EnablePatchEvent.cpp \ + events/EnablePatchEvent.hpp \ + events/LoadPluginsEvent.cpp \ + events/LoadPluginsEvent.hpp \ + events/MidiLearnEvent.cpp \ + events/MidiLearnEvent.hpp \ + events/NoteEvent.cpp \ + events/NoteEvent.hpp \ + events/PingQueuedEvent.hpp \ + events/RegisterClientEvent.cpp \ + events/RegisterClientEvent.hpp \ + events/RenameEvent.cpp \ + events/RenameEvent.hpp \ + events/RequestAllObjectsEvent.cpp \ + events/RequestAllObjectsEvent.hpp \ + events/RequestMetadataEvent.cpp \ + events/RequestMetadataEvent.hpp \ + events/RequestObjectEvent.cpp \ + events/RequestObjectEvent.hpp \ + events/RequestPluginEvent.cpp \ + events/RequestPluginEvent.hpp \ + events/RequestPluginsEvent.cpp \ + events/RequestPluginsEvent.hpp \ + events/RequestPortValueEvent.cpp \ + events/RequestPortValueEvent.hpp \ + events/SendPortActivityEvent.cpp \ + events/SendPortActivityEvent.hpp \ + events/SendPortValueEvent.cpp \ + events/SendPortValueEvent.hpp \ + events/SetMetadataEvent.cpp \ + events/SetMetadataEvent.hpp \ + events/SetPolyphonicEvent.cpp \ + events/SetPolyphonicEvent.hpp \ + events/SetPolyphonyEvent.cpp \ + events/SetPolyphonyEvent.hpp \ + events/SetPortValueEvent.cpp \ + events/SetPortValueEvent.hpp \ + events/UnregisterClientEvent.cpp \ + events/UnregisterClientEvent.hpp \ + jack_compat.h \ + lv2_contexts.h \ + tuning.hpp \ + types.hpp \ + util.hpp + +if WITH_LADSPA +libingen_engine_la_SOURCES += \ + LADSPANode.hpp \ + LADSPANode.cpp +endif + +if WITH_LV2 +libingen_engine_la_SOURCES += \ + LV2Node.hpp \ + LV2Node.cpp +endif + +if WITH_SOUP +libingen_engine_la_SOURCES += \ + HTTPEngineReceiver.cpp \ + HTTPEngineReceiver.hpp +endif + + + diff --git a/src/engine/MessageContext.cpp b/src/engine/MessageContext.cpp new file mode 100644 index 00000000..30f04b05 --- /dev/null +++ b/src/engine/MessageContext.cpp @@ -0,0 +1,32 @@ +/* This file is part of Ingen. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "MessageContext.hpp" +#include "NodeImpl.hpp" + +namespace Ingen { + +void +MessageContext::run(NodeImpl* node) +{ + uint32_t outputs; + node->message_process(*this, &outputs); + + // Don't care what the plugin output, yet... +} + +} // namespace Ingen diff --git a/src/engine/MessageContext.hpp b/src/engine/MessageContext.hpp new file mode 100644 index 00000000..31531521 --- /dev/null +++ b/src/engine/MessageContext.hpp @@ -0,0 +1,51 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2008 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MESSAGECONTEXT_H +#define MESSAGECONTEXT_H + +#include "EventSink.hpp" +#include "Context.hpp" + +namespace Ingen { + +class NodeImpl; + +/** Context of a message_process() call. + * + * The message context is a non-hard-realtime thread used to execute things + * that can take too long to execute in an audio thread, and do sloppy timed + * event propagation and scheduling. Interface to plugins via the + * LV2 contexts extension. + * + * \ingroup engine + */ +class MessageContext : public Context +{ +public: + MessageContext(Engine& engine) + : Context(engine, MESSAGE) + {} + + void run(NodeImpl* node); +}; + + +} // namespace Ingen + +#endif // MESSAGECONTEXT_H + diff --git a/src/engine/MidiControlNode.cpp b/src/engine/MidiControlNode.cpp new file mode 100644 index 00000000..bfe0e57e --- /dev/null +++ b/src/engine/MidiControlNode.cpp @@ -0,0 +1,141 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <math.h> +#include <raul/midi_events.h> +#include "MidiControlNode.hpp" +#include "PostProcessor.hpp" +#include "MidiLearnEvent.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" +#include "EventBuffer.hpp" +#include "util.hpp" + +namespace Ingen { + + +MidiControlNode::MidiControlNode(const string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate, + size_t buffer_size) + : NodeBase(new InternalPlugin(NS_INGEN "control_node", "controller", "MIDI Controller") + , path, false, parent, srate, buffer_size) + , _learning(false) +{ + _ports = new Raul::Array<PortImpl*>(6); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _param_port = new InputPort(this, "controller", 1, 1, DataType::CONTROL, 0.0f, 1); + _param_port->set_variable("ingen:minimum", 0.0f); + _param_port->set_variable("ingen:maximum", 127.0f); + _param_port->set_variable("ingen:integer", true); + _ports->at(1) = _param_port; + + _log_port = new InputPort(this, "logarithmic", 2, 1, DataType::CONTROL, 0.0f, 1); + _log_port->set_variable("ingen:toggled", true); + _ports->at(2) = _log_port; + + _min_port = new InputPort(this, "minimum", 3, 1, DataType::CONTROL, 0.0f, 1); + _ports->at(3) = _min_port; + + _max_port = new InputPort(this, "maximum", 4, 1, DataType::CONTROL, 1.0f, 1); + _ports->at(4) = _max_port; + + _audio_port = new OutputPort(this, "ar_output", 5, 1, DataType::AUDIO, 0.0f, _buffer_size); + _ports->at(5) = _audio_port; +} + + +void +MidiControlNode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0); + assert(midi_in->this_nframes() == context.nframes()); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + // FIXME: type + if (size >= 3 && (buf[0] & 0xF0) == MIDI_CMD_CONTROL) + control(context, buf[1], buf[2], frames + context.start()); + + midi_in->increment(); + } + + NodeBase::post_process(context); +} + + +void +MidiControlNode::control(ProcessContext& context, uchar control_num, uchar val, FrameTime time) +{ + assert(time - context.start() < _buffer_size); + + Sample scaled_value; + + const Sample nval = (val / 127.0f); // normalized [0, 1] + + if (_learning) { + assert(false); // FIXME FIXME FIXME +#if 0 + assert(_learn_event != NULL); + _param_port->set_value(control_num, offset); + assert(_param_port->buffer(0)->value_at(0) == control_num); + _learn_event->set_value(control_num); + _learn_event->execute(offset); + //Engine::instance().post_processor()->push(_learn_event); + //Engine::instance().post_processor()->whip(); + _learning = false; + _learn_event = NULL; +#endif + } + + const Sample min_port_val = ((AudioBuffer*)_min_port->buffer(0))->value_at(0); + const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0))->value_at(0); + const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0))->value_at(0); + + if (log_port_val > 0.0f) { + // haaaaack, stupid negatives and logarithms + Sample log_offset = 0; + if (min_port_val < 0) + log_offset = fabs(min_port_val); + const Sample min = log(min_port_val + 1 + log_offset); + const Sample max = log(max_port_val + 1 + log_offset); + scaled_value = expf(nval * (max - min) + min) - 1 - log_offset; + } else { + scaled_value = ((nval) * (max_port_val - min_port_val)) + min_port_val; + } + + if (control_num == ((AudioBuffer*)_param_port->buffer(0))->value_at(0)) + ((AudioBuffer*)_audio_port->buffer(0))->set_value(scaled_value, context.start(), time); +} + + +} // namespace Ingen + diff --git a/src/engine/MidiControlNode.hpp b/src/engine/MidiControlNode.hpp new file mode 100644 index 00000000..2f0496a3 --- /dev/null +++ b/src/engine/MidiControlNode.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MIDICONTROLNODE_H +#define MIDICONTROLNODE_H + +#include <string> +#include "NodeBase.hpp" + +namespace Ingen { + +class MidiLearnResponseEvent; +class InputPort; +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 NodeBase +{ +public: + MidiControlNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + void process(ProcessContext& context); + + void control(ProcessContext& context, uchar control_num, uchar val, FrameTime time); + + void learn(MidiLearnResponseEvent* ev) { _learning = true; _learn_event = ev; } + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _param_port; + InputPort* _log_port; + InputPort* _min_port; + InputPort* _max_port; + OutputPort* _audio_port; + + MidiLearnResponseEvent* _learn_event; +}; + + +} // namespace Ingen + +#endif // MIDICONTROLNODE_H diff --git a/src/engine/MidiDriver.hpp b/src/engine/MidiDriver.hpp new file mode 100644 index 00000000..e268124b --- /dev/null +++ b/src/engine/MidiDriver.hpp @@ -0,0 +1,100 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MIDIDRIVER_H +#define MIDIDRIVER_H + +#include <iostream> +#include "types.hpp" +#include "Driver.hpp" +#include "EventBuffer.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { + +class ProcessContext; + + +/** Midi driver abstract base class. + * + * \ingroup engine + */ +class MidiDriver : public Driver +{ +public: + MidiDriver() : Driver(DataType::EVENT) {} + + /** Prepare input for the specified (upcoming) cycle. + * + * Realtime safe, run in audio thread before executing the graph for a cycle. + */ + virtual void pre_process(ProcessContext& context) = 0; + + /** Prepare output for the specified (just completed) cycle. + * + * Realtime safe, run in audio thread after executing the graph for a cycle. + */ + virtual void post_process(ProcessContext& context) = 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() { + std::cout << "[DummyMidiDriver] Started Dummy MIDI driver." << std::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(DuplexPort* patch_port) { return NULL; } + + DriverPort* driver_port(const Raul::Path& path) { return NULL; } + + DriverPort* new_port(DuplexPort* patch_port) { return NULL; } + + void add_port(DriverPort* port) {} + DriverPort* remove_port(const Raul::Path& path) { return NULL; } + + void pre_process(ProcessContext& context) {} + void post_process(ProcessContext& context) {} +}; + + + +} // namespace Ingen + +#endif // MIDIDRIVER_H diff --git a/src/engine/MidiNoteNode.cpp b/src/engine/MidiNoteNode.cpp new file mode 100644 index 00000000..ae7a7f0e --- /dev/null +++ b/src/engine/MidiNoteNode.cpp @@ -0,0 +1,390 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Array.hpp> +#include <raul/Maid.hpp> +#include <raul/midi_events.h> +#include <cmath> +#include <iostream> +#include "AudioBuffer.hpp" +#include "AudioDriver.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "EventBuffer.hpp" +#include "MidiNoteNode.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { + + +MidiNoteNode::MidiNoteNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeBase(new InternalPlugin(NS_INGEN "note_node", "note", "MIDI Note"), + path, polyphonic, parent, srate, buffer_size) + , _voices(new Raul::Array<Voice>(_polyphony)) + , _prepared_voices(NULL) + , _sustain(false) +{ + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _freq_port = new OutputPort(this, "frequency", 1, _polyphony, DataType::AUDIO, 440.0f, _buffer_size); + _ports->at(1) = _freq_port; + + _vel_port = new OutputPort(this, "velocity", 2, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _vel_port->set_variable("ingen:minimum", 0.0f); + _vel_port->set_variable("ingen:maximum", 1.0f); + _ports->at(2) = _vel_port; + + _gate_port = new OutputPort(this, "gate", 3, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _gate_port->set_variable("ingen:toggled", true); + _ports->at(3) = _gate_port; + + _trig_port = new OutputPort(this, "trigger", 4, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _trig_port->set_variable("ingen:toggled", true); + _ports->at(4) = _trig_port; +} + + +MidiNoteNode::~MidiNoteNode() +{ + delete _voices; +} + + +bool +MidiNoteNode::prepare_poly(uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeBase::prepare_poly(poly); + + if (_prepared_voices && poly <= _prepared_voices->size()) + return true; + + _prepared_voices = new Raul::Array<Voice>(poly, *_voices); + + return true; +} + + +bool +MidiNoteNode::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeBase::apply_poly(maid, poly); + + if (_prepared_voices) { + assert(poly <= _prepared_voices->size()); + maid.push(_voices); + _voices = _prepared_voices; + _prepared_voices = NULL; + } + + _polyphony = poly; + assert(_voices->size() >= _polyphony); + + return true; +} + + +void +MidiNoteNode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + unsigned char* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0); + assert(midi_in->this_nframes() == context.nframes()); + + //cerr << path() << " # input events: " << midi_in->event_count() << endl; + + if (midi_in->event_count() > 0) + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + + /*cout << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": "; + for (uint16_t i = 0; i < size; ++i) + cout << (int)((char)buf[i]) << " "; + cout << endl;*/ + + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + switch (buf[1]) { + case MIDI_CTL_ALL_NOTES_OFF: + case MIDI_CTL_ALL_SOUNDS_OFF: + all_notes_off(context, time); + break; + case MIDI_CTL_SUSTAIN: + if (buf[2] > 63) + sustain_on(context, time); + else + sustain_off(context, time); + break; + case MIDI_CMD_BENDER: + // ? + break; + default: + //cerr << "Ignored controller " << buf[1] << endl; + break; + } + break; + default: + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + break; + } + } else { + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + } + + if (midi_in->increment() == midi_in->this_nframes()) + break; + } + + NodeBase::post_process(context); +} + + +void +MidiNoteNode::note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + assert(note_num <= 127); + + Key* key = &_keys[note_num]; + Voice* voice = NULL; + uint32_t voice_num = 0; + + if (key->state != Key::OFF) { + //cerr << "[MidiNoteNode] Double midi note received" << endl; + return; + } + + // Look for free voices + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state == Voice::Voice::FREE) { + voice = &(*_voices)[i]; + voice_num = i; + break; + } + } + + // If we didn't find a free one, steal the oldest + if (voice == NULL) { + voice_num = 0; + voice = &(*_voices)[0]; + jack_nframes_t oldest_time = (*_voices)[0].time; + for (uint32_t i=1; i < _polyphony; ++i) { + if ((*_voices)[i].time < oldest_time) { + voice = &(*_voices)[i]; + voice_num = i; + oldest_time = voice->time; + } + } + } + assert(voice != NULL); + assert(voice == &(*_voices)[voice_num]); + + /*cerr << "[MidiNoteNode] Note " << (int)note_num << " on @ " << time + << ". Voice " << voice_num << " / " << _polyphony << endl;*/ + + // Update stolen key, if applicable + if (voice->state == Voice::Voice::ACTIVE) { + assert(_keys[voice->note].state == Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + _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; + + // Trigger voice + voice->state = Voice::Voice::ACTIVE; + voice->note = note_num; + voice->time = time; + + assert(_keys[voice->note].state == Key::Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + + ((AudioBuffer*)_freq_port->buffer(voice_num))->set_value(note_to_freq(note_num), context.start(), time); + ((AudioBuffer*)_vel_port->buffer(voice_num))->set_value(velocity/127.0, context.start(), time); + ((AudioBuffer*)_gate_port->buffer(voice_num))->set_value(1.0f, context.start(), time); + + // trigger (one sample) + ((AudioBuffer*)_trig_port->buffer(voice_num))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(voice_num))->set_value(0.0f, context.start(), time + 1); + + assert(key->state == Key::Key::ON_ASSIGNED); + assert(voice->state == Voice::Voice::ACTIVE); + assert(key->voice == voice_num); + assert((*_voices)[key->voice].note == note_num); +} + + +void +MidiNoteNode::note_off(ProcessContext& context, uchar note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + Key* key = &_keys[note_num]; + + //cerr << "[MidiNoteNode] Note " << (int)note_num << " off @ " << time << endl; + + if (key->state == Key::ON_ASSIGNED) { + // Assigned key, turn off voice and key + if ((*_voices)[key->voice].state == Voice::ACTIVE) { + assert((*_voices)[key->voice].note == note_num); + + if ( ! _sustain) { + //cerr << "... free voice " << key->voice << endl; + free_voice(context, key->voice, time); + } else { + //cerr << "... hold voice " << key->voice << endl; + (*_voices)[key->voice].state = Voice::HOLDING; + } + + } else { +#ifndef NDEBUG + cerr << "WARNING: Assigned key, but voice not active" << endl; +#endif + } + } + + key->state = Key::OFF; +} + + +void +MidiNoteNode::free_voice(ProcessContext& context, uint32_t voice, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + // 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 (_keys[i].state == Key::ON_UNASSIGNED) { + if (replace_key == NULL || _keys[i].time > replace_key->time) { + replace_key = &_keys[i]; + replace_key_num = i; + } + } + } + + if (replace_key != NULL) { // Found a key to assign to freed voice + assert(&_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 + ((AudioBuffer*)_freq_port->buffer(voice))->set_value(note_to_freq(replace_key_num), context.start(), time); + + replace_key->state = Key::ON_ASSIGNED; + replace_key->voice = voice; + _keys[(*_voices)[voice].note].state = Key::ON_UNASSIGNED; + (*_voices)[voice].note = replace_key_num; + (*_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; + ((AudioBuffer*)_gate_port->buffer(voice))->set_value(0.0f, context.start(), time); + (*_voices)[voice].state = Voice::FREE; + } +} + + +void +MidiNoteNode::all_notes_off(ProcessContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + //cerr << "All notes off @ " << offset << endl; + + // FIXME: set all keys to Key::OFF? + + for (uint32_t i = 0; i < _polyphony; ++i) { + ((AudioBuffer*)_gate_port->buffer(i))->set_value(0.0f, context.start(), time); + (*_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(ProcessContext& context, FrameTime time) +{ + _sustain = true; +} + + +void +MidiNoteNode::sustain_off(ProcessContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + _sustain = false; + + for (uint32_t i=0; i < _polyphony; ++i) + if ((*_voices)[i].state == Voice::HOLDING) + free_voice(context, i, time); +} + + +} // namespace Ingen + diff --git a/src/engine/MidiNoteNode.hpp b/src/engine/MidiNoteNode.hpp new file mode 100644 index 00000000..eebcbda6 --- /dev/null +++ b/src/engine/MidiNoteNode.hpp @@ -0,0 +1,88 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MIDINOTENODE_H +#define MIDINOTENODE_H + +#include <string> +#include "types.hpp" +#include "NodeBase.hpp" + +namespace Ingen { + +class InputPort; +class OutputPort; + + +/** MIDI note input node. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class MidiNoteNode : public NodeBase +{ +public: + MidiNoteNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + ~MidiNoteNode(); + + bool prepare_poly(uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time); + void note_off(ProcessContext& context, uchar note_num, FrameTime time); + void all_notes_off(ProcessContext& context, FrameTime time); + + void sustain_on(ProcessContext& context, FrameTime time); + void sustain_off(ProcessContext& context, FrameTime time); + +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; uint32_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(ProcessContext& context, uint32_t voice, FrameTime time); + + Raul::Array<Voice>* _voices; + Raul::Array<Voice>* _prepared_voices; + Key _keys[128]; + bool _sustain; ///< Whether or not hold pedal is depressed + + InputPort* _midi_in_port; + OutputPort* _freq_port; + OutputPort* _vel_port; + OutputPort* _gate_port; + OutputPort* _trig_port; +}; + + +} // namespace Ingen + +#endif // MIDINOTENODE_H diff --git a/src/engine/MidiTriggerNode.cpp b/src/engine/MidiTriggerNode.cpp new file mode 100644 index 00000000..aa2c272f --- /dev/null +++ b/src/engine/MidiTriggerNode.cpp @@ -0,0 +1,135 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cmath> +#include <raul/midi_events.h> +#include "MidiTriggerNode.hpp" +#include "AudioBuffer.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "ProcessContext.hpp" +#include "EventBuffer.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { + + +MidiTriggerNode::MidiTriggerNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeBase(new InternalPlugin(NS_INGEN "trigger_node", "trigger", "MIDI Trigger"), + path, false, parent, srate, buffer_size) +{ + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _note_port = new InputPort(this, "note", 1, 1, DataType::CONTROL, 60.0f, 1); + _note_port->set_variable("ingen:minimum", 0.0f); + _note_port->set_variable("ingen:maximum", 127.0f); + _note_port->set_variable("ingen:integer", true); + _ports->at(1) = _note_port; + + _gate_port = new OutputPort(this, "gate", 2, 1, DataType::AUDIO, 0.0f, _buffer_size); + _ports->at(2) = _gate_port; + + _trig_port = new OutputPort(this, "trigger", 3, 1, DataType::AUDIO, 0.0f, _buffer_size); + _ports->at(3) = _trig_port; + + _vel_port = new OutputPort(this, "velocity", 4, 1, DataType::AUDIO, 0.0f, _buffer_size); + _ports->at(4) = _vel_port; +} + + +void +MidiTriggerNode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0); + assert(midi_in->this_nframes() == context.nframes()); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + if (buf[1] == MIDI_CTL_ALL_NOTES_OFF + || buf[1] == MIDI_CTL_ALL_SOUNDS_OFF) + ((AudioBuffer*)_gate_port->buffer(0))->set_value(0.0f, context.start(), time); + default: + break; + } + } + + midi_in->increment(); + } + + NodeBase::post_process(context); +} + + +void +MidiTriggerNode::note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + /*cerr << "[MidiTriggerNode] " << path() << " Note " << (int)note_num << " on @ " << time << endl;*/ + + Sample filter_note = ((AudioBuffer*)_note_port->buffer(0))->value_at(0); + if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uchar)filter_note)) { + ((AudioBuffer*)_gate_port->buffer(0))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0))->set_value(0.0f, context.start(), time + 1); + ((AudioBuffer*)_vel_port->buffer(0))->set_value(velocity / 127.0f, context.start(), time); + assert(((AudioBuffer*)_trig_port->buffer(0))->data()[time - context.start()] == 1.0f); + } +} + + +void +MidiTriggerNode::note_off(ProcessContext& context, uchar note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(time - context.start() < _buffer_size); + + if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0))->value_at(0))) + ((AudioBuffer*)_gate_port->buffer(0))->set_value(0.0f, context.start(), time); +} + + +} // namespace Ingen + diff --git a/src/engine/MidiTriggerNode.hpp b/src/engine/MidiTriggerNode.hpp new file mode 100644 index 00000000..ba96589b --- /dev/null +++ b/src/engine/MidiTriggerNode.hpp @@ -0,0 +1,61 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MIDITRIGGERNODE_H +#define MIDITRIGGERNODE_H + +#include <string> +#include "NodeBase.hpp" + +namespace Ingen { + +class InputPort; +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 NodeBase +{ +public: + MidiTriggerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uchar note_num, uchar velocity, FrameTime time); + void note_off(ProcessContext& context, uchar note_num, FrameTime time); + +private: + InputPort* _midi_in_port; + InputPort* _note_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _vel_port; +}; + + +} // namespace Ingen + +#endif // MIDITRIGGERNODE_H diff --git a/src/engine/NodeBase.cpp b/src/engine/NodeBase.cpp new file mode 100644 index 00000000..bb4f0e5c --- /dev/null +++ b/src/engine/NodeBase.cpp @@ -0,0 +1,230 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "NodeBase.hpp" +#include <cassert> +#include <iostream> +#include <stdint.h> +#include <raul/List.hpp> +#include <raul/Array.hpp> +#include "util.hpp" +#include "PluginImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "PortImpl.hpp" +#include "PatchImpl.hpp" +#include "EngineStore.hpp" +#include "ThreadManager.hpp" + +using namespace std; + +namespace Ingen { + + +NodeBase::NodeBase(PluginImpl* plugin, const string& name, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeImpl(parent, name, polyphonic) + , _plugin(plugin) + , _polyphony((polyphonic && parent) ? parent->internal_polyphony() : 1) + , _srate(srate) + , _buffer_size(buffer_size) + , _activated(false) + , _traversed(false) + , _input_ready(1) + , _process_lock(0) + , _n_inputs_ready(0) + , _ports(NULL) + , _providers(new Raul::List<NodeImpl*>()) + , _dependants(new Raul::List<NodeImpl*>()) +{ + assert(_plugin); + assert(_polyphony > 0); + assert(_parent == NULL || (_polyphony == parent->internal_polyphony() || _polyphony == 1)); +} + + +NodeBase::~NodeBase() +{ + if (_activated) + deactivate(); + + delete _providers; + delete _dependants; +} + + +Port* +NodeBase::port(uint32_t index) const +{ + return (*_ports)[index]; +} + + +const Plugin* +NodeBase::plugin() const +{ + return _plugin; +} + + +void +NodeBase::activate() +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + assert(!_activated); + _activated = true; +} + + +void +NodeBase::deactivate() +{ + // FIXME: Not true witn monolithic GUI/engine + //assert(ThreadManager::current_thread_id() == THREAD_POST_PROCESS); + assert(_activated); + _activated = false; +} + + +bool +NodeBase::prepare_poly(uint32_t poly) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + if (!_polyphonic) + return true; + + if (_ports) + for (size_t i=0; i < _ports->size(); ++i) + _ports->at(i)->prepare_poly(poly); + + return true; +} + + +bool +NodeBase::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + if (!_polyphonic) + return true; + + for (size_t i=0; i < num_ports(); ++i) { + _ports->at(i)->apply_poly(maid, poly); + assert(_ports->at(i)->poly() == poly); + } + + for (uint32_t i=0; i < num_ports(); ++i) + for (uint32_t j=0; j < _polyphony; ++j) + set_port_buffer(j, i, _ports->at(i)->buffer(j)); + + return true; +} + + +void +NodeBase::set_buffer_size(size_t size) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + _buffer_size = size; + + if (_ports) + for (size_t i=0; i < _ports->size(); ++i) + _ports->at(i)->set_buffer_size(size); +} + + +void +NodeBase::reset_input_ready() +{ + //cout << path() << " RESET" << endl; + _n_inputs_ready = 0; + _process_lock = 0; + _input_ready.reset(0); +} + + +bool +NodeBase::process_lock() +{ + return _process_lock.compare_and_exchange(0, 1); +} + + +void +NodeBase::process_unlock() +{ + _process_lock = 0; +} + + +void +NodeBase::wait_for_input(size_t num_providers) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + assert(_process_lock.get() == 1); + + while ((unsigned)_n_inputs_ready.get() < num_providers) { + //cout << path() << " WAITING " << _n_inputs_ready.get() << endl; + _input_ready.wait(); + //cout << path() << " CAUGHT SIGNAL" << endl; + //++_n_inputs_ready; + } + + //cout << path() << " READY" << endl; +} + + +void +NodeBase::signal_input_ready() +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + //cout << path() << " SIGNAL" << endl; + ++_n_inputs_ready; + _input_ready.post(); +} + + +/** Prepare to run a cycle (in the audio thread) + */ +void +NodeBase::pre_process(ProcessContext& context) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + // Mix down any ports with multiple inputs + for (size_t i=0; i < num_ports(); ++i) + _ports->at(i)->pre_process(context); +} + + +/** Prepare to run a cycle (in the audio thread) + */ +void +NodeBase::post_process(ProcessContext& context) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + /* Write output ports */ + if (_ports) + for (size_t i=0; i < _ports->size(); ++i) + _ports->at(i)->post_process(context); +} + + +} // namespace Ingen + diff --git a/src/engine/NodeBase.hpp b/src/engine/NodeBase.hpp new file mode 100644 index 00000000..e3710aa9 --- /dev/null +++ b/src/engine/NodeBase.hpp @@ -0,0 +1,132 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef NODEBASE_H +#define NODEBASE_H + +#include "types.hpp" +#include <string> +#include <cstdlib> +#include <raul/Semaphore.hpp> +#include <raul/AtomicInt.hpp> +#include "interface/Port.hpp" +#include "NodeImpl.hpp" + +using std::string; + +namespace Ingen { + +class PluginImpl; +class PatchImpl; +class EngineStore; + +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 NodeImpl +{ +public: + NodeBase(PluginImpl* plugin, + const string& name, + bool poly, + PatchImpl* parent, + SampleRate rate, + size_t buffer_size); + + virtual ~NodeBase(); + + virtual void activate(); + virtual void deactivate(); + bool activated() { return _activated; } + + virtual bool prepare_poly(uint32_t poly); + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly); + + virtual void reset_input_ready(); + virtual bool process_lock(); + virtual void process_unlock(); + virtual void wait_for_input(size_t num_providers); + virtual unsigned n_inputs_ready() const { return _n_inputs_ready.get(); } + + virtual void message_process(MessageContext& context, uint32_t* output) {} + + virtual void pre_process(ProcessContext& context); + virtual void process(ProcessContext& context) = 0; + virtual void post_process(ProcessContext& context); + + virtual void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) {} + + virtual void set_buffer_size(size_t size); + + SampleRate sample_rate() const { return _srate; } + size_t buffer_size() const { return _buffer_size; } + uint32_t num_ports() const { return _ports ? _ports->size() : 0; } + uint32_t polyphony() const { return _polyphony; } + bool traversed() const { return _traversed; } + void traversed(bool b) { _traversed = b; } + + virtual Port* port(uint32_t index) const; + virtual PortImpl* port_impl(uint32_t index) const { return (*_ports)[index]; } + + /* These are NOT to be used in the audio thread! + * The providers and dependants in CompiledNode are for that + */ + + Raul::List<NodeImpl*>* providers() { return _providers; } + void providers(Raul::List<NodeImpl*>* l) { _providers = l; } + + Raul::List<NodeImpl*>* dependants() { return _dependants; } + void dependants(Raul::List<NodeImpl*>* l) { _dependants = l; } + + virtual const Plugin* plugin() const; + virtual PluginImpl* plugin_impl() const { return _plugin; } + virtual void plugin(PluginImpl* pi) { _plugin = pi; } + + /** A node's parent is always a patch, so static cast should be safe */ + inline PatchImpl* parent_patch() const { return (PatchImpl*)_parent; } + +protected: + virtual void signal_input_ready(); + + PluginImpl* _plugin; + + uint32_t _polyphony; + SampleRate _srate; + size_t _buffer_size; + bool _activated; + + bool _traversed; ///< Flag for process order algorithm + Raul::Semaphore _input_ready; ///< Parallelism: input ready signal + Raul::AtomicInt _process_lock; ///< Parallelism: Waiting on inputs 'lock' + Raul::AtomicInt _n_inputs_ready; ///< Parallelism: # input ready signals this cycle + Raul::Array<PortImpl*>* _ports; ///< Access in audio thread only + Raul::List<NodeImpl*>* _providers; ///< Nodes connected to this one's input ports + Raul::List<NodeImpl*>* _dependants; ///< Nodes this one's output ports are connected to +}; + + +} // namespace Ingen + +#endif // NODEBASE_H diff --git a/src/engine/NodeFactory.cpp b/src/engine/NodeFactory.cpp new file mode 100644 index 00000000..c868d067 --- /dev/null +++ b/src/engine/NodeFactory.cpp @@ -0,0 +1,285 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include CONFIG_H_PATH +#include <cstdlib> +#include <pthread.h> +#include <dirent.h> +#include <float.h> +#include <cmath> +#include <redlandmm/World.hpp> +#include "module/World.hpp" +#include "NodeFactory.hpp" +#include "ThreadManager.hpp" +#include "MidiNoteNode.hpp" +#include "MidiTriggerNode.hpp" +#include "MidiControlNode.hpp" +#include "TransportNode.hpp" +#include "PatchImpl.hpp" +#include "InternalPlugin.hpp" +#ifdef HAVE_LADSPA +#include "LADSPANode.hpp" +#include "LADSPAPlugin.hpp" +#endif +#ifdef HAVE_SLV2 +#include <slv2/slv2.h> +#include "LV2Plugin.hpp" +#include "LV2Node.hpp" +#endif + +using namespace std; + +namespace Ingen { + + +NodeFactory::NodeFactory(Ingen::Shared::World* world) + : _world(world) + , _has_loaded(false) +#ifdef HAVE_SLV2 + , _lv2_info(new LV2Info(world)) +#endif +{ +} + + +NodeFactory::~NodeFactory() +{ + for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i) + if (i->second->type() != Plugin::Internal) + delete i->second; + + _plugins.clear(); +} + + +PluginImpl* +NodeFactory::plugin(const string& uri) +{ + const Plugins::const_iterator i = _plugins.find(uri); + return ((i != _plugins.end()) ? i->second : NULL); +} + + +/** DEPRECATED: Find a plugin by type, lib, label. + * + * Slow. Evil. Do not use. + */ +PluginImpl* +NodeFactory::plugin(const string& type, const string& lib, const string& label) +{ + if (type != "LADSPA" || lib == "" || label == "") + return NULL; + +#ifdef HAVE_LADSPA + for (Plugins::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) { + LADSPAPlugin* lp = dynamic_cast<LADSPAPlugin*>(i->second); + if (lp && lp->type_string() == type + && lp->library_name() == lib + && lp->label() == label) + return lp; + } +#endif + + cerr << "ERROR: Failed to find " << type << " plugin " << lib << " / " << label << endl; + + return NULL; +} + + +void +NodeFactory::load_plugins() +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + _world->rdf_world->mutex().lock(); + + // 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 (!_has_loaded) { + _plugins.clear(); // FIXME: assert empty? + + load_internal_plugins(); + +#ifdef HAVE_SLV2 + load_lv2_plugins(); +#endif + +#ifdef HAVE_LADSPA + load_ladspa_plugins(); +#endif + + _has_loaded = true; + } + + _world->rdf_world->mutex().unlock(); + + //cerr << "[NodeFactory] # Plugins: " << _plugins.size() << endl; +} + + +void +NodeFactory::load_internal_plugins() +{ + // This is a touch gross... + + PatchImpl* parent = new PatchImpl(*_world->local_engine, "dummy", 1, NULL, 1, 1, 1); + + NodeImpl* n = NULL; + n = new MidiNoteNode("foo", 1, parent, 1, 1); + _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl())); + delete n; + n = new MidiTriggerNode("foo", 1, parent, 1, 1); + _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl())); + delete n; + n = new MidiControlNode("foo", 1, parent, 1, 1); + _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl())); + delete n; + n = new TransportNode("foo", 1, parent, 1, 1); + _plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl())); + delete n; + + delete parent; +} + + +#ifdef HAVE_SLV2 +/** Loads information about all LV2 plugins into internal plugin database. + */ +void +NodeFactory::load_lv2_plugins() +{ + SLV2Plugins plugins = slv2_world_get_all_plugins(_world->slv2_world); + + //cerr << "[NodeFactory] Found " << slv2_plugins_size(plugins) << " LV2 plugins:" << endl; + + for (unsigned i=0; i < slv2_plugins_size(plugins); ++i) { + + SLV2Plugin lv2_plug = slv2_plugins_get_at(plugins, i); + + const string uri(slv2_value_as_uri(slv2_plugin_get_uri(lv2_plug))); + +#ifndef NDEBUG + assert(_plugins.find(uri) == _plugins.end()); +#endif + + LV2Plugin* const plugin = new LV2Plugin(_lv2_info, uri); + + plugin->slv2_plugin(lv2_plug); + plugin->library_path(slv2_uri_to_path(slv2_value_as_uri( + slv2_plugin_get_library_uri(lv2_plug)))); + _plugins.insert(make_pair(uri, plugin)); + } + + slv2_plugins_free(_world->slv2_world, plugins); +} +#endif // HAVE_SLV2 + + +#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; + } + + // Yep, this should use an sstream alright.. + while (ladspa_path != "") { + const string 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))) { + + LADSPA_Descriptor_Function df = NULL; + LADSPA_Descriptor* descriptor = NULL; + + if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) + continue; + + const string lib_path = dir +"/"+ pfile->d_name; + + // Ignore stupid libtool files. Kludge alert. + if (lib_path.substr(lib_path.length()-3) == ".la") { + //cerr << "WARNING: Skipping stupid libtool file " << pfile->d_name << endl; + continue; + } + + Glib::Module* plugin_library = new Glib::Module(lib_path, Glib::MODULE_BIND_LOCAL); + if (!plugin_library || !(*plugin_library)) { + cerr << "WARNING: Failed to load LADSPA library " << lib_path << endl; + continue; + } + + bool found = plugin_library->get_symbol("ladspa_descriptor", (void*&)df); + if (!found || !df) { + cerr << "WARNING: Non-LADSPA library found in LADSPA path: " << + lib_path << endl; + // Not a LADSPA plugin library + delete plugin_library; + continue; + } + + for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df(i)) != NULL; ++i) { + char id_str[11]; + snprintf(id_str, 11, "%lu", descriptor->UniqueID); + const string uri = string("ladspa:").append(id_str); + + const Plugins::const_iterator i = _plugins.find(uri); + + if (i == _plugins.end()) { + LADSPAPlugin* plugin = new LADSPAPlugin(lib_path, uri, + descriptor->UniqueID, + descriptor->Label, + descriptor->Name); + + _plugins.insert(make_pair(uri, plugin)); + + } else { + cerr << "Warning: Duplicate " << uri + << " - Using " << i->second->library_path() + << " over " << lib_path << endl; + } + } + + delete plugin_library; + } + closedir(pdir); + } +} +#endif // HAVE_LADSPA + + +} // namespace Ingen diff --git a/src/engine/NodeFactory.hpp b/src/engine/NodeFactory.hpp new file mode 100644 index 00000000..95194350 --- /dev/null +++ b/src/engine/NodeFactory.hpp @@ -0,0 +1,92 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef NODEFACTORY_H +#define NODEFACTORY_H + +#include CONFIG_H_PATH +#include "module/global.hpp" + +#include <list> +#include <map> +#include <string> +#include <pthread.h> +#include <glibmm/module.h> +#ifdef HAVE_SLV2 +#include <slv2/slv2.h> +#include "LV2Info.hpp" +#endif +#include "types.hpp" + +using std::string; + +namespace Ingen { + +class NodeImpl; +class PatchImpl; +class PluginImpl; + + +/** 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 LADSPANode) are responsible + * for actually creating a Node instance of the plugin. + * + * \ingroup engine + */ +class NodeFactory +{ +public: + NodeFactory(Ingen::Shared::World* world); + ~NodeFactory(); + + void load_plugins(); + + typedef std::map<std::string,PluginImpl*> Plugins; + const Plugins& plugins() const { return _plugins; } + + PluginImpl* plugin(const string& uri); + + /** DEPRECATED */ + PluginImpl* plugin(const string& type, const string& lib, const string& label); + +private: +#ifdef HAVE_LADSPA + void load_ladspa_plugins(); +#endif + +#ifdef HAVE_SLV2 + void load_lv2_plugins(); +#endif + + void load_internal_plugins(); + + Plugins _plugins; + Ingen::Shared::World* _world; + bool _has_loaded; +#ifdef HAVE_SLV2 + SharedPtr<LV2Info> _lv2_info; +#endif +}; + + +} // namespace Ingen + +#endif // NODEFACTORY_H diff --git a/src/engine/NodeImpl.hpp b/src/engine/NodeImpl.hpp new file mode 100644 index 00000000..ac125c13 --- /dev/null +++ b/src/engine/NodeImpl.hpp @@ -0,0 +1,170 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef NODEIMPL_H +#define NODEIMPL_H + +#include <string> +#include <raul/Array.hpp> +#include "interface/Node.hpp" +#include "types.hpp" +#include "GraphObjectImpl.hpp" + +namespace Raul { template <typename T> class List; class Maid; } + +namespace Ingen { + +namespace Shared { class Plugin; class Node; class Port; } + +using namespace Shared; + +class Buffer; +class PluginImpl; +class PatchImpl; +class PortImpl; +class MessageContext; + + +/** A Node (or "module") in a Patch (which is also a Node). + * + * A Node is a unit with input/output ports, a process() 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. + * + * \ingroup engine + */ +class NodeImpl : public GraphObjectImpl, virtual public Ingen::Shared::Node +{ +public: + NodeImpl(GraphObjectImpl* parent, const std::string& name, bool poly) + : GraphObjectImpl(parent, name, poly) + {} + + /** 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; + + /** Prepare for a new (external) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_poly. + * \return true on success. + */ + virtual bool prepare_poly(uint32_t poly) = 0; + + /** Apply a new (external) polyphony value. + * + * Audio thread. + * + * \param poly Must be < the most recent value passed to prepare_poly. + * \param maid Any objects no longer needed will be pushed to this + */ + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly) = 0; + + /** Parallelism: Reset flags for start of a new cycle. + */ + virtual void reset_input_ready() = 0; + + /** Parallelism: Claim this node (to wait on its input). + * Only one thread will ever take this lock on a particular Node. + * \return true if lock was aquired, false otherwise + */ + virtual bool process_lock() = 0; + + /** Parallelism: Unclaim this node (let someone else wait on its input). + * Only a thread which successfully called process_lock may call this. + */ + virtual void process_unlock() = 0; + + /** Parallelism: Wait for signal that input is ready. + * Only a thread which successfully called process_lock may call this. + */ + virtual void wait_for_input(size_t num_providers) = 0; + + /** Parallelism: Signal that input is ready. Realtime safe. + * Calling this will wake up the thread which blocked on wait_for_input + * if there is one, and otherwise cause it to return true the next call. + * \return true if lock was aquired and input is ready, false otherwise + */ + virtual void signal_input_ready() = 0; + + /** Parallelism: Return the number of providers that have signalled. + */ + virtual unsigned n_inputs_ready() const = 0; + + /** Run the node for one instant in the message thread. + */ + virtual void message_process(MessageContext& context, uint32_t* output) = 0; + + /** Run the node for @a nframes input/output. + * + * @a start and @a end are transport times: end is not redundant in the case + * of varispeed, where end-start != nframes. + */ + virtual void process(ProcessContext& context) = 0; + + virtual void set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf) = 0; + + virtual uint32_t num_ports() const = 0; + + virtual Port* port(uint32_t index) const = 0; + virtual PortImpl* port_impl(uint32_t index) 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 Raul::List<NodeImpl*>* providers() = 0; + virtual void providers(Raul::List<NodeImpl*>* l) = 0; + + /** Nodes are are connected to this Node's outputs. + * (They depend on this Node) + */ + virtual Raul::List<NodeImpl*>* dependants() = 0; + virtual void dependants(Raul::List<NodeImpl*>* l) = 0; + + /** The Patch this Node belongs to. */ + virtual PatchImpl* parent_patch() const = 0; + + /** Information about the Plugin this Node is an instance of. + * Not the best name - not all nodes come from plugins (ie Patch) + */ + virtual PluginImpl* plugin_impl() const = 0; + + /** Information about the Plugin this Node is an instance of. + * Not the best name - not all nodes come from plugins (ie Patch) + */ + virtual const Shared::Plugin* plugin() const = 0; + + virtual void set_buffer_size(size_t size) = 0; +}; + + +} // namespace Ingen + +#endif // NODEIMPL_H diff --git a/src/engine/OSCClientSender.cpp b/src/engine/OSCClientSender.cpp new file mode 100644 index 00000000..dca2e0ed --- /dev/null +++ b/src/engine/OSCClientSender.cpp @@ -0,0 +1,358 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "OSCClientSender.hpp" +#include <cassert> +#include <iostream> +#include <unistd.h> +#include <raul/AtomLiblo.hpp> +#include "EngineStore.hpp" +#include "NodeFactory.hpp" +#include "util.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "AudioDriver.hpp" +#include "interface/ClientInterface.hpp" + +using namespace std; + +namespace Ingen { + + +/*! \page client_osc_namespace Client OSC Namespace Documentation + * + * <p>NOTE: this comment doesn't really apply any longer.. sort of. + * but maybe it still should.. maybe. so it remains...</p> + * + * <p>These are all the messages sent from the engine to the client. + * 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 + */ + + +/** \page client_osc_namespace + * \n + * <h2>Control Band</h2> + */ + +/** \page client_osc_namespace + * <p> \b /ingen/ok - Respond to a successful user command + * \arg \b response-id (int) - Request ID this is a response to + * </p> \n \n + */ +void +OSCClientSender::response_ok(int32_t id) +{ + if (!_enabled) + return; + + if (lo_send(_address, "/ingen/ok", "i", id) < 0) { + cerr << "Unable to send ok " << id << "! (" + << lo_address_errstr(_address) << ")" << endl; + } +} + + +/** \page client_osc_namespace + * <p> \b /ingen/response - Respond to a user command + * \arg \b response-id (int) - Request ID this is a response to + * \arg \b message (string) - Error message (natural language text) + * </p> \n \n + */ +void +OSCClientSender::response_error(int32_t id, const std::string& msg) +{ + if (!_enabled) + return; + + if (lo_send(_address, "/ingen/error", "is", id, msg.c_str()) < 0) { + cerr << "Unable to send error " << id << "! (" + << lo_address_errstr(_address) << ")" << endl; + } +} + + +/** \page client_osc_namespace + * \n + * <h2>Notification Band</h2> + */ + + +/** \page client_osc_namespace + * <p> \b /ingen/error - Notification that an error has occurred + * \arg \b message (string) - Error message (natural language text) \n\n + * \li This is for notification of errors that aren't a direct response to a + * user command, ie "unexpected" errors.</p> \n \n + */ +void +OSCClientSender::error(const std::string& msg) +{ + send("/ingen/error", "s", msg.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 (boolean) - Node is polyphonic\n\n + * \li New nodes are sent as a bundle. The first message in the bundle will be + * this one (/ingen/new_node), followed by a series of /ingen/new_port commands, + * followed by /ingen/new_node_end. </p> \n \n + */ +void OSCClientSender::new_node(const std::string& node_path, + const std::string& plugin_uri) +{ + send("/ingen/new_node", "ss", node_path.c_str(), plugin_uri.c_str(), LO_ARGS_END); +} + + + +/** \page client_osc_namespace + * <p> \b /ingen/new_port - Notification of a new port's creation. + * \arg \b path (string) - Path of new port + * \arg \b index (integer) - Index (or sort key) of port on parent + * \arg \b data-type (string) - Type of port (ingen:AudioPort, ingen:ControlPort, ingen:MIDIPort, or ingen:OSCPort) + * \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 variable.</p> \n \n + */ +void +OSCClientSender::new_port(const std::string& path, + uint32_t index, + const std::string& data_type, + bool is_output) +{ + send("/ingen/new_port", "sisi", path.c_str(), index, data_type.c_str(), is_output, LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/destroyed - Notification an object has been destroyed + * \arg \b path (string) - Path of object (which no longer exists) </p> \n \n + */ +void +OSCClientSender::destroy(const std::string& path) +{ + assert(path != "/"); + + send("/ingen/destroyed", "s", path.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 +OSCClientSender::patch_cleared(const std::string& patch_path) +{ + send("/ingen/patch_cleared", "s", patch_path.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 +OSCClientSender::connect(const std::string& src_port_path, const std::string& dst_port_path) +{ + send("/ingen/new_connection", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 +OSCClientSender::disconnect(const std::string& src_port_path, const std::string& dst_port_path) +{ + send("/ingen/disconnection", "ss", src_port_path.c_str(), dst_port_path.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/set_variable - Notification of a variable. + * \arg \b path (string) - Path of the object associated with variable (node, patch, or port) + * \arg \b key (string) + * \arg \b value (string)</p> \n \n + */ +void +OSCClientSender::set_variable(const std::string& path, const std::string& key, const Atom& value) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, path.c_str()); + lo_message_add_string(m, key.c_str()); + Raul::AtomLiblo::lo_message_add_atom(m, value); + send_message("/ingen/set_variable", m); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/set_property - Notification of a property. + * \arg \b path (string) - Path of the object associated with property (node, patch, or port) + * \arg \b key (string) + * \arg \b value (string)</p> \n \n + */ +void +OSCClientSender::set_property(const std::string& path, const std::string& key, const Atom& value) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, path.c_str()); + lo_message_add_string(m, key.c_str()); + Raul::AtomLiblo::lo_message_add_atom(m, value); + send_message("/ingen/set_property", m); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/set_port_value - Notification the value of a port has changed + * \arg \b path (string) - Path of port + * \arg \b value (any) - New value of port </p> \n \n + */ +void +OSCClientSender::set_port_value(const std::string& port_path, const Raul::Atom& value) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, port_path.c_str()); + Raul::AtomLiblo::lo_message_add_atom(m, value); + send_message("/ingen/set_port_value", m); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/set_port_value - Notification the value of a port has changed + * \arg \b path (string) - Path of port + * \arg \b voice (int) - Voice which is set to this value + * \arg \b value (any) - New value of port </p> \n \n + */ +void +OSCClientSender::set_voice_value(const std::string& port_path, uint32_t voice, const Raul::Atom& value) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, port_path.c_str()); + Raul::AtomLiblo::lo_message_add_atom(m, value); + send_message("/ingen/set_port_value", m); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/port_activity - Notification of activity for a port (e.g. MIDI messages) + * \arg \b path (string) - Path of port </p> \n \n + */ +void +OSCClientSender::port_activity(const std::string& port_path) +{ + if (!_enabled) + return; + + lo_send(_address, "/ingen/port_activity", "s", port_path.c_str(), LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/plugin - Notification of the existance of a plugin + * \arg \b uri (string) - URI of plugin (e.g. http://example.org/filtermatic) + * \arg \b type (string) - Type of plugin (e.g. "ingen:LV2Plugin") + * \arg \b symbol (string) - Valid symbol for plugin (default symbol for nodes) (e.g. "adsr") + * \arg \b name (string) - Descriptive human-readable name of plugin (e.g. "ADSR Envelope") + */ +void +OSCClientSender::new_plugin(const std::string& uri, + const std::string& type_uri, + const std::string& symbol, + const std::string& name) +{ + lo_message m = lo_message_new(); + lo_message_add_string(m, uri.c_str()); + lo_message_add_string(m, type_uri.c_str()); + lo_message_add_string(m, symbol.c_str()); + lo_message_add_string(m, name.c_str()); + send_message("/ingen/plugin", m); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 +OSCClientSender::new_patch(const std::string& path, uint32_t poly) +{ + send("/ingen/new_patch", "si", path.c_str(), poly, LO_ARGS_END); +} + + +/** \page client_osc_namespace + * <p> \b /ingen/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 +OSCClientSender::object_renamed(const std::string& old_path, const std::string& new_path) +{ + send("/ingen/object_renamed", "ss", old_path.c_str(), new_path.c_str(), LO_ARGS_END); +} + + +/** Sends information about a program associated with a node. + */ +void +OSCClientSender::program_add(const std::string& node_path, uint32_t bank, uint32_t program, const std::string& name) +{ + send("/ingen/program_add", "siis", + node_path.c_str(), bank, program, name.c_str(), LO_ARGS_END); +} + + +void +OSCClientSender::program_remove(const std::string& node_path, uint32_t bank, uint32_t program) +{ + send("/ingen/program_remove", "sii", + node_path.c_str(), bank, program, LO_ARGS_END); +} + + +} // namespace Ingen diff --git a/src/engine/OSCClientSender.hpp b/src/engine/OSCClientSender.hpp new file mode 100644 index 00000000..c8c8418f --- /dev/null +++ b/src/engine/OSCClientSender.hpp @@ -0,0 +1,136 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OSCCLIENTSENDER_H +#define OSCCLIENTSENDER_H + +#include <cassert> +#include <string> +#include <iostream> +#include <lo/lo.h> +#include <pthread.h> +#include "types.hpp" +#include "interface/ClientInterface.hpp" +#include "shared/OSCSender.hpp" + +namespace Ingen { + +namespace Shared { class EngineInterface; } + + +/** Implements ClientInterface for OSC clients (sends OSC messages). + * + * \ingroup engine + */ +class OSCClientSender : public Shared::ClientInterface, public Shared::OSCSender +{ +public: + OSCClientSender(const std::string& url) + : _url(url) + { + _address = lo_address_new_from_url(url.c_str()); + } + + virtual ~OSCClientSender() + { lo_address_free(_address); } + + bool enabled() const { return _enabled; } + + void enable() { _enabled = true; } + void disable() { _enabled = false; } + + void bundle_begin() { OSCSender::bundle_begin(); } + void bundle_end() { OSCSender::bundle_end(); } + void transfer_begin() { OSCSender::transfer_begin(); } + void transfer_end() { OSCSender::transfer_end(); } + + std::string uri() const { return lo_address_get_url(_address); } + + void subscribe(Shared::EngineInterface* engine) { } + + /* *** ClientInterface Implementation Below *** */ + + //void client_registration(const std::string& url, int client_id); + + void response_ok(int32_t id); + void response_error(int32_t id, const std::string& msg); + + void error(const std::string& msg); + + virtual void new_plugin(const std::string& uri, + const std::string& type_uri, + const std::string& symbol, + const std::string& name); + + virtual void new_patch(const std::string& path, uint32_t poly); + + virtual void new_node(const std::string& path, + const std::string& plugin_uri); + + virtual void new_port(const std::string& path, + uint32_t index, + const std::string& data_type, + bool is_output); + + virtual void patch_cleared(const std::string& path); + + virtual void destroy(const std::string& path); + + virtual void object_renamed(const std::string& old_path, + const std::string& new_path); + + virtual void connect(const std::string& src_port_path, + const std::string& dst_port_path); + + virtual void disconnect(const std::string& src_port_path, + const std::string& dst_port_path); + + virtual void set_variable(const std::string& subject_path, + const std::string& predicate, + const Raul::Atom& value); + + virtual void set_property(const std::string& subject_path, + const std::string& predicate, + const Raul::Atom& value); + + virtual void set_port_value(const std::string& port_path, + const Raul::Atom& value); + + virtual void set_voice_value(const std::string& port_path, + uint32_t voice, + const Raul::Atom& value); + + virtual void port_activity(const std::string& port_path); + + virtual void program_add(const std::string& node_path, + uint32_t bank, + uint32_t program, + const std::string& program_name); + + virtual void program_remove(const std::string& node_path, + uint32_t bank, + uint32_t program); + +private: + std::string _url; +}; + + +} // namespace Ingen + +#endif // OSCCLIENTSENDER_H + diff --git a/src/engine/OSCDriver.hpp b/src/engine/OSCDriver.hpp new file mode 100644 index 00000000..06125217 --- /dev/null +++ b/src/engine/OSCDriver.hpp @@ -0,0 +1,82 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OSCDRIVER_H +#define OSCDRIVER_H + +#include "types.hpp" +#include "Driver.hpp" +#include <iostream> + +namespace Ingen { + + +/** OSC driver abstract base class. + * + * \ingroup engine + */ +class OSCDriver : public Driver +{ +public: + OSCDriver() : Driver(DataType::EVENT) {} + + /** Prepare events (however neccessary) for the specified block (realtime safe) */ + virtual void prepare_block(const SampleCount block_start, const SampleCount block_end) = 0; +}; + + + +/** Dummy OSCDriver. + * + * Not abstract, all functions are dummies. One of these will be allocated and + * "used" if no working OSC driver is loaded. (Doing it this way as opposed to + * just making OSCDriver have dummy functions makes sure any existing OSCDriver + * derived class actually implements the required functions). + * + * \ingroup engine + */ +class DummyOSCDriver : public OSCDriver +{ +public: + DummyOSCDriver() { + std::cout << "[DummyOSCDriver] Started Dummy OSC driver." << std::endl; + } + + ~DummyOSCDriver() {} + + void activate() {} + void deactivate() {} + + bool is_activated() const { return false; } + bool is_enabled() const { return false; } + + void enable() {} + void disable() {} + + DriverPort* create_port(DuplexPort* patch_port) { return NULL; } + + void add_port(DriverPort* port) {} + DriverPort* remove_port(const Raul::Path& path) { return NULL; } + + void prepare_block(const SampleCount block_start, const SampleCount block_end) {} +}; + + + +} // namespace Ingen + +#endif // OSCDRIVER_H diff --git a/src/engine/OSCEngineReceiver.cpp b/src/engine/OSCEngineReceiver.cpp new file mode 100644 index 00000000..e4e2fec1 --- /dev/null +++ b/src/engine/OSCEngineReceiver.cpp @@ -0,0 +1,884 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <cstdlib> +#include <string> +//#define ENABLE_AVAHI 1 +#include <lo/lo.h> +#include "types.hpp" +#include <raul/SharedPtr.hpp> +#include <raul/AtomLiblo.hpp> +#include "interface/ClientInterface.hpp" +#include "engine/ThreadManager.hpp" +#include "OSCEngineReceiver.hpp" +#include "QueuedEventSource.hpp" +#include "OSCClientSender.hpp" +#include "ClientBroadcaster.hpp" + +using namespace std; + +namespace Ingen { + + +/*! \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.</p>\n\n + */ + + +OSCEngineReceiver::OSCEngineReceiver(Engine& engine, size_t queue_size, uint16_t port) + : QueuedEngineInterface(engine, queue_size, queue_size) // FIXME + , _server(NULL) +{ + _receive_thread = new ReceiveThread(*this); + + char port_str[6]; + snprintf(port_str, 6, "%u", port); + + _server = lo_server_new(port_str, error_cb); +#ifdef ENABLE_AVAHI + lo_server_avahi_init(_server, "ingen"); +#endif + + if (_server == NULL) { + cerr << "[OSC] Could not start OSC server. Aborting." << endl; + exit(EXIT_FAILURE); + } else { + char* lo_url = lo_server_get_url(_server); + cout << "[OSC] Started OSC server at " << lo_url << endl; + free(lo_url); + } + + // For debugging, print all incoming OSC messages + //lo_server_add_method(_server, NULL, NULL, generic_cb, NULL); + + // Set response address for this message. + // It's important this is first and returns nonzero. + lo_server_add_method(_server, NULL, NULL, set_response_address_cb, this); + + // Commands + lo_server_add_method(_server, "/ingen/ping", "i", ping_cb, this); + lo_server_add_method(_server, "/ingen/ping_queued", "i", ping_slow_cb, this); + lo_server_add_method(_server, "/ingen/quit", "i", quit_cb, this); + //lo_server_add_method(_server, "/ingen/register_client", "is", register_client_cb, this); + lo_server_add_method(_server, "/ingen/register_client", "i", register_client_cb, this); + lo_server_add_method(_server, "/ingen/unregister_client", "i", unregister_client_cb, this); + lo_server_add_method(_server, "/ingen/load_plugins", "i", load_plugins_cb, this); + lo_server_add_method(_server, "/ingen/activate", "i", engine_activate_cb, this); + lo_server_add_method(_server, "/ingen/deactivate", "i", engine_deactivate_cb, this); + lo_server_add_method(_server, "/ingen/new_patch", "isi", new_patch_cb, this); + lo_server_add_method(_server, "/ingen/clear_patch", "is", clear_patch_cb, this); + lo_server_add_method(_server, "/ingen/set_polyphony", "isi", set_polyphony_cb, this); + lo_server_add_method(_server, "/ingen/set_polyphonic", "isT", set_polyphonic_cb, this); + lo_server_add_method(_server, "/ingen/set_polyphonic", "isF", set_polyphonic_cb, this); + lo_server_add_method(_server, "/ingen/new_port", "issi", new_port_cb, this); + lo_server_add_method(_server, "/ingen/new_node", "issss", new_node_cb, this); + lo_server_add_method(_server, "/ingen/new_node", "issss", new_node_cb, this); + lo_server_add_method(_server, "/ingen/new_node", "iss", new_node_by_uri_cb, this); + lo_server_add_method(_server, "/ingen/new_node", "iss", new_node_by_uri_cb, this); + lo_server_add_method(_server, "/ingen/destroy", "is", destroy_cb, this); + lo_server_add_method(_server, "/ingen/rename", "iss", rename_cb, this); + lo_server_add_method(_server, "/ingen/connect", "iss", connect_cb, this); + lo_server_add_method(_server, "/ingen/disconnect", "iss", disconnect_cb, this); + lo_server_add_method(_server, "/ingen/disconnect_all", "iss", disconnect_all_cb, this); + lo_server_add_method(_server, "/ingen/set_port_value", NULL, set_port_value_cb, this); + lo_server_add_method(_server, "/ingen/note_on", "isii", note_on_cb, this); + lo_server_add_method(_server, "/ingen/note_off", "isi", note_off_cb, this); + lo_server_add_method(_server, "/ingen/all_notes_off", "isi", all_notes_off_cb, this); + lo_server_add_method(_server, "/ingen/midi_learn", "is", midi_learn_cb, this); + lo_server_add_method(_server, "/ingen/set_variable", NULL, variable_set_cb, this); + lo_server_add_method(_server, "/ingen/set_property", NULL, property_set_cb, this); + + // Queries + lo_server_add_method(_server, "/ingen/request_variable", "iss", variable_get_cb, this); + lo_server_add_method(_server, "/ingen/request_plugin", "is", request_plugin_cb, this); + lo_server_add_method(_server, "/ingen/request_object", "is", request_object_cb, this); + lo_server_add_method(_server, "/ingen/request_port_value", "is", request_port_value_cb, this); + lo_server_add_method(_server, "/ingen/request_plugins", "i", request_plugins_cb, this); + lo_server_add_method(_server, "/ingen/request_all_objects", "i", request_all_objects_cb, this); + + lo_server_add_method(_server, NULL, NULL, unknown_cb, NULL); + + Thread::set_name("OSC Pre-Processor"); +} + + +OSCEngineReceiver::~OSCEngineReceiver() +{ + deactivate(); + stop(); + _receive_thread->stop(); + delete _receive_thread; + + if (_server != NULL) { +#ifdef ENABLE_AVAHI + lo_server_avahi_free(_server); +#endif + lo_server_free(_server); + _server = NULL; + } +} + + +void +OSCEngineReceiver::activate() +{ + QueuedEventSource::activate(); + _receive_thread->set_name("OSC Receiver"); + _receive_thread->start(); + _receive_thread->set_scheduling(SCHED_FIFO, 5); // Jack default appears to be 10 +} + + +void +OSCEngineReceiver::deactivate() +{ + cout << "[OSCEngineReceiver] Stopped OSC listening thread" << endl; + _receive_thread->stop(); + QueuedEventSource::deactivate(); +} + + +/** Override the semaphore driven _run method of QueuedEngineInterface + * to wait on OSC messages and prepare them right away in the same thread. + */ +void +OSCEngineReceiver::ReceiveThread::_run() +{ + Thread::get().set_context(THREAD_PRE_PROCESS); + + /* get a timestamp here and stamp all the events with the same time so + * they all get executed in the same cycle */ + + while (true) { + assert(_receiver._server); + /*if ( ! _server) { + cout << "[OSCEngineReceiver] Server is NULL, exiting" << endl; + break; + }*/ + + // Wait on a message and enqueue it + lo_server_recv(_receiver._server); + + // Enqueue every other message that is here "now" + // (would this provide truly atomic bundles?) + while (lo_server_recv_noblock(_receiver._server, 0) > 0) + if (_receiver.unprepared_events()) + _receiver.whip(); + + // No more unprepared events + } +} + + +/** 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. + * Hooray for reference counting. + * + * 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. + * Lack of a fast liblo address comparison really sucks here, in any case. + */ +int +OSCEngineReceiver::set_response_address_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + OSCEngineReceiver* const me = reinterpret_cast<OSCEngineReceiver*>(user_data); + + if (argc < 1 || types[0] != 'i') // Not a valid Ingen message + return 0; // Save liblo the trouble + + const int32_t id = argv[0]->i; + + const lo_address addr = lo_message_get_source(msg); + char* const url = lo_address_get_url(addr); + + const SharedPtr<Responder> r = me->_responder; + + /* Different address than last time, have to do a lookup */ + if (!r || !r->client() || strcmp(url, r->client()->uri().c_str())) { + ClientInterface* client = me->_engine.broadcaster()->client(url); + if (client) + me->_responder = SharedPtr<Responder>(new Responder(client, id)); + else + me->_responder = SharedPtr<Responder>(new Responder()); + } + + if (id != -1) { + me->set_next_response_id(id); + } else { + me->disable_responses(); + } + + // If this returns 0 no OSC commands will work + return 1; +} + + +void +OSCEngineReceiver::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 /ingen/ping - Immediately sends a successful response to the given response id. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCEngineReceiver::_ping_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const lo_address addr = lo_message_get_source(msg); + if (lo_send(addr, "/ingen/ok", "i", argv[0]->i) < 0) + cerr << "WARNING: Unable to send response: " << lo_address_errstr(addr) << endl; + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/ping_queued - Sends response after going through the event queue. + * \arg \b response-id (integer) + * + * \li See the documentation for /ingen/set_port_value_queued for an explanation of how + * this differs from /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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 /ingen/register_client - Registers a new client with the engine + * \arg \b response-id (integer) \n\n + * \li The incoming address will be used for the new registered client. If you + * want to register a different specific address, use the URL version. </p> \n \n + */ +int +OSCEngineReceiver::_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); + ClientInterface* client = new OSCClientSender((const char*)url); + register_client(client); + free(url); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/unregister_client - Unregisters a client + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCEngineReceiver::_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(url); + free(url); + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/load_plugins - Locates all available plugins, making them available for use. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_engine_activate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + QueuedEngineInterface::activate(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/deactivate - Deactivate the engine completely. + * \arg \b response-id (integer) </p> \n \n + */ +int +OSCEngineReceiver::_engine_deactivate_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + QueuedEngineInterface::deactivate(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/new_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 +OSCEngineReceiver::_new_patch_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + const int32_t poly = argv[2]->i; + + new_patch(patch_path, poly); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/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 +OSCEngineReceiver::_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 /ingen/clear_patch - Remove all nodes from a patch + * \arg \b response-id (integer) + * \arg \b patch-path - Patch's path </p> \n \n + */ +int +OSCEngineReceiver::_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 /ingen/set_polyphony - Set the polyphony of a patch + * \arg \b response-id (integer) + * \arg \b patch-path - Patch's path + * \arg \b poly (integer) </p> \n \n + */ +int +OSCEngineReceiver::_set_polyphony_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + const uint32_t poly = argv[2]->i; + + set_polyphony(patch_path, poly); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/set_polyphonic - Toggle a node's or port's polyphonic mode + * \arg \b response-id (integer) + * \arg \b path - Object's path + * \arg \b polyphonic (bool) </p> \n \n + */ +int +OSCEngineReceiver::_set_polyphonic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* object_path = &argv[1]->s; + bool polyphonic = (types[2] == 'T'); + + set_polyphonic(object_path, polyphonic); + return 0; +} + + +// FIXME: add index +/** \page engine_osc_namespace + * <p> \b /ingen/new_port - Add a port into a given patch (load a plugin by URI) + * \arg \b response-id (integer) + * \arg \b path (string) - Full path of the new port (ie. /patch2/subpatch/newport) + * \arg \b data-type (string) - Type of port (ingen:AudioPort, ingen:ControlPort, ingen:MIDIPort, or ingen:OSCPort) + * \arg \b direction ("is-output") (integer) - Direction of data flow (Input = 0, Output = 1) </p> \n \n + */ +int +OSCEngineReceiver::_new_port_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* port_path = &argv[1]->s; + const char* data_type = &argv[2]->s; + const int32_t direction = argv[3]->i; + + new_port(port_path, 0, data_type, (direction == 1)); + return 0; +} + +/** \page engine_osc_namespace + * <p> \b /ingen/new_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 plug-uri (string) - URI of the plugin to load \n \n + */ +int +OSCEngineReceiver::_new_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* plug_uri = &argv[2]->s; + + new_node(node_path, plug_uri); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/new_node - Add a node into a given patch (load a plugin by libname, label) \b DEPRECATED + * \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") \n \n + * + * \li This is only here to provide backwards compatibility for old patches that store LADSPA plugin + * references as libname, label. It is to be removed ASAP, don't use it. + * </p> \n \n + */ +int +OSCEngineReceiver::_new_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; + + new_node_deprecated(node_path, type, lib_name, plug_label); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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 /ingen/disconnect_all - Disconnect all connections to/from a node/port. + * \arg \b response-id (integer) + * \arg \b patch-path (string) - The (parent) patch in which to disconnect object. </p> \n \n + * \arg \b node-path (string) - Full path of object. </p> \n \n + */ +int +OSCEngineReceiver::_disconnect_all_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* patch_path = &argv[1]->s; + const char* object_path = &argv[2]->s; + + disconnect_all(patch_path, object_path); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/set_port_value - 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.</p> \n \n + */ +int +OSCEngineReceiver::_set_port_value_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + if (argc < 3 || argc > 5 || strncmp(types, "is", 2)) + return 1; + + const char* port_path = &argv[1]->s; + + using Raul::Atom; + + if (!strcmp(types, "isf")) { // float, all voices + const float value = argv[2]->f; + set_port_value(port_path, Atom(value)); + } else if (!strcmp(types, "isif")) { // float, specific voice + const float value = argv[3]->f; + set_voice_value(port_path, argv[2]->i, Atom(value)); + } else if (!strcmp(types, "issb")) { // blob (event), all voices + const char* type = &argv[2]->s; + lo_blob b = argv[3]; + size_t data_size = lo_blob_datasize(b); + void* data = lo_blob_dataptr(b); + set_port_value(port_path, Atom(type, data_size, data)); + } else if (!strcmp(types, "isisb")) { // blob (event), specific voice + const char* type = &argv[3]->s; + lo_blob b = argv[4]; + size_t data_size = lo_blob_datasize(b); + void* data = lo_blob_dataptr(b); + set_voice_value(port_path, argv[2]->i, Atom(type, data_size, data)); + } else if (!strcmp(types, "issN")) { // empty event (type only), all voices + const char* type = &argv[2]->s; + set_port_value(port_path, Atom(type, 0, NULL)); + } else if (!strcmp(types, "isisN")) { // empty event (type only), specific voice + const char* type = &argv[3]->s; + set_voice_value(port_path, argv[2]->i, Atom(type, 0, NULL)); + } else { + return 1; + } + + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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 /ingen/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 +OSCEngineReceiver::_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; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/set_variable - Set a variable, associated with a synth-space object (node, etc) + * \arg \b response-id (integer) + * \arg \b object-path (string) - Full path of object to associate variable with + * \arg \b key (string) - Key (index/predicate/ID) for new variable + * \arg \b value (string) - Value of new variable </p> \n \n + */ +int +OSCEngineReceiver::_variable_set_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + if (argc != 4 || types[0] != 'i' || types[1] != 's' || types[2] != 's') + return 1; + + const char* object_path = &argv[1]->s; + const char* key = &argv[2]->s; + + Raul::Atom value = Raul::AtomLiblo::lo_arg_to_atom(types[3], argv[3]); + + set_variable(object_path, key, value); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/set_property - Set an (RDF) property, associated with a synth-space object (node, etc) + * \arg \b response-id (integer) + * \arg \b object-path (string) - Full path of object to associate variable with + * \arg \b key (string) - URI/QName for predicate of this property (e.g. "ingen:enabled") + * \arg \b value (string) - Value of property </p> \n \n + */ +int +OSCEngineReceiver::_property_set_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + if (argc != 4 || types[0] != 'i' || types[1] != 's' || types[2] != 's') + return 1; + + const char* object_path = &argv[1]->s; + const char* key = &argv[2]->s; + + Raul::Atom value = Raul::AtomLiblo::lo_arg_to_atom(types[3], argv[3]); + + set_property(object_path, key, value); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_variable - Requests the engine send a piece of variable, associated with a synth-space object (node, etc) + * \arg \b response-id (integer) + * \arg \b object-path (string) - Full path of object variable is associated with + * \arg \b key (string) - Key (index) for piece of variable + * + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCEngineReceiver::_variable_get_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* object_path = &argv[1]->s; + const char* key = &argv[2]->s; + + request_variable(object_path, key); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_plugin - 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 \n\n + * \li Reply will be sent to client registered with the source address of this message.</p>\n\n + */ +int +OSCEngineReceiver::_request_plugin_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* uri = &argv[1]->s; + + request_plugin(uri); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_object - 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 \n\n + * \li Reply will be sent to client registered with the source address of this message.</p>\n\n + */ +int +OSCEngineReceiver::_request_object_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + const char* object_path = &argv[1]->s; + + request_object(object_path); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_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 \n\n + * \li Reply will be sent to client registered with the source address of this message.</p>\n\n + */ +int +OSCEngineReceiver::_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; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_plugins - Requests the engine send a list of all known plugins. + * \arg \b response-id (integer) \n\n + * \li Reply will be sent to client registered with the source address of this message.</p>\n\n + */ +int +OSCEngineReceiver::_request_plugins_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + request_plugins(); + return 0; +} + + +/** \page engine_osc_namespace + * <p> \b /ingen/request_all_objects - Requests the engine send information about \em all objects (patches, nodes, etc) + * \arg \b response-id (integer)\n\n + * \li Reply will be sent to client registered with the source address of this message.</p> \n \n + */ +int +OSCEngineReceiver::_request_all_objects_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg) +{ + request_all_objects(); + return 0; +} + + +// Static Callbacks // + + +// Display incoming OSC messages (for debugging purposes) +int +OSCEngineReceiver::generic_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + printf("[OSCMsg] %s (%s)\t", path, types); + + for (int i=0; i < argc; ++i) { + lo_arg_pp(lo_type(types[i]), argv[i]); + printf("\t"); + } + printf("\n"); + + return 1; // not handled +} + + +int +OSCEngineReceiver::unknown_cb(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) +{ + const lo_address addr = lo_message_get_source(msg); + char* const url = lo_address_get_url(addr); + + cerr << "Unknown command " << path << " (" << types << "), sending error.\n"; + + string error_msg = "Unknown command: "; + error_msg.append(path).append(" ").append(types); + + OSCClientSender(url).error(error_msg); + + return 0; +} + + +} // namespace Ingen diff --git a/src/engine/OSCEngineReceiver.hpp b/src/engine/OSCEngineReceiver.hpp new file mode 100644 index 00000000..c6e0bf59 --- /dev/null +++ b/src/engine/OSCEngineReceiver.hpp @@ -0,0 +1,129 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OSCENGINERECEIVER_H +#define OSCENGINERECEIVER_H + +#include CONFIG_H_PATH +#include <string> +#include <stdint.h> +#include <lo/lo.h> +#include <raul/SharedPtr.hpp> +#include "QueuedEngineInterface.hpp" +#include "Responder.hpp" +using std::string; + +namespace Ingen { + +class JackDriver; +class NodeFactory; +class PatchImpl; + + +/* 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 _##name##_cb (LO_HANDLER_ARGS);\ +inline static int name##_cb(LO_HANDLER_ARGS, void* myself)\ +{ return ((OSCEngineReceiver*)myself)->_##name##_cb(path, types, argv, argc, msg); } + + +/* FIXME: Make this receive and preprocess in the same thread? */ + + +/** 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 should be implemented-as-a (privately inherit) + * QueuedEngineInterface, but it needs to be public so it's an EventSource + * the Driver can use. This probably should be fixed somehow.. + * + * \ingroup engine + */ +class OSCEngineReceiver : public QueuedEngineInterface +{ +public: + OSCEngineReceiver(Engine& engine, size_t queue_size, uint16_t port); + ~OSCEngineReceiver(); + + void activate(); + void deactivate(); + +private: + struct ReceiveThread : public Raul::Thread { + ReceiveThread(OSCEngineReceiver& receiver) : _receiver(receiver) {} + virtual void _run(); + private: + OSCEngineReceiver& _receiver; + }; + + friend class ReceiveThread; + + ReceiveThread* _receive_thread; + + static void error_cb(int num, const char* msg, const char* path); + static int set_response_address_cb(LO_HANDLER_ARGS, void* myself); + static int generic_cb(LO_HANDLER_ARGS, void* myself); + static int unknown_cb(LO_HANDLER_ARGS, void* myself); + + 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(new_patch); + LO_HANDLER(rename); + LO_HANDLER(new_port); + LO_HANDLER(new_node); + LO_HANDLER(new_node_by_uri); + LO_HANDLER(clear_patch); + LO_HANDLER(set_polyphony); + LO_HANDLER(set_polyphonic); + LO_HANDLER(destroy); + LO_HANDLER(connect); + LO_HANDLER(disconnect); + LO_HANDLER(disconnect_all); + LO_HANDLER(set_port_value); + LO_HANDLER(note_on); + LO_HANDLER(note_off); + LO_HANDLER(all_notes_off); + LO_HANDLER(midi_learn); + LO_HANDLER(variable_get); + LO_HANDLER(variable_set); + LO_HANDLER(property_set); + LO_HANDLER(request_plugin); + LO_HANDLER(request_object); + LO_HANDLER(request_port_value); + LO_HANDLER(request_variable); + LO_HANDLER(request_plugins); + LO_HANDLER(request_all_objects); + + lo_server _server; +}; + + +} // namespace Ingen + +#endif // OSCENGINERECEIVER_H diff --git a/src/engine/ObjectSender.cpp b/src/engine/ObjectSender.cpp new file mode 100644 index 00000000..688cea8e --- /dev/null +++ b/src/engine/ObjectSender.cpp @@ -0,0 +1,150 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ObjectSender.hpp" +#include "interface/ClientInterface.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "NodeFactory.hpp" +#include "interface/DataType.hpp" +#include "AudioBuffer.hpp" + +namespace Ingen { + + +void +ObjectSender::send_patch(ClientInterface* client, const PatchImpl* patch, bool recursive) +{ + client->bundle_begin(); + + client->new_patch(patch->path(), patch->internal_polyphony()); + client->set_property(patch->path(), "ingen:polyphonic", patch->polyphonic()); + + // Send variable + const GraphObjectImpl::Variables& data = patch->variables(); + for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j) + client->set_variable(patch->path(), (*j).first, (*j).second); + + client->set_property(patch->path(), "ingen:enabled", (bool)patch->enabled()); + + client->bundle_end(); + + if (recursive) { + + // Send nodes + for (List<NodeImpl*>::const_iterator j = patch->nodes().begin(); + j != patch->nodes().end(); ++j) { + const NodeImpl* const node = (*j); + send_node(client, node, true); + } + + // Send ports + for (uint32_t i=0; i < patch->num_ports(); ++i) { + PortImpl* const port = patch->port_impl(i); + send_port(client, port); + } + + // Send connections + client->transfer_begin(); + for (PatchImpl::Connections::const_iterator j = patch->connections().begin(); + j != patch->connections().end(); ++j) + client->connect((*j)->src_port_path(), (*j)->dst_port_path()); + client->transfer_end(); + } +} + + +/** Sends a node or a patch */ +void +ObjectSender::send_node(ClientInterface* client, const NodeImpl* node, bool recursive) +{ + PluginImpl* const plugin = node->plugin_impl(); + + assert(node->path().length() > 0); + + if (plugin->type() == Plugin::Patch) { + send_patch(client, (PatchImpl*)node, recursive); + return; + } + + if (plugin->uri().length() == 0) { + cerr << "Node " << node->path() << " plugin has no URI! Not sending." << endl; + return; + } + + client->bundle_begin(); + + client->new_node(node->path(), node->plugin()->uri()); + client->set_property(node->path(), "ingen:polyphonic", node->polyphonic()); + + // Send variables + const GraphObjectImpl::Variables& data = node->variables(); + for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j) + client->set_variable(node->path(), (*j).first, (*j).second); + + // Send properties + const GraphObjectImpl::Properties& prop = node->properties(); + for (GraphObjectImpl::Properties::const_iterator j = prop.begin(); j != prop.end(); ++j) + client->set_property(node->path(), (*j).first, (*j).second); + + client->bundle_end(); + + if (recursive) { + // Send ports + for (size_t j=0; j < node->num_ports(); ++j) + send_port(client, node->port_impl(j)); + } +} + + +void +ObjectSender::send_port(ClientInterface* client, const PortImpl* port) +{ + assert(port); + + client->bundle_begin(); + + client->new_port(port->path(), port->index(), port->type().uri(), port->is_output()); + client->set_property(port->path(), "ingen:polyphonic", port->polyphonic()); + + // Send variable + const GraphObjectImpl::Variables& data = port->variables(); + for (GraphObjectImpl::Variables::const_iterator j = data.begin(); j != data.end(); ++j) + client->set_variable(port->path(), (*j).first, (*j).second); + + // Send properties + const GraphObjectImpl::Properties& prop = port->properties(); + for (GraphObjectImpl::Properties::const_iterator j = prop.begin(); j != prop.end(); ++j) + client->set_property(port->path(), (*j).first, (*j).second); + + // Send control value + if (port->type() == DataType::CONTROL) { + const Sample value = dynamic_cast<const AudioBuffer*>(port->buffer(0))->value_at(0); + //cerr << port->path() << " sending default value " << default_value << endl; + client->set_port_value(port->path(), value); + } + + client->bundle_end(); +} + + +} // namespace Ingen + diff --git a/src/engine/ObjectSender.hpp b/src/engine/ObjectSender.hpp new file mode 100644 index 00000000..9b6eb000 --- /dev/null +++ b/src/engine/ObjectSender.hpp @@ -0,0 +1,57 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OBJECTSENDER_H +#define OBJECTSENDER_H + +#include <list> + +namespace Ingen { + +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + +class PatchImpl; +class NodeImpl; +class PortImpl; +class PluginImpl; + + +/** Utility class for sending GraphObjects 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 serialiser, except to calls on ClientInterface rather than + * eg a byte stream. + */ +class ObjectSender { +public: + + // FIXME: Make all object parameters const + + static void send_patch(ClientInterface* client, const PatchImpl* patch, bool recursive); + static void send_node(ClientInterface* client, const NodeImpl* node, bool recursive); + static void send_port(ClientInterface* client, const PortImpl* port); +}; + +} // namespace Ingen + +#endif // OBJECTSENDER_H + diff --git a/src/engine/OmInProcess.cpp b/src/engine/OmInProcess.cpp new file mode 100644 index 00000000..6a65cf38 --- /dev/null +++ b/src/engine/OmInProcess.cpp @@ -0,0 +1,66 @@ +/* This file is part of Ingen. Copyright (C) 2006 Mario Lang. + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include <jack/jack.h> +#include "Engine.hpp" +#include "OSCReceiver.hpp" +#include "JackAudioDriver.hpp" + +extern "C" +{ + int jack_initialize(jack_client_t* client, const char* load_init); + void jack_finish(void* arg); +} + + +void* +run_main(void* arg) +{ + Engine::instance().main(); + + // FIXME: cleanup + + return 0; +} + + +pthread_t main_thread; + + +int +jack_initialize(jack_client_t* client, const char* load_init) +{ + if ((Ingen::om = new Engine(load_init, new Ingen::JackAudioDriver(client))) != NULL) { + pthread_create(&main_thread, NULL, run_main, NULL); + return 0; // Success + } else { + return 1; + } +} + + +void +jack_finish(void* arg) +{ + void* ret; + Engine::instance().quit(); + pthread_join(main_thread, &ret); +} + diff --git a/src/engine/OutputPort.cpp b/src/engine/OutputPort.cpp new file mode 100644 index 00000000..116d9a3e --- /dev/null +++ b/src/engine/OutputPort.cpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include "OutputPort.hpp" +#include "Buffer.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { + + +OutputPort::OutputPort(NodeImpl* parent, + const string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size) + : PortImpl(parent, name, index, poly, type, value, buffer_size) +{ + if (type == DataType::CONTROL) + _broadcast = true; +} + + +void +OutputPort::pre_process(ProcessContext& context) +{ + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->prepare_write(context.start(), context.nframes()); +} + + +void +OutputPort::post_process(ProcessContext& context) +{ + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->prepare_read(context.start(), context.nframes()); + + //cerr << path() << " output post: buffer: " << buffer(0) << endl; + + broadcast(context); +} + + +} // namespace Ingen diff --git a/src/engine/OutputPort.hpp b/src/engine/OutputPort.hpp new file mode 100644 index 00000000..8d441b5c --- /dev/null +++ b/src/engine/OutputPort.hpp @@ -0,0 +1,63 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef OUTPUTPORT_H +#define OUTPUTPORT_H + +#include <string> +#include <cstdlib> +#include "PortImpl.hpp" +#include "types.hpp" + +namespace Ingen { + + +/** 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 + */ +class OutputPort : virtual public PortImpl +{ +public: + OutputPort(NodeImpl* parent, + const std::string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size); + + void pre_process(ProcessContext& context); + void post_process(ProcessContext& context); + + virtual ~OutputPort() {} + + bool is_input() const { return false; } + bool is_output() const { return true; } +}; + + +} // namespace Ingen + +#endif // OUTPUTPORT_H diff --git a/src/engine/PatchImpl.cpp b/src/engine/PatchImpl.cpp new file mode 100644 index 00000000..9f0ae701 --- /dev/null +++ b/src/engine/PatchImpl.cpp @@ -0,0 +1,481 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <cmath> +#include <iostream> +#include "ThreadManager.hpp" +#include "NodeImpl.hpp" +#include "PatchImpl.hpp" +#include "PatchPlugin.hpp" +#include "PortImpl.hpp" +#include "ConnectionImpl.hpp" +#include "DuplexPort.hpp" +#include "Engine.hpp" +#include "ProcessSlave.hpp" + +using namespace std; + +namespace Ingen { + + +PatchImpl::PatchImpl(Engine& engine, const string& path, uint32_t poly, PatchImpl* parent, SampleRate srate, size_t buffer_size, uint32_t internal_poly) + : NodeBase(new PatchPlugin("http://example.org/FIXME", "patch", "Ingen Patch"), + path, poly, parent, srate, buffer_size) + , _engine(engine) + , _internal_poly(internal_poly) + , _compiled_patch(NULL) + , _process(false) +{ + assert(internal_poly >= 1); +} + + +PatchImpl::~PatchImpl() +{ + assert(!_activated); + + delete _compiled_patch; +} + + +void +PatchImpl::activate() +{ + NodeBase::activate(); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->activate(); + + assert(_activated); +} + + +void +PatchImpl::deactivate() +{ + if (_activated) { + + NodeBase::deactivate(); + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + if ((*i)->activated()) + (*i)->deactivate(); + assert(!(*i)->activated()); + } + } + assert(!_activated); +} + + +void +PatchImpl::disable() +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + _process = false; + + for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i) + (*i)->clear_buffers(); +} + + +bool +PatchImpl::prepare_internal_poly(uint32_t poly) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + /* TODO: ports? internal/external poly? */ + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->prepare_poly(poly); + + for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i) + ((ConnectionImpl*)i->get())->prepare_poly(poly); + + /* FIXME: Deal with failure */ + + return true; +} + + +bool +PatchImpl::apply_internal_poly(Raul::Maid& maid, uint32_t poly) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + /* TODO: ports? internal/external poly? */ + + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->apply_poly(maid, poly); + + _internal_poly = poly; + + return true; +} + + +/** Run the patch for the specified number of frames. + * + * Calls all Nodes in (roughly, if parallel) the order _compiled_patch specifies. + */ +void +PatchImpl::process(ProcessContext& context) +{ + if (!_process) + return; + + NodeBase::pre_process(context); + + /*if (_ports) + for (size_t i=0; i < _ports->size(); ++i) + if (_ports->at(i)->is_input() && _ports->at(i)->type() == DataType::MIDI) + cerr << _ports->at(i)->path() << " " + << _ports->at(i)->buffer(0) << " # events: " + << ((MidiBuffer*)_ports->at(i)->buffer(0))->event_count() << endl;*/ + + /* Run */ + if (_compiled_patch && _compiled_patch->size() > 0) { + if (_engine.process_slaves().size() > 0) + process_parallel(context); + else + process_single(context); + } + + NodeBase::post_process(context); +} + + +void +PatchImpl::process_parallel(ProcessContext& context) +{ + size_t n_slaves = _engine.process_slaves().size(); + + CompiledPatch* const cp = _compiled_patch; + + /* Start p-1 slaves */ + + if (n_slaves >= cp->size()) + n_slaves = cp->size()-1; + + if (n_slaves > 0) { + for (size_t i=0; i < cp->size(); ++i) + (*cp)[i].node()->reset_input_ready(); + + for (size_t i=0; i < n_slaves; ++i) + _engine.process_slaves()[i]->whip(cp, i+1, context); + } + + + /* Process ourself until everything is done + * This is analogous to ProcessSlave::_whipped(), but this is the master + * (i.e. what the main Jack process thread calls). Where ProcessSlave + * waits on input, this just skips the node and tries the next, to avoid + * waiting in the Jack thread which pisses Jack off. + */ + + size_t index = 0; + size_t num_finished = 0; // Number of consecutive finished nodes hit + + while (num_finished < cp->size()) { + + CompiledNode& n = (*cp)[index]; + + if (n.node()->process_lock()) { + if (n.node()->n_inputs_ready() == n.n_providers()) { + n.node()->process(context); + + /* Signal dependants their input is ready */ + for (size_t i=0; i < n.dependants().size(); ++i) + n.dependants()[i]->signal_input_ready(); + + ++num_finished; + } else { + n.node()->process_unlock(); + num_finished = 0; + } + } else { + if (n.node()->n_inputs_ready() == n.n_providers()) + ++num_finished; + else + num_finished = 0; + } + + index = (index + 1) % cp->size(); + } + + /* Tell slaves we're done in case we beat them, and pray they're + * really done by the start of next cycle. + * FIXME: This probably breaks (race) at extremely small nframes where + * ingen is the majority of the DSP load. + */ + for (size_t i=0; i < n_slaves; ++i) + _engine.process_slaves()[i]->finish(); +} + + +void +PatchImpl::process_single(ProcessContext& context) +{ + CompiledPatch* const cp = _compiled_patch; + + for (size_t i=0; i < cp->size(); ++i) + (*cp)[i].node()->process(context); +} + + +void +PatchImpl::set_buffer_size(size_t size) +{ + NodeBase::set_buffer_size(size); + assert(_buffer_size == size); + + CompiledPatch* const cp = _compiled_patch; + + for (size_t i=0; i < cp->size(); ++i) + (*cp)[i].node()->set_buffer_size(size); +} + + +// Patch specific stuff + + +/** Add a node. + * Preprocessing thread only. + */ +void +PatchImpl::add_node(List<NodeImpl*>::Node* ln) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + assert(ln != NULL); + assert(ln->elem() != NULL); + assert(ln->elem()->parent_patch() == this); + //assert(ln->elem()->polyphony() == _internal_poly); + + _nodes.push_back(ln); +} + + +/** Remove a node. + * Preprocessing thread only. + */ +PatchImpl::Nodes::Node* +PatchImpl::remove_node(const string& symbol) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + for (List<NodeImpl*>::iterator i = _nodes.begin(); i != _nodes.end(); ++i) + if ((*i)->symbol() == symbol) + return _nodes.erase(i); + + return NULL; +} + + +/** Remove a connection. + * Preprocessing thread only. + */ +PatchImpl::Connections::Node* +PatchImpl::remove_connection(const PortImpl* src_port, const PortImpl* dst_port) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + bool found = false; + Connections::Node* connection = NULL; + for (Connections::iterator i = _connections.begin(); i != _connections.end(); ++i) { + ConnectionImpl* const c = (ConnectionImpl*)i->get(); + if (c->src_port() == src_port && c->dst_port() == dst_port) { + connection = _connections.erase(i); + found = true; + break; + } + } + + if ( ! found) + cerr << "WARNING: [PatchImpl::remove_connection] Connection not found !" << endl; + + return connection; +} + + +bool +PatchImpl::has_connection(const PortImpl* src_port, const PortImpl* dst_port) const +{ + // FIXME: Doesn't scale + for (Connections::const_iterator i = _connections.begin(); i != _connections.end(); ++i) { + ConnectionImpl* const c = (ConnectionImpl*)i->get(); + if (c->src_port() == src_port && c->dst_port() == dst_port) + return true; + } + + return false; +} + + +uint32_t +PatchImpl::num_ports() const +{ + ThreadID context = ThreadManager::current_thread_id(); + + if (context == THREAD_PROCESS) + return NodeBase::num_ports(); + else + return _input_ports.size() + _output_ports.size(); +} + + +/** Create a port. Not realtime safe. + */ +PortImpl* +PatchImpl::create_port(const string& name, DataType type, size_t buffer_size, bool is_output) +{ + if (type == DataType::UNKNOWN) { + cerr << "[PatchImpl::create_port] Unknown port type " << type.uri() << endl; + return NULL; + } + + assert( !(type == DataType::UNKNOWN) ); + + return new DuplexPort(this, name, num_ports(), _polyphony, type, Atom(), buffer_size, is_output); +} + + +/** Remove port from ports list used in pre-processing thread. + * + * Port is not removed from ports array for process thread (which could be + * simultaneously running). + * + * Realtime safe. Preprocessing thread only. + */ +List<PortImpl*>::Node* +PatchImpl::remove_port(const string& symbol) +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + bool found = false; + List<PortImpl*>::Node* ret = NULL; + for (List<PortImpl*>::iterator i = _input_ports.begin(); i != _input_ports.end(); ++i) { + if ((*i)->symbol() == symbol) { + ret = _input_ports.erase(i); + found = true; + } + } + + if (!found) + for (List<PortImpl*>::iterator i = _output_ports.begin(); i != _output_ports.end(); ++i) { + if ((*i)->symbol() == symbol) { + ret = _output_ports.erase(i); + found = true; + } + } + + if ( ! found) + cerr << "WARNING: [PatchImpl::remove_port] Port not found !" << endl; + + return ret; +} + + +/** Remove all ports from ports list used in pre-processing thread. + * + * Ports are not removed from ports array for process thread (which could be + * simultaneously running). Returned is a (inputs, outputs) pair. + * + * Realtime safe. Preprocessing thread only. + */ +void +PatchImpl::clear_ports() +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + _input_ports.clear(); + _output_ports.clear(); +} + + +Raul::Array<PortImpl*>* +PatchImpl::build_ports_array() const +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + Raul::Array<PortImpl*>* const result = new Raul::Array<PortImpl*>(_input_ports.size() + _output_ports.size()); + + size_t i = 0; + + for (List<PortImpl*>::const_iterator p = _input_ports.begin(); p != _input_ports.end(); ++p,++i) + result->at(i) = *p; + + for (List<PortImpl*>::const_iterator p = _output_ports.begin(); p != _output_ports.end(); ++p,++i) + result->at(i) = *p; + + return result; +} + + +/** 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). + * + * Not realtime safe. + */ +CompiledPatch* +PatchImpl::compile() const +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + + //cerr << "*********** Building process order for " << path() << endl; + + CompiledPatch* const compiled_patch = new CompiledPatch();//_nodes.size()); + + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) + (*i)->traversed(false); + + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + NodeImpl* const node = (*i); + // Either a sink or connected to our output ports: + if ( ( ! node->traversed()) && node->dependants()->size() == 0) + compile_recursive(node, compiled_patch); + } + + // Traverse any nodes we didn't hit yet + for (Nodes::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i) { + NodeImpl* const node = (*i); + if ( ! node->traversed()) + compile_recursive(node, compiled_patch); + } + + /*cerr << "----------------------------------------\n"; + for (size_t i=0; i < process_order->size(); ++i) { + assert(process_order->at(i)); + cerr << process_order->at(i)->path() << endl; + } + cerr << "----------------------------------------\n";*/ + + assert(compiled_patch->size() == _nodes.size()); + +#ifndef NDEBUG + for (size_t i=0; i < compiled_patch->size(); ++i) + assert(compiled_patch->at(i).node()); +#endif + + return compiled_patch; +} + + +} // namespace Ingen diff --git a/src/engine/PatchImpl.hpp b/src/engine/PatchImpl.hpp new file mode 100644 index 00000000..3629f6e5 --- /dev/null +++ b/src/engine/PatchImpl.hpp @@ -0,0 +1,168 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PATCHIMPL_H +#define PATCHIMPL_H + +#include <cstdlib> +#include <string> +#include <raul/List.hpp> +#include <raul/SharedPtr.hpp> +#include "interface/DataType.hpp" +#include "interface/Patch.hpp" +#include "NodeBase.hpp" +#include "PluginImpl.hpp" +#include "CompiledPatch.hpp" + +using std::string; + +template <typename T> class Array; +using Raul::List; + +namespace Ingen { + +namespace Shared { class Connection; } + +class ConnectionImpl; +class Engine; +class CompiledPatch; + + +/** 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 PatchImpl : public NodeBase, public Ingen::Shared::Patch +{ +public: + PatchImpl(Engine& engine, + const string& name, + uint32_t poly, + PatchImpl* parent, + SampleRate srate, + size_t buffer_size, + uint32_t local_poly); + + virtual ~PatchImpl(); + + void activate(); + void deactivate(); + + void process(ProcessContext& context); + + void set_buffer_size(size_t size); + + /** Prepare for a new (internal) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_internal_poly. + * \return true on success. + */ + bool prepare_internal_poly(uint32_t poly); + + /** Apply a new (internal) polyphony value. + * + * Audio thread. + * + * \param poly Must be < the most recent value passed to prepare_internal_poly. + * \param maid Any objects no longer needed will be pushed to this + */ + bool apply_internal_poly(Raul::Maid& maid, uint32_t poly); + + // Patch specific stuff not inherited from Node + + typedef List<NodeImpl*> Nodes; + + void add_node(Nodes::Node* tn); + Nodes::Node* remove_node(const string& name); + + Nodes& nodes() { return _nodes; } + Connections& connections() { return _connections; } + + const Nodes& nodes() const { return _nodes; } + const Connections& connections() const { return _connections; } + + uint32_t num_ports() const; + + PortImpl* create_port(const string& name, DataType type, size_t buffer_size, bool is_output); + void add_input(List<PortImpl*>::Node* port) { _input_ports.push_back(port); } ///< Preprocesser thread + void add_output(List<PortImpl*>::Node* port) { _output_ports.push_back(port); } ///< Preprocessor thread + List<PortImpl*>::Node* remove_port(const string& name); + void clear_ports(); + + void add_connection(Connections::Node* c) { _connections.push_back(c); } + Connections::Node* remove_connection(const PortImpl* src_port, const PortImpl* dst_port); + + bool has_connection(const PortImpl* src_port, const PortImpl* dst_port) const; + + CompiledPatch* compiled_patch() { return _compiled_patch; } + void compiled_patch(CompiledPatch* cp) { _compiled_patch = cp; } + + Raul::Array<PortImpl*>* external_ports() { return _ports; } + void external_ports(Raul::Array<PortImpl*>* pa) { _ports = pa; } + + CompiledPatch* compile() const; + Raul::Array<PortImpl*>* build_ports_array() const; + + /** Whether to run this patch's DSP bits in the audio thread */ + bool enabled() const { return _process; } + void enable() { _process = true; } + void disable(); + + uint32_t internal_polyphony() const { return _internal_poly; } + +private: + inline void compile_recursive(NodeImpl* n, CompiledPatch* output) const; + void process_parallel(ProcessContext& context); + void process_single(ProcessContext& context); + + Engine& _engine; + uint32_t _internal_poly; + CompiledPatch* _compiled_patch; ///< Accessed in audio thread only + Connections _connections; ///< Accessed in preprocessing thread only + List<PortImpl*> _input_ports; ///< Accessed in preprocessing thread only + List<PortImpl*> _output_ports; ///< Accessed in preprocessing thread only + Nodes _nodes; ///< Accessed in preprocessing thread only + bool _process; +}; + + + +/** Private helper for compile */ +inline void +PatchImpl::compile_recursive(NodeImpl* n, CompiledPatch* output) const +{ + if (n == NULL || n->traversed()) + return; + + n->traversed(true); + assert(output != NULL); + + for (List<NodeImpl*>::iterator i = n->providers()->begin(); i != n->providers()->end(); ++i) + if ( ! (*i)->traversed() ) + compile_recursive((*i), output); + + output->push_back(CompiledNode(n, n->providers()->size(), n->dependants())); +} + + +} // namespace Ingen + +#endif // PATCHIMPL_H diff --git a/src/engine/PatchPlugin.hpp b/src/engine/PatchPlugin.hpp new file mode 100644 index 00000000..a7334392 --- /dev/null +++ b/src/engine/PatchPlugin.hpp @@ -0,0 +1,64 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PATCHPLUGIN_H +#define PATCHPLUGIN_H + +#include CONFIG_H_PATH + +#include <string> +#include "PluginImpl.hpp" + +namespace Ingen { + +class NodeImpl; + + +/** Implementation of a Patch plugin. + * + * Patches don't actually work like this yet... + */ +class PatchPlugin : public PluginImpl +{ +public: + PatchPlugin(const std::string& uri, + const std::string& symbol, + const std::string& name) + : PluginImpl(Plugin::Patch, uri) + {} + + NodeImpl* instantiate(const std::string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine) + { + return NULL; + } + + const string symbol() const { return "patch"; } + const string name() const { return "Ingen Patch"; } + +private: + const string _symbol; + const string _name; +}; + + +} // namespace Ingen + +#endif // PATCHPLUGIN_H + diff --git a/src/engine/PluginImpl.cpp b/src/engine/PluginImpl.cpp new file mode 100644 index 00000000..215cf4ce --- /dev/null +++ b/src/engine/PluginImpl.cpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include "PluginImpl.hpp" +#include "MidiNoteNode.hpp" +#include "MidiTriggerNode.hpp" +#include "MidiControlNode.hpp" +#include "TransportNode.hpp" + +using namespace std; + +namespace Ingen { + + +void +PluginImpl::load() +{ + if (!_module) { + //cerr << "Loading " << _library_path << " library" << endl; + _module = new Glib::Module(_library_path, Glib::MODULE_BIND_LOCAL); + if (!(*_module)) + delete _module; + } +} + + +void +PluginImpl::unload() +{ + if (_module) { + //cerr << "Unloading " << _library_path << endl; + delete _module; + _module = NULL; + } +} + + +} // namespace Ingen + diff --git a/src/engine/PluginImpl.hpp b/src/engine/PluginImpl.hpp new file mode 100644 index 00000000..0301d942 --- /dev/null +++ b/src/engine/PluginImpl.hpp @@ -0,0 +1,106 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PLUGINIMPL_H +#define PLUGINIMPL_H + +#include CONFIG_H_PATH + +#include <cstdlib> +#include <glibmm/module.h> +#include <boost/utility.hpp> +#include <dlfcn.h> +#include <string> +#include <iostream> +#include "types.hpp" +#include "interface/Plugin.hpp" + +using std::string; +using Ingen::Shared::Plugin; + +namespace Ingen { + +class PatchImpl; +class NodeImpl; +class Engine; + + +/** Implementation of a plugin (internal code, or a loaded shared library). + * + * Conceptually, a Node is an instance of this. + */ +class PluginImpl : public Ingen::Shared::Plugin, public boost::noncopyable +{ +public: + PluginImpl(Type type, const string& uri, const string library_path="") + : _type(type) + , _uri(uri) + , _library_path(library_path) + , _module(NULL) + {} + + virtual NodeImpl* instantiate(const std::string& name, + bool polyphonic, + Ingen::PatchImpl* parent, + Engine& engine) = 0; + + virtual const string symbol() const = 0; + virtual const string name() const = 0; + + const std::string& library_path() const { return _library_path; } + void library_path(const std::string& s) { _library_path = s;} + + void load(); + void unload(); + + const char* type_string() const { + if (_type == LADSPA) return "LADSPA"; + else if (_type == LV2) return "LV2"; + else if (_type == Internal) return "Internal"; + else if (_type == Patch) return "Patch"; + else return ""; + } + + const string type_uri() const { + return string("ingen:").append(type_string()); + } + + void set_type(const string& type_string) { + if (type_string == "LADSPA") _type = LADSPA; + else if (type_string == "LV2") _type = LV2; + else if (type_string == "Internal") _type = Internal; + else if (type_string == "Patch") _type = Patch; + } + + Plugin::Type type() const { return _type; } + void type(Plugin::Type t) { _type = t; } + const string& uri() const { return _uri; } + Glib::Module* module() const { return _module; } + void module(Glib::Module* module) { _module = module; } + +protected: + Plugin::Type _type; + const string _uri; + string _library_path; + Glib::Module* _module; +}; + + +} // namespace Ingen + +#endif // PLUGINIMPL_H + diff --git a/src/engine/PortImpl.cpp b/src/engine/PortImpl.cpp new file mode 100644 index 00000000..3eac65ca --- /dev/null +++ b/src/engine/PortImpl.cpp @@ -0,0 +1,190 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <raul/Array.hpp> +#include <raul/Maid.hpp> +#include "PortImpl.hpp" +#include "NodeImpl.hpp" +#include "interface/DataType.hpp" +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "ProcessContext.hpp" +#include "SendPortActivityEvent.hpp" + +using namespace std; + +namespace Ingen { + + +PortImpl::PortImpl(NodeImpl* const node, + const string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Atom& value, + size_t buffer_size) + : GraphObjectImpl(node, name, (type == DataType::AUDIO || type == DataType::CONTROL)) + , _index(index) + , _poly(poly) + , _buffer_size(buffer_size) + , _type(type) + , _value(value) + , _fixed_buffers(false) + , _broadcast(false) + , _set_by_user(false) + , _last_broadcasted_value(_value.type() == Atom::FLOAT ? _value.get_float() : 0.0f) // default? + , _context(Context::AUDIO) + , _buffers(new Raul::Array<Buffer*>(poly)) +{ + assert(node != NULL); + assert(_poly > 0); + + allocate_buffers(); + clear_buffers(); + + if (node->parent() == NULL) + _polyphonic = false; + else + _polyphonic = true; + + if (type == DataType::EVENT) + _broadcast = true; // send activity blips + + assert(_buffers->size() > 0); +} + + +PortImpl::~PortImpl() +{ + for (uint32_t i=0; i < _poly; ++i) + delete _buffers->at(i); + + delete _buffers; +} + + +bool +PortImpl::set_polyphonic(Raul::Maid& maid, bool p) +{ + if (_type == DataType::CONTROL || _type == DataType::AUDIO) + return GraphObjectImpl::set_polyphonic(maid, p); + else + return (!p); +} + + +bool +PortImpl::prepare_poly(uint32_t poly) +{ + if (!_polyphonic || !_parent->polyphonic()) + return true; + + /* FIXME: poly never goes down, harsh on memory.. */ + if (poly > _poly) { + _prepared_buffers = new Raul::Array<Buffer*>(poly, *_buffers); + for (uint32_t i = _poly; i < _prepared_buffers->size(); ++i) + _prepared_buffers->at(i) = Buffer::create(_type, _buffer_size); + } + + return true; +} + + +bool +PortImpl::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic || !_parent->polyphonic()) + return true; + + assert(poly <= _prepared_buffers->size()); + + // Apply a new set of buffers from a preceding call to prepare_poly + if (_prepared_buffers && _buffers != _prepared_buffers) { + maid.push(_buffers); + _buffers = _prepared_buffers; + } + + _poly = poly; + assert(_buffers->size() >= poly); + assert(this->poly() == poly); + + return true; +} + + +void +PortImpl::allocate_buffers() +{ + _buffers->alloc(_poly); + + for (uint32_t i=0; i < _poly; ++i) + _buffers->at(i) = Buffer::create(_type, _buffer_size); +} + + +void +PortImpl::set_buffer_size(size_t size) +{ + _buffer_size = size; + + for (uint32_t i=0; i < _poly; ++i) + _buffers->at(i)->resize(size); + + connect_buffers(); +} + + +void +PortImpl::connect_buffers() +{ + for (uint32_t i=0; i < _poly; ++i) + PortImpl::parent_node()->set_port_buffer(i, _index, buffer(i)); +} + + +void +PortImpl::clear_buffers() +{ + for (uint32_t i=0; i < _poly; ++i) + buffer(i)->clear(); +} + + +void +PortImpl::broadcast(ProcessContext& context) +{ + if (_broadcast) { + if (_type == DataType::CONTROL || _type == DataType::AUDIO) { + const Sample value = ((AudioBuffer*)buffer(0))->value_at(0); + if (value != _last_broadcasted_value) { + const SendPortValueEvent ev(context.engine(), context.start(), this, false, 0, value); + context.event_sink().write(sizeof(ev), &ev); + _last_broadcasted_value = value; + } + } else if (_type == DataType::EVENT) { + if (((EventBuffer*)buffer(0))->event_count() > 0) { + const SendPortActivityEvent ev(context.engine(), context.start(), this); + context.event_sink().write(sizeof(ev), &ev); + } + } + } +} + + + +} // namespace Ingen diff --git a/src/engine/PortImpl.hpp b/src/engine/PortImpl.hpp new file mode 100644 index 00000000..be96a910 --- /dev/null +++ b/src/engine/PortImpl.hpp @@ -0,0 +1,147 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PORTIMPL_H +#define PORTIMPL_H + +#include <cstdlib> +#include <string> +#include <raul/Array.hpp> +#include "interface/Port.hpp" +#include "types.hpp" +#include "GraphObjectImpl.hpp" +#include "interface/DataType.hpp" +#include "Buffer.hpp" +#include "Context.hpp" + +namespace Raul { class Maid; class Atom; } + +namespace Ingen { + +class NodeImpl; +class Buffer; +class ProcessContext; + + +/** 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 PortImpl : public GraphObjectImpl, public Ingen::Shared::Port +{ +public: + virtual ~PortImpl(); + + /** A port's parent is always a node, so static cast should be safe */ + NodeImpl* parent_node() const { return (NodeImpl*)_parent; } + + bool set_polyphonic(Raul::Maid& maid, bool p); + + /** Prepare for a new (external) polyphony value. + * + * Preprocessor thread, poly is actually applied by apply_poly. + */ + virtual bool prepare_poly(uint32_t poly); + + /** Apply a new polyphony value. + * + * Audio thread. + * + * \param poly Must be < the most recent value passed to prepare_poly. + */ + virtual bool apply_poly(Raul::Maid& maid, uint32_t poly); + + const Raul::Atom& value() const { return _value; } + void set_value(const Raul::Atom& v) { _value = v; } + + inline Buffer* buffer(uint32_t voice) const { + Buffer* const buf = _buffers->at(voice); + if (buf->is_joined()) { + assert(buf->joined_buffer()); + return buf->joined_buffer(); + } else { + return buf; + } + } + + /** Called once per process cycle */ + virtual void pre_process(ProcessContext& context) = 0; + virtual void process(ProcessContext& context) {}; + virtual void post_process(ProcessContext& context) = 0; + + /** Empty buffer contents completely (ie silence) */ + virtual void clear_buffers(); + + virtual bool is_input() const = 0; + virtual bool is_output() const = 0; + + uint32_t index() const { return _index; } + uint32_t poly() const { return _poly; } + DataType type() const { return _type; } + size_t buffer_size() const { return _buffer_size; } + + virtual void set_buffer_size(size_t size); + + void fixed_buffers(bool b) { _fixed_buffers = b; } + bool fixed_buffers() { return _fixed_buffers; } + + void broadcast(bool b) { _broadcast = b; } + bool broadcast() { return _broadcast; } + + void raise_set_by_user_flag() { _set_by_user = true; } + + Context::ID context() const { return _context; } + void set_context(Context::ID c) { _context = c; } + +protected: + PortImpl(NodeImpl* node, + const std::string& name, + uint32_t index, + uint32_t poly, + DataType type, + const Raul::Atom& value, + size_t buffer_size); + + virtual void allocate_buffers(); + virtual void connect_buffers(); + virtual void broadcast(ProcessContext& context); + + uint32_t _index; + uint32_t _poly; + uint32_t _buffer_size; + DataType _type; + Raul::Atom _value; + bool _fixed_buffers; + bool _broadcast; + bool _set_by_user; + Sample _last_broadcasted_value; + + Context::ID _context; + Raul::Array<Buffer*>* _buffers; + + // Dynamic polyphony + Raul::Array<Buffer*>* _prepared_buffers; +}; + + +} // namespace Ingen + +#endif // PORTIMPL_H diff --git a/src/engine/PostProcessor.cpp b/src/engine/PostProcessor.cpp new file mode 100644 index 00000000..a50ca275 --- /dev/null +++ b/src/engine/PostProcessor.cpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cassert> +#include <iostream> +#include <pthread.h> +#include <raul/SRSWQueue.hpp> +//#include <raul/Maid.hpp> +#include "events/SendPortValueEvent.hpp" +#include "Event.hpp" +#include "PostProcessor.hpp" +#include "Engine.hpp" +#include "AudioDriver.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { + + +PostProcessor::PostProcessor(Engine& engine, size_t queue_size) + : _engine(engine) + , _max_time(0) + , _events(queue_size) + , _event_buffer_size(sizeof(SendPortValueEvent)) // FIXME: make generic + , _event_buffer((uint8_t*)malloc(_event_buffer_size)) +{ +} + + +void +PostProcessor::process() +{ + const FrameTime end_time = _max_time.get(); + + /* Process any audio thread generated events */ + /* FIXME: process events from all threads if parallel */ + + while (_engine.audio_driver()->context().event_sink().read( + _event_buffer_size, _event_buffer)) { + if (((Event*)_event_buffer)->time() > end_time) + break; // FIXME: loses event? + ((Event*)_event_buffer)->post_process(); + } + + /* Process normal events */ + while ( ! _events.empty()) { + Event* const ev = _events.front(); + if (ev->time() > end_time) + break; + _events.pop(); + assert(ev); + ev->post_process(); + delete ev; + } +} + + +} // namespace Ingen diff --git a/src/engine/PostProcessor.hpp b/src/engine/PostProcessor.hpp new file mode 100644 index 00000000..3d51136d --- /dev/null +++ b/src/engine/PostProcessor.hpp @@ -0,0 +1,73 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef POSTPROCESSOR_H +#define POSTPROCESSOR_H + +#include <pthread.h> +#include "types.hpp" +#include <raul/SRSWQueue.hpp> +//#include <raul/Slave.hpp> + +//namespace Raul { class Maid; } + +namespace Ingen { + +class Event; +class Engine; + + +/** 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. + * + * Update: This is all run from main_iteration now to solve scripting + * thread issues. Not sure if this is permanent/ideal or not... + * + * \ingroup engine + */ +class PostProcessor //: public Raul::Slave +{ +public: + PostProcessor(Engine& engine, /*Raul::Maid& maid, */size_t queue_size); + + /** Push an event on to the process queue, realtime-safe, not thread-safe. */ + inline void push(Event* const ev) { _events.push(ev); } + + /** Post-process and delete all pending events */ + void process(); + + /** Set the latest event time that should be post-processed */ + void set_end_time(FrameTime time) { _max_time = time; } + +private: + Engine& _engine; + Raul::AtomicInt _max_time; + //Raul::Maid& _maid; + Raul::SRSWQueue<Event*> _events; + uint32_t _event_buffer_size; + uint8_t* _event_buffer; + + //virtual void _whipped(); +}; + + +} // namespace Ingen + +#endif // POSTPROCESSOR_H diff --git a/src/engine/ProcessContext.hpp b/src/engine/ProcessContext.hpp new file mode 100644 index 00000000..57677126 --- /dev/null +++ b/src/engine/ProcessContext.hpp @@ -0,0 +1,71 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PROCESSCONTEXT_H +#define PROCESSCONTEXT_H + +#include "EventSink.hpp" +#include "Context.hpp" + +namespace Ingen { + + +/** Context of a process() call. + * + * This is used to pass whatever information a GraphObject might need to + * process in the audio thread, e.g. the available thread pool, sink for + * events (generated in the audio thread, not user initiated events), etc. + * + * Note the distinction between nframes and start/end. If transport speed + * != 1.0, end-start != nframes (though currently that is never the case, it + * may be in the future with sequencerey things). + * + * \ingroup engine + */ +class ProcessContext : public Context +{ +public: + ProcessContext(Engine& engine) + : Context(engine, AUDIO) + , _event_sink(engine, 1024) // FIXME: size? + {} + + void set_time_slice(SampleCount nframes, FrameTime start, FrameTime end) { + _nframes = nframes; + _start = start; + _end = end; + } + + inline SampleCount nframes() const { return _nframes; } + inline FrameTime start() const { return _start; } + inline FrameTime end() const { return _end; } + inline const EventSink& event_sink() const { return _event_sink; } + inline EventSink& event_sink() { return _event_sink; } + +private: + SampleCount _nframes; ///< Number of actual time (Jack) frames this cycle + FrameTime _start; ///< Start frame of this cycle, timeline relative + FrameTime _end; ///< End frame of this cycle, timeline relative + EventSink _event_sink; ///< Sink for events generated in the audio thread +}; + + + +} // namespace Ingen + +#endif // PROCESSCONTEXT_H + diff --git a/src/engine/ProcessSlave.cpp b/src/engine/ProcessSlave.cpp new file mode 100644 index 00000000..c7c868e8 --- /dev/null +++ b/src/engine/ProcessSlave.cpp @@ -0,0 +1,75 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include "ProcessSlave.hpp" +#include "NodeImpl.hpp" +#include "CompiledPatch.hpp" + +using namespace std; + +namespace Ingen { + + +uint32_t ProcessSlave::_next_id = 0; + + +void +ProcessSlave::_whipped() +{ + assert(_compiled_patch); + CompiledPatch* const cp = _compiled_patch; + + /* Iterate over all nodes attempting to run immediately or block then run, + * until we've been through the entire array without getting a lock, + * and thus are finished this cycle. + */ + + size_t num_finished = 0; // Number of consecutive finished nodes hit + + while (_state == STATE_RUNNING) { + + CompiledNode& n = (*cp)[_index]; + + if (n.node()->process_lock()) { + + n.node()->wait_for_input(n.n_providers()); + + n.node()->process(_process_context); + + /* Signal dependants their input is ready */ + for (size_t i=0; i < n.dependants().size(); ++i) + n.dependants()[i]->signal_input_ready(); + + num_finished = 1; + } else { + ++num_finished; + } + + _index = (_index + 1) % cp->size(); + + if (num_finished >= cp->size()) + break; + } + + _index = 0; + _compiled_patch = NULL; + _state = STATE_FINISHED; +} + + +} // namespace Ingen diff --git a/src/engine/ProcessSlave.hpp b/src/engine/ProcessSlave.hpp new file mode 100644 index 00000000..40becd48 --- /dev/null +++ b/src/engine/ProcessSlave.hpp @@ -0,0 +1,100 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PROCESS_SLAVE_HPP +#define PROCESS_SLAVE_HPP + +#include CONFIG_H_PATH + +#include <sstream> +#include <raul/Slave.hpp> +#include <raul/Array.hpp> +#include <raul/AtomicInt.hpp> +#include "ProcessContext.hpp" +#include "types.hpp" + +namespace Ingen { + +class NodeImpl; +class CompiledPatch; + + +class ProcessSlave : protected Raul::Slave { +public: + ProcessSlave(Engine& engine, bool realtime) + : _id(_next_id++) + , _index(0) + , _state(STATE_FINISHED) + , _compiled_patch(NULL) + , _process_context(engine) + { + std::stringstream ss; + ss << "Process Slave "; + ss << _id; + set_name(ss.str()); + + start(); + + if (realtime) + set_scheduling(SCHED_FIFO, 40); + } + + ~ProcessSlave() { + stop(); + } + + inline void whip(CompiledPatch* compiled_patch, uint32_t start_index, ProcessContext& context) { + assert(_state == STATE_FINISHED); + _index = start_index; + _state = STATE_RUNNING; + _compiled_patch = compiled_patch; + _process_context.set_time_slice(context.nframes(), context.start(), context.end()); + + Raul::Slave::whip(); + } + + inline void finish() { + while (_state.get() != STATE_FINISHED) + _state.compare_and_exchange(STATE_RUNNING, STATE_FINISH_SIGNALLED); + } + + inline uint32_t id() const { return _id; } + inline const ProcessContext& context() const { return _process_context; } + inline ProcessContext& context() { return _process_context; } + +private: + + void _whipped(); + + static uint32_t _next_id; + + static const int STATE_RUNNING = 0; + static const int STATE_FINISH_SIGNALLED = 1; + static const int STATE_FINISHED = 2; + + uint32_t _id; + uint32_t _index; + Raul::AtomicInt _state; + CompiledPatch* _compiled_patch; + ProcessContext _process_context; +}; + + +} // namespace Ingen + +#endif // PROCESS_SLAVE_HPP + diff --git a/src/engine/QueuedEngineInterface.cpp b/src/engine/QueuedEngineInterface.cpp new file mode 100644 index 00000000..962410b2 --- /dev/null +++ b/src/engine/QueuedEngineInterface.cpp @@ -0,0 +1,370 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include "QueuedEngineInterface.hpp" +#include CONFIG_H_PATH +#include "QueuedEventSource.hpp" +#include "events.hpp" +#include "Engine.hpp" +#include "AudioDriver.hpp" + +namespace Ingen { + +QueuedEngineInterface::QueuedEngineInterface(Engine& engine, size_t queued_size, size_t stamped_size) + : QueuedEventSource(queued_size, stamped_size) + , _responder(new Responder(NULL, 0)) + , _engine(engine) + , _in_bundle(false) +{ +} + + +SampleCount +QueuedEngineInterface::now() const +{ + // Exactly one cycle latency (some could run ASAP if we get lucky, but not always, and a slight + // constant latency is far better than jittery lower (average) latency + assert(_engine.audio_driver()); + return _engine.audio_driver()->frame_time() + _engine.audio_driver()->buffer_size(); +} + + +void +QueuedEngineInterface::set_next_response_id(int32_t id) +{ + if (_responder) + _responder->set_id(id); +} + + +void +QueuedEngineInterface::disable_responses() +{ + _responder->set_client(NULL); + _responder->set_id(0); +} + + +/* *** EngineInterface implementation below here *** */ + + +void +QueuedEngineInterface::register_client(ClientInterface* client) +{ + push_queued(new RegisterClientEvent(_engine, _responder, now(), client->uri(), client)); + if (!_responder) { + _responder = SharedPtr<Responder>(new Responder(client, 1)); + } else { + _responder->set_id(1); + _responder->set_client(client); + } +} + + +void +QueuedEngineInterface::unregister_client(const string& uri) +{ + push_queued(new UnregisterClientEvent(_engine, _responder, now(), uri)); + if (_responder && _responder->client() && _responder->client()->uri() == uri) { + _responder->set_id(0); + _responder->set_client(NULL); + } +} + + + +// Engine commands +void +QueuedEngineInterface::load_plugins() +{ + push_queued(new LoadPluginsEvent(_engine, _responder, now(), this)); +} + + +void +QueuedEngineInterface::activate() +{ + QueuedEventSource::activate(); + push_queued(new PingQueuedEvent(_engine, _responder, now())); +} + + +void +QueuedEngineInterface::deactivate() +{ + push_queued(new DeactivateEvent(_engine, _responder, now())); +} + + +void +QueuedEngineInterface::quit() +{ + _responder->respond_ok(); + _engine.quit(); +} + + +// Bundle commands + +void +QueuedEngineInterface::bundle_begin() +{ + _in_bundle = true; +} + + +void +QueuedEngineInterface::bundle_end() +{ + _in_bundle = false; +} + + +// Object commands + +void +QueuedEngineInterface::new_patch(const string& path, + uint32_t poly) +{ + push_queued(new CreatePatchEvent(_engine, _responder, now(), path, poly)); +} + + +// FIXME: use index +void QueuedEngineInterface::new_port(const string& path, + uint32_t index, + const string& data_type, + bool direction) +{ + push_queued(new CreatePortEvent(_engine, _responder, now(), path, data_type, direction, this)); +} + + +void +QueuedEngineInterface::new_node(const string& path, + const string& plugin_uri) +{ + push_queued(new CreateNodeEvent(_engine, _responder, now(), + path, plugin_uri, true)); // FIXME: polyphonic by default +} + + +void +QueuedEngineInterface::new_node_deprecated(const string& path, + const string& plugin_type, + const string& plugin_lib, + const string& plugin_label) +{ + push_queued(new CreateNodeEvent(_engine, _responder, now(), + path, plugin_type, plugin_lib, plugin_label, true)); // FIXME: polyphonic by default +} + +void +QueuedEngineInterface::rename(const string& old_path, + const string& new_symbol) +{ + push_queued(new RenameEvent(_engine, _responder, now(), old_path, new_symbol)); +} + + +void +QueuedEngineInterface::destroy(const string& path) +{ + push_queued(new DestroyEvent(_engine, _responder, now(), this, path)); +} + + +void +QueuedEngineInterface::clear_patch(const string& patch_path) +{ + push_queued(new ClearPatchEvent(_engine, _responder, now(), this, patch_path)); +} + + +void +QueuedEngineInterface::set_polyphony(const string& patch_path, uint32_t poly) +{ + push_queued(new SetPolyphonyEvent(_engine, _responder, now(), this, patch_path, poly)); +} + + +void +QueuedEngineInterface::set_polyphonic(const string& path, bool poly) +{ + push_queued(new SetPolyphonicEvent(_engine, _responder, now(), this, path, poly)); +} + + +void +QueuedEngineInterface::connect(const string& src_port_path, + const string& dst_port_path) +{ + push_queued(new ConnectionEvent(_engine, _responder, now(), src_port_path, dst_port_path)); + +} + + +void +QueuedEngineInterface::disconnect(const string& src_port_path, + const string& dst_port_path) +{ + push_queued(new DisconnectionEvent(_engine, _responder, now(), src_port_path, dst_port_path)); +} + + +void +QueuedEngineInterface::disconnect_all(const string& patch_path, + const string& node_path) +{ + push_queued(new DisconnectAllEvent(_engine, _responder, now(), patch_path, node_path)); +} + + +void +QueuedEngineInterface::set_port_value(const string& port_path, + const Raul::Atom& value) +{ + push_queued(new SetPortValueEvent(_engine, _responder, true, now(), port_path, value)); +} + + +void +QueuedEngineInterface::set_voice_value(const string& port_path, + uint32_t voice, + const Raul::Atom& value) +{ + push_queued(new SetPortValueEvent(_engine, _responder, true, now(), voice, port_path, value)); +} + + +void +QueuedEngineInterface::set_program(const string& node_path, + uint32_t bank, + uint32_t program) +{ + std::cerr << "FIXME: set program" << std::endl; +} + + +void +QueuedEngineInterface::midi_learn(const string& node_path) +{ + push_queued(new MidiLearnEvent(_engine, _responder, now(), node_path)); +} + + +void +QueuedEngineInterface::set_variable(const string& path, + const string& predicate, + const Atom& value) +{ + push_queued(new SetMetadataEvent(_engine, _responder, now(), false, path, predicate, value)); +} + + +void +QueuedEngineInterface::set_property(const string& path, + const string& predicate, + const Atom& value) +{ + // FIXME: implement generically + if (predicate == "ingen:enabled") { + if (value.type() == Atom::BOOL) { + push_queued(new EnablePatchEvent(_engine, _responder, now(), path, value.get_bool())); + return; + } + } else if (predicate == "ingen:polyphonic") { + if (value.type() == Atom::BOOL) { + push_queued(new SetPolyphonicEvent(_engine, _responder, now(), this, path, value.get_bool())); + return; + } + } else if (predicate == "ingen:polyphony") { + if (value.type() == Atom::INT) { + push_queued(new SetPolyphonyEvent(_engine, _responder, now(), this, path, value.get_int32())); + return; + } + } else { + push_queued(new SetMetadataEvent(_engine, _responder, now(), true, path, predicate, value)); + } +} + +// Requests // + +void +QueuedEngineInterface::ping() +{ + if (_engine.activated()) { + push_queued(new PingQueuedEvent(_engine, _responder, now())); + } else if (_responder) { + _responder->respond_ok(); + } +} + + +void +QueuedEngineInterface::request_plugin(const string& uri) +{ + push_queued(new RequestPluginEvent(_engine, _responder, now(), uri)); +} + + +void +QueuedEngineInterface::request_object(const string& path) +{ + push_queued(new RequestObjectEvent(_engine, _responder, now(), path)); +} + + +void +QueuedEngineInterface::request_port_value(const string& port_path) +{ + push_queued(new RequestPortValueEvent(_engine, _responder, now(), port_path)); +} + + +void +QueuedEngineInterface::request_variable(const string& object_path, const string& key) +{ + push_queued(new RequestMetadataEvent(_engine, _responder, now(), false, object_path, key)); +} + + +void +QueuedEngineInterface::request_property(const string& object_path, const string& key) +{ + push_queued(new RequestMetadataEvent(_engine, _responder, now(), true, object_path, key)); +} + + +void +QueuedEngineInterface::request_plugins() +{ + push_queued(new RequestPluginsEvent(_engine, _responder, now())); +} + + +void +QueuedEngineInterface::request_all_objects() +{ + push_queued(new RequestAllObjectsEvent(_engine, _responder, now())); +} + + +} // namespace Ingen + + diff --git a/src/engine/QueuedEngineInterface.hpp b/src/engine/QueuedEngineInterface.hpp new file mode 100644 index 00000000..73c790c8 --- /dev/null +++ b/src/engine/QueuedEngineInterface.hpp @@ -0,0 +1,170 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QUEUEDENGINEINTERFACE_H +#define QUEUEDENGINEINTERFACE_H + +#include <inttypes.h> +#include <string> +#include <memory> +#include <raul/SharedPtr.hpp> +#include "interface/EngineInterface.hpp" +#include "interface/ClientInterface.hpp" +#include "Responder.hpp" +#include "QueuedEventSource.hpp" +#include "Engine.hpp" +using std::string; + +namespace Ingen { + +using Shared::ClientInterface; +using Shared::EngineInterface; +class Engine; + + +/** 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 QueuedEventSource, public EngineInterface +{ +public: + QueuedEngineInterface(Engine& engine, size_t queued_size, size_t stamped_size); + virtual ~QueuedEngineInterface() {} + + std::string uri() const { return "ingen:internal"; } + + void set_next_response_id(int32_t id); + + // Client registration + virtual void register_client(ClientInterface* client); + virtual void unregister_client(const string& uri); + + // Engine commands + virtual void load_plugins(); + virtual void activate(); + virtual void deactivate(); + virtual void quit(); + + // Bundles + virtual void bundle_begin(); + virtual void bundle_end(); + + // Object commands + + virtual void new_patch(const string& path, + uint32_t poly); + + virtual void new_port(const string& path, + uint32_t index, + const string& data_type, + bool direction); + + virtual void new_node(const string& path, + const string& plugin_uri); + + /** FIXME: DEPRECATED, REMOVE */ + virtual void new_node_deprecated(const string& path, + const string& plugin_type, + const string& lib_path, + const string& plug_label); + + 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 set_polyphony(const string& patch_path, uint32_t poly); + + virtual void set_polyphonic(const string& path, bool poly); + + 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& patch_path, + const string& node_path); + + virtual void set_port_value(const string& port_path, + const Raul::Atom& value); + + virtual void set_voice_value(const string& port_path, + uint32_t voice, + const Raul::Atom& value); + + 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_variable(const string& path, + const string& predicate, + const Raul::Atom& value); + + virtual void set_property(const string& path, + const string& predicate, + const Raul::Atom& value); + + // Requests // + + virtual void ping(); + virtual void request_plugin(const string& uri); + virtual void request_object(const string& path); + virtual void request_port_value(const string& port_path); + virtual void request_variable(const string& object_path, const string& key); + virtual void request_property(const string& object_path, const string& key); + virtual void request_plugins(); + virtual void request_all_objects(); + +protected: + + virtual void disable_responses(); + + SharedPtr<Responder> _responder; ///< NULL if responding disabled + Engine& _engine; + bool _in_bundle; ///< True iff a bundle is currently being received + +private: + SampleCount now() const; +}; + + +} // namespace Ingen + +#endif // QUEUEDENGINEINTERFACE_H + diff --git a/src/engine/QueuedEvent.cpp b/src/engine/QueuedEvent.cpp new file mode 100644 index 00000000..8ed6fa02 --- /dev/null +++ b/src/engine/QueuedEvent.cpp @@ -0,0 +1,50 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "QueuedEvent.hpp" +#include "ThreadManager.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { + + +void +QueuedEvent::pre_process() +{ + assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS); + assert(_pre_processed == false); + _pre_processed = true; +} + + +void +QueuedEvent::execute(ProcessContext& context) +{ + assert(_pre_processed); + assert(_time <= context.end()); + + // Didn't prepare in time. QueuedEvents aren't (necessarily) sample accurate + // so just run at the beginning of this cycle + if (_time <= context.start()) + _time = context.start(); + + Event::execute(context); +} + + +} // namespace Ingen + diff --git a/src/engine/QueuedEvent.hpp b/src/engine/QueuedEvent.hpp new file mode 100644 index 00000000..e616d269 --- /dev/null +++ b/src/engine/QueuedEvent.hpp @@ -0,0 +1,83 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QUEUEDEVENT_H +#define QUEUEDEVENT_H + +#include "Event.hpp" + +namespace Ingen { + +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(); + + virtual void execute(ProcessContext& context); + + /** If this event blocks the prepare phase of other slow events */ + bool is_blocking() { return _blocking; } + + bool is_prepared() { return _pre_processed; } + +protected: + QueuedEvent(Engine& engine, + SharedPtr<Responder> responder, + FrameTime time, + bool blocking = false, + QueuedEventSource* source = NULL) + : Event(engine, responder, time) + , _pre_processed(false), _blocking(blocking), _source(source) + { + if (blocking) + assert(_source); + } + + // NULL event base (for internal events only!) + QueuedEvent(Engine& engine) + : Event(engine, SharedPtr<Responder>(), 0) + , _pre_processed(false), _blocking(false), _source(NULL) + {} + + bool _pre_processed; + bool _blocking; + QueuedEventSource* _source; +}; + + +} // namespace Ingen + +#endif // QUEUEDEVENT_H diff --git a/src/engine/QueuedEventSource.cpp b/src/engine/QueuedEventSource.cpp new file mode 100644 index 00000000..69ab805a --- /dev/null +++ b/src/engine/QueuedEventSource.cpp @@ -0,0 +1,183 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <sys/mman.h> +#include <iostream> +#include "QueuedEventSource.hpp" +#include "QueuedEvent.hpp" +#include "PostProcessor.hpp" +#include "ThreadManager.hpp" +#include "ProcessContext.hpp" + +using namespace std; + +namespace Ingen { + + +QueuedEventSource::QueuedEventSource(size_t queued_size, size_t stamped_size) + : _front(0) + , _back(0) + , _prepared_back(0) + , _size(queued_size+1) + , _blocking_semaphore(0) + , _full_semaphore(0) + , _stamped_queue(stamped_size) +{ + _events = (QueuedEvent**)calloc(_size, sizeof(QueuedEvent*)); + + mlock(_events, _size * sizeof(QueuedEvent*)); + + Thread::set_context(THREAD_PRE_PROCESS); + assert(context() == THREAD_PRE_PROCESS); + + set_name("QueuedEventSource"); +} + + +QueuedEventSource::~QueuedEventSource() +{ + Thread::stop(); + + free(_events); +} + + +/** Push an unprepared event onto the queue. + */ +void +QueuedEventSource::push_queued(QueuedEvent* const ev) +{ + assert(!ev->is_prepared()); + + unsigned back = _back.get(); + bool full = (((_front.get() - back + _size) % _size) == 1); + while (full) { + whip(); + cerr << "WARNING: Event queue full. Waiting..." << endl; + _full_semaphore.wait(); + back = _back.get(); + full = (((_front.get() - back + _size) % _size) == 1); + } + + assert(_events[back] == NULL); + _events[back] = ev; + _back = (back + 1) % _size; + whip(); +} + + +/** Process all events for a cycle. + * + * Executed events will be pushed to @a dest. + */ +void +QueuedEventSource::process(PostProcessor& dest, ProcessContext& context) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + Event* ev = NULL; + + /* Limit the maximum number of queued events to process per cycle. This + * makes the process callback (more) realtime-safe by preventing being + * choked by events coming in faster than they can be processed. + * FIXME: test this and figure out a good value */ + const unsigned int MAX_QUEUED_EVENTS = context.nframes() / 100; + + unsigned int num_events_processed = 0; + + /* FIXME: Merge these next two loops into one */ + + while ((ev = pop_earliest_queued_before(context.end()))) { + ev->execute(context); + dest.push(ev); + if (++num_events_processed > MAX_QUEUED_EVENTS) + break; + } + + while ((ev = pop_earliest_stamped_before(context.end()))) { + ev->execute(context); + dest.push(ev); + ++num_events_processed; + } + + if (_full_semaphore.has_waiter() && num_events_processed > 0) + _full_semaphore.post(); + + /*if (num_events_processed > 0) + dest.whip();*/ + //else + // cerr << "NO PROC: queued: " << unprepared_events() << ", stamped: " << !_stamped_queue.empty() << endl; +} + + +/** Pops the prepared event at the front of the prepare 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 QueuedEvents, but after this they are "normal" events and the + * engine deals with them just like a realtime in-band event. The engine will + * not use the timestamps of the returned events in any way, since it is free + * to execute these non-time-stamped events whenever it wants (at whatever rate + * it wants). + */ +Event* +QueuedEventSource::pop_earliest_queued_before(const SampleCount time) +{ + assert(ThreadManager::current_thread_id() == THREAD_PROCESS); + + const unsigned front = _front.get(); + QueuedEvent* const front_event = _events[front]; + + // Pop + if (front_event && front_event->is_prepared() && front_event->time() < time) { + _events[front] = NULL; + _front = (front + 1) % _size; + return front_event; + } else { + return NULL; + } +} + + +// Private // + + +/** Pre-process a single event */ +void +QueuedEventSource::_whipped() +{ + const unsigned prepared_back = _prepared_back.get(); + QueuedEvent* const ev = _events[prepared_back]; + if (!ev) + return; + + assert(!ev->is_prepared()); + ev->pre_process(); + assert(ev->is_prepared()); + + _prepared_back = (prepared_back + 1) % _size; + + // If event was blocking, wait for event to being run through the + // process thread before preparing the next event + if (ev->is_blocking()) + _blocking_semaphore.wait(); +} + + +} // namespace Ingen + diff --git a/src/engine/QueuedEventSource.hpp b/src/engine/QueuedEventSource.hpp new file mode 100644 index 00000000..6dea092d --- /dev/null +++ b/src/engine/QueuedEventSource.hpp @@ -0,0 +1,128 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QUEUEDEVENTSOURCE_H +#define QUEUEDEVENTSOURCE_H + +#include <cstdlib> +#include <pthread.h> +#include "types.hpp" +#include <raul/Semaphore.hpp> +#include <raul/AtomicInt.hpp> +#include <raul/SRSWQueue.hpp> +#include <raul/Slave.hpp> +#include "Event.hpp" +#include "EventSource.hpp" + +using Raul::AtomicInt; + +namespace Ingen { + +class QueuedEvent; +class PostProcessor; + + +/** 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). Creating an instance of this class spawns + * a pre-processing thread to prepare queued events. + * + * This class is it's own slave. :) + */ +class QueuedEventSource : public EventSource, protected Raul::Slave +{ +public: + QueuedEventSource(size_t queued_size, size_t stamped_size); + ~QueuedEventSource(); + + void activate() { Slave::start(); } + void deactivate() { Slave::stop(); } + + void process(PostProcessor& dest, ProcessContext& context); + + void unblock(); + +protected: + void push_queued(QueuedEvent* const ev); + inline void push_stamped(Event* const ev) { _stamped_queue.push(ev); } + Event* pop_earliest_queued_before(const SampleCount time); + inline Event* pop_earliest_stamped_before(const SampleCount time); + + inline bool unprepared_events() { return (_prepared_back.get() != _back.get()); } + + virtual void _whipped(); ///< Prepare 1 event + +private: + // Note that it's crucially important which functions access which of these + // variables, to maintain threadsafeness. + + //(FIXME: make this a separate class?) + // 2-part queue for events that require pre-processing: + AtomicInt _front; ///< Front of queue + AtomicInt _back; ///< Back of entire queue (1 past index of back element) + AtomicInt _prepared_back; ///< Back of prepared section (1 past index of back prepared element) + const size_t _size; + QueuedEvent** _events; + Raul::Semaphore _blocking_semaphore; + + Raul::Semaphore _full_semaphore; + + /** Queue for timestamped events (no pre-processing). */ + Raul::SRSWQueue<Event*> _stamped_queue; +}; + + +/** Pops the realtime (timestamped, not preprocessed) event off the realtime queue. + * + * Engine will use the sample timestamps of returned events directly and execute the + * event with sample accuracy. Timestamps in the past will be bumped forward to + * the beginning of the cycle (offset 0), when eg. skipped cycles occur. + */ +inline Event* +QueuedEventSource::pop_earliest_stamped_before(const SampleCount time) +{ + Event* ret = NULL; + + if (!_stamped_queue.empty()) { + if (_stamped_queue.front()->time() < time) { + ret = _stamped_queue.front(); + _stamped_queue.pop(); + } + } + + return ret; +} + + +/** Signal that the blocking event is finished. + * + * When this is called preparing will resume. This MUST be called by + * blocking events in their post_process() method. + */ +inline void +QueuedEventSource::unblock() +{ + _blocking_semaphore.post(); +} + + +} // namespace Ingen + +#endif // QUEUEDEVENTSOURCE_H diff --git a/src/engine/Responder.hpp b/src/engine/Responder.hpp new file mode 100644 index 00000000..80d2e24c --- /dev/null +++ b/src/engine/Responder.hpp @@ -0,0 +1,71 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RESPONDER_H +#define RESPONDER_H + +#include <inttypes.h> +#include <string> +#include <raul/SharedPtr.hpp> +#include "interface/ClientInterface.hpp" + +namespace Ingen { + + +/** Class to handle responding to clients. + * + * This is a glorified std::pair<ClientInterface*, int32_t> for replying + * to numbered messages from a client. + * + * For responses that involve more messages, the response will come first + * followed by the messages (eg object notifications, values, errors, etc.) + * in a bundle (or "transfer" if too large). + */ +class Responder +{ +public: + Responder(Shared::ClientInterface* client=0, int32_t id=1) + : _client(client) + , _id(id) + {} + + int32_t id() const { return _id; } + void set_id(int32_t id) { _id = id; } + + Shared::ClientInterface* client() const { return _client; } + void set_client(Shared::ClientInterface* client) { _client = client; } + + void respond_ok() { + if (_client) + _client->response_ok(_id); + } + + void respond_error(const std::string& msg) { + if (_client) + _client->response_error(_id, msg); + } + +private: + Shared::ClientInterface* _client; + int32_t _id; +}; + + +} // namespace Ingen + +#endif // RESPONDER_H + diff --git a/src/engine/ThreadManager.hpp b/src/engine/ThreadManager.hpp new file mode 100644 index 00000000..12f27000 --- /dev/null +++ b/src/engine/ThreadManager.hpp @@ -0,0 +1,43 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef THREADMANAGER_H +#define THREADMANAGER_H + +#include <raul/Thread.hpp> + +using Raul::Thread; + +namespace Ingen { + + +enum ThreadID { + THREAD_PRE_PROCESS, + THREAD_PROCESS, + THREAD_POST_PROCESS +}; + + +class ThreadManager { +public: + inline static ThreadID current_thread_id() { return (ThreadID)Thread::get().context(); } +}; + + +} // namespace Ingen + +#endif // THREADMANAGER_H diff --git a/src/engine/TransportNode.cpp b/src/engine/TransportNode.cpp new file mode 100644 index 00000000..3fc14a43 --- /dev/null +++ b/src/engine/TransportNode.cpp @@ -0,0 +1,154 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "TransportNode.hpp" +#include <jack/transport.h> +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "JackAudioDriver.hpp" +#include "PortImpl.hpp" +#include "util.hpp" +//#include "Engine.hpp" + +namespace Ingen { + + +TransportNode::TransportNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) +: NodeBase(new InternalPlugin(NS_INGEN "transport_node", "transport", "Transport Follower"), + path, false, parent, srate, buffer_size) +{ +#if 0 + _num_ports = 10; + _ports.alloc(_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); + _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); + _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); + _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); + _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); + _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); + _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); + _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); + _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); + _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); + _ports.at(9) = bar_trig_port; +#endif +} + + +void +TransportNode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); +#if 0 + + // FIXME: this will die horribly with any driver other than jack (in theory) + const jack_position_t* const position = ((JackAudioDriver*)Engine::instance().audio_driver())->position(); + jack_transport_state_t state = ((JackAudioDriver*)Engine::instance().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>*)_ports.at(0))->buffer(0)->set(spb, 0, 0); + ((OutputPort<Sample>*)_ports.at(1))->buffer(0)->set(bpb, 0, 0); + + // fill the trigger buffers with zeros + ((OutputPort<Sample>*)_ports.at(2))->buffer(0)->set(0.0f, 0, nframes - 1); + ((OutputPort<Sample>*)_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>*)_ports.at(2))->buffer(0)->set(1.0f, size_t(first_beat)); + if (first_beat_no % int(bpb) == 0) { + ((OutputPort<Sample>*)_ports.at(3))->buffer(0)->set(1.0f, size_t(first_beat)); + ++first_beat_no; + } + } + } + #endif + + NodeBase::post_process(context); +} + + +} // namespace Ingen + diff --git a/src/engine/TransportNode.hpp b/src/engine/TransportNode.hpp new file mode 100644 index 00000000..af0ed207 --- /dev/null +++ b/src/engine/TransportNode.hpp @@ -0,0 +1,45 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TRANSPORTNODE_H +#define TRANSPORTNODE_H + +#include <string> +#include <jack/transport.h> +#include "NodeBase.hpp" + +namespace Ingen { + + +/** 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 NodeBase +{ +public: + TransportNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + virtual void process(ProcessContext& context); +}; + + +} // namespace Ingen + +#endif // TRANSPORTNODE_H diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp new file mode 100644 index 00000000..45c17354 --- /dev/null +++ b/src/engine/engine.cpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include CONFIG_H_PATH + +#include <raul/Process.hpp> +#include "engine.hpp" +#include "Engine.hpp" +#include "QueuedEngineInterface.hpp" +#include "tuning.hpp" +#include "util.hpp" + +namespace Ingen { + +Engine* +new_engine(Ingen::Shared::World* world) +{ + set_denormal_flags(); + return new Engine(world); +} + + +bool +launch_osc_engine(int port) +{ + char port_str[6]; + snprintf(port_str, 6, "%u", port); + const string cmd = string("ingen -e --engine-port=").append(port_str); + + if (Raul::Process::launch(cmd)) { + return true; + //return SharedPtr<EngineInterface>(new OSCEngineSender( + // string("osc.udp://localhost:").append(port_str))); + } else { + std::cerr << "Failed to launch engine process." << std::endl; + //return SharedPtr<EngineInterface>(); + return false; + } +} + +} // namespace Ingen + diff --git a/src/engine/engine.hpp b/src/engine/engine.hpp new file mode 100644 index 00000000..93b426ac --- /dev/null +++ b/src/engine/engine.hpp @@ -0,0 +1,42 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INGEN_ENGINE_H +#define INGEN_ENGINE_H + +namespace Ingen { + +namespace Shared { class World; } + +class Engine; + +extern "C" { + + /** Create a new engine in this process */ + Engine* new_engine(Ingen::Shared::World* world); + + /** Launch an OSC engine as a completely separate process + * \return true if successful + */ + bool launch_osc_engine(int port); +} + + +} // namespace Ingen + +#endif // INGEN_ENGINE_H + diff --git a/src/engine/events.hpp b/src/engine/events.hpp new file mode 100644 index 00000000..14f5230c --- /dev/null +++ b/src/engine/events.hpp @@ -0,0 +1,53 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVENTS_H +#define EVENTS_H + +#include CONFIG_H_PATH + +#include "AllNotesOffEvent.hpp" +#include "ClearPatchEvent.hpp" +#include "ConnectionEvent.hpp" +#include "CreateNodeEvent.hpp" +#include "CreatePatchEvent.hpp" +#include "CreatePortEvent.hpp" +#include "DeactivateEvent.hpp" +#include "DestroyEvent.hpp" +#include "DisconnectAllEvent.hpp" +#include "DisconnectionEvent.hpp" +#include "EnablePatchEvent.hpp" +#include "LoadPluginsEvent.hpp" +#include "MidiLearnEvent.hpp" +#include "NoteEvent.hpp" +#include "PingQueuedEvent.hpp" +#include "RegisterClientEvent.hpp" +#include "RenameEvent.hpp" +#include "RequestAllObjectsEvent.hpp" +#include "RequestMetadataEvent.hpp" +#include "RequestObjectEvent.hpp" +#include "RequestPluginEvent.hpp" +#include "RequestPluginsEvent.hpp" +#include "RequestPortValueEvent.hpp" +#include "SetMetadataEvent.hpp" +#include "SetPolyphonicEvent.hpp" +#include "SetPolyphonyEvent.hpp" +#include "SetPortValueEvent.hpp" +#include "UnregisterClientEvent.hpp" + +#endif // EVENTS_H + diff --git a/src/engine/events/AllNotesOffEvent.cpp b/src/engine/events/AllNotesOffEvent.cpp new file mode 100644 index 00000000..fcb68b31 --- /dev/null +++ b/src/engine/events/AllNotesOffEvent.cpp @@ -0,0 +1,71 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "AllNotesOffEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "module/World.hpp" +#include "shared/Store.hpp" + +namespace Ingen { + + +/** Note off with patch explicitly passed - triggered by MIDI. + */ +AllNotesOffEvent::AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PatchImpl* patch) +: Event(engine, responder, timestamp), + _patch(patch) +{ +} + + +/** Note off event with lookup - triggered by OSC. + */ +AllNotesOffEvent::AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path) +: Event(engine, responder, timestamp), + _patch_path(patch_path), + _patch(NULL) +{ +} + + +void +AllNotesOffEvent::execute(ProcessContext& context) +{ + Event::execute(context); + + if (_patch == NULL && _patch_path != "") + _patch = _engine.engine_store()->find_patch(_patch_path); + + //if (_patch != NULL) + // for (Raul::List<MidiInNode*>::iterator j = _patch->midi_in_nodes().begin(); j != _patch->midi_in_nodes().end(); ++j) + // (*j)->all_notes_off(offset); +} + + +void +AllNotesOffEvent::post_process() +{ + if (_patch != NULL) + _responder->respond_ok(); +} + + +} // namespace Ingen + + diff --git a/src/engine/events/AllNotesOffEvent.hpp b/src/engine/events/AllNotesOffEvent.hpp new file mode 100644 index 00000000..3e4d56b3 --- /dev/null +++ b/src/engine/events/AllNotesOffEvent.hpp @@ -0,0 +1,51 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ALLNOTESOFFEVENT_H +#define ALLNOTESOFFEVENT_H + +#include "Event.hpp" +#include <string> +using std::string; + +namespace Ingen { + +class PatchImpl; + + +/** A note off event for all active voices. + * + * \ingroup engine + */ +class AllNotesOffEvent : public Event +{ +public: + AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PatchImpl* patch); + AllNotesOffEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path); + + void execute(ProcessContext& context); + void post_process(); + +private: + const string _patch_path; + PatchImpl* _patch; +}; + + +} // namespace Ingen + +#endif // ALLNOTESOFFEVENT_H diff --git a/src/engine/events/ClearPatchEvent.cpp b/src/engine/events/ClearPatchEvent.cpp new file mode 100644 index 00000000..c1fb0749 --- /dev/null +++ b/src/engine/events/ClearPatchEvent.cpp @@ -0,0 +1,133 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include "ClearPatchEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "util.hpp" +#include "EngineStore.hpp" +#include "PortImpl.hpp" +#include "NodeImpl.hpp" +#include "ConnectionImpl.hpp" +#include "QueuedEventSource.hpp" +#include "AudioDriver.hpp" +#include "MidiDriver.hpp" + +namespace Ingen { + + +ClearPatchEvent::ClearPatchEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path) + : QueuedEvent(engine, responder, time, true, source) + , _patch_path(patch_path) + , _driver_port(NULL) + , _process(false) + , _ports_array(NULL) + , _compiled_patch(NULL) +{ +} + + +void +ClearPatchEvent::pre_process() +{ + EngineStore::Objects::iterator patch_iterator = _engine.engine_store()->find(_patch_path); + + if (patch_iterator != _engine.engine_store()->end()) { + _patch = PtrCast<PatchImpl>(patch_iterator->second); + if (_patch) { + _process = _patch->enabled(); + _removed_table = _engine.engine_store()->remove_children(patch_iterator); + _patch->nodes().clear(); + _patch->connections().clear(); + _ports_array = _patch->build_ports_array(); + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + } + } + + QueuedEvent::pre_process(); +} + + +void +ClearPatchEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch && _removed_table) { + _patch->disable(); + + if (_patch->compiled_patch() != NULL) { + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(NULL); + } + + _patch->clear_ports(); + _patch->connections().clear(); + _patch->compiled_patch(_compiled_patch); + Raul::Array<PortImpl*>* old_ports = _patch->external_ports(); + _patch->external_ports(_ports_array); + _ports_array = old_ports; + + // Remove driver ports, if necessary + if (_patch->parent() == NULL) { + for (EngineStore::Objects::iterator i = _removed_table->begin(); i != _removed_table->end(); ++i) { + SharedPtr<PortImpl> port = PtrCast<PortImpl>(i->second); + if (port && port->type() == DataType::AUDIO) + _driver_port = _engine.audio_driver()->remove_port(port->path()); + else if (port && port->type() == DataType::EVENT) + _driver_port = _engine.midi_driver()->remove_port(port->path()); + } + } + } +} + + +void +ClearPatchEvent::post_process() +{ + if (_patch != NULL) { + delete _ports_array; + delete _driver_port; + + // Restore patch's run state + if (_process) + _patch->enable(); + else + _patch->disable(); + + // Make sure everything's sane + assert(_patch->nodes().size() == 0); + assert(_patch->num_ports() == 0); + assert(_patch->connections().size() == 0); + + // Reply + _responder->respond_ok(); + _engine.broadcaster()->send_patch_cleared(_patch_path); + } else { + _responder->respond_error(string("Patch ") + _patch_path + " not found"); + } + + _source->unblock(); // FIXME: can be done earlier in execute? +} + + +} // namespace Ingen + diff --git a/src/engine/events/ClearPatchEvent.hpp b/src/engine/events/ClearPatchEvent.hpp new file mode 100644 index 00000000..803721fb --- /dev/null +++ b/src/engine/events/ClearPatchEvent.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CLEARPATCHEVENT_H +#define CLEARPATCHEVENT_H + +#include <string> +#include <raul/Array.hpp> +#include <raul/Table.hpp> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" + +using std::string; + +namespace Ingen { + +class PatchImpl; +class DriverPort; + + +/** Delete all nodes from a patch. + * + * \ingroup engine + */ +class ClearPatchEvent : public QueuedEvent +{ +public: + ClearPatchEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _patch_path; + SharedPtr<PatchImpl> _patch; + DriverPort* _driver_port; + bool _process; + Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports for Patch + CompiledPatch* _compiled_patch; ///< Patch's new process order + + SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > _removed_table; +}; + + +} // namespace Ingen + + +#endif // CLEARPATCHEVENT_H diff --git a/src/engine/events/ConnectionEvent.cpp b/src/engine/events/ConnectionEvent.cpp new file mode 100644 index 00000000..24b80dee --- /dev/null +++ b/src/engine/events/ConnectionEvent.cpp @@ -0,0 +1,202 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <string> +#include <boost/format.hpp> +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include "ClientBroadcaster.hpp" +#include "ConnectionEvent.hpp" +#include "ConnectionImpl.hpp" +#include "Engine.hpp" +#include "InputPort.hpp" +#include "EngineStore.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "Responder.hpp" +#include "types.hpp" + +using std::string; +namespace Ingen { + + +ConnectionEvent::ConnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path) +: QueuedEvent(engine, responder, timestamp), + _src_port_path(src_port_path), + _dst_port_path(dst_port_path), + _patch(NULL), + _src_port(NULL), + _dst_port(NULL), + _compiled_patch(NULL), + _patch_listnode(NULL), + _port_listnode(NULL), + _error(NO_ERROR) +{ +} + + +void +ConnectionEvent::pre_process() +{ + if (_src_port_path.parent().parent() != _dst_port_path.parent().parent() + && _src_port_path.parent() != _dst_port_path.parent().parent() + && _src_port_path.parent().parent() != _dst_port_path.parent()) { + _error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + _src_port = _engine.engine_store()->find_port(_src_port_path); + _dst_port = _engine.engine_store()->find_port(_dst_port_path); + + if (_src_port == NULL || _dst_port == NULL) { + _error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if ( ! (_src_port->type() == _dst_port->type() + || ( (_src_port->type() == DataType::CONTROL || _src_port->type() == DataType::AUDIO) + && (_dst_port->type() == DataType::CONTROL || _dst_port->type() == DataType::AUDIO) ))) { + _error = TYPE_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + _dst_input_port = dynamic_cast<InputPort*>(_dst_port); + _src_output_port = dynamic_cast<OutputPort*>(_src_port); + + if (!_dst_input_port || !_src_output_port) { + _error = DIRECTION_MISMATCH; + QueuedEvent::pre_process(); + return; + } + + NodeImpl* const src_node = _src_port->parent_node(); + NodeImpl* const dst_node = _dst_port->parent_node(); + + // Connection to a patch port from inside the patch + if (src_node->parent_patch() != dst_node->parent_patch()) { + + assert(src_node->parent() == dst_node || dst_node->parent() == src_node); + if (src_node->parent() == dst_node) + _patch = dynamic_cast<PatchImpl*>(dst_node); + else + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Connection from a patch input to a patch output (pass through) + } else if (src_node == dst_node && dynamic_cast<PatchImpl*>(src_node)) { + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Normal connection between nodes with the same parent + } else { + _patch = src_node->parent_patch(); + } + + assert(_patch); + + //if (_dst_input_port->is_connected_to(_src_output_port)) { + if (_patch->has_connection(_src_output_port, _dst_input_port)) { + _error = ALREADY_CONNECTED; + QueuedEvent::pre_process(); + return; + } + + if (src_node == NULL || dst_node == NULL) { + _error = PARENTS_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (_patch != src_node && src_node->parent() != _patch && dst_node->parent() != _patch) { + _error = PARENTS_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + _connection = SharedPtr<ConnectionImpl>(new ConnectionImpl(_src_port, _dst_port)); + _patch_listnode = new PatchImpl::Connections::Node(_connection); + _port_listnode = new InputPort::Connections::Node(_connection); + + // Need to be careful about patch port connections here and adding a node's + // parent as a dependant/provider, or adding a patch as it's own provider... + if (src_node != dst_node && src_node->parent() == dst_node->parent()) { + dst_node->providers()->push_back(new Raul::List<NodeImpl*>::Node(src_node)); + src_node->dependants()->push_back(new Raul::List<NodeImpl*>::Node(dst_node)); + } + + _patch->add_connection(_patch_listnode); + + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + + QueuedEvent::pre_process(); +} + + +void +ConnectionEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + // This must be inserted here, since they're actually used by the audio thread + _dst_input_port->add_connection(_port_listnode); + if (_patch->compiled_patch() != NULL) + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } +} + + +void +ConnectionEvent::post_process() +{ + std::ostringstream ss; + if (_error == NO_ERROR) { + _responder->respond_ok(); + _engine.broadcaster()->send_connection(_connection); + return; + } + + ss << boost::format("Unable to make connection %1% -> %2% (") % _src_port_path % _dst_port_path; + + switch (_error) { + case PARENT_PATCH_DIFFERENT: + ss << "Ports have mismatched parents"; break; + case PORT_NOT_FOUND: + ss << "Port not found"; break; + case TYPE_MISMATCH: + ss << "Type mismatch"; break; + case DIRECTION_MISMATCH: + ss << "Direction mismatch"; break; + case ALREADY_CONNECTED: + ss << "Already connected"; break; + case PARENTS_NOT_FOUND: + ss << "Parents not found"; break; + default: + ss << "Unknown error"; + } + ss << ")"; + _responder->respond_error(ss.str()); +} + + +} // namespace Ingen + diff --git a/src/engine/events/ConnectionEvent.hpp b/src/engine/events/ConnectionEvent.hpp new file mode 100644 index 00000000..89407957 --- /dev/null +++ b/src/engine/events/ConnectionEvent.hpp @@ -0,0 +1,92 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CONNECTIONEVENT_H +#define CONNECTIONEVENT_H + +#include <string> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" +#include "PatchImpl.hpp" +#include "InputPort.hpp" +#include "types.hpp" +using std::string; + +namespace Raul { + template <typename T> class ListNode; + template <typename T> class Array; +} + +namespace Ingen { + +class PatchImpl; +class NodeImpl; +class ConnectionImpl; +class MidiMessage; +class PortImpl; +class InputPort; +class OutputPort; +class CompiledPatch; + + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class ConnectionEvent : public QueuedEvent +{ +public: + ConnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + + enum ErrorType { + NO_ERROR, + PARENT_PATCH_DIFFERENT, + PORT_NOT_FOUND, + TYPE_MISMATCH, + DIRECTION_MISMATCH, + ALREADY_CONNECTED, + PARENTS_NOT_FOUND + }; + + Raul::Path _src_port_path; + Raul::Path _dst_port_path; + + PatchImpl* _patch; + PortImpl* _src_port; + PortImpl* _dst_port; + OutputPort* _src_output_port; + InputPort* _dst_input_port; + + CompiledPatch* _compiled_patch; ///< New process order for Patch + + SharedPtr<ConnectionImpl> _connection; + PatchImpl::Connections::Node* _patch_listnode; + InputPort::Connections::Node* _port_listnode; + + ErrorType _error; +}; + + +} // namespace Ingen + +#endif // CONNECTIONEVENT_H diff --git a/src/engine/events/CreateNodeEvent.cpp b/src/engine/events/CreateNodeEvent.cpp new file mode 100644 index 00000000..b58bc2c3 --- /dev/null +++ b/src/engine/events/CreateNodeEvent.cpp @@ -0,0 +1,151 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include <raul/Path.hpp> +#include <redlandmm/World.hpp> +#include "module/World.hpp" +#include "CreateNodeEvent.hpp" +#include "Responder.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "NodeFactory.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" +#include "PortImpl.hpp" +#include "AudioDriver.hpp" + +namespace Ingen { + + +CreateNodeEvent::CreateNodeEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, + const string& plugin_uri, bool polyphonic) +: QueuedEvent(engine, responder, timestamp), + _path(path), + _plugin_uri(plugin_uri), + _polyphonic(polyphonic), + _patch(NULL), + _node(NULL), + _compiled_patch(NULL), + _node_already_exists(false) +{ +} + + +/** DEPRECATED: Construct from type, library name, and plugin label. + * + * Do not use. + */ +CreateNodeEvent::CreateNodeEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, + const string& plugin_type, const string& plugin_lib, const string& plugin_label, bool polyphonic) +: QueuedEvent(engine, responder, timestamp), + _path(path), + _plugin_type(plugin_type), + _plugin_lib(plugin_lib), + _plugin_label(plugin_label), + _polyphonic(polyphonic), + _patch(NULL), + _node(NULL), + _compiled_patch(NULL), + _node_already_exists(false) +{ +} + + +void +CreateNodeEvent::pre_process() +{ + if (_engine.engine_store()->find_object(_path) != NULL) { + _node_already_exists = true; + QueuedEvent::pre_process(); + return; + } + + _patch = _engine.engine_store()->find_patch(_path.parent()); + + PluginImpl* const plugin = (_plugin_uri != "") + ? _engine.node_factory()->plugin(_plugin_uri) + : _engine.node_factory()->plugin(_plugin_type, _plugin_lib, _plugin_label); + + if (_patch && plugin) { + + _node = plugin->instantiate(_path.name(), _polyphonic, _patch, _engine); + + if (_node != NULL) { + _node->activate(); + + // This can be done here because the audio thread doesn't touch the + // node tree - just the process order array + _patch->add_node(new PatchImpl::Nodes::Node(_node)); + //_node->add_to_store(_engine.engine_store()); + _engine.engine_store()->add(_node); + + // FIXME: not really necessary to build process order since it's not connected, + // just append to the list + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + } + } + QueuedEvent::pre_process(); +} + + +void +CreateNodeEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_node != NULL) { + if (_patch->compiled_patch() != NULL) + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } +} + + +void +CreateNodeEvent::post_process() +{ + string msg; + if (_node_already_exists) { + msg = string("Could not create node - ").append(_path);// + " already exists."; + _responder->respond_error(msg); + } else if (_patch == NULL) { + msg = "Could not find patch '" + _path.parent() +"' for add_node."; + _responder->respond_error(msg); + } else if (_node == NULL) { + msg = "Unable to load node "; + msg += _path + " (you're missing the plugin "; + if (_plugin_uri != "") + msg += _plugin_uri; + else + msg += _plugin_lib + ":" + _plugin_label + " (" + _plugin_type + ")"; + msg += ")"; + _responder->respond_error(msg); + } else { + _responder->respond_ok(); + _engine.broadcaster()->send_node(_node, true); // yes, send ports + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/CreateNodeEvent.hpp b/src/engine/events/CreateNodeEvent.hpp new file mode 100644 index 00000000..c3ef6313 --- /dev/null +++ b/src/engine/events/CreateNodeEvent.hpp @@ -0,0 +1,81 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CREATENODEEVENT_H +#define CREATENODEEVENT_H + +#include "QueuedEvent.hpp" +#include <raul/Path.hpp> +#include <string> +using std::string; + +namespace Raul { template <typename T> class Array; } +template<typename T> class TreeNode; + +namespace Ingen { + +class PatchImpl; +class NodeImpl; +class CompiledPatch; + + +/** An event to load a Node and insert it into a Patch. + * + * \ingroup engine + */ +class CreateNodeEvent : public QueuedEvent +{ +public: + CreateNodeEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& node_path, + const string& plugin_uri, + bool poly); + + // DEPRECATED + CreateNodeEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& node_path, + const string& plugin_type, + const string& lib_name, + const string& plugin_label, + bool poly); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + string _patch_name; + Raul::Path _path; + string _plugin_uri; ///< If nonempty then type, library, label, are ignored + string _plugin_type; + string _plugin_lib; + string _plugin_label; + bool _polyphonic; + PatchImpl* _patch; + NodeImpl* _node; + CompiledPatch* _compiled_patch; ///< Patch's new process order + bool _node_already_exists; +}; + + +} // namespace Ingen + +#endif // CREATENODEEVENT_H diff --git a/src/engine/events/CreatePatchEvent.cpp b/src/engine/events/CreatePatchEvent.cpp new file mode 100644 index 00000000..64fe5c63 --- /dev/null +++ b/src/engine/events/CreatePatchEvent.cpp @@ -0,0 +1,157 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include "CreatePatchEvent.hpp" +#include "Responder.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PluginImpl.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" +#include "AudioDriver.hpp" +#include "EngineStore.hpp" + +namespace Ingen { + + +CreatePatchEvent::CreatePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, int poly) +: QueuedEvent(engine, responder, timestamp), + _path(path), + _patch(NULL), + _parent(NULL), + _compiled_patch(NULL), + _poly(poly), + _error(NO_ERROR) +{ +} + + +void +CreatePatchEvent::pre_process() +{ + if (!Path::is_valid(_path)) { + _error = INVALID_PATH; + QueuedEvent::pre_process(); + return; + } + + if (_path == "/" || _engine.engine_store()->find_object(_path) != NULL) { + _error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + if (_poly < 1) { + _error = INVALID_POLY; + QueuedEvent::pre_process(); + return; + } + + const Path& path = (const Path&)_path; + + _parent = _engine.engine_store()->find_patch(path.parent()); + if (_parent == NULL) { + _error = PARENT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + uint32_t poly = 1; + if (_parent != NULL && _poly > 1 && _poly == static_cast<int>(_parent->internal_polyphony())) + poly = _poly; + + _patch = new PatchImpl(_engine, path.name(), poly, _parent, _engine.audio_driver()->sample_rate(), _engine.audio_driver()->buffer_size(), _poly); + + if (_parent != NULL) { + _parent->add_node(new PatchImpl::Nodes::Node(_patch)); + + if (_parent->enabled()) + _compiled_patch = _parent->compile(); + } + + _patch->activate(); + + // Insert into EngineStore + //_patch->add_to_store(_engine.engine_store()); + _engine.engine_store()->add(_patch); + + QueuedEvent::pre_process(); +} + + +void +CreatePatchEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch != NULL) { + if (_parent == NULL) { + assert(_path == "/"); + assert(_patch->parent_patch() == NULL); + _engine.audio_driver()->set_root_patch(_patch); + } else { + assert(_parent != NULL); + assert(_path != "/"); + + if (_parent->compiled_patch() != NULL) + _engine.maid()->push(_parent->compiled_patch()); + _parent->compiled_patch(_compiled_patch); + } + } +} + + +void +CreatePatchEvent::post_process() +{ + if (_responder.get()) { + if (_error == NO_ERROR) { + + _responder->respond_ok(); + + // Don't send ports/nodes that have been added since prepare() + // (otherwise they would be sent twice) + _engine.broadcaster()->send_patch(_patch, false); + + } else if (_error == INVALID_PATH) { + string msg = "Attempt to create patch with illegal path "; + msg.append(_path); + _responder->respond_error(msg); + } else if (_error == OBJECT_EXISTS) { + _responder->respond_ok(); + /*string msg = "Unable to create patch: "; + msg.append(_path).append(" already exists."); + _responder->respond_error(msg);*/ + } else if (_error == PARENT_NOT_FOUND) { + string msg = "Unable to create patch: Parent "; + msg.append(Path(_path).parent()).append(" not found."); + _responder->respond_error(msg); + } else if (_error == INVALID_POLY) { + string msg = "Unable to create patch "; + msg.append(_path).append(": ").append("Invalid polyphony respondered."); + _responder->respond_error(msg); + } else { + _responder->respond_error("Unable to load patch."); + } + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/CreatePatchEvent.hpp b/src/engine/events/CreatePatchEvent.hpp new file mode 100644 index 00000000..733aba17 --- /dev/null +++ b/src/engine/events/CreatePatchEvent.hpp @@ -0,0 +1,64 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CREATEPATCHEVENT_H +#define CREATEPATCHEVENT_H + +#include <string> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" + +using std::string; + +namespace Raul { template<typename T> class Array; } +template<typename T> class TreeNode; + +namespace Ingen { + +class PatchImpl; +class CompiledPatch; + + +/** Creates a new Patch. + * + * \ingroup engine + */ +class CreatePatchEvent : public QueuedEvent +{ +public: + CreatePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, int poly); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { NO_ERROR, OBJECT_EXISTS, PARENT_NOT_FOUND, INVALID_POLY, INVALID_PATH }; + + const std::string _path; + PatchImpl* _patch; + PatchImpl* _parent; + CompiledPatch* _compiled_patch; + int _poly; + ErrorType _error; +}; + + +} // namespace Ingen + + +#endif // CREATEPATCHEVENT_H diff --git a/src/engine/events/CreatePortEvent.cpp b/src/engine/events/CreatePortEvent.cpp new file mode 100644 index 00000000..e767f522 --- /dev/null +++ b/src/engine/events/CreatePortEvent.cpp @@ -0,0 +1,173 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Path.hpp> +#include <raul/Array.hpp> +#include <raul/List.hpp> +#include <raul/Maid.hpp> +#include "Responder.hpp" +#include "CreatePortEvent.hpp" +#include "PatchImpl.hpp" +#include "PluginImpl.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "QueuedEventSource.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "PortImpl.hpp" +#include "AudioDriver.hpp" +#include "MidiDriver.hpp" +#include "OSCDriver.hpp" +#include "DuplexPort.hpp" + +namespace Ingen { + + +CreatePortEvent::CreatePortEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& path, + const string& type, + bool is_output, + QueuedEventSource* source) +: QueuedEvent(engine, responder, timestamp, true, source), + _error(NO_ERROR), + _path(path), + _type(type), + _is_output(is_output), + _data_type(type), + _patch(NULL), + _patch_port(NULL), + _driver_port(NULL) +{ + /* This is blocking because of the two different sets of Patch ports, the array used in the + * audio thread (inherited from NodeBase), and the arrays used in the pre processor thread. + * If two add port events arrive in the same cycle and the second pre processes before the + * first executes, bad things happen (ports are lost). + * + * FIXME: fix this using RCU + */ + + if (_data_type == DataType::UNKNOWN) { + cerr << "[CreatePortEvent] Unknown port type " << type << endl; + _error = UNKNOWN_TYPE; + } +} + + +void +CreatePortEvent::pre_process() +{ + if (_error == UNKNOWN_TYPE || _engine.engine_store()->find_object(_path)) { + QueuedEvent::pre_process(); + return; + } + + // FIXME: this is just a mess :/ + + _patch = _engine.engine_store()->find_patch(_path.parent()); + + if (_patch != NULL) { + assert(_patch->path() == _path.parent()); + + size_t buffer_size = 1; + if (_type != "ingen:Float") + buffer_size = _engine.audio_driver()->buffer_size(); + + const uint32_t old_num_ports = _patch->num_ports(); + + _patch_port = _patch->create_port(_path.name(), _data_type, buffer_size, _is_output); + + if (_patch_port) { + + if (_is_output) + _patch->add_output(new Raul::List<PortImpl*>::Node(_patch_port)); + else + _patch->add_input(new Raul::List<PortImpl*>::Node(_patch_port)); + + if (_patch->external_ports()) + _ports_array = new Raul::Array<PortImpl*>(old_num_ports + 1, *_patch->external_ports()); + else + _ports_array = new Raul::Array<PortImpl*>(old_num_ports + 1, NULL); + + + _ports_array->at(_patch->num_ports()-1) = _patch_port; + //_patch_port->add_to_store(_engine.engine_store()); + _engine.engine_store()->add(_patch_port); + + if (!_patch->parent()) { + if (_type == "ingen:AudioPort") + _driver_port = _engine.audio_driver()->create_port( + dynamic_cast<DuplexPort*>(_patch_port)); + else if (_type == "ingen:MIDIPort" || _type == "ingen:EventPort") + _driver_port = _engine.midi_driver()->create_port( + dynamic_cast<DuplexPort*>(_patch_port)); + else if (_type == "ingen:OSCPort" && _engine.osc_driver()) + _driver_port = _engine.osc_driver()->create_port( + dynamic_cast<DuplexPort*>(_patch_port)); + } + + assert(_ports_array->size() == _patch->num_ports()); + + } + } + QueuedEvent::pre_process(); +} + + +void +CreatePortEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch_port) { + + _engine.maid()->push(_patch->external_ports()); + //_patch->add_port(_port); + + _patch->external_ports(_ports_array); + } + + if (_driver_port) { + if (_type == "ingen:AudioPort") + _engine.audio_driver()->add_port(_driver_port); + else if (_type == "ingen:MIDIPort" || _type == "ingen:EventPort") + _engine.midi_driver()->add_port(_driver_port); + else if (_type == "ingen:OSCPort") + cerr << "OSC DRIVER PORT" << endl; + } + + if (_source) + _source->unblock(); +} + + +void +CreatePortEvent::post_process() +{ + if (_error != NO_ERROR || !_patch_port) { + const string msg = string("Could not create port - ").append(_path); + _responder->respond_error(msg); + } else { + _responder->respond_ok(); + _engine.broadcaster()->send_port(_patch_port); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/CreatePortEvent.hpp b/src/engine/events/CreatePortEvent.hpp new file mode 100644 index 00000000..5ddd8aa3 --- /dev/null +++ b/src/engine/events/CreatePortEvent.hpp @@ -0,0 +1,72 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CREATEPORTEVENT_H +#define CREATEPORTEVENT_H + +#include "QueuedEvent.hpp" +#include <raul/Path.hpp> +#include <raul/Array.hpp> +#include "interface/DataType.hpp" +#include <string> +using std::string; + +template <typename T> class Array; + +namespace Ingen { + +class PatchImpl; +class PortImpl; +class DriverPort; + + +/** An event to add a Port to a Patch. + * + * \ingroup engine + */ +class CreatePortEvent : public QueuedEvent +{ +public: + CreatePortEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& type, bool is_output, QueuedEventSource* source); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + + enum ErrorType { + NO_ERROR, + UNKNOWN_TYPE + }; + + ErrorType _error; + Raul::Path _path; + string _type; + bool _is_output; + DataType _data_type; + PatchImpl* _patch; + PortImpl* _patch_port; + Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports array for Patch + DriverPort* _driver_port; ///< Driver (eg Jack) port if this is a toplevel port + bool _succeeded; +}; + + +} // namespace Ingen + +#endif // CREATEPORTEVENT_H diff --git a/src/engine/events/DeactivateEvent.cpp b/src/engine/events/DeactivateEvent.cpp new file mode 100644 index 00000000..a68419f0 --- /dev/null +++ b/src/engine/events/DeactivateEvent.cpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "DeactivateEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" + +namespace Ingen { + + +DeactivateEvent::DeactivateEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp) +: QueuedEvent(engine, responder, timestamp) +{ +} + + +void +DeactivateEvent::pre_process() +{ + QueuedEvent::pre_process(); +} + + +void +DeactivateEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); +} + + +void +DeactivateEvent::post_process() +{ + _responder->respond_ok(); + _engine.deactivate(); +} + + +} // namespace Ingen + diff --git a/src/engine/events/DeactivateEvent.hpp b/src/engine/events/DeactivateEvent.hpp new file mode 100644 index 00000000..5a6750ba --- /dev/null +++ b/src/engine/events/DeactivateEvent.hpp @@ -0,0 +1,43 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DEACTIVATEEVENT_H +#define DEACTIVATEEVENT_H + +#include "QueuedEvent.hpp" + +namespace Ingen { + + +/** Deactivates the engine. + * + * \ingroup engine + */ +class DeactivateEvent : public QueuedEvent +{ +public: + DeactivateEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); +}; + + +} // namespace Ingen + +#endif // DEACTIVATEEVENT_H diff --git a/src/engine/events/DestroyEvent.cpp b/src/engine/events/DestroyEvent.cpp new file mode 100644 index 00000000..07770c0a --- /dev/null +++ b/src/engine/events/DestroyEvent.cpp @@ -0,0 +1,201 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include "DestroyEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "NodeBase.hpp" +#include "PluginImpl.hpp" +#include "AudioDriver.hpp" +#include "MidiDriver.hpp" +#include "DisconnectAllEvent.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" +#include "QueuedEventSource.hpp" +#include "PortImpl.hpp" + +namespace Ingen { + + +DestroyEvent::DestroyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& path, bool block) + : QueuedEvent(engine, responder, time, source, source) + , _path(path) + , _store_iterator(engine.engine_store()->end()) + , _driver_port(NULL) + , _patch_node_listnode(NULL) + , _patch_port_listnode(NULL) + , _ports_array(NULL) + , _compiled_patch(NULL) + , _disconnect_event(NULL) +{ + assert(_source); +} + + +DestroyEvent::~DestroyEvent() +{ + delete _disconnect_event; +} + + +void +DestroyEvent::pre_process() +{ + _store_iterator = _engine.engine_store()->find(_path); + + if (_store_iterator != _engine.engine_store()->end()) { + _node = PtrCast<NodeImpl>(_store_iterator->second); + + if (!_node) + _port = PtrCast<PortImpl>(_store_iterator->second); + } + + if (_store_iterator != _engine.engine_store()->end()) { + _removed_table = _engine.engine_store()->remove(_store_iterator); + } + + if (_node != NULL && _path != "/") { + assert(_node->parent_patch()); + _patch_node_listnode = _node->parent_patch()->remove_node(_path.name()); + if (_patch_node_listnode) { + assert(_patch_node_listnode->elem() == _node.get()); + + _disconnect_event = new DisconnectAllEvent(_engine, _node->parent_patch(), _node.get()); + _disconnect_event->pre_process(); + + if (_node->parent_patch()->enabled()) { + // FIXME: is this called multiple times? + _compiled_patch = _node->parent_patch()->compile(); +#ifndef NDEBUG + // Be sure node is removed from process order, so it can be destroyed + for (size_t i=0; i < _compiled_patch->size(); ++i) { + assert(_compiled_patch->at(i).node() != _node.get()); + // FIXME: check providers/dependants too + } +#endif + } + } + } else if (_port) { + assert(_port->parent_patch()); + _patch_port_listnode = _port->parent_patch()->remove_port(_path.name()); + if (_patch_port_listnode) { + assert(_patch_port_listnode->elem() == _port.get()); + + _disconnect_event = new DisconnectAllEvent(_engine, _port->parent_patch(), _port.get()); + _disconnect_event->pre_process(); + + if (_port->parent_patch()->enabled()) { + // FIXME: is this called multiple times? + _compiled_patch = _port->parent_patch()->compile(); + _ports_array = _port->parent_patch()->build_ports_array(); + assert(_ports_array->size() == _port->parent_patch()->num_ports()); + } + } + + } + + QueuedEvent::pre_process(); +} + + +void +DestroyEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch_node_listnode) { + assert(_node); + + if (_disconnect_event) + _disconnect_event->execute(context); + + if (_node->parent_patch()->compiled_patch()) + _engine.maid()->push(_node->parent_patch()->compiled_patch()); + _node->parent_patch()->compiled_patch(_compiled_patch); + + } else if (_patch_port_listnode) { + assert(_port); + + if (_disconnect_event) + _disconnect_event->execute(context); + + if (_port->parent_patch()->compiled_patch()) + _engine.maid()->push(_port->parent_patch()->compiled_patch()); + + _port->parent_patch()->compiled_patch(_compiled_patch); + + if (_port->parent_patch()->external_ports()) + _engine.maid()->push(_port->parent_patch()->external_ports()); + + _port->parent_patch()->external_ports(_ports_array); + + if ( ! _port->parent_patch()->parent()) { + if (_port->type() == DataType::AUDIO) + _driver_port = _engine.audio_driver()->remove_port(_port->path()); + else if (_port->type() == DataType::EVENT) + _driver_port = _engine.midi_driver()->remove_port(_port->path()); + } + } + + if (_source) + _source->unblock(); +} + + +void +DestroyEvent::post_process() +{ + if (!_node && !_port) { + if (_path == "/") { + _responder->respond_error("You can not destroy the root patch (/)"); + } else { + string msg = string("Could not find object ") + _path + " to destroy"; + _responder->respond_error(msg); + } + } + + if (_patch_node_listnode) { + assert(_node); + _node->deactivate(); + _responder->respond_ok(); + _engine.broadcaster()->bundle_begin(); + if (_disconnect_event) + _disconnect_event->post_process(); + _engine.broadcaster()->send_destroyed(_path); + _engine.broadcaster()->bundle_end(); + _engine.maid()->push(_patch_node_listnode); + } else if (_patch_port_listnode) { + assert(_port); + _responder->respond_ok(); + _engine.broadcaster()->bundle_begin(); + if (_disconnect_event) + _disconnect_event->post_process(); + _engine.broadcaster()->send_destroyed(_path); + _engine.broadcaster()->bundle_end(); + _engine.maid()->push(_patch_port_listnode); + } else { + _responder->respond_error("Unable to destroy object"); + } + + delete _driver_port; +} + + +} // namespace Ingen diff --git a/src/engine/events/DestroyEvent.hpp b/src/engine/events/DestroyEvent.hpp new file mode 100644 index 00000000..b24934f8 --- /dev/null +++ b/src/engine/events/DestroyEvent.hpp @@ -0,0 +1,77 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DESTROYEVENT_H +#define DESTROYEVENT_H + +#include <string> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" + +using std::string; + +namespace Raul { + template<typename T> class Array; + template<typename T> class ListNode; +} +template<typename T> class TreeNode; + +namespace Ingen { + +class GraphObjectImpl; +class NodeImpl; +class PortImpl; +class DriverPort; +class DisconnectAllEvent; +class CompiledPatch; + + +/** An event to remove and delete a Node. + * + * \ingroup engine + */ +class DestroyEvent : public QueuedEvent +{ +public: + DestroyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime timestamp, QueuedEventSource* source, const string& path, bool block = true); + ~DestroyEvent(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + Path _path; + EngineStore::iterator _store_iterator; + SharedPtr<NodeImpl> _node; ///< Non-NULL iff a node + SharedPtr<PortImpl> _port; ///< Non-NULL iff a port + DriverPort* _driver_port; + PatchImpl::Nodes::Node* _patch_node_listnode; + Raul::List<PortImpl*>::Node* _patch_port_listnode; + Raul::Array<PortImpl*>* _ports_array; ///< New (external) ports for Patch + CompiledPatch* _compiled_patch; ///< Patch's new process order + DisconnectAllEvent* _disconnect_event; + + SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > _removed_table; +}; + + +} // namespace Ingen + +#endif // DESTROYEVENT_H diff --git a/src/engine/events/DisablePortMonitoringEvent.cpp b/src/engine/events/DisablePortMonitoringEvent.cpp new file mode 100644 index 00000000..cecc8dfd --- /dev/null +++ b/src/engine/events/DisablePortMonitoringEvent.cpp @@ -0,0 +1,87 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string> +#include "interface/ClientInterface.hpp" +#include "events/DisablePortMonitoringEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "AudioBuffer.hpp" + +using std::string; + +namespace Ingen { + + +DisablePortMonitoringEvent::DisablePortMonitoringEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const std::string& port_path) +: QueuedEvent(engine, responder, timestamp), + _port_path(port_path), + _port(NULL) +{ +} + + +void +DisablePortMonitoringEvent::pre_process() +{ + _port = _engine.engine_store()->find_port(_port_path); + + QueuedEvent::pre_process(); +} + + +void +DisablePortMonitoringEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + +#if 0 + assert(_time >= start && _time <= end); + + if (_port != NULL && _port->type() == DataType::FLOAT) + _value = ((AudioBuffer*)_port->buffer(0))->value_at(0/*_time - start*/); + else + _port = NULL; // triggers error response +#endif +} + + +void +DisablePortMonitoringEvent::post_process() +{ +#if 0 + string msg; + if (!_port) { + _responder->respond_error("Unable to find port for get_value responder."); + } else if (_responder->client()) { + _responder->respond_ok(); + _responder->client()->control_change(_port_path, _value); + } else { + _responder->respond_error("Unable to find client to send port value"); + } +#endif +} + + +} // namespace Ingen + diff --git a/src/engine/events/DisablePortMonitoringEvent.hpp b/src/engine/events/DisablePortMonitoringEvent.hpp new file mode 100644 index 00000000..7a8e23f7 --- /dev/null +++ b/src/engine/events/DisablePortMonitoringEvent.hpp @@ -0,0 +1,58 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DISABLEPORTNOTIFICATIONEVENT_H +#define DISABLEPORTNOTIFICATIONEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include "types.hpp" + +using std::string; + +namespace Ingen { + +class PortImpl; +namespace Shared { class ClientInterface; } +using Shared::ClientInterface; + + +/** Disable sending of dynamic value change notifications for a port. + * + * \ingroup engine + */ +class DisablePortMonitoringEvent : public QueuedEvent +{ +public: + DisablePortMonitoringEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const std::string& port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const std::string _port_path; + Port* _port; +}; + + +} // namespace Ingen + +#endif // DISABLEPORTNOTIFICATIONEVENT_H diff --git a/src/engine/events/DisconnectAllEvent.cpp b/src/engine/events/DisconnectAllEvent.cpp new file mode 100644 index 00000000..77b1b1b3 --- /dev/null +++ b/src/engine/events/DisconnectAllEvent.cpp @@ -0,0 +1,183 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <boost/format.hpp> +#include <raul/Array.hpp> +#include <raul/List.hpp> +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include "ClientBroadcaster.hpp" +#include "ConnectionImpl.hpp" +#include "DisconnectAllEvent.hpp" +#include "DisconnectionEvent.hpp" +#include "Engine.hpp" +#include "InputPort.hpp" +#include "NodeImpl.hpp" +#include "EngineStore.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "PortImpl.hpp" +#include "Responder.hpp" +#include "util.hpp" + +namespace Ingen { + + +DisconnectAllEvent::DisconnectAllEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& parent_path, const string& node_path) + : QueuedEvent(engine, responder, timestamp) + , _parent_path(parent_path) + , _path(node_path) + , _parent(NULL) + , _node(NULL) + , _port(NULL) + , _lookup(true) + , _error(NO_ERROR) +{ +} + + +/** Internal version for use by other events. + */ +DisconnectAllEvent::DisconnectAllEvent(Engine& engine, PatchImpl* parent, GraphObjectImpl* object) + : QueuedEvent(engine) + , _parent_path(parent->path()) + , _path(object->path()) + , _parent(parent) + , _node(dynamic_cast<NodeImpl*>(object)) + , _port(dynamic_cast<PortImpl*>(object)) + , _lookup(false) + , _error(NO_ERROR) +{ +} + + +DisconnectAllEvent::~DisconnectAllEvent() +{ + for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin(); i != _disconnection_events.end(); ++i) + delete (*i); +} + + +void +DisconnectAllEvent::pre_process() +{ + if (_lookup) { + _parent = _engine.engine_store()->find_patch(_parent_path); + + if (_parent == NULL) { + _error = PARENT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + GraphObjectImpl* object = _engine.engine_store()->find_object(_path); + + if (object == NULL) { + _error = OBJECT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (object->parent_patch() != _parent && object->parent()->parent_patch() != _parent) { + _error = INVALID_PARENT_PATH; + QueuedEvent::pre_process(); + return; + } + + // Only one of these will succeed + _node = dynamic_cast<NodeImpl*>(object); + _port = dynamic_cast<PortImpl*>(object); + + assert((_node || _port) && !(_node && _port)); + } + + if (_node) { + for (PatchImpl::Connections::const_iterator i = _parent->connections().begin(); + i != _parent->connections().end(); ++i) { + ConnectionImpl* c = (ConnectionImpl*)i->get(); + if ((c->src_port()->parent_node() == _node || c->dst_port()->parent_node() == _node) + && !c->pending_disconnection()) { + DisconnectionEvent* ev = new DisconnectionEvent(_engine, + SharedPtr<Responder>(new Responder()), _time, c->src_port(), c->dst_port()); + ev->pre_process(); + _disconnection_events.push_back(new Raul::List<DisconnectionEvent*>::Node(ev)); + c->pending_disconnection(true); + } + } + } else { // _port + for (PatchImpl::Connections::const_iterator i = _parent->connections().begin(); + i != _parent->connections().end(); ++i) { + ConnectionImpl* c = (ConnectionImpl*)i->get(); + if ((c->src_port() == _port || c->dst_port() == _port) && !c->pending_disconnection()) { + DisconnectionEvent* ev = new DisconnectionEvent(_engine, + SharedPtr<Responder>(new Responder()), _time, c->src_port(), c->dst_port()); + ev->pre_process(); + _disconnection_events.push_back(new Raul::List<DisconnectionEvent*>::Node(ev)); + c->pending_disconnection(true); + } + } + } + + QueuedEvent::pre_process(); +} + + +void +DisconnectAllEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin(); i != _disconnection_events.end(); ++i) + (*i)->execute(context); + } +} + + +void +DisconnectAllEvent::post_process() +{ + if (_error == NO_ERROR) { + if (_responder) + _responder->respond_ok(); + for (Raul::List<DisconnectionEvent*>::iterator i = _disconnection_events.begin(); + i != _disconnection_events.end(); ++i) + (*i)->post_process(); + } else { + if (_responder) { + boost::format fmt("Unable to disconnect %1% (%2%)"); + fmt % _path; + switch (_error) { + case INVALID_PARENT_PATH: + fmt % string("Invalid parent path: ").append(_parent_path); + break; + case PARENT_NOT_FOUND: + fmt % string("Unable to find parent: ").append(_parent_path); + break; + case OBJECT_NOT_FOUND: + fmt % string("Unable to find object"); + default: + break; + } + _responder->respond_error(fmt.str()); + } + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/DisconnectAllEvent.hpp b/src/engine/events/DisconnectAllEvent.hpp new file mode 100644 index 00000000..6b75e6df --- /dev/null +++ b/src/engine/events/DisconnectAllEvent.hpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DISCONNECTNODEEVENT_H +#define DISCONNECTNODEEVENT_H + +#include <string> +#include <raul/List.hpp> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" + +using std::string; + +namespace Ingen { + +class DisconnectionEvent; +class PatchImpl; +class NodeImpl; +class Connection; +class PortImpl; +class InputPort; +class OutputPort; + + +/** An event to disconnect all connections to a Node. + * + * \ingroup engine + */ +class DisconnectAllEvent : public QueuedEvent +{ +public: + DisconnectAllEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& parent_path, const string& node_path); + DisconnectAllEvent(Engine& engine, PatchImpl* parent, GraphObjectImpl* object); + ~DisconnectAllEvent(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { + NO_ERROR, + INVALID_PARENT_PATH, + PARENT_NOT_FOUND, + OBJECT_NOT_FOUND, + }; + + Raul::Path _parent_path; + Raul::Path _path; + PatchImpl* _parent; + NodeImpl* _node; + PortImpl* _port; + Raul::List<DisconnectionEvent*> _disconnection_events; + + bool _lookup; + bool _disconnect_parent; + + ErrorType _error; +}; + + +} // namespace Ingen + + +#endif // DISCONNECTNODEEVENT_H diff --git a/src/engine/events/DisconnectionEvent.cpp b/src/engine/events/DisconnectionEvent.cpp new file mode 100644 index 00000000..86ad8b4e --- /dev/null +++ b/src/engine/events/DisconnectionEvent.cpp @@ -0,0 +1,213 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "DisconnectionEvent.hpp" +#include <string> +#include <raul/Maid.hpp> +#include <raul/Path.hpp> +#include "Responder.hpp" +#include "Engine.hpp" +#include "ConnectionImpl.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "PortImpl.hpp" +#include "EngineStore.hpp" + +using std::string; +namespace Ingen { + + +//// DisconnectionEvent //// + + +DisconnectionEvent::DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path) +: QueuedEvent(engine, responder, timestamp), + _src_port_path(src_port_path), + _dst_port_path(dst_port_path), + _patch(NULL), + _src_port(NULL), + _dst_port(NULL), + _lookup(true), + _patch_connection(NULL), + _compiled_patch(NULL), + _error(NO_ERROR) +{ +} + + +DisconnectionEvent::DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PortImpl* const src_port, PortImpl* const dst_port) +: QueuedEvent(engine, responder, timestamp), + _src_port_path(src_port->path()), + _dst_port_path(dst_port->path()), + _patch(src_port->parent_node()->parent_patch()), + _src_port(src_port), + _dst_port(dst_port), + _lookup(false), + _compiled_patch(NULL), + _error(NO_ERROR) +{ + // FIXME: These break for patch ports.. is that ok? + /*assert(src_port->is_output()); + assert(dst_port->is_input()); + assert(src_port->type() == dst_port->type()); + assert(src_port->parent_node()->parent_patch() + == dst_port->parent_node()->parent_patch()); */ +} + + +void +DisconnectionEvent::pre_process() +{ + if (_lookup) { + if (_src_port_path.parent().parent() != _dst_port_path.parent().parent() + && _src_port_path.parent() != _dst_port_path.parent().parent() + && _src_port_path.parent().parent() != _dst_port_path.parent()) { + _error = PARENT_PATCH_DIFFERENT; + QueuedEvent::pre_process(); + return; + } + + _src_port = _engine.engine_store()->find_port(_src_port_path); + _dst_port = _engine.engine_store()->find_port(_dst_port_path); + } + + if (_src_port == NULL || _dst_port == NULL) { + _error = PORT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + _dst_input_port = dynamic_cast<InputPort*>(_dst_port); + _src_output_port = dynamic_cast<OutputPort*>(_src_port); + assert(_src_output_port); + assert(_dst_input_port); + + NodeImpl* const src_node = _src_port->parent_node(); + NodeImpl* const dst_node = _dst_port->parent_node(); + + // Connection to a patch port from inside the patch + if (src_node->parent_patch() != dst_node->parent_patch()) { + + assert(src_node->parent() == dst_node || dst_node->parent() == src_node); + if (src_node->parent() == dst_node) + _patch = dynamic_cast<PatchImpl*>(dst_node); + else + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Connection from a patch input to a patch output (pass through) + } else if (src_node == dst_node && dynamic_cast<PatchImpl*>(src_node)) { + _patch = dynamic_cast<PatchImpl*>(src_node); + + // Normal connection between nodes with the same parent + } else { + _patch = src_node->parent_patch(); + } + + assert(_patch); + + //if (_dst_input_port->is_connected_to(_src_output_port)) { + if (!_patch->has_connection(_src_output_port, _dst_input_port)) { + _error = NOT_CONNECTED; + QueuedEvent::pre_process(); + return; + } + + if (src_node == NULL || dst_node == NULL) { + _error = PARENTS_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + for (Raul::List<NodeImpl*>::iterator i = dst_node->providers()->begin(); i != dst_node->providers()->end(); ++i) + if ((*i) == src_node) { + delete dst_node->providers()->erase(i); + break; + } + + for (Raul::List<NodeImpl*>::iterator i = src_node->dependants()->begin(); i != src_node->dependants()->end(); ++i) + if ((*i) == dst_node) { + delete src_node->dependants()->erase(i); + break; + } + + _patch_connection = _patch->remove_connection(_src_port, _dst_port); + + if (_patch->enabled()) + _compiled_patch = _patch->compile(); + + QueuedEvent::pre_process(); +} + + +void +DisconnectionEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_error == NO_ERROR) { + InputPort::Connections::Node* const port_connection + = _dst_input_port->remove_connection(_src_output_port); + + if (port_connection != NULL) { + assert(_patch_connection); + + if (port_connection->elem() != _patch_connection->elem()) { + cerr << "ERROR: Corrupt connections:" << endl; + cerr << "\t" << port_connection->elem() << ": " + << port_connection->elem()->src_port_path() + << " -> " << port_connection->elem()->dst_port_path() << endl + << "!=" << endl + << "\t" << _patch_connection->elem() << ": " + << _patch_connection->elem()->src_port_path() + << " -> " << _patch_connection->elem()->dst_port_path() << endl; + } + assert(port_connection->elem() == _patch_connection->elem()); + + // Destroy list node, which will drop reference to connection itself + _engine.maid()->push(port_connection); + _engine.maid()->push(_patch_connection); + + if (_patch->compiled_patch() != NULL) + _engine.maid()->push(_patch->compiled_patch()); + _patch->compiled_patch(_compiled_patch); + } else { + _error = CONNECTION_NOT_FOUND; + } + } +} + + +void +DisconnectionEvent::post_process() +{ + if (_error == NO_ERROR) { + _responder->respond_ok(); + _engine.broadcaster()->send_disconnection(_src_port->path(), _dst_port->path()); + } else { + // FIXME: better error messages + string msg = "Unable to disconnect "; + msg.append(_src_port_path + " -> " + _dst_port_path); + cerr << "DISCONNECTION ERROR " << (unsigned)_error << endl; + _responder->respond_error(msg); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/DisconnectionEvent.hpp b/src/engine/events/DisconnectionEvent.hpp new file mode 100644 index 00000000..700febeb --- /dev/null +++ b/src/engine/events/DisconnectionEvent.hpp @@ -0,0 +1,90 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DISCONNECTIONEVENT_H +#define DISCONNECTIONEVENT_H + +#include <string> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" +#include "types.hpp" +#include "PatchImpl.hpp" +using std::string; + +namespace Raul { + template <typename T> class ListNode; + template <typename T> class Array; +} + +namespace Ingen { + +class NodeImpl; +class ConnectionImpl; +class MidiMessage; +class PortImpl; +class InputPort; +class OutputPort; +class CompiledPatch; + + +/** Make a Connection between two Ports. + * + * \ingroup engine + */ +class DisconnectionEvent : public QueuedEvent +{ +public: + DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& src_port_path, const string& dst_port_path); + DisconnectionEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, PortImpl* const src_port, PortImpl* const dst_port); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + + enum ErrorType { + NO_ERROR, + PARENT_PATCH_DIFFERENT, + PORT_NOT_FOUND, + TYPE_MISMATCH, + NOT_CONNECTED, + PARENTS_NOT_FOUND, + CONNECTION_NOT_FOUND + }; + + Raul::Path _src_port_path; + Raul::Path _dst_port_path; + + PatchImpl* _patch; + PortImpl* _src_port; + PortImpl* _dst_port; + OutputPort* _src_output_port; + InputPort* _dst_input_port; + + bool _lookup; + + PatchImpl::Connections::Node* _patch_connection; + CompiledPatch* _compiled_patch; ///< New process order for Patch + + ErrorType _error; +}; + + +} // namespace Ingen + +#endif // DISCONNECTIONEVENT_H diff --git a/src/engine/events/EnablePatchEvent.cpp b/src/engine/events/EnablePatchEvent.cpp new file mode 100644 index 00000000..04759cea --- /dev/null +++ b/src/engine/events/EnablePatchEvent.cpp @@ -0,0 +1,86 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "EnablePatchEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "util.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" + +namespace Ingen { + + +EnablePatchEvent::EnablePatchEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& patch_path, bool enable) +: QueuedEvent(engine, responder, timestamp), + _patch_path(patch_path), + _patch(NULL), + _compiled_patch(NULL), + _enable(enable) +{ +} + + +void +EnablePatchEvent::pre_process() +{ + _patch = _engine.engine_store()->find_patch(_patch_path); + + if (_enable && _patch) { + /* Any event that requires a new process order will set the patch's + * compiled_patch to NULL if it is executed when the patch is not + * active. So, if the CP is NULL, calculate it here */ + if (_patch->compiled_patch() == NULL) + _compiled_patch = _patch->compile(); + } + + QueuedEvent::pre_process(); +} + + +void +EnablePatchEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch != NULL) { + if (_enable) + _patch->enable(); + else + _patch->disable(); + + if (_enable && _patch->compiled_patch() == NULL) + _patch->compiled_patch(_compiled_patch); + } +} + + +void +EnablePatchEvent::post_process() +{ + if (_patch != NULL) { + _responder->respond_ok(); + _engine.broadcaster()->send_property_change(_patch_path, "ingen:enabled", (bool)_enable); + } else { + _responder->respond_error(string("Patch ") + _patch_path + " not found"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/EnablePatchEvent.hpp b/src/engine/events/EnablePatchEvent.hpp new file mode 100644 index 00000000..dad1803a --- /dev/null +++ b/src/engine/events/EnablePatchEvent.hpp @@ -0,0 +1,63 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ENABLEPATCHEVENT_H +#define ENABLEPATCHEVENT_H + +#include <string> +#include "QueuedEvent.hpp" + +using std::string; + +namespace Raul { template <typename T> class Array; } + +namespace Ingen { + +class PatchImpl; +class NodeImpl; +class CompiledPatch; + + +/** Enables a patch's DSP processing. + * + * \ingroup engine + */ +class EnablePatchEvent : public QueuedEvent +{ +public: + EnablePatchEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& patch_path, + bool enable); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + string _patch_path; + PatchImpl* _patch; + CompiledPatch* _compiled_patch; // Patch's new process order + bool _enable; +}; + + +} // namespace Ingen + + +#endif // ENABLEPATCHEVENT_H diff --git a/src/engine/events/LoadPluginsEvent.cpp b/src/engine/events/LoadPluginsEvent.cpp new file mode 100644 index 00000000..df5ff5d9 --- /dev/null +++ b/src/engine/events/LoadPluginsEvent.cpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "LoadPluginsEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "NodeFactory.hpp" +#include "ClientBroadcaster.hpp" +#include "QueuedEventSource.hpp" + +#include <iostream> +using std::cerr; + +namespace Ingen { + + +LoadPluginsEvent::LoadPluginsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, QueuedEventSource* source) +: QueuedEvent(engine, responder, timestamp, true, source) +{ + /* FIXME: Not sure why this has to be blocking, but it fixes some nasty bugs.. */ +} + +void +LoadPluginsEvent::pre_process() +{ + _engine.node_factory()->load_plugins(); + + QueuedEvent::pre_process(); +} + +void +LoadPluginsEvent::post_process() +{ + if (_source) + _source->unblock(); + + _responder->respond_ok(); +} + + +} // namespace Ingen + diff --git a/src/engine/events/LoadPluginsEvent.hpp b/src/engine/events/LoadPluginsEvent.hpp new file mode 100644 index 00000000..cd9a2884 --- /dev/null +++ b/src/engine/events/LoadPluginsEvent.hpp @@ -0,0 +1,46 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LOADPLUGINSEVENT_H +#define LOADPLUGINSEVENT_H + +#include <list> +#include "QueuedEvent.hpp" + +namespace Ingen { + + +/** Loads all plugins into the internal plugin database (in NodeFactory). + * + * \ingroup engine + */ +class LoadPluginsEvent : public QueuedEvent +{ +public: + LoadPluginsEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + QueuedEventSource* source); + + void pre_process(); + void post_process(); +}; + + +} // namespace Ingen + +#endif // LOADPLUGINSEVENT_H diff --git a/src/engine/events/Makefile.am b/src/engine/events/Makefile.am new file mode 100644 index 00000000..9a8cfbd5 --- /dev/null +++ b/src/engine/events/Makefile.am @@ -0,0 +1,63 @@ +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = \ + AllNotesOffEvent.cpp \ + AllNotesOffEvent.hpp \ + ClearPatchEvent.cpp \ + ClearPatchEvent.hpp \ + ConnectionEvent.cpp \ + ConnectionEvent.hpp \ + CreateNodeEvent.cpp \ + CreateNodeEvent.hpp \ + CreatePatchEvent.cpp \ + CreatePatchEvent.hpp \ + CreatePortEvent.cpp \ + CreatePortEvent.hpp \ + DeactivateEvent.cpp \ + DeactivateEvent.hpp \ + DestroyEvent.cpp \ + DestroyEvent.hpp \ + DisconnectAllEvent.cpp \ + DisconnectAllEvent.hpp \ + DisconnectionEvent.cpp \ + DisconnectionEvent.hpp \ + EnablePatchEvent.cpp \ + EnablePatchEvent.hpp \ + LoadPluginsEvent.cpp \ + LoadPluginsEvent.hpp \ + MidiLearnEvent.cpp \ + MidiLearnEvent.hpp \ + NoteEvent.cpp \ + NoteEvent.hpp \ + PingQueuedEvent.hpp \ + RegisterClientEvent.cpp \ + RegisterClientEvent.hpp \ + RenameEvent.cpp \ + RenameEvent.hpp \ + RequestAllObjectsEvent.cpp \ + RequestAllObjectsEvent.hpp \ + RequestMetadataEvent.cpp \ + RequestMetadataEvent.hpp \ + RequestObjectEvent.cpp \ + RequestObjectEvent.hpp \ + RequestPluginEvent.cpp \ + RequestPluginEvent.hpp \ + RequestPluginsEvent.cpp \ + RequestPluginsEvent.hpp \ + RequestPortValueEvent.cpp \ + RequestPortValueEvent.hpp \ + SendPortActivityEvent.cpp \ + SendPortActivityEvent.hpp \ + SendPortValueEvent.cpp \ + SendPortValueEvent.hpp \ + SetMetadataEvent.cpp \ + SetMetadataEvent.hpp \ + SetPolyphonicEvent.cpp \ + SetPolyphonicEvent.hpp \ + SetPolyphonyEvent.cpp \ + SetPolyphonyEvent.hpp \ + SetPortValueEvent.cpp \ + SetPortValueEvent.hpp \ + UnregisterClientEvent.cpp \ + UnregisterClientEvent.hpp + diff --git a/src/engine/events/MidiLearnEvent.cpp b/src/engine/events/MidiLearnEvent.cpp new file mode 100644 index 00000000..2f37f30d --- /dev/null +++ b/src/engine/events/MidiLearnEvent.cpp @@ -0,0 +1,89 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "MidiLearnEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "NodeImpl.hpp" +#include "MidiControlNode.hpp" +#include "ClientBroadcaster.hpp" +#include "PluginImpl.hpp" + +namespace Ingen { + + +// MidiLearnResponseEvent + +void +MidiLearnResponseEvent::post_process() +{ + _engine.broadcaster()->send_port_value(_port_path, _value); +} + + + +// MidiLearnEvent + +MidiLearnEvent::MidiLearnEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path) +: QueuedEvent(engine, responder, timestamp), + _node_path(node_path), + _node(NULL), + _response_event(NULL) +{ +} + + +void +MidiLearnEvent::pre_process() +{ + _node = _engine.engine_store()->find_node(_node_path); + _response_event = new MidiLearnResponseEvent(_engine, _node_path + "/Controller_Number", _time); + + QueuedEvent::pre_process(); +} + + +void +MidiLearnEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + // FIXME: this isn't very good at all. + if (_node != NULL && _node->plugin_impl()->type() == Plugin::Internal + && _node->plugin_impl()->uri() == "ingen:control_node") { + ((MidiControlNode*)_node)->learn(_response_event); + } +} + + +void +MidiLearnEvent::post_process() +{ + if (_node != NULL) { + _responder->respond_ok(); + } else { + string msg = "Did not find node '"; + msg.append(_node_path).append("' for MIDI learn."); + _responder->respond_error(msg); + } +} + + +} // namespace Ingen + + diff --git a/src/engine/events/MidiLearnEvent.hpp b/src/engine/events/MidiLearnEvent.hpp new file mode 100644 index 00000000..c0fc4a17 --- /dev/null +++ b/src/engine/events/MidiLearnEvent.hpp @@ -0,0 +1,85 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MIDILEARNEVENT_H +#define MIDILEARNEVENT_H + +#include "QueuedEvent.hpp" +#include "MidiControlNode.hpp" +#include "types.hpp" +#include <string> +using std::string; + +namespace Ingen { + +class NodeImpl; +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(Engine& engine, const string& port_path, SampleCount timestamp) + : Event(engine, SharedPtr<Responder>(), timestamp), + _port_path(port_path), + _value(0.0f) + {} + + void set_value(Sample val) { _value = val; } + void post_process(); + +private: + string _port_path; + Sample _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(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _node_path; + NodeImpl* _node; + + /// Event to respond with when learned + MidiLearnResponseEvent* _response_event; +}; + + +} // namespace Ingen + +#endif // MIDILEARNEVENT_H diff --git a/src/engine/events/NoteEvent.cpp b/src/engine/events/NoteEvent.cpp new file mode 100644 index 00000000..58842ae6 --- /dev/null +++ b/src/engine/events/NoteEvent.cpp @@ -0,0 +1,102 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "NoteEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "NodeImpl.hpp" +#include "MidiNoteNode.hpp" +#include "MidiTriggerNode.hpp" +#include "PluginImpl.hpp" +#include "InternalPlugin.hpp" +#include "ProcessContext.hpp" + +namespace Ingen { + + +/** Note on with Patch explicitly passed. + * + * Used to be triggered by MIDI. Not used anymore. + */ +NoteEvent::NoteEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, NodeImpl* node, bool on, uchar note_num, uchar velocity) +: Event(engine, responder, timestamp), + _node(node), + _on(on), + _note_num(note_num), + _velocity(velocity) +{ +} + + +/** Note on with Node lookup. + * + * Triggered by OSC. + */ +NoteEvent::NoteEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& node_path, bool on, uchar note_num, uchar velocity) +: Event(engine, responder, timestamp), + _node(NULL), + _node_path(node_path), + _on(on), + _note_num(note_num), + _velocity(velocity) +{ +} + + +void +NoteEvent::execute(ProcessContext& context) +{ + Event::execute(context); + assert(_time >= context.start() && _time <= context.end()); + + // Lookup if neccessary + if (!_node) + _node = _engine.engine_store()->find_node(_node_path); + + // FIXME: barf + + if (_node != NULL && _node->plugin()->type() == Plugin::Internal) { + if (_on) { + if (_node->plugin_impl()->uri() == NS_INGEN "note_node") + ((MidiNoteNode*)_node)->note_on(context, _note_num, _velocity, _time); + else if (_node->plugin_impl()->uri() == NS_INGEN "trigger_node") + ((MidiTriggerNode*)_node)->note_on(context, _note_num, _velocity, _time); + } else { + if (_node->plugin_impl()->uri() == NS_INGEN "note_node") + ((MidiNoteNode*)_node)->note_off(context, _note_num, _time); + else if (_node->plugin_impl()->uri() == NS_INGEN "trigger_node") + ((MidiTriggerNode*)_node)->note_off(context, _note_num, _time); + } + } +} + + +void +NoteEvent::post_process() +{ + if (_responder) { + if (_node) + _responder->respond_ok(); + else + _responder->respond_error("Did not find node for note_on"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/NoteEvent.hpp b/src/engine/events/NoteEvent.hpp new file mode 100644 index 00000000..31ae9d27 --- /dev/null +++ b/src/engine/events/NoteEvent.hpp @@ -0,0 +1,68 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef NOTEEVENT_H +#define NOTEEVENT_H + +#include "Event.hpp" +#include "types.hpp" +#include <string> +using std::string; + +namespace Ingen { + +class NodeImpl; + + +/** A note on event. + * + * \ingroup engine + */ +class NoteEvent : public Event +{ +public: + NoteEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + NodeImpl* node, + bool on, + uchar note_num, + uchar velocity); + + NoteEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& node_path, + bool on, + uchar note_num, + uchar velocity); + + void execute(ProcessContext& context); + void post_process(); + +private: + NodeImpl* _node; + const string _node_path; + bool _on; + uchar _note_num; + uchar _velocity; +}; + + +} // namespace Ingen + +#endif // NOTEEVENT_H diff --git a/src/engine/events/PingQueuedEvent.hpp b/src/engine/events/PingQueuedEvent.hpp new file mode 100644 index 00000000..08897bfe --- /dev/null +++ b/src/engine/events/PingQueuedEvent.hpp @@ -0,0 +1,48 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PINGQUEUEDEVENT_H +#define PINGQUEUEDEVENT_H + +#include "QueuedEvent.hpp" +#include "types.hpp" +#include "Responder.hpp" + +namespace Ingen { + +class PortImpl; + + +/** A ping that travels through the pre-processed event queue before responding + * (useful for the order guarantee). + * + * \ingroup engine + */ +class PingQueuedEvent : public QueuedEvent +{ +public: + PingQueuedEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp) + : QueuedEvent(engine, responder, timestamp) + {} + + void post_process() { _responder->respond_ok(); } +}; + + +} // namespace Ingen + +#endif // PINGQUEUEDEVENT_H diff --git a/src/engine/events/RegisterClientEvent.cpp b/src/engine/events/RegisterClientEvent.cpp new file mode 100644 index 00000000..a8f68e21 --- /dev/null +++ b/src/engine/events/RegisterClientEvent.cpp @@ -0,0 +1,55 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "Responder.hpp" +#include "RegisterClientEvent.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" + +namespace Ingen { + + +RegisterClientEvent::RegisterClientEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& uri, + ClientInterface* client) + : QueuedEvent(engine, responder, timestamp) + , _uri(uri) + , _client(client) +{ +} + + +void +RegisterClientEvent::pre_process() +{ + _engine.broadcaster()->register_client(_uri, _client); + + QueuedEvent::pre_process(); +} + + +void +RegisterClientEvent::post_process() +{ + _responder->respond_ok(); +} + + +} // namespace Ingen + diff --git a/src/engine/events/RegisterClientEvent.hpp b/src/engine/events/RegisterClientEvent.hpp new file mode 100644 index 00000000..9e12b5ba --- /dev/null +++ b/src/engine/events/RegisterClientEvent.hpp @@ -0,0 +1,55 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REGISTERCLIENTEVENT_H +#define REGISTERCLIENTEVENT_H + +#include "QueuedEvent.hpp" +#include "interface/ClientInterface.hpp" +#include <string> +using std::string; +using Ingen::Shared::ClientInterface; +using Ingen::Responder; + +namespace Ingen { + + +/** Registers a new client with the OSC system, so it can receive updates. + * + * \ingroup engine + */ +class RegisterClientEvent : public QueuedEvent +{ +public: + RegisterClientEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& uri, + ClientInterface* client); + + void pre_process(); + void post_process(); + +private: + string _uri; + ClientInterface* _client; +}; + + +} // namespace Ingen + +#endif // REGISTERCLIENTEVENT_H diff --git a/src/engine/events/RenameEvent.cpp b/src/engine/events/RenameEvent.cpp new file mode 100644 index 00000000..164676aa --- /dev/null +++ b/src/engine/events/RenameEvent.cpp @@ -0,0 +1,152 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Path.hpp> +#include "ClientBroadcaster.hpp" +#include "Engine.hpp" +#include "NodeImpl.hpp" +#include "EngineStore.hpp" +#include "PatchImpl.hpp" +#include "RenameEvent.hpp" +#include "Responder.hpp" +#include "AudioDriver.hpp" +#include "MidiDriver.hpp" + +using namespace std; + +namespace Ingen { + + +RenameEvent::RenameEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& name) +: QueuedEvent(engine, responder, timestamp), + _old_path(path), + _name(name), + _new_path("/"), + _parent_patch(NULL), + _store_iterator(engine.engine_store()->end()), + _error(NO_ERROR) +{ + /* + if (_old_path.parent() == "/") + _new_path = string("/") + _name; + else + _new_path = _old_path.parent() +"/"+ _name;*/ +} + + +RenameEvent::~RenameEvent() +{ +} + + +void +RenameEvent::pre_process() +{ + if ((!Raul::Path::is_valid_name(_name)) || _name.find("/") != string::npos) { + _error = INVALID_NAME; + QueuedEvent::pre_process(); + return; + } + + _new_path = _old_path.parent().base() + _name; + + _store_iterator = _engine.engine_store()->find(_old_path); + if (_store_iterator == _engine.engine_store()->end()) { + _error = OBJECT_NOT_FOUND; + QueuedEvent::pre_process(); + return; + } + + if (_engine.engine_store()->find_object(_new_path)) { + _error = OBJECT_EXISTS; + QueuedEvent::pre_process(); + return; + } + + SharedPtr< Table<Path, SharedPtr<Shared::GraphObject> > > removed + = _engine.engine_store()->remove(_store_iterator); + + assert(removed->size() > 0); + + for (Table<Path, SharedPtr<Shared::GraphObject> >::iterator i = removed->begin(); i != removed->end(); ++i) { + const Path& child_old_path = i->first; + assert(Path::descendant_comparator(_old_path, child_old_path)); + + Path child_new_path; + if (child_old_path == _old_path) + child_new_path = _new_path; + else + child_new_path = _new_path.base() + child_old_path.substr(_old_path.length()+1); + + PtrCast<GraphObjectImpl>(i->second)->set_path(child_new_path); + i->first = child_new_path; + } + + _engine.engine_store()->add(*removed.get()); + + QueuedEvent::pre_process(); +} + + +void +RenameEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + SharedPtr<PortImpl> port = PtrCast<PortImpl>(_store_iterator->second); + if (port && port->parent()->parent() == NULL) { + DriverPort* driver_port = NULL; + + if (port->type() == DataType::AUDIO) + driver_port = _engine.audio_driver()->driver_port(_new_path); + else if (port->type() == DataType::EVENT) + driver_port = _engine.midi_driver()->driver_port(_new_path); + + if (driver_port) { + cerr << "DRIVER PORT :)!" << endl; + driver_port->set_name(_new_path); + } else { + cerr << "NO DRIVER PORT :(" << endl; + } + } +} + + +void +RenameEvent::post_process() +{ + string msg = "Unable to rename object - "; + + if (_error == NO_ERROR) { + _responder->respond_ok(); + _engine.broadcaster()->send_rename(_old_path, _new_path); + } else { + if (_error == OBJECT_EXISTS) + msg.append("Object already exists at ").append(_new_path); + else if (_error == OBJECT_NOT_FOUND) + msg.append("Could not find object ").append(_old_path); + else if (_error == OBJECT_NOT_RENAMABLE) + msg.append(_old_path).append(" is not renamable"); + else if (_error == INVALID_NAME) + msg.append(_name).append(" is not a valid name"); + + _responder->respond_error(msg); + } +} + + +} // namespace Ingen diff --git a/src/engine/events/RenameEvent.hpp b/src/engine/events/RenameEvent.hpp new file mode 100644 index 00000000..e230c589 --- /dev/null +++ b/src/engine/events/RenameEvent.hpp @@ -0,0 +1,64 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RENAMEEVENT_H +#define RENAMEEVENT_H + +#include <string> +#include <raul/Path.hpp> +#include "QueuedEvent.hpp" +#include "EngineStore.hpp" + +using std::string; + +template<typename T> class TreeNode; +template<typename T> class ListNode; + +namespace Ingen { + +class PatchImpl; + + +/** An event to change the name of an GraphObjectImpl. + * + * \ingroup engine + */ +class RenameEvent : public QueuedEvent +{ +public: + RenameEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path, const string& name); + ~RenameEvent(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { NO_ERROR, OBJECT_NOT_FOUND, OBJECT_EXISTS, OBJECT_NOT_RENAMABLE, INVALID_NAME }; + + Path _old_path; + string _name; + Path _new_path; + PatchImpl* _parent_patch; + EngineStore::iterator _store_iterator; + ErrorType _error; +}; + + +} // namespace Ingen + +#endif // RENAMEEVENT_H diff --git a/src/engine/events/RequestAllObjectsEvent.cpp b/src/engine/events/RequestAllObjectsEvent.cpp new file mode 100644 index 00000000..d57080aa --- /dev/null +++ b/src/engine/events/RequestAllObjectsEvent.cpp @@ -0,0 +1,59 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "RequestAllObjectsEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "ObjectSender.hpp" +#include "ClientBroadcaster.hpp" +#include "EngineStore.hpp" + +namespace Ingen { + + +RequestAllObjectsEvent::RequestAllObjectsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp) +: QueuedEvent(engine, responder, timestamp) +{ +} + + +void +RequestAllObjectsEvent::pre_process() +{ + QueuedEvent::pre_process(); +} + + +void +RequestAllObjectsEvent::post_process() +{ + if (_responder->client()) { + _responder->respond_ok(); + + // Everything is a child of the root patch, so this sends it all + PatchImpl* root = _engine.engine_store()->find_patch("/"); + if (root && _responder->client()) + ObjectSender::send_patch(_responder->client(), root, true); + + } else { + _responder->respond_error("Unable to find client to send all objects"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestAllObjectsEvent.hpp b/src/engine/events/RequestAllObjectsEvent.hpp new file mode 100644 index 00000000..0537575a --- /dev/null +++ b/src/engine/events/RequestAllObjectsEvent.hpp @@ -0,0 +1,48 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTALLOBJECTSEVENT_H +#define REQUESTALLOBJECTSEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +using std::string; + +namespace Ingen { + +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(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp); + + void pre_process(); + void post_process(); +}; + + +} // namespace Ingen + +#endif // REQUESTALLOBJECTSEVENT_H diff --git a/src/engine/events/RequestMetadataEvent.cpp b/src/engine/events/RequestMetadataEvent.cpp new file mode 100644 index 00000000..733a6a82 --- /dev/null +++ b/src/engine/events/RequestMetadataEvent.cpp @@ -0,0 +1,85 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "RequestMetadataEvent.hpp" +#include <string> +#include "Responder.hpp" +#include "Engine.hpp" +#include "GraphObjectImpl.hpp" +#include "EngineStore.hpp" +#include "interface/ClientInterface.hpp" +#include "ClientBroadcaster.hpp" +using std::string; + +namespace Ingen { + + +RequestMetadataEvent::RequestMetadataEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + bool property, + const string& node_path, + const string& key) + : QueuedEvent(engine, responder, timestamp) + , _path(node_path) + , _property(property) + , _key(key) + , _object(NULL) +{ +} + + +void +RequestMetadataEvent::pre_process() +{ + if (_responder->client()) { + _object = _engine.engine_store()->find_object(_path); + if (_object == NULL) { + QueuedEvent::pre_process(); + return; + } + } + + if (_property) + _value = _object->get_property(_key); + else + _value = _object->get_variable(_key); + + QueuedEvent::pre_process(); +} + + +void +RequestMetadataEvent::post_process() +{ + if (_responder->client()) { + if (!_object) { + string msg = "Unable to find variable subject "; + msg += _path; + _responder->respond_error(msg); + } else { + _responder->respond_ok(); + _responder->client()->set_variable(_path, _key, _value); + } + } else { + _responder->respond_error("Unknown client"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestMetadataEvent.hpp b/src/engine/events/RequestMetadataEvent.hpp new file mode 100644 index 00000000..f6a18dfc --- /dev/null +++ b/src/engine/events/RequestMetadataEvent.hpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTMETADATAEVENT_H +#define REQUESTMETADATAEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include <raul/Atom.hpp> +using std::string; + +namespace Ingen { + +class GraphObjectImpl; +namespace Shared { + class ClientInterface; +} using Shared::ClientInterface; + + +/** A request from a client for a piece of variable. + * + * \ingroup engine + */ +class RequestMetadataEvent : public QueuedEvent +{ +public: + RequestMetadataEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + bool property, + const string& path, + const string& key); + + void pre_process(); + void post_process(); + +private: + string _path; + bool _property; + string _key; + Raul::Atom _value; + GraphObjectImpl* _object; +}; + + +} // namespace Ingen + +#endif // REQUESTMETADATAEVENT_H diff --git a/src/engine/events/RequestObjectEvent.cpp b/src/engine/events/RequestObjectEvent.cpp new file mode 100644 index 00000000..88479482 --- /dev/null +++ b/src/engine/events/RequestObjectEvent.cpp @@ -0,0 +1,98 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "RequestObjectEvent.hpp" +#include <string> +#include "interface/ClientInterface.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "PatchImpl.hpp" +#include "NodeImpl.hpp" +#include "PortImpl.hpp" +#include "ObjectSender.hpp" +#include "ProcessContext.hpp" + +using std::string; + +namespace Ingen { + + +RequestObjectEvent::RequestObjectEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& path) +: QueuedEvent(engine, responder, timestamp), + _path(path), + _object(NULL) +{ +} + + +void +RequestObjectEvent::pre_process() +{ + _object = _engine.engine_store()->find_object(_path); + + QueuedEvent::pre_process(); +} + + +void +RequestObjectEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + assert(_time >= context.start() && _time <= context.end()); +} + + +void +RequestObjectEvent::post_process() +{ + if (!_object) { + _responder->respond_error("Unable to find object requested."); + + } else if (_responder->client()) { + PatchImpl* const patch = dynamic_cast<PatchImpl*>(_object); + if (patch) { + _responder->respond_ok(); + ObjectSender::send_patch(_responder->client(), patch, true); + return; + } + + NodeImpl* const node = dynamic_cast<NodeImpl*>(_object); + if (node) { + _responder->respond_ok(); + ObjectSender::send_node(_responder->client(), node, true); + return; + } + + PortImpl* const port = dynamic_cast<PortImpl*>(_object); + if (port) { + _responder->respond_ok(); + ObjectSender::send_port(_responder->client(), port); + return; + } + + _responder->respond_error("Object of unknown type requested."); + + } else { + _responder->respond_error("Unable to find client to send object."); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestObjectEvent.hpp b/src/engine/events/RequestObjectEvent.hpp new file mode 100644 index 00000000..52459ada --- /dev/null +++ b/src/engine/events/RequestObjectEvent.hpp @@ -0,0 +1,55 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTOBJECTEVENT_H +#define REQUESTOBJECTEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include "types.hpp" + +using std::string; + +namespace Ingen { + +class GraphObjectImpl; +namespace Shared { class ClientInterface; } +using Shared::ClientInterface; + + +/** A request from a client to send the value of a port. + * + * \ingroup engine + */ +class RequestObjectEvent : public QueuedEvent +{ +public: + RequestObjectEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _path; + GraphObjectImpl* _object; +}; + + +} // namespace Ingen + +#endif // REQUESTOBJECTEVENT_H diff --git a/src/engine/events/RequestPluginEvent.cpp b/src/engine/events/RequestPluginEvent.cpp new file mode 100644 index 00000000..36358df7 --- /dev/null +++ b/src/engine/events/RequestPluginEvent.cpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string> +#include "interface/ClientInterface.hpp" +#include "RequestPluginEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "NodeFactory.hpp" +#include "PluginImpl.hpp" +#include "ProcessContext.hpp" + +using std::string; + +namespace Ingen { + + +RequestPluginEvent::RequestPluginEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& uri) +: QueuedEvent(engine, responder, timestamp), + _uri(uri), + _plugin(NULL) +{ +} + + +void +RequestPluginEvent::pre_process() +{ + _plugin = _engine.node_factory()->plugin(_uri); + + QueuedEvent::pre_process(); +} + + +void +RequestPluginEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + assert(_time >= context.start() && _time <= context.end()); +} + + +void +RequestPluginEvent::post_process() +{ + if (!_plugin) { + _responder->respond_error("Unable to find plugin requested."); + + } else if (_responder->client()) { + + _responder->respond_ok(); + assert(_plugin->uri() == _uri); + _responder->client()->new_plugin(_uri, _plugin->type_uri(), _plugin->symbol(), _plugin->name()); + + } else { + _responder->respond_error("Unable to find client to send plugin."); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestPluginEvent.hpp b/src/engine/events/RequestPluginEvent.hpp new file mode 100644 index 00000000..8f936098 --- /dev/null +++ b/src/engine/events/RequestPluginEvent.hpp @@ -0,0 +1,53 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTPLUGINEVENT_H +#define REQUESTPLUGINEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include "types.hpp" + +using std::string; + +namespace Ingen { + +class PluginImpl; + + +/** A request from a client to send information about a plugin. + * + * \ingroup engine + */ +class RequestPluginEvent : public QueuedEvent +{ +public: + RequestPluginEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& uri); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _uri; + const PluginImpl* _plugin; +}; + + +} // namespace Ingen + +#endif // REQUESTPLUGINEVENT_H diff --git a/src/engine/events/RequestPluginsEvent.cpp b/src/engine/events/RequestPluginsEvent.cpp new file mode 100644 index 00000000..8d7fc1ba --- /dev/null +++ b/src/engine/events/RequestPluginsEvent.cpp @@ -0,0 +1,57 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "RequestPluginsEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" +#include "NodeFactory.hpp" + +namespace Ingen { + + +RequestPluginsEvent::RequestPluginsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp) +: QueuedEvent(engine, responder, timestamp) +{ +} + + +void +RequestPluginsEvent::pre_process() +{ + // Take a copy to send in the post processing thread (to avoid problems + // because std::map isn't thread safe) + _plugins = _engine.node_factory()->plugins(); + + QueuedEvent::pre_process(); +} + + +void +RequestPluginsEvent::post_process() +{ + if (_responder->client()) { + _engine.broadcaster()->send_plugins_to(_responder->client(), _plugins); + _responder->respond_ok(); + } else { + _responder->respond_error("Unable to find client to send plugins"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestPluginsEvent.hpp b/src/engine/events/RequestPluginsEvent.hpp new file mode 100644 index 00000000..f6b41d7a --- /dev/null +++ b/src/engine/events/RequestPluginsEvent.hpp @@ -0,0 +1,47 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTPLUGINSEVENT_H +#define REQUESTPLUGINSEVENT_H + +#include "QueuedEvent.hpp" +#include "NodeFactory.hpp" + +namespace Ingen { + +class Responder; + +/** A request from a client to send notification of all objects (ie refresh). + * + * \ingroup engine + */ +class RequestPluginsEvent : public QueuedEvent +{ +public: + RequestPluginsEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp); + + void pre_process(); + void post_process(); + +private: + NodeFactory::Plugins _plugins; +}; + + +} // namespace Ingen + +#endif // REQUESTPLUGINSEVENT_H diff --git a/src/engine/events/RequestPortValueEvent.cpp b/src/engine/events/RequestPortValueEvent.cpp new file mode 100644 index 00000000..025d3700 --- /dev/null +++ b/src/engine/events/RequestPortValueEvent.cpp @@ -0,0 +1,81 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "RequestPortValueEvent.hpp" +#include <string> +#include "interface/ClientInterface.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "EngineStore.hpp" +#include "ClientBroadcaster.hpp" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" + +using std::string; + +namespace Ingen { + + +RequestPortValueEvent::RequestPortValueEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& port_path) +: QueuedEvent(engine, responder, timestamp), + _port_path(port_path), + _port(NULL), + _value(0.0) +{ +} + + +void +RequestPortValueEvent::pre_process() +{ + _port = _engine.engine_store()->find_port(_port_path); + + QueuedEvent::pre_process(); +} + + +void +RequestPortValueEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + assert(_time >= context.start() && _time <= context.end()); + + if (_port != NULL && (_port->type() == DataType::CONTROL || _port->type() == DataType::AUDIO)) + _value = ((AudioBuffer*)_port->buffer(0))->value_at(0/*_time - start*/); + else + _port = NULL; // triggers error response +} + + +void +RequestPortValueEvent::post_process() +{ + string msg; + if (!_port) { + _responder->respond_error("Unable to find port for get_value responder."); + } else if (_responder->client()) { + _responder->respond_ok(); + _responder->client()->set_port_value(_port_path, _value); + } else { + _responder->respond_error("Unable to find client to send port value"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/RequestPortValueEvent.hpp b/src/engine/events/RequestPortValueEvent.hpp new file mode 100644 index 00000000..dd52c535 --- /dev/null +++ b/src/engine/events/RequestPortValueEvent.hpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REQUESTPORTVALUEEVENT_H +#define REQUESTPORTVALUEEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include "types.hpp" + +using std::string; + +namespace Ingen { + +class PortImpl; +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(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& port_path); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _port_path; + PortImpl* _port; + Sample _value; +}; + + +} // namespace Ingen + +#endif // REQUESTPORTVALUEEVENT_H diff --git a/src/engine/events/SendPortActivityEvent.cpp b/src/engine/events/SendPortActivityEvent.cpp new file mode 100644 index 00000000..0ab3abdd --- /dev/null +++ b/src/engine/events/SendPortActivityEvent.cpp @@ -0,0 +1,34 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "SendPortActivityEvent.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" + +namespace Ingen { + + +void +SendPortActivityEvent::post_process() +{ + _engine.broadcaster()->send_port_activity(_port->path()); +} + + +} // namespace Ingen + diff --git a/src/engine/events/SendPortActivityEvent.hpp b/src/engine/events/SendPortActivityEvent.hpp new file mode 100644 index 00000000..dfbb8a10 --- /dev/null +++ b/src/engine/events/SendPortActivityEvent.hpp @@ -0,0 +1,67 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SENDPORTACTIVITYEVENT_H +#define SENDPORTACTIVITYEVENT_H + +#include <string> +#include "Event.hpp" +#include "types.hpp" +using std::string; + +namespace Ingen { + +class PortImpl; + + +/** A special event used internally to send port activity notification (e.g. + * MIDI event activity) from the audio thread. + * + * This is created in the audio thread (directly in a ringbuffer, not new'd) + * for processing in the post processing thread (unlike normal events which + * are created in the pre-processor thread then run through the audio + * thread). This event's job is done entirely in post_process. + * + * This only really makes sense for message ports. + * + * \ingroup engine + */ +class SendPortActivityEvent : public Event +{ +public: + inline SendPortActivityEvent(Engine& engine, + SampleCount timestamp, + PortImpl* port) + : Event(engine, SharedPtr<Responder>(), timestamp) + , _port(port) + { + } + + inline void operator=(const SendPortActivityEvent& ev) { + _port = ev._port; + } + + void post_process(); + +private: + PortImpl* _port; +}; + + +} // namespace Ingen + +#endif // SENDPORTACTIVITYEVENT_H diff --git a/src/engine/events/SendPortValueEvent.cpp b/src/engine/events/SendPortValueEvent.cpp new file mode 100644 index 00000000..d3fb0d36 --- /dev/null +++ b/src/engine/events/SendPortValueEvent.cpp @@ -0,0 +1,43 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <sstream> +#include "SendPortValueEvent.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" + +using namespace std; + +namespace Ingen { + + +void +SendPortValueEvent::post_process() +{ + // FIXME... + + if (_omni) { + _engine.broadcaster()->send_port_value(_port->path(), _value); + } else { + _engine.broadcaster()->send_port_value(_port->path(), _value); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/SendPortValueEvent.hpp b/src/engine/events/SendPortValueEvent.hpp new file mode 100644 index 00000000..e8505914 --- /dev/null +++ b/src/engine/events/SendPortValueEvent.hpp @@ -0,0 +1,78 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SENDPORTVALUEEVENT_H +#define SENDPORTVALUEEVENT_H + +#include <string> +#include "Event.hpp" +#include "types.hpp" +using std::string; + +namespace Ingen { + +class PortImpl; + + +/** A special event used internally to send port values from the audio thread. + * + * This is created in the audio thread (using in-place new on a preallocated + * buffer) for processing in the post processing thread (unlike normal events + * which are created in the pre-processor thread then run through the audio + * thread). This event's job is done entirely in post_process. + * + * This only works for control ports right now. + * + * \ingroup engine + */ +class SendPortValueEvent : public Event +{ +public: + inline SendPortValueEvent(Engine& engine, + SampleCount timestamp, + PortImpl* port, + bool omni, + uint32_t voice_num, + Sample value) + : Event(engine, SharedPtr<Responder>(), timestamp) + , _port(port) + , _omni(omni) + , _voice_num(voice_num) + , _value(value) + { + } + + inline void operator=(const SendPortValueEvent& ev) { + _port = ev._port; + _omni = ev._omni; + _voice_num = ev._voice_num; + _value = ev._value; + } + + void post_process(); + +private: + PortImpl* _port; + bool _omni; + uint32_t _voice_num; + Sample _value; +}; + + +} // namespace Ingen + +#endif // SENDPORTVALUEEVENT_H diff --git a/src/engine/events/SetMetadataEvent.cpp b/src/engine/events/SetMetadataEvent.cpp new file mode 100644 index 00000000..db4bbc30 --- /dev/null +++ b/src/engine/events/SetMetadataEvent.cpp @@ -0,0 +1,114 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "SetMetadataEvent.hpp" +#include <string> +#include <boost/format.hpp> +#include "Responder.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "GraphObjectImpl.hpp" +#include "EngineStore.hpp" + +using std::string; + +namespace Ingen { + + +SetMetadataEvent::SetMetadataEvent( + Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + bool property, + const string& path, + const string& key, + const Atom& value) + : QueuedEvent(engine, responder, timestamp) + , _error(NO_ERROR) + , _special_type(NONE) + , _property(property) + , _path(path) + , _key(key) + , _value(value) + , _object(NULL) +{ +} + + +void +SetMetadataEvent::pre_process() +{ + if (!Path::is_valid(_path)) { + _error = INVALID_PATH; + QueuedEvent::pre_process(); + return; + } + + _object = _engine.engine_store()->find_object(_path); + if (_object == NULL) { + QueuedEvent::pre_process(); + return; + } + + if (_property) + _object->set_property(_key, _value); + else + _object->set_variable(_key, _value); + + if (_key == "ingen:broadcast") { + std::cout << "BROADCAST" << std::endl; + _special_type = ENABLE_BROADCAST; + } + + QueuedEvent::pre_process(); +} + + +void +SetMetadataEvent::execute(ProcessContext& context) +{ + if (_special_type == ENABLE_BROADCAST) { + PortImpl* port = dynamic_cast<PortImpl*>(_object); + if (port) + port->broadcast(_value.get_bool()); + } + + QueuedEvent::execute(context); + // Do nothing +} + + +void +SetMetadataEvent::post_process() +{ + if (_error == INVALID_PATH) { + _responder->respond_error((boost::format("Invalid path %1% setting %2%") % _path % _key).str()); + } else if (_object == NULL) { + string msg = (boost::format("Unable to find object %1% to set %2%") % _path % _key).str(); + _responder->respond_error(msg); + } else { + _responder->respond_ok(); + if (_property) + _engine.broadcaster()->send_property_change(_path, _key, _value); + else + _engine.broadcaster()->send_variable_change(_path, _key, _value); + } +} + + +} // namespace Ingen diff --git a/src/engine/events/SetMetadataEvent.hpp b/src/engine/events/SetMetadataEvent.hpp new file mode 100644 index 00000000..6b9be81c --- /dev/null +++ b/src/engine/events/SetMetadataEvent.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SETMETADATAEVENT_H +#define SETMETADATAEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include <raul/Atom.hpp> + +using std::string; + +namespace Ingen { + +class GraphObjectImpl; + + +/** An event to set a piece of variable for an GraphObjectImpl. + * + * \ingroup engine + */ +class SetMetadataEvent : public QueuedEvent +{ +public: + SetMetadataEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + bool property, + const string& path, + const string& key, + const Raul::Atom& value); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum { NO_ERROR, INVALID_PATH } _error; + enum { NONE, ENABLE_BROADCAST } _special_type; + + bool _property; + string _path; + string _key; + Raul::Atom _value; + GraphObjectImpl* _object; +}; + + +} // namespace Ingen + +#endif // SETMETADATAEVENT_H diff --git a/src/engine/events/SetPolyphonicEvent.cpp b/src/engine/events/SetPolyphonicEvent.cpp new file mode 100644 index 00000000..d1fc6a7c --- /dev/null +++ b/src/engine/events/SetPolyphonicEvent.cpp @@ -0,0 +1,82 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include "SetPolyphonicEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "util.hpp" +#include "EngineStore.hpp" +#include "PortImpl.hpp" +#include "NodeImpl.hpp" +#include "ConnectionImpl.hpp" +#include "QueuedEventSource.hpp" + +namespace Ingen { + + +SetPolyphonicEvent::SetPolyphonicEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& path, bool poly) +: QueuedEvent(engine, responder, time, true, source), + _path(path), + _object(NULL), + _poly(poly), + _success(false) +{ +} + + +void +SetPolyphonicEvent::pre_process() +{ + _object = _engine.engine_store()->find_object(_path); + + QueuedEvent::pre_process(); +} + + +void +SetPolyphonicEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_object) + _success = _object->set_polyphonic(*_engine.maid(), _poly); + + _source->unblock(); +} + + +void +SetPolyphonicEvent::post_process() +{ + if (_object) { + if (_success) { + _responder->respond_ok(); + _engine.broadcaster()->send_property_change(_path, "ingen:polyphonic", _poly); + } else { + _responder->respond_error("Unable to set object as polyphonic"); + } + } else { + _responder->respond_error("Unable to find object to set polyphonic"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/SetPolyphonicEvent.hpp b/src/engine/events/SetPolyphonicEvent.hpp new file mode 100644 index 00000000..9079d49f --- /dev/null +++ b/src/engine/events/SetPolyphonicEvent.hpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SETPOLYPHONICEVENT_H +#define SETPOLYPHONICEVENT_H + +#include <string> +#include <raul/Array.hpp> +#include "QueuedEvent.hpp" + +using std::string; + +namespace Ingen { + +class GraphObjectImpl; + + +/** Delete all nodes from a patch. + * + * \ingroup engine + */ +class SetPolyphonicEvent : public QueuedEvent +{ +public: + SetPolyphonicEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& path, bool poly); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _path; + GraphObjectImpl* _object; + bool _poly; + bool _success; +}; + + +} // namespace Ingen + + +#endif // SETPOLYPHONICEVENT_H diff --git a/src/engine/events/SetPolyphonyEvent.cpp b/src/engine/events/SetPolyphonyEvent.cpp new file mode 100644 index 00000000..eb6550cb --- /dev/null +++ b/src/engine/events/SetPolyphonyEvent.cpp @@ -0,0 +1,79 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <raul/Maid.hpp> +#include "SetPolyphonyEvent.hpp" +#include "Responder.hpp" +#include "Engine.hpp" +#include "PatchImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "util.hpp" +#include "EngineStore.hpp" +#include "PortImpl.hpp" +#include "NodeImpl.hpp" +#include "ConnectionImpl.hpp" +#include "QueuedEventSource.hpp" + +namespace Ingen { + + +SetPolyphonyEvent::SetPolyphonyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path, uint32_t poly) +: QueuedEvent(engine, responder, time, true, source), + _patch_path(patch_path), + _patch(NULL), + _poly(poly) +{ +} + + +void +SetPolyphonyEvent::pre_process() +{ + _patch = _engine.engine_store()->find_patch(_patch_path); + if (_patch && _poly > _patch->internal_polyphony()) + _patch->prepare_internal_poly(_poly); + + QueuedEvent::pre_process(); +} + + +void +SetPolyphonyEvent::execute(ProcessContext& context) +{ + QueuedEvent::execute(context); + + if (_patch) + _patch->apply_internal_poly(*_engine.maid(), _poly); + + _source->unblock(); +} + + +void +SetPolyphonyEvent::post_process() +{ + if (_patch) { + _responder->respond_ok(); + _engine.broadcaster()->send_property_change(_patch_path, "ingen:polyphony", (int32_t)_poly); + } else { + _responder->respond_error("Unable to find patch"); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/SetPolyphonyEvent.hpp b/src/engine/events/SetPolyphonyEvent.hpp new file mode 100644 index 00000000..8aba997a --- /dev/null +++ b/src/engine/events/SetPolyphonyEvent.hpp @@ -0,0 +1,56 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SETPOLYPHONYEVENT_H +#define SETPOLYPHONYEVENT_H + +#include <string> +#include <raul/Array.hpp> +#include "QueuedEvent.hpp" + +using std::string; + +namespace Ingen { + +class PatchImpl; + + +/** Delete all nodes from a patch. + * + * \ingroup engine + */ +class SetPolyphonyEvent : public QueuedEvent +{ +public: + SetPolyphonyEvent(Engine& engine, SharedPtr<Responder> responder, FrameTime time, QueuedEventSource* source, const string& patch_path, uint32_t poly); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + const string _patch_path; + PatchImpl* _patch; + const uint32_t _poly; + +}; + + +} // namespace Ingen + + +#endif // SETPOLYPHONYEVENT_H diff --git a/src/engine/events/SetPortValueEvent.cpp b/src/engine/events/SetPortValueEvent.cpp new file mode 100644 index 00000000..a99b9f9a --- /dev/null +++ b/src/engine/events/SetPortValueEvent.cpp @@ -0,0 +1,224 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <sstream> +#include <lv2ext/lv2_event.h> +#include "Responder.hpp" +#include "SetPortValueEvent.hpp" +#include "Engine.hpp" +#include "PortImpl.hpp" +#include "ClientBroadcaster.hpp" +#include "NodeImpl.hpp" +#include "EngineStore.hpp" +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "ProcessContext.hpp" +#include "MessageContext.hpp" + +using namespace std; + +namespace Ingen { + + +/** Omni (all voices) control setting */ +SetPortValueEvent::SetPortValueEvent(Engine& engine, + SharedPtr<Responder> responder, + bool queued, + SampleCount timestamp, + const string& port_path, + const Raul::Atom& value) + : QueuedEvent(engine, responder, timestamp) + , _queued(queued) + , _omni(true) + , _voice_num(0) + , _port_path(port_path) + , _value(value) + , _port(NULL) + , _error(NO_ERROR) +{ +} + + +/** Voice-specific control setting */ +SetPortValueEvent::SetPortValueEvent(Engine& engine, + SharedPtr<Responder> responder, + bool queued, + SampleCount timestamp, + uint32_t voice_num, + const string& port_path, + const Raul::Atom& value) + : QueuedEvent(engine, responder, timestamp) + , _queued(queued) + , _omni(false) + , _voice_num(voice_num) + , _port_path(port_path) + , _value(value) + , _port(NULL) + , _error(NO_ERROR) +{ +} + + +SetPortValueEvent::~SetPortValueEvent() +{ +} + + +void +SetPortValueEvent::pre_process() +{ + if (_queued) { + if (_port == NULL) { + if (Path::is_valid(_port_path)) + _port = _engine.engine_store()->find_port(_port_path); + else + _error = ILLEGAL_PATH; + } + + if (_port == NULL && _error == NO_ERROR) + _error = PORT_NOT_FOUND; + } + + // Port is a message context port, set its value and + // call the plugin's message run function once + if (_port && _port->context() == Context::MESSAGE) { + _engine.message_context()->run(_port->parent_node()); + } + + QueuedEvent::pre_process(); +} + + +void +SetPortValueEvent::execute(ProcessContext& context) +{ + Event::execute(context); + assert(_time >= context.start() && _time <= context.end()); + + if (_port && _port->context() == Context::MESSAGE) + return; + + if (_error == NO_ERROR && _port == NULL) { + if (Path::is_valid(_port_path)) + _port = _engine.engine_store()->find_port(_port_path); + else + _error = ILLEGAL_PATH; + } + + if (_port == NULL) { + if (_error == NO_ERROR) + _error = PORT_NOT_FOUND; + /*} else if (_port->buffer(0)->capacity() < _data_size) { + _error = NO_SPACE;*/ + } else { + Buffer* const buf = _port->buffer(0); + AudioBuffer* const abuf = dynamic_cast<AudioBuffer*>(buf); + if (abuf) { + if (_value.type() != Atom::FLOAT) { + _error = TYPE_MISMATCH; + return; + } + + if (_omni) { + for (uint32_t i=0; i < _port->poly(); ++i) + ((AudioBuffer*)_port->buffer(i))->set_value( + _value.get_float(), context.start(), _time); + } else { + if (_voice_num < _port->poly()) + ((AudioBuffer*)_port->buffer(_voice_num))->set_value( + _value.get_float(), context.start(), _time); + else + _error = ILLEGAL_VOICE; + } + return; + } + + EventBuffer* const ebuf = dynamic_cast<EventBuffer*>(buf); + + const LV2Features::Feature* f = _engine.world()->lv2_features->feature(LV2_URI_MAP_URI); + LV2URIMap* map = (LV2URIMap*)f->controller; + + // FIXME: eliminate lookups + // FIXME: need a proper prefix system + if (ebuf && _value.type() == Atom::BLOB) { + const uint32_t frames = std::max( + (uint32_t)(_time - context.start()), + ebuf->latest_frames()); + + // Size 0 event, pass it along to the plugin as a typed but empty event + if (_value.data_size() == 0) { + cout << "BANG!" << endl; + const uint32_t type_id = map->uri_to_id(NULL, _value.get_blob_type()); + ebuf->append(frames, 0, type_id, 0, NULL); + _port->raise_set_by_user_flag(); + return; + + } else if (!strcmp(_value.get_blob_type(), "lv2_midi:MidiEvent")) { + const uint32_t type_id = map->uri_to_id(NULL, + "http://lv2plug.in/ns/ext/midi#MidiEvent"); + + ebuf->prepare_write(context.start(), context.nframes()); + // FIXME: use OSC midi type? avoid MIDI over OSC entirely? + ebuf->append(frames, 0, type_id, _value.data_size(), + (const uint8_t*)_value.get_blob()); + _port->raise_set_by_user_flag(); + return; + } + } + + if (_value.type() == Atom::BLOB) + cerr << "WARNING: Unknown value blob type " << _value.get_blob_type() << endl; + else + cerr << "WARNING: Unknown value type " << (int)_value.type() << endl; + } +} + + +void +SetPortValueEvent::post_process() +{ + if (_error == NO_ERROR) { + assert(_port != NULL); + _responder->respond_ok(); + _engine.broadcaster()->send_port_value(_port_path, _value); + + } else if (_error == ILLEGAL_PATH) { + string msg = "Illegal port path \""; + msg.append(_port_path).append("\""); + _responder->respond_error(msg); + + } else if (_error == ILLEGAL_VOICE) { + std::ostringstream ss; + ss << "Illegal voice number " << _voice_num; + _responder->respond_error(ss.str()); + + } else if (_error == PORT_NOT_FOUND) { + string msg = "Unable to find port "; + msg.append(_port_path).append(" for set_port_value"); + _responder->respond_error(msg); + + } else if (_error == NO_SPACE) { + std::ostringstream msg("Attempt to write "); + msg << _value.data_size() << " bytes to " << _port_path << ", with capacity " + << _port->buffer_size() << endl; + _responder->respond_error(msg.str()); + } +} + + +} // namespace Ingen + diff --git a/src/engine/events/SetPortValueEvent.hpp b/src/engine/events/SetPortValueEvent.hpp new file mode 100644 index 00000000..2fc68d9b --- /dev/null +++ b/src/engine/events/SetPortValueEvent.hpp @@ -0,0 +1,80 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SETPORTVALUEEVENT_H +#define SETPORTVALUEEVENT_H + +#include <string> +#include "QueuedEvent.hpp" +#include "types.hpp" +using std::string; + +namespace Ingen { + +class PortImpl; + + +/** An event to change the value of a port. + * + * This event can either be queued or immediate, depending on the queued + * parameter passed to the constructor. It must be passed to the appropriate + * place (ie queued event passed to the event queue and non-queued event + * processed in the audio thread) or nasty things will happen. + * + * \ingroup engine + */ +class SetPortValueEvent : public QueuedEvent +{ +public: + SetPortValueEvent(Engine& engine, + SharedPtr<Responder> responder, + bool queued, + SampleCount timestamp, + const string& port_path, + const Raul::Atom& value); + + SetPortValueEvent(Engine& engine, + SharedPtr<Responder> responder, + bool queued, + SampleCount timestamp, + uint32_t voice_num, + const string& port_path, + const Raul::Atom& value); + + ~SetPortValueEvent(); + + void pre_process(); + void execute(ProcessContext& context); + void post_process(); + +private: + enum ErrorType { NO_ERROR, PORT_NOT_FOUND, NO_SPACE, + ILLEGAL_PATH, ILLEGAL_VOICE, TYPE_MISMATCH }; + + bool _queued; + bool _omni; + uint32_t _voice_num; + const string _port_path; + const Raul::Atom _value; + PortImpl* _port; + ErrorType _error; +}; + + +} // namespace Ingen + +#endif // SETPORTVALUEEVENT_H diff --git a/src/engine/events/UnregisterClientEvent.cpp b/src/engine/events/UnregisterClientEvent.cpp new file mode 100644 index 00000000..72ab8048 --- /dev/null +++ b/src/engine/events/UnregisterClientEvent.cpp @@ -0,0 +1,45 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "interface/ClientInterface.hpp" +#include "Responder.hpp" +#include "UnregisterClientEvent.hpp" +#include "Engine.hpp" +#include "ClientBroadcaster.hpp" + +namespace Ingen { + + +UnregisterClientEvent::UnregisterClientEvent(Engine& engine, SharedPtr<Responder> responder, SampleCount timestamp, const string& uri) +: QueuedEvent(engine, responder, timestamp) +, _uri(uri) +{ +} + + +void +UnregisterClientEvent::post_process() +{ + if (_engine.broadcaster()->unregister_client(_uri)) + _responder->respond_ok(); + else + _responder->respond_error("Unable to unregister client"); +} + + +} // namespace Ingen + diff --git a/src/engine/events/UnregisterClientEvent.hpp b/src/engine/events/UnregisterClientEvent.hpp new file mode 100644 index 00000000..c21bc5da --- /dev/null +++ b/src/engine/events/UnregisterClientEvent.hpp @@ -0,0 +1,54 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef UNREGISTERCLIENTEVENT_H +#define UNREGISTERCLIENTEVENT_H + +#include "QueuedEvent.hpp" +#include <string> +using std::string; + +namespace Ingen { + +namespace Shared { + class ClientInterface; +} +using Shared::ClientInterface; + + +/** Unregisters an OSC client so it no longer receives notifications. + * + * \ingroup engine + */ +class UnregisterClientEvent : public QueuedEvent +{ +public: + UnregisterClientEvent(Engine& engine, + SharedPtr<Responder> responder, + SampleCount timestamp, + const string& uri); + + void post_process(); + +private: + string _uri; +}; + + +} // namespace Ingen + +#endif // UNREGISTERCLIENTEVENT_H diff --git a/src/engine/jack_compat.h b/src/engine/jack_compat.h new file mode 100644 index 00000000..792148e8 --- /dev/null +++ b/src/engine/jack_compat.h @@ -0,0 +1,56 @@ +/* JACK MIDI API compatibility hacks. + * Copyright (C) 2007 Nedko Arnaudov <nedko@arnaudov.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef JACK_COMPAT_H +#define JACK_COMPAT_H + + +#if defined(JACK_MIDI_NEEDS_NFRAMES) + +jack_nframes_t +jack_midi_get_event_count_compat( + void * port_buffer) +{ +#if defined(HAVE_OLD_JACK_MIDI) + return jack_midi_port_get_info(port_buffer, 0)->event_count; +#else + return jack_midi_get_event_count(port_buffer, 0); +#endif +} + +#define jack_midi_get_event_count jack_midi_get_event_count_compat + +int +jack_midi_event_get_compat( + jack_midi_event_t * event, + void * port_buffer, + jack_nframes_t event_index) +{ + return jack_midi_event_get(event, port_buffer, event_index, 0); +} + +#define jack_midi_event_get jack_midi_event_get_compat + +#else + +#if defined(HAVE_OLD_JACK_MIDI) +#error "Old (0.102.20) JACK MIDI API needs nframes (autotools probably gone mad)" +#endif + +#endif /* #if defined(JACK_MIDI_NEEDS_NFRAMES) */ + +#endif /* JACK_COMPAT_H */ diff --git a/src/engine/lv2_contexts.h b/src/engine/lv2_contexts.h new file mode 120000 index 00000000..8556a582 --- /dev/null +++ b/src/engine/lv2_contexts.h @@ -0,0 +1 @@ +../../../../lv2/lv2/contexts/lv2_contexts.h
\ No newline at end of file diff --git a/src/engine/tuning.hpp b/src/engine/tuning.hpp new file mode 100644 index 00000000..7ec55ae4 --- /dev/null +++ b/src/engine/tuning.hpp @@ -0,0 +1,39 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TUNING_H +#define TUNING_H + +#include <stddef.h> +#include <time.h> + +namespace Ingen { + +// 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; + +//static const timespec main_rate = { 0, 500000000 }; // 1/2 second +static const timespec main_rate = { 0, 125000000 }; // 1/8 second + + +} // namespace Ingen + +#endif // TUNING_H diff --git a/src/engine/types.hpp b/src/engine/types.hpp new file mode 100644 index 00000000..d242e61d --- /dev/null +++ b/src/engine/types.hpp @@ -0,0 +1,33 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef TYPES_HPP +#define TYPES_HPP + +#include <cstddef> // for NULL, size_t, etc +#include <jack/jack.h> + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef jack_default_audio_sample_t Sample; +typedef jack_nframes_t SampleCount; +typedef jack_nframes_t SampleRate; +typedef jack_nframes_t FrameTime; + +#endif // TYPES_HPP diff --git a/src/engine/util.hpp b/src/engine/util.hpp new file mode 100644 index 00000000..8f97beb3 --- /dev/null +++ b/src/engine/util.hpp @@ -0,0 +1,78 @@ +/* This file is part of Ingen. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef UTIL_HPP +#define UTIL_HPP + +#include CONFIG_H_PATH +#include <iostream> +#include <cstdlib> + +#include <fenv.h> +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +#ifdef USE_ASSEMBLY +# if SIZEOF_VOID_P==8 +# define cpuid(a,b,c,d,n) asm("xchgq %%rbx, %1; cpuid; xchgq %%rbx, %1": "=a" (a), "=r" (b), "=c" (c), "=d" (d) : "a" (n)); +# else +# define cpuid(a,b,c,d,n) asm("xchgl %%ebx, %1; cpuid; xchgl %%ebx, %1": "=a" (a), "=r" (b), "=c" (c), "=d" (d) : "a" (n)); +# endif +#endif + +namespace Ingen { + +/** Set flags to disable denormal processing. + */ +inline void +set_denormal_flags() +{ +#ifdef USE_ASSEMBLY +#ifdef __SSE__ + unsigned long a, b, c, d0, d1; + int stepping, model, family, extfamily; + + cpuid(a,b,c,d1,1); + if (d1 & 1<<25) { /* It has SSE support */ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + family = (a >> 8) & 0xf; + extfamily = (a >> 20) & 0xff; + model = (a >> 4) & 0xf; + stepping = a & 0xf; + cpuid(a,b,c,d0,0); + if (b == 0x756e6547) { /* It's an Intel */ + if (family == 15 && extfamily == 0 && model == 0 && stepping < 7) { + return; + } + } + if (d1 & 1<<26) { /* bit 26, SSE2 support */ + _mm_setcsr(_mm_getcsr() | 0x8040); // set DAZ and FZ bits of MXCSR + //cerr << "Set SSE denormal fix flag." << endl; + } + } else { + std::cerr << "This code has been built with SSE support, but your processor does" + << " not support the SSE instruction set." << std::endl << "Exiting." << std::endl; + exit(EXIT_FAILURE); + } +#endif +#endif +} + +} // namespace Ingen + +#endif // UTIL_HPP diff --git a/src/engine/wscript b/src/engine/wscript new file mode 100644 index 00000000..1ad135d3 --- /dev/null +++ b/src/engine/wscript @@ -0,0 +1,73 @@ +#!/usr/bin/env python +import Params + +def build(bld): + obj = bld.create_obj('cpp', 'shlib') + obj.source = ''' + AudioBuffer.cpp + Buffer.cpp + ClientBroadcaster.cpp + ConnectionImpl.cpp + DuplexPort.cpp + Engine.cpp + EngineStore.cpp + Event.cpp + EventBuffer.cpp + EventSink.cpp + InputPort.cpp + InternalPlugin.cpp + JackAudioDriver.cpp + JackMidiDriver.cpp + LADSPAPlugin.cpp + LV2Info.cpp + LV2Plugin.cpp + MessageContext.cpp + MidiControlNode.cpp + MidiNoteNode.cpp + MidiTriggerNode.cpp + NodeBase.cpp + NodeFactory.cpp + OSCClientSender.cpp + OSCEngineReceiver.cpp + ObjectSender.cpp + OutputPort.cpp + PluginImpl.cpp + PortImpl.cpp + PostProcessor.cpp + ProcessSlave.cpp + QueuedEngineInterface.cpp + QueuedEvent.cpp + QueuedEventSource.cpp + TransportNode.cpp + engine.cpp + events/AllNotesOffEvent.cpp + events/ConnectionEvent.cpp + events/CreateNodeEvent.cpp + events/CreatePortEvent.cpp + events/DeactivateEvent.cpp + events/DestroyEvent.cpp + events/DisconnectAllEvent.cpp + events/DisconnectionEvent.cpp + events/LoadPluginsEvent.cpp + events/MidiLearnEvent.cpp + events/NoteEvent.cpp + events/RegisterClientEvent.cpp + events/RenameEvent.cpp + events/RequestAllObjectsEvent.cpp + events/RequestMetadataEvent.cpp + events/RequestObjectEvent.cpp + events/RequestPluginEvent.cpp + events/RequestPluginsEvent.cpp + events/RequestPortValueEvent.cpp + events/SendPortActivityEvent.cpp + events/SendPortValueEvent.cpp + events/SetMetadataEvent.cpp + events/SetPortValueEvent.cpp + events/UnregisterClientEvent.cpp + ''' + obj.includes = ['..', '../../common', '../..', './events'] + obj.name = 'libingen_engine' + obj.target = 'ingen_engine' + obj.uselib = 'GLIBMM GTHREAD SLV2 JACK LIBLO RAUL REDLANDMM SOUP' + obj.vnum = '0.0.0' + |