Mercurial > hg > touchkeys
view Source/TouchKeys/TouchkeyOscEmulator.cpp @ 20:dfff66c07936
Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sun, 09 Feb 2014 18:40:51 +0000 |
parents | 73703cb77094 |
children | 73576f49ad1c |
line wrap: on
line source
/* TouchKeys: multi-touch musical keyboard control software Copyright (c) 2013 Andrew McPherson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ===================================================================== TouchkeyOscEmulator.cpp: emulates a TouchKeys source using OSC messages */ #include <cstdlib> #include <cstring> #include "TouchkeyOscEmulator.h" // Main constructor TouchkeyOscEmulator::TouchkeyOscEmulator(PianoKeyboard& keyboard, OscMessageSource& messageSource) : keyboard_(keyboard), source_(messageSource), lowestMidiNote_(48) { allTouchesOff(false); setOscController(&source_); addOscListener("/emulation0*"); } // Main destructor TouchkeyOscEmulator::~TouchkeyOscEmulator() { } // Turn off all touches on the virtual TouchKeys keyboard // send indicates whether to send messages for touches that are disabled // (false silently resets state) void TouchkeyOscEmulator::allTouchesOff(bool send) { for(int i = 0; i < 127; i++) { if(send && touchFrames_[i].count > 0) { if(keyboard_.key(i) != 0) keyboard_.key(i)->touchOff(keyboard_.schedulerCurrentTimestamp()); } clearTouchData(i); } } // OSC handler method, called when a registered message is received bool TouchkeyOscEmulator::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) { // Parse the OSC path looking for particular emulation messages if(!strncmp(path, "/emulation0/note", 16) && strlen(path) > 16) { // Emulation0 messages are of this form: // noteN/M // noteN/M/z // Here, N specifies the number of the note (with 0 being the lowest on the controller) // and M specifies the touch number (starting at 1 for the first touch). The messages ending // in /z indicate a touch on-off event for that particular touch. std::string subpath(&path[16]); int separatorLoc = subpath.find_first_of('/'); if(separatorLoc == std::string::npos || separatorLoc == subpath.length() - 1) { // Malformed input (no slash or it's the last character): ignore return false; } const char *noteNumberStr = subpath.substr(0, separatorLoc).c_str(); if(noteNumberStr == 0) // Malformed input return false; int noteNumber = atoi(noteNumberStr); if(noteNumber < 0) // Unknown note number return false; // Now we have a note number from the OSC path // Figure out the touch number, starting after the separator subpath = subpath.substr(separatorLoc + 1); separatorLoc = subpath.find_first_of("/z"); bool isZ = false; if(separatorLoc != std::string::npos) { // This is a z message; drop the last part isZ = true; subpath = subpath.substr(0, separatorLoc); } int touchNumber = atoi(subpath.c_str()); // We only care about touch numbers 1-3, since we're emulating the capabilities // of the TouchKeys if(touchNumber < 1 || touchNumber > 3) return false; if(isZ) { // Z messages indicate touch on/off. We only respond specifically // to the off message: the on message is implicit in receiving XY data if(numValues >= 1) { if(types[0] == 'i') { if(values[0]->i == 0) touchOffReceived(noteNumber, touchNumber); } else if(types[0] == 'f') { if(values[0]->f == 0) touchOffReceived(noteNumber, touchNumber); } } } else { // Other messages contain XY data for the given touch, but with Y first as // the layout is turned sideways (landscape) if(numValues >= 2) { if(types[0] == 'f' && types[1] == 'f') touchReceived(noteNumber, touchNumber, values[1]->f, values[0]->f); } } } return true; } // New touch data point received void TouchkeyOscEmulator::touchReceived(int key, int touch, float x, float y) { // std::cout << "Key " << key << " touch " << touch << ": (" << x << ", " << y << ")\n"; int noteNumber = lowestMidiNote_ + key; // Sanity checks if(noteNumber < 0 || noteNumber > 127) return; if(touch < 1 || touch > 3) return; if(y < 0 || y > 1.0 || x > 1.0) // Okay for x < 0 return; // Find TouchKeys ID associated with this OSC touch ID int touchId = touchIdAssignments_[noteNumber][touch - 1]; bool updatedExistingTouch = false; if(touchId >= 0) { for(int i = 0; i < 3; i++) { if(touchFrames_[noteNumber].ids[i] == touchId) { // Found continuing touch // std::cout << "matched touch " << touch << " to ID " << touchId << std::endl; updateTouchInFrame(noteNumber, i, x, y); updatedExistingTouch = true; } } } if(!updatedExistingTouch) { // Didn't find an ID for this touch: add it to the frame // provided there aren't 3 existing touches (shouldn't happen) if(touchFrames_[noteNumber].count < 3) { // std::cout << "assigning touch " << touch << " to ID " << touchFrames_[noteNumber].nextId << std::endl; touchIdAssignments_[noteNumber][touch - 1] = touchFrames_[noteNumber].nextId++; addTouchToFrame(noteNumber, touchIdAssignments_[noteNumber][touch - 1], x, y); } } if(keyboard_.key(noteNumber) != 0) { // Pass the frame to the keyboard by copy since PianoKey does its own ID number tracking. // The important thing is that the Y values are always ordered. If ID tracking later changes // in PianoKey it's of no consequence here as long as we retain the ability to track OSC // touch IDs. KeyTouchFrame copyFrame(touchFrames_[noteNumber]); keyboard_.key(noteNumber)->touchInsertFrame(copyFrame, keyboard_.schedulerCurrentTimestamp()); } } // Touch removed void TouchkeyOscEmulator::touchOffReceived(int key, int touch) { int noteNumber = lowestMidiNote_ + key; // Sanity checks if(noteNumber < 0 || noteNumber > 127) return; if(touch < 1 || touch > 3) return; // Find TouchKeys ID associated with this OSC touch ID and // meanwhile disassociate this touch with any future touch ID int touchId = touchIdAssignments_[noteNumber][touch - 1]; touchIdAssignments_[noteNumber][touch - 1] = -1; if(touchId < 0) // No known touch with this ID return; for(int i = 0; i < 3; i++) { if(touchFrames_[noteNumber].ids[i] == touchId) { // Found the touch: remove it removeTouchFromFrame(noteNumber, i); // Anything left? if(touchFrames_[noteNumber].count == 0) { clearTouchData(noteNumber); if(keyboard_.key(noteNumber) != 0) keyboard_.key(noteNumber)->touchOff(keyboard_.schedulerCurrentTimestamp()); } else if(keyboard_.key(noteNumber) != 0) { KeyTouchFrame copyFrame(touchFrames_[noteNumber]); keyboard_.key(noteNumber)->touchInsertFrame(copyFrame, keyboard_.schedulerCurrentTimestamp()); } break; } } } // Clear touch data for a particular key void TouchkeyOscEmulator::clearTouchData(int i) { touchFrames_[i].count = 0; touchFrames_[i].locH = -1.0; touchFrames_[i].nextId = 0; for(int j = 0; j < 3; j++) { touchFrames_[i].ids[j] = -1; touchFrames_[i].locs[j] = -1.0; touchFrames_[i].sizes[j] = 0; touchIdAssignments_[i][j] = -1; } int key = i % 12; if(key == 1 || key == 3 || key == 6 || key == 8 || key == 10) touchFrames_[i].white = false; else touchFrames_[i].white = true; } void TouchkeyOscEmulator::removeTouchFromFrame(int note, int index) { // Remove the touch and collapse the other touches around it down // so they stay in order. int lastTouchIndex = touchFrames_[note].count - 1; if(lastTouchIndex < 0) return; for(int i = index; i < lastTouchIndex; i++) { touchFrames_[note].ids[i] = touchFrames_[note].ids[i+1]; touchFrames_[note].locs[i] = touchFrames_[note].locs[i+1]; touchFrames_[note].sizes[i] = touchFrames_[note].sizes[i+1]; } touchFrames_[note].ids[lastTouchIndex] = -1; touchFrames_[note].locs[lastTouchIndex] = -1.0; touchFrames_[note].sizes[lastTouchIndex] = 0.0; touchFrames_[note].count--; } // Add a touch with the indicated ID to the frame. Its position in the KeyTouchFrame // order may depend on its y value void TouchkeyOscEmulator::addTouchToFrame(int note, int touchId, float x, float y) { if(touchFrames_[note].count >= 3) // Already full? return; // Touches are ordered by y position. Look for where this fits in the sequence // and insert it there. int insertLoc; for(insertLoc = 0; insertLoc < touchFrames_[note].count; insertLoc++) { if(touchFrames_[note].locs[insertLoc] > y) break; } // Move the other touches back to make space for(int i = touchFrames_[note].count; i > insertLoc; i--) { touchFrames_[note].ids[i] = touchFrames_[note].ids[i-1]; touchFrames_[note].locs[i] = touchFrames_[note].locs[i-1]; touchFrames_[note].sizes[i] = touchFrames_[note].sizes[i-1]; } // Add the new touch in the vacant spot touchFrames_[note].ids[insertLoc] = touchId; touchFrames_[note].locs[insertLoc] = y; touchFrames_[note].sizes[insertLoc] = 1.0; // Size is not supported over OSC emulation touchFrames_[note].count++; // Lowest touch controls the horizontal position if(insertLoc == 0) { // Emulate the partial X sensing of the white TouchKeys if(touchFrames_[note].locs[0] > kWhiteFrontBackCutoff && touchFrames_[note].white) touchFrames_[note].locH = -1.0; else touchFrames_[note].locH = x; } } // Update the touch at the given index to the new value. Depending on the values, it // may be necessary to reorder the touches to keep them in order of increasing Y value. void TouchkeyOscEmulator::updateTouchInFrame(int note, int index, float x, float y) { // Is it still in proper order? bool ordered = true; if(index > 0) { if(touchFrames_[note].locs[index-1] > y) ordered = false; } if(index < touchFrames_[note].count - 1) { if(touchFrames_[note].locs[index + 1] < y) ordered = false; } // If out of order, the simplest strategy is to remove the touch and re-add it which // will keep everything in order. Otherwise just update the information if(ordered) { touchFrames_[note].locs[index] = y; if(index == 0) { if(y > kWhiteFrontBackCutoff && touchFrames_[note].white) touchFrames_[note].locH = -1; else touchFrames_[note].locH = x; } } else { int currentId = touchFrames_[note].ids[index]; removeTouchFromFrame(note, index); addTouchToFrame(note, currentId, x, y); } }