view Source/Display/KeyboardTesterDisplay.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 c8387e4f119f
children 85577160a0d4
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/>.
 
  =====================================================================

  KeyboardTesterDisplay.cpp: A keyboard display for raw data that can be used
  for testing the functionality of individual TouchKeys sensors
*/

#ifdef ENABLE_TOUCHKEYS_SENSOR_TEST

#include "KeyboardTesterDisplay.h"
#include "../MainApplicationController.h"
#include "../TouchKeys/PianoKeyboard.h"

const int KeyboardTesterDisplay::kNumSensorsPerKey = 26;
const int KeyboardTesterDisplay::kDefaultSensorThreshold = 16;

// Constructor
KeyboardTesterDisplay::KeyboardTesterDisplay(MainApplicationController& controller, PianoKeyboard& keyboard)
: controller_(controller), keyboard_(keyboard),
  currentlyActiveKey_(-1), sensorThreshold_(kDefaultSensorThreshold) {
    for(int i = 0; i < 128; i++)
        resetSensorState(i);
    
    setOscController(&keyboard_);
    addOscListener("/touchkeys/rawbytes");
}

// Render the display starting with the underlying keyboard and
// then adding our own display info on top
void KeyboardTesterDisplay::render() {
	if(lowestMidiNote_ == highestMidiNote_)
		return;
	
	// Start with a light gray background
	glClearColor(0.8, 0.8, 0.8, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	glLoadIdentity();
    
	float invAspectRatio = totalDisplayWidth_ / totalDisplayHeight_;
	float scaleValue = 2.0 / totalDisplayWidth_;
	
	glScalef(scaleValue, scaleValue * invAspectRatio, scaleValue);
	glTranslatef(-1.0 / scaleValue, -totalDisplayHeight_ / 2.0, 0);
	glTranslatef(kDisplaySideMargin, kDisplayBottomMargin, 0.0);
	
	glPushMatrix();
    
	// Draw the keys themselves first, with analog values if present, then draw the touches
	for(int key = lowestMidiNote_; key <= highestMidiNote_; key++) {
		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_));
            // Draw sensor state for this key
            drawSensorState(key, 0, 0, kWhiteKeyBackWidths[keyShape(key)], kWhiteKeyFrontLength + kWhiteKeyBackLength,
                            true, kWhiteKeyBackOffsets[keyShape(key)]);
			glTranslatef(kWhiteKeyFrontWidth + kInterKeySpacing, 0, 0);
		}
		else {
			// Black keys: draw and leave the frame in place
			int previousWhiteKeyShape = keyShape(key - 1);
			float offsetH = -1.0 + kWhiteKeyBackOffsets[previousWhiteKeyShape] + kWhiteKeyBackWidths[previousWhiteKeyShape];
			float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength;
            
			glTranslatef(offsetH, offsetV, 0.0);
			drawBlackKey(0, 0, (key == currentlyActiveKey_) || (key == currentHighlightedKey_));
            // Draw sensor state for this key
            drawSensorState(key, 0, 0, kBlackKeyWidth, kBlackKeyLength, false, 0);
			glTranslatef(-offsetH, -offsetV, 0.0);
		}
	}
	
	// Restore to the original location we used when drawing the keys
	glPopMatrix();
    
	needsUpdate_ = false;    
	glFlush();
}

// Called when a given key is clicked by mouse
void KeyboardTesterDisplay::keyClicked(int key) {
    controller_.touchkeySensorTestSetKey(key);
}

// Set the threshold level at which a sensor is considered active
void KeyboardTesterDisplay::setSensorThreshold(int threshold) {
    sensorThreshold_ = threshold;
}

// Set the state of a given sensor on a given key to be on or off,
// based on some externally-computed threshold. Sensors that are on
// will flip the "good" flag to true, which remains set until cleared
// externally.
void KeyboardTesterDisplay::setSensorState(int key, int sensor, bool active) {
    if(key < 0 || key > 127)
        return;
    if(sensor < 0 || sensor >= kNumSensorsPerKey)
        return;
    if(active) {
        keySensorActive_[key] |= (1 << sensor);
        keySensorGood_[key] |= (1 << sensor);
    }
    else
        keySensorActive_[key] &= ~(1 << sensor);
    currentlyActiveKey_ = key;
    needsUpdate_ = true;
    
    if(allSensorsGood(currentlyActiveKey_)) {
        controller_.touchkeySensorTestSetKey(key + 1);
    }
}

// Indicate whether all sensors have shown an active value on this key
bool KeyboardTesterDisplay::allSensorsGood(int key) {
    if(key < 0 || key > 127)
        return false;
    unsigned int mask = (1 << kNumSensorsPerKey) - 1;
    
    return ((keySensorGood_[key] & mask) == mask);
}

// Reset the sensor state to all off
void KeyboardTesterDisplay::resetSensorState(int key) {
    if(key < 0 || key > 127)
        return;
    keySensorGood_[key] = 0;
    keySensorActive_[key] = 0;
    if(currentlyActiveKey_ == key)
        currentlyActiveKey_ = -1;
}

// Draw the given key sensors as being active, good, or inactive
void KeyboardTesterDisplay::drawSensorState(int key, float x, float y, float width, float height, bool white, float whiteOffset) {
    float heightInset = height / ((float)kNumSensorsPerKey * 10);
    
    glPushMatrix();
    glTranslatef(x, y, 0);
    
    if(white) {
        float hSensorWidth = kWhiteKeyFrontWidth * 0.25;
        
        // Draw the first four sensors horizontally for the white keys
        glPushMatrix();
        
        for(int i = 0; i < 4; i++) {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            if(keySensorActive_[key] & (1 << i) && key == currentlyActiveKey_)
                glColor3f(1.0, 1.0, 0.0);   // Sensor active right now = yellow
            else if(keySensorGood_[key] & (1 << i))
                glColor3f(0.0, 1.0, 0.0);   // Sensor has been active (good) = green
            else
                glColor3f(1.0, 0.0, 0.0);   // Sensor has not yet been active = red
            
            glBegin(GL_POLYGON);
            glVertex2f(hSensorWidth * 0.1, heightInset);
            glVertex2f(hSensorWidth * 0.1, 4.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(hSensorWidth * 0.9, 4.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(hSensorWidth * 0.9, heightInset);
            glEnd();
            glTranslatef(hSensorWidth, 0, 0);
        }
        
        glPopMatrix();
        glTranslatef(whiteOffset, 4.0*height / (float)kNumSensorsPerKey, 0);
        
        for(int i = 4; i < kNumSensorsPerKey; i++) {
            // Draw each sensor in sequence: red = never activated; yellow = active; green = previously
            // activated (good)
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            if(keySensorActive_[key] & (1 << i) && key == currentlyActiveKey_)
                glColor3f(1.0, 1.0, 0.0);   // Sensor active right now = yellow
            else if(keySensorGood_[key] & (1 << i))
                glColor3f(0.0, 1.0, 0.0);   // Sensor has been active (good) = green
            else
                glColor3f(1.0, 0.0, 0.0);   // Sensor has not yet been active = red
            
            glBegin(GL_POLYGON);
            glVertex2f(width * 0.1, heightInset);
            glVertex2f(width * 0.1, height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.9, height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.9, heightInset);
            glEnd();
            
            glTranslatef(0, height / (float)kNumSensorsPerKey, 0);
        }
    }
    else { // Black
        // Draw in two rows
        glPushMatrix();
        for(int i = 0; i < kNumSensorsPerKey / 2; i++) {
            // Draw each sensor in sequence: red = never activated; yellow = active; green = previously
            // activated (good)
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            if(keySensorActive_[key] & (1 << i) && key == currentlyActiveKey_)
                glColor3f(1.0, 1.0, 0.0);   // Sensor active right now = yellow
            else if(keySensorGood_[key] & (1 << i))
                glColor3f(0.0, 1.0, 0.0);   // Sensor has been active (good) = green
            else
                glColor3f(1.0, 0.0, 0.0);   // Sensor has not yet been active = red
            
            glBegin(GL_POLYGON);
            glVertex2f(width * 0.55, heightInset);
            glVertex2f(width * 0.55, 2.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.9, 2.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.9, heightInset);
            glEnd();
            
            glTranslatef(0, 2.0*height / (float)kNumSensorsPerKey, 0);
        }
        glPopMatrix();
        glPushMatrix();
        for(int i = kNumSensorsPerKey / 2; i < kNumSensorsPerKey; i++) {
            // Draw each sensor in sequence: red = never activated; yellow = active; green = previously
            // activated (good)
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            if(keySensorActive_[key] & (1 << i) && key == currentlyActiveKey_)
                glColor3f(1.0, 1.0, 0.0);   // Sensor active right now = yellow
            else if(keySensorGood_[key] & (1 << i))
                glColor3f(0.0, 1.0, 0.0);   // Sensor has been active (good) = green
            else
                glColor3f(1.0, 0.0, 0.0);   // Sensor has not yet been active = red
            
            glBegin(GL_POLYGON);
            glVertex2f(width * 0.1, heightInset);
            glVertex2f(width * 0.1, 2.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.45, 2.0*height / (float)kNumSensorsPerKey - heightInset);
            glVertex2f(width * 0.45, heightInset);
            glEnd();
            
            glTranslatef(0, 2.0*height / (float)kNumSensorsPerKey, 0);
        }
        glPopMatrix();
    }
    glPopMatrix();
}

// OSC callback method, for when data comes in
bool KeyboardTesterDisplay::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
    // Look for a blob in value 0 holding the raw data
    if(numValues < 1)
        return false;
    if(types[0] != 'b')
        return false;
    
    // Get OSC blob which holds raw data
    lo_blob blob = values[0];
    int bufferSize = lo_blob_datasize(blob);
    const unsigned char *buffer = (const unsigned char *)lo_blob_dataptr(blob);
    
    // buffer[0] holds the key number from which this data came. Make sure it's sane.
    if(bufferSize == 0)
        return false;
    if(buffer[0] > 127)
        return false;
    
    // The remainder is raw data, with each single byte corresponding to a sensor.
    for(int i = 1; i < bufferSize; i++) {
        bool active = (buffer[i] >= sensorThreshold_);
        setSensorState(buffer[0], i - 1, active);
    }
    
    return true;
}

#endif // ENABLE_TOUCHKEYS_SENSOR_TEST