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: KeyPositionTracker.h: parses continuous key position and detects the andrewm@0: state of the key. andrewm@0: */ andrewm@0: andrewm@0: andrewm@0: #ifndef __touchkeys__KeyPositionTracker__ andrewm@0: #define __touchkeys__KeyPositionTracker__ andrewm@0: andrewm@0: #include andrewm@0: #include "../Utility/Node.h" andrewm@0: #include "../Utility/Accumulator.h" andrewm@0: #include "PianoTypes.h" andrewm@0: andrewm@0: // Three states of idle detector andrewm@0: enum { andrewm@0: kPositionTrackerStateUnknown = 0, andrewm@0: kPositionTrackerStatePartialPressAwaitingMax, andrewm@0: kPositionTrackerStatePartialPressFoundMax, andrewm@0: kPositionTrackerStatePressInProgress, andrewm@0: kPositionTrackerStateDown, andrewm@0: kPositionTrackerStateReleaseInProgress, andrewm@0: kPositionTrackerStateReleaseFinished andrewm@0: }; andrewm@0: andrewm@0: // Constants for key state detection andrewm@0: const key_position kPositionTrackerPressPosition = scale_key_position(0.75); andrewm@0: const key_position kPositionTrackerPressHysteresis = scale_key_position(0.05); andrewm@0: const key_position kPositionTrackerMinMaxSpacingThreshold = scale_key_position(0.02); andrewm@0: const key_position kPositionTrackerFirstMaxThreshold = scale_key_position(0.075); andrewm@0: const key_position kPositionTrackerReleaseFinishPosition = scale_key_position(0.2); andrewm@0: andrewm@0: // How far back to search at the beginning to find the real start or release of a key press andrewm@0: const int kPositionTrackerSamplesToSearchForStartLocation = 50; andrewm@0: const int kPositionTrackerSamplesToSearchBeyondStartLocation = 20; andrewm@0: const int kPositionTrackerSamplesToSearchForReleaseLocation = 100; andrewm@0: const int kPositionTrackerSamplesToAverageForStartVelocity = 3; andrewm@0: const key_velocity kPositionTrackerStartVelocityThreshold = scale_key_velocity(0.5); andrewm@0: const key_velocity kPositionTrackerStartVelocitySpikeThreshold = scale_key_velocity(2.5); andrewm@0: const key_velocity kPositionTrackerReleaseVelocityThreshold = scale_key_velocity(-0.2); andrewm@0: andrewm@0: // Constants for feature calculations. The first one is the approximate location of the escapement andrewm@0: // (empirically measured on one piano, so only approximate), used for velocity calculations andrewm@0: const key_position kPositionTrackerDefaultPositionForPressVelocityCalculation = scale_key_position(0.65); andrewm@0: const key_position kPositionTrackerDefaultPositionForReleaseVelocityCalculation = scale_key_position(0.5); andrewm@0: const key_position kPositionTrackerPositionThresholdForPercussivenessCalculation = scale_key_position(0.4); andrewm@0: const int kPositionTrackerSamplesNeededForPressVelocityAfterEscapement = 1; andrewm@0: const int kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement = 1; andrewm@0: andrewm@0: // KeyPositionTrackerNotification andrewm@0: // andrewm@0: // This class contains information on the notifications sent and stored by andrewm@0: // KeyPositionTracker. Includes state changes and feature available. andrewm@0: andrewm@0: class KeyPositionTrackerNotification { andrewm@0: public: andrewm@0: enum { andrewm@0: kNotificationTypeStateChange = 1, andrewm@0: kNotificationTypeFeatureAvailableVelocity, andrewm@0: kNotificationTypeFeatureAvailableReleaseVelocity, andrewm@0: kNotificationTypeFeatureAvailablePercussiveness, andrewm@0: kNotificationTypeNewMinimum, andrewm@0: kNotificationTypeNewMaximum andrewm@0: }; andrewm@0: andrewm@0: enum { andrewm@0: kFeaturesNone = 0, andrewm@0: kFeaturePressVelocity = 0x0001, andrewm@0: kFeatureReleaseVelocity = 0x0002, andrewm@0: kFeaturePercussiveness = 0x0004 andrewm@0: }; andrewm@0: andrewm@0: int type; andrewm@0: int state; andrewm@0: int features; andrewm@0: }; andrewm@0: andrewm@0: // KeyPositionTracker andrewm@0: // andrewm@0: // This class implements a state machine for a key that is currently active (not idle), andrewm@0: // using continuous key position data to determine the location and parameters of key andrewm@0: // presses and other events. It includes management of partial press patterns and detection andrewm@0: // of percussiveness as well as velocity features. andrewm@0: // andrewm@0: // This class is triggered by new data points in the key position buffer. Its output is andrewm@0: // a series of state changes which indicate what the key is doing. andrewm@0: andrewm@0: class KeyPositionTracker : public Node { andrewm@0: public: andrewm@0: typedef Node::size_type key_buffer_index; andrewm@0: //typedef void (*KeyActionFunction)(KeyPositionTracker *object, void *userData); andrewm@0: andrewm@0: // Simple class to hold index/position/timestamp triads andrewm@0: class Event { andrewm@0: public: andrewm@0: Event() : index(0), position(missing_value::missing()), andrewm@0: timestamp(missing_value::missing()) {} andrewm@0: andrewm@0: Event(key_buffer_index i, key_position p, timestamp_type t) andrewm@0: : index(i), position(p), timestamp(t) {} andrewm@0: andrewm@0: Event(const Event& obj) andrewm@0: : index(obj.index), position(obj.position), timestamp(obj.timestamp) {} andrewm@0: andrewm@0: Event& operator=(const Event& obj) { andrewm@0: index = obj.index; andrewm@0: position = obj.position; andrewm@0: timestamp = obj.timestamp; andrewm@0: return *this; andrewm@0: } andrewm@0: andrewm@0: key_buffer_index index; andrewm@0: key_position position; andrewm@0: timestamp_type timestamp; andrewm@0: }; andrewm@0: andrewm@0: // Collection of features related to whether a key is percussively played or not andrewm@0: struct PercussivenessFeatures { andrewm@0: float percussiveness; // Calculated single feature based on everything below andrewm@0: Event velocitySpikeMaximum; // Maximum and minimum points of the initial andrewm@0: Event velocitySpikeMinimum; // velocity spike on a percussive press andrewm@0: timestamp_type timeFromStartToSpike; // How long it took to reach the velocity spike andrewm@0: key_velocity areaPrecedingSpike; // Total sum of velocity values from start to max andrewm@0: key_velocity areaFollowingSpike; // Total sum of velocity values from max to min andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: // ***** Constructors ***** andrewm@0: andrewm@0: // Default constructor, passing the buffer on which to trigger andrewm@0: KeyPositionTracker(capacity_type capacity, Node& keyBuffer); andrewm@0: andrewm@0: // Copy constructor andrewm@0: //KeyPositionTracker(KeyPositionTracker const& obj); andrewm@0: andrewm@0: // ***** State Access ***** andrewm@0: andrewm@0: // Whether this object is currently tracking states andrewm@0: bool engaged() { andrewm@0: return engaged_; andrewm@0: } andrewm@0: andrewm@0: // Return the current state (unknown if nothing is in the buffer) andrewm@0: int currentState() { andrewm@0: return currentState_; andrewm@0: } andrewm@0: andrewm@0: // Information about important recent points andrewm@0: Event currentMax() { andrewm@0: return Event(currentMaxIndex_, currentMaxPosition_, currentMaxTimestamp_); andrewm@0: } andrewm@0: Event currentMin() { andrewm@0: return Event(currentMinIndex_, currentMinPosition_, currentMinTimestamp_); andrewm@0: } andrewm@0: Event pressStart() { andrewm@0: return Event(startIndex_, startPosition_, startTimestamp_); andrewm@0: } andrewm@0: Event pressFinish() { andrewm@0: return Event(pressIndex_, pressPosition_, pressTimestamp_); andrewm@0: } andrewm@0: Event releaseStart() { andrewm@0: return Event(releaseBeginIndex_, releaseBeginPosition_, releaseBeginTimestamp_); andrewm@0: } andrewm@0: Event releaseFinish() { andrewm@0: return Event(releaseEndIndex_, releaseEndPosition_, releaseEndTimestamp_); andrewm@0: } andrewm@0: andrewm@0: // ***** Key Press Features ***** andrewm@0: andrewm@0: // Velocity for onset and release. The values without an argument use the stored andrewm@0: // current escapement point (which is also used for notification of availability). andrewm@0: std::pair pressVelocity(); andrewm@0: std::pair releaseVelocity(); andrewm@0: andrewm@0: std::pair pressVelocity(key_position escapementPosition); andrewm@0: std::pair releaseVelocity(key_position returnPosition); andrewm@0: andrewm@0: // Set the threshold where we look for press velocity calculations. It andrewm@0: // can be anything up to the press position threshold on the upward side andrewm@0: // and anything down to the final release position on the downward side. andrewm@0: void setPressVelocityEscapementPosition(key_position pos) { andrewm@0: if(pos > kPositionTrackerPressPosition + kPositionTrackerPressHysteresis) andrewm@0: pressVelocityEscapementPosition_ = kPositionTrackerPressPosition + kPositionTrackerPressHysteresis; andrewm@0: else andrewm@0: pressVelocityEscapementPosition_ = pos; andrewm@0: } andrewm@0: void setReleaseVelocityEscapementPosition(key_position pos) { andrewm@0: if(pos < kPositionTrackerReleaseFinishPosition) andrewm@0: releaseVelocityEscapementPosition_ = kPositionTrackerReleaseFinishPosition; andrewm@0: else andrewm@0: releaseVelocityEscapementPosition_ = pos; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: // Percussiveness (struck vs. pressed keys) andrewm@0: PercussivenessFeatures pressPercussiveness(); andrewm@0: andrewm@0: // ***** Modifiers ***** andrewm@0: andrewm@0: // Register for updates from the key positon buffer andrewm@0: void engage(); andrewm@0: andrewm@0: // Unregister for updates from the key position buffer andrewm@0: void disengage(); andrewm@0: andrewm@0: // Reset the state back initial values andrewm@0: void reset(); andrewm@0: andrewm@0: // ***** Evaluator ***** andrewm@0: andrewm@0: // This method receives triggers whenever a new sample enters the buffer. It updates andrewm@0: // the state depending on the profile of the key position. andrewm@0: void triggerReceived(TriggerSource* who, timestamp_type timestamp); andrewm@0: andrewm@0: private: andrewm@0: // ***** Internal Helper Methods ***** andrewm@0: andrewm@0: // Change the current state andrewm@0: void changeState(int newState, timestamp_type timestamp); andrewm@0: andrewm@0: // Insert a new feature notification andrewm@0: void notifyFeature(int notificationType, timestamp_type timestamp); andrewm@0: andrewm@0: // Work backwards in the key position buffer to find the start/release of a press andrewm@0: void findKeyPressStart(timestamp_type timestamp); andrewm@0: void findKeyReleaseStart(timestamp_type timestamp); andrewm@0: andrewm@0: // Generic method to find the most recent crossing of a given point andrewm@0: key_buffer_index findMostRecentKeyPositionCrossing(key_position threshold, bool greaterThan, int maxDistance); andrewm@0: andrewm@0: // Look for the crossing of the release velocity threshold to prepare to send the feature andrewm@0: void prepareReleaseVelocityFeature(KeyPositionTracker::key_buffer_index mostRecentIndex, timestamp_type timestamp); andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: Node& keyBuffer_; // Raw key position data andrewm@0: bool engaged_; // Whether we're actively listening to incoming updates andrewm@0: int currentState_; // Our current state andrewm@0: int currentlyAvailableFeatures_; // Which features can be calculated for the current press andrewm@0: andrewm@0: // Position tracking information for significant points (minima and maxima) andrewm@0: key_position startPosition_; // Position of where the key press started andrewm@0: timestamp_type startTimestamp_; // Timestamp of where the key press started andrewm@0: key_buffer_index startIndex_; // Index in the buffer where the start occurred andrewm@0: key_position pressPosition_; // Position of where the key is fully pressed andrewm@0: timestamp_type pressTimestamp_; // Timestamp of where the key is fully pressed andrewm@0: key_buffer_index pressIndex_; // Index in the buffer where the press occurred andrewm@0: key_position releaseBeginPosition_; // Position of where the key release began andrewm@0: timestamp_type releaseBeginTimestamp_; // Timestamp of where the key release began andrewm@0: key_buffer_index releaseBeginIndex_; // Index in the buffer of where the key release began andrewm@0: key_position releaseEndPosition_; // Position of where the key release ended andrewm@0: timestamp_type releaseEndTimestamp_; // Timestamp of where the key release ended andrewm@0: key_buffer_index releaseEndIndex_; // Index in the buffer of where the key release ended andrewm@0: key_position currentMinPosition_, currentMaxPosition_; // Running min and max key position andrewm@0: timestamp_type currentMinTimestamp_, currentMaxTimestamp_; // Times for the above positions andrewm@0: key_buffer_index currentMinIndex_, currentMaxIndex_; // Indices in the buffer for the recent min/max andrewm@0: key_position lastMinMaxPosition_; // Position of the last significant point andrewm@0: andrewm@0: // Persistent parameters relating to feature calculation andrewm@0: key_position pressVelocityEscapementPosition_; // Position at which onset velocity is calculated andrewm@0: key_position releaseVelocityEscapementPosition_; // Position at which release velocity is calculate andrewm@0: key_buffer_index pressVelocityAvailableIndex_; // When we can calculate press velocity andrewm@0: key_buffer_index releaseVelocityAvailableIndex_; // When we can calculate release velocity andrewm@0: bool releaseVelocityWaitingForThresholdCross_; // Set to true if we need to look for release escapement cross andrewm@0: key_buffer_index percussivenessAvailableIndex_; // When we can calculate percussiveness features andrewm@0: andrewm@0: /* andrewm@0: typedef struct { andrewm@0: int runningSum; // sum of last N points (i.e. mean * N) andrewm@0: int runningSumMaxLength; // the value of N above andrewm@0: int runningSumCurrentLength; // How many values are actually part of the sum right now (transient condition) andrewm@0: int startValuesSum; // sum of the last N start values (to calculate returning quiescent position) andrewm@0: int startValuesSumMaxLength; andrewm@0: int startValuesSumCurrentLength; andrewm@0: andrewm@0: int maxVariation; // The maximum deviation from mean of the last group of samples andrewm@0: int flatCounter; // how many successive samples have been "flat" (minimal change) andrewm@0: int currentStartValue; // values and positions of several key points for active keys andrewm@0: int currentStartPosition; andrewm@0: int currentMinValue; andrewm@0: int currentMinPosition; andrewm@0: int currentMaxValue; andrewm@0: int currentMaxPosition; andrewm@0: int lastKeyPointValue; // the value of the last important point {start, max, min} andrewm@0: andrewm@0: deque recentKeyPoints; // the minima and maxima since the key started andrewm@0: bool sentPercussiveMidiOn; // HACK: whether we've sent the percussive MIDI event andrewm@0: andrewm@0: int pressValue; // the value at the maximum corresponding to the end of the key press motion andrewm@0: int pressPosition; // the location in the buffer of this event (note: not the timestamp) andrewm@0: int releaseValue; // the value the key held right before release andrewm@0: int releasePosition; // the location in the buffer of the release corner andrewm@0: } keyParameters; andrewm@0: */ andrewm@0: }; andrewm@0: andrewm@0: andrewm@0: #endif /* defined(__touchkeys__KeyPositionTracker__) */