annotate Source/TouchKeys/TouchkeyOscEmulator.cpp @ 16:61e3c9df4674

Fix bug where TouchKeys standalone mode turns off when mode is changed.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Mon, 25 Nov 2013 21:36:02 +0000
parents 73703cb77094
children dfff66c07936
rev   line source
andrewm@9 1 /*
andrewm@9 2 TouchKeys: multi-touch musical keyboard control software
andrewm@9 3 Copyright (c) 2013 Andrew McPherson
andrewm@9 4
andrewm@9 5 This program is free software: you can redistribute it and/or modify
andrewm@9 6 it under the terms of the GNU General Public License as published by
andrewm@9 7 the Free Software Foundation, either version 3 of the License, or
andrewm@9 8 (at your option) any later version.
andrewm@9 9
andrewm@9 10 This program is distributed in the hope that it will be useful,
andrewm@9 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@9 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@9 13 GNU General Public License for more details.
andrewm@9 14
andrewm@9 15 You should have received a copy of the GNU General Public License
andrewm@9 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
andrewm@9 17
andrewm@9 18 =====================================================================
andrewm@9 19
andrewm@9 20 TouchkeyOscEmulator.cpp: emulates a TouchKeys source using OSC messages
andrewm@9 21 */
andrewm@9 22
andrewm@9 23 #include "TouchkeyOscEmulator.h"
andrewm@9 24 #include <cstdlib>
andrewm@9 25 #include <cstring>
andrewm@9 26
andrewm@9 27 // Main constructor
andrewm@9 28 TouchkeyOscEmulator::TouchkeyOscEmulator(PianoKeyboard& keyboard, OscMessageSource& messageSource)
andrewm@9 29 : keyboard_(keyboard), source_(messageSource), lowestMidiNote_(48)
andrewm@9 30 {
andrewm@9 31 allTouchesOff(false);
andrewm@9 32 setOscController(&source_);
andrewm@9 33 addOscListener("/emulation0*");
andrewm@9 34 }
andrewm@9 35
andrewm@9 36 // Main destructor
andrewm@9 37 TouchkeyOscEmulator::~TouchkeyOscEmulator()
andrewm@9 38 {
andrewm@9 39 }
andrewm@9 40
andrewm@9 41 // Turn off all touches on the virtual TouchKeys keyboard
andrewm@9 42 // send indicates whether to send messages for touches that are disabled
andrewm@9 43 // (false silently resets state)
andrewm@9 44 void TouchkeyOscEmulator::allTouchesOff(bool send) {
andrewm@9 45 for(int i = 0; i < 127; i++) {
andrewm@9 46 if(send && touchFrames_[i].count > 0) {
andrewm@9 47 if(keyboard_.key(i) != 0)
andrewm@9 48 keyboard_.key(i)->touchOff(keyboard_.schedulerCurrentTimestamp());
andrewm@9 49 }
andrewm@9 50 clearTouchData(i);
andrewm@9 51 }
andrewm@9 52 }
andrewm@9 53
andrewm@9 54 // OSC handler method, called when a registered message is received
andrewm@9 55 bool TouchkeyOscEmulator::oscHandlerMethod(const char *path, const char *types,
andrewm@9 56 int numValues, lo_arg **values, void *data) {
andrewm@9 57 // Parse the OSC path looking for particular emulation messages
andrewm@9 58 if(!strncmp(path, "/emulation0/note", 16) && strlen(path) > 16) {
andrewm@9 59 // Emulation0 messages are of this form:
andrewm@9 60 // noteN/M
andrewm@9 61 // noteN/M/z
andrewm@9 62 // Here, N specifies the number of the note (with 0 being the lowest on the controller)
andrewm@9 63 // and M specifies the touch number (starting at 1 for the first touch). The messages ending
andrewm@9 64 // in /z indicate a touch on-off event for that particular touch.
andrewm@9 65 std::string subpath(&path[16]);
andrewm@9 66 int separatorLoc = subpath.find_first_of('/');
andrewm@9 67 if(separatorLoc == std::string::npos || separatorLoc == subpath.length() - 1) {
andrewm@9 68 // Malformed input (no slash or it's the last character): ignore
andrewm@9 69 return false;
andrewm@9 70 }
andrewm@9 71 const char *noteNumberStr = subpath.substr(0, separatorLoc).c_str();
andrewm@9 72 if(noteNumberStr == 0) // Malformed input
andrewm@9 73 return false;
andrewm@9 74 int noteNumber = atoi(noteNumberStr);
andrewm@9 75 if(noteNumber < 0) // Unknown note number
andrewm@9 76 return false;
andrewm@9 77 // Now we have a note number from the OSC path
andrewm@9 78 // Figure out the touch number, starting after the separator
andrewm@9 79 subpath = subpath.substr(separatorLoc + 1);
andrewm@9 80 separatorLoc = subpath.find_first_of("/z");
andrewm@9 81 bool isZ = false;
andrewm@9 82 if(separatorLoc != std::string::npos) {
andrewm@9 83 // This is a z message; drop the last part
andrewm@9 84 isZ = true;
andrewm@9 85 subpath = subpath.substr(0, separatorLoc);
andrewm@9 86 }
andrewm@9 87 int touchNumber = atoi(subpath.c_str());
andrewm@9 88
andrewm@9 89 // We only care about touch numbers 1-3, since we're emulating the capabilities
andrewm@9 90 // of the TouchKeys
andrewm@9 91 if(touchNumber < 1 || touchNumber > 3)
andrewm@9 92 return false;
andrewm@9 93
andrewm@9 94 if(isZ) {
andrewm@9 95 // Z messages indicate touch on/off. We only respond specifically
andrewm@9 96 // to the off message: the on message is implicit in receiving XY data
andrewm@9 97 if(numValues >= 1) {
andrewm@9 98 if(types[0] == 'i') {
andrewm@9 99 if(values[0]->i == 0)
andrewm@9 100 touchOffReceived(noteNumber, touchNumber);
andrewm@9 101 }
andrewm@9 102 else if(types[0] == 'f') {
andrewm@9 103 if(values[0]->f == 0)
andrewm@9 104 touchOffReceived(noteNumber, touchNumber);
andrewm@9 105 }
andrewm@9 106 }
andrewm@9 107 }
andrewm@9 108 else {
andrewm@9 109 // Other messages contain XY data for the given touch, but with Y first as
andrewm@9 110 // the layout is turned sideways (landscape)
andrewm@9 111 if(numValues >= 2) {
andrewm@9 112 if(types[0] == 'f' && types[1] == 'f')
andrewm@9 113 touchReceived(noteNumber, touchNumber, values[1]->f, values[0]->f);
andrewm@9 114 }
andrewm@9 115 }
andrewm@9 116 }
andrewm@9 117
andrewm@9 118 return true;
andrewm@9 119 }
andrewm@9 120
andrewm@9 121 // New touch data point received
andrewm@9 122 void TouchkeyOscEmulator::touchReceived(int key, int touch, float x, float y) {
andrewm@11 123 // std::cout << "Key " << key << " touch " << touch << ": (" << x << ", " << y << ")\n";
andrewm@9 124
andrewm@9 125 int noteNumber = lowestMidiNote_ + key;
andrewm@9 126
andrewm@9 127 // Sanity checks
andrewm@9 128 if(noteNumber < 0 || noteNumber > 127)
andrewm@9 129 return;
andrewm@9 130 if(touch < 1 || touch > 3)
andrewm@9 131 return;
andrewm@9 132 if(y < 0 || y > 1.0 || x > 1.0) // Okay for x < 0
andrewm@9 133 return;
andrewm@9 134
andrewm@9 135 // Find TouchKeys ID associated with this OSC touch ID
andrewm@9 136 int touchId = touchIdAssignments_[noteNumber][touch - 1];
andrewm@9 137 bool updatedExistingTouch = false;
andrewm@9 138
andrewm@9 139 if(touchId >= 0) {
andrewm@9 140 for(int i = 0; i < 3; i++) {
andrewm@9 141 if(touchFrames_[noteNumber].ids[i] == touchId) {
andrewm@9 142 // Found continuing touch
andrewm@11 143 // std::cout << "matched touch " << touch << " to ID " << touchId << std::endl;
andrewm@9 144 updateTouchInFrame(noteNumber, i, x, y);
andrewm@9 145 updatedExistingTouch = true;
andrewm@9 146 }
andrewm@9 147 }
andrewm@9 148 }
andrewm@9 149
andrewm@9 150 if(!updatedExistingTouch) {
andrewm@9 151 // Didn't find an ID for this touch: add it to the frame
andrewm@9 152 // provided there aren't 3 existing touches (shouldn't happen)
andrewm@9 153 if(touchFrames_[noteNumber].count < 3) {
andrewm@11 154 // std::cout << "assigning touch " << touch << " to ID " << touchFrames_[noteNumber].nextId << std::endl;
andrewm@9 155 touchIdAssignments_[noteNumber][touch - 1] = touchFrames_[noteNumber].nextId++;
andrewm@9 156 addTouchToFrame(noteNumber, touchIdAssignments_[noteNumber][touch - 1], x, y);
andrewm@9 157 }
andrewm@9 158 }
andrewm@9 159
andrewm@10 160 if(keyboard_.key(noteNumber) != 0) {
andrewm@10 161 // Pass the frame to the keyboard by copy since PianoKey does its own ID number tracking.
andrewm@10 162 // The important thing is that the Y values are always ordered. If ID tracking later changes
andrewm@10 163 // in PianoKey it's of no consequence here as long as we retain the ability to track OSC
andrewm@10 164 // touch IDs.
andrewm@10 165 KeyTouchFrame copyFrame(touchFrames_[noteNumber]);
andrewm@10 166 keyboard_.key(noteNumber)->touchInsertFrame(copyFrame, keyboard_.schedulerCurrentTimestamp());
andrewm@10 167 }
andrewm@9 168 }
andrewm@9 169
andrewm@9 170 // Touch removed
andrewm@9 171 void TouchkeyOscEmulator::touchOffReceived(int key, int touch) {
andrewm@9 172 int noteNumber = lowestMidiNote_ + key;
andrewm@9 173
andrewm@9 174 // Sanity checks
andrewm@9 175 if(noteNumber < 0 || noteNumber > 127)
andrewm@9 176 return;
andrewm@9 177 if(touch < 1 || touch > 3)
andrewm@9 178 return;
andrewm@9 179
andrewm@9 180 // Find TouchKeys ID associated with this OSC touch ID and
andrewm@9 181 // meanwhile disassociate this touch with any future touch ID
andrewm@9 182 int touchId = touchIdAssignments_[noteNumber][touch - 1];
andrewm@9 183 touchIdAssignments_[noteNumber][touch - 1] = -1;
andrewm@9 184
andrewm@9 185 if(touchId < 0) // No known touch with this ID
andrewm@9 186 return;
andrewm@9 187
andrewm@9 188 for(int i = 0; i < 3; i++) {
andrewm@9 189 if(touchFrames_[noteNumber].ids[i] == touchId) {
andrewm@9 190 // Found the touch: remove it
andrewm@9 191 removeTouchFromFrame(noteNumber, i);
andrewm@9 192
andrewm@9 193 // Anything left?
andrewm@9 194 if(touchFrames_[noteNumber].count == 0) {
andrewm@9 195 clearTouchData(noteNumber);
andrewm@9 196 if(keyboard_.key(noteNumber) != 0)
andrewm@9 197 keyboard_.key(noteNumber)->touchOff(keyboard_.schedulerCurrentTimestamp());
andrewm@9 198 }
andrewm@10 199 else if(keyboard_.key(noteNumber) != 0) {
andrewm@10 200 KeyTouchFrame copyFrame(touchFrames_[noteNumber]);
andrewm@10 201 keyboard_.key(noteNumber)->touchInsertFrame(copyFrame, keyboard_.schedulerCurrentTimestamp());
andrewm@10 202 }
andrewm@9 203 break;
andrewm@9 204 }
andrewm@9 205 }
andrewm@9 206 }
andrewm@9 207
andrewm@9 208 // Clear touch data for a particular key
andrewm@9 209 void TouchkeyOscEmulator::clearTouchData(int i) {
andrewm@9 210 touchFrames_[i].count = 0;
andrewm@9 211 touchFrames_[i].locH = -1.0;
andrewm@9 212 touchFrames_[i].nextId = 0;
andrewm@9 213 for(int j = 0; j < 3; j++) {
andrewm@9 214 touchFrames_[i].ids[j] = -1;
andrewm@9 215 touchFrames_[i].locs[j] = -1.0;
andrewm@9 216 touchFrames_[i].sizes[j] = 0;
andrewm@9 217 touchIdAssignments_[i][j] = -1;
andrewm@9 218 }
andrewm@9 219 int key = i % 12;
andrewm@9 220 if(key == 1 || key == 3 || key == 6 || key == 8 || key == 10)
andrewm@9 221 touchFrames_[i].white = false;
andrewm@9 222 else
andrewm@9 223 touchFrames_[i].white = true;
andrewm@9 224 }
andrewm@9 225
andrewm@9 226 void TouchkeyOscEmulator::removeTouchFromFrame(int note, int index) {
andrewm@9 227 // Remove the touch and collapse the other touches around it down
andrewm@9 228 // so they stay in order.
andrewm@9 229 int lastTouchIndex = touchFrames_[note].count - 1;
andrewm@9 230 if(lastTouchIndex < 0)
andrewm@9 231 return;
andrewm@9 232
andrewm@9 233 for(int i = index; i < lastTouchIndex; i++) {
andrewm@9 234 touchFrames_[note].ids[i] = touchFrames_[note].ids[i+1];
andrewm@9 235 touchFrames_[note].locs[i] = touchFrames_[note].locs[i+1];
andrewm@9 236 touchFrames_[note].sizes[i] = touchFrames_[note].sizes[i+1];
andrewm@9 237 }
andrewm@9 238
andrewm@9 239 touchFrames_[note].ids[lastTouchIndex] = -1;
andrewm@9 240 touchFrames_[note].locs[lastTouchIndex] = -1.0;
andrewm@9 241 touchFrames_[note].sizes[lastTouchIndex] = 0.0;
andrewm@9 242 touchFrames_[note].count--;
andrewm@9 243 }
andrewm@9 244
andrewm@9 245 // Add a touch with the indicated ID to the frame. Its position in the KeyTouchFrame
andrewm@9 246 // order may depend on its y value
andrewm@9 247 void TouchkeyOscEmulator::addTouchToFrame(int note, int touchId, float x, float y) {
andrewm@9 248 if(touchFrames_[note].count >= 3) // Already full?
andrewm@9 249 return;
andrewm@9 250
andrewm@9 251 // Touches are ordered by y position. Look for where this fits in the sequence
andrewm@9 252 // and insert it there.
andrewm@9 253 int insertLoc;
andrewm@9 254 for(insertLoc = 0; insertLoc < touchFrames_[note].count; insertLoc++) {
andrewm@9 255 if(touchFrames_[note].locs[insertLoc] > y)
andrewm@9 256 break;
andrewm@9 257 }
andrewm@9 258
andrewm@9 259 // Move the other touches back to make space
andrewm@9 260 for(int i = touchFrames_[note].count; i > insertLoc; i--) {
andrewm@9 261 touchFrames_[note].ids[i] = touchFrames_[note].ids[i-1];
andrewm@9 262 touchFrames_[note].locs[i] = touchFrames_[note].locs[i-1];
andrewm@9 263 touchFrames_[note].sizes[i] = touchFrames_[note].sizes[i-1];
andrewm@9 264 }
andrewm@9 265
andrewm@9 266 // Add the new touch in the vacant spot
andrewm@9 267 touchFrames_[note].ids[insertLoc] = touchId;
andrewm@9 268 touchFrames_[note].locs[insertLoc] = y;
andrewm@9 269 touchFrames_[note].sizes[insertLoc] = 1.0; // Size is not supported over OSC emulation
andrewm@11 270 touchFrames_[note].count++;
andrewm@9 271
andrewm@9 272 // Lowest touch controls the horizontal position
andrewm@12 273 if(insertLoc == 0) {
andrewm@12 274 // Emulate the partial X sensing of the white TouchKeys
andrewm@12 275 if(touchFrames_[note].locs[0] > kWhiteFrontBackCutoff && touchFrames_[note].white)
andrewm@12 276 touchFrames_[note].locH = -1.0;
andrewm@12 277 else
andrewm@12 278 touchFrames_[note].locH = x;
andrewm@12 279 }
andrewm@9 280 }
andrewm@9 281
andrewm@9 282 // Update the touch at the given index to the new value. Depending on the values, it
andrewm@9 283 // may be necessary to reorder the touches to keep them in order of increasing Y value.
andrewm@9 284 void TouchkeyOscEmulator::updateTouchInFrame(int note, int index, float x, float y) {
andrewm@9 285 // Is it still in proper order?
andrewm@9 286 bool ordered = true;
andrewm@9 287 if(index > 0) {
andrewm@9 288 if(touchFrames_[note].locs[index-1] > y)
andrewm@9 289 ordered = false;
andrewm@9 290 }
andrewm@9 291 if(index < touchFrames_[note].count - 1) {
andrewm@9 292 if(touchFrames_[note].locs[index + 1] < y)
andrewm@9 293 ordered = false;
andrewm@9 294 }
andrewm@9 295
andrewm@9 296 // If out of order, the simplest strategy is to remove the touch and re-add it which
andrewm@9 297 // will keep everything in order. Otherwise just update the information
andrewm@9 298 if(ordered) {
andrewm@9 299 touchFrames_[note].locs[index] = y;
andrewm@12 300 if(index == 0) {
andrewm@12 301 if(y > kWhiteFrontBackCutoff && touchFrames_[note].white)
andrewm@12 302 touchFrames_[note].locH = -1;
andrewm@12 303 else
andrewm@12 304 touchFrames_[note].locH = x;
andrewm@12 305 }
andrewm@9 306 }
andrewm@9 307 else {
andrewm@9 308 int currentId = touchFrames_[note].ids[index];
andrewm@9 309 removeTouchFromFrame(note, index);
andrewm@9 310 addTouchToFrame(note, currentId, x, y);
andrewm@9 311 }
andrewm@9 312 }