Mercurial > hg > touchkeys
comparison Source/Mappings/Control/TouchkeyControlMapping.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 | c6f30c1e2bda |
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 TouchkeyControlMapping.cpp: per-note mapping for the TouchKeys control | |
21 mapping, which converts an arbitrary touch parameter into a MIDI or | |
22 OSC control message. | |
23 */ | |
24 | |
25 #include "TouchkeyControlMapping.h" | |
26 #include <vector> | |
27 #include <climits> | |
28 #include <cmath> | |
29 #include "../MappingScheduler.h" | |
30 | |
31 #undef DEBUG_CONTROL_MAPPING | |
32 | |
33 // Class constants | |
34 const int TouchkeyControlMapping::kDefaultMIDIChannel = 0; | |
35 const int TouchkeyControlMapping::kDefaultFilterBufferLength = 300; | |
36 | |
37 const bool TouchkeyControlMapping::kDefaultIgnoresTwoFingers = false; | |
38 const bool TouchkeyControlMapping::kDefaultIgnoresThreeFingers = false; | |
39 const int TouchkeyControlMapping::kDefaultDirection = TouchkeyControlMapping::kDirectionPositive; | |
40 | |
41 // Main constructor takes references/pointers from objects which keep track | |
42 // of touch location, continuous key position and the state detected from that | |
43 // position. The PianoKeyboard object is strictly required as it gives access to | |
44 // Scheduler and OSC methods. The others are optional since any given system may | |
45 // contain only one of continuous key position or touch sensitivity | |
46 TouchkeyControlMapping::TouchkeyControlMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node<KeyTouchFrame>* touchBuffer, | |
47 Node<key_position>* positionBuffer, KeyPositionTracker* positionTracker) | |
48 : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker), | |
49 controlIsEngaged_(false), | |
50 inputMin_(0.0), inputMax_(1.0), outputMin_(0.0), outputMax_(1.0), outputDefault_(0.0), | |
51 inputParameter_(kInputParameterYPosition), inputType_(kTypeAbsolute), | |
52 threshold_(0.0), ignoresTwoFingers_(kDefaultIgnoresTwoFingers), | |
53 ignoresThreeFingers_(kDefaultIgnoresThreeFingers), direction_(kDefaultDirection), | |
54 touchOnsetValue_(missing_value<float>::missing()), | |
55 midiOnsetValue_(missing_value<float>::missing()), | |
56 lastValue_(missing_value<float>::missing()), | |
57 lastTimestamp_(missing_value<timestamp_type>::missing()), lastProcessedIndex_(0), | |
58 controlEngageLocation_(missing_value<float>::missing()), | |
59 controlScalerPositive_(missing_value<float>::missing()), | |
60 controlScalerNegative_(missing_value<float>::missing()), | |
61 lastControlValue_(outputDefault_), | |
62 rawValues_(kDefaultFilterBufferLength) | |
63 { | |
64 resetDetectionState(); | |
65 } | |
66 | |
67 TouchkeyControlMapping::~TouchkeyControlMapping() { | |
68 #if 0 | |
69 #ifndef NEW_MAPPING_SCHEDULER | |
70 try { | |
71 disengage(); | |
72 } | |
73 catch(...) { | |
74 std::cerr << "~TouchkeyControlMapping(): exception during disengage()\n"; | |
75 } | |
76 #endif | |
77 #endif | |
78 } | |
79 | |
80 // Turn on mapping of data. | |
81 /*void TouchkeyControlMapping::engage() { | |
82 Mapping::engage(); | |
83 | |
84 // Register for OSC callbacks on MIDI note on/off | |
85 addOscListener("/midi/noteon"); | |
86 addOscListener("/midi/noteoff"); | |
87 } | |
88 | |
89 // Turn off mapping of data. Remove our callback from the scheduler | |
90 void TouchkeyControlMapping::disengage(bool shouldDelete) { | |
91 // Remove OSC listeners first | |
92 removeOscListener("/midi/noteon"); | |
93 removeOscListener("/midi/noteoff"); | |
94 | |
95 // Don't send any separate message here, leave it where it was | |
96 | |
97 Mapping::disengage(shouldDelete); | |
98 | |
99 if(noteIsOn_) { | |
100 // TODO | |
101 } | |
102 noteIsOn_ = false; | |
103 }*/ | |
104 | |
105 // Reset state back to defaults | |
106 void TouchkeyControlMapping::reset() { | |
107 TouchkeyBaseMapping::reset(); | |
108 sendControlMessage(outputDefault_); | |
109 resetDetectionState(); | |
110 //noteIsOn_ = false; | |
111 } | |
112 | |
113 // Resend all current parameters | |
114 void TouchkeyControlMapping::resend() { | |
115 sendControlMessage(lastControlValue_, true); | |
116 } | |
117 | |
118 // Name for this control, used in the OSC path | |
119 /*void TouchkeyControlMapping::setName(const std::string& name) { | |
120 controlName_ = name; | |
121 }*/ | |
122 | |
123 // Parameters for the controller handling | |
124 // Input parameter to use for this control mapping and whether it is absolute or relative | |
125 void TouchkeyControlMapping::setInputParameter(int parameter, int type) { | |
126 if(inputParameter_ >= 0 && inputParameter_ < kInputParameterMaxValue) | |
127 inputParameter_ = parameter; | |
128 if(type >= 0 && type < kTypeMaxValue) | |
129 inputType_ = type; | |
130 } | |
131 | |
132 // Input/output range for this parameter | |
133 void TouchkeyControlMapping::setRange(float inputMin, float inputMax, float outputMin, float outputMax, float outputDefault) { | |
134 inputMin_ = inputMin; | |
135 inputMax_ = inputMax; | |
136 outputMin_ = outputMin; | |
137 outputMax_ = outputMax; | |
138 outputDefault_ = outputDefault; | |
139 } | |
140 | |
141 // Threshold which must be exceeded for the control to engage (for relative position), or 0 if not used | |
142 void TouchkeyControlMapping::setThreshold(float threshold) { | |
143 threshold_ = threshold; | |
144 } | |
145 | |
146 void TouchkeyControlMapping::setIgnoresMultipleFingers(bool ignoresTwo, bool ignoresThree) { | |
147 ignoresTwoFingers_ = ignoresTwo; | |
148 ignoresThreeFingers_ = ignoresThree; | |
149 } | |
150 | |
151 void TouchkeyControlMapping::setDirection(int direction) { | |
152 if(direction >= 0 && direction < kDirectionMaxValue) | |
153 direction_ = direction; | |
154 } | |
155 | |
156 // OSC handler method. Called from PianoKeyboard when MIDI data comes in. | |
157 /*bool TouchkeyControlMapping::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) { | |
158 if(!strcmp(path, "/midi/noteon") && !noteIsOn_ && numValues >= 1) { | |
159 if(types[0] == 'i' && values[0]->i == noteNumber_) { | |
160 // MIDI note has gone on. Set the starting location to be most recent | |
161 // location. It's possible there has been no touch data before this, | |
162 // in which case lastX and lastY will hold missing values. | |
163 midiOnsetValue_ = lastValue_; | |
164 if(!missing_value<float>::isMissing(midiOnsetValue_)) { | |
165 if(inputType_ == kTypeNoteOnsetRelative) { | |
166 // Already have touch data. Clear the buffer here. | |
167 // Clear buffer and start with default value for this point | |
168 clearBuffers(); | |
169 | |
170 #ifdef DEBUG_CONTROL_MAPPING | |
171 std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n"; | |
172 #endif | |
173 } | |
174 } | |
175 else { | |
176 #ifdef DEBUG_CONTROL_MAPPING | |
177 std::cout << "MIDI on but no touch\n"; | |
178 #endif | |
179 } | |
180 | |
181 noteIsOn_ = true; | |
182 return false; | |
183 } | |
184 } | |
185 else if(!strcmp(path, "/midi/noteoff") && noteIsOn_ && numValues >= 1) { | |
186 if(types[0] == 'i' && values[0]->i == noteNumber_) { | |
187 // MIDI note goes off | |
188 noteIsOn_ = false; | |
189 if(controlIsEngaged_) { | |
190 // TODO: should anything happen here? | |
191 } | |
192 #ifdef DEBUG_CONTROL_MAPPING | |
193 std::cout << "MIDI off\n"; | |
194 #endif | |
195 return false; | |
196 } | |
197 } | |
198 | |
199 return false; | |
200 }*/ | |
201 | |
202 // Trigger method. This receives updates from the TouchKey data or from state changes in | |
203 // the continuous key position (KeyPositionTracker). It will potentially change the scheduled | |
204 // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different | |
205 // thread. | |
206 void TouchkeyControlMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) { | |
207 if(who == 0) | |
208 return; | |
209 | |
210 if(who == touchBuffer_) { | |
211 if(!touchBuffer_->empty()) { | |
212 // New touch data is available. Find the distance from the onset location. | |
213 KeyTouchFrame frame = touchBuffer_->latest(); | |
214 lastTimestamp_ = timestamp; | |
215 | |
216 if(frame.count == 0) { | |
217 // No touches. Last values are "missing", and we're not tracking any | |
218 // particular touch ID | |
219 lastValue_ = missing_value<float>::missing(); | |
220 idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1; | |
221 | |
222 #ifdef DEBUG_CONTROL_MAPPING | |
223 std::cout << "Touch off\n"; | |
224 #endif | |
225 } | |
226 else { | |
227 //ScopedLock sl(rawValueAccessMutex_); | |
228 | |
229 // At least one touch. Check if we are already tracking an ID and, if so, | |
230 // use its coordinates. Otherwise grab the lowest current ID. | |
231 lastValue_ = getValue(frame); | |
232 | |
233 // Check that the value actually exists | |
234 if(!missing_value<float>::isMissing(lastValue_)) { | |
235 // If we have no onset value, this is it | |
236 if(missing_value<float>::isMissing(touchOnsetValue_)) { | |
237 touchOnsetValue_ = lastValue_; | |
238 if(inputType_ == kTypeFirstTouchRelative) { | |
239 clearBuffers(); | |
240 #ifdef DEBUG_CONTROL_MAPPING | |
241 std::cout << "Starting at " << lastValue_ << std::endl; | |
242 #endif | |
243 } | |
244 } | |
245 | |
246 // If MIDI note is on and we don't previously have a value, this is it | |
247 if(noteIsOn_ && missing_value<float>::isMissing(midiOnsetValue_)) { | |
248 midiOnsetValue_ = lastValue_; | |
249 if(inputType_ == kTypeNoteOnsetRelative) { | |
250 clearBuffers(); | |
251 #ifdef DEBUG_CONTROL_MAPPING | |
252 std::cout << "Starting at " << lastValue_ << std::endl; | |
253 #endif | |
254 } | |
255 } | |
256 | |
257 if(noteIsOn_) { | |
258 // Insert the latest sample into the buffer depending on how the data should be processed | |
259 if(inputType_ == kTypeAbsolute) { | |
260 rawValues_.insert(lastValue_, timestamp); | |
261 } | |
262 else if(inputType_ == kTypeFirstTouchRelative) { | |
263 rawValues_.insert(lastValue_ - touchOnsetValue_, timestamp); | |
264 } | |
265 else if(inputType_ == kTypeNoteOnsetRelative) { | |
266 rawValues_.insert(lastValue_ - midiOnsetValue_, timestamp); | |
267 } | |
268 | |
269 // Move the current scheduled event up to the present time. | |
270 // FIXME: this may be more inefficient than just doing everything in the current thread! | |
271 #ifdef NEW_MAPPING_SCHEDULER | |
272 keyboard_.mappingScheduler().scheduleNow(this); | |
273 #else | |
274 keyboard_.unscheduleEvent(this); | |
275 keyboard_.scheduleEvent(this, mappingAction_, keyboard_.schedulerCurrentTimestamp()); | |
276 #endif | |
277 } | |
278 } | |
279 } | |
280 } | |
281 } | |
282 } | |
283 | |
284 // Mapping method. This actually does the real work of sending OSC data in response to the | |
285 // latest information from the touch sensors or continuous key angle | |
286 timestamp_type TouchkeyControlMapping::performMapping() { | |
287 //ScopedLock sl(rawValueAccessMutex_); | |
288 | |
289 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp(); | |
290 bool newSamplePresent = false; | |
291 | |
292 // Go through the filtered distance samples that are remaining to process. | |
293 if(lastProcessedIndex_ < rawValues_.beginIndex() + 1) { | |
294 // Fell off the beginning of the position buffer. Skip to the samples we have | |
295 // (shouldn't happen except in cases of exceptional system load, and not too | |
296 // consequential if it does happen). | |
297 lastProcessedIndex_ = rawValues_.beginIndex() + 1; | |
298 } | |
299 | |
300 while(lastProcessedIndex_ < rawValues_.endIndex()) { | |
301 float value = rawValues_[lastProcessedIndex_]; | |
302 //timestamp_type timestamp = rawValues_.timestampAt(lastProcessedIndex_); | |
303 newSamplePresent = true; | |
304 | |
305 if(inputType_ == kTypeAbsolute) { | |
306 controlIsEngaged_ = true; | |
307 } | |
308 else if(!controlIsEngaged_) { | |
309 // Compare value against threshold to see if the control should engage | |
310 if(fabsf(value) > threshold_) { | |
311 float startingValue; | |
312 | |
313 controlIsEngaged_ = true; | |
314 controlEngageLocation_ = (value > 0 ? threshold_ : -threshold_); | |
315 | |
316 #ifdef DEBUG_CONTROL_MAPPING | |
317 std::cout << "engaging control at distance " << controlEngageLocation_ << std::endl; | |
318 #endif | |
319 | |
320 if(inputType_ == kTypeFirstTouchRelative) | |
321 startingValue = touchOnsetValue_; | |
322 else | |
323 startingValue = midiOnsetValue_; | |
324 | |
325 // This is how much range we would have had without the threshold | |
326 float distanceToPositiveEdgeWithoutThreshold = 1.0 - startingValue; | |
327 float distanceToNegativeEdgeWithoutThreshold = 0.0 + startingValue; | |
328 | |
329 // This is how much range we actually have with the threshold | |
330 float actualDistanceToPositiveEdge = 1.0 - (startingValue + controlEngageLocation_); | |
331 float actualDistanceToNegativeEdge = 0.0 + startingValue + controlEngageLocation_; | |
332 | |
333 // Make it so moving toward edge of key gets as far as it would have without | |
334 // the distance lost by the threshold | |
335 if(actualDistanceToPositiveEdge > 0.0) | |
336 controlScalerPositive_ = (outputMax_ - outputDefault_) * distanceToPositiveEdgeWithoutThreshold / actualDistanceToPositiveEdge; | |
337 else | |
338 controlScalerPositive_ = (outputMax_ - outputDefault_); // Sanity check | |
339 if(actualDistanceToNegativeEdge > 0.0) | |
340 controlScalerNegative_ = (outputDefault_ - outputMin_) * distanceToNegativeEdgeWithoutThreshold / actualDistanceToNegativeEdge; | |
341 else | |
342 controlScalerNegative_ = (outputDefault_ - outputMin_); // Sanity check | |
343 } | |
344 } | |
345 | |
346 lastProcessedIndex_++; | |
347 } | |
348 | |
349 if(controlIsEngaged_) { | |
350 // Having processed every sample individually for any detection/filtering, now send | |
351 // the most recent output as an OSC message | |
352 if(newSamplePresent) { | |
353 float latestValue = rawValues_.latest(); | |
354 | |
355 // In cases of relative values, the place the control engages will actually be where it crosses | |
356 // the threshold, not the onset location itself. Need to update the value accordingly. | |
357 if(inputType_ == kTypeFirstTouchRelative || | |
358 inputType_ == kTypeNoteOnsetRelative) { | |
359 if(latestValue > 0) { | |
360 latestValue -= threshold_; | |
361 if(latestValue < 0) | |
362 latestValue = 0; | |
363 } | |
364 else if(latestValue < 0) { | |
365 latestValue += threshold_; | |
366 if(latestValue > 0) | |
367 latestValue = 0; | |
368 } | |
369 } | |
370 | |
371 if(direction_ == kDirectionNegative) | |
372 latestValue = -latestValue; | |
373 else if((direction_ == kDirectionBoth) && latestValue < 0) | |
374 latestValue = -latestValue; | |
375 | |
376 sendControlMessage(latestValue); | |
377 lastControlValue_ = latestValue; | |
378 } | |
379 } | |
380 | |
381 // Register for the next update by returning its timestamp | |
382 nextScheduledTimestamp_ = currentTimestamp + updateInterval_; | |
383 return nextScheduledTimestamp_; | |
384 } | |
385 | |
386 // MIDI note-on message received | |
387 void TouchkeyControlMapping::midiNoteOnReceived(int channel, int velocity) { | |
388 // MIDI note has gone on. Set the starting location to be most recent | |
389 // location. It's possible there has been no touch data before this, | |
390 // in which case lastX and lastY will hold missing values. | |
391 midiOnsetValue_ = lastValue_; | |
392 if(!missing_value<float>::isMissing(midiOnsetValue_)) { | |
393 if(inputType_ == kTypeNoteOnsetRelative) { | |
394 // Already have touch data. Clear the buffer here. | |
395 // Clear buffer and start with default value for this point | |
396 clearBuffers(); | |
397 | |
398 #ifdef DEBUG_CONTROL_MAPPING | |
399 std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n"; | |
400 #endif | |
401 } | |
402 } | |
403 else { | |
404 #ifdef DEBUG_CONTROL_MAPPING | |
405 std::cout << "MIDI on but no touch\n"; | |
406 #endif | |
407 } | |
408 } | |
409 | |
410 // MIDI note-off message received | |
411 void TouchkeyControlMapping::midiNoteOffReceived(int channel) { | |
412 if(controlIsEngaged_) { | |
413 // TODO: should anything happen here? | |
414 } | |
415 } | |
416 | |
417 // Reset variables involved in detecting a pitch bend gesture | |
418 void TouchkeyControlMapping::resetDetectionState() { | |
419 controlIsEngaged_ = false; | |
420 controlEngageLocation_ = missing_value<float>::missing(); | |
421 idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1; | |
422 } | |
423 | |
424 // Clear the buffers that hold distance measurements | |
425 void TouchkeyControlMapping::clearBuffers() { | |
426 rawValues_.clear(); | |
427 rawValues_.insert(0.0, lastTimestamp_); | |
428 lastProcessedIndex_ = 0; | |
429 } | |
430 | |
431 // Return the current parameter value depending on which one we are listening to | |
432 float TouchkeyControlMapping::getValue(const KeyTouchFrame& frame) { | |
433 if(inputParameter_ == kInputParameterXPosition) | |
434 return frame.locH; | |
435 /*else if(inputParameter_ == kInputParameter2FingerMean || | |
436 inputParameter_ == kInputParameter2FingerDistance) { | |
437 if(frame.count < 2) | |
438 return missing_value<float>::missing(); | |
439 if(frame.count == 3 && ignoresThreeFingers_) | |
440 return missing_value<float>::missing(); | |
441 | |
442 bool foundCurrentTouch = false; | |
443 float currentValue; | |
444 | |
445 // Look for the touches we were tracking last frame | |
446 if(idsOfCurrentTouches_[0] >= 0) { | |
447 for(int i = 0; i < frame.count; i++) { | |
448 if(frame.ids[i] == idsOfCurrentTouches_[0]) { | |
449 if(inputParameter_ == kInputParameterYPosition) | |
450 currentValue = frame.locs[i]; | |
451 else // kInputParameterTouchSize | |
452 currentValue = frame.sizes[i]; | |
453 foundCurrentTouch = true; | |
454 break; | |
455 } | |
456 } | |
457 } | |
458 | |
459 if(!foundCurrentTouch) { | |
460 // Assign a new touch to be tracked | |
461 int lowestRemainingId = INT_MAX; | |
462 int lowestIndex = 0; | |
463 | |
464 for(int i = 0; i < frame.count; i++) { | |
465 if(frame.ids[i] < lowestRemainingId) { | |
466 lowestRemainingId = frame.ids[i]; | |
467 lowestIndex = i; | |
468 } | |
469 } | |
470 | |
471 idsOfCurrentTouches_[0] = lowestRemainingId; | |
472 if(inputParameter_ == kInputParameterYPosition) | |
473 currentValue = frame.locs[lowestIndex]; | |
474 else if(inputParameter_ == kInputParameterTouchSize) | |
475 currentValue = frame.sizes[lowestIndex]; | |
476 else // Shouldn't happen | |
477 currentValue = missing_value<float>::missing(); | |
478 | |
479 #ifdef DEBUG_CONTROL_MAPPING | |
480 std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n"; | |
481 #endif | |
482 } | |
483 | |
484 }*/ | |
485 else { | |
486 if(frame.count == 0) | |
487 return missing_value<float>::missing(); | |
488 if((inputParameter_ == kInputParameter2FingerMean || | |
489 inputParameter_ == kInputParameter2FingerDistance) && | |
490 frame.count < 2) | |
491 return missing_value<float>::missing(); | |
492 if(frame.count == 2 && ignoresTwoFingers_) | |
493 return missing_value<float>::missing(); | |
494 if(frame.count == 3 && ignoresThreeFingers_) | |
495 return missing_value<float>::missing(); | |
496 /* | |
497 // The other values are dependent on individual touches | |
498 bool foundCurrentTouch = false; | |
499 float currentValue; | |
500 | |
501 // Look for the touch we were tracking last frame | |
502 if(idsOfCurrentTouches_[0] >= 0) { | |
503 for(int i = 0; i < frame.count; i++) { | |
504 if(frame.ids[i] == idsOfCurrentTouches_[0]) { | |
505 if(inputParameter_ == kInputParameterYPosition) | |
506 currentValue = frame.locs[i]; | |
507 else // kInputParameterTouchSize | |
508 currentValue = frame.sizes[i]; | |
509 foundCurrentTouch = true; | |
510 break; | |
511 } | |
512 } | |
513 } | |
514 | |
515 if(!foundCurrentTouch) { | |
516 // Assign a new touch to be tracked | |
517 int lowestRemainingId = INT_MAX; | |
518 int lowestIndex = 0; | |
519 | |
520 for(int i = 0; i < frame.count; i++) { | |
521 if(frame.ids[i] < lowestRemainingId) { | |
522 lowestRemainingId = frame.ids[i]; | |
523 lowestIndex = i; | |
524 } | |
525 } | |
526 | |
527 idsOfCurrentTouches_[0] = lowestRemainingId; | |
528 if(inputParameter_ == kInputParameterYPosition) | |
529 currentValue = frame.locs[lowestIndex]; | |
530 else if(inputParameter_ == kInputParameterTouchSize) | |
531 currentValue = frame.sizes[lowestIndex]; | |
532 else // Shouldn't happen | |
533 currentValue = missing_value<float>::missing(); | |
534 | |
535 #ifdef DEBUG_CONTROL_MAPPING | |
536 std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n"; | |
537 #endif | |
538 }*/ | |
539 | |
540 float currentValue = 0; | |
541 | |
542 int idWithinFrame0 = locateTouchId(frame, 0); | |
543 if(idWithinFrame0 < 0) { | |
544 // Touch ID not found, start a new value | |
545 idsOfCurrentTouches_[0] = lowestUnassignedTouch(frame, &idWithinFrame0); | |
546 #ifdef DEBUG_CONTROL_MAPPING | |
547 std::cout << "Previous touch stopped (0); now ID " << idsOfCurrentTouches_[0] << endl; | |
548 #endif | |
549 if(idsOfCurrentTouches_[0] < 0) { | |
550 cout << "BUG: didn't find any unassigned touch!\n"; | |
551 } | |
552 } | |
553 | |
554 if(inputParameter_ == kInputParameterYPosition) | |
555 currentValue = frame.locs[idWithinFrame0]; | |
556 else if(inputParameter_ == kInputParameterTouchSize) // kInputParameterTouchSize | |
557 currentValue = frame.sizes[idWithinFrame0]; | |
558 else if(inputParameter_ == kInputParameter2FingerMean || | |
559 inputParameter_ == kInputParameter2FingerDistance) { | |
560 int idWithinFrame1 = locateTouchId(frame, 1); | |
561 if(idWithinFrame1 < 0) { | |
562 // Touch ID not found, start a new value | |
563 idsOfCurrentTouches_[1] = lowestUnassignedTouch(frame, &idWithinFrame1); | |
564 #ifdef DEBUG_CONTROL_MAPPING | |
565 std::cout << "Previous touch stopped (1); now ID " << idsOfCurrentTouches_[1] << endl; | |
566 #endif | |
567 if(idsOfCurrentTouches_[1] < 0) { | |
568 cout << "BUG: didn't find any unassigned touch for second finger!\n"; | |
569 } | |
570 } | |
571 | |
572 if(inputParameter_ == kInputParameter2FingerMean) | |
573 currentValue = (frame.locs[idWithinFrame0] + frame.locs[idWithinFrame1]) * 0.5; | |
574 else | |
575 currentValue = fabsf(frame.locs[idWithinFrame1] - frame.locs[idWithinFrame0]); | |
576 } | |
577 | |
578 return currentValue; | |
579 } | |
580 } | |
581 | |
582 // Look for a touch index in the frame matching the given value of idsOfCurrentTouches[index] | |
583 // Returns -1 if not found | |
584 int TouchkeyControlMapping::locateTouchId(KeyTouchFrame const& frame, int index) { | |
585 if(idsOfCurrentTouches_[index] < 0) | |
586 return -1; | |
587 | |
588 for(int i = 0; i < frame.count; i++) { | |
589 if(frame.ids[i] == idsOfCurrentTouches_[index]) { | |
590 return i; | |
591 } | |
592 } | |
593 | |
594 return -1; | |
595 } | |
596 | |
597 // Locates the lowest touch ID that is not assigned to a current touch | |
598 // Returns -1 if no unassigned touches were found | |
599 int TouchkeyControlMapping::lowestUnassignedTouch(KeyTouchFrame const& frame, int *indexWithinFrame) { | |
600 int lowestRemainingId = INT_MAX; | |
601 int lowestIndex = -1; | |
602 | |
603 for(int i = 0; i < frame.count; i++) { | |
604 if(frame.ids[i] < lowestRemainingId) { | |
605 bool alreadyAssigned = false; | |
606 for(int j = 0; j < 3; j++) { | |
607 if(idsOfCurrentTouches_[j] == frame.ids[i]) | |
608 alreadyAssigned = true; | |
609 } | |
610 | |
611 if(!alreadyAssigned) { | |
612 lowestRemainingId = frame.ids[i]; | |
613 lowestIndex = i; | |
614 | |
615 } | |
616 } | |
617 } | |
618 | |
619 if(indexWithinFrame != 0) | |
620 *indexWithinFrame = lowestIndex; | |
621 return lowestRemainingId; | |
622 } | |
623 | |
624 // Send the pitch bend message of a given number of a semitones. Send by OSC, | |
625 // which can be mapped to MIDI CC externally | |
626 void TouchkeyControlMapping::sendControlMessage(float value, bool force) { | |
627 if(force || !suspended_) { | |
628 #ifdef DEBUG_CONTROL_MAPPING | |
629 std::cout << "TouchkeyControlMapping: sending " << value << " for note " << noteNumber_ << std::endl; | |
630 #endif | |
631 keyboard_.sendMessage(controlName_.c_str(), "if", noteNumber_, value, LO_ARGS_END); | |
632 } | |
633 } | |
634 |