andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: TouchkeyControlMapping.cpp: per-note mapping for the TouchKeys control andrewm@0: mapping, which converts an arbitrary touch parameter into a MIDI or andrewm@0: OSC control message. andrewm@0: */ andrewm@0: andrewm@0: #include "TouchkeyControlMapping.h" andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "../MappingScheduler.h" andrewm@0: andrewm@0: #undef DEBUG_CONTROL_MAPPING andrewm@0: andrewm@0: // Class constants andrewm@0: const int TouchkeyControlMapping::kDefaultMIDIChannel = 0; andrewm@0: const int TouchkeyControlMapping::kDefaultFilterBufferLength = 300; andrewm@0: andrewm@0: const bool TouchkeyControlMapping::kDefaultIgnoresTwoFingers = false; andrewm@0: const bool TouchkeyControlMapping::kDefaultIgnoresThreeFingers = false; andrewm@0: const int TouchkeyControlMapping::kDefaultDirection = TouchkeyControlMapping::kDirectionPositive; andrewm@0: andrewm@0: // Main constructor takes references/pointers from objects which keep track andrewm@0: // of touch location, continuous key position and the state detected from that andrewm@0: // position. The PianoKeyboard object is strictly required as it gives access to andrewm@0: // Scheduler and OSC methods. The others are optional since any given system may andrewm@0: // contain only one of continuous key position or touch sensitivity andrewm@0: TouchkeyControlMapping::TouchkeyControlMapping(PianoKeyboard &keyboard, MappingFactory *factory, int noteNumber, Node* touchBuffer, andrewm@0: Node* positionBuffer, KeyPositionTracker* positionTracker) andrewm@0: : TouchkeyBaseMapping(keyboard, factory, noteNumber, touchBuffer, positionBuffer, positionTracker), andrewm@0: controlIsEngaged_(false), andrewm@0: inputMin_(0.0), inputMax_(1.0), outputMin_(0.0), outputMax_(1.0), outputDefault_(0.0), andrewm@0: inputParameter_(kInputParameterYPosition), inputType_(kTypeAbsolute), andrewm@0: threshold_(0.0), ignoresTwoFingers_(kDefaultIgnoresTwoFingers), andrewm@0: ignoresThreeFingers_(kDefaultIgnoresThreeFingers), direction_(kDefaultDirection), andrewm@0: touchOnsetValue_(missing_value::missing()), andrewm@0: midiOnsetValue_(missing_value::missing()), andrewm@0: lastValue_(missing_value::missing()), andrewm@0: lastTimestamp_(missing_value::missing()), lastProcessedIndex_(0), andrewm@0: controlEngageLocation_(missing_value::missing()), andrewm@0: controlScalerPositive_(missing_value::missing()), andrewm@0: controlScalerNegative_(missing_value::missing()), andrewm@0: lastControlValue_(outputDefault_), andrewm@0: rawValues_(kDefaultFilterBufferLength) andrewm@0: { andrewm@0: resetDetectionState(); andrewm@0: } andrewm@0: andrewm@0: TouchkeyControlMapping::~TouchkeyControlMapping() { andrewm@0: #if 0 andrewm@0: #ifndef NEW_MAPPING_SCHEDULER andrewm@0: try { andrewm@0: disengage(); andrewm@0: } andrewm@0: catch(...) { andrewm@0: std::cerr << "~TouchkeyControlMapping(): exception during disengage()\n"; andrewm@0: } andrewm@0: #endif andrewm@0: #endif andrewm@0: } andrewm@0: andrewm@0: // Turn on mapping of data. andrewm@0: /*void TouchkeyControlMapping::engage() { andrewm@0: Mapping::engage(); andrewm@0: andrewm@0: // Register for OSC callbacks on MIDI note on/off andrewm@0: addOscListener("/midi/noteon"); andrewm@0: addOscListener("/midi/noteoff"); andrewm@0: } andrewm@0: andrewm@0: // Turn off mapping of data. Remove our callback from the scheduler andrewm@0: void TouchkeyControlMapping::disengage(bool shouldDelete) { andrewm@0: // Remove OSC listeners first andrewm@0: removeOscListener("/midi/noteon"); andrewm@0: removeOscListener("/midi/noteoff"); andrewm@0: andrewm@0: // Don't send any separate message here, leave it where it was andrewm@0: andrewm@0: Mapping::disengage(shouldDelete); andrewm@0: andrewm@0: if(noteIsOn_) { andrewm@0: // TODO andrewm@0: } andrewm@0: noteIsOn_ = false; andrewm@0: }*/ andrewm@0: andrewm@0: // Reset state back to defaults andrewm@0: void TouchkeyControlMapping::reset() { andrewm@0: TouchkeyBaseMapping::reset(); andrewm@0: sendControlMessage(outputDefault_); andrewm@0: resetDetectionState(); andrewm@0: //noteIsOn_ = false; andrewm@0: } andrewm@0: andrewm@0: // Resend all current parameters andrewm@0: void TouchkeyControlMapping::resend() { andrewm@0: sendControlMessage(lastControlValue_, true); andrewm@0: } andrewm@0: andrewm@0: // Name for this control, used in the OSC path andrewm@0: /*void TouchkeyControlMapping::setName(const std::string& name) { andrewm@0: controlName_ = name; andrewm@0: }*/ andrewm@0: andrewm@0: // Parameters for the controller handling andrewm@0: // Input parameter to use for this control mapping and whether it is absolute or relative andrewm@0: void TouchkeyControlMapping::setInputParameter(int parameter, int type) { andrewm@0: if(inputParameter_ >= 0 && inputParameter_ < kInputParameterMaxValue) andrewm@0: inputParameter_ = parameter; andrewm@0: if(type >= 0 && type < kTypeMaxValue) andrewm@0: inputType_ = type; andrewm@0: } andrewm@0: andrewm@0: // Input/output range for this parameter andrewm@0: void TouchkeyControlMapping::setRange(float inputMin, float inputMax, float outputMin, float outputMax, float outputDefault) { andrewm@0: inputMin_ = inputMin; andrewm@0: inputMax_ = inputMax; andrewm@0: outputMin_ = outputMin; andrewm@0: outputMax_ = outputMax; andrewm@0: outputDefault_ = outputDefault; andrewm@0: } andrewm@0: andrewm@0: // Threshold which must be exceeded for the control to engage (for relative position), or 0 if not used andrewm@0: void TouchkeyControlMapping::setThreshold(float threshold) { andrewm@0: threshold_ = threshold; andrewm@0: } andrewm@0: andrewm@0: void TouchkeyControlMapping::setIgnoresMultipleFingers(bool ignoresTwo, bool ignoresThree) { andrewm@0: ignoresTwoFingers_ = ignoresTwo; andrewm@0: ignoresThreeFingers_ = ignoresThree; andrewm@0: } andrewm@0: andrewm@0: void TouchkeyControlMapping::setDirection(int direction) { andrewm@0: if(direction >= 0 && direction < kDirectionMaxValue) andrewm@0: direction_ = direction; andrewm@0: } andrewm@0: andrewm@0: // OSC handler method. Called from PianoKeyboard when MIDI data comes in. andrewm@0: /*bool TouchkeyControlMapping::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) { andrewm@0: if(!strcmp(path, "/midi/noteon") && !noteIsOn_ && numValues >= 1) { andrewm@0: if(types[0] == 'i' && values[0]->i == noteNumber_) { andrewm@0: // MIDI note has gone on. Set the starting location to be most recent andrewm@0: // location. It's possible there has been no touch data before this, andrewm@0: // in which case lastX and lastY will hold missing values. andrewm@0: midiOnsetValue_ = lastValue_; andrewm@0: if(!missing_value::isMissing(midiOnsetValue_)) { andrewm@0: if(inputType_ == kTypeNoteOnsetRelative) { andrewm@0: // Already have touch data. Clear the buffer here. andrewm@0: // Clear buffer and start with default value for this point andrewm@0: clearBuffers(); andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n"; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "MIDI on but no touch\n"; andrewm@0: #endif andrewm@0: } andrewm@0: andrewm@0: noteIsOn_ = true; andrewm@0: return false; andrewm@0: } andrewm@0: } andrewm@0: else if(!strcmp(path, "/midi/noteoff") && noteIsOn_ && numValues >= 1) { andrewm@0: if(types[0] == 'i' && values[0]->i == noteNumber_) { andrewm@0: // MIDI note goes off andrewm@0: noteIsOn_ = false; andrewm@0: if(controlIsEngaged_) { andrewm@0: // TODO: should anything happen here? andrewm@0: } andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "MIDI off\n"; andrewm@0: #endif andrewm@0: return false; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: return false; andrewm@0: }*/ andrewm@0: andrewm@0: // Trigger method. This receives updates from the TouchKey data or from state changes in andrewm@0: // the continuous key position (KeyPositionTracker). It will potentially change the scheduled andrewm@0: // behavior of future mapping calls, but the actual OSC messages should be transmitted in a different andrewm@0: // thread. andrewm@0: void TouchkeyControlMapping::triggerReceived(TriggerSource* who, timestamp_type timestamp) { andrewm@0: if(who == 0) andrewm@0: return; andrewm@0: andrewm@0: if(who == touchBuffer_) { andrewm@0: if(!touchBuffer_->empty()) { andrewm@0: // New touch data is available. Find the distance from the onset location. andrewm@0: KeyTouchFrame frame = touchBuffer_->latest(); andrewm@0: lastTimestamp_ = timestamp; andrewm@0: andrewm@0: if(frame.count == 0) { andrewm@0: // No touches. Last values are "missing", and we're not tracking any andrewm@0: // particular touch ID andrewm@0: lastValue_ = missing_value::missing(); andrewm@0: idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1; andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Touch off\n"; andrewm@0: #endif andrewm@0: } andrewm@0: else { andrewm@0: //ScopedLock sl(rawValueAccessMutex_); andrewm@0: andrewm@0: // At least one touch. Check if we are already tracking an ID and, if so, andrewm@0: // use its coordinates. Otherwise grab the lowest current ID. andrewm@0: lastValue_ = getValue(frame); andrewm@0: andrewm@0: // Check that the value actually exists andrewm@0: if(!missing_value::isMissing(lastValue_)) { andrewm@0: // If we have no onset value, this is it andrewm@0: if(missing_value::isMissing(touchOnsetValue_)) { andrewm@0: touchOnsetValue_ = lastValue_; andrewm@0: if(inputType_ == kTypeFirstTouchRelative) { andrewm@0: clearBuffers(); andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Starting at " << lastValue_ << std::endl; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // If MIDI note is on and we don't previously have a value, this is it andrewm@0: if(noteIsOn_ && missing_value::isMissing(midiOnsetValue_)) { andrewm@0: midiOnsetValue_ = lastValue_; andrewm@0: if(inputType_ == kTypeNoteOnsetRelative) { andrewm@0: clearBuffers(); andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Starting at " << lastValue_ << std::endl; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(noteIsOn_) { andrewm@0: // Insert the latest sample into the buffer depending on how the data should be processed andrewm@0: if(inputType_ == kTypeAbsolute) { andrewm@0: rawValues_.insert(lastValue_, timestamp); andrewm@0: } andrewm@0: else if(inputType_ == kTypeFirstTouchRelative) { andrewm@0: rawValues_.insert(lastValue_ - touchOnsetValue_, timestamp); andrewm@0: } andrewm@0: else if(inputType_ == kTypeNoteOnsetRelative) { andrewm@0: rawValues_.insert(lastValue_ - midiOnsetValue_, timestamp); andrewm@0: } andrewm@0: andrewm@0: // Move the current scheduled event up to the present time. andrewm@0: // FIXME: this may be more inefficient than just doing everything in the current thread! andrewm@0: #ifdef NEW_MAPPING_SCHEDULER andrewm@0: keyboard_.mappingScheduler().scheduleNow(this); andrewm@0: #else andrewm@0: keyboard_.unscheduleEvent(this); andrewm@0: keyboard_.scheduleEvent(this, mappingAction_, keyboard_.schedulerCurrentTimestamp()); andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Mapping method. This actually does the real work of sending OSC data in response to the andrewm@0: // latest information from the touch sensors or continuous key angle andrewm@0: timestamp_type TouchkeyControlMapping::performMapping() { andrewm@0: //ScopedLock sl(rawValueAccessMutex_); andrewm@0: andrewm@0: timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp(); andrewm@0: bool newSamplePresent = false; andrewm@0: andrewm@0: // Go through the filtered distance samples that are remaining to process. andrewm@0: if(lastProcessedIndex_ < rawValues_.beginIndex() + 1) { andrewm@0: // Fell off the beginning of the position buffer. Skip to the samples we have andrewm@0: // (shouldn't happen except in cases of exceptional system load, and not too andrewm@0: // consequential if it does happen). andrewm@0: lastProcessedIndex_ = rawValues_.beginIndex() + 1; andrewm@0: } andrewm@0: andrewm@0: while(lastProcessedIndex_ < rawValues_.endIndex()) { andrewm@0: float value = rawValues_[lastProcessedIndex_]; andrewm@0: //timestamp_type timestamp = rawValues_.timestampAt(lastProcessedIndex_); andrewm@0: newSamplePresent = true; andrewm@0: andrewm@0: if(inputType_ == kTypeAbsolute) { andrewm@0: controlIsEngaged_ = true; andrewm@0: } andrewm@0: else if(!controlIsEngaged_) { andrewm@0: // Compare value against threshold to see if the control should engage andrewm@0: if(fabsf(value) > threshold_) { andrewm@0: float startingValue; andrewm@0: andrewm@0: controlIsEngaged_ = true; andrewm@0: controlEngageLocation_ = (value > 0 ? threshold_ : -threshold_); andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "engaging control at distance " << controlEngageLocation_ << std::endl; andrewm@0: #endif andrewm@0: andrewm@0: if(inputType_ == kTypeFirstTouchRelative) andrewm@0: startingValue = touchOnsetValue_; andrewm@0: else andrewm@0: startingValue = midiOnsetValue_; andrewm@0: andrewm@0: // This is how much range we would have had without the threshold andrewm@0: float distanceToPositiveEdgeWithoutThreshold = 1.0 - startingValue; andrewm@0: float distanceToNegativeEdgeWithoutThreshold = 0.0 + startingValue; andrewm@0: andrewm@0: // This is how much range we actually have with the threshold andrewm@0: float actualDistanceToPositiveEdge = 1.0 - (startingValue + controlEngageLocation_); andrewm@0: float actualDistanceToNegativeEdge = 0.0 + startingValue + controlEngageLocation_; andrewm@0: andrewm@0: // Make it so moving toward edge of key gets as far as it would have without andrewm@0: // the distance lost by the threshold andrewm@0: if(actualDistanceToPositiveEdge > 0.0) andrewm@0: controlScalerPositive_ = (outputMax_ - outputDefault_) * distanceToPositiveEdgeWithoutThreshold / actualDistanceToPositiveEdge; andrewm@0: else andrewm@0: controlScalerPositive_ = (outputMax_ - outputDefault_); // Sanity check andrewm@0: if(actualDistanceToNegativeEdge > 0.0) andrewm@0: controlScalerNegative_ = (outputDefault_ - outputMin_) * distanceToNegativeEdgeWithoutThreshold / actualDistanceToNegativeEdge; andrewm@0: else andrewm@0: controlScalerNegative_ = (outputDefault_ - outputMin_); // Sanity check andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: lastProcessedIndex_++; andrewm@0: } andrewm@0: andrewm@0: if(controlIsEngaged_) { andrewm@0: // Having processed every sample individually for any detection/filtering, now send andrewm@0: // the most recent output as an OSC message andrewm@0: if(newSamplePresent) { andrewm@0: float latestValue = rawValues_.latest(); andrewm@0: andrewm@0: // In cases of relative values, the place the control engages will actually be where it crosses andrewm@0: // the threshold, not the onset location itself. Need to update the value accordingly. andrewm@0: if(inputType_ == kTypeFirstTouchRelative || andrewm@0: inputType_ == kTypeNoteOnsetRelative) { andrewm@0: if(latestValue > 0) { andrewm@0: latestValue -= threshold_; andrewm@0: if(latestValue < 0) andrewm@0: latestValue = 0; andrewm@0: } andrewm@0: else if(latestValue < 0) { andrewm@0: latestValue += threshold_; andrewm@0: if(latestValue > 0) andrewm@0: latestValue = 0; andrewm@0: } andrewm@41: andrewm@41: if(direction_ == kDirectionNegative) andrewm@41: latestValue = -latestValue; andrewm@41: else if((direction_ == kDirectionBoth) && latestValue < 0) andrewm@41: latestValue = -latestValue; andrewm@0: } andrewm@0: andrewm@0: sendControlMessage(latestValue); andrewm@0: lastControlValue_ = latestValue; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Register for the next update by returning its timestamp andrewm@11: nextScheduledTimestamp_ = 0; //currentTimestamp + updateInterval_; andrewm@0: return nextScheduledTimestamp_; andrewm@0: } andrewm@0: andrewm@0: // MIDI note-on message received andrewm@0: void TouchkeyControlMapping::midiNoteOnReceived(int channel, int velocity) { andrewm@0: // MIDI note has gone on. Set the starting location to be most recent andrewm@0: // location. It's possible there has been no touch data before this, andrewm@0: // in which case lastX and lastY will hold missing values. andrewm@0: midiOnsetValue_ = lastValue_; andrewm@0: if(!missing_value::isMissing(midiOnsetValue_)) { andrewm@0: if(inputType_ == kTypeNoteOnsetRelative) { andrewm@0: // Already have touch data. Clear the buffer here. andrewm@0: // Clear buffer and start with default value for this point andrewm@0: clearBuffers(); andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "MIDI on: starting at (" << midiOnsetValue_ << ")\n"; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "MIDI on but no touch\n"; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // MIDI note-off message received andrewm@0: void TouchkeyControlMapping::midiNoteOffReceived(int channel) { andrewm@0: if(controlIsEngaged_) { andrewm@0: // TODO: should anything happen here? andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Reset variables involved in detecting a pitch bend gesture andrewm@0: void TouchkeyControlMapping::resetDetectionState() { andrewm@0: controlIsEngaged_ = false; andrewm@0: controlEngageLocation_ = missing_value::missing(); andrewm@0: idsOfCurrentTouches_[0] = idsOfCurrentTouches_[1] = idsOfCurrentTouches_[2] = -1; andrewm@0: } andrewm@0: andrewm@0: // Clear the buffers that hold distance measurements andrewm@0: void TouchkeyControlMapping::clearBuffers() { andrewm@0: rawValues_.clear(); andrewm@0: rawValues_.insert(0.0, lastTimestamp_); andrewm@0: lastProcessedIndex_ = 0; andrewm@0: } andrewm@0: andrewm@0: // Return the current parameter value depending on which one we are listening to andrewm@0: float TouchkeyControlMapping::getValue(const KeyTouchFrame& frame) { andrewm@0: if(inputParameter_ == kInputParameterXPosition) andrewm@0: return frame.locH; andrewm@0: /*else if(inputParameter_ == kInputParameter2FingerMean || andrewm@0: inputParameter_ == kInputParameter2FingerDistance) { andrewm@0: if(frame.count < 2) andrewm@0: return missing_value::missing(); andrewm@0: if(frame.count == 3 && ignoresThreeFingers_) andrewm@0: return missing_value::missing(); andrewm@0: andrewm@0: bool foundCurrentTouch = false; andrewm@0: float currentValue; andrewm@0: andrewm@0: // Look for the touches we were tracking last frame andrewm@0: if(idsOfCurrentTouches_[0] >= 0) { andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] == idsOfCurrentTouches_[0]) { andrewm@0: if(inputParameter_ == kInputParameterYPosition) andrewm@0: currentValue = frame.locs[i]; andrewm@0: else // kInputParameterTouchSize andrewm@0: currentValue = frame.sizes[i]; andrewm@0: foundCurrentTouch = true; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(!foundCurrentTouch) { andrewm@0: // Assign a new touch to be tracked andrewm@0: int lowestRemainingId = INT_MAX; andrewm@0: int lowestIndex = 0; andrewm@0: andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] < lowestRemainingId) { andrewm@0: lowestRemainingId = frame.ids[i]; andrewm@0: lowestIndex = i; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: idsOfCurrentTouches_[0] = lowestRemainingId; andrewm@0: if(inputParameter_ == kInputParameterYPosition) andrewm@0: currentValue = frame.locs[lowestIndex]; andrewm@0: else if(inputParameter_ == kInputParameterTouchSize) andrewm@0: currentValue = frame.sizes[lowestIndex]; andrewm@0: else // Shouldn't happen andrewm@0: currentValue = missing_value::missing(); andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n"; andrewm@0: #endif andrewm@0: } andrewm@0: andrewm@0: }*/ andrewm@0: else { andrewm@0: if(frame.count == 0) andrewm@0: return missing_value::missing(); andrewm@0: if((inputParameter_ == kInputParameter2FingerMean || andrewm@0: inputParameter_ == kInputParameter2FingerDistance) && andrewm@0: frame.count < 2) andrewm@0: return missing_value::missing(); andrewm@0: if(frame.count == 2 && ignoresTwoFingers_) andrewm@0: return missing_value::missing(); andrewm@0: if(frame.count == 3 && ignoresThreeFingers_) andrewm@0: return missing_value::missing(); andrewm@0: /* andrewm@0: // The other values are dependent on individual touches andrewm@0: bool foundCurrentTouch = false; andrewm@0: float currentValue; andrewm@0: andrewm@0: // Look for the touch we were tracking last frame andrewm@0: if(idsOfCurrentTouches_[0] >= 0) { andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] == idsOfCurrentTouches_[0]) { andrewm@0: if(inputParameter_ == kInputParameterYPosition) andrewm@0: currentValue = frame.locs[i]; andrewm@0: else // kInputParameterTouchSize andrewm@0: currentValue = frame.sizes[i]; andrewm@0: foundCurrentTouch = true; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(!foundCurrentTouch) { andrewm@0: // Assign a new touch to be tracked andrewm@0: int lowestRemainingId = INT_MAX; andrewm@0: int lowestIndex = 0; andrewm@0: andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] < lowestRemainingId) { andrewm@0: lowestRemainingId = frame.ids[i]; andrewm@0: lowestIndex = i; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: idsOfCurrentTouches_[0] = lowestRemainingId; andrewm@0: if(inputParameter_ == kInputParameterYPosition) andrewm@0: currentValue = frame.locs[lowestIndex]; andrewm@0: else if(inputParameter_ == kInputParameterTouchSize) andrewm@0: currentValue = frame.sizes[lowestIndex]; andrewm@0: else // Shouldn't happen andrewm@0: currentValue = missing_value::missing(); andrewm@0: andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Previous touch stopped; now ID " << idsOfCurrentTouches_[0] << " at (" << currentValue << ")\n"; andrewm@0: #endif andrewm@0: }*/ andrewm@0: andrewm@0: float currentValue = 0; andrewm@0: andrewm@0: int idWithinFrame0 = locateTouchId(frame, 0); andrewm@0: if(idWithinFrame0 < 0) { andrewm@0: // Touch ID not found, start a new value andrewm@0: idsOfCurrentTouches_[0] = lowestUnassignedTouch(frame, &idWithinFrame0); andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Previous touch stopped (0); now ID " << idsOfCurrentTouches_[0] << endl; andrewm@0: #endif andrewm@0: if(idsOfCurrentTouches_[0] < 0) { andrewm@0: cout << "BUG: didn't find any unassigned touch!\n"; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(inputParameter_ == kInputParameterYPosition) andrewm@0: currentValue = frame.locs[idWithinFrame0]; andrewm@0: else if(inputParameter_ == kInputParameterTouchSize) // kInputParameterTouchSize andrewm@0: currentValue = frame.sizes[idWithinFrame0]; andrewm@0: else if(inputParameter_ == kInputParameter2FingerMean || andrewm@0: inputParameter_ == kInputParameter2FingerDistance) { andrewm@0: int idWithinFrame1 = locateTouchId(frame, 1); andrewm@0: if(idWithinFrame1 < 0) { andrewm@0: // Touch ID not found, start a new value andrewm@0: idsOfCurrentTouches_[1] = lowestUnassignedTouch(frame, &idWithinFrame1); andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "Previous touch stopped (1); now ID " << idsOfCurrentTouches_[1] << endl; andrewm@0: #endif andrewm@0: if(idsOfCurrentTouches_[1] < 0) { andrewm@0: cout << "BUG: didn't find any unassigned touch for second finger!\n"; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(inputParameter_ == kInputParameter2FingerMean) andrewm@0: currentValue = (frame.locs[idWithinFrame0] + frame.locs[idWithinFrame1]) * 0.5; andrewm@0: else andrewm@0: currentValue = fabsf(frame.locs[idWithinFrame1] - frame.locs[idWithinFrame0]); andrewm@0: } andrewm@0: andrewm@0: return currentValue; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Look for a touch index in the frame matching the given value of idsOfCurrentTouches[index] andrewm@0: // Returns -1 if not found andrewm@0: int TouchkeyControlMapping::locateTouchId(KeyTouchFrame const& frame, int index) { andrewm@0: if(idsOfCurrentTouches_[index] < 0) andrewm@0: return -1; andrewm@0: andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] == idsOfCurrentTouches_[index]) { andrewm@0: return i; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: return -1; andrewm@0: } andrewm@0: andrewm@0: // Locates the lowest touch ID that is not assigned to a current touch andrewm@0: // Returns -1 if no unassigned touches were found andrewm@0: int TouchkeyControlMapping::lowestUnassignedTouch(KeyTouchFrame const& frame, int *indexWithinFrame) { andrewm@0: int lowestRemainingId = INT_MAX; andrewm@0: int lowestIndex = -1; andrewm@0: andrewm@0: for(int i = 0; i < frame.count; i++) { andrewm@0: if(frame.ids[i] < lowestRemainingId) { andrewm@0: bool alreadyAssigned = false; andrewm@0: for(int j = 0; j < 3; j++) { andrewm@0: if(idsOfCurrentTouches_[j] == frame.ids[i]) andrewm@0: alreadyAssigned = true; andrewm@0: } andrewm@0: andrewm@0: if(!alreadyAssigned) { andrewm@0: lowestRemainingId = frame.ids[i]; andrewm@0: lowestIndex = i; andrewm@0: andrewm@0: } andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: if(indexWithinFrame != 0) andrewm@0: *indexWithinFrame = lowestIndex; andrewm@0: return lowestRemainingId; andrewm@0: } andrewm@0: andrewm@0: // Send the pitch bend message of a given number of a semitones. Send by OSC, andrewm@0: // which can be mapped to MIDI CC externally andrewm@0: void TouchkeyControlMapping::sendControlMessage(float value, bool force) { andrewm@0: if(force || !suspended_) { andrewm@0: #ifdef DEBUG_CONTROL_MAPPING andrewm@0: std::cout << "TouchkeyControlMapping: sending " << value << " for note " << noteNumber_ << std::endl; andrewm@0: #endif andrewm@0: keyboard_.sendMessage(controlName_.c_str(), "if", noteNumber_, value, LO_ARGS_END); andrewm@0: } andrewm@0: } andrewm@0: