comparison Source/TouchKeys/KeyPositionTracker.cpp @ 0:3580ffe87dc8

First commit of TouchKeys public pre-release.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Mon, 11 Nov 2013 18:19:35 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3580ffe87dc8
1 /*
2 TouchKeys: multi-touch musical keyboard control software
3 Copyright (c) 2013 Andrew McPherson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 =====================================================================
19
20 KeyPositionTracker.cpp: parses continuous key position and detects the
21 state of the key.
22 */
23
24 #include "KeyPositionTracker.h"
25
26 // Default constructor
27 KeyPositionTracker::KeyPositionTracker(capacity_type capacity, Node<key_position>& keyBuffer)
28 : Node<KeyPositionTrackerNotification>(capacity), keyBuffer_(keyBuffer), engaged_(false) {
29 reset();
30 }
31
32 // Copy constructor
33 /*KeyPositionTracker::KeyPositionTracker(KeyPositionTracker const& obj)
34 : Node<int>(obj), keyBuffer_(obj.keyBuffer_), engaged_(obj.engaged_) {
35 if(engaged_)
36 registerForTrigger(&keyBuffer_);
37 }*/
38
39 // Calculate (MIDI-style) key press velocity from continuous key position
40 std::pair<timestamp_type, key_velocity> KeyPositionTracker::pressVelocity() {
41 return pressVelocity(pressVelocityEscapementPosition_);
42 }
43
44 std::pair<timestamp_type, key_velocity> KeyPositionTracker::pressVelocity(key_position escapementPosition) {
45 // Check that we have a valid start point from which to calculate
46 if(missing_value<timestamp_type>::isMissing(startTimestamp_)) {
47 return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(),
48 missing_value<key_velocity>::missing());
49 }
50
51 // Find where the key position crosses the indicated level
52 key_buffer_index index = startIndex_;
53 if(index < keyBuffer_.beginIndex() + 2)
54 index = keyBuffer_.beginIndex() + 2;
55
56 while(index < keyBuffer_.endIndex() - kPositionTrackerSamplesNeededForPressVelocityAfterEscapement) {
57 // If the key press has a defined end, make sure we don't go past it
58 if(pressIndex_ != 0 && index >= pressIndex_)
59 break;
60
61 if(keyBuffer_[index] > escapementPosition) {
62 // Found the place the position crosses the indicated threshold
63 // Now find the exact (interpolated) timestamp and velocity
64 timestamp_type exactPressTimestamp = keyBuffer_.timestampAt(index); // TODO
65
66 // Velocity is calculated by an average of 2 samples before and 1 after
67 key_position diffPosition = keyBuffer_[index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement] - keyBuffer_[index - 2];
68 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement) - keyBuffer_.timestampAt(index - 2);
69 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
70
71 return std::pair<timestamp_type, key_velocity>(exactPressTimestamp, velocity);
72 }
73 index++;
74 }
75
76 // Didn't find anything matching that threshold
77 return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(),
78 missing_value<key_velocity>::missing());
79 }
80
81 // Calculate (MIDI-style) key release velocity from continuous key position
82 std::pair<timestamp_type, key_velocity> KeyPositionTracker::releaseVelocity() {
83 return releaseVelocity(releaseVelocityEscapementPosition_);
84 }
85
86 std::pair<timestamp_type, key_velocity> KeyPositionTracker::releaseVelocity(key_position returnPosition) {
87 // Check that we have a valid start point from which to calculate
88 if(missing_value<timestamp_type>::isMissing(releaseBeginTimestamp_)) {
89 return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(),
90 missing_value<key_velocity>::missing());
91 }
92
93 // Find where the key position crosses the indicated level
94 key_buffer_index index = releaseBeginIndex_;
95 if(index < keyBuffer_.beginIndex() + 2)
96 index = keyBuffer_.beginIndex() + 2;
97
98 while(index < keyBuffer_.endIndex() - kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement) {
99 // Check for whether we've hit the end of the release interval, assuming
100 // the interval exists yet
101 if(releaseEndIndex_ != 0 && index >= releaseEndIndex_)
102 break;
103
104 if(keyBuffer_[index] < returnPosition) {
105 // Found the place the position crosses the indicated threshold
106 // Now find the exact (interpolated) timestamp and velocity
107 timestamp_type exactPressTimestamp = keyBuffer_.timestampAt(index); // TODO
108
109 // Velocity is calculated by an average of 2 samples before and 1 after
110 key_position diffPosition = keyBuffer_[index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement] - keyBuffer_[index - 2];
111 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement) - keyBuffer_.timestampAt(index - 2);
112 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
113
114 std::cout << "found release velocity " << velocity << "(diffp " << diffPosition << ", diffT " << diffTimestamp << ")" << std::endl;
115
116 return std::pair<timestamp_type, key_velocity>(exactPressTimestamp, velocity);
117 }
118 index++;
119 }
120
121 // Didn't find anything matching that threshold
122 return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(),
123 missing_value<key_velocity>::missing());
124 }
125
126 // Calculate and return features about the percussiveness of the key press
127 KeyPositionTracker::PercussivenessFeatures KeyPositionTracker::pressPercussiveness() {
128 PercussivenessFeatures features;
129 key_buffer_index index;
130 key_velocity maximumVelocity, largestVelocityDifference;
131 key_buffer_index maximumVelocityIndex, largestVelocityDifferenceIndex;
132
133 // Check that we have a valid start point from which to calculate
134 if(missing_value<timestamp_type>::isMissing(startTimestamp_) || keyBuffer_.beginIndex() > startIndex_ - 1) {
135 std::cout << "*** no start time\n";
136 features.percussiveness = missing_value<float>::missing();
137 return features;
138 }
139
140 // From the start of the key press, look for an initial maximum in velocity
141 index = startIndex_;
142
143 maximumVelocity = scale_key_velocity(0);
144 maximumVelocityIndex = startIndex_;
145 largestVelocityDifference = scale_key_velocity(0);
146 largestVelocityDifferenceIndex = startIndex_;
147
148 std::cout << "*** start index " << index << std::endl;
149
150 while(index < keyBuffer_.endIndex()) {
151 if(pressIndex_ != 0 && index >= pressIndex_)
152 break;
153
154 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1];
155 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1);
156 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
157
158 // Look for maximum of velocity
159 if(velocity > maximumVelocity) {
160 maximumVelocity = velocity;
161 maximumVelocityIndex = index;
162 std::cout << "*** found new max velocity " << maximumVelocity << " at index " << index << std::endl;
163 }
164
165 // And given the difference between the max and the current sample,
166 // look for the largest rebound (velocity hitting a peak and falling)
167 if(maximumVelocity - velocity > largestVelocityDifference) {
168 largestVelocityDifference = maximumVelocity - velocity;
169 largestVelocityDifferenceIndex = index;
170 std::cout << "*** found new diff velocity " << largestVelocityDifference << " at index " << index << std::endl;
171 }
172
173 // Only look at the early part of the key press: if the key position
174 // makes it more than a certain amount down, assume the initial spike
175 // has passed and finish up. But always allow at least 5 points for the
176 // fastest key presses to be considered.
177 if(index - startIndex_ >= 4 && keyBuffer_[index] > kPositionTrackerPositionThresholdForPercussivenessCalculation) {
178 break;
179 }
180
181 index++;
182 }
183
184 // Now transfer what we've found to the data structure
185 features.velocitySpikeMaximum = Event(maximumVelocityIndex, maximumVelocity, keyBuffer_.timestampAt(maximumVelocityIndex));
186 features.velocitySpikeMinimum = Event(largestVelocityDifferenceIndex, maximumVelocity - largestVelocityDifference,
187 keyBuffer_.timestampAt(largestVelocityDifferenceIndex));
188 features.timeFromStartToSpike = keyBuffer_.timestampAt(maximumVelocityIndex) - keyBuffer_.timestampAt(startIndex_);
189
190 // Check if we found a meaningful difference. If not, percussiveness is set to 0
191 if(largestVelocityDifference == scale_key_velocity(0)) {
192 features.percussiveness = 0.0;
193 features.areaPrecedingSpike = scale_key_velocity(0);
194 features.areaFollowingSpike = scale_key_velocity(0);
195 return features;
196 }
197
198 // Calculate the area under the velocity curve before and after the maximum
199 features.areaPrecedingSpike = scale_key_velocity(0);
200 for(index = startIndex_; index < maximumVelocityIndex; index++) {
201 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1];
202 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1);
203 features.areaPrecedingSpike += calculate_key_velocity(diffPosition, diffTimestamp);
204 }
205 features.areaFollowingSpike = scale_key_velocity(0);
206 for(index = maximumVelocityIndex; index < largestVelocityDifferenceIndex; index++) {
207 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1];
208 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1);
209 features.areaFollowingSpike += calculate_key_velocity(diffPosition, diffTimestamp);
210 }
211
212 std::cout << "area before = " << features.areaPrecedingSpike << " after = " << features.areaFollowingSpike << std::endl;
213
214 features.percussiveness = features.velocitySpikeMaximum.position;
215
216 return features;
217 }
218
219 // Register to receive messages from the key buffer on each new sample
220 void KeyPositionTracker::engage() {
221 if(engaged_)
222 return;
223
224 registerForTrigger(&keyBuffer_);
225 engaged_ = true;
226 }
227
228 // Unregister from receiving message on new samples
229 void KeyPositionTracker::disengage() {
230 if(!engaged_)
231 return;
232
233 unregisterForTrigger(&keyBuffer_);
234 engaged_ = false;
235 }
236
237 // Clear current state and reset to unknown state
238 void KeyPositionTracker::reset() {
239 Node<KeyPositionTrackerNotification>::clear();
240
241 currentState_ = kPositionTrackerStateUnknown;
242 currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone;
243 currentMinIndex_ = currentMaxIndex_ = startIndex_ = pressIndex_ = 0;
244 releaseBeginIndex_ = releaseEndIndex_ = 0;
245 lastMinMaxPosition_ = startPosition_ = pressPosition_ = missing_value<key_position>::missing();
246 releaseBeginPosition_ = releaseEndPosition_ = missing_value<key_position>::missing();
247 currentMinPosition_ = currentMaxPosition_ = missing_value<key_position>::missing();
248 startTimestamp_ = pressTimestamp_ = missing_value<timestamp_type>::missing();
249 currentMinTimestamp_ = currentMaxTimestamp_ = missing_value<timestamp_type>::missing();
250 releaseBeginTimestamp_ = releaseEndTimestamp_ = missing_value<timestamp_type>::missing();
251 pressVelocityEscapementPosition_ = kPositionTrackerDefaultPositionForPressVelocityCalculation;
252 releaseVelocityEscapementPosition_ = kPositionTrackerDefaultPositionForReleaseVelocityCalculation;
253 pressVelocityAvailableIndex_ = releaseVelocityAvailableIndex_ = percussivenessAvailableIndex_ = 0;
254 releaseVelocityWaitingForThresholdCross_ = false;
255 }
256
257 // Evaluator function. Update the current state
258 void KeyPositionTracker::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
259
260 if(who != &keyBuffer_)
261 return;
262
263 // Always start in the partial press state after a reset, retroactively locating
264 // the start position for this key press
265 if(empty()) {
266 findKeyPressStart(timestamp);
267 changeState(kPositionTrackerStatePartialPressAwaitingMax, timestamp);
268 }
269
270 key_position currentKeyPosition = keyBuffer_.latest();
271 key_buffer_index currentBufferIndex = keyBuffer_.endIndex() - 1;
272
273 // First, check queued actions to see if we can calculate a new feature
274 // ** Press Velocity **
275 if(pressVelocityAvailableIndex_ != 0) {
276 if(currentBufferIndex >= pressVelocityAvailableIndex_) {
277 // Can now calculate press velocity
278 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePressVelocity;
279 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableVelocity, timestamp);
280 pressVelocityAvailableIndex_ = 0;
281 }
282 }
283 // ** Release Velocity **
284 if(releaseVelocityWaitingForThresholdCross_) {
285 if(currentKeyPosition < releaseVelocityEscapementPosition_)
286 prepareReleaseVelocityFeature(currentBufferIndex, timestamp);
287 }
288 else if(releaseVelocityAvailableIndex_ != 0) {
289 if(currentBufferIndex >= releaseVelocityAvailableIndex_) {
290 // Can now calculate release velocity
291 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeatureReleaseVelocity;
292 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableReleaseVelocity, timestamp);
293 releaseVelocityAvailableIndex_ = 0;
294 }
295 }
296 // ** Percussiveness **
297 if(percussivenessAvailableIndex_ != 0) {
298 if(currentBufferIndex >= percussivenessAvailableIndex_) {
299 // Can now calculate percussiveness
300 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness;
301 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp);
302 percussivenessAvailableIndex_ = 0;
303 }
304 }
305
306 // Major state transitions next, centered on whether the key is pressed
307 // fully or partially
308 if(currentState_ == kPositionTrackerStatePartialPressAwaitingMax ||
309 currentState_ == kPositionTrackerStatePartialPressFoundMax) {
310 // These are collectively the pre-press states
311 if(currentKeyPosition >= kPositionTrackerPressPosition + kPositionTrackerPressHysteresis) {
312 // Key has gone far enough down to be considered pressed, but hasn't necessarily
313 // made it down yet.
314 pressIndex_ = 0;
315 pressPosition_ = missing_value<key_position>::missing();
316 pressTimestamp_ = missing_value<timestamp_type>::missing();
317
318 changeState(kPositionTrackerStatePressInProgress, timestamp);
319 }
320 }
321 else if(currentState_ == kPositionTrackerStateReleaseInProgress ||
322 currentState_ == kPositionTrackerStateReleaseFinished) {
323 if(currentKeyPosition >= kPositionTrackerPressPosition + kPositionTrackerPressHysteresis) {
324 // Key was releasing but is now back down. Need to reprime the start
325 // position information, which will be taken as the last minimum.
326 startIndex_ = currentMinIndex_;
327 startPosition_ = currentMinPosition_;
328 startTimestamp_ = currentMinTimestamp_;
329 pressIndex_ = 0;
330 pressPosition_ = missing_value<key_position>::missing();
331 pressTimestamp_ = missing_value<timestamp_type>::missing();
332
333 changeState(kPositionTrackerStatePressInProgress, timestamp);
334 }
335 }
336 else if(currentState_ == kPositionTrackerStatePressInProgress) {
337 // Press has started, wait to find its max position before labeling the key as "down"
338 if(currentKeyPosition < kPositionTrackerPressPosition - kPositionTrackerPressHysteresis) {
339 // Key is on its way back up: find where release began
340 findKeyReleaseStart(timestamp);
341
342 changeState(kPositionTrackerStateReleaseInProgress, timestamp);
343 }
344 }
345 else if(currentState_ == kPositionTrackerStateDown) {
346 if(currentKeyPosition < kPositionTrackerPressPosition - kPositionTrackerPressHysteresis) {
347 // Key is on its way back up: find where release began
348 findKeyReleaseStart(timestamp);
349
350 changeState(kPositionTrackerStateReleaseInProgress, timestamp);
351 }
352 }
353
354 // Find the maxima and minima of the key motion
355 if(missing_value<key_position>::isMissing(currentMaxPosition_) ||
356 currentKeyPosition > currentMaxPosition_) {
357 // Found a new local maximum
358 currentMaxIndex_ = currentBufferIndex;
359 currentMaxPosition_ = currentKeyPosition;
360 currentMaxTimestamp_ = timestamp;
361
362 // If we previously found a maximum, go back to the original
363 // state so we can process the new max that is in progress
364 if(currentState_ == kPositionTrackerStatePartialPressFoundMax)
365 changeState(kPositionTrackerStatePartialPressAwaitingMax, timestamp);
366 }
367 else if(missing_value<key_position>::isMissing(currentMinPosition_) ||
368 currentKeyPosition < currentMinPosition_) {
369 // Found a new local minimum
370 currentMinIndex_ = currentBufferIndex;
371 currentMinPosition_ = currentKeyPosition;
372 currentMinTimestamp_ = timestamp;
373 }
374
375 // Check if the deviation between min and max exceeds the threshold of significance,
376 // and if so, figure out when a peak occurs
377 if(!missing_value<key_position>::isMissing(currentMaxPosition_) &&
378 !missing_value<key_position>::isMissing(lastMinMaxPosition_)) {
379 if(currentMaxPosition_ - lastMinMaxPosition_ >= kPositionTrackerMinMaxSpacingThreshold && currentBufferIndex != currentMaxIndex_) {
380 // We need to come down off the current maximum before we can be sure that we've found the right location.
381 // Implement a sliding threshold that gets lower the farther away from the maximum we get
382 key_position triggerThreshold = kPositionTrackerMinMaxSpacingThreshold / (key_position)(currentBufferIndex - currentMaxIndex_);
383
384 if(currentKeyPosition < currentMaxPosition_ - triggerThreshold) {
385 // Found the local maximum and the position has already retreated from it
386 lastMinMaxPosition_ = currentMaxPosition_;
387
388 if(currentState_ == kPositionTrackerStatePressInProgress) {
389 // If we were waiting for a press to complete, this is it.
390 pressIndex_ = currentMaxIndex_;
391 pressPosition_ = currentMaxPosition_;
392 pressTimestamp_ = currentMaxTimestamp_;
393
394 // Insert the state change into the buffer timestamped according to
395 // when the maximum arrived, unless that would put it earlier than what's already there
396 timestamp_type stateChangeTimestamp = latestTimestamp() > currentMaxTimestamp_ ? latestTimestamp() : currentMaxTimestamp_;
397 changeState(kPositionTrackerStateDown, stateChangeTimestamp);
398 }
399 else if(currentState_ == kPositionTrackerStatePartialPressAwaitingMax) {
400 // Otherwise if we were waiting for a maximum to occur that was
401 // short of a full press, this might be it if it is of sufficient size
402 if(currentMaxPosition_ >= kPositionTrackerFirstMaxThreshold) {
403 timestamp_type stateChangeTimestamp = latestTimestamp() > currentMaxTimestamp_ ? latestTimestamp() : currentMaxTimestamp_;
404 changeState(kPositionTrackerStatePartialPressFoundMax, stateChangeTimestamp);
405 }
406 }
407
408 // Reinitialize the minimum value for the next search
409 currentMinIndex_ = currentBufferIndex;
410 currentMinPosition_ = currentKeyPosition;
411 currentMinTimestamp_ = timestamp;
412 }
413 }
414 }
415 if(!missing_value<key_position>::isMissing(currentMinPosition_) &&
416 !missing_value<key_position>::isMissing(lastMinMaxPosition_)) {
417 if(lastMinMaxPosition_ - currentMinPosition_ >= kPositionTrackerMinMaxSpacingThreshold && currentBufferIndex != currentMinIndex_) {
418 // We need to come up from the current minimum before we can be sure that we've found the right location.
419 // Implement a sliding threshold that gets lower the farther away from the minimum we get
420 key_position triggerThreshold = kPositionTrackerMinMaxSpacingThreshold / (key_position)(currentBufferIndex - currentMinIndex_);
421
422 if(currentKeyPosition > currentMinPosition_ + triggerThreshold) {
423 // Found the local minimum and the position has already retreated from it
424 lastMinMaxPosition_ = currentMinPosition_;
425
426 // If in the middle of releasing, see whether this minimum appears to have completed the release
427 if(currentState_ == kPositionTrackerStateReleaseInProgress) {
428 if(currentMinPosition_ < kPositionTrackerReleaseFinishPosition) {
429 releaseEndIndex_ = currentMinIndex_;
430 releaseEndPosition_ = currentMinPosition_;
431 releaseEndTimestamp_ = currentMinTimestamp_;
432
433 timestamp_type stateChangeTimestamp = latestTimestamp() > currentMinTimestamp_ ? latestTimestamp() : currentMinTimestamp_;
434 changeState(kPositionTrackerStateReleaseFinished, stateChangeTimestamp);
435 }
436 }
437
438 // Reinitialize the maximum value for the next search
439 currentMaxIndex_ = currentBufferIndex;
440 currentMaxPosition_ = currentKeyPosition;
441 currentMaxTimestamp_ = timestamp;
442 }
443 }
444 }
445 }
446
447 // Change the current state of the tracker and generate a notification
448 void KeyPositionTracker::changeState(int newState, timestamp_type timestamp) {
449 KeyPositionTracker::key_buffer_index index;
450 KeyPositionTracker::key_buffer_index mostRecentIndex = 0;
451
452 if(keyBuffer_.empty())
453 mostRecentIndex = keyBuffer_.endIndex() - 1;
454
455 // Manage features based on state
456 switch(newState) {
457 case kPositionTrackerStatePressInProgress:
458 // Clear features for a retrigger
459 if(currentState_ == kPositionTrackerStateReleaseInProgress ||
460 currentState_ == kPositionTrackerStateReleaseFinished)
461 currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone;
462
463 // Look for percussiveness first since it will always be available by the time of
464 // key press. That means we can count on it arriving before velocity every time.
465 if((currentlyAvailableFeatures_ & KeyPositionTrackerNotification::kFeaturePercussiveness) == 0
466 && percussivenessAvailableIndex_ == 0) {
467 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness;
468 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp);
469 percussivenessAvailableIndex_ = 0;
470 }
471
472 // Start looking for the data needed for MIDI onset velocity.
473 // Where did the key cross the escapement position? How many more samples do
474 // we need to calculate velocity?
475 index = findMostRecentKeyPositionCrossing(pressVelocityEscapementPosition_, false, 1000);
476 if(index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement <= mostRecentIndex) {
477 // Here, we already have the velocity information
478 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePressVelocity;
479 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableVelocity, timestamp);
480 }
481 else {
482 // Otherwise, we need to send a notification when the information becomes available
483 pressVelocityAvailableIndex_ = index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement;
484 }
485 break;
486 case kPositionTrackerStateReleaseInProgress:
487 // Start looking for the data needed for MIDI release velocity.
488 // Where did the key cross the release escaoentb position? How many more samples do
489 // we need to calculate velocity?
490 prepareReleaseVelocityFeature(mostRecentIndex, timestamp);
491 break;
492 case kPositionTrackerStatePartialPressFoundMax:
493 // Also look for the percussiveness features, if not already present
494 if((currentlyAvailableFeatures_ & KeyPositionTrackerNotification::kFeaturePercussiveness) == 0
495 && percussivenessAvailableIndex_ == 0) {
496 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness;
497 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp);
498 percussivenessAvailableIndex_ = 0;
499 }
500 break;
501 case kPositionTrackerStatePartialPressAwaitingMax:
502 case kPositionTrackerStateUnknown:
503 // Reset all features
504 currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone;
505 break;
506 case kPositionTrackerStateDown:
507 case kPositionTrackerStateReleaseFinished:
508 default:
509 // Don't change features
510 break;
511 }
512
513 currentState_ = newState;
514
515 KeyPositionTrackerNotification notification;
516 notification.type = KeyPositionTrackerNotification::kNotificationTypeStateChange;
517 notification.state = newState;
518 notification.features = currentlyAvailableFeatures_;
519
520 insert(notification, timestamp);
521 }
522
523 // Notify listeners that a given feature has become available
524 void KeyPositionTracker::notifyFeature(int notificationType, timestamp_type timestamp) {
525 // Can now calculate press velocity
526 KeyPositionTrackerNotification notification;
527
528 notification.state = currentState_;
529 notification.type = notificationType;
530 notification.features = currentlyAvailableFeatures_;
531
532 insert(notification, timestamp);
533 }
534
535 // When starting from a blank state, retroactively locate
536 // the start of the key press so it can be used to calculate
537 // features of key motion
538 void KeyPositionTracker::findKeyPressStart(timestamp_type timestamp) {
539 if(keyBuffer_.size() < kPositionTrackerSamplesToAverageForStartVelocity + 1)
540 return;
541
542 key_buffer_index index = keyBuffer_.endIndex() - 1;
543 int searchBackCounter = 0;
544
545 while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchForStartLocation) {
546 // Take the N-sample velocity average and compare to a minimum threshold
547 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity];
548 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity);
549 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
550
551 if(velocity < kPositionTrackerStartVelocityThreshold) {
552 break;
553 }
554
555 searchBackCounter++;
556 index--;
557 }
558
559 // Having either found the minimum velocity or reached the beginning of the search period,
560 // store the key start information. Since the velocity is calculated over a window, choose
561 // a start position in the middle of the window.
562 startIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2;
563 startPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2];
564 startTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2);
565 lastMinMaxPosition_ = startPosition_;
566
567 // After saving that information, look further back for a specified number of samples to see if there
568 // is another mini-spike at the beginning of the key press. This can happen with highly percussive presses.
569 // If so, the start is actually the earlier time.
570
571 // Leave index where it was...
572 searchBackCounter = 0;
573 bool haveFoundVelocitySpike = false, haveFoundNewMinimum = false;
574
575 while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchBeyondStartLocation) {
576 // Take the N-sample velocity average and compare to a minimum threshold
577 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity];
578 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity);
579 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
580
581 if(velocity > kPositionTrackerStartVelocitySpikeThreshold) {
582 std::cout << "At index " << index << ", velocity is " << velocity << std::endl;
583 haveFoundVelocitySpike = true;
584 }
585
586 if(velocity < kPositionTrackerStartVelocityThreshold && haveFoundVelocitySpike) {
587 std::cout << "At index " << index << ", velocity is " << velocity << std::endl;
588 haveFoundNewMinimum = true;
589 break;
590 }
591
592 searchBackCounter++;
593 index--;
594 }
595
596 if(haveFoundNewMinimum) {
597 // Here we looked back beyond a small spike and found an earlier start time
598 startIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2;
599 startPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2];
600 startTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2);
601 lastMinMaxPosition_ = startPosition_;
602
603 std::cout << "Found previous location\n";
604 }
605 }
606
607 // When a key is released, retroactively locate where the release started
608 void KeyPositionTracker::findKeyReleaseStart(timestamp_type timestamp) {
609 if(keyBuffer_.size() < kPositionTrackerSamplesToAverageForStartVelocity + 1)
610 return;
611
612 key_buffer_index index = keyBuffer_.endIndex() - 1;
613 int searchBackCounter = 0;
614
615 while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchForReleaseLocation) {
616 // Take the N-sample velocity average and compare to a minimum threshold
617 key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity];
618 timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity);
619 key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp);
620
621 if(velocity > kPositionTrackerReleaseVelocityThreshold) {
622 std::cout << "Found release at index " << index << " (vel = " << velocity << ")\n";
623 break;
624 }
625
626 searchBackCounter++;
627 index--;
628 }
629
630 // Having either found the minimum velocity or reached the beginning of the search period,
631 // store the key release information.
632 releaseBeginIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2;
633 releaseBeginPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2];
634 releaseBeginTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2);
635 lastMinMaxPosition_ = releaseBeginPosition_;
636
637 // Clear the release end position so there's no possibility of an inconsistent state
638 releaseEndIndex_ = 0;
639 releaseEndPosition_ = missing_value<key_position>::missing();
640 releaseEndTimestamp_ = missing_value<timestamp_type>::missing();
641 }
642
643 // Find the index at which the key position crosses the given threshold. Returns 0 if not found.
644 KeyPositionTracker::key_buffer_index KeyPositionTracker::findMostRecentKeyPositionCrossing(key_position threshold, bool greaterThan, int maxDistance) {
645 if(keyBuffer_.empty())
646 return 0;
647
648 key_buffer_index index = keyBuffer_.endIndex() - 1;
649 int searchBackCounter = 0;
650
651 // Check if the most recent sample already meets the criterion. If so,
652 // there's no crossing yet.
653 if(keyBuffer_[index] >= threshold && greaterThan)
654 return 0;
655 if(keyBuffer_[index] <= threshold && !greaterThan)
656 return 0;
657
658 while(index >= keyBuffer_.beginIndex() && searchBackCounter <= maxDistance) {
659 if(keyBuffer_[index] >= threshold && greaterThan)
660 return index;
661 else if(keyBuffer_[index] <= threshold && !greaterThan)
662 return index;
663
664 searchBackCounter++;
665 index--;
666 }
667
668 return 0;
669 }
670
671 void KeyPositionTracker::prepareReleaseVelocityFeature(KeyPositionTracker::key_buffer_index mostRecentIndex, timestamp_type timestamp) {
672 KeyPositionTracker::key_buffer_index index;
673
674 // Find the sample where the key position crosses the release threshold. What is returned
675 // will be the last sample which is above the threshold. What we need is the first sample
676 // below the threshold plus at least one more (SamplesNeededForReleaseVelocity...) to
677 // perform a local velocity calculation.
678 index = findMostRecentKeyPositionCrossing(releaseVelocityEscapementPosition_, true, 1000);
679
680 if(index == 0) {
681 // Haven't crossed the threshold yet
682 releaseVelocityWaitingForThresholdCross_ = true;
683 }
684 else if(index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1 <= mostRecentIndex) {
685 // Here, we already have the velocity information
686 std::cout << "release available, at index = " << keyBuffer_[index] << ", most recent position = " << keyBuffer_[mostRecentIndex] << std::endl;
687 currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeatureReleaseVelocity;
688 notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableReleaseVelocity, timestamp);
689 releaseVelocityWaitingForThresholdCross_ = false;
690 }
691 else {
692 // Otherwise, we need to send a notification when the information becomes available
693 std::cout << "release available at index " << index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1 << std::endl;
694 releaseVelocityAvailableIndex_ = index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1;
695 releaseVelocityWaitingForThresholdCross_ = false;
696 }
697 }