andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: Node.h: template class which wraps a circular buffer, providing a collection andrewm@0: of methods for inserting, accessing and iterating over data (including andrewm@0: interpolation). Also provides trigger functionality to notify listeners when andrewm@0: new objects are inserted. Unlike conventional containers, indices always increment andrewm@0: over time, even though only the last N objects will be held in the buffer. The andrewm@0: beginning index will therefore be greater than 0 in many cases, but the index of andrewm@0: a particular item will never change, even if the item itself eventually is dropped andrewm@0: from the buffer. andrewm@0: */ andrewm@0: andrewm@0: #ifndef KEYCONTROL_NODE_H andrewm@0: #define KEYCONTROL_NODE_H andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@20: #include andrewm@20: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "Types.h" andrewm@0: #include "Trigger.h" andrewm@20: #include "../JuceLibraryCode/JuceHeader.h" andrewm@20: andrewm@0: andrewm@0: template class NodeNonInterpolating; andrewm@0: template class Node; andrewm@0: andrewm@0: /* andrewm@0: * NodeIterator andrewm@0: * andrewm@0: * Abstract class that implements common functionality for all types of Sources and Filters andrewm@0: * Modeled on (and contains) boost::cb_details::iterator andrewm@0: * See boost/circular_buffer/details.hpp for more information andrewm@0: * andrewm@0: */ andrewm@0: andrewm@0: // Custom iterator type to move through the Node buffer andrewm@0: template andrewm@0: struct NodeIterator : andrewm@0: public boost::iterator< andrewm@0: std::random_access_iterator_tag, andrewm@0: typename Traits::value_type, andrewm@0: typename Traits::difference_type, andrewm@0: typename Traits::pointer, andrewm@0: typename Traits::reference> andrewm@0: { andrewm@0: typedef boost::iterator< andrewm@0: std::random_access_iterator_tag, andrewm@0: typename Traits::value_type, andrewm@0: typename Traits::difference_type, andrewm@0: typename Traits::pointer, andrewm@0: typename Traits::reference> base_iterator; andrewm@0: andrewm@0: typedef NodeNonInterpolating Buff; andrewm@0: andrewm@0: typedef NodeIterator nonconst_self; andrewm@0: typedef typename boost::cb_details::iterator, NonConstTraits> cb_iterator; andrewm@0: andrewm@0: typedef typename base_iterator::value_type value_type; andrewm@0: typedef typename base_iterator::pointer pointer; andrewm@0: typedef typename base_iterator::reference reference; andrewm@0: typedef typename Traits::size_type size_type; andrewm@0: typedef typename base_iterator::difference_type difference_type; andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: // Pointer to the Node object andrewm@0: Buff* m_buff; andrewm@0: andrewm@0: // Base (non-const) iterator to the circular buffer andrewm@0: cb_iterator m_cb_it; andrewm@0: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default constructor andrewm@0: NodeIterator() : m_buff(0), m_cb_it(cb_iterator()) {} andrewm@0: andrewm@0: // Copy constructor andrewm@0: NodeIterator(const nonconst_self& it) : m_cb_it(it.m_cb_it) {} andrewm@0: andrewm@0: // Constructor based on a circular_buffer iterator andrewm@0: NodeIterator(Buff* cb, const cb_iterator cb_it) : m_buff(cb), m_cb_it(cb_it) {} andrewm@0: andrewm@0: // ***** Operators ***** andrewm@0: // andrewm@0: // Modeled on boost::cb_details::iterator (boost/circular_buffer/details.hpp) andrewm@0: andrewm@0: NodeIterator& operator = (const NodeIterator& it) { andrewm@0: if (this == &it) andrewm@0: return *this; andrewm@0: m_buff = it.m_buff; andrewm@0: m_cb_it = it.m_cb_it; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: // Dereferencing operator. We change the behavior here to evaluate missing values as needed. andrewm@0: // Note that this requires m_cb_it to be a non_const type, even if we are a const iterator. andrewm@0: andrewm@0: reference operator * () const { andrewm@0: return *m_cb_it; andrewm@0: //reference val = *m_cb_it; andrewm@0: //if(!missing_value::isMissing(val)) andrewm@0: // return val; andrewm@0: //return (*m_cb_it = m_buff->evaluate(index())); andrewm@0: } andrewm@0: andrewm@0: pointer operator -> () const { return &(operator*()); } andrewm@0: andrewm@0: template andrewm@0: difference_type operator - (const NodeIterator& it) const { return m_cb_it - it.m_cb_it; } andrewm@0: andrewm@0: NodeIterator& operator ++ () { // ++it andrewm@0: ++m_cb_it; andrewm@0: return *this; andrewm@0: } andrewm@0: NodeIterator operator ++ (int) { // it++ andrewm@0: NodeIterator tmp = *this; andrewm@0: ++m_cb_it; andrewm@0: return tmp; andrewm@0: } andrewm@0: NodeIterator& operator -- () { // --it andrewm@0: --m_cb_it; andrewm@0: return *this; andrewm@0: } andrewm@0: NodeIterator operator -- (int) { // it-- andrewm@0: NodeIterator tmp = *this; andrewm@0: m_cb_it--; andrewm@0: return tmp; andrewm@0: } andrewm@0: NodeIterator& operator += (difference_type n) { // it += n andrewm@0: m_cb_it += n; andrewm@0: return *this; andrewm@0: } andrewm@0: NodeIterator& operator -= (difference_type n) { // it -= n andrewm@0: m_cb_it -= n; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: NodeIterator operator + (difference_type n) const { return NodeIterator(*this) += n; } andrewm@0: NodeIterator operator - (difference_type n) const { return NodeIterator(*this) -= n; } andrewm@0: andrewm@0: reference operator [] (difference_type n) const { return *(*this + n); } andrewm@0: andrewm@0: // ***** Comparisons ***** andrewm@0: // andrewm@0: // This iterator class implements some unusual comparison behavior: two iterators are evaluated by their offset within andrewm@0: // their respective buffers, even if they point to separate buffers. When used on synchronized buffers, this allows andrewm@0: // us to evaluate which of two iterators points to the earlier event. andrewm@0: andrewm@0: template andrewm@0: bool operator == (const NodeIterator& it) const { andrewm@0: return index() == it.index(); andrewm@0: } andrewm@0: andrewm@0: template andrewm@0: bool operator != (const NodeIterator& it) const { andrewm@0: return index() != it.index(); andrewm@0: } andrewm@0: andrewm@0: template andrewm@0: bool operator < (const NodeIterator& it) const { andrewm@0: return index() < it.index(); andrewm@0: } andrewm@0: andrewm@0: template andrewm@0: bool operator > (const NodeIterator& it) const { return it < *this; } andrewm@0: andrewm@0: template andrewm@0: bool operator <= (const NodeIterator& it) const { return !(it < *this); } andrewm@0: andrewm@0: template andrewm@0: bool operator >= (const NodeIterator& it) const { return !(*this < it); } andrewm@0: andrewm@0: // ***** Special Methods ***** andrewm@0: andrewm@0: // Return the offset within the buffer for this iterator's current location andrewm@0: // Can be used with at() or operator[], and can be used to compare relative locations andrewm@0: // of two iterators, even if they don't refer to the same buffer andrewm@0: andrewm@0: size_type index() const { andrewm@0: return (size_type)(*this - m_buff->begin() + m_buff->firstSampleIndex_); andrewm@0: } andrewm@0: andrewm@0: // Return the timestamp associated with the sample this iterator points to andrewm@0: andrewm@0: timestamp_type timestamp() const { return m_buff->timestampAt(index()); } andrewm@0: andrewm@0: // Tells us whether the index points to a valid place in the buffer. andrewm@0: // TODO: make sure this is right andrewm@0: // bool isValid() const { return (index() >= m_buff->beginIndex() && index() <= (m_buff->endIndex() - 1)); } andrewm@0: }; andrewm@0: andrewm@0: /* andrewm@0: * NodeReverseIterator andrewm@0: * andrewm@0: * Adapter for reverse iterators on Node classes, preserving some of the andrewm@0: * special behavior the iterators implement. andrewm@0: */ andrewm@0: andrewm@0: template andrewm@0: struct NodeReverseIterator : public boost::reverse_iterator { andrewm@0: NodeReverseIterator() {} andrewm@0: explicit NodeReverseIterator(T baseIt) : boost::reverse_iterator(baseIt) {} andrewm@0: andrewm@0: typedef typename boost::reverse_iterator::base_type::size_type size_type; andrewm@0: andrewm@0: size_type index() const { return boost::prior(this->base_reference()).index(); } andrewm@0: timestamp_type timestamp() const { return boost::prior(this->base_reference()).timestamp(); } andrewm@0: }; andrewm@0: andrewm@0: /* andrewm@0: * NodeInterpolatedIterator andrewm@0: * andrewm@0: * Extends the iterator concept to fractional indices, using linear interpolation to return andrewm@0: * values and timestamps. This is always a const iterator class. andrewm@0: */ andrewm@0: andrewm@0: template andrewm@0: struct NodeInterpolatedIterator : andrewm@0: public boost::iterator< andrewm@0: std::random_access_iterator_tag, andrewm@0: typename Traits::value_type, andrewm@0: typename Traits::difference_type, andrewm@0: typename Traits::pointer, andrewm@0: typename Traits::reference> andrewm@0: { andrewm@0: typedef NodeInterpolatedIterator self_type; andrewm@0: andrewm@0: typedef boost::iterator< andrewm@0: std::random_access_iterator_tag, andrewm@0: typename Traits::value_type, andrewm@0: typename Traits::difference_type, andrewm@0: typename Traits::pointer, andrewm@0: typename Traits::reference> base_iterator; andrewm@0: andrewm@0: typedef typename Traits::size_type size_type; andrewm@0: typedef typename base_iterator::value_type value_type; andrewm@0: typedef typename base_iterator::pointer pointer; andrewm@0: typedef typename base_iterator::reference reference; andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: // Reference to the buffer this iterator indexes andrewm@0: Node* m_buff; andrewm@0: andrewm@0: // Index location within the buffer andrewm@0: double m_index; andrewm@0: andrewm@0: // Step size for ++ and similar operators andrewm@0: double m_step; andrewm@0: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default (empty) constructor andrewm@0: NodeInterpolatedIterator() : m_buff(0), m_index(0.0), m_step(1.0) {} andrewm@0: andrewm@0: // Constructor that should be used primarily by the Node class itself andrewm@0: NodeInterpolatedIterator(Node* buff, double index, double stepSize) andrewm@0: : m_buff(buff), m_index(index), m_step(stepSize) {} andrewm@0: andrewm@0: // Copy constructor andrewm@0: NodeInterpolatedIterator(const self_type& obj) : m_buff(obj.m_buff), m_index(obj.m_index), m_step(obj.m_step) {} andrewm@0: andrewm@0: // ***** Operators ***** andrewm@0: // andrewm@0: // Modeled on STL iterators, but using fractional indices. This class should be considered a sort of random access, andrewm@0: // bidirectional iterator. andrewm@0: andrewm@0: NodeInterpolatedIterator& operator = (const self_type& it) { andrewm@0: if (this == &it) andrewm@0: return *this; andrewm@0: m_buff = it.m_buff; andrewm@0: m_index = it.m_index; andrewm@0: m_step = it.m_step; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: // Dereferencing operators. Like the non-interpolated version of this iterator, it will calculate the values as needed. andrewm@0: // This happens within the operator[] method of Node, rather than in this class itself. andrewm@0: andrewm@0: value_type operator * () const { return m_buff->interpolate(m_index); } andrewm@0: pointer operator -> () const { return &(operator*()); } andrewm@0: andrewm@0: // Difference of two iterators (return the difference in indices) andrewm@0: double operator - (const self_type& it) const { return m_index - it.m_index; } andrewm@0: andrewm@0: // Increment and decrement are typically integer operators. We define their behavior here to change the index by a predetermined andrewm@0: // step size, set at construction but changeable. andrewm@0: andrewm@0: self_type& operator ++ () { // ++it andrewm@0: m_index += m_step; andrewm@0: return *this; andrewm@0: } andrewm@0: self_type operator ++ (int) { // it++ andrewm@0: self_type tmp = *this; andrewm@0: m_index += m_step; andrewm@0: return tmp; andrewm@0: } andrewm@0: self_type& operator -- () { // --it andrewm@0: m_index -= m_step; andrewm@0: return *this; andrewm@0: } andrewm@0: self_type operator -- (int) { // it-- andrewm@0: self_type tmp = *this; andrewm@0: m_index -= m_step; andrewm@0: return tmp; andrewm@0: } andrewm@0: andrewm@0: // These methods change the iterator location by a fractional amount. Notice that this is NOT scaled by m_step. andrewm@0: self_type& operator += (double n) { // it += n andrewm@0: m_index += n; andrewm@0: return *this; andrewm@0: } andrewm@0: self_type& operator -= (double n) { // it -= n andrewm@0: m_index -= n; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: self_type operator + (double n) const { return NodeInterpolatedIterator(*this) += n; } andrewm@0: self_type operator - (double n) const { return NodeInterpolatedIterator(*this) -= n; } andrewm@0: andrewm@0: reference operator [] (double n) const { return *(*this + n); } andrewm@0: andrewm@0: andrewm@0: // ***** Comparisons ***** andrewm@0: // andrewm@0: // The comparison behavior is the same as for NodeIterator: even if two iterators point to different buffers, andrewm@0: // they can be compared on the basis of the indices. Of course, this is only meaningful if the two buffers are synchronized andrewm@0: // in time. andrewm@0: andrewm@0: template andrewm@0: bool operator == (const NodeInterpolatedIterator& it) const { return m_index == it.m_index; } andrewm@0: andrewm@0: template andrewm@0: bool operator != (const NodeInterpolatedIterator& it) const { return m_index != it.m_index; } andrewm@0: andrewm@0: template andrewm@0: bool operator < (const NodeInterpolatedIterator& it) const { return m_index < it.m_index; } andrewm@0: andrewm@0: template andrewm@0: bool operator > (const NodeInterpolatedIterator& it) const { return m_index > it.m_index; } andrewm@0: andrewm@0: template andrewm@0: bool operator <= (const NodeInterpolatedIterator& it) const { return !(it < *this); } andrewm@0: andrewm@0: template andrewm@0: bool operator >= (const NodeInterpolatedIterator& it) const { return !(*this < it); } andrewm@0: andrewm@0: // We can also compare interpolated and non-interpolated iterators. andrewm@0: andrewm@0: template andrewm@0: bool operator == (const NodeIterator& it) const { return m_index == (double)it.index(); } andrewm@0: andrewm@0: template andrewm@0: bool operator != (const NodeIterator& it) const { return m_index != (double)it.index(); } andrewm@0: andrewm@0: template andrewm@0: bool operator < (const NodeIterator& it) const { return m_index < (double)it.index(); } andrewm@0: andrewm@0: template andrewm@0: bool operator > (const NodeIterator& it) const { return m_index > (double)it.index(); } andrewm@0: andrewm@0: template andrewm@0: bool operator <= (const NodeIterator& it) const { return m_index <= (double)it.index(); } andrewm@0: andrewm@0: template andrewm@0: bool operator >= (const NodeIterator& it) const { return m_index >= (double)it.index(); } andrewm@0: andrewm@0: // ***** Special Methods ***** andrewm@0: andrewm@0: // Round a fractional index up, down, or to the nearest integer andrewm@0: andrewm@0: self_type& roundDown() { andrewm@0: m_index = floor(m_index); andrewm@0: return *this; andrewm@0: } andrewm@0: self_type& roundUp() { andrewm@0: m_index = ceil(m_index); andrewm@0: return *this; andrewm@0: } andrewm@0: self_type& round() { andrewm@0: m_index = floor(m_index + 0.5); andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: // Increment the iterator by a difference in time rather than a difference in index. This is less efficient to andrewm@0: // compute, but can be useful for buffers whose samples are not regularly spaced in time. andrewm@0: andrewm@0: self_type& incrementTime(timestamp_diff_type ts) { andrewm@0: if(ts > 0) { andrewm@0: size_type afterIndex = (size_type)ceil(m_index); andrewm@0: if(afterIndex >= m_buff->endIndex()) { andrewm@0: m_index = (double)m_buff->endIndex(); andrewm@0: return *this; andrewm@0: } andrewm@0: timestamp_type target = timestamp() + ts; andrewm@0: timestamp_type after = m_buff->timestampAt(afterIndex); andrewm@0: andrewm@0: // First of all, find the first integer index with a timestamp greater than our new target time. andrewm@0: while(after < target) { andrewm@0: afterIndex++; andrewm@0: if(afterIndex >= m_buff->endIndex()) { andrewm@0: m_index = (double)m_buff->endIndex(); andrewm@0: return *this; andrewm@0: } andrewm@0: after = m_buff->timestampAt(afterIndex); andrewm@0: } andrewm@0: andrewm@0: // Then find the timestamp immediately before that. We'll interpolate between these two to get andrewm@0: // the adjusted index. andrewm@0: timestamp_type before = m_buff->timestampAt(afterIndex-1); andrewm@0: m_index = ((target - before) / (after - before)) + (double)(afterIndex - 1); andrewm@0: } andrewm@0: else if(ts < 0) { andrewm@0: size_type beforeIndex = (size_type)floor(m_index); andrewm@0: if(beforeIndex < m_buff->beginIndex()) { andrewm@0: m_index = (double)m_buff->beginIndex()-1.0; andrewm@0: return *this; andrewm@0: } andrewm@0: timestamp_type target = timestamp() + ts; andrewm@0: timestamp_type before = m_buff->timestampAt(beforeIndex); andrewm@0: andrewm@0: // Find the first integer index with a timestamp less than our new target time. andrewm@0: while(before > target) { andrewm@0: beforeIndex--; andrewm@0: if(beforeIndex < m_buff->beginIndex()) { andrewm@0: m_index = (double)m_buff->beginIndex()-1.0; andrewm@0: return *this; andrewm@0: } andrewm@0: before = m_buff->timestampAt(beforeIndex); andrewm@0: } andrewm@0: andrewm@0: // Now find the timestamp immediately after that. Interpolated to get the adjusted index. andrewm@0: timestamp_type after = m_buff->timestampAt(beforeIndex+1); andrewm@0: m_index = ((target - before)/(after - before)) + (double)beforeIndex; andrewm@0: } andrewm@0: // if(ts == 0), do nothing andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: // Return (or change) the step size andrewm@0: double& step() { return m_step; } andrewm@0: andrewm@0: // Return the index within the buffer. The index is an always-increasing sample number (which means that as the andrewm@0: // buffer fills, an index will continue to point to the same piece of data until that data is overwritten, at which andrewm@0: // point that index is no longer valid for accessing the buffer at all.) andrewm@0: double index() const { return m_index; } andrewm@0: andrewm@0: // Return the timestamp for this particular location in the buffer (using linear interpolation). andrewm@0: timestamp_type timestamp() const { return m_buff->interpolatedTimestampAt(m_index); } andrewm@0: andrewm@0: // Tells us whether the index points to a valid place in the buffer. andrewm@0: bool isValid() const { return (m_index >= m_buff->beginIndex() && m_index <= (m_buff->endIndex() - 1)); } andrewm@0: }; andrewm@0: andrewm@0: /* andrewm@0: * NodeBase andrewm@0: * andrewm@0: * Abstract class that implements common functionality for all Node data types. andrewm@0: * andrewm@0: */ andrewm@0: andrewm@0: class NodeBase : public TriggerSource, public TriggerDestination { andrewm@0: public: andrewm@0: typedef uint32_t size_type; andrewm@0: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default constructor andrewm@0: NodeBase() {} andrewm@0: andrewm@0: // Copy constructor: can't copy the mutexes andrewm@0: NodeBase(NodeBase const& obj) {} andrewm@0: andrewm@0: NodeBase& operator= (NodeBase const& obj) { andrewm@0: //listeners_ = obj.listeners_; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: andrewm@0: virtual ~NodeBase() { andrewm@0: //clearTriggerSources(); andrewm@0: } andrewm@0: andrewm@0: // ***** Modifiers ***** andrewm@0: andrewm@0: virtual void clear() = 0; andrewm@0: //virtual void insertMissing(timestamp_type timestamp) = 0; andrewm@0: andrewm@0: // ***** Listener Methods ***** andrewm@0: // andrewm@0: // We need to keep the buffers of all connected units in sync. That means any Source or Filter needs to know what its output andrewm@0: // connects to. That functionality is accomplished by "listeners": any time a new sample is added to the buffer, the listeners andrewm@0: // are notified with a corresponding timestamp. The latter is to ensure that a buffer does not respond to notifications from multiple andrewm@0: // inputs for the same data point. andrewm@0: andrewm@0: /*void registerListener(NodeBase* listener) { andrewm@0: if(listener != 0) { andrewm@0: listenerAccessMutex_.lock(); andrewm@0: listeners_.insert(listener); andrewm@0: listenerAccessMutex_.unlock(); andrewm@0: } andrewm@0: } andrewm@0: void unregisterListener(NodeBase* listener) { andrewm@0: listenerAccessMutex_.lock(); andrewm@0: listeners_.erase(listener); andrewm@0: listenerAccessMutex_.unlock(); andrewm@0: } andrewm@0: void clearListeners() { andrewm@0: listenerAccessMutex_.lock(); andrewm@0: listeners_.clear(); andrewm@0: listenerAccessMutex_.unlock(); andrewm@0: } andrewm@0: protected: andrewm@0: // Tell all our listeners about a new data point with the given timestamp andrewm@0: void notifyListenersOfInsert(timestamp_type timestamp) { andrewm@0: std::set::iterator it; andrewm@0: andrewm@0: listenerAccessMutex_.lock(); andrewm@0: for(it = listeners_.begin(); it != listeners_.end(); it++) andrewm@0: (*it)->insertMissing(timestamp); andrewm@0: listenerAccessMutex_.unlock(); andrewm@0: } andrewm@0: // Tell all our listeners that the buffer has cleared andrewm@0: void notifyListenersOfClear() { andrewm@0: std::set::iterator it; andrewm@0: andrewm@0: listenerAccessMutex_.lock(); andrewm@0: for(it = listeners_.begin(); it != listeners_.end(); it++) andrewm@0: (*it)->clear(); andrewm@0: listenerAccessMutex_.unlock(); andrewm@0: }*/ andrewm@0: andrewm@0: public: andrewm@0: // ***** Tree-Parsing Methods ***** andrewm@0: // andrewm@0: // Sometimes we want to find out properties of the initial data source, regardless of what filters it's passed through. These virtual andrewm@0: // methods should be implemented differently by Source and Filter classes. andrewm@0: andrewm@0: //virtual SourceBase& source() = 0; // Return the source at the top of the tree for this unit andrewm@0: andrewm@0: // ***** Mutex Methods ***** andrewm@0: // andrewm@0: // These methods should be used to acquire a lock whenever a process wants to read values from the buffer. This would, for example, andrewm@0: // allow iteration over the contents of the buffer without worrying that the contents will change in the course of the iteration. andrewm@0: andrewm@0: void lock_mutex() { bufferAccessMutex_.enter(); } andrewm@0: bool try_lock_mutex() { return bufferAccessMutex_.tryEnter(); } andrewm@0: void unlock_mutex() { bufferAccessMutex_.exit(); } andrewm@0: andrewm@0: // ***** Circular Buffer (STL) Methods ***** andrewm@0: // andrewm@0: // Certain STL methods (and related queries) that do not depend on the specific data type of the buffer should andrewm@0: // be available to any object with a NodeBase reference. andrewm@0: andrewm@0: virtual size_type size() = 0; // Size: how many elements are currently in the buffer andrewm@0: virtual bool empty() = 0; andrewm@0: virtual bool full() = 0; andrewm@0: virtual size_type reserve() = 0; // Reserve: how many elements are left before the buffer is full andrewm@0: virtual size_type capacity() const = 0; // Capacity: how many elements could be in the buffer andrewm@0: andrewm@0: virtual size_type beginIndex() = 0; // Index of the first sample we still have in the buffer andrewm@0: virtual size_type endIndex() = 0; // Index just past the end of the buffer andrewm@0: andrewm@0: // ***** Timestamp Methods ***** andrewm@0: // andrewm@0: // Every sample is tagged with a timestamp. We don't necessarily need to know the type of the sample to retrieve its andrewm@0: // associated timestamp. However, we DO need to know the type in order to return an iterator. andrewm@0: andrewm@0: virtual timestamp_type timestampAt(size_type index) = 0; andrewm@0: virtual timestamp_type latestTimestamp() = 0; andrewm@0: virtual timestamp_type earliestTimestamp() = 0; andrewm@0: andrewm@0: virtual size_type indexNearestTo(timestamp_type t) = 0; andrewm@0: virtual size_type indexNearestBefore(timestamp_type t) = 0; andrewm@0: virtual size_type indexNearestAfter(timestamp_type t) = 0; andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: protected: andrewm@0: // A collection of the units that are listening for updates on this unit. andrewm@0: //std::set listeners_; andrewm@0: andrewm@0: // This mutex protects access to the underlying buffer. It is locked every time a sample is written to the buffer, andrewm@0: // and external systems reading values from the buffer should also acquire at least a shared lock. andrewm@0: CriticalSection bufferAccessMutex_; andrewm@0: andrewm@0: // This mutex protects the list of listeners. It prevents a listener from being added or removed while a notification andrewm@0: // is in progress. andrewm@0: //boost::mutex listenerAccessMutex_; andrewm@0: andrewm@0: // Keep an internal registry of who we've asked to send us triggers. It's important to keep andrewm@0: // a list of these so that when this object is destroyed, all triggers are automatically unregistered. andrewm@0: //std::set triggerSources_; andrewm@0: andrewm@0: // This list tracks the destinations we are *sending* triggers to (as opposed to the sources we're receiving from above) andrewm@0: //std::set triggerDestinations_; andrewm@0: }; andrewm@0: andrewm@0: /* andrewm@0: * NodeNonInterpolating andrewm@0: * andrewm@0: * This class handles all functionality for a Node of a specific data type EXCEPT: andrewm@0: * -- Interpolating accessors (for data types that support interpolation, use the more common Node subclass) andrewm@0: * -- triggerReceived() which should be implemented by a specific subclass. andrewm@0: */ andrewm@0: andrewm@0: template andrewm@0: class NodeNonInterpolating : public NodeBase { andrewm@0: public: andrewm@0: // Useful type shorthands. See for details. andrewm@47: typedef typename boost::container::allocator_traits > Alloc; andrewm@47: andrewm@0: typedef typename boost::circular_buffer::value_type value_type; andrewm@0: typedef typename boost::circular_buffer::pointer pointer; andrewm@0: typedef typename boost::circular_buffer::const_pointer const_pointer; andrewm@0: typedef typename boost::circular_buffer::reference reference; andrewm@0: typedef typename boost::circular_buffer::const_reference const_reference; andrewm@0: typedef typename boost::circular_buffer::difference_type difference_type; andrewm@0: //typedef typename boost::circular_buffer::size_type size_type; andrewm@0: typedef typename boost::circular_buffer::capacity_type capacity_type; andrewm@0: typedef typename boost::circular_buffer::array_range array_range; andrewm@0: typedef typename boost::circular_buffer::const_array_range const_array_range; andrewm@20: typedef typename boost::circular_buffer::param_value_type return_value_type; andrewm@0: andrewm@0: // We only support const iterators. (Modifying data in the buffer is restricted to only a few specialized instances.) andrewm@0: andrewm@0: typedef NodeIterator, andrewm@0: boost::cb_details::nonconst_traits > const_iterator; andrewm@0: typedef const_iterator iterator; andrewm@0: typedef NodeReverseIterator const_reverse_iterator; andrewm@0: typedef const_reverse_iterator reverse_iterator; andrewm@0: andrewm@0: template friend struct NodeIterator; andrewm@0: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: //Node() : buffer_(0), insertMissingLastTimestamp_(0), numSamples_(0), firstSampleIndex_(0) {} andrewm@0: andrewm@0: // Recommended constructor: specify the capacity in samples andrewm@0: explicit NodeNonInterpolating(capacity_type capacity) : insertMissingLastTimestamp_(0), buffer_(0), numSamples_(0), firstSampleIndex_(0) { andrewm@0: buffer_ = new boost::circular_buffer(capacity); andrewm@0: timestamps_ = new boost::circular_buffer(capacity); andrewm@0: } andrewm@0: andrewm@0: // Copy constructor andrewm@0: NodeNonInterpolating(const NodeNonInterpolating& obj) : numSamples_(obj.numSamples_), firstSampleIndex_(obj.firstSampleIndex_) { andrewm@0: if(obj.buffer_ != 0) andrewm@0: this->buffer_ = new boost::circular_buffer(*obj.buffer_); andrewm@0: else andrewm@0: this->buffer_ = 0; andrewm@0: if(obj.timestamps_ != 0) andrewm@0: timestamps_ = new boost::circular_buffer(*obj.timestamps_); andrewm@0: else andrewm@0: this->timestamps_ = 0; andrewm@0: } andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: andrewm@0: virtual ~NodeNonInterpolating() { andrewm@0: delete buffer_; andrewm@0: delete timestamps_; andrewm@0: } andrewm@0: andrewm@0: // ***** Circular Buffer (STL) Methods ***** andrewm@0: // andrewm@0: // In general we support const methods accessing the contents of the buffer, but only in limited cases can the buffer andrewm@0: // contents be modified. Source objects allow inserting objects into the buffer, but Filters require the buffer to contain andrewm@0: // either the result of the evaluator function or a "missing" value. andrewm@0: andrewm@0: // ***** Accessors ***** andrewm@0: andrewm@0: const_iterator begin() { return const_iterator(this, buffer_->begin()); } andrewm@0: const_iterator end() { return const_iterator(this, buffer_->end()); } andrewm@0: const_reverse_iterator rbegin() { return const_reverse_iterator(end()); } andrewm@0: const_reverse_iterator rend() { return const_reverse_iterator(begin()); } andrewm@0: andrewm@0: const_iterator iteratorAtIndex(size_type index) { return const_iterator(this, buffer_->begin()) + (index - firstSampleIndex_); } andrewm@0: const_reverse_iterator riteratorAtIndex(size_type index) { return const_reverse_iterator(iteratorAtIndex(index+1)); } andrewm@0: andrewm@0: return_value_type operator [] (size_type index) { return (*(this->buffer_))[index-this->firstSampleIndex_]; } andrewm@0: return_value_type at(size_type index) { return this->buffer_->at(index-this->firstSampleIndex_); } andrewm@0: return_value_type front() { return this->buffer_->front(); } andrewm@0: return_value_type back() { return this->buffer_->back(); } andrewm@0: andrewm@0: // Two more convenience methods to avoid confusion about what front and back mean! andrewm@0: return_value_type earliest() { return this->buffer_->front(); } andrewm@0: return_value_type latest() { return this->buffer_->back(); } andrewm@0: andrewm@0: // In the following methods, check whether the value is missing and calculate it as necessary andrewm@0: // These methods return a value_type (i.e. not a reference, can't be used to modify the buffer.) andrewm@0: // However, they internally make use of modifying calls in order to update "missing" values. andrewm@0: andrewm@0: /*return_value_type operator [] (size_type index) { andrewm@0: return_value_type val = (*buffer_)[index-firstSampleIndex_]; andrewm@0: if(!missing_value::isMissing(val)) andrewm@0: return val; andrewm@0: return ((*buffer_)[index-firstSampleIndex_] = evaluate(index)); andrewm@0: } andrewm@0: return_value_type at(size_type index) { andrewm@0: return_value_type val = buffer_->at(index-firstSampleIndex_); andrewm@0: if(!missing_value::isMissing(val)) andrewm@0: return val; andrewm@0: return (buffer_->at(index-firstSampleIndex_) = evaluate(index)); andrewm@0: } andrewm@0: return_value_type front() { andrewm@0: return_value_type val = buffer_->front(); andrewm@0: if(!missing_value::isMissing(val)) andrewm@0: return val; andrewm@0: return (buffer_->front() = evaluate(firstSampleIndex_)); andrewm@0: } andrewm@0: return_value_type back() { andrewm@0: return_value_type val = buffer_->back(); andrewm@0: if(!missing_value::isMissing(val) && buffer_->size() > 0) andrewm@0: return val; andrewm@0: return (buffer_->back() = evaluate(buffer_->size() - 1 + firstSampleIndex_)); andrewm@0: }*/ andrewm@0: andrewm@0: size_type size() { return buffer_->size(); } // Size: how many elements are currently in the buffer andrewm@0: bool empty() { return buffer_->empty(); } andrewm@0: bool full() { return buffer_->full(); } andrewm@0: size_type reserve() { return buffer_->reserve(); } // Reserve: how many elements are left before the buffer is full andrewm@0: size_type capacity() const { return buffer_->capacity(); } // Capacity: how many elements could be in the buffer andrewm@0: andrewm@0: size_type beginIndex() { return firstSampleIndex_; } // Index of the first sample we still have in the buffer andrewm@0: size_type endIndex() { return firstSampleIndex_ + buffer_->size(); } // Index just past the end of the buffer andrewm@0: andrewm@0: // ***** Modifiers ***** andrewm@0: andrewm@0: // Clear all stored samples and timestamps andrewm@0: void clear() { andrewm@0: bufferAccessMutex_.enter(); andrewm@0: timestamps_->clear(); andrewm@0: buffer_->clear(); andrewm@0: numSamples_ = firstSampleIndex_ = 0; andrewm@0: bufferAccessMutex_.exit(); andrewm@0: andrewm@0: //notifyListenersOfClear(); andrewm@0: } andrewm@0: andrewm@0: // Insert a new item into the buffer andrewm@0: void insert(const OutputType& item, timestamp_type timestamp) { andrewm@0: this->bufferAccessMutex_.enter(); andrewm@0: if(this->buffer_->full()) andrewm@0: this->firstSampleIndex_++; andrewm@0: this->timestamps_->push_back(timestamp); andrewm@0: this->buffer_->push_back(item); andrewm@0: this->numSamples_++; andrewm@0: this->bufferAccessMutex_.exit(); andrewm@0: andrewm@0: // Notify anyone who's listening for a trigger andrewm@0: this->sendTrigger(timestamp); andrewm@0: } andrewm@0: andrewm@0: // Insert a "missing" item into the buffer. This is really for the Filter subclasses, but we should provide an implementation andrewm@0: /*void insertMissing(timestamp_type timestamp) { andrewm@0: if(timestamp == insertMissingLastTimestamp_) andrewm@0: return; andrewm@0: this->bufferAccessMutex_.lock(); andrewm@0: if(this->buffer_->full()) andrewm@0: this->firstSampleIndex_++; andrewm@0: this->timestamps_->push_back(timestamp); andrewm@0: this->buffer_->push_back(missing_value::missing()); andrewm@0: this->numSamples_++; andrewm@0: this->bufferAccessMutex_.unlock(); andrewm@0: andrewm@0: this->insertMissingLastTimestamp_ = timestamp; andrewm@0: this->notifyListenersOfInsert(timestamp); andrewm@0: } */ andrewm@0: andrewm@0: protected: andrewm@0: // Subclasses are allowed to change the values stored in their buffers. Give this a different andrewm@0: // name to avoid confusion with the behavior of [] and at(), which call evaluate() if the sample andrewm@0: // is missing. andrewm@0: andrewm@0: reference rawValueAt(size_type index) { return (*buffer_)[index-firstSampleIndex_]; } andrewm@0: andrewm@0: public: andrewm@0: // ***** Timestamp Methods ***** andrewm@0: // andrewm@0: // Every sample is tagged with a timestamp. The Filter classes will look up the chain to find the timestamp associated andrewm@0: // with the Source of any particular sample. We also support methods to return an iterator to a piece of data most closely andrewm@0: // matching a given timestamp. andrewm@0: andrewm@0: timestamp_type timestampAt(size_type index) { return timestamps_->at(index-this->firstSampleIndex_); } andrewm@0: timestamp_type latestTimestamp() { return timestamps_->back(); } andrewm@0: timestamp_type earliestTimestamp() { return timestamps_->front(); } andrewm@0: andrewm@0: size_type indexNearestBefore(timestamp_type t) { andrewm@0: boost::circular_buffer::iterator it = std::find_if(timestamps_->begin(), timestamps_->end(), t < boost::lambda::_1); andrewm@0: if(it == timestamps_->end()) andrewm@0: return timestamps_->size()-1+this->firstSampleIndex_; andrewm@0: if(it - timestamps_->begin() == 0) andrewm@0: return this->firstSampleIndex_; andrewm@0: return (size_type)((--it) - timestamps_->begin()) + this->firstSampleIndex_; andrewm@0: } andrewm@0: size_type indexNearestAfter(timestamp_type t) { andrewm@0: boost::circular_buffer::iterator it = std::find_if(timestamps_->begin(), timestamps_->end(), t < boost::lambda::_1); andrewm@0: return std::min((it - timestamps_->begin()), timestamps_->size()-1) + this->firstSampleIndex_; andrewm@0: } andrewm@0: size_type indexNearestTo(timestamp_type t) { andrewm@0: boost::circular_buffer::iterator it = std::find_if(timestamps_->begin(), timestamps_->end(), t < boost::lambda::_1); andrewm@0: if(it == timestamps_->end()) andrewm@0: return timestamps_->size()-1+this->firstSampleIndex_; andrewm@0: if(it - timestamps_->begin() == 0) andrewm@0: return this->firstSampleIndex_; andrewm@0: timestamp_diff_type after = *it - t; // Calculate the distance between the desired timestamp and the before/after values, andrewm@0: timestamp_diff_type before = t - *(it-1); // then return whichever index gets closer to the target. andrewm@0: if(after < before) andrewm@0: return (size_type)(it - timestamps_->begin()) + this->firstSampleIndex_; andrewm@0: return (size_type)((--it) - timestamps_->begin()) + this->firstSampleIndex_; andrewm@0: } andrewm@0: andrewm@0: const_iterator nearestTo(timestamp_type t) { return begin() + (difference_type)indexNearestTo(t); } andrewm@0: const_iterator nearestBefore(timestamp_type t) { return begin() + (difference_type)indexNearestBefore(t); } andrewm@0: const_iterator nearestAfter(timestamp_type t) { return begin() + (difference_type)indexNearestAfter(t); } andrewm@0: andrewm@0: const_reverse_iterator rnearestTo(timestamp_type t) { return rend() - (difference_type)indexNearestTo(t); } andrewm@0: const_reverse_iterator rnearestBefore(timestamp_type t) { return rend() - (difference_type)indexNearestBefore(t); } andrewm@0: const_reverse_iterator rnearestAfter(timestamp_type t) { return rend() - (difference_type)indexNearestAfter(t); } andrewm@0: andrewm@0: private: andrewm@0: // Calculate the actual value of one sample. Behavior of this method will be different for Source and Filter types. andrewm@0: // virtual OutputType evaluate(size_type index) = 0; andrewm@0: andrewm@0: timestamp_type insertMissingLastTimestamp_; // The last timestamp that came from insertMissing(), so we can avoid duplication andrewm@0: andrewm@0: protected: andrewm@0: boost::circular_buffer *buffer_; // Internal buffer to store or cache values andrewm@0: boost::circular_buffer *timestamps_; // Internal buffer to hold timestamps for each value andrewm@0: andrewm@0: size_type numSamples_; // How many samples total we've stored in this buffer andrewm@0: size_type firstSampleIndex_; // Index of the first sample that still remains in the buffer andrewm@0: }; andrewm@0: andrewm@0: /* andrewm@0: * Node andrewm@0: * andrewm@0: * This class handles a node of a specific data type, including the ability to interpolate between samples. This is the recommended andrewm@0: * class to use for numeric data types and others for which linear interpolation makes sense. andrewm@0: */ andrewm@0: andrewm@0: template andrewm@0: class Node : public NodeNonInterpolating { andrewm@0: public: andrewm@0: typedef typename std::allocator Alloc; andrewm@0: typedef NodeInterpolatedIterator > interpolated_iterator; andrewm@0: andrewm@0: typedef typename NodeNonInterpolating::return_value_type return_value_type; andrewm@0: typedef typename NodeNonInterpolating::capacity_type capacity_type; andrewm@0: typedef typename NodeNonInterpolating::size_type size_type; andrewm@0: andrewm@0: // ***** Constructors ***** andrewm@0: // andrewm@0: // Use the same constructors as the non-interpolating version. andrewm@0: andrewm@0: explicit Node(capacity_type capacity) : NodeNonInterpolating(capacity) {} andrewm@0: Node(Node const& obj) : NodeNonInterpolating(obj) {} andrewm@0: andrewm@0: // ***** Interpolating Accessors ***** andrewm@0: // andrewm@0: // These overloaded methods allow querying a location between two samples, using linear andrewm@0: // interpolation to generate the value. andrewm@0: andrewm@0: return_value_type interpolate(double index) { andrewm@0: size_type before = floor(index); // Find the sample before the interpolated location andrewm@0: double frac = index - (double)before; // Find the fractional remainder component andrewm@0: OutputType val1 = this->buffer_->at(before-this->firstSampleIndex_); andrewm@0: if(before == this->endIndex()-1) andrewm@0: return val1; andrewm@0: OutputType val2 = this->buffer_->at(before+1-this->firstSampleIndex_); andrewm@0: //if(missing_value::isMissing(val1)) // Make sure both values have been calculated andrewm@0: // val1 = (buffer_->at(before-firstSampleIndex_) = evaluate(before)); andrewm@0: //if(missing_value::isMissing(val2)) andrewm@0: // val2 = (buffer_->at(before+1-firstSampleIndex_) = evaluate(before+1)); andrewm@0: return val1*(1.0-frac) + val2*frac; // Return the interpolated value andrewm@0: } andrewm@0: andrewm@0: interpolated_iterator interpolatedBegin(double step = 1.0) { return interpolated_iterator(this, (double)this->beginIndex(), step); } andrewm@0: interpolated_iterator interpolatedEnd(double step = 1.0) { return interpolated_iterator(this, (double)this->endIndex(), step); } andrewm@0: interpolated_iterator interpolatedIteratorAtIndex(double index, double step = 1.0) { return interpolated_iterator(this, index, step); } andrewm@0: andrewm@0: // ***** Interpolating Timestamp Methods ***** andrewm@0: // andrewm@0: // Using linear interpolation, find the exact relationship between a timestamp and an index in the buffer. andrewm@0: // Designed to be used in conjunction with the interpolate() method for buffer access. andrewm@0: andrewm@0: // Fractional index --> timestamp andrewm@0: timestamp_type interpolatedTimestampAt(double index) { andrewm@0: size_type before = floor(index); andrewm@0: double frac = index - (double)before; andrewm@0: timestamp_type ts1 = timestampAt(before); andrewm@0: if(before == this->endIndex()-1) andrewm@0: return ts1; andrewm@0: timestamp_type ts2 = timestampAt(before+1); andrewm@0: return ts1*(1.0-frac)+ts2*frac; andrewm@0: } andrewm@0: andrewm@0: // Timestamp --> fractional index andrewm@0: double interpolatedIndexForTimestamp(timestamp_type timestamp) { andrewm@0: size_type before = this->indexNearestBefore(timestamp); andrewm@0: if(before >= this->timestamps_->size() - 1 + this->firstSampleIndex_) // If it's at the end of the buffer, return the last available timestamp andrewm@0: return (double)before; andrewm@0: timestamp_type beforeTimestamp = this->timestampAt(before); // Get the timestamp immediately before andrewm@0: if(beforeTimestamp >= timestamp) // If it comes after the requested timestamp, we're at the beginning of the buffer andrewm@0: return (double)before; andrewm@0: timestamp_type afterTimestamp = this->timestampAt(before+1); andrewm@0: double frac = (timestamp - beforeTimestamp)/(afterTimestamp-beforeTimestamp); andrewm@0: return (double)before + frac; andrewm@0: } andrewm@0: }; andrewm@0: andrewm@0: #endif /* KEYCONTROL_NODE_H */