Mercurial > hg > touchkeys
view Source/TouchKeys/KeyPositionTracker.cpp @ 20:dfff66c07936
Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sun, 09 Feb 2014 18:40:51 +0000 |
parents | 3580ffe87dc8 |
children |
line wrap: on
line source
/* TouchKeys: multi-touch musical keyboard control software Copyright (c) 2013 Andrew McPherson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ===================================================================== KeyPositionTracker.cpp: parses continuous key position and detects the state of the key. */ #include "KeyPositionTracker.h" // Default constructor KeyPositionTracker::KeyPositionTracker(capacity_type capacity, Node<key_position>& keyBuffer) : Node<KeyPositionTrackerNotification>(capacity), keyBuffer_(keyBuffer), engaged_(false) { reset(); } // Copy constructor /*KeyPositionTracker::KeyPositionTracker(KeyPositionTracker const& obj) : Node<int>(obj), keyBuffer_(obj.keyBuffer_), engaged_(obj.engaged_) { if(engaged_) registerForTrigger(&keyBuffer_); }*/ // Calculate (MIDI-style) key press velocity from continuous key position std::pair<timestamp_type, key_velocity> KeyPositionTracker::pressVelocity() { return pressVelocity(pressVelocityEscapementPosition_); } std::pair<timestamp_type, key_velocity> KeyPositionTracker::pressVelocity(key_position escapementPosition) { // Check that we have a valid start point from which to calculate if(missing_value<timestamp_type>::isMissing(startTimestamp_)) { return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(), missing_value<key_velocity>::missing()); } // Find where the key position crosses the indicated level key_buffer_index index = startIndex_; if(index < keyBuffer_.beginIndex() + 2) index = keyBuffer_.beginIndex() + 2; while(index < keyBuffer_.endIndex() - kPositionTrackerSamplesNeededForPressVelocityAfterEscapement) { // If the key press has a defined end, make sure we don't go past it if(pressIndex_ != 0 && index >= pressIndex_) break; if(keyBuffer_[index] > escapementPosition) { // Found the place the position crosses the indicated threshold // Now find the exact (interpolated) timestamp and velocity timestamp_type exactPressTimestamp = keyBuffer_.timestampAt(index); // TODO // Velocity is calculated by an average of 2 samples before and 1 after key_position diffPosition = keyBuffer_[index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement] - keyBuffer_[index - 2]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement) - keyBuffer_.timestampAt(index - 2); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); return std::pair<timestamp_type, key_velocity>(exactPressTimestamp, velocity); } index++; } // Didn't find anything matching that threshold return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(), missing_value<key_velocity>::missing()); } // Calculate (MIDI-style) key release velocity from continuous key position std::pair<timestamp_type, key_velocity> KeyPositionTracker::releaseVelocity() { return releaseVelocity(releaseVelocityEscapementPosition_); } std::pair<timestamp_type, key_velocity> KeyPositionTracker::releaseVelocity(key_position returnPosition) { // Check that we have a valid start point from which to calculate if(missing_value<timestamp_type>::isMissing(releaseBeginTimestamp_)) { return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(), missing_value<key_velocity>::missing()); } // Find where the key position crosses the indicated level key_buffer_index index = releaseBeginIndex_; if(index < keyBuffer_.beginIndex() + 2) index = keyBuffer_.beginIndex() + 2; while(index < keyBuffer_.endIndex() - kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement) { // Check for whether we've hit the end of the release interval, assuming // the interval exists yet if(releaseEndIndex_ != 0 && index >= releaseEndIndex_) break; if(keyBuffer_[index] < returnPosition) { // Found the place the position crosses the indicated threshold // Now find the exact (interpolated) timestamp and velocity timestamp_type exactPressTimestamp = keyBuffer_.timestampAt(index); // TODO // Velocity is calculated by an average of 2 samples before and 1 after key_position diffPosition = keyBuffer_[index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement] - keyBuffer_[index - 2]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement) - keyBuffer_.timestampAt(index - 2); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); std::cout << "found release velocity " << velocity << "(diffp " << diffPosition << ", diffT " << diffTimestamp << ")" << std::endl; return std::pair<timestamp_type, key_velocity>(exactPressTimestamp, velocity); } index++; } // Didn't find anything matching that threshold return std::pair<timestamp_type, key_velocity>(missing_value<timestamp_type>::missing(), missing_value<key_velocity>::missing()); } // Calculate and return features about the percussiveness of the key press KeyPositionTracker::PercussivenessFeatures KeyPositionTracker::pressPercussiveness() { PercussivenessFeatures features; key_buffer_index index; key_velocity maximumVelocity, largestVelocityDifference; key_buffer_index maximumVelocityIndex, largestVelocityDifferenceIndex; // Check that we have a valid start point from which to calculate if(missing_value<timestamp_type>::isMissing(startTimestamp_) || keyBuffer_.beginIndex() > startIndex_ - 1) { std::cout << "*** no start time\n"; features.percussiveness = missing_value<float>::missing(); return features; } // From the start of the key press, look for an initial maximum in velocity index = startIndex_; maximumVelocity = scale_key_velocity(0); maximumVelocityIndex = startIndex_; largestVelocityDifference = scale_key_velocity(0); largestVelocityDifferenceIndex = startIndex_; std::cout << "*** start index " << index << std::endl; while(index < keyBuffer_.endIndex()) { if(pressIndex_ != 0 && index >= pressIndex_) break; key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); // Look for maximum of velocity if(velocity > maximumVelocity) { maximumVelocity = velocity; maximumVelocityIndex = index; std::cout << "*** found new max velocity " << maximumVelocity << " at index " << index << std::endl; } // And given the difference between the max and the current sample, // look for the largest rebound (velocity hitting a peak and falling) if(maximumVelocity - velocity > largestVelocityDifference) { largestVelocityDifference = maximumVelocity - velocity; largestVelocityDifferenceIndex = index; std::cout << "*** found new diff velocity " << largestVelocityDifference << " at index " << index << std::endl; } // Only look at the early part of the key press: if the key position // makes it more than a certain amount down, assume the initial spike // has passed and finish up. But always allow at least 5 points for the // fastest key presses to be considered. if(index - startIndex_ >= 4 && keyBuffer_[index] > kPositionTrackerPositionThresholdForPercussivenessCalculation) { break; } index++; } // Now transfer what we've found to the data structure features.velocitySpikeMaximum = Event(maximumVelocityIndex, maximumVelocity, keyBuffer_.timestampAt(maximumVelocityIndex)); features.velocitySpikeMinimum = Event(largestVelocityDifferenceIndex, maximumVelocity - largestVelocityDifference, keyBuffer_.timestampAt(largestVelocityDifferenceIndex)); features.timeFromStartToSpike = keyBuffer_.timestampAt(maximumVelocityIndex) - keyBuffer_.timestampAt(startIndex_); // Check if we found a meaningful difference. If not, percussiveness is set to 0 if(largestVelocityDifference == scale_key_velocity(0)) { features.percussiveness = 0.0; features.areaPrecedingSpike = scale_key_velocity(0); features.areaFollowingSpike = scale_key_velocity(0); return features; } // Calculate the area under the velocity curve before and after the maximum features.areaPrecedingSpike = scale_key_velocity(0); for(index = startIndex_; index < maximumVelocityIndex; index++) { key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1); features.areaPrecedingSpike += calculate_key_velocity(diffPosition, diffTimestamp); } features.areaFollowingSpike = scale_key_velocity(0); for(index = maximumVelocityIndex; index < largestVelocityDifferenceIndex; index++) { key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - 1]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - 1); features.areaFollowingSpike += calculate_key_velocity(diffPosition, diffTimestamp); } std::cout << "area before = " << features.areaPrecedingSpike << " after = " << features.areaFollowingSpike << std::endl; features.percussiveness = features.velocitySpikeMaximum.position; return features; } // Register to receive messages from the key buffer on each new sample void KeyPositionTracker::engage() { if(engaged_) return; registerForTrigger(&keyBuffer_); engaged_ = true; } // Unregister from receiving message on new samples void KeyPositionTracker::disengage() { if(!engaged_) return; unregisterForTrigger(&keyBuffer_); engaged_ = false; } // Clear current state and reset to unknown state void KeyPositionTracker::reset() { Node<KeyPositionTrackerNotification>::clear(); currentState_ = kPositionTrackerStateUnknown; currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone; currentMinIndex_ = currentMaxIndex_ = startIndex_ = pressIndex_ = 0; releaseBeginIndex_ = releaseEndIndex_ = 0; lastMinMaxPosition_ = startPosition_ = pressPosition_ = missing_value<key_position>::missing(); releaseBeginPosition_ = releaseEndPosition_ = missing_value<key_position>::missing(); currentMinPosition_ = currentMaxPosition_ = missing_value<key_position>::missing(); startTimestamp_ = pressTimestamp_ = missing_value<timestamp_type>::missing(); currentMinTimestamp_ = currentMaxTimestamp_ = missing_value<timestamp_type>::missing(); releaseBeginTimestamp_ = releaseEndTimestamp_ = missing_value<timestamp_type>::missing(); pressVelocityEscapementPosition_ = kPositionTrackerDefaultPositionForPressVelocityCalculation; releaseVelocityEscapementPosition_ = kPositionTrackerDefaultPositionForReleaseVelocityCalculation; pressVelocityAvailableIndex_ = releaseVelocityAvailableIndex_ = percussivenessAvailableIndex_ = 0; releaseVelocityWaitingForThresholdCross_ = false; } // Evaluator function. Update the current state void KeyPositionTracker::triggerReceived(TriggerSource* who, timestamp_type timestamp) { if(who != &keyBuffer_) return; // Always start in the partial press state after a reset, retroactively locating // the start position for this key press if(empty()) { findKeyPressStart(timestamp); changeState(kPositionTrackerStatePartialPressAwaitingMax, timestamp); } key_position currentKeyPosition = keyBuffer_.latest(); key_buffer_index currentBufferIndex = keyBuffer_.endIndex() - 1; // First, check queued actions to see if we can calculate a new feature // ** Press Velocity ** if(pressVelocityAvailableIndex_ != 0) { if(currentBufferIndex >= pressVelocityAvailableIndex_) { // Can now calculate press velocity currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePressVelocity; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableVelocity, timestamp); pressVelocityAvailableIndex_ = 0; } } // ** Release Velocity ** if(releaseVelocityWaitingForThresholdCross_) { if(currentKeyPosition < releaseVelocityEscapementPosition_) prepareReleaseVelocityFeature(currentBufferIndex, timestamp); } else if(releaseVelocityAvailableIndex_ != 0) { if(currentBufferIndex >= releaseVelocityAvailableIndex_) { // Can now calculate release velocity currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeatureReleaseVelocity; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableReleaseVelocity, timestamp); releaseVelocityAvailableIndex_ = 0; } } // ** Percussiveness ** if(percussivenessAvailableIndex_ != 0) { if(currentBufferIndex >= percussivenessAvailableIndex_) { // Can now calculate percussiveness currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp); percussivenessAvailableIndex_ = 0; } } // Major state transitions next, centered on whether the key is pressed // fully or partially if(currentState_ == kPositionTrackerStatePartialPressAwaitingMax || currentState_ == kPositionTrackerStatePartialPressFoundMax) { // These are collectively the pre-press states if(currentKeyPosition >= kPositionTrackerPressPosition + kPositionTrackerPressHysteresis) { // Key has gone far enough down to be considered pressed, but hasn't necessarily // made it down yet. pressIndex_ = 0; pressPosition_ = missing_value<key_position>::missing(); pressTimestamp_ = missing_value<timestamp_type>::missing(); changeState(kPositionTrackerStatePressInProgress, timestamp); } } else if(currentState_ == kPositionTrackerStateReleaseInProgress || currentState_ == kPositionTrackerStateReleaseFinished) { if(currentKeyPosition >= kPositionTrackerPressPosition + kPositionTrackerPressHysteresis) { // Key was releasing but is now back down. Need to reprime the start // position information, which will be taken as the last minimum. startIndex_ = currentMinIndex_; startPosition_ = currentMinPosition_; startTimestamp_ = currentMinTimestamp_; pressIndex_ = 0; pressPosition_ = missing_value<key_position>::missing(); pressTimestamp_ = missing_value<timestamp_type>::missing(); changeState(kPositionTrackerStatePressInProgress, timestamp); } } else if(currentState_ == kPositionTrackerStatePressInProgress) { // Press has started, wait to find its max position before labeling the key as "down" if(currentKeyPosition < kPositionTrackerPressPosition - kPositionTrackerPressHysteresis) { // Key is on its way back up: find where release began findKeyReleaseStart(timestamp); changeState(kPositionTrackerStateReleaseInProgress, timestamp); } } else if(currentState_ == kPositionTrackerStateDown) { if(currentKeyPosition < kPositionTrackerPressPosition - kPositionTrackerPressHysteresis) { // Key is on its way back up: find where release began findKeyReleaseStart(timestamp); changeState(kPositionTrackerStateReleaseInProgress, timestamp); } } // Find the maxima and minima of the key motion if(missing_value<key_position>::isMissing(currentMaxPosition_) || currentKeyPosition > currentMaxPosition_) { // Found a new local maximum currentMaxIndex_ = currentBufferIndex; currentMaxPosition_ = currentKeyPosition; currentMaxTimestamp_ = timestamp; // If we previously found a maximum, go back to the original // state so we can process the new max that is in progress if(currentState_ == kPositionTrackerStatePartialPressFoundMax) changeState(kPositionTrackerStatePartialPressAwaitingMax, timestamp); } else if(missing_value<key_position>::isMissing(currentMinPosition_) || currentKeyPosition < currentMinPosition_) { // Found a new local minimum currentMinIndex_ = currentBufferIndex; currentMinPosition_ = currentKeyPosition; currentMinTimestamp_ = timestamp; } // Check if the deviation between min and max exceeds the threshold of significance, // and if so, figure out when a peak occurs if(!missing_value<key_position>::isMissing(currentMaxPosition_) && !missing_value<key_position>::isMissing(lastMinMaxPosition_)) { if(currentMaxPosition_ - lastMinMaxPosition_ >= kPositionTrackerMinMaxSpacingThreshold && currentBufferIndex != currentMaxIndex_) { // We need to come down off the current maximum before we can be sure that we've found the right location. // Implement a sliding threshold that gets lower the farther away from the maximum we get key_position triggerThreshold = kPositionTrackerMinMaxSpacingThreshold / (key_position)(currentBufferIndex - currentMaxIndex_); if(currentKeyPosition < currentMaxPosition_ - triggerThreshold) { // Found the local maximum and the position has already retreated from it lastMinMaxPosition_ = currentMaxPosition_; if(currentState_ == kPositionTrackerStatePressInProgress) { // If we were waiting for a press to complete, this is it. pressIndex_ = currentMaxIndex_; pressPosition_ = currentMaxPosition_; pressTimestamp_ = currentMaxTimestamp_; // Insert the state change into the buffer timestamped according to // when the maximum arrived, unless that would put it earlier than what's already there timestamp_type stateChangeTimestamp = latestTimestamp() > currentMaxTimestamp_ ? latestTimestamp() : currentMaxTimestamp_; changeState(kPositionTrackerStateDown, stateChangeTimestamp); } else if(currentState_ == kPositionTrackerStatePartialPressAwaitingMax) { // Otherwise if we were waiting for a maximum to occur that was // short of a full press, this might be it if it is of sufficient size if(currentMaxPosition_ >= kPositionTrackerFirstMaxThreshold) { timestamp_type stateChangeTimestamp = latestTimestamp() > currentMaxTimestamp_ ? latestTimestamp() : currentMaxTimestamp_; changeState(kPositionTrackerStatePartialPressFoundMax, stateChangeTimestamp); } } // Reinitialize the minimum value for the next search currentMinIndex_ = currentBufferIndex; currentMinPosition_ = currentKeyPosition; currentMinTimestamp_ = timestamp; } } } if(!missing_value<key_position>::isMissing(currentMinPosition_) && !missing_value<key_position>::isMissing(lastMinMaxPosition_)) { if(lastMinMaxPosition_ - currentMinPosition_ >= kPositionTrackerMinMaxSpacingThreshold && currentBufferIndex != currentMinIndex_) { // We need to come up from the current minimum before we can be sure that we've found the right location. // Implement a sliding threshold that gets lower the farther away from the minimum we get key_position triggerThreshold = kPositionTrackerMinMaxSpacingThreshold / (key_position)(currentBufferIndex - currentMinIndex_); if(currentKeyPosition > currentMinPosition_ + triggerThreshold) { // Found the local minimum and the position has already retreated from it lastMinMaxPosition_ = currentMinPosition_; // If in the middle of releasing, see whether this minimum appears to have completed the release if(currentState_ == kPositionTrackerStateReleaseInProgress) { if(currentMinPosition_ < kPositionTrackerReleaseFinishPosition) { releaseEndIndex_ = currentMinIndex_; releaseEndPosition_ = currentMinPosition_; releaseEndTimestamp_ = currentMinTimestamp_; timestamp_type stateChangeTimestamp = latestTimestamp() > currentMinTimestamp_ ? latestTimestamp() : currentMinTimestamp_; changeState(kPositionTrackerStateReleaseFinished, stateChangeTimestamp); } } // Reinitialize the maximum value for the next search currentMaxIndex_ = currentBufferIndex; currentMaxPosition_ = currentKeyPosition; currentMaxTimestamp_ = timestamp; } } } } // Change the current state of the tracker and generate a notification void KeyPositionTracker::changeState(int newState, timestamp_type timestamp) { KeyPositionTracker::key_buffer_index index; KeyPositionTracker::key_buffer_index mostRecentIndex = 0; if(keyBuffer_.empty()) mostRecentIndex = keyBuffer_.endIndex() - 1; // Manage features based on state switch(newState) { case kPositionTrackerStatePressInProgress: // Clear features for a retrigger if(currentState_ == kPositionTrackerStateReleaseInProgress || currentState_ == kPositionTrackerStateReleaseFinished) currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone; // Look for percussiveness first since it will always be available by the time of // key press. That means we can count on it arriving before velocity every time. if((currentlyAvailableFeatures_ & KeyPositionTrackerNotification::kFeaturePercussiveness) == 0 && percussivenessAvailableIndex_ == 0) { currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp); percussivenessAvailableIndex_ = 0; } // Start looking for the data needed for MIDI onset velocity. // Where did the key cross the escapement position? How many more samples do // we need to calculate velocity? index = findMostRecentKeyPositionCrossing(pressVelocityEscapementPosition_, false, 1000); if(index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement <= mostRecentIndex) { // Here, we already have the velocity information currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePressVelocity; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableVelocity, timestamp); } else { // Otherwise, we need to send a notification when the information becomes available pressVelocityAvailableIndex_ = index + kPositionTrackerSamplesNeededForPressVelocityAfterEscapement; } break; case kPositionTrackerStateReleaseInProgress: // Start looking for the data needed for MIDI release velocity. // Where did the key cross the release escaoentb position? How many more samples do // we need to calculate velocity? prepareReleaseVelocityFeature(mostRecentIndex, timestamp); break; case kPositionTrackerStatePartialPressFoundMax: // Also look for the percussiveness features, if not already present if((currentlyAvailableFeatures_ & KeyPositionTrackerNotification::kFeaturePercussiveness) == 0 && percussivenessAvailableIndex_ == 0) { currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeaturePercussiveness; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailablePercussiveness, timestamp); percussivenessAvailableIndex_ = 0; } break; case kPositionTrackerStatePartialPressAwaitingMax: case kPositionTrackerStateUnknown: // Reset all features currentlyAvailableFeatures_ = KeyPositionTrackerNotification::kFeaturesNone; break; case kPositionTrackerStateDown: case kPositionTrackerStateReleaseFinished: default: // Don't change features break; } currentState_ = newState; KeyPositionTrackerNotification notification; notification.type = KeyPositionTrackerNotification::kNotificationTypeStateChange; notification.state = newState; notification.features = currentlyAvailableFeatures_; insert(notification, timestamp); } // Notify listeners that a given feature has become available void KeyPositionTracker::notifyFeature(int notificationType, timestamp_type timestamp) { // Can now calculate press velocity KeyPositionTrackerNotification notification; notification.state = currentState_; notification.type = notificationType; notification.features = currentlyAvailableFeatures_; insert(notification, timestamp); } // When starting from a blank state, retroactively locate // the start of the key press so it can be used to calculate // features of key motion void KeyPositionTracker::findKeyPressStart(timestamp_type timestamp) { if(keyBuffer_.size() < kPositionTrackerSamplesToAverageForStartVelocity + 1) return; key_buffer_index index = keyBuffer_.endIndex() - 1; int searchBackCounter = 0; while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchForStartLocation) { // Take the N-sample velocity average and compare to a minimum threshold key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); if(velocity < kPositionTrackerStartVelocityThreshold) { break; } searchBackCounter++; index--; } // Having either found the minimum velocity or reached the beginning of the search period, // store the key start information. Since the velocity is calculated over a window, choose // a start position in the middle of the window. startIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2; startPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2]; startTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2); lastMinMaxPosition_ = startPosition_; // After saving that information, look further back for a specified number of samples to see if there // is another mini-spike at the beginning of the key press. This can happen with highly percussive presses. // If so, the start is actually the earlier time. // Leave index where it was... searchBackCounter = 0; bool haveFoundVelocitySpike = false, haveFoundNewMinimum = false; while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchBeyondStartLocation) { // Take the N-sample velocity average and compare to a minimum threshold key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); if(velocity > kPositionTrackerStartVelocitySpikeThreshold) { std::cout << "At index " << index << ", velocity is " << velocity << std::endl; haveFoundVelocitySpike = true; } if(velocity < kPositionTrackerStartVelocityThreshold && haveFoundVelocitySpike) { std::cout << "At index " << index << ", velocity is " << velocity << std::endl; haveFoundNewMinimum = true; break; } searchBackCounter++; index--; } if(haveFoundNewMinimum) { // Here we looked back beyond a small spike and found an earlier start time startIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2; startPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2]; startTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2); lastMinMaxPosition_ = startPosition_; std::cout << "Found previous location\n"; } } // When a key is released, retroactively locate where the release started void KeyPositionTracker::findKeyReleaseStart(timestamp_type timestamp) { if(keyBuffer_.size() < kPositionTrackerSamplesToAverageForStartVelocity + 1) return; key_buffer_index index = keyBuffer_.endIndex() - 1; int searchBackCounter = 0; while(index >= keyBuffer_.beginIndex() + kPositionTrackerSamplesToAverageForStartVelocity && searchBackCounter <= kPositionTrackerSamplesToSearchForReleaseLocation) { // Take the N-sample velocity average and compare to a minimum threshold key_position diffPosition = keyBuffer_[index] - keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity]; timestamp_diff_type diffTimestamp = keyBuffer_.timestampAt(index) - keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity); key_velocity velocity = calculate_key_velocity(diffPosition, diffTimestamp); if(velocity > kPositionTrackerReleaseVelocityThreshold) { std::cout << "Found release at index " << index << " (vel = " << velocity << ")\n"; break; } searchBackCounter++; index--; } // Having either found the minimum velocity or reached the beginning of the search period, // store the key release information. releaseBeginIndex_ = index - kPositionTrackerSamplesToAverageForStartVelocity/2; releaseBeginPosition_ = keyBuffer_[index - kPositionTrackerSamplesToAverageForStartVelocity/2]; releaseBeginTimestamp_ = keyBuffer_.timestampAt(index - kPositionTrackerSamplesToAverageForStartVelocity/2); lastMinMaxPosition_ = releaseBeginPosition_; // Clear the release end position so there's no possibility of an inconsistent state releaseEndIndex_ = 0; releaseEndPosition_ = missing_value<key_position>::missing(); releaseEndTimestamp_ = missing_value<timestamp_type>::missing(); } // Find the index at which the key position crosses the given threshold. Returns 0 if not found. KeyPositionTracker::key_buffer_index KeyPositionTracker::findMostRecentKeyPositionCrossing(key_position threshold, bool greaterThan, int maxDistance) { if(keyBuffer_.empty()) return 0; key_buffer_index index = keyBuffer_.endIndex() - 1; int searchBackCounter = 0; // Check if the most recent sample already meets the criterion. If so, // there's no crossing yet. if(keyBuffer_[index] >= threshold && greaterThan) return 0; if(keyBuffer_[index] <= threshold && !greaterThan) return 0; while(index >= keyBuffer_.beginIndex() && searchBackCounter <= maxDistance) { if(keyBuffer_[index] >= threshold && greaterThan) return index; else if(keyBuffer_[index] <= threshold && !greaterThan) return index; searchBackCounter++; index--; } return 0; } void KeyPositionTracker::prepareReleaseVelocityFeature(KeyPositionTracker::key_buffer_index mostRecentIndex, timestamp_type timestamp) { KeyPositionTracker::key_buffer_index index; // Find the sample where the key position crosses the release threshold. What is returned // will be the last sample which is above the threshold. What we need is the first sample // below the threshold plus at least one more (SamplesNeededForReleaseVelocity...) to // perform a local velocity calculation. index = findMostRecentKeyPositionCrossing(releaseVelocityEscapementPosition_, true, 1000); if(index == 0) { // Haven't crossed the threshold yet releaseVelocityWaitingForThresholdCross_ = true; } else if(index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1 <= mostRecentIndex) { // Here, we already have the velocity information std::cout << "release available, at index = " << keyBuffer_[index] << ", most recent position = " << keyBuffer_[mostRecentIndex] << std::endl; currentlyAvailableFeatures_ |= KeyPositionTrackerNotification::kFeatureReleaseVelocity; notifyFeature(KeyPositionTrackerNotification::kNotificationTypeFeatureAvailableReleaseVelocity, timestamp); releaseVelocityWaitingForThresholdCross_ = false; } else { // Otherwise, we need to send a notification when the information becomes available std::cout << "release available at index " << index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1 << std::endl; releaseVelocityAvailableIndex_ = index + kPositionTrackerSamplesNeededForReleaseVelocityAfterEscapement + 1; releaseVelocityWaitingForThresholdCross_ = false; } }