annotate Source/TouchKeys/KeyPositionTracker.cpp @ 56:b4a2d2ae43cf tip

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