Mercurial > hg > touchkeys
view Source/Display/KeyboardDisplay.cpp @ 29:f3efbf2984c3
OpenGL workaround for Linux (will get to the bottom of it later) and a bugfix on virtual ports.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Mon, 03 Mar 2014 00:37:35 +0000 |
parents | eef567a60146 |
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/>. ===================================================================== KeyboardDisplay.cpp: displays the keyboard state, including active MIDI notes and current touch position and size. */ #include "KeyboardDisplay.h" #include "OpenGLJuceCanvas.h" #include <iostream> #include <cmath> // Class constants const float KeyboardDisplay::kWhiteKeyFrontWidth = 1.0; const float KeyboardDisplay::kBlackKeyWidth = 0.5; const float KeyboardDisplay::kWhiteKeyFrontLength = 2.3; const float KeyboardDisplay::kWhiteKeyBackLength = 4.1; const float KeyboardDisplay::kBlackKeyLength = 4.0; const float KeyboardDisplay::kInterKeySpacing = 0.1; const float KeyboardDisplay::kAnalogSliderVerticalSpacing = 0.2; const float KeyboardDisplay::kAnalogSliderLength = 3.0; const float KeyboardDisplay::kAnalogSliderWidth = 0.4; const float KeyboardDisplay::kAnalogSliderMinimumValue = -0.2; const float KeyboardDisplay::kAnalogSliderMaximumValue = 1.2; const float KeyboardDisplay::kAnalogSliderZeroLocation = kAnalogSliderLength * (0.0 - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue); const float KeyboardDisplay::kAnalogSliderOneLocation = kAnalogSliderLength * (1.0 - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue); // Individual geometry for C, D, E, F, G, A, B, c' const float KeyboardDisplay::kWhiteKeyBackOffsets[9] = {0, 0.22, 0.42, 0, 0.14, 0.3, 0.44, 0.22, 0}; const float KeyboardDisplay::kWhiteKeyBackWidths[9] = {0.6, 0.58, 0.58, 0.56, 0.56, 0.56, 0.56, 0.58, 1.0}; // Display margins const float KeyboardDisplay::kDisplaySideMargin = 0.4; const float KeyboardDisplay::kDisplayBottomMargin = 0.8; const float KeyboardDisplay::kDisplayTopMargin = 0.8; // Key shape constants const int KeyboardDisplay::kShapeForNote[12] = {0, -1, 1, -1, 2, 3, -1, 4, -1, 5, -1, 6}; const int KeyboardDisplay::kWhiteToChromatic[7] = {0, 2, 4, 5, 7, 9, 11}; const float KeyboardDisplay::kWhiteKeyFrontBackCutoff = (6.5 / 19.0); // Touch constants const float KeyboardDisplay::kDisplayMinTouchSize = 0.1; const float KeyboardDisplay::kDisplayTouchSizeScaler = 0.5; KeyboardDisplay::KeyboardDisplay() : canvas_(0), lowestMidiNote_(0), highestMidiNote_(0), totalDisplayWidth_(1.0), totalDisplayHeight_(1.0), displayPixelWidth_(1.0), displayPixelHeight_(1.0), currentHighlightedKey_(-1), touchSensingEnabled_(false), analogSensorsPresent_(false) { // Initialize OpenGL settings: 2D only //glMatrixMode(GL_PROJECTION); //glDisable(GL_DEPTH_TEST); clearAllTouches(); for(int i = 0; i < 128; i++) midiActiveForKey_[i] = false; } // Tell the underlying canvas to repaint itself void KeyboardDisplay::tellCanvasToRepaint() { if(canvas_ != 0) canvas_->triggerRepaint(); } void KeyboardDisplay::setKeyboardRange(int lowest, int highest) { if(lowest < 0 || highest < 0) return; ScopedLock sl(displayMutex_); lowestMidiNote_ = lowest; if(keyShape(lowest) < 0) // Lowest key must always be a white key for display to lowest++; // render properly highestMidiNote_ = highest; // Recalculate relevant display parameters // Display size is based on the number of white keys int numKeys = 0; for(int i = lowestMidiNote_; i <= highestMidiNote_; i++) { if(keyShape(i) >= 0) numKeys++; if(i >= 0 && i < 128) { analogValueForKey_[i] = 0.0; analogValueIsCalibratedForKey_[i] = false; } } if(numKeys == 0) { return; } // Width: N keys, N-1 interkey spaces, 2 side margins totalDisplayWidth_ = (float)numKeys * (kWhiteKeyFrontWidth + kInterKeySpacing) - kInterKeySpacing + 2.0 * kDisplaySideMargin; // Height: white key height plus top and bottom margins if(analogSensorsPresent_) totalDisplayHeight_ = kDisplayTopMargin + kDisplayBottomMargin + kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing + kAnalogSliderLength; else totalDisplayHeight_ = kDisplayTopMargin + kDisplayBottomMargin + kWhiteKeyFrontLength + kWhiteKeyBackLength; } void KeyboardDisplay::setDisplaySize(float width, float height) { ScopedLock sl(displayMutex_); displayPixelWidth_ = width; displayPixelHeight_ = height; refreshViewport(); } // Render the keyboard display void KeyboardDisplay::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_; //displayPixelWidth_ / displayPixelHeight_; float scaleValue = 2.0 / totalDisplayWidth_; glScalef(scaleValue, scaleValue * invAspectRatio, scaleValue); glTranslatef(-1.0 / scaleValue, -totalDisplayHeight_ / 2.0, 0); glTranslatef(kDisplaySideMargin, kDisplayBottomMargin, 0.0); //ScopedLock sl(displayMutex_); 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 == currentHighlightedKey_) ||*/ midiActiveForKey_[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; drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing, analogValueIsCalibratedForKey_[key], true, analogValueForKey_[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 == currentHighlightedKey_) ||*/ midiActiveForKey_[key]); if(analogSensorsPresent_) { drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing, analogValueIsCalibratedForKey_[key], false, analogValueForKey_[key]); } glTranslatef(-offsetH, -offsetV, 0.0); } } // Restore to the original location we used when drawing the keys glPopMatrix(); // Transfer the touch display data from storage buffer to a local copy we can use for display. // This avoids OpenGL calls happening while the mutex is locked, which stalls the data producer thread displayMutex_.enter(); memcpy(currentTouchesMirror_, currentTouches_, 128*sizeof(TouchInfo)); displayMutex_.exit(); // Draw touches for(int key = lowestMidiNote_; key <= highestMidiNote_; key++) { if(keyShape(key) >= 0) { // Check whether there are any current touches for this key //if(currentTouches_.count(key) > 0) { if(currentTouchesMirror_[key].active) { TouchInfo& t = currentTouchesMirror_[key]; if(t.locV1 >= 0) drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV1, t.size1); if(t.locV2 >= 0) drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV2, t.size2); if(t.locV3 >= 0) drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV3, t.size3); } 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); // Check whether there are any current touches for this key //if(currentTouches_.count(key) > 0) { if(currentTouchesMirror_[key].active) { TouchInfo& t = currentTouchesMirror_[key]; if(t.locV1 >= 0) drawBlackTouch(0, 0, t.locH, t.locV1, t.size1); if(t.locV2 >= 0) drawBlackTouch(0, 0, t.locH, t.locV2, t.size2); if(t.locV3 >= 0) drawBlackTouch(0, 0, t.locH, t.locV3, t.size3); } glTranslatef(-offsetH, -offsetV, 0.0); } } glFlush(); } // Mouse interaction methods void KeyboardDisplay::mouseDown(float x, float y) { Point mousePoint = {x, y}; Point scaledPoint = screenToInternal(mousePoint); currentHighlightedKey_ = keyForLocation(scaledPoint); tellCanvasToRepaint(); } void KeyboardDisplay::mouseDragged(float x, float y) { Point mousePoint = {x, y}; Point scaledPoint = screenToInternal(mousePoint); currentHighlightedKey_ = keyForLocation(scaledPoint); tellCanvasToRepaint(); } void KeyboardDisplay::mouseUp(float x, float y) { //Point mousePoint = {x, y}; //Point scaledPoint = screenToInternal(mousePoint); // When the mouse is released, see if it was over a key. If so, take any action // associated with clicking that key. if(currentHighlightedKey_ != -1) keyClicked(currentHighlightedKey_); currentHighlightedKey_ = -1; tellCanvasToRepaint(); } void KeyboardDisplay::rightMouseDown(float x, float y) { Point mousePoint = {x, y}; Point scaledPoint = screenToInternal(mousePoint); int key = keyForLocation(scaledPoint); if(key != -1) keyRightClicked(key); tellCanvasToRepaint(); } void KeyboardDisplay::rightMouseDragged(float x, float y) { //Point mousePoint = {x, y}; //Point scaledPoint = screenToInternal(mousePoint); } void KeyboardDisplay::rightMouseUp(float x, float y) { //Point mousePoint = {x, y}; //Point scaledPoint = screenToInternal(mousePoint); } void KeyboardDisplay::keyClicked(int key) { } void KeyboardDisplay::keyRightClicked(int key) { } // Insert new touch information for the given key and request a display update. void KeyboardDisplay::setTouchForKey(int key, const KeyTouchFrame& touch) { if(key < lowestMidiNote_ || key > highestMidiNote_) return; ScopedLock sl(displayMutex_); //TouchInfo t = {touch.locH, touch.locs[0], touch.locs[1], touch.locs[2], touch.sizes[0], touch.sizes[1], touch.sizes[2]}; //currentTouches_[key] = t; //currentTouches_[key] = {true, touch.locH, touch.locs[0], touch.locs[1], touch.locs[2], touch.sizes[0], touch.sizes[1], touch.sizes[2]}; currentTouches_[key].active = true; currentTouches_[key].locH = touch.locH; currentTouches_[key].locV1 = touch.locs[0]; currentTouches_[key].locV2 = touch.locs[1]; currentTouches_[key].locV3 = touch.locs[2]; currentTouches_[key].size1 = touch.sizes[0]; currentTouches_[key].size2 = touch.sizes[1]; currentTouches_[key].size3 = touch.sizes[2]; tellCanvasToRepaint(); } // Clear touch information for this key void KeyboardDisplay::clearTouchForKey(int key) { ScopedLock sl(displayMutex_); //currentTouches_.erase(key); currentTouches_[key].active = 0; tellCanvasToRepaint(); } // Clear all current touch information void KeyboardDisplay::clearAllTouches() { ScopedLock sl(displayMutex_); //currentTouches_.clear(); for(int i = 0; i < 128; i++) currentTouches_[i].active = false; tellCanvasToRepaint(); } // Indicate whether the given key is calibrated or not void KeyboardDisplay::setAnalogCalibrationStatusForKey(int key, bool isCalibrated) { if(key < 0 || key > 127) return; analogValueIsCalibratedForKey_[key] = isCalibrated; tellCanvasToRepaint(); } // Set the current value of the analog sensor for the given key. // Whether calibrated or not, the data should be in the range 0.0-1.0 // with a bit of room for deviation outside that range (i.e. for extra key // pressure > 1.0, or for resting key states slightly miscalibrated < 0.0). void KeyboardDisplay::setAnalogValueForKey(int key, float value) { if(key < 0 || key > 127) return; analogValueForKey_[key] = value; tellCanvasToRepaint(); } // Clear all the analog data for all keys void KeyboardDisplay::clearAnalogData() { for(int key = 0; key < 128; key++) { analogValueForKey_[key] = 0.0; } tellCanvasToRepaint(); } void KeyboardDisplay::setMidiActive(int key, bool active) { if(key < 0 || key > 127) return; midiActiveForKey_[key] = active; tellCanvasToRepaint(); } void KeyboardDisplay::clearMidiData() { for(int key = 0; key < 128; key++) midiActiveForKey_[key] = false; tellCanvasToRepaint(); } // Indicate whether a given key has touch sensing capability void KeyboardDisplay::setTouchSensorPresentForKey(int key, bool present) { if(key < 0 || key > 127 || !touchSensingEnabled_) return; touchSensingPresentOnKey_[key] = present; } // Indicate whether touch sensing is active at all on the keyboard. // Clear all key-specific information on whether a touch-sensing key is connected void KeyboardDisplay::setTouchSensingEnabled(bool enabled) { touchSensingEnabled_ = enabled; for(int i = 0; i < 128; i++) touchSensingPresentOnKey_[i] = false; } // 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) { // First and last keys will have special geometry since there is no black key below // Figure out the precise geometry in this case... float backOffset, backWidth; if(first) { backOffset = 0.0; backWidth = kWhiteKeyBackOffsets[shape] + kWhiteKeyBackWidths[shape]; } else if(last) { backOffset = kWhiteKeyBackOffsets[shape]; backWidth = 1.0 - kWhiteKeyBackOffsets[shape]; } else { backOffset = kWhiteKeyBackOffsets[shape]; backWidth = kWhiteKeyBackWidths[shape]; } // First draw white fill as two squares glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if(highlighted) glColor3f(1.0, 0.8, 0.8); else glColor3f(1.0, 1.0, 1.0); glBegin(GL_QUADS); glVertex2f(x, y); glVertex2f(x, y + kWhiteKeyFrontLength); glVertex2f(x + kWhiteKeyFrontWidth, y + kWhiteKeyFrontLength); glVertex2f(x + kWhiteKeyFrontWidth, y); glVertex2f(x + backOffset, y + kWhiteKeyFrontLength); glVertex2f(x + backOffset, y + kWhiteKeyFrontLength + kWhiteKeyBackLength); glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength + kWhiteKeyBackLength); glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength); glEnd(); // Now draw the outline as black line segments glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3f(0.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2f(x, y); glVertex2f(x, y + kWhiteKeyFrontLength); glVertex2f(x + backOffset, y + kWhiteKeyFrontLength); glVertex2f(x + backOffset, y + kWhiteKeyFrontLength + kWhiteKeyBackLength); glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength + kWhiteKeyBackLength); glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength); glVertex2f(x + kWhiteKeyFrontWidth, y + kWhiteKeyFrontLength); glVertex2f(x + kWhiteKeyFrontWidth, y); glEnd(); } // Draw the outline of a black key, given its lower-left corner void KeyboardDisplay::drawBlackKey(float x, float y, bool highlighted) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if(highlighted) glColor3f(0.7, 0.0, 0.0); else glColor3f(0.0, 0.0, 0.0); // Display color black glBegin(GL_POLYGON); glVertex2f(x, y); glVertex2f(x, y + kBlackKeyLength); glVertex2f(x + kBlackKeyWidth, y + kBlackKeyLength); glVertex2f(x + kBlackKeyWidth, y); glEnd(); } // Draw a circle indicating a touch on the white key surface void KeyboardDisplay::drawWhiteTouch(float x, float y, int shape, float touchLocH, float touchLocV, float touchSize) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glColor3f(1.0, 0.0, 1.0); glBegin(GL_POLYGON); if(/*touchLocV < kWhiteKeyFrontBackCutoff && */touchLocH >= 0.0) { // FIXME: find a more permanent solution // Here, the touch is in a location that has both horizontal and vertical information. for(int i = 0; i < 360; i += 5) { glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + touchLocH*kWhiteKeyFrontWidth, y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + kWhiteKeyFrontLength*(touchLocV/kWhiteKeyFrontBackCutoff)); } } else { // The touch is in the back part of the key, or for some reason lacks horizontal information for(int i = 0; i < 360; i += 5) { glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + kWhiteKeyBackOffsets[shape] + kWhiteKeyBackWidths[shape]/2, y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + kWhiteKeyFrontLength + (kWhiteKeyBackLength* ((touchLocV-kWhiteKeyFrontBackCutoff)/(1.0-kWhiteKeyFrontBackCutoff)))); } } glEnd(); } // Draw a circle indicating a touch on the black key surface void KeyboardDisplay::drawBlackTouch(float x, float y, float touchLocH, float touchLocV, float touchSize) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glColor3f(0.0, 1.0, 0.0); glBegin(GL_POLYGON); if(touchLocH < 0.0) touchLocH = 0.5; for(int i = 0; i < 360; i += 5) { glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + touchLocH * kBlackKeyWidth, y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + kBlackKeyLength*touchLocV); } glEnd(); } // Draw a slider bar indicating the current key analog position void KeyboardDisplay::drawAnalogSlider(float x, float y, bool calibrated, bool whiteKey, float value) { // First some gray lines indicating the 0.0 and 1.0 marks glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3f(0.5, 0.5, 0.5); glBegin(GL_POLYGON); glVertex2f(x, y + kAnalogSliderZeroLocation); glVertex2f(x, y + kAnalogSliderOneLocation); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderOneLocation); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderZeroLocation); glEnd(); // Draw a red box at the top for uncalibrated values if(!calibrated) { glColor3f(1.0, 0.0, 0.0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glBegin(GL_POLYGON); glVertex2f(x, y + kAnalogSliderOneLocation); glVertex2f(x, y + kAnalogSliderLength); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderLength); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderOneLocation); glEnd(); } // Next the filled part indicating the specific value (same color as touches), then the outline if(whiteKey) glColor3f(1.0, 0.0, 1.0); else glColor3f(0.0, 1.0, 0.0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glBegin(GL_POLYGON); float locationForValue = kAnalogSliderLength * (value - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue); if(locationForValue < 0.0) locationForValue = 0.0; if(locationForValue > kAnalogSliderLength) locationForValue = kAnalogSliderLength; // Draw solid box from 0.0 to current value glVertex2f(x, y + kAnalogSliderZeroLocation); glVertex2f(x, y + locationForValue); glVertex2f(x + kAnalogSliderWidth, y + locationForValue); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderZeroLocation); glEnd(); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3f(0.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2f(x, y); glVertex2f(x, y + kAnalogSliderLength); glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderLength); glVertex2f(x + kAnalogSliderWidth, y); glEnd(); } void KeyboardDisplay::refreshViewport() { //glViewport(0, 0, displayPixelWidth_, displayPixelHeight_); } // Conversion from internal coordinate space to external pixel values and back // Pixel values go from 0,0 (lower left) to displayPixelWidth_, displayPixelHeight_ (upper right) // Internal values go from -totalDisplayWidth_/2, -totalDisplayHeight_/2 (lower left) // to totalDisplayWidth_/2, totalDisplayHeight_/2 (upper right) // Pixel value in --> OpenGL value out KeyboardDisplay::Point KeyboardDisplay::screenToInternal(Point& inPoint) { Point out; out.x = -totalDisplayWidth_*0.5 + (inPoint.x/displayPixelWidth_) * totalDisplayWidth_; out.y = -totalDisplayHeight_*0.5 + (inPoint.y/displayPixelHeight_) * totalDisplayHeight_; return out; } // OpenGL value in --> Pixel value out KeyboardDisplay::Point KeyboardDisplay::internalToScreen(Point& inPoint) { Point out; out.x = ((inPoint.x + totalDisplayWidth_*0.5)/totalDisplayWidth_) * displayPixelWidth_; out.y = ((inPoint.y + totalDisplayHeight_*0.5)/totalDisplayHeight_) * displayPixelHeight_; return out; } // Given an internal-coordinate representation, return the number of the key that it belongs // in, otherwise return -1 if no key matches. int KeyboardDisplay::keyForLocation(Point& internalPoint) { // std::cout << "(" << internalPoint.x << "," << internalPoint.y << ")\n"; // First, check that the point is within the overall bounding box of the keyboard if(internalPoint.y < -totalDisplayHeight_*0.5 + kDisplayBottomMargin || internalPoint.y > totalDisplayHeight_*0.5 - kDisplayTopMargin) return -1; if(internalPoint.x < -totalDisplayWidth_*0.5 + kDisplaySideMargin || internalPoint.x > totalDisplayWidth_*0.5 - kDisplaySideMargin) return -1; // Now, look for the key region corresponding to this horizontal location // hLoc indicates the relative distance from the beginning of the first key float hLoc = internalPoint.x + totalDisplayWidth_*0.5 - kDisplaySideMargin; if(hLoc < 0.0) return -1; // normalizedHLoc indicates the index of the white key this touch is near. float normalizedHLoc = hLoc / (kWhiteKeyFrontWidth + kInterKeySpacing); // Two relevant regions: front of the white keys, back of the white keys with black keys // Distinguish them by vertical position. int shapeOfBottomKey = keyShape(lowestMidiNote_); // White key index of lowest key int lowestC = (lowestMidiNote_ / 12) * 12; // C below lowest key int whiteKeyNumber = floorf(normalizedHLoc); // Number of white key int whiteOctaveNumber = (whiteKeyNumber + shapeOfBottomKey) / 7; // Octave the key is in int chromaticKeyNumber = 12 * whiteOctaveNumber + kWhiteToChromatic[(whiteKeyNumber + shapeOfBottomKey) % 7]; // Check if we're on the front area of the white keys, and if so, ignore points located in the gaps // between the keys // std::cout << "norm " << (-internalPoint.y + totalDisplayHeight_*0.5) << std::endl; if(-internalPoint.y + totalDisplayHeight_*0.5 - kDisplayBottomMargin <= kWhiteKeyFrontLength) { if(normalizedHLoc - floorf(normalizedHLoc) > kWhiteKeyFrontWidth / (kWhiteKeyFrontWidth + kInterKeySpacing)) return -1; return lowestC + chromaticKeyNumber; } else { // Back of white keys, or black keys int whiteKeyShape = keyShape(chromaticKeyNumber); if(whiteKeyShape < 0) // Shouldn't happen return -1; float locRelativeToLeft = (normalizedHLoc - floorf(normalizedHLoc)) * (kWhiteKeyFrontWidth + kInterKeySpacing); // Check if we are in the back region of the white key. Handle the lowest and highest notes specially since // the white keys are generally wider on account of no adjacent black key. if(lowestC + chromaticKeyNumber == lowestMidiNote_) { if(locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape]) return lowestC + chromaticKeyNumber; } else if(lowestC + chromaticKeyNumber == highestMidiNote_) { if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape]) return lowestC + chromaticKeyNumber; } else if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape] && locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape]) { return lowestC + chromaticKeyNumber; } // By now, we've established that we're not on top of a white key. See if we align to a black key. // Watch the vertical gap between white and black keys if(-internalPoint.y + totalDisplayHeight_*0.5 - kDisplayBottomMargin <= kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength) return -1; // Is there a black key below this white key? if(keyShape(chromaticKeyNumber - 1) < 0) { if(locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] - kInterKeySpacing && lowestC + chromaticKeyNumber > lowestMidiNote_) return lowestC + chromaticKeyNumber - 1; } // Or is there a black key above this white key? if(keyShape(chromaticKeyNumber + 1) < 0) { if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape] + kInterKeySpacing && lowestC + chromaticKeyNumber < highestMidiNote_) return lowestC + chromaticKeyNumber + 1; } } // If all else fails, assume we're not on any key return -1; }