diff options
Diffstat (limited to 'src/engine/InputPort.cpp')
-rw-r--r-- | src/engine/InputPort.cpp | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/src/engine/InputPort.cpp b/src/engine/InputPort.cpp new file mode 100644 index 00000000..3837da1b --- /dev/null +++ b/src/engine/InputPort.cpp @@ -0,0 +1,352 @@ +/* This file is part of Om. Copyright (C) 2006 Dave Robillard. + * + * Om is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Om is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "InputPort.h" +#include <iostream> +#include <cstdlib> +#include <cassert> +#include "ConnectionBase.h" +#include "OutputPort.h" +#include "PortInfo.h" +#include "Node.h" +#include "Om.h" +#include "util.h" + +using std::cerr; using std::cout; using std::endl; + + +namespace Om { + + +template <typename T> +InputPort<T>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size) +: PortBase<T>(node, name, index, poly, port_info, buffer_size) +{ + assert(port_info->is_input() && !port_info->is_output()); +} +template InputPort<sample>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); +template InputPort<MidiMessage>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size); + + +/** Add a connection. Realtime safe. + * + * The buffer of this port will be set directly to the connection's buffer + * if there is only one connection, since no mixing needs to take place. + */ +template<typename T> +void +InputPort<T>::add_connection(ListNode<ConnectionBase<T>*>* const c) +{ + m_connections.push_back(c); + + bool modify_buffers = !m_fixed_buffers; + if (modify_buffers && m_is_tied) + modify_buffers = !m_tied_port->fixed_buffers(); + + if (modify_buffers) { + if (m_connections.size() == 1) { + // Use buffer directly to avoid copying + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->join(c->elem()->buffer(i)); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + assert(m_buffers.at(i)->data() == c->elem()->buffer(i)->data()); + } + } else if (m_connections.size() == 2) { + // Used to directly use single connection buffer, now there's two + // so have to use local ones again and mix down + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->unjoin(); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } + update_buffers(); + } + + assert( ! m_is_tied || m_tied_port != NULL); + assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); +} +template void InputPort<sample>::add_connection(ListNode<ConnectionBase<sample>*>* const c); +template void InputPort<MidiMessage>::add_connection(ListNode<ConnectionBase<MidiMessage>*>* const c); + + +/** Remove a connection. Realtime safe. + */ +template <typename T> +ListNode<ConnectionBase<T>*>* +InputPort<T>::remove_connection(const OutputPort<T>* const src_port) +{ + bool modify_buffers = !m_fixed_buffers; + if (modify_buffers && m_is_tied) + modify_buffers = !m_tied_port->fixed_buffers(); + + typedef typename List<ConnectionBase<T>*>::iterator ConnectionBaseListIterator; + bool found = false; + ListNode<ConnectionBase<T>*>* connection = NULL; + for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i) { + if ((*i)->src_port()->path() == src_port->path()) { + connection = m_connections.remove(i); + found = true; + } + } + + if ( ! found) { + cerr << "WARNING: [InputPort<T>::remove_connection] Connection not found !" << endl; + exit(EXIT_FAILURE); + } else { + if (m_connections.size() == 0) { + for (size_t i=0; i < m_poly; ++i) { + // Use a local buffer + if (modify_buffers && m_buffers.at(i)->is_joined()) + m_buffers.at(i)->unjoin(); + m_buffers.at(i)->clear(); // Write silence + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } else if (modify_buffers && m_connections.size() == 1) { + // Share a buffer + for (size_t i=0; i < m_poly; ++i) { + m_buffers.at(i)->join((*m_connections.begin())->buffer(i)); + if (m_is_tied) + m_tied_port->buffer(i)->join(m_buffers.at(i)); + } + } + } + + if (modify_buffers) + update_buffers(); + + assert( ! m_is_tied || m_tied_port != NULL); + assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + return connection; +} +template ListNode<ConnectionBase<sample>*>* +InputPort<sample>::remove_connection(const OutputPort<sample>* const src_port); +template ListNode<ConnectionBase<MidiMessage>*>* +InputPort<MidiMessage>::remove_connection(const OutputPort<MidiMessage>* const src_port); + + +/** Update any changed buffers with the plugin this is a port on. + * + * This calls ie the LADSPA connect_port function when buffers have been changed + * due to a connection or disconnection. + */ +template <typename T> +void +InputPort<T>::update_buffers() +{ + for (size_t i=0; i < m_poly; ++i) + InputPort<T>::parent_node()->set_port_buffer(i, m_index, m_buffers.at(i)->data()); +} +template void InputPort<sample>::update_buffers(); +template void InputPort<MidiMessage>::update_buffers(); + + +/** Returns whether this port is connected to the passed port. + */ +template <typename T> +bool +InputPort<T>::is_connected_to(const OutputPort<T>* const port) const +{ + typedef typename List<ConnectionBase<T>*>::const_iterator ConnectionBaseListIterator; + for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i) + if ((*i)->src_port() == port) + return true; + + return false; +} +template bool InputPort<sample>::is_connected_to(const OutputPort<sample>* const port) const; +template bool InputPort<MidiMessage>::is_connected_to(const OutputPort<MidiMessage>* const port) const; + + +/** "Ties" this port to an OutputPort, so they share the same buffer. + * + * This is used by OutputNode and InputNode to provide two different ports + * (internal and external) that share a buffer. + */ +template <typename T> +void +InputPort<T>::tie(OutputPort<T>* const port) +{ + assert((Port*)port != (Port*)this); + assert(port->poly() == this->poly()); + assert(!m_is_tied); + assert(m_tied_port == NULL); + + if (Port::parent_node() != NULL) { + assert(m_poly == port->poly()); + + for (size_t i=0; i < m_poly; ++i) + port->buffer(i)->join(m_buffers.at(i)); + } + m_is_tied = true; + m_tied_port = port; + port->set_tied_port(this); + + assert(m_buffers.at(0)->data() == port->buffer(0)->data()); + + //cerr << "*** Tied " << this->path() << " <-> " << port->path() << endl; +} +template void InputPort<sample>::tie(OutputPort<sample>* const port); +template void InputPort<MidiMessage>::tie(OutputPort<MidiMessage>* const port); + + +/** Prepare buffer for access, mixing if necessary. Realtime safe. + * FIXME: nframes parameter not used, + */ +template<> +void +InputPort<sample>::prepare_buffers(size_t nframes) +{ + assert(!m_is_tied || m_tied_port != NULL); + + typedef List<ConnectionBase<sample>*>::iterator ConnectionBaseListIterator; + bool do_mixdown = true; + + if (m_connections.size() == 0) return; + + for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c) + (*c)->prepare_buffers(); + + // If only one connection, buffer is (maybe) used directly (no copying) + if (m_connections.size() == 1) { + // Buffer changed since connection + if (m_buffers.at(0)->data() != (*m_connections.begin())->buffer(0)->data()) { + if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) { + // can't change buffer, must copy + do_mixdown = true; + } else { + // zero-copy + assert(m_buffers.at(0)->is_joined()); + m_buffers.at(0)->join((*m_connections.begin())->buffer(0)); + do_mixdown = false; + } + } else { + do_mixdown = false; + } + update_buffers(); + } + + if (!do_mixdown) { + assert(m_buffers.at(0)->data() == (*m_connections.begin())->buffer(0)->data()); + return; + } + + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + for (size_t voice=0; voice < m_poly; ++voice) { + m_buffers.at(voice)->copy((*m_connections.begin())->buffer(voice), 0, m_buffer_size-1); + + if (m_connections.size() > 1) { + // Copy first connection + ConnectionBaseListIterator c = m_connections.begin(); + + // Add all other connections + for (++c; c != m_connections.end(); ++c) + m_buffers.at(voice)->accumulate((*c)->buffer(voice), 0, m_buffer_size-1); + } + } +} + + +/** Prepare buffer for access, realtime safe. + * + * MIDI mixing not yet implemented. + */ +template <> +void +InputPort<MidiMessage>::prepare_buffers(size_t nframes) +{ + assert(!m_is_tied || m_tied_port != NULL); + + const size_t num_ins = m_connections.size(); + bool do_mixdown = true; + + assert(num_ins == 0 || num_ins == 1); + + typedef List<ConnectionBase<MidiMessage>*>::iterator ConnectionBaseListIterator; + assert(m_poly == 1); + + for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c) + (*c)->prepare_buffers(); + + + // If only one connection, buffer is used directly (no copying) + if (num_ins == 1) { + // Buffer changed since connection + if (m_buffers.at(0) != (*m_connections.begin())->buffer(0)) { + if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) { + // can't change buffer, must copy + do_mixdown = true; + } else { + // zero-copy + assert(m_buffers.at(0)->is_joined()); + m_buffers.at(0)->join((*m_connections.begin())->buffer(0)); + if (m_is_tied) + m_tied_port->buffer(0)->join(m_buffers.at(0)); + do_mixdown = false; + } + update_buffers(); + } else { + do_mixdown = false; + } + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + assert(!m_is_tied || m_buffers.at(0)->filled_size() == m_tied_port->buffer(0)->filled_size()); + assert(do_mixdown || m_buffers.at(0)->filled_size() == + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + } + + // Get valid buffer size from inbound connections, unless a port on a top-level + // patch (which will be fed by the MidiDriver) + if (m_parent->parent() != NULL) { + if (num_ins == 1) { + m_buffers.at(0)->filled_size( + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + + if (m_is_tied) + m_tied_port->buffer(0)->filled_size(m_buffers.at(0)->filled_size()); + + assert(m_buffers.at(0)->filled_size() == + (*m_connections.begin())->src_port()->buffer(0)->filled_size()); + } else { + // Mixing not implemented + m_buffers.at(0)->clear(); + } + } + + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + if (!do_mixdown || m_buffers.at(0)->filled_size() == 0 || num_ins == 0) + return; + + //cerr << path() << " - Copying MIDI buffer" << endl; + + // Be sure buffers are the same as tied port's, if joined + assert(!m_is_tied || m_tied_port != NULL); + assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data()); + + if (num_ins > 0) + for (size_t i=0; i < m_buffers.at(0)->filled_size(); ++i) + m_buffers.at(0)[i] = (*m_connections.begin())->buffer(0)[i]; +} + + +} // namespace Om + |