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: