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

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