annotate Source/TouchKeys/PianoKey.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents 78b9808a2c65
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 PianoKey.cpp: handles the operation of a single key on the keyboard,
andrewm@0 21 including fusing touch and MIDI data. Also has hooks for continuous
andrewm@0 22 key angle.
andrewm@0 23 */
andrewm@0 24
andrewm@0 25 #include "PianoKey.h"
andrewm@0 26 #include "PianoKeyboard.h"
andrewm@0 27 #include "../Mappings/MappingFactory.h"
andrewm@0 28
andrewm@0 29 #undef TOUCHKEYS_LEGACY_OSC
andrewm@0 30
andrewm@0 31 // Default constructor
andrewm@0 32 PianoKey::PianoKey(PianoKeyboard& keyboard, int noteNumber, int bufferLength)
andrewm@0 33 : TriggerDestination(), keyboard_(keyboard), noteNumber_(noteNumber),
andrewm@0 34 midiNoteIsOn_(false), midiChannel_(-1), midiOutputPort_(0), midiVelocity_(0),
andrewm@0 35 midiAftertouch_(bufferLength), midiOnTimestamp_(0), midiOffTimestamp_(0),
andrewm@0 36 positionBuffer_(bufferLength),
andrewm@0 37 idleDetector_(kPianoKeyIdleBufferLength, positionBuffer_, kPianoKeyDefaultIdlePositionThreshold,
andrewm@0 38 kPianoKeyDefaultIdleActivityThreshold, kPianoKeyDefaultIdleCounter),
andrewm@0 39 positionTracker_(kPianoKeyPositionTrackerBufferLength, positionBuffer_),
andrewm@0 40 stateBuffer_(kPianoKeyStateBufferLength), state_(kKeyStateToBeInitialized),
andrewm@0 41 touchSensorsArePresent_(true), touchIsActive_(false),
andrewm@0 42 touchBuffer_(bufferLength), touchIsWaiting_(false), touchWaitingSource_(0),
andrewm@0 43 touchWaitingTimestamp_(0),
andrewm@0 44 touchTimeoutInterval_(kPianoKeyDefaultTouchTimeoutInterval)
andrewm@0 45 {
andrewm@0 46 enable();
andrewm@0 47 registerForTrigger(&idleDetector_);
andrewm@0 48 }
andrewm@0 49
andrewm@0 50 // Destructor
andrewm@0 51 PianoKey::~PianoKey() {
andrewm@0 52 // Remove any mappings we've created
andrewm@0 53 //keyboard_.removeMapping(noteNumber_);
andrewm@0 54 }
andrewm@0 55
andrewm@0 56
andrewm@0 57 // Disable the key from sending events. Do this by removing anything that
andrewm@0 58 // listens to its status.
andrewm@0 59 void PianoKey::disable() {
andrewm@0 60 ScopedLock sl(stateMutex_);
andrewm@0 61
andrewm@0 62 if(state_ == kKeyStateDisabled) {
andrewm@0 63 return;
andrewm@0 64 }
andrewm@0 65 // No longer run the idle comparator. This ensures that no further state
andrewm@0 66 // changes take place, and that the idle coefficient is not calculated.
andrewm@0 67 //idleComparator_.clearTriggers();
andrewm@0 68
andrewm@0 69 terminateActivity();
andrewm@0 70 changeState(kKeyStateDisabled);
andrewm@0 71 }
andrewm@0 72
andrewm@0 73 // Start listening for key activity. This will allow the state to transition to
andrewm@0 74 // idle, and then to active as appropriate.
andrewm@0 75 void PianoKey::enable() {
andrewm@0 76 ScopedLock sl(stateMutex_);
andrewm@0 77
andrewm@0 78 if(state_ != kKeyStateDisabled) {
andrewm@0 79 return;
andrewm@0 80 }
andrewm@0 81 changeState(kKeyStateUnknown);
andrewm@0 82 }
andrewm@0 83
andrewm@0 84 // Reset the key to its default state
andrewm@0 85 void PianoKey::reset() {
andrewm@0 86 ScopedLock sl(stateMutex_);
andrewm@0 87
andrewm@0 88 terminateActivity(); // Stop any current activity
andrewm@0 89 positionBuffer_.clear(); // Clear all history
andrewm@0 90 stateBuffer_.clear();
andrewm@0 91 idleDetector_.clear();
andrewm@0 92 changeState(kKeyStateUnknown); // Reinitialize with unknown state
andrewm@0 93 }
andrewm@0 94
andrewm@0 95 // Insert a new sample in the key buffer
andrewm@0 96 void PianoKey::insertSample(key_position pos, timestamp_type ts) {
andrewm@0 97 positionBuffer_.insert(pos, ts);
andrewm@0 98
andrewm@0 99 if((timestamp_diff_type)ts - (timestamp_diff_type)timeOfLastGuiUpdate_ > kPianoKeyGuiUpdateInterval) {
andrewm@0 100 timeOfLastGuiUpdate_ = ts;
andrewm@0 101 if(keyboard_.gui() != 0) {
andrewm@0 102 keyboard_.gui()->setAnalogValueForKey(noteNumber_, pos);
andrewm@0 103 }
andrewm@0 104 }
andrewm@0 105
andrewm@0 106 /*if((timestamp_diff_type)ts - (timestamp_diff_type)timeOfLastDebugPrint_ > 1.0) {
andrewm@0 107 timeOfLastDebugPrint_ = ts;
andrewm@0 108 key_position kmin = missing_value<key_position>::missing(), kmax = missing_value<key_position>::missing();
andrewm@0 109 key_position mean = 0;
andrewm@0 110 int count = 0;
andrewm@0 111 Node<key_position>::iterator it = positionBuffer_.begin();
andrewm@0 112 while(it != positionBuffer_.end()) {
andrewm@0 113 if(missing_value<key_position>::isMissing(*it))
andrewm@0 114 continue;
andrewm@0 115 if(missing_value<key_position>::isMissing(kmin) || *it < kmin)
andrewm@0 116 kmin = *it;
andrewm@0 117 if(missing_value<key_position>::isMissing(kmax) || *it > kmax)
andrewm@0 118 kmax = *it;
andrewm@0 119 mean += *it;
andrewm@0 120 it++;
andrewm@0 121 count++;
andrewm@0 122 }
andrewm@0 123 mean /= (key_position)count;
andrewm@0 124
andrewm@0 125 key_position var = 0;
andrewm@0 126 it = positionBuffer_.begin();
andrewm@0 127 while(it != positionBuffer_.end()) {
andrewm@0 128 if(missing_value<key_position>::isMissing(*it))
andrewm@0 129 continue;
andrewm@0 130 var += (*it - mean)*(*it - mean);
andrewm@0 131 it++;
andrewm@0 132 }
andrewm@0 133 var /= (key_position)count;
andrewm@0 134
andrewm@0 135 std::cout << "Key " << noteNumber_ << " mean " << mean << " var " << var << std::endl;
andrewm@0 136 }*/
andrewm@0 137 }
andrewm@0 138
andrewm@0 139 // If a key is active, force it to become idle, stopping any processes that it has created
andrewm@0 140 void PianoKey::forceIdle() {
andrewm@0 141 ScopedLock sl(stateMutex_);
andrewm@0 142
andrewm@0 143 if(state_ == kKeyStateDisabled || state_ == kKeyStateIdle) {
andrewm@0 144 return;
andrewm@0 145 }
andrewm@0 146 terminateActivity();
andrewm@0 147 changeState(kKeyStateIdle);
andrewm@0 148 }
andrewm@0 149
andrewm@0 150 // Handle triggers sent when specific conditions are met (called by various objects)
andrewm@0 151
andrewm@0 152 void PianoKey::triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 153 ScopedLock sl(stateMutex_);
andrewm@0 154
andrewm@0 155 if(who == &idleDetector_) {
andrewm@0 156 //std::cout << "Key " << noteNumber_ << ": IdleDetector says: " << idleDetector_.latest() << std::endl;
andrewm@0 157
andrewm@0 158 if(idleDetector_.latest() == kIdleDetectorIdle) {
andrewm@0 159 cout << "Key " << noteNumber_ << " --> Idle\n";
andrewm@0 160
andrewm@0 161 keyboard_.tellAllMappingFactoriesKeyMotionIdle(noteNumber_, midiNoteIsOn_, touchIsActive_,
andrewm@0 162 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 163
andrewm@0 164 // Remove any mapping present on this key
andrewm@0 165 //keyboard_.removeMapping(noteNumber_);
andrewm@0 166
andrewm@0 167 positionTracker_.disengage();
andrewm@0 168 unregisterForTrigger(&positionTracker_);
andrewm@0 169 terminateActivity();
andrewm@0 170 changeState(kKeyStateIdle);
andrewm@0 171 keyboard_.setKeyLEDColorRGB(noteNumber_, 0, 0, 0);
andrewm@0 172 }
andrewm@0 173 else if(idleDetector_.latest() == kIdleDetectorActive && state_ != kKeyStateUnknown) {
andrewm@0 174 cout << "Key " << noteNumber_ << " --> Active\n";
andrewm@0 175
andrewm@0 176 keyboard_.tellAllMappingFactoriesKeyMotionActive(noteNumber_, midiNoteIsOn_, touchIsActive_,
andrewm@0 177 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 178
andrewm@0 179 // Only allow transition to active from a known previous state
andrewm@0 180 // TODO: set up min/max listener
andrewm@0 181 // TODO: may want to change the parameters on the idleDetector
andrewm@0 182 changeState(kKeyStateActive);
andrewm@0 183 //keyboard_.setKeyLEDColorRGB(noteNumber_, 1.0, 0.0, 0);
andrewm@0 184
andrewm@0 185 // Engage the position tracker that handles specific measurement of key states
andrewm@0 186 registerForTrigger(&positionTracker_);
andrewm@0 187 positionTracker_.reset();
andrewm@0 188 positionTracker_.engage();
andrewm@0 189
andrewm@0 190 // Allocate a new mapping that converts key position gestures to sound
andrewm@0 191 // control messages. TODO: how do we handle this with the TouchKey data too?
andrewm@0 192 //MRPMapping *mapping = new MRPMapping(keyboard_, noteNumber_, &touchBuffer_,
andrewm@0 193 // &positionBuffer_, &positionTracker_);
andrewm@0 194 //MIDIKeyPositionMapping *mapping = new MIDIKeyPositionMapping(keyboard_, noteNumber_, &touchBuffer_,
andrewm@0 195 // &positionBuffer_, &positionTracker_);
andrewm@0 196 //keyboard_.addMapping(noteNumber_, mapping);
andrewm@0 197 //mapping->setPercussivenessMIDIChannel(1);
andrewm@0 198 //mapping->engage();
andrewm@0 199 }
andrewm@0 200 }
andrewm@0 201 else if(who == &positionTracker_ && !positionTracker_.empty()) {
andrewm@0 202 KeyPositionTrackerNotification notification = positionTracker_.latest();
andrewm@0 203
andrewm@0 204 if(notification.type == KeyPositionTrackerNotification::kNotificationTypeStateChange) {
andrewm@0 205 int positionTrackerState = notification.state;
andrewm@0 206
andrewm@0 207 KeyPositionTracker::Event recentEvent;
andrewm@0 208 std::pair<timestamp_type, key_velocity> velocityInfo;
andrewm@0 209 cout << "Key " << noteNumber_ << " --> State " << positionTrackerState << endl;
andrewm@0 210
andrewm@0 211 switch(positionTrackerState) {
andrewm@0 212 case kPositionTrackerStatePartialPressAwaitingMax:
andrewm@0 213 //keyboard_.setKeyLEDColorRGB(noteNumber_, 1.0, 0.0, 0);
andrewm@0 214 recentEvent = positionTracker_.pressStart();
andrewm@0 215 cout << " start = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 216 break;
andrewm@0 217 case kPositionTrackerStatePartialPressFoundMax:
andrewm@0 218 //keyboard_.setKeyLEDColorRGB(noteNumber_, 1.0, 0.6, 0);
andrewm@0 219 recentEvent = positionTracker_.currentMax();
andrewm@0 220 cout << " max = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 221 break;
andrewm@0 222 case kPositionTrackerStatePressInProgress:
andrewm@0 223 //keyboard_.setKeyLEDColorRGB(noteNumber_, 0.8, 0.8, 0);
andrewm@0 224 velocityInfo = positionTracker_.pressVelocity();
andrewm@0 225 cout << " escapement time = " << velocityInfo.first << " velocity = " << velocityInfo.second << endl;
andrewm@0 226 break;
andrewm@0 227 case kPositionTrackerStateDown:
andrewm@0 228 //keyboard_.setKeyLEDColorRGB(noteNumber_, 0, 1.0, 0);
andrewm@0 229 recentEvent = positionTracker_.pressStart();
andrewm@0 230 cout << " start = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 231 recentEvent = positionTracker_.pressFinish();
andrewm@0 232 cout << " finish = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 233 velocityInfo = positionTracker_.pressVelocity();
andrewm@0 234 cout << " escapement time = " << velocityInfo.first << " velocity = " << velocityInfo.second << endl;
andrewm@0 235
andrewm@0 236 if(keyboard_.graphGUI() != 0) {
andrewm@0 237 keyboard_.graphGUI()->setKeyPressStart(positionTracker_.pressStart().position, positionTracker_.pressStart().timestamp);
andrewm@0 238 keyboard_.graphGUI()->setKeyPressFinish(positionTracker_.pressFinish().position, positionTracker_.pressFinish().timestamp);
andrewm@0 239 keyboard_.graphGUI()->copyKeyDataFromBuffer(positionBuffer_, positionTracker_.pressStart().index - 10,
andrewm@0 240 positionBuffer_.endIndex());
andrewm@0 241 }
andrewm@0 242 break;
andrewm@0 243 case kPositionTrackerStateReleaseInProgress:
andrewm@0 244 //keyboard_.setKeyLEDColorRGB(noteNumber_, 0, 0, 1.0);
andrewm@0 245 recentEvent = positionTracker_.releaseStart();
andrewm@0 246 cout << " start = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 247 if(keyboard_.graphGUI() != 0) {
andrewm@0 248 keyboard_.graphGUI()->setKeyReleaseStart(positionTracker_.releaseStart().position, positionTracker_.releaseStart().timestamp);
andrewm@0 249 keyboard_.graphGUI()->copyKeyDataFromBuffer(positionBuffer_, positionTracker_.pressStart().index - 10,
andrewm@0 250 positionBuffer_.endIndex());
andrewm@0 251 //keyboard_.graphGUI()->copyKeyDataFromBuffer(testFilter_, testFilter_.indexNearestTo(positionBuffer_.timestampAt(positionTracker_.pressStart().index - 10)), testFilter_.endIndex());
andrewm@0 252 }
andrewm@0 253 break;
andrewm@0 254 case kPositionTrackerStateReleaseFinished:
andrewm@0 255 //keyboard_.setKeyLEDColorRGB(noteNumber_, 0.5, 0, 1.0);
andrewm@0 256 recentEvent = positionTracker_.releaseFinish();
andrewm@0 257 cout << " finish = (" << recentEvent.index << ", " << recentEvent.position << ", " << recentEvent.timestamp << ")\n";
andrewm@0 258 if(keyboard_.graphGUI() != 0) {
andrewm@0 259 keyboard_.graphGUI()->setKeyReleaseStart(positionTracker_.releaseStart().position, positionTracker_.releaseStart().timestamp);
andrewm@0 260 keyboard_.graphGUI()->setKeyReleaseFinish(positionTracker_.releaseFinish().position, positionTracker_.releaseFinish().timestamp);
andrewm@0 261 keyboard_.graphGUI()->copyKeyDataFromBuffer(positionBuffer_, positionTracker_.pressStart().index - 10,
andrewm@0 262 positionBuffer_.endIndex());
andrewm@0 263 //keyboard_.graphGUI()->copyKeyDataFromBuffer(testFilter_, testFilter_.indexNearestTo(positionBuffer_.timestampAt(positionTracker_.pressStart().index - 10)), testFilter_.endIndex());
andrewm@0 264 }
andrewm@0 265 break;
andrewm@0 266 default:
andrewm@0 267 break;
andrewm@0 268 }
andrewm@0 269 }
andrewm@0 270 }
andrewm@0 271 }
andrewm@0 272
andrewm@0 273 // Update the current state
andrewm@0 274
andrewm@0 275 void PianoKey::changeState(key_state newState) {
andrewm@0 276 if(!positionBuffer_.empty())
andrewm@0 277 changeState(newState, positionBuffer_.latestTimestamp());
andrewm@0 278 else
andrewm@0 279 changeState(newState, 0);
andrewm@0 280 }
andrewm@0 281
andrewm@0 282 void PianoKey::changeState(key_state newState, timestamp_type timestamp) {
andrewm@0 283 stateBuffer_.insert(newState, timestamp);
andrewm@0 284 state_ = newState;
andrewm@0 285 }
andrewm@0 286
andrewm@0 287 // Stop any activity that's currently taking place on account of the key motion
andrewm@0 288
andrewm@0 289 void PianoKey::terminateActivity() {
andrewm@0 290
andrewm@0 291 }
andrewm@0 292
andrewm@0 293 #pragma mark MIDI Methods
andrewm@0 294 // ***** MIDI Methods *****
andrewm@0 295
andrewm@0 296 // Note On message from associated MIDI keyboard: record channel we should use
andrewm@0 297 // for the duration of this note, as well as the note's velocity
andrewm@0 298 void PianoKey::midiNoteOn(MidiKeyboardSegment *who, int velocity, int channel, timestamp_type timestamp) {
andrewm@0 299 midiNoteIsOn_ = true;
andrewm@0 300 midiChannel_ = channel;
andrewm@0 301 midiVelocity_ = velocity;
andrewm@0 302 midiOnTimestamp_ = timestamp;
andrewm@0 303
andrewm@0 304 if(keyboard_.mappingFactory(who) != 0)
andrewm@0 305 keyboard_.mappingFactory(who)->midiNoteOn(noteNumber_, touchIsActive_, (idleDetector_.idleState() == kIdleDetectorActive),
andrewm@0 306 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 307
andrewm@0 308 // Start the note right away unless we either need to wait for a touch or a delay has been enforced
andrewm@0 309 if((touchIsActive_ && !touchBuffer_.empty()) || !touchSensorsArePresent_ || (touchTimeoutInterval_ == 0)) {
andrewm@0 310 midiNoteOnHelper(who);
andrewm@0 311 }
andrewm@0 312 else {
andrewm@0 313 // If touch isn't yet active, we might delay the note onset for a short
andrewm@0 314 // time to wait for it.
andrewm@0 315
andrewm@0 316 // Cases:
andrewm@0 317 // (1) Touch was active --> send above messages and tell the MidiController to go ahead
andrewm@0 318 // (a) Channel Selection mode: OSC messages will be used to choose a channel; messages
andrewm@0 319 // that translate to control changes need to be archived and resent once the channel is known
andrewm@0 320 // (b) Polyphonic and other modes: OSC messages used to generate control changes; channel
andrewm@0 321 // is already known
andrewm@0 322 // (2) Touch not yet active --> schedule a note on for a specified time interval in the future
andrewm@0 323 // (a) Touch event arrives on this key before that --> send OSC and call the MidiController,
andrewm@0 324 // removing the scheduled future event
andrewm@0 325 // (b) No touch event arrives --> future event triggers without touch info, call MidiController
andrewm@0 326 // and tell it to use defaults
andrewm@0 327 touchWaitingSource_ = who;
andrewm@0 328 touchIsWaiting_ = true;
andrewm@0 329 touchWaitingTimestamp_ = keyboard_.schedulerCurrentTimestamp() + touchTimeoutInterval_;
andrewm@0 330 keyboard_.scheduleEvent(this,
andrewm@0 331 boost::bind(&PianoKey::touchTimedOut, this),
andrewm@0 332 touchWaitingTimestamp_);
andrewm@0 333 }
andrewm@0 334 }
andrewm@0 335
andrewm@0 336 // This private method does the real work of midiNoteOn(). It's separated because under certain
andrewm@0 337 // circumstances, the helper function is called in a delayed manner, after a touch has been received.
andrewm@0 338 void PianoKey::midiNoteOnHelper(MidiKeyboardSegment *who) {
andrewm@0 339 touchIsWaiting_ = false;
andrewm@0 340 touchWaitingSource_ = 0;
andrewm@0 341
andrewm@0 342 if(!touchBuffer_.empty()) {
andrewm@0 343 const KeyTouchFrame& frame(touchBuffer_.latest());
andrewm@0 344 int indexOfFirstTouch = 0;
andrewm@0 345
andrewm@0 346 // Find which touch happened first so we can report its location
andrewm@0 347 for(int i = 0; i < frame.count; i++) {
andrewm@0 348 if(frame.ids[i] < frame.ids[indexOfFirstTouch])
andrewm@0 349 indexOfFirstTouch = i;
andrewm@0 350 }
andrewm@0 351
andrewm@0 352 // Send a message reporting the touch location of the first touch and the
andrewm@0 353 // current number of touches. The target (either MidiInputController or external)
andrewm@0 354 // may use this to change its behavior independently of later changes in touch.
andrewm@0 355
andrewm@0 356 keyboard_.sendMessage("/touchkeys/preonset", "iiiiiiffiffifff",
andrewm@0 357 noteNumber_, midiChannel_, midiVelocity_, // MIDI data
andrewm@0 358 frame.count, indexOfFirstTouch, // General information: how many touches, which was first?
andrewm@0 359 frame.ids[0], frame.locs[0], frame.sizes[0], // Specific touch information
andrewm@0 360 frame.ids[1], frame.locs[1], frame.sizes[1],
andrewm@0 361 frame.ids[2], frame.locs[2], frame.sizes[2],
andrewm@0 362 frame.locH, LO_ARGS_END);
andrewm@0 363
andrewm@0 364 // ----
andrewm@0 365 // The above function will trigger the callback in MidiInputController, if it is enabled.
andrewm@0 366 // Therefore, the calls below will take place after MidiInputController has handled its callback.
andrewm@0 367 // ----
andrewm@0 368
andrewm@0 369 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 370 // Send move and resize gestures for each active touch
andrewm@0 371 for(int i = 0; i < frame.count; i++) {
andrewm@0 372 keyboard_.sendMessage("/touchkeys/move", "iiff", noteNumber_, frame.ids[i],
andrewm@0 373 frame.locs[i], frame.horizontal(i), LO_ARGS_END);
andrewm@0 374 keyboard_.sendMessage("/touchkeys/resize", "iif", noteNumber_, frame.ids[i],
andrewm@0 375 frame.sizes[i], LO_ARGS_END);
andrewm@0 376 }
andrewm@0 377
andrewm@0 378 // If more than one touch is present, resend any pinch and slide gestures
andrewm@0 379 // before we start.
andrewm@0 380 if(frame.count == 2) {
andrewm@0 381 float newCentroid = (frame.locs[0] + frame.locs[1]) / 2.0;
andrewm@0 382 float newWidth = frame.locs[1] - frame.locs[0];
andrewm@0 383
andrewm@0 384 keyboard_.sendMessage("/touchkeys/twofinger/pinch", "iiif",
andrewm@0 385 noteNumber_, frame.ids[0], frame.ids[1], newWidth, LO_ARGS_END);
andrewm@0 386 keyboard_.sendMessage("/touchkeys/twofinger/slide", "iiif",
andrewm@0 387 noteNumber_, frame.ids[0], frame.ids[1], newCentroid, LO_ARGS_END);
andrewm@0 388 }
andrewm@0 389 else if(frame.count == 3) {
andrewm@0 390 float newCentroid = (frame.locs[0] + frame.locs[1] + frame.locs[2]) / 3.0;
andrewm@0 391 float newWidth = frame.locs[2] - frame.locs[0];
andrewm@0 392
andrewm@0 393 keyboard_.sendMessage("/touchkeys/threefinger/pinch", "iiiif",
andrewm@0 394 noteNumber_, frame.ids[0], frame.ids[1], frame.ids[2], newWidth, LO_ARGS_END);
andrewm@0 395 keyboard_.sendMessage("/touchkeys/threefinger/slide", "iiiif",
andrewm@0 396 noteNumber_, frame.ids[0], frame.ids[1], frame.ids[2], newCentroid, LO_ARGS_END);
andrewm@0 397 }
andrewm@0 398 #endif
andrewm@0 399 }
andrewm@0 400
andrewm@0 401 // Before the note starts, inform the mapping factory in case there are default values to be sent out.
andrewm@0 402 if(keyboard_.mappingFactory(who) != 0)
andrewm@0 403 keyboard_.mappingFactory(who)->noteWillBegin(noteNumber_, midiChannel_, midiVelocity_);
andrewm@0 404
andrewm@0 405 keyboard_.sendMessage("/midi/noteon", "iii", noteNumber_, midiChannel_, midiVelocity_, LO_ARGS_END);
andrewm@0 406
andrewm@0 407 // Update GUI if it is available. TODO: fix the ordering problem for real!
andrewm@0 408 if(keyboard_.gui() != 0 && midiNoteIsOn_) {
andrewm@0 409 keyboard_.gui()->setMidiActive(noteNumber_, true);
andrewm@0 410 }
andrewm@0 411 }
andrewm@0 412
andrewm@0 413 // Note Off message from associated MIDI keyboard. Clear all old MIDI state.
andrewm@0 414 void PianoKey::midiNoteOff(MidiKeyboardSegment *who, timestamp_type timestamp) {
andrewm@0 415 midiNoteIsOn_ = false;
andrewm@0 416 midiOffTimestamp_ = timestamp;
andrewm@0 417
andrewm@0 418 if(keyboard_.mappingFactory(who) != 0)
andrewm@0 419 keyboard_.mappingFactory(who)->midiNoteOff(noteNumber_, touchIsActive_, (idleDetector_.idleState() == kIdleDetectorActive),
andrewm@0 420 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 421
andrewm@0 422 keyboard_.sendMessage("/midi/noteoff", "ii", noteNumber_, midiChannel_, LO_ARGS_END);
andrewm@0 423
andrewm@46 424 midiVelocity_ = 0;
andrewm@46 425 midiChannel_ = -1;
andrewm@46 426 midiAftertouch_.clear();
andrewm@46 427
andrewm@0 428 // Update GUI if it is available
andrewm@0 429 if(keyboard_.gui() != 0) {
andrewm@0 430 keyboard_.gui()->setMidiActive(noteNumber_, false);
andrewm@0 431 }
andrewm@0 432 }
andrewm@0 433
andrewm@0 434 // Aftertouch (either channel or polyphonic) message from associated MIDI keyboard
andrewm@0 435 void PianoKey::midiAftertouch(MidiKeyboardSegment *who, int value, timestamp_type timestamp) {
andrewm@0 436 if(!midiNoteIsOn_)
andrewm@0 437 return;
andrewm@0 438 midiAftertouch_.insert(value, timestamp);
andrewm@0 439
andrewm@0 440 keyboard_.sendMessage("/midi/aftertouch-poly", "iii", noteNumber_, midiChannel_, value, LO_ARGS_END);
andrewm@0 441 }
andrewm@0 442
andrewm@0 443 #pragma mark Touch Methods
andrewm@0 444 // ***** Touch Methods *****
andrewm@0 445
andrewm@0 446 // Insert a new frame of touchkey data, making any necessary status changes
andrewm@0 447 // (i.e. touch active, possibly changing number of active touches)
andrewm@0 448
andrewm@0 449 void PianoKey::touchInsertFrame(KeyTouchFrame& newFrame, timestamp_type timestamp) {
andrewm@0 450 if(!touchSensorsArePresent_)
andrewm@0 451 return;
andrewm@9 452
andrewm@0 453 // First check if the key was previously inactive. If so, send a message
andrewm@0 454 // that the touch has begun
andrewm@0 455 if(!touchIsActive_) {
andrewm@0 456 keyboard_.sendMessage("/touchkeys/on", "i", noteNumber_, LO_ARGS_END);
andrewm@0 457 keyboard_.tellAllMappingFactoriesTouchBegan(noteNumber_, midiNoteIsOn_, (idleDetector_.idleState() == kIdleDetectorActive),
andrewm@0 458 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 459 }
andrewm@0 460
andrewm@0 461 touchIsActive_ = true;
andrewm@0 462
andrewm@0 463 // If previous touch frames are present on this key, check the preceding
andrewm@0 464 // frame to see if the state has changed in any important ways
andrewm@0 465 if(!touchBuffer_.empty()) {
andrewm@0 466 const KeyTouchFrame& lastFrame(touchBuffer_.latest());
andrewm@0 467
andrewm@0 468 // Next ID is the touch ID that should be used for any new touches. Scoop this
andrewm@0 469 // info from the last frame.
andrewm@0 470
andrewm@0 471 newFrame.nextId = lastFrame.nextId;
andrewm@0 472
andrewm@0 473 // Assign ID numbers to each touch. This is easy if the number of touches
andrewm@0 474 // from the previous frame to this one stayed the same, somewhat more complex
andrewm@0 475 // if a touch was added or removed.
andrewm@0 476
andrewm@0 477 if(newFrame.count > lastFrame.count) {
andrewm@0 478 // One or more points have been added. Match the new points to the old ones to figure out
andrewm@0 479 // which points have been added, versus which moved from before.
andrewm@0 480
andrewm@0 481 std::set<int> availableNewPoints;
andrewm@0 482 for(int i = 0; i < newFrame.count; i++)
andrewm@0 483 availableNewPoints.insert(i);
andrewm@0 484
andrewm@0 485 std::list<int> ordering(touchMatchClosestPoints(lastFrame.locs, newFrame.locs, 3, 0, availableNewPoints, 0.0).second);
andrewm@0 486
andrewm@0 487 // ordering tells us the index of the new point corresponding to each old index,
andrewm@0 488 // e.g. {2, 0, 1} --> old point 0 goes to new point 2, old point 1 goes to new point 0, ...
andrewm@0 489
andrewm@0 490 // new points are still in ascending position order, so we use this matching to assign unique IDs
andrewm@0 491 // and send relevant "add" messages
andrewm@0 492
andrewm@0 493 int counter = 0;
andrewm@0 494 for(std::list<int>::iterator it = ordering.begin(); it != ordering.end(); ++it) {
andrewm@0 495 newFrame.ids[*it] = lastFrame.ids[counter];
andrewm@0 496
andrewm@0 497 if(newFrame.ids[*it] < 0) {
andrewm@0 498 // Matching to a negative ID means the touch is new
andrewm@0 499
andrewm@0 500 newFrame.ids[*it] = newFrame.nextId++;
andrewm@0 501 touchAdd(newFrame, *it, timestamp);
andrewm@0 502 }
andrewm@0 503 else {
andrewm@0 504 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 505 // Send "move" messages for the points that have moved
andrewm@0 506 if(fabsf(newFrame.locs[*it] - lastFrame.locs[counter]) > 0 /*moveThreshold_*/)
andrewm@0 507 keyboard_.sendMessage("/touchkeys/move", "iiff", noteNumber_, newFrame.ids[*it],
andrewm@0 508 newFrame.locs[*it], newFrame.horizontal(*it), LO_ARGS_END);
andrewm@0 509 if(fabsf(newFrame.sizes[*it] - lastFrame.sizes[counter]) > 0 /*resizeThreshold_*/)
andrewm@0 510 keyboard_.sendMessage("/touchkeys/resize", "iif", noteNumber_, newFrame.ids[*it],
andrewm@0 511 newFrame.sizes[*it], LO_ARGS_END);
andrewm@0 512 #endif
andrewm@0 513 }
andrewm@0 514
andrewm@0 515 counter++;
andrewm@0 516 }
andrewm@0 517 }
andrewm@0 518 else if(newFrame.count < lastFrame.count) {
andrewm@0 519 // One or more points have been removed. Match the new points to the old ones to figure out
andrewm@0 520 // which points have been removed, versus which moved from before.
andrewm@0 521
andrewm@0 522 std::set<int> availableNewPoints;
andrewm@0 523 for(int i = 0; i < 3; i++)
andrewm@0 524 availableNewPoints.insert(i);
andrewm@0 525
andrewm@0 526 std::list<int> ordering(touchMatchClosestPoints(lastFrame.locs, newFrame.locs, 3, 0, availableNewPoints, 0.0).second);
andrewm@0 527
andrewm@0 528 // ordering tells us the index of the new point corresponding to each old index,
andrewm@0 529 // e.g. {2, 0, 1} --> old point 0 goes to new point 2, old point 1 goes to new point 0, ...
andrewm@0 530
andrewm@0 531 // new points are still in ascending position order, so we use this matching to assign unique IDs
andrewm@0 532 // and send relevant "add" messages
andrewm@0 533
andrewm@0 534 int counter = 0;
andrewm@0 535 for(std::list<int>::iterator it = ordering.begin(); it != ordering.end(); ++it) {
andrewm@0 536 if(*it < newFrame.count) {
andrewm@0 537 // Old index {counter} matches a valid new touch
andrewm@0 538
andrewm@0 539 newFrame.ids[*it] = lastFrame.ids[counter]; // Match IDs for currently active touches
andrewm@0 540
andrewm@0 541 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 542 // Send "move" messages for the points that have moved
andrewm@0 543 if(fabsf(newFrame.locs[*it] - lastFrame.locs[counter]) > 0 /*moveThreshold_*/)
andrewm@0 544 keyboard_.sendMessage("/touchkeys/move", "iiff", noteNumber_, newFrame.ids[*it],
andrewm@0 545 newFrame.locs[*it], newFrame.horizontal(*it), LO_ARGS_END);
andrewm@0 546 if(fabsf(newFrame.sizes[*it] - lastFrame.sizes[counter]) > 0 /*resizeThreshold_*/)
andrewm@0 547 keyboard_.sendMessage("/touchkeys/resize", "iif", noteNumber_, newFrame.ids[*it],
andrewm@0 548 newFrame.sizes[*it], LO_ARGS_END);
andrewm@0 549 #endif
andrewm@0 550 }
andrewm@0 551 else if(lastFrame.ids[counter] >= 0) {
andrewm@0 552 // Old index {counter} matches an invalid new index, meaning a touch has been removed.
andrewm@0 553 touchRemove(lastFrame, lastFrame.ids[counter], newFrame.count, timestamp);
andrewm@0 554 }
andrewm@0 555
andrewm@0 556 counter++;
andrewm@0 557 }
andrewm@0 558 }
andrewm@0 559 else {
andrewm@0 560 // Same number of touches as before. Touches are always stored in increasing order,
andrewm@0 561 // so we just need to copy these over, maintaining the same ID numbers.
andrewm@0 562
andrewm@0 563 for(int i = 0; i < newFrame.count; i++) {
andrewm@0 564 newFrame.ids[i] = lastFrame.ids[i];
andrewm@0 565
andrewm@0 566 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 567 // Send "move" messages for the points that have moved
andrewm@0 568 if(fabsf(newFrame.locs[i] - lastFrame.locs[i]) > 0 /*moveThreshold_*/)
andrewm@0 569 keyboard_.sendMessage("/touchkeys/move", "iiff", noteNumber_, newFrame.ids[i],
andrewm@0 570 newFrame.locs[i], newFrame.horizontal(i), LO_ARGS_END);
andrewm@0 571 if(fabsf(newFrame.sizes[i] - lastFrame.sizes[i]) > 0 /*resizeThreshold_*/)
andrewm@0 572 keyboard_.sendMessage("/touchkeys/resize", "iif", noteNumber_, newFrame.ids[i],
andrewm@0 573 newFrame.sizes[i], LO_ARGS_END);
andrewm@0 574 #endif
andrewm@0 575 }
andrewm@0 576
andrewm@0 577 // If the number of touches has stayed the same, look for multi-finger gestures (pinch and slide)
andrewm@0 578 if(newFrame.count > 1) {
andrewm@0 579 touchMultiFingerGestures(lastFrame, newFrame, timestamp);
andrewm@0 580 }
andrewm@0 581 }
andrewm@0 582 }
andrewm@0 583 else {
andrewm@0 584 // With no previous frame to compare to, assign IDs to each active touch sequentially
andrewm@0 585
andrewm@0 586 newFrame.nextId = 0;
andrewm@0 587 for(int i = 0; i < newFrame.count; i++) {
andrewm@0 588 newFrame.ids[i] = newFrame.nextId++;
andrewm@0 589 touchAdd(newFrame, i, timestamp);
andrewm@0 590 }
andrewm@0 591 }
andrewm@0 592
andrewm@0 593 // Add the new touch
andrewm@0 594 touchBuffer_.insert(newFrame, timestamp);
andrewm@0 595
andrewm@0 596 if(touchIsWaiting_) {
andrewm@0 597 // If this flag was set, we were waiting for a touch to occur before taking further
andrewm@0 598 // action. A timeout will have been scheduled, which we should clear.
andrewm@0 599 keyboard_.unscheduleEvent(this, touchWaitingTimestamp_);
andrewm@0 600
andrewm@0 601 // Send the queued up MIDI/OSC events
andrewm@0 602 midiNoteOnHelper(touchWaitingSource_);
andrewm@0 603 }
andrewm@0 604
andrewm@0 605 // Update GUI if it is available
andrewm@0 606 if(keyboard_.gui() != 0) {
andrewm@0 607 keyboard_.gui()->setTouchForKey(noteNumber_, newFrame);
andrewm@0 608 }
andrewm@0 609 }
andrewm@0 610
andrewm@0 611 // This is called when all touch is removed from a key. Clear out the previous state
andrewm@0 612
andrewm@0 613 void PianoKey::touchOff(timestamp_type timestamp) {
andrewm@0 614 if(!touchIsActive_ || !touchSensorsArePresent_)
andrewm@0 615 return;
andrewm@0 616
andrewm@0 617 keyboard_.tellAllMappingFactoriesTouchEnded(noteNumber_, midiNoteIsOn_, (idleDetector_.idleState() == kIdleDetectorActive),
andrewm@0 618 &touchBuffer_, &positionBuffer_, &positionTracker_);
andrewm@0 619
andrewm@0 620 touchEvents_.clear();
andrewm@0 621
andrewm@0 622 // Create a new event that records the timestamp of the idle event
andrewm@0 623 // and the last frame before it occurred (but only if we have data
andrewm@0 624 // on at least one frame before idle occurred)
andrewm@0 625 if(!touchBuffer_.empty()) {
andrewm@0 626 KeyTouchEvent event = { kTouchEventIdle, timestamp, touchBuffer_.latest() };
andrewm@0 627 touchEvents_.insert(std::pair<int, KeyTouchEvent>(-1, event));
andrewm@0 628 }
andrewm@0 629
andrewm@0 630 // Insert a blank touch frame into the buffer so anyone listening knows the touch has gone off
andrewm@0 631 KeyTouchFrame emptyFrame;
andrewm@0 632 emptyFrame.count = 0;
andrewm@0 633 touchBuffer_.insert(emptyFrame, timestamp);
andrewm@0 634
andrewm@0 635 // Send a message that the touch has ended
andrewm@0 636 touchIsActive_ = false;
andrewm@0 637 touchBuffer_.clear();
andrewm@0 638 keyboard_.sendMessage("/touchkeys/off", "i", noteNumber_, LO_ARGS_END);
andrewm@0 639 // Update GUI if it is available
andrewm@0 640 if(keyboard_.gui() != 0) {
andrewm@0 641 keyboard_.gui()->clearTouchForKey(noteNumber_);
andrewm@0 642 }
andrewm@0 643 }
andrewm@0 644
andrewm@0 645 // This function is called when we time out waiting for a touch on the given note
andrewm@0 646
andrewm@0 647 timestamp_type PianoKey::touchTimedOut() {
andrewm@0 648 //cout << "Touch timed out on note " << noteNumber_ << endl;
andrewm@0 649
andrewm@0 650 // Do all the things we were planning to do once the touch was received.
andrewm@0 651 midiNoteOnHelper(touchWaitingSource_);
andrewm@0 652
andrewm@0 653 return 0;
andrewm@0 654 }
andrewm@0 655
andrewm@0 656 // Recursive function for matching old and new frames of touch locations, each with up to (count) points
andrewm@0 657 //
andrewm@0 658 // Example: old points 1-3, new points A-C
andrewm@0 659 // 1A *2A* 3A
andrewm@0 660 // *1B* 2B 3B
andrewm@0 661 // 1C 2C *3C*
andrewm@0 662
andrewm@0 663 std::pair<float, std::list<int> > PianoKey::touchMatchClosestPoints(const float* oldPoints, const float *newPoints, float count,
andrewm@0 664 int oldIndex, std::set<int>& availableNewPoints, float currentTotalDistance) {
andrewm@0 665 if(availableNewPoints.size() == 0) // Shouldn't happen but prevent an infinite loop
andrewm@0 666 throw new std::exception;
andrewm@0 667
andrewm@0 668 // End case: only one possible point available
andrewm@0 669 if(availableNewPoints.size() == 1) {
andrewm@0 670 int newIndex = *(availableNewPoints.begin());
andrewm@0 671
andrewm@0 672 std::list<int> singleOrder;
andrewm@0 673 singleOrder.push_front(newIndex);
andrewm@0 674
andrewm@0 675 if(oldPoints[oldIndex] < 0.0 || newPoints[newIndex] < 0.0) {
andrewm@0 676 //if(verbose_ >= 4)
andrewm@0 677 // cout << " -> [" << newIndex << "] (" << currentTotalDistance + 100.0 << ")\n";
andrewm@0 678
andrewm@0 679 // Return the distance between the last old point and the only available new point
andrewm@0 680 return std::pair<float, std::list<int> > (currentTotalDistance + 100.0, singleOrder);
andrewm@0 681 }
andrewm@0 682 else {
andrewm@0 683 //if(verbose_ >= 4)
andrewm@0 684 // cout << " -> [" << newIndex << "] (" << currentTotalDistance + (oldPoints[oldIndex] - newPoints[newIndex])*(oldPoints[oldIndex] - newPoints[newIndex]) << ")\n";
andrewm@0 685
andrewm@0 686 // Return the distance between the last old point and the only available new point
andrewm@0 687 return std::pair<float, std::list<int> > (currentTotalDistance + (oldPoints[oldIndex] - newPoints[newIndex])*(oldPoints[oldIndex] - newPoints[newIndex]), singleOrder);
andrewm@0 688 }
andrewm@0 689 }
andrewm@0 690
andrewm@20 691 float minVal = std::numeric_limits<float>::infinity();
andrewm@0 692 std::set<int> newPointsCopy(availableNewPoints);
andrewm@0 693 std::set<int>::iterator it;
andrewm@0 694 std::list<int> order;
andrewm@0 695
andrewm@0 696 // Go through all available new points
andrewm@0 697 for(it = availableNewPoints.begin(); it != availableNewPoints.end(); ++it) {
andrewm@0 698 // Temporarily remove (and test) one point and recursively call ourselves
andrewm@0 699 newPointsCopy.erase(*it);
andrewm@0 700
andrewm@0 701 float dist;
andrewm@0 702 if(newPoints[*it] >= 0.0 && oldPoints[oldIndex] >= 0.0)
andrewm@0 703 dist = (oldPoints[oldIndex] - newPoints[*it])*(oldPoints[oldIndex] - newPoints[*it]);
andrewm@0 704 else
andrewm@0 705 dist = 100.0;
andrewm@0 706
andrewm@0 707 std::pair<float, std::list<int> > rval = touchMatchClosestPoints(oldPoints, newPoints, count, oldIndex + 1, newPointsCopy,
andrewm@0 708 currentTotalDistance + dist);
andrewm@0 709
andrewm@0 710 //if(verbose_ >= 4)
andrewm@0 711 // cout << " from " << *it << " got " << rval.first << endl;
andrewm@0 712
andrewm@0 713 if(rval.first < minVal) {
andrewm@0 714 minVal = rval.first;
andrewm@0 715 order = rval.second;
andrewm@0 716 order.push_front(*it);
andrewm@0 717 }
andrewm@0 718
andrewm@0 719 newPointsCopy.insert(*it);
andrewm@0 720 }
andrewm@0 721
andrewm@0 722 /*if(verbose_ >= 4) {
andrewm@0 723 cout << " -> [";
andrewm@0 724 list<int>::iterator it2;
andrewm@0 725
andrewm@0 726 for(it2 = order.begin(); it2 != order.end(); ++it2)
andrewm@0 727 cout << *it2 << ", ";
andrewm@0 728 cout << "] (" << minVal << ")\n";
andrewm@0 729 }*/
andrewm@0 730
andrewm@0 731 return std::pair<float, std::list<int> >(minVal, order);
andrewm@0 732 }
andrewm@0 733
andrewm@0 734 // A new touch was added from the last frame to this one
andrewm@0 735
andrewm@0 736 void PianoKey::touchAdd(const KeyTouchFrame& frame, int index, timestamp_type timestamp) {
andrewm@0 737 KeyTouchEvent event = { kTouchEventAdd, timestamp, frame };
andrewm@0 738 touchEvents_.insert(std::pair<int, KeyTouchEvent>(frame.ids[index], event));
andrewm@0 739 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 740 keyboard_.sendMessage("/touchkeys/add", "iiifff", noteNumber_, frame.ids[index], frame.count,
andrewm@0 741 frame.locs[index], frame.sizes[index], frame.horizontal(index),
andrewm@0 742 LO_ARGS_END);
andrewm@0 743 #endif
andrewm@0 744 }
andrewm@0 745
andrewm@0 746 // A touch was removed from the last frame. The frame in this case is the last frame containing
andrewm@0 747 // the touch in question (so we can find its ending position later).
andrewm@0 748
andrewm@0 749 void PianoKey::touchRemove(const KeyTouchFrame& frame, int idRemoved, int remainingCount, timestamp_type timestamp) {
andrewm@0 750 KeyTouchEvent event = { kTouchEventRemove, timestamp, frame };
andrewm@0 751 touchEvents_.insert(std::pair<int, KeyTouchEvent>(idRemoved, event));
andrewm@0 752 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 753 keyboard_.sendMessage("/touchkeys/remove", "iii", noteNumber_, idRemoved,
andrewm@0 754 remainingCount, LO_ARGS_END);
andrewm@0 755 #endif
andrewm@0 756 }
andrewm@0 757
andrewm@0 758 // Process multi-finger gestures (pinch and slide) based on previous and current frames
andrewm@0 759
andrewm@0 760 void PianoKey::touchMultiFingerGestures(const KeyTouchFrame& lastFrame, const KeyTouchFrame& newFrame, timestamp_type timestamp) {
andrewm@0 761 #ifdef TOUCHKEYS_LEGACY_OSC
andrewm@0 762 if(newFrame.count == 2 && lastFrame.count == 2) {
andrewm@0 763 float previousCentroid = (lastFrame.locs[0] + lastFrame.locs[1]) / 2.0;
andrewm@0 764 float newCentroid = (newFrame.locs[0] + newFrame.locs[1]) / 2.0;
andrewm@0 765 float previousWidth = lastFrame.locs[1] - lastFrame.locs[0];
andrewm@0 766 float newWidth = newFrame.locs[1] - newFrame.locs[0];
andrewm@0 767
andrewm@0 768 if(fabsf(newWidth - previousWidth) >= 0 /*pinchThreshold_*/) {
andrewm@0 769 keyboard_.sendMessage("/touchkeys/twofinger/pinch", "iiif",
andrewm@0 770 noteNumber_, newFrame.ids[0], newFrame.ids[1], newWidth, LO_ARGS_END);
andrewm@0 771 }
andrewm@0 772 if(fabsf(newCentroid - previousCentroid) >= 0 /*slideThreshold_*/) {
andrewm@0 773 keyboard_.sendMessage("/touchkeys/twofinger/slide", "iiif",
andrewm@0 774 noteNumber_, newFrame.ids[0], newFrame.ids[1], newCentroid, LO_ARGS_END);
andrewm@0 775 }
andrewm@0 776 }
andrewm@0 777 else if(newFrame.count == 3 && lastFrame.count == 3) {
andrewm@0 778 float previousCentroid = (lastFrame.locs[0] + lastFrame.locs[1] + lastFrame.locs[2]) / 3.0;
andrewm@0 779 float newCentroid = (newFrame.locs[0] + newFrame.locs[1] + newFrame.locs[2]) / 3.0;
andrewm@0 780 float previousWidth = lastFrame.locs[2] - lastFrame.locs[0];
andrewm@0 781 float newWidth = newFrame.locs[2] - newFrame.locs[0];
andrewm@0 782
andrewm@0 783 if(fabsf(newWidth - previousWidth) >= 0 /*pinchThreshold_*/) {
andrewm@0 784 keyboard_.sendMessage("/touchkeys/threefinger/pinch", "iiiif",
andrewm@0 785 noteNumber_, newFrame.ids[0], newFrame.ids[1], newFrame.ids[2], newWidth, LO_ARGS_END);
andrewm@0 786 }
andrewm@0 787 if(fabsf(newCentroid - previousCentroid) >= 0 /*slideThreshold_*/) {
andrewm@0 788 keyboard_.sendMessage("/touchkeys/threefinger/slide", "iiiif",
andrewm@0 789 noteNumber_, newFrame.ids[0], newFrame.ids[1], newFrame.ids[2], newCentroid, LO_ARGS_END);
andrewm@0 790 }
andrewm@0 791 }
andrewm@0 792 #endif
andrewm@0 793 }
andrewm@0 794
andrewm@0 795 /*
andrewm@0 796 * State Machine:
andrewm@0 797 *
andrewm@0 798 * Disabled
andrewm@0 799 * --> Unknown: (by user)
andrewm@0 800 * enable triggers on comparator
andrewm@0 801 * Unknown
andrewm@0 802 * --> Idle: activity <= X, pos < Y
andrewm@0 803 * Idle
andrewm@0 804 * --> Active: activity > X
andrewm@0 805 * start looking for maxes and mins
andrewm@0 806 * watch for key down
andrewm@0 807 * Active
andrewm@0 808 * --> Idle: activity <= X, pos < Y
andrewm@0 809 * stop looking for maxes and mins
andrewm@0 810 * --> Max: found maximum position > Z
andrewm@0 811 * calculate features
andrewm@0 812 * Max:
andrewm@0 813 * --> Max: found maximum position greater than before; time from start < T
andrewm@0 814 * recalculate features
andrewm@0 815 * --> Idle: activity <= X, pos < Y
andrewm@0 816 * Down:
andrewm@0 817 * (just a special case of Max?)
andrewm@0 818 * Release:
andrewm@0 819 * (means the user is no longer playing the key, ignore its motion)
andrewm@0 820 * --> Idle: activity <= X, pos < Y
andrewm@0 821 *
andrewm@0 822 */