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: LineSegment.h: template class implementing a line segment over time. andrewm@0: */ andrewm@0: andrewm@0: #ifndef touchkeys_LineSegment_h andrewm@0: #define touchkeys_LineSegment_h andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "Types.h" andrewm@0: andrewm@0: /* andrewm@0: * LineSegment andrewm@0: * andrewm@0: * This template class implements a line (or curve) segment. Given start and end times and andrewm@0: * start and end values, this class calculates the current value based on the given timestamp. andrewm@0: * Options are available to freeze at the final value or to extrapolate from it. andrewm@0: * andrewm@0: * One assumption that is made with this class is that calls are always made in monotonically andrewm@0: * non-decreasing order of timestamp. Evaluating the value of the segment may implicitly update andrewm@0: * its state so out-of-order evaluations could return unexpected results. andrewm@0: */ andrewm@0: andrewm@0: andrewm@0: template andrewm@0: class LineSegment { andrewm@0: public: andrewm@0: typedef boost::function warp_function; andrewm@0: andrewm@0: struct { andrewm@0: timestamp_type time; // Time to achieve a given value andrewm@0: DataType value; // Value that should be achieved andrewm@0: warp_function warp; // How to warp the data getting there. FIXME: null? andrewm@0: bool jump; // Whether to do an immediate jump andrewm@0: } Endpoint; andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default constructor with default starting value andrewm@0: LineSegment() : lastEvaluatedTimestamp_(0), lastEvaluatedValue_() { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // Constructor with arbitrary starting value andrewm@0: LineSegment(DataType const& startingValue) : lastEvaluatedTimestamp_(0), andrewm@0: lastEvaluatedValue_(startingValue) { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // Copy constructor andrewm@0: LineSegment(LineSegment const& obj) : andrewm@0: lastEvaluatedTimestamp_(obj.lastEvaluatedTimestamp_), andrewm@0: lastEvaluatedValue_(obj.lastEvaluatedValue_) { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // ***** Destructor ***** andrewm@0: ~LineSegment() { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // ***** Modifiers ***** andrewm@0: // Add a new segment from a given time to a given time. The "from" value is implicitly andrewm@0: // calculated based on the current state of the segment. andrewm@0: void addSegment(timestamp_type const fromTimestamp, timestamp_type const toTimestamp, DataType const& toValue) { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // As above, but use a non-linear (warped) transfer function to get from one end to the other. andrewm@0: // The function should take in one value between 0 and 1 (or their DataType equivalents) and return andrewm@0: // a value between 0 and 1 (or their DataType equivalents). Scaling will be done internally. andrewm@0: void addSegment(timestamp_type const fromTimestamp, timestamp_type const toTimestamp, DataType const& toValue, andrewm@0: warp_function& warp) { andrewm@0: andrewm@0: } andrewm@0: andrewm@0: // Freeze the value starting at the indicated timestamp (immediately if no argument is given) andrewm@0: void hold(timestamp_type const timestamp = 0) { andrewm@0: if(timestamp == 0) { andrewm@0: segments_.clear(); // Hold at whatever last evaluation returned andrewm@0: } andrewm@0: else { andrewm@0: evaluate(timestamp); // Recalculate value for this time, then hold andrewm@0: segments_.clear(); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Jump to a given value at the indicated timestamp (immediately if no argument is given) andrewm@0: void jump(timestamp_type const timestamp = 0, DataType const& toValue) { andrewm@0: if(timestamp == 0) { andrewm@0: lastEvaluatedValue_ = toValue; andrewm@0: segments_.clear(); andrewm@0: } andrewm@0: else { andrewm@0: // Jump to a value at a given time andrewm@0: Endpoint newEndpoint; andrewm@0: andrewm@0: newEndpoint.time = timestamp; andrewm@0: newEndpoint.value = toValue; andrewm@0: newEndpoint.jump = true; andrewm@0: andrewm@0: if(segments_.empty()) andrewm@0: segments_.push_back(newEndpoint); andrewm@0: else { andrewm@0: std::list::iterator it = segments_.begin(); andrewm@0: andrewm@0: // Look for any elements in the list that have a later timestamp. andrewm@0: // Remove them and make this jump the last element. andrewm@0: while(it != segments_.end()) { andrewm@0: if(it->time >= timestamp) { andrewm@0: segments_.erase(it, segments_.end()); andrewm@0: break; andrewm@0: } andrewm@0: it++; andrewm@0: } andrewm@0: andrewm@0: segments_.push_back(newEndpoint); andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Reset state to defaults, including times andrewm@0: void reset() { andrewm@0: lastEvaluatedValue_ = DataType(); andrewm@0: lastEvaluatedTimestamp_ = 0; andrewm@0: segments_.clear(); andrewm@0: } andrewm@0: andrewm@0: // ***** Evaluator ***** andrewm@0: andrewm@0: // Return the current value of the segment based on current timestamp andrewm@0: DataType const& evaluate(timestamp_type const timestamp) { andrewm@0: // Check that the current timestamp is later than the previous one. If andrewm@0: // earlier, print a warning. If identical, return the last value identically. andrewm@0: // If there are no further changes planned, likewise return the last value. andrewm@0: if(timestamp < lastEvaluatedTimestamp_) { andrewm@0: std::cout << "Warning: LineSegment::evaluate() called with timestamp " << timestamp << " earlier than previous " << lastEvaluatedTimestamp_ << std::endl; andrewm@0: return lastEvaluatedValue_; andrewm@0: } andrewm@0: if(timestamp == lastEvaluatedValue_ || !changesRemaining_) andrewm@0: return lastEvaluatedValue_; andrewm@0: andrewm@0: // TODO: evaluate andrewm@0: } andrewm@0: andrewm@0: // Return whether all in-progress segments have finished. Call this after evaluate(). andrewm@0: bool finished() { andrewm@0: return segments_.empty(); andrewm@0: } andrewm@0: andrewm@0: private: andrewm@0: // ***** Internal Methods ***** andrewm@0: andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: timestamp_type lastEvaluatedTimestamp_; // When evaluate() was last called andrewm@0: DataType lastEvaluatedValue_; // What evaluate() last returned andrewm@0: std::list segments_; // List of segment times and values andrewm@0: }; andrewm@0: andrewm@0: andrewm@0: andrewm@0: andrewm@0: andrewm@0: #endif