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