andrewm@0
|
1 /*
|
andrewm@0
|
2 TouchKeys: multi-touch musical keyboard control software
|
andrewm@0
|
3 Copyright (c) 2013 Andrew McPherson
|
andrewm@0
|
4
|
andrewm@0
|
5 This program is free software: you can redistribute it and/or modify
|
andrewm@0
|
6 it under the terms of the GNU General Public License as published by
|
andrewm@0
|
7 the Free Software Foundation, either version 3 of the License, or
|
andrewm@0
|
8 (at your option) any later version.
|
andrewm@0
|
9
|
andrewm@0
|
10 This program is distributed in the hope that it will be useful,
|
andrewm@0
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
andrewm@0
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
andrewm@0
|
13 GNU General Public License for more details.
|
andrewm@0
|
14
|
andrewm@0
|
15 You should have received a copy of the GNU General Public License
|
andrewm@0
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
andrewm@0
|
17
|
andrewm@0
|
18 =====================================================================
|
andrewm@0
|
19
|
andrewm@0
|
20 KeyPositionTracker.h: parses continuous key position and detects the
|
andrewm@0
|
21 state of the key.
|
andrewm@0
|
22 */
|
andrewm@0
|
23
|
andrewm@0
|
24
|
andrewm@0
|
25 #ifndef __touchkeys__KeyPositionTracker__
|
andrewm@0
|
26 #define __touchkeys__KeyPositionTracker__
|
andrewm@0
|
27
|
andrewm@0
|
28 #include <set>
|
andrewm@0
|
29 #include "../Utility/Node.h"
|
andrewm@0
|
30 #include "../Utility/Accumulator.h"
|
andrewm@0
|
31 #include "PianoTypes.h"
|
andrewm@0
|
32
|
andrewm@0
|
33 // Three states of idle detector
|
andrewm@0
|
34 enum {
|
andrewm@0
|
35 kPositionTrackerStateUnknown = 0,
|
andrewm@0
|
36 kPositionTrackerStatePartialPressAwaitingMax,
|
andrewm@0
|
37 kPositionTrackerStatePartialPressFoundMax,
|
andrewm@0
|
38 kPositionTrackerStatePressInProgress,
|
andrewm@0
|
39 kPositionTrackerStateDown,
|
andrewm@0
|
40 kPositionTrackerStateReleaseInProgress,
|
andrewm@0
|
41 kPositionTrackerStateReleaseFinished
|
andrewm@0
|
42 };
|
andrewm@0
|
43
|
andrewm@0
|
44 // Constants for key state detection
|
andrewm@0
|
45 const key_position kPositionTrackerPressPosition = scale_key_position(0.75);
|
andrewm@0
|
46 const key_position kPositionTrackerPressHysteresis = scale_key_position(0.05);
|
andrewm@0
|
47 const key_position kPositionTrackerMinMaxSpacingThreshold = scale_key_position(0.02);
|
andrewm@0
|
48 const key_position kPositionTrackerFirstMaxThreshold = scale_key_position(0.075);
|
andrewm@0
|
49 const key_position kPositionTrackerReleaseFinishPosition = scale_key_position(0.2);
|
andrewm@0
|
50
|
andrewm@0
|
51 // How far back to search at the beginning to find the real start or release of a key press
|
andrewm@0
|
52 const int kPositionTrackerSamplesToSearchForStartLocation = 50;
|
andrewm@0
|
53 const int kPositionTrackerSamplesToSearchBeyondStartLocation = 20;
|
andrewm@0
|
54 const int kPositionTrackerSamplesToSearchForReleaseLocation = 100;
|
andrewm@0
|
55 const int kPositionTrackerSamplesToAverageForStartVelocity = 3;
|
andrewm@0
|
56 const key_velocity kPositionTrackerStartVelocityThreshold = scale_key_velocity(0.5);
|
andrewm@0
|
57 const key_velocity kPositionTrackerStartVelocitySpikeThreshold = scale_key_velocity(2.5);
|
andrewm@0
|
58 const key_velocity kPositionTrackerReleaseVelocityThreshold = scale_key_velocity(-0.2);
|
andrewm@0
|
59
|
andrewm@0
|
60 // Constants for feature calculations. The first one is the approximate location of the escapement
|
andrewm@0
|
61 // (empirically measured on one piano, so only approximate), used for velocity calculations
|
andrewm@0
|
62 const key_position kPositionTrackerDefaultPositionForPressVelocityCalculation = scale_key_position(0.65);
|
andrewm@0
|
63 const key_position kPositionTrackerDefaultPositionForReleaseVelocityCalculation = scale_key_position(0.5);
|
andrewm@0
|
64 const key_position kPositionTrackerPositionThresholdForPercussivenessCalculation = scale_key_position(0.4);
|
andrewm@0
|
65 const int kPositionTrackerSamplesNeededForPressVelocityAfterEscapement = 1;
|
andrewm@0
|
66 const int kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement = 1;
|
andrewm@0
|
67
|
andrewm@0
|
68 // KeyPositionTrackerNotification
|
andrewm@0
|
69 //
|
andrewm@0
|
70 // This class contains information on the notifications sent and stored by
|
andrewm@0
|
71 // KeyPositionTracker. Includes state changes and feature available.
|
andrewm@0
|
72
|
andrewm@0
|
73 class KeyPositionTrackerNotification {
|
andrewm@0
|
74 public:
|
andrewm@0
|
75 enum {
|
andrewm@0
|
76 kNotificationTypeStateChange = 1,
|
andrewm@0
|
77 kNotificationTypeFeatureAvailableVelocity,
|
andrewm@0
|
78 kNotificationTypeFeatureAvailableReleaseVelocity,
|
andrewm@0
|
79 kNotificationTypeFeatureAvailablePercussiveness,
|
andrewm@0
|
80 kNotificationTypeNewMinimum,
|
andrewm@0
|
81 kNotificationTypeNewMaximum
|
andrewm@0
|
82 };
|
andrewm@0
|
83
|
andrewm@0
|
84 enum {
|
andrewm@0
|
85 kFeaturesNone = 0,
|
andrewm@0
|
86 kFeaturePressVelocity = 0x0001,
|
andrewm@0
|
87 kFeatureReleaseVelocity = 0x0002,
|
andrewm@0
|
88 kFeaturePercussiveness = 0x0004
|
andrewm@0
|
89 };
|
andrewm@0
|
90
|
andrewm@0
|
91 int type;
|
andrewm@0
|
92 int state;
|
andrewm@0
|
93 int features;
|
andrewm@0
|
94 };
|
andrewm@0
|
95
|
andrewm@0
|
96 // KeyPositionTracker
|
andrewm@0
|
97 //
|
andrewm@0
|
98 // This class implements a state machine for a key that is currently active (not idle),
|
andrewm@0
|
99 // using continuous key position data to determine the location and parameters of key
|
andrewm@0
|
100 // presses and other events. It includes management of partial press patterns and detection
|
andrewm@0
|
101 // of percussiveness as well as velocity features.
|
andrewm@0
|
102 //
|
andrewm@0
|
103 // This class is triggered by new data points in the key position buffer. Its output is
|
andrewm@0
|
104 // a series of state changes which indicate what the key is doing.
|
andrewm@0
|
105
|
andrewm@0
|
106 class KeyPositionTracker : public Node<KeyPositionTrackerNotification> {
|
andrewm@0
|
107 public:
|
andrewm@0
|
108 typedef Node<key_position>::size_type key_buffer_index;
|
andrewm@0
|
109 //typedef void (*KeyActionFunction)(KeyPositionTracker *object, void *userData);
|
andrewm@0
|
110
|
andrewm@0
|
111 // Simple class to hold index/position/timestamp triads
|
andrewm@0
|
112 class Event {
|
andrewm@0
|
113 public:
|
andrewm@0
|
114 Event() : index(0), position(missing_value<key_position>::missing()),
|
andrewm@0
|
115 timestamp(missing_value<timestamp_type>::missing()) {}
|
andrewm@0
|
116
|
andrewm@0
|
117 Event(key_buffer_index i, key_position p, timestamp_type t)
|
andrewm@0
|
118 : index(i), position(p), timestamp(t) {}
|
andrewm@0
|
119
|
andrewm@0
|
120 Event(const Event& obj)
|
andrewm@0
|
121 : index(obj.index), position(obj.position), timestamp(obj.timestamp) {}
|
andrewm@0
|
122
|
andrewm@0
|
123 Event& operator=(const Event& obj) {
|
andrewm@0
|
124 index = obj.index;
|
andrewm@0
|
125 position = obj.position;
|
andrewm@0
|
126 timestamp = obj.timestamp;
|
andrewm@0
|
127 return *this;
|
andrewm@0
|
128 }
|
andrewm@0
|
129
|
andrewm@0
|
130 key_buffer_index index;
|
andrewm@0
|
131 key_position position;
|
andrewm@0
|
132 timestamp_type timestamp;
|
andrewm@0
|
133 };
|
andrewm@0
|
134
|
andrewm@0
|
135 // Collection of features related to whether a key is percussively played or not
|
andrewm@0
|
136 struct PercussivenessFeatures {
|
andrewm@0
|
137 float percussiveness; // Calculated single feature based on everything below
|
andrewm@0
|
138 Event velocitySpikeMaximum; // Maximum and minimum points of the initial
|
andrewm@0
|
139 Event velocitySpikeMinimum; // velocity spike on a percussive press
|
andrewm@0
|
140 timestamp_type timeFromStartToSpike; // How long it took to reach the velocity spike
|
andrewm@0
|
141 key_velocity areaPrecedingSpike; // Total sum of velocity values from start to max
|
andrewm@0
|
142 key_velocity areaFollowingSpike; // Total sum of velocity values from max to min
|
andrewm@0
|
143 };
|
andrewm@0
|
144
|
andrewm@0
|
145 public:
|
andrewm@0
|
146 // ***** Constructors *****
|
andrewm@0
|
147
|
andrewm@0
|
148 // Default constructor, passing the buffer on which to trigger
|
andrewm@0
|
149 KeyPositionTracker(capacity_type capacity, Node<key_position>& keyBuffer);
|
andrewm@0
|
150
|
andrewm@0
|
151 // Copy constructor
|
andrewm@0
|
152 //KeyPositionTracker(KeyPositionTracker const& obj);
|
andrewm@0
|
153
|
andrewm@0
|
154 // ***** State Access *****
|
andrewm@0
|
155
|
andrewm@0
|
156 // Whether this object is currently tracking states
|
andrewm@0
|
157 bool engaged() {
|
andrewm@0
|
158 return engaged_;
|
andrewm@0
|
159 }
|
andrewm@0
|
160
|
andrewm@0
|
161 // Return the current state (unknown if nothing is in the buffer)
|
andrewm@0
|
162 int currentState() {
|
andrewm@0
|
163 return currentState_;
|
andrewm@0
|
164 }
|
andrewm@0
|
165
|
andrewm@0
|
166 // Information about important recent points
|
andrewm@0
|
167 Event currentMax() {
|
andrewm@0
|
168 return Event(currentMaxIndex_, currentMaxPosition_, currentMaxTimestamp_);
|
andrewm@0
|
169 }
|
andrewm@0
|
170 Event currentMin() {
|
andrewm@0
|
171 return Event(currentMinIndex_, currentMinPosition_, currentMinTimestamp_);
|
andrewm@0
|
172 }
|
andrewm@0
|
173 Event pressStart() {
|
andrewm@0
|
174 return Event(startIndex_, startPosition_, startTimestamp_);
|
andrewm@0
|
175 }
|
andrewm@0
|
176 Event pressFinish() {
|
andrewm@0
|
177 return Event(pressIndex_, pressPosition_, pressTimestamp_);
|
andrewm@0
|
178 }
|
andrewm@0
|
179 Event releaseStart() {
|
andrewm@0
|
180 return Event(releaseBeginIndex_, releaseBeginPosition_, releaseBeginTimestamp_);
|
andrewm@0
|
181 }
|
andrewm@0
|
182 Event releaseFinish() {
|
andrewm@0
|
183 return Event(releaseEndIndex_, releaseEndPosition_, releaseEndTimestamp_);
|
andrewm@0
|
184 }
|
andrewm@0
|
185
|
andrewm@0
|
186 // ***** Key Press Features *****
|
andrewm@0
|
187
|
andrewm@0
|
188 // Velocity for onset and release. The values without an argument use the stored
|
andrewm@0
|
189 // current escapement point (which is also used for notification of availability).
|
andrewm@0
|
190 std::pair<timestamp_type, key_velocity> pressVelocity();
|
andrewm@0
|
191 std::pair<timestamp_type, key_velocity> releaseVelocity();
|
andrewm@0
|
192
|
andrewm@0
|
193 std::pair<timestamp_type, key_velocity> pressVelocity(key_position escapementPosition);
|
andrewm@0
|
194 std::pair<timestamp_type, key_velocity> releaseVelocity(key_position returnPosition);
|
andrewm@0
|
195
|
andrewm@0
|
196 // Set the threshold where we look for press velocity calculations. It
|
andrewm@0
|
197 // can be anything up to the press position threshold on the upward side
|
andrewm@0
|
198 // and anything down to the final release position on the downward side.
|
andrewm@0
|
199 void setPressVelocityEscapementPosition(key_position pos) {
|
andrewm@0
|
200 if(pos > kPositionTrackerPressPosition + kPositionTrackerPressHysteresis)
|
andrewm@0
|
201 pressVelocityEscapementPosition_ = kPositionTrackerPressPosition + kPositionTrackerPressHysteresis;
|
andrewm@0
|
202 else
|
andrewm@0
|
203 pressVelocityEscapementPosition_ = pos;
|
andrewm@0
|
204 }
|
andrewm@0
|
205 void setReleaseVelocityEscapementPosition(key_position pos) {
|
andrewm@0
|
206 if(pos < kPositionTrackerReleaseFinishPosition)
|
andrewm@0
|
207 releaseVelocityEscapementPosition_ = kPositionTrackerReleaseFinishPosition;
|
andrewm@0
|
208 else
|
andrewm@0
|
209 releaseVelocityEscapementPosition_ = pos;
|
andrewm@0
|
210 }
|
andrewm@0
|
211
|
andrewm@0
|
212
|
andrewm@0
|
213 // Percussiveness (struck vs. pressed keys)
|
andrewm@0
|
214 PercussivenessFeatures pressPercussiveness();
|
andrewm@0
|
215
|
andrewm@0
|
216 // ***** Modifiers *****
|
andrewm@0
|
217
|
andrewm@0
|
218 // Register for updates from the key positon buffer
|
andrewm@0
|
219 void engage();
|
andrewm@0
|
220
|
andrewm@0
|
221 // Unregister for updates from the key position buffer
|
andrewm@0
|
222 void disengage();
|
andrewm@0
|
223
|
andrewm@0
|
224 // Reset the state back initial values
|
andrewm@0
|
225 void reset();
|
andrewm@0
|
226
|
andrewm@0
|
227 // ***** Evaluator *****
|
andrewm@0
|
228
|
andrewm@0
|
229 // This method receives triggers whenever a new sample enters the buffer. It updates
|
andrewm@0
|
230 // the state depending on the profile of the key position.
|
andrewm@0
|
231 void triggerReceived(TriggerSource* who, timestamp_type timestamp);
|
andrewm@0
|
232
|
andrewm@0
|
233 private:
|
andrewm@0
|
234 // ***** Internal Helper Methods *****
|
andrewm@0
|
235
|
andrewm@0
|
236 // Change the current state
|
andrewm@0
|
237 void changeState(int newState, timestamp_type timestamp);
|
andrewm@0
|
238
|
andrewm@0
|
239 // Insert a new feature notification
|
andrewm@0
|
240 void notifyFeature(int notificationType, timestamp_type timestamp);
|
andrewm@0
|
241
|
andrewm@0
|
242 // Work backwards in the key position buffer to find the start/release of a press
|
andrewm@0
|
243 void findKeyPressStart(timestamp_type timestamp);
|
andrewm@0
|
244 void findKeyReleaseStart(timestamp_type timestamp);
|
andrewm@0
|
245
|
andrewm@0
|
246 // Generic method to find the most recent crossing of a given point
|
andrewm@0
|
247 key_buffer_index findMostRecentKeyPositionCrossing(key_position threshold, bool greaterThan, int maxDistance);
|
andrewm@0
|
248
|
andrewm@0
|
249 // Look for the crossing of the release velocity threshold to prepare to send the feature
|
andrewm@0
|
250 void prepareReleaseVelocityFeature(KeyPositionTracker::key_buffer_index mostRecentIndex, timestamp_type timestamp);
|
andrewm@0
|
251
|
andrewm@0
|
252 // ***** Member Variables *****
|
andrewm@0
|
253
|
andrewm@0
|
254 Node<key_position>& keyBuffer_; // Raw key position data
|
andrewm@0
|
255 bool engaged_; // Whether we're actively listening to incoming updates
|
andrewm@0
|
256 int currentState_; // Our current state
|
andrewm@0
|
257 int currentlyAvailableFeatures_; // Which features can be calculated for the current press
|
andrewm@0
|
258
|
andrewm@0
|
259 // Position tracking information for significant points (minima and maxima)
|
andrewm@0
|
260 key_position startPosition_; // Position of where the key press started
|
andrewm@0
|
261 timestamp_type startTimestamp_; // Timestamp of where the key press started
|
andrewm@0
|
262 key_buffer_index startIndex_; // Index in the buffer where the start occurred
|
andrewm@0
|
263 key_position pressPosition_; // Position of where the key is fully pressed
|
andrewm@0
|
264 timestamp_type pressTimestamp_; // Timestamp of where the key is fully pressed
|
andrewm@0
|
265 key_buffer_index pressIndex_; // Index in the buffer where the press occurred
|
andrewm@0
|
266 key_position releaseBeginPosition_; // Position of where the key release began
|
andrewm@0
|
267 timestamp_type releaseBeginTimestamp_; // Timestamp of where the key release began
|
andrewm@0
|
268 key_buffer_index releaseBeginIndex_; // Index in the buffer of where the key release began
|
andrewm@0
|
269 key_position releaseEndPosition_; // Position of where the key release ended
|
andrewm@0
|
270 timestamp_type releaseEndTimestamp_; // Timestamp of where the key release ended
|
andrewm@0
|
271 key_buffer_index releaseEndIndex_; // Index in the buffer of where the key release ended
|
andrewm@0
|
272 key_position currentMinPosition_, currentMaxPosition_; // Running min and max key position
|
andrewm@0
|
273 timestamp_type currentMinTimestamp_, currentMaxTimestamp_; // Times for the above positions
|
andrewm@0
|
274 key_buffer_index currentMinIndex_, currentMaxIndex_; // Indices in the buffer for the recent min/max
|
andrewm@0
|
275 key_position lastMinMaxPosition_; // Position of the last significant point
|
andrewm@0
|
276
|
andrewm@0
|
277 // Persistent parameters relating to feature calculation
|
andrewm@0
|
278 key_position pressVelocityEscapementPosition_; // Position at which onset velocity is calculated
|
andrewm@0
|
279 key_position releaseVelocityEscapementPosition_; // Position at which release velocity is calculate
|
andrewm@0
|
280 key_buffer_index pressVelocityAvailableIndex_; // When we can calculate press velocity
|
andrewm@0
|
281 key_buffer_index releaseVelocityAvailableIndex_; // When we can calculate release velocity
|
andrewm@0
|
282 bool releaseVelocityWaitingForThresholdCross_; // Set to true if we need to look for release escapement cross
|
andrewm@0
|
283 key_buffer_index percussivenessAvailableIndex_; // When we can calculate percussiveness features
|
andrewm@0
|
284
|
andrewm@0
|
285 /*
|
andrewm@0
|
286 typedef struct {
|
andrewm@0
|
287 int runningSum; // sum of last N points (i.e. mean * N)
|
andrewm@0
|
288 int runningSumMaxLength; // the value of N above
|
andrewm@0
|
289 int runningSumCurrentLength; // How many values are actually part of the sum right now (transient condition)
|
andrewm@0
|
290 int startValuesSum; // sum of the last N start values (to calculate returning quiescent position)
|
andrewm@0
|
291 int startValuesSumMaxLength;
|
andrewm@0
|
292 int startValuesSumCurrentLength;
|
andrewm@0
|
293
|
andrewm@0
|
294 int maxVariation; // The maximum deviation from mean of the last group of samples
|
andrewm@0
|
295 int flatCounter; // how many successive samples have been "flat" (minimal change)
|
andrewm@0
|
296 int currentStartValue; // values and positions of several key points for active keys
|
andrewm@0
|
297 int currentStartPosition;
|
andrewm@0
|
298 int currentMinValue;
|
andrewm@0
|
299 int currentMinPosition;
|
andrewm@0
|
300 int currentMaxValue;
|
andrewm@0
|
301 int currentMaxPosition;
|
andrewm@0
|
302 int lastKeyPointValue; // the value of the last important point {start, max, min}
|
andrewm@0
|
303
|
andrewm@0
|
304 deque<keyPointHistory> recentKeyPoints; // the minima and maxima since the key started
|
andrewm@0
|
305 bool sentPercussiveMidiOn; // HACK: whether we've sent the percussive MIDI event
|
andrewm@0
|
306
|
andrewm@0
|
307 int pressValue; // the value at the maximum corresponding to the end of the key press motion
|
andrewm@0
|
308 int pressPosition; // the location in the buffer of this event (note: not the timestamp)
|
andrewm@0
|
309 int releaseValue; // the value the key held right before release
|
andrewm@0
|
310 int releasePosition; // the location in the buffer of the release corner
|
andrewm@0
|
311 } keyParameters;
|
andrewm@0
|
312 */
|
andrewm@0
|
313 };
|
andrewm@0
|
314
|
andrewm@0
|
315
|
andrewm@0
|
316 #endif /* defined(__touchkeys__KeyPositionTracker__) */
|