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