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;
 }