annotate Source/Mappings/Control/TouchkeyControlMapping.cpp @ 56:b4a2d2ae43cf tip

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