Mercurial > hg > touchkeys
changeset 44:73576f49ad1c
Trying out a new method for parsing OSC emulation strings which may be more Windows-friendly. Also added basic support for keyboard divisions on the display.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sat, 23 Aug 2014 23:46:38 +0100 |
parents | fa39caec190b |
children | 518027b4a3eb |
files | Source/Display/KeyboardDisplay.cpp Source/Display/KeyboardDisplay.h Source/Display/KeyboardTesterDisplay.cpp Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.cpp Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h Source/TouchKeys/OscMidiConverter.cpp Source/TouchKeys/TouchkeyOscEmulator.cpp |
diffstat | 7 files changed, 187 insertions(+), 67 deletions(-) [+] |
line wrap: on
line diff
--- a/Source/Display/KeyboardDisplay.cpp Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/Display/KeyboardDisplay.cpp Sat Aug 23 23:46:38 2014 +0100 @@ -74,6 +74,8 @@ clearAllTouches(); for(int i = 0; i < 128; i++) midiActiveForKey_[i] = false; + + recalculateKeyDivisions(); } // Tell the underlying canvas to repaint itself @@ -159,7 +161,7 @@ if(keyShape(key) >= 0) { // White keys: draw and move the frame over for the next key drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_, - /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key]); + /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]); // Analog slider should be centered with respect to the back of the white key if(analogSensorsPresent_ && keyShape(key) >= 0) { float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5; @@ -175,7 +177,7 @@ float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength; glTranslatef(offsetH, offsetV, 0.0); - drawBlackKey(0, 0, /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key]); + drawBlackKey(0, 0, /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]); if(analogSensorsPresent_) { drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing, analogValueIsCalibratedForKey_[key], false, analogValueForKey_[key]); @@ -406,10 +408,35 @@ touchSensingPresentOnKey_[i] = false; } +// Key division methods: indicate that certain keys are divided into more than +// one segment on the display. Useful for certain mappings. + +void KeyboardDisplay::addKeyDivision(void *who, int noteLow, int noteHigh, int divisions) { + KeyDivision div; + + div.noteLow = noteLow; + div.noteHigh = noteHigh; + div.divisions = divisions; + + keyDivisions_[who] = div; + + recalculateKeyDivisions(); + tellCanvasToRepaint(); +} + +void KeyboardDisplay::removeKeyDivision(void *who) { + if(keyDivisions_.count(who) == 0) + return; + keyDivisions_.erase(who); + + recalculateKeyDivisions(); + tellCanvasToRepaint(); +} + // Draw the outline of a white key. Shape ranges from 0-7, giving the type of white key to draw // Coordinates give the lower-left corner of the key -void KeyboardDisplay::drawWhiteKey(float x, float y, int shape, bool first, bool last, bool highlighted) { +void KeyboardDisplay::drawWhiteKey(float x, float y, int shape, bool first, bool last, bool highlighted, int divisions) { // First and last keys will have special geometry since there is no black key below // Figure out the precise geometry in this case... @@ -463,11 +490,31 @@ glVertex2f(x + kWhiteKeyFrontWidth, y); glEnd(); + + if(divisions > 1) { + glColor3f(0.0, 0.0, 0.0); + + for(int i = 1; i < divisions; i++) { + float ratio = (float)i / (float)divisions; + if(ratio > kWhiteFrontBackCutoff) { + glBegin(GL_LINES); + glVertex2d(x + backOffset, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio); + glVertex2d(x + backOffset + backWidth, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio); + glEnd(); + } + else { + glBegin(GL_LINES); + glVertex2d(x, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio); + glVertex2d(x + kWhiteKeyFrontWidth, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio); + glEnd(); + } + } + } } // Draw the outline of a black key, given its lower-left corner -void KeyboardDisplay::drawBlackKey(float x, float y, bool highlighted) { +void KeyboardDisplay::drawBlackKey(float x, float y, bool highlighted, int divisions) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if(highlighted) glColor3f(0.7, 0.0, 0.0); @@ -481,6 +528,16 @@ glVertex2f(x + kBlackKeyWidth, y); glEnd(); + + if(divisions > 1) { + glColor3f(1.0, 1.0, 1.0); + for(int i = 1; i < divisions; i++) { + glBegin(GL_LINES); + glVertex2d(x, y + kBlackKeyLength * (float)i / (float)divisions); + glVertex2d(x + kBlackKeyWidth, y + kBlackKeyLength * (float)i / (float)divisions); + glEnd(); + } + } } // Draw a circle indicating a touch on the white key surface @@ -710,4 +767,30 @@ // If all else fails, assume we're not on any key return -1; +} + +// Convert the map of keyboard segments with divisions into an array ordered by +// note number, to save time during display + +void KeyboardDisplay::recalculateKeyDivisions() { + // By default, 1 division per key + for(int i = 0; i < 128; i++) + keyDivisionsForNote_[i] = 1; + + // Increase divisions wherever we find a relevant mapping + std::map<void*, KeyDivision>::iterator it; + for(it = keyDivisions_.begin(); it != keyDivisions_.end(); ++it) { + int start = it->second.noteLow; + int end = it->second.noteHigh; + int div = it->second.divisions; + + if(start < 0) + start = 0; + if(end > 127) + end = 127; + for(int i = start; i <= end; i++) { + if(div > keyDivisionsForNote_[i]) + keyDivisionsForNote_[i] = div; + } + } } \ No newline at end of file
--- a/Source/Display/KeyboardDisplay.h Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/Display/KeyboardDisplay.h Sat Aug 23 23:46:38 2014 +0100 @@ -91,6 +91,12 @@ float x; float y; } Point; + + typedef struct { + int noteLow; + int noteHigh; + int divisions; + } KeyDivision; public: KeyboardDisplay(); @@ -134,10 +140,14 @@ void setAnalogSensorsPresent(bool present) { analogSensorsPresent_ = present; } void setTouchSensorPresentForKey(int key, bool present); void setTouchSensingEnabled(bool enabled); + + // Key division methods + void addKeyDivision(void *who, int noteLow, int noteHigh, int divisions); + void removeKeyDivision(void *who); protected: - void drawWhiteKey(float x, float y, int shape, bool first, bool last, bool highlighted); - void drawBlackKey(float x, float y, bool highlighted); + void drawWhiteKey(float x, float y, int shape, bool first, bool last, bool highlighted, int divisions); + void drawBlackKey(float x, float y, bool highlighted, int divisions); void drawWhiteTouch(float x, float y, int shape, float touchLocH, float touchLocV, float touchSize); void drawBlackTouch(float x, float y, float touchLocH, float touchLocV, float touchSize); @@ -160,6 +170,9 @@ // Figure out which key (if any) the current point corresponds to int keyForLocation(Point& internalPoint); + + // Convert key division map into a number of divisions for each key + void recalculateKeyDivisions(); protected: OpenGLJuceCanvas *canvas_; // Reference to object which handles rendering @@ -178,7 +191,8 @@ TouchInfo currentTouches_[128]; // Touch data for each key TouchInfo currentTouchesMirror_[128]; // Mirror of the above, used for active display - //std::map<int, TouchInfo> currentTouches_; // Collection of current touch data + std::map<void*, KeyDivision> keyDivisions_; // Division of keys into more than one segment, for certain mappings + int keyDivisionsForNote_[128]; // Number of key divisions per note CriticalSection displayMutex_; // Synchronize access between data and display threads };
--- a/Source/Display/KeyboardTesterDisplay.cpp Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/Display/KeyboardTesterDisplay.cpp Sat Aug 23 23:46:38 2014 +0100 @@ -66,7 +66,7 @@ if(keyShape(key) >= 0) { // White keys: draw and move the frame over for the next key drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, - key == highestMidiNote_, (key == currentlyActiveKey_) || (key == currentHighlightedKey_)); + key == highestMidiNote_, (key == currentlyActiveKey_) || (key == currentHighlightedKey_), 1); // Draw sensor state for this key drawSensorState(key, 0, 0, kWhiteKeyBackWidths[keyShape(key)], kWhiteKeyFrontLength + kWhiteKeyBackLength, true, kWhiteKeyBackOffsets[keyShape(key)]); @@ -79,7 +79,7 @@ float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength; glTranslatef(offsetH, offsetV, 0.0); - drawBlackKey(0, 0, (key == currentlyActiveKey_) || (key == currentHighlightedKey_)); + drawBlackKey(0, 0, (key == currentlyActiveKey_) || (key == currentHighlightedKey_), 1); // Draw sensor state for this key drawSensorState(key, 0, 0, kBlackKeyWidth, kBlackKeyLength, false, 0); glTranslatef(-offsetH, -offsetV, 0.0);
--- a/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.cpp Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.cpp Sat Aug 23 23:46:38 2014 +0100 @@ -23,6 +23,7 @@ */ #include "TouchkeyKeyDivisionMappingFactory.h" +#include "../../Display/KeyboardDisplay.h" // Yarman-24 Turkish microtonal tuning: /* 1/1 RAST C @@ -124,6 +125,18 @@ { //setName("/touchkeys/segmentpitch"); setBendParameters(); + + KeyboardDisplay *display = keyboard_.gui(); + if(display != 0) { + display->addKeyDivision(this, segment.noteRange().first, segment.noteRange().second, numSegmentsPerKey_); + } +} + +TouchkeyKeyDivisionMappingFactory::~TouchkeyKeyDivisionMappingFactory() { + // Remove the divisions from the keys, if this mapping has added them + KeyboardDisplay *display = keyboard_.gui(); + if(display != 0) + display->removeKeyDivision(this); } void TouchkeyKeyDivisionMappingFactory::setName(const string& name) {
--- a/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/Mappings/KeyDivision/TouchkeyKeyDivisionMappingFactory.h Sat Aug 23 23:46:38 2014 +0100 @@ -40,7 +40,7 @@ // ***** Destructor ***** - ~TouchkeyKeyDivisionMappingFactory() {} + ~TouchkeyKeyDivisionMappingFactory(); // ***** Accessors / Modifiers *****
--- a/Source/TouchKeys/OscMidiConverter.cpp Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/TouchKeys/OscMidiConverter.cpp Sat Aug 23 23:46:38 2014 +0100 @@ -23,7 +23,7 @@ #include "OscMidiConverter.h" #include "MidiKeyboardSegment.h" -#define DEBUG_OSC_MIDI_CONVERTER +#undef DEBUG_OSC_MIDI_CONVERTER // Main constructor: set up OSC reception from the keyboard OscMidiConverter::OscMidiConverter(PianoKeyboard& keyboard, MidiKeyboardSegment& segment, int controllerId) :
--- a/Source/TouchKeys/TouchkeyOscEmulator.cpp Sat Aug 23 21:39:46 2014 +0100 +++ b/Source/TouchKeys/TouchkeyOscEmulator.cpp Sat Aug 23 23:46:38 2014 +0100 @@ -22,6 +22,7 @@ #include <cstdlib> #include <cstring> +#include <sstream> #include "TouchkeyOscEmulator.h" // Main constructor @@ -54,67 +55,76 @@ // 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); + try { + // 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; + } + std::stringstream noteNumberSStream(subpath.substr(0, separatorLoc)); + + int noteNumber = 0; + noteNumberSStream >> noteNumber; + + 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); + } + + std::stringstream touchNumberSStream(subpath); + int touchNumber = 0; + touchNumberSStream >> touchNumber; + + // 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 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); } } } - 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); - } - } } - + catch(...) { + return false; + } + return true; }