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 } |