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