andrewm@0: /*
andrewm@0: TouchKeys: multi-touch musical keyboard control software
andrewm@0: Copyright (c) 2013 Andrew McPherson
andrewm@0:
andrewm@0: This program is free software: you can redistribute it and/or modify
andrewm@0: it under the terms of the GNU General Public License as published by
andrewm@0: the Free Software Foundation, either version 3 of the License, or
andrewm@0: (at your option) any later version.
andrewm@0:
andrewm@0: This program is distributed in the hope that it will be useful,
andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0: GNU General Public License for more details.
andrewm@0:
andrewm@0: You should have received a copy of the GNU General Public License
andrewm@0: along with this program. If not, see .
andrewm@0:
andrewm@0: =====================================================================
andrewm@0:
andrewm@0: KeyboardDisplay.cpp: displays the keyboard state, including active MIDI
andrewm@0: notes and current touch position and size.
andrewm@0: */
andrewm@0:
andrewm@0: #include "KeyboardDisplay.h"
andrewm@27: #include "OpenGLJuceCanvas.h"
andrewm@0: #include
andrewm@0: #include
andrewm@0:
andrewm@0: // Class constants
andrewm@0:
andrewm@0: const float KeyboardDisplay::kWhiteKeyFrontWidth = 1.0;
andrewm@0: const float KeyboardDisplay::kBlackKeyWidth = 0.5;
andrewm@0: const float KeyboardDisplay::kWhiteKeyFrontLength = 2.3;
andrewm@0: const float KeyboardDisplay::kWhiteKeyBackLength = 4.1;
andrewm@0: const float KeyboardDisplay::kBlackKeyLength = 4.0;
andrewm@0: const float KeyboardDisplay::kInterKeySpacing = 0.1;
andrewm@0: const float KeyboardDisplay::kAnalogSliderVerticalSpacing = 0.2;
andrewm@0: const float KeyboardDisplay::kAnalogSliderLength = 3.0;
andrewm@0: const float KeyboardDisplay::kAnalogSliderWidth = 0.4;
andrewm@0: const float KeyboardDisplay::kAnalogSliderMinimumValue = -0.2;
andrewm@0: const float KeyboardDisplay::kAnalogSliderMaximumValue = 1.2;
andrewm@0: const float KeyboardDisplay::kAnalogSliderZeroLocation = kAnalogSliderLength * (0.0 - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue);
andrewm@0: const float KeyboardDisplay::kAnalogSliderOneLocation = kAnalogSliderLength * (1.0 - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue);
andrewm@0:
andrewm@0: // Individual geometry for C, D, E, F, G, A, B, c'
andrewm@0:
andrewm@0: const float KeyboardDisplay::kWhiteKeyBackOffsets[9] = {0, 0.22, 0.42, 0, 0.14, 0.3, 0.44, 0.22, 0};
andrewm@0: const float KeyboardDisplay::kWhiteKeyBackWidths[9] = {0.6, 0.58, 0.58, 0.56, 0.56, 0.56, 0.56, 0.58, 1.0};
andrewm@0:
andrewm@0: // Display margins
andrewm@0:
andrewm@0: const float KeyboardDisplay::kDisplaySideMargin = 0.4;
andrewm@0: const float KeyboardDisplay::kDisplayBottomMargin = 0.8;
andrewm@0: const float KeyboardDisplay::kDisplayTopMargin = 0.8;
andrewm@0:
andrewm@0: // Key shape constants
andrewm@0:
andrewm@0: const int KeyboardDisplay::kShapeForNote[12] = {0, -1, 1, -1, 2, 3, -1, 4, -1, 5, -1, 6};
andrewm@0: const int KeyboardDisplay::kWhiteToChromatic[7] = {0, 2, 4, 5, 7, 9, 11};
andrewm@0: const float KeyboardDisplay::kWhiteKeyFrontBackCutoff = (6.5 / 19.0);
andrewm@0:
andrewm@0: // Touch constants
andrewm@0: const float KeyboardDisplay::kDisplayMinTouchSize = 0.1;
andrewm@0: const float KeyboardDisplay::kDisplayTouchSizeScaler = 0.5;
andrewm@0:
andrewm@27: KeyboardDisplay::KeyboardDisplay() : canvas_(0), lowestMidiNote_(0), highestMidiNote_(0),
andrewm@0: totalDisplayWidth_(1.0), totalDisplayHeight_(1.0), displayPixelWidth_(1.0), displayPixelHeight_(1.0),
andrewm@27: currentHighlightedKey_(-1), touchSensingEnabled_(false), analogSensorsPresent_(false) {
andrewm@0: // Initialize OpenGL settings: 2D only
andrewm@0:
andrewm@0: //glMatrixMode(GL_PROJECTION);
andrewm@0: //glDisable(GL_DEPTH_TEST);
andrewm@0:
andrewm@0: clearAllTouches();
andrewm@0: for(int i = 0; i < 128; i++)
andrewm@0: midiActiveForKey_[i] = false;
andrewm@44:
andrewm@44: recalculateKeyDivisions();
andrewm@0: }
andrewm@0:
andrewm@27: // Tell the underlying canvas to repaint itself
andrewm@27: void KeyboardDisplay::tellCanvasToRepaint() {
andrewm@27: if(canvas_ != 0)
andrewm@27: canvas_->triggerRepaint();
andrewm@27: }
andrewm@27:
andrewm@0: void KeyboardDisplay::setKeyboardRange(int lowest, int highest) {
andrewm@0: if(lowest < 0 || highest < 0)
andrewm@0: return;
andrewm@0:
andrewm@0: ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: lowestMidiNote_ = lowest;
andrewm@0: if(keyShape(lowest) < 0) // Lowest key must always be a white key for display to
andrewm@0: lowest++; // render properly
andrewm@0:
andrewm@0: highestMidiNote_ = highest;
andrewm@0:
andrewm@0: // Recalculate relevant display parameters
andrewm@0: // Display size is based on the number of white keys
andrewm@0:
andrewm@0: int numKeys = 0;
andrewm@0: for(int i = lowestMidiNote_; i <= highestMidiNote_; i++) {
andrewm@0: if(keyShape(i) >= 0)
andrewm@0: numKeys++;
andrewm@0: if(i >= 0 && i < 128) {
andrewm@0: analogValueForKey_[i] = 0.0;
andrewm@0: analogValueIsCalibratedForKey_[i] = false;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: if(numKeys == 0) {
andrewm@0: return;
andrewm@0: }
andrewm@0:
andrewm@0: // Width: N keys, N-1 interkey spaces, 2 side margins
andrewm@0: totalDisplayWidth_ = (float)numKeys * (kWhiteKeyFrontWidth + kInterKeySpacing)
andrewm@0: - kInterKeySpacing + 2.0 * kDisplaySideMargin;
andrewm@0:
andrewm@0: // Height: white key height plus top and bottom margins
andrewm@0: if(analogSensorsPresent_)
andrewm@0: totalDisplayHeight_ = kDisplayTopMargin + kDisplayBottomMargin + kWhiteKeyFrontLength + kWhiteKeyBackLength
andrewm@0: + kAnalogSliderVerticalSpacing + kAnalogSliderLength;
andrewm@0: else
andrewm@0: totalDisplayHeight_ = kDisplayTopMargin + kDisplayBottomMargin + kWhiteKeyFrontLength + kWhiteKeyBackLength;
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::setDisplaySize(float width, float height) {
andrewm@0: ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: displayPixelWidth_ = width;
andrewm@0: displayPixelHeight_ = height;
andrewm@0: refreshViewport();
andrewm@0: }
andrewm@0:
andrewm@0:
andrewm@0: // Render the keyboard display
andrewm@0:
andrewm@0: void KeyboardDisplay::render() {
andrewm@0: if(lowestMidiNote_ == highestMidiNote_)
andrewm@0: return;
andrewm@0:
andrewm@0: // Start with a light gray background
andrewm@0: glClearColor(0.8, 0.8, 0.8, 1.0);
andrewm@0: glClear(GL_COLOR_BUFFER_BIT);
andrewm@0: glLoadIdentity();
andrewm@0:
andrewm@0: float invAspectRatio = totalDisplayWidth_ / totalDisplayHeight_; //displayPixelWidth_ / displayPixelHeight_;
andrewm@0: float scaleValue = 2.0 / totalDisplayWidth_;
andrewm@0:
andrewm@0: glScalef(scaleValue, scaleValue * invAspectRatio, scaleValue);
andrewm@0: glTranslatef(-1.0 / scaleValue, -totalDisplayHeight_ / 2.0, 0);
andrewm@0: glTranslatef(kDisplaySideMargin, kDisplayBottomMargin, 0.0);
andrewm@0:
andrewm@0: //ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: glPushMatrix();
andrewm@0:
andrewm@0: // Draw the keys themselves first, with analog values if present, then draw the touches
andrewm@0: for(int key = lowestMidiNote_; key <= highestMidiNote_; key++) {
andrewm@0: if(keyShape(key) >= 0) {
andrewm@46: if(key < 0 || key > 127) {
andrewm@46: // Safety check
andrewm@46: drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_,
andrewm@46: false, 1);
andrewm@46: // Analog slider should be centered with respect to the back of the white key
andrewm@46: if(analogSensorsPresent_ && keyShape(key) >= 0) {
andrewm@46: float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5;
andrewm@46: drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing,
andrewm@46: false, true, 0);
andrewm@46: }
andrewm@46: }
andrewm@46: else {
andrewm@46: // White keys: draw and move the frame over for the next key
andrewm@46: drawWhiteKey(0, 0, keyShape(key), key == lowestMidiNote_, key == highestMidiNote_,
andrewm@46: /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
andrewm@46: // Analog slider should be centered with respect to the back of the white key
andrewm@46: if(analogSensorsPresent_ && keyShape(key) >= 0) {
andrewm@46: float sliderOffset = kWhiteKeyBackOffsets[keyShape(key)] + (kWhiteKeyBackWidths[keyShape(key)] - kAnalogSliderWidth) * 0.5;
andrewm@46: drawAnalogSlider(sliderOffset, kWhiteKeyFrontLength + kWhiteKeyBackLength + kAnalogSliderVerticalSpacing,
andrewm@46: analogValueIsCalibratedForKey_[key], true, analogValueForKey_[key]);
andrewm@46: }
andrewm@0: }
andrewm@0: glTranslatef(kWhiteKeyFrontWidth + kInterKeySpacing, 0, 0);
andrewm@0: }
andrewm@0: else {
andrewm@0: // Black keys: draw and leave the frame in place
andrewm@0: int previousWhiteKeyShape = keyShape(key - 1);
andrewm@0: float offsetH = -1.0 + kWhiteKeyBackOffsets[previousWhiteKeyShape] + kWhiteKeyBackWidths[previousWhiteKeyShape];
andrewm@0: float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength;
andrewm@0:
andrewm@0: glTranslatef(offsetH, offsetV, 0.0);
andrewm@46:
andrewm@46: if(key < 0 || key > 127) {
andrewm@46: // Safety check
andrewm@46: drawBlackKey(0, 0, false, 1);
andrewm@46: if(analogSensorsPresent_) {
andrewm@46: drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing,
andrewm@46: false, false, 0);
andrewm@46: }
andrewm@46: }
andrewm@46: else {
andrewm@46: drawBlackKey(0, 0, /*(key == currentHighlightedKey_) ||*/ midiActiveForKey_[key], keyDivisionsForNote_[key]);
andrewm@46: if(analogSensorsPresent_) {
andrewm@46: drawAnalogSlider((kBlackKeyWidth - kAnalogSliderWidth) * 0.5, kBlackKeyLength + kAnalogSliderVerticalSpacing,
andrewm@46: analogValueIsCalibratedForKey_[key], false, analogValueForKey_[key]);
andrewm@46: }
andrewm@0: }
andrewm@0: glTranslatef(-offsetH, -offsetV, 0.0);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Restore to the original location we used when drawing the keys
andrewm@0: glPopMatrix();
andrewm@0:
andrewm@0: // Transfer the touch display data from storage buffer to a local copy we can use for display.
andrewm@0: // This avoids OpenGL calls happening while the mutex is locked, which stalls the data producer thread
andrewm@0: displayMutex_.enter();
andrewm@0: memcpy(currentTouchesMirror_, currentTouches_, 128*sizeof(TouchInfo));
andrewm@0: displayMutex_.exit();
andrewm@0:
andrewm@0: // Draw touches
andrewm@0: for(int key = lowestMidiNote_; key <= highestMidiNote_; key++) {
andrewm@0: if(keyShape(key) >= 0) {
andrewm@0: // Check whether there are any current touches for this key
andrewm@0: //if(currentTouches_.count(key) > 0) {
andrewm@0: if(currentTouchesMirror_[key].active) {
andrewm@0: TouchInfo& t = currentTouchesMirror_[key];
andrewm@0:
andrewm@0: if(t.locV1 >= 0)
andrewm@0: drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV1, t.size1);
andrewm@0: if(t.locV2 >= 0)
andrewm@0: drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV2, t.size2);
andrewm@0: if(t.locV3 >= 0)
andrewm@0: drawWhiteTouch(0, 0, keyShape(key), t.locH, t.locV3, t.size3);
andrewm@0: }
andrewm@0:
andrewm@0: glTranslatef(kWhiteKeyFrontWidth + kInterKeySpacing, 0, 0);
andrewm@0: }
andrewm@0: else {
andrewm@0: // Black keys: draw and leave the frame in place
andrewm@0: int previousWhiteKeyShape = keyShape(key - 1);
andrewm@0: float offsetH = -1.0 + kWhiteKeyBackOffsets[previousWhiteKeyShape] + kWhiteKeyBackWidths[previousWhiteKeyShape];
andrewm@0: float offsetV = kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength;
andrewm@0:
andrewm@0: glTranslatef(offsetH, offsetV, 0.0);
andrewm@0:
andrewm@0: // Check whether there are any current touches for this key
andrewm@0: //if(currentTouches_.count(key) > 0) {
andrewm@0: if(currentTouchesMirror_[key].active) {
andrewm@0: TouchInfo& t = currentTouchesMirror_[key];
andrewm@0:
andrewm@0: if(t.locV1 >= 0)
andrewm@0: drawBlackTouch(0, 0, t.locH, t.locV1, t.size1);
andrewm@0: if(t.locV2 >= 0)
andrewm@0: drawBlackTouch(0, 0, t.locH, t.locV2, t.size2);
andrewm@0: if(t.locV3 >= 0)
andrewm@0: drawBlackTouch(0, 0, t.locH, t.locV3, t.size3);
andrewm@0: }
andrewm@0:
andrewm@0: glTranslatef(-offsetH, -offsetV, 0.0);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: glFlush();
andrewm@0: }
andrewm@0:
andrewm@0: // Mouse interaction methods
andrewm@0:
andrewm@0: void KeyboardDisplay::mouseDown(float x, float y) {
andrewm@0: Point mousePoint = {x, y};
andrewm@0: Point scaledPoint = screenToInternal(mousePoint);
andrewm@0:
andrewm@0: currentHighlightedKey_ = keyForLocation(scaledPoint);
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::mouseDragged(float x, float y) {
andrewm@0: Point mousePoint = {x, y};
andrewm@0: Point scaledPoint = screenToInternal(mousePoint);
andrewm@0:
andrewm@0: currentHighlightedKey_ = keyForLocation(scaledPoint);
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::mouseUp(float x, float y) {
andrewm@0: //Point mousePoint = {x, y};
andrewm@0: //Point scaledPoint = screenToInternal(mousePoint);
andrewm@0:
andrewm@0: // When the mouse is released, see if it was over a key. If so, take any action
andrewm@0: // associated with clicking that key.
andrewm@0:
andrewm@0: if(currentHighlightedKey_ != -1)
andrewm@0: keyClicked(currentHighlightedKey_);
andrewm@0:
andrewm@18: currentHighlightedKey_ = -1;
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::rightMouseDown(float x, float y) {
andrewm@0: Point mousePoint = {x, y};
andrewm@0: Point scaledPoint = screenToInternal(mousePoint);
andrewm@0:
andrewm@0: int key = keyForLocation(scaledPoint);
andrewm@0: if(key != -1)
andrewm@0: keyRightClicked(key);
andrewm@0:
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::rightMouseDragged(float x, float y) {
andrewm@0: //Point mousePoint = {x, y};
andrewm@0: //Point scaledPoint = screenToInternal(mousePoint);
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::rightMouseUp(float x, float y) {
andrewm@0: //Point mousePoint = {x, y};
andrewm@0: //Point scaledPoint = screenToInternal(mousePoint);
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::keyClicked(int key) {
andrewm@0:
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::keyRightClicked(int key) {
andrewm@0:
andrewm@0: }
andrewm@0:
andrewm@0: // Insert new touch information for the given key and request a display update.
andrewm@0:
andrewm@0: void KeyboardDisplay::setTouchForKey(int key, const KeyTouchFrame& touch) {
andrewm@0: if(key < lowestMidiNote_ || key > highestMidiNote_)
andrewm@0: return;
andrewm@0:
andrewm@0: ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: //TouchInfo t = {touch.locH, touch.locs[0], touch.locs[1], touch.locs[2], touch.sizes[0], touch.sizes[1], touch.sizes[2]};
andrewm@0: //currentTouches_[key] = t;
andrewm@20: //currentTouches_[key] = {true, touch.locH, touch.locs[0], touch.locs[1], touch.locs[2], touch.sizes[0], touch.sizes[1], touch.sizes[2]};
andrewm@20: currentTouches_[key].active = true;
andrewm@20: currentTouches_[key].locH = touch.locH;
andrewm@20: currentTouches_[key].locV1 = touch.locs[0];
andrewm@20: currentTouches_[key].locV2 = touch.locs[1];
andrewm@20: currentTouches_[key].locV3 = touch.locs[2];
andrewm@20: currentTouches_[key].size1 = touch.sizes[0];
andrewm@20: currentTouches_[key].size2 = touch.sizes[1];
andrewm@20: currentTouches_[key].size3 = touch.sizes[2];
andrewm@20:
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Clear touch information for this key
andrewm@0:
andrewm@0: void KeyboardDisplay::clearTouchForKey(int key) {
andrewm@0: ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: //currentTouches_.erase(key);
andrewm@0: currentTouches_[key].active = 0;
andrewm@0:
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Clear all current touch information
andrewm@0:
andrewm@0: void KeyboardDisplay::clearAllTouches() {
andrewm@0: ScopedLock sl(displayMutex_);
andrewm@0:
andrewm@0: //currentTouches_.clear();
andrewm@0: for(int i = 0; i < 128; i++)
andrewm@0: currentTouches_[i].active = false;
andrewm@0:
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Indicate whether the given key is calibrated or not
andrewm@0:
andrewm@0: void KeyboardDisplay::setAnalogCalibrationStatusForKey(int key, bool isCalibrated) {
andrewm@0: if(key < 0 || key > 127)
andrewm@0: return;
andrewm@0: analogValueIsCalibratedForKey_[key] = isCalibrated;
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Set the current value of the analog sensor for the given key.
andrewm@0: // Whether calibrated or not, the data should be in the range 0.0-1.0
andrewm@0: // with a bit of room for deviation outside that range (i.e. for extra key
andrewm@0: // pressure > 1.0, or for resting key states slightly miscalibrated < 0.0).
andrewm@0:
andrewm@0: void KeyboardDisplay::setAnalogValueForKey(int key, float value) {
andrewm@0: if(key < 0 || key > 127)
andrewm@0: return;
andrewm@0: analogValueForKey_[key] = value;
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Clear all the analog data for all keys
andrewm@0: void KeyboardDisplay::clearAnalogData() {
andrewm@0: for(int key = 0; key < 128; key++) {
andrewm@0: analogValueForKey_[key] = 0.0;
andrewm@0: }
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::setMidiActive(int key, bool active) {
andrewm@0: if(key < 0 || key > 127)
andrewm@0: return;
andrewm@0: midiActiveForKey_[key] = active;
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::clearMidiData() {
andrewm@0: for(int key = 0; key < 128; key++)
andrewm@0: midiActiveForKey_[key] = false;
andrewm@27: tellCanvasToRepaint();
andrewm@0: }
andrewm@0:
andrewm@0: // Indicate whether a given key has touch sensing capability
andrewm@0:
andrewm@0: void KeyboardDisplay::setTouchSensorPresentForKey(int key, bool present) {
andrewm@0: if(key < 0 || key > 127 || !touchSensingEnabled_)
andrewm@0: return;
andrewm@0: touchSensingPresentOnKey_[key] = present;
andrewm@0: }
andrewm@0:
andrewm@0: // Indicate whether touch sensing is active at all on the keyboard.
andrewm@0: // Clear all key-specific information on whether a touch-sensing key is connected
andrewm@0:
andrewm@0: void KeyboardDisplay::setTouchSensingEnabled(bool enabled) {
andrewm@0: touchSensingEnabled_ = enabled;
andrewm@0:
andrewm@0: for(int i = 0; i < 128; i++)
andrewm@0: touchSensingPresentOnKey_[i] = false;
andrewm@0: }
andrewm@0:
andrewm@44: // Key division methods: indicate that certain keys are divided into more than
andrewm@44: // one segment on the display. Useful for certain mappings.
andrewm@44:
andrewm@44: void KeyboardDisplay::addKeyDivision(void *who, int noteLow, int noteHigh, int divisions) {
andrewm@44: KeyDivision div;
andrewm@44:
andrewm@44: div.noteLow = noteLow;
andrewm@44: div.noteHigh = noteHigh;
andrewm@44: div.divisions = divisions;
andrewm@44:
andrewm@44: keyDivisions_[who] = div;
andrewm@44:
andrewm@44: recalculateKeyDivisions();
andrewm@44: tellCanvasToRepaint();
andrewm@44: }
andrewm@44:
andrewm@44: void KeyboardDisplay::removeKeyDivision(void *who) {
andrewm@44: if(keyDivisions_.count(who) == 0)
andrewm@44: return;
andrewm@44: keyDivisions_.erase(who);
andrewm@44:
andrewm@44: recalculateKeyDivisions();
andrewm@44: tellCanvasToRepaint();
andrewm@44: }
andrewm@44:
andrewm@0: // Draw the outline of a white key. Shape ranges from 0-7, giving the type of white key to draw
andrewm@0: // Coordinates give the lower-left corner of the key
andrewm@0:
andrewm@44: void KeyboardDisplay::drawWhiteKey(float x, float y, int shape, bool first, bool last, bool highlighted, int divisions) {
andrewm@0: // First and last keys will have special geometry since there is no black key below
andrewm@0: // Figure out the precise geometry in this case...
andrewm@0:
andrewm@0: float backOffset, backWidth;
andrewm@0:
andrewm@0: if(first) {
andrewm@0: backOffset = 0.0;
andrewm@0: backWidth = kWhiteKeyBackOffsets[shape] + kWhiteKeyBackWidths[shape];
andrewm@0: }
andrewm@0: else if(last) {
andrewm@0: backOffset = kWhiteKeyBackOffsets[shape];
andrewm@0: backWidth = 1.0 - kWhiteKeyBackOffsets[shape];
andrewm@0: }
andrewm@0: else {
andrewm@0: backOffset = kWhiteKeyBackOffsets[shape];
andrewm@0: backWidth = kWhiteKeyBackWidths[shape];
andrewm@0: }
andrewm@0:
andrewm@0: // First draw white fill as two squares
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: if(highlighted)
andrewm@0: glColor3f(1.0, 0.8, 0.8);
andrewm@0: else
andrewm@0: glColor3f(1.0, 1.0, 1.0);
andrewm@0: glBegin(GL_QUADS);
andrewm@0:
andrewm@0: glVertex2f(x, y);
andrewm@0: glVertex2f(x, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + kWhiteKeyFrontWidth, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + kWhiteKeyFrontWidth, y);
andrewm@0:
andrewm@0: glVertex2f(x + backOffset, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + backOffset, y + kWhiteKeyFrontLength + kWhiteKeyBackLength);
andrewm@0: glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength + kWhiteKeyBackLength);
andrewm@0: glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength);
andrewm@0:
andrewm@0: glEnd();
andrewm@0:
andrewm@0: // Now draw the outline as black line segments
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
andrewm@0: glColor3f(0.0, 0.0, 0.0);
andrewm@0: glBegin(GL_POLYGON);
andrewm@0:
andrewm@0: glVertex2f(x, y);
andrewm@0: glVertex2f(x, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + backOffset, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + backOffset, y + kWhiteKeyFrontLength + kWhiteKeyBackLength);
andrewm@0: glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength + kWhiteKeyBackLength);
andrewm@0: glVertex2f(x + backOffset + backWidth, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + kWhiteKeyFrontWidth, y + kWhiteKeyFrontLength);
andrewm@0: glVertex2f(x + kWhiteKeyFrontWidth, y);
andrewm@0:
andrewm@0: glEnd();
andrewm@44:
andrewm@44: if(divisions > 1) {
andrewm@44: glColor3f(0.0, 0.0, 0.0);
andrewm@44:
andrewm@44: for(int i = 1; i < divisions; i++) {
andrewm@44: float ratio = (float)i / (float)divisions;
andrewm@44: if(ratio > kWhiteFrontBackCutoff) {
andrewm@44: glBegin(GL_LINES);
andrewm@44: glVertex2d(x + backOffset, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio);
andrewm@44: glVertex2d(x + backOffset + backWidth, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio);
andrewm@44: glEnd();
andrewm@44: }
andrewm@44: else {
andrewm@44: glBegin(GL_LINES);
andrewm@44: glVertex2d(x, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio);
andrewm@44: glVertex2d(x + kWhiteKeyFrontWidth, y + (kWhiteKeyFrontLength + kWhiteKeyBackLength) * ratio);
andrewm@44: glEnd();
andrewm@44: }
andrewm@44: }
andrewm@44: }
andrewm@0: }
andrewm@0:
andrewm@0: // Draw the outline of a black key, given its lower-left corner
andrewm@0:
andrewm@44: void KeyboardDisplay::drawBlackKey(float x, float y, bool highlighted, int divisions) {
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: if(highlighted)
andrewm@0: glColor3f(0.7, 0.0, 0.0);
andrewm@0: else
andrewm@0: glColor3f(0.0, 0.0, 0.0); // Display color black
andrewm@0: glBegin(GL_POLYGON);
andrewm@0:
andrewm@0: glVertex2f(x, y);
andrewm@0: glVertex2f(x, y + kBlackKeyLength);
andrewm@0: glVertex2f(x + kBlackKeyWidth, y + kBlackKeyLength);
andrewm@0: glVertex2f(x + kBlackKeyWidth, y);
andrewm@0:
andrewm@0: glEnd();
andrewm@44:
andrewm@44: if(divisions > 1) {
andrewm@44: glColor3f(1.0, 1.0, 1.0);
andrewm@44: for(int i = 1; i < divisions; i++) {
andrewm@44: glBegin(GL_LINES);
andrewm@44: glVertex2d(x, y + kBlackKeyLength * (float)i / (float)divisions);
andrewm@44: glVertex2d(x + kBlackKeyWidth, y + kBlackKeyLength * (float)i / (float)divisions);
andrewm@44: glEnd();
andrewm@44: }
andrewm@44: }
andrewm@0: }
andrewm@0:
andrewm@0: // Draw a circle indicating a touch on the white key surface
andrewm@0:
andrewm@0: void KeyboardDisplay::drawWhiteTouch(float x, float y, int shape, float touchLocH, float touchLocV, float touchSize) {
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: glColor3f(1.0, 0.0, 1.0);
andrewm@0:
andrewm@0: glBegin(GL_POLYGON);
andrewm@0: if(/*touchLocV < kWhiteKeyFrontBackCutoff && */touchLocH >= 0.0) { // FIXME: find a more permanent solution
andrewm@0: // Here, the touch is in a location that has both horizontal and vertical information.
andrewm@0: for(int i = 0; i < 360; i += 5) {
andrewm@0: glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler)
andrewm@0: + touchLocH*kWhiteKeyFrontWidth,
andrewm@0: y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler)
andrewm@0: + kWhiteKeyFrontLength*(touchLocV/kWhiteKeyFrontBackCutoff));
andrewm@0: }
andrewm@0: }
andrewm@0: else {
andrewm@0: // The touch is in the back part of the key, or for some reason lacks horizontal information
andrewm@0: for(int i = 0; i < 360; i += 5) {
andrewm@0: glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler)
andrewm@0: + kWhiteKeyBackOffsets[shape] + kWhiteKeyBackWidths[shape]/2,
andrewm@0: y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler)
andrewm@0: + kWhiteKeyFrontLength + (kWhiteKeyBackLength*
andrewm@0: ((touchLocV-kWhiteKeyFrontBackCutoff)/(1.0-kWhiteKeyFrontBackCutoff))));
andrewm@0: }
andrewm@0: }
andrewm@0: glEnd();
andrewm@0: }
andrewm@0:
andrewm@0: // Draw a circle indicating a touch on the black key surface
andrewm@0:
andrewm@0: void KeyboardDisplay::drawBlackTouch(float x, float y, float touchLocH, float touchLocV, float touchSize) {
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: glColor3f(0.0, 1.0, 0.0);
andrewm@0:
andrewm@0: glBegin(GL_POLYGON);
andrewm@0:
andrewm@0: if(touchLocH < 0.0)
andrewm@0: touchLocH = 0.5;
andrewm@0:
andrewm@0: for(int i = 0; i < 360; i += 5) {
andrewm@0: glVertex2f(x + cosf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler)
andrewm@0: + touchLocH * kBlackKeyWidth,
andrewm@0: y + sinf((float)i*3.14159/180.0)*(kDisplayMinTouchSize + touchSize*kDisplayTouchSizeScaler) + kBlackKeyLength*touchLocV);
andrewm@0: }
andrewm@0:
andrewm@0: glEnd();
andrewm@0: }
andrewm@0:
andrewm@0: // Draw a slider bar indicating the current key analog position
andrewm@0:
andrewm@0: void KeyboardDisplay::drawAnalogSlider(float x, float y, bool calibrated, bool whiteKey, float value) {
andrewm@0: // First some gray lines indicating the 0.0 and 1.0 marks
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
andrewm@0: glColor3f(0.5, 0.5, 0.5);
andrewm@0:
andrewm@0: glBegin(GL_POLYGON);
andrewm@0: glVertex2f(x, y + kAnalogSliderZeroLocation);
andrewm@0: glVertex2f(x, y + kAnalogSliderOneLocation);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderOneLocation);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderZeroLocation);
andrewm@0: glEnd();
andrewm@0:
andrewm@0: // Draw a red box at the top for uncalibrated values
andrewm@0: if(!calibrated) {
andrewm@0: glColor3f(1.0, 0.0, 0.0);
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: glBegin(GL_POLYGON);
andrewm@0: glVertex2f(x, y + kAnalogSliderOneLocation);
andrewm@0: glVertex2f(x, y + kAnalogSliderLength);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderLength);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderOneLocation);
andrewm@0: glEnd();
andrewm@0: }
andrewm@0:
andrewm@0: // Next the filled part indicating the specific value (same color as touches), then the outline
andrewm@0: if(whiteKey)
andrewm@0: glColor3f(1.0, 0.0, 1.0);
andrewm@0: else
andrewm@0: glColor3f(0.0, 1.0, 0.0);
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
andrewm@0: glBegin(GL_POLYGON);
andrewm@0:
andrewm@0: float locationForValue = kAnalogSliderLength * (value - kAnalogSliderMinimumValue) / (kAnalogSliderMaximumValue - kAnalogSliderMinimumValue);
andrewm@0: if(locationForValue < 0.0)
andrewm@0: locationForValue = 0.0;
andrewm@0: if(locationForValue > kAnalogSliderLength)
andrewm@0: locationForValue = kAnalogSliderLength;
andrewm@0:
andrewm@0: // Draw solid box from 0.0 to current value
andrewm@0: glVertex2f(x, y + kAnalogSliderZeroLocation);
andrewm@0: glVertex2f(x, y + locationForValue);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + locationForValue);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderZeroLocation);
andrewm@0: glEnd();
andrewm@0:
andrewm@0: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
andrewm@0: glColor3f(0.0, 0.0, 0.0);
andrewm@0:
andrewm@0: glBegin(GL_POLYGON);
andrewm@0: glVertex2f(x, y);
andrewm@0: glVertex2f(x, y + kAnalogSliderLength);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y + kAnalogSliderLength);
andrewm@0: glVertex2f(x + kAnalogSliderWidth, y);
andrewm@0: glEnd();
andrewm@0: }
andrewm@0:
andrewm@0: void KeyboardDisplay::refreshViewport() {
andrewm@0: //glViewport(0, 0, displayPixelWidth_, displayPixelHeight_);
andrewm@0: }
andrewm@0:
andrewm@0: // Conversion from internal coordinate space to external pixel values and back
andrewm@0:
andrewm@0: // Pixel values go from 0,0 (lower left) to displayPixelWidth_, displayPixelHeight_ (upper right)
andrewm@0: // Internal values go from -totalDisplayWidth_/2, -totalDisplayHeight_/2 (lower left)
andrewm@0: // to totalDisplayWidth_/2, totalDisplayHeight_/2 (upper right)
andrewm@0:
andrewm@0: // Pixel value in --> OpenGL value out
andrewm@0: KeyboardDisplay::Point KeyboardDisplay::screenToInternal(Point& inPoint) {
andrewm@0: Point out;
andrewm@0:
andrewm@0: out.x = -totalDisplayWidth_*0.5 + (inPoint.x/displayPixelWidth_) * totalDisplayWidth_;
andrewm@0: out.y = -totalDisplayHeight_*0.5 + (inPoint.y/displayPixelHeight_) * totalDisplayHeight_;
andrewm@0:
andrewm@0: return out;
andrewm@0: }
andrewm@0:
andrewm@0: // OpenGL value in --> Pixel value out
andrewm@0: KeyboardDisplay::Point KeyboardDisplay::internalToScreen(Point& inPoint) {
andrewm@0: Point out;
andrewm@0:
andrewm@0: out.x = ((inPoint.x + totalDisplayWidth_*0.5)/totalDisplayWidth_) * displayPixelWidth_;
andrewm@0: out.y = ((inPoint.y + totalDisplayHeight_*0.5)/totalDisplayHeight_) * displayPixelHeight_;
andrewm@0:
andrewm@0: return out;
andrewm@0: }
andrewm@0:
andrewm@0: // Given an internal-coordinate representation, return the number of the key that it belongs
andrewm@0: // in, otherwise return -1 if no key matches.
andrewm@0:
andrewm@0: int KeyboardDisplay::keyForLocation(Point& internalPoint) {
andrewm@27: // std::cout << "(" << internalPoint.x << "," << internalPoint.y << ")\n";
andrewm@18:
andrewm@0: // First, check that the point is within the overall bounding box of the keyboard
andrewm@0: if(internalPoint.y < -totalDisplayHeight_*0.5 + kDisplayBottomMargin ||
andrewm@0: internalPoint.y > totalDisplayHeight_*0.5 - kDisplayTopMargin)
andrewm@0: return -1;
andrewm@0: if(internalPoint.x < -totalDisplayWidth_*0.5 + kDisplaySideMargin ||
andrewm@0: internalPoint.x > totalDisplayWidth_*0.5 - kDisplaySideMargin)
andrewm@0: return -1;
andrewm@0:
andrewm@0: // Now, look for the key region corresponding to this horizontal location
andrewm@0: // hLoc indicates the relative distance from the beginning of the first key
andrewm@0:
andrewm@0: float hLoc = internalPoint.x + totalDisplayWidth_*0.5 - kDisplaySideMargin;
andrewm@0:
andrewm@0: if(hLoc < 0.0)
andrewm@0: return -1;
andrewm@0:
andrewm@0: // normalizedHLoc indicates the index of the white key this touch is near.
andrewm@0: float normalizedHLoc = hLoc / (kWhiteKeyFrontWidth + kInterKeySpacing);
andrewm@0:
andrewm@0: // Two relevant regions: front of the white keys, back of the white keys with black keys
andrewm@0: // Distinguish them by vertical position.
andrewm@0:
andrewm@0: int shapeOfBottomKey = keyShape(lowestMidiNote_); // White key index of lowest key
andrewm@0: int lowestC = (lowestMidiNote_ / 12) * 12; // C below lowest key
andrewm@0: int whiteKeyNumber = floorf(normalizedHLoc); // Number of white key
andrewm@0: int whiteOctaveNumber = (whiteKeyNumber + shapeOfBottomKey) / 7; // Octave the key is in
andrewm@0: int chromaticKeyNumber = 12 * whiteOctaveNumber + kWhiteToChromatic[(whiteKeyNumber + shapeOfBottomKey) % 7];
andrewm@0:
andrewm@0: // Check if we're on the front area of the white keys, and if so, ignore points located in the gaps
andrewm@0: // between the keys
andrewm@18:
andrewm@27: // std::cout << "norm " << (-internalPoint.y + totalDisplayHeight_*0.5) << std::endl;
andrewm@0:
andrewm@18: if(-internalPoint.y + totalDisplayHeight_*0.5 - kDisplayBottomMargin <= kWhiteKeyFrontLength) {
andrewm@0: if(normalizedHLoc - floorf(normalizedHLoc) > kWhiteKeyFrontWidth / (kWhiteKeyFrontWidth + kInterKeySpacing))
andrewm@0: return -1;
andrewm@0: return lowestC + chromaticKeyNumber;
andrewm@0: }
andrewm@0: else {
andrewm@0: // Back of white keys, or black keys
andrewm@0:
andrewm@0: int whiteKeyShape = keyShape(chromaticKeyNumber);
andrewm@0: if(whiteKeyShape < 0) // Shouldn't happen
andrewm@0: return -1;
andrewm@0:
andrewm@0: float locRelativeToLeft = (normalizedHLoc - floorf(normalizedHLoc)) * (kWhiteKeyFrontWidth + kInterKeySpacing);
andrewm@0:
andrewm@0: // Check if we are in the back region of the white key. Handle the lowest and highest notes specially since
andrewm@0: // the white keys are generally wider on account of no adjacent black key.
andrewm@0: if(lowestC + chromaticKeyNumber == lowestMidiNote_) {
andrewm@0: if(locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape])
andrewm@0: return lowestC + chromaticKeyNumber;
andrewm@0: }
andrewm@0: else if(lowestC + chromaticKeyNumber == highestMidiNote_) {
andrewm@0: if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape])
andrewm@0: return lowestC + chromaticKeyNumber;
andrewm@0: }
andrewm@0: else if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape] &&
andrewm@0: locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape]) {
andrewm@0: return lowestC + chromaticKeyNumber;
andrewm@0: }
andrewm@0:
andrewm@0: // By now, we've established that we're not on top of a white key. See if we align to a black key.
andrewm@0: // Watch the vertical gap between white and black keys
andrewm@18: if(-internalPoint.y + totalDisplayHeight_*0.5 - kDisplayBottomMargin <=
andrewm@0: kWhiteKeyFrontLength + kWhiteKeyBackLength - kBlackKeyLength)
andrewm@0: return -1;
andrewm@0:
andrewm@0: // Is there a black key below this white key?
andrewm@0: if(keyShape(chromaticKeyNumber - 1) < 0) {
andrewm@0: if(locRelativeToLeft <= kWhiteKeyBackOffsets[whiteKeyShape] - kInterKeySpacing &&
andrewm@0: lowestC + chromaticKeyNumber > lowestMidiNote_)
andrewm@0: return lowestC + chromaticKeyNumber - 1;
andrewm@0: }
andrewm@0: // Or is there a black key above this white key?
andrewm@0: if(keyShape(chromaticKeyNumber + 1) < 0) {
andrewm@0: if(locRelativeToLeft >= kWhiteKeyBackOffsets[whiteKeyShape] + kWhiteKeyBackWidths[whiteKeyShape] + kInterKeySpacing
andrewm@0: && lowestC + chromaticKeyNumber < highestMidiNote_)
andrewm@0: return lowestC + chromaticKeyNumber + 1;
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // If all else fails, assume we're not on any key
andrewm@0: return -1;
andrewm@44: }
andrewm@44:
andrewm@44: // Convert the map of keyboard segments with divisions into an array ordered by
andrewm@44: // note number, to save time during display
andrewm@44:
andrewm@44: void KeyboardDisplay::recalculateKeyDivisions() {
andrewm@44: // By default, 1 division per key
andrewm@44: for(int i = 0; i < 128; i++)
andrewm@44: keyDivisionsForNote_[i] = 1;
andrewm@44:
andrewm@44: // Increase divisions wherever we find a relevant mapping
andrewm@44: std::map::iterator it;
andrewm@44: for(it = keyDivisions_.begin(); it != keyDivisions_.end(); ++it) {
andrewm@44: int start = it->second.noteLow;
andrewm@44: int end = it->second.noteHigh;
andrewm@44: int div = it->second.divisions;
andrewm@44:
andrewm@44: if(start < 0)
andrewm@44: start = 0;
andrewm@44: if(end > 127)
andrewm@44: end = 127;
andrewm@44: for(int i = start; i <= end; i++) {
andrewm@44: if(div > keyDivisionsForNote_[i])
andrewm@44: keyDivisionsForNote_[i] = div;
andrewm@44: }
andrewm@44: }
andrewm@0: }