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