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: PianoKeyboard.cpp: main class that keeps track of each key (and pedal)
andrewm@0: on the keyboard, while also providing hooks for mapping and scheduling
andrewm@0: of events. One shared instance of this class is used widely throughout
andrewm@0: the program.
andrewm@0: */
andrewm@0:
andrewm@0: #include "PianoKeyboard.h"
andrewm@0: #include "TouchkeyDevice.h"
andrewm@0: #include "../Mappings/Mapping.h"
andrewm@46: #include "MidiOutputController.h"
andrewm@0: #include "../Mappings/MappingFactory.h"
andrewm@0: #include "../Mappings/MappingScheduler.h"
andrewm@0:
andrewm@0: // Constructor
andrewm@0: PianoKeyboard::PianoKeyboard()
andrewm@0: : gui_(0), graphGui_(0), midiOutputController_(0),
andrewm@0: oscTransmitter_(0), touchkeyDevice_(0),
andrewm@0: lowestMidiNote_(0), highestMidiNote_(0), numberOfPedals_(0),
andrewm@0: isInitialized_(false), isRunning_(false), isCalibrated_(false), calibrationInProgress_(false)
andrewm@0: {
andrewm@0: // Start a thread by which we can schedule future events
andrewm@0: futureEventScheduler_.start(0);
andrewm@0:
andrewm@0: // Build the key list
andrewm@0: for(int i = 0; i <= 127; i++)
andrewm@0: keys_.push_back(new PianoKey(*this, i, kDefaultKeyHistoryLength));
andrewm@0:
andrewm@0: mappingScheduler_ = new MappingScheduler(*this);
andrewm@0: mappingScheduler_->start();
andrewm@0: }
andrewm@0:
andrewm@0: // Reset all keys and pedals to their default state.
andrewm@0: void PianoKeyboard::reset() {
andrewm@0: // Clear any history in the source buffers
andrewm@0: std::vector::iterator itKey;
andrewm@0: std::vector::iterator itPed;
andrewm@0:
andrewm@0: for(itKey = keys_.begin(); itKey != keys_.end(); itKey++)
andrewm@0: (*itKey)->reset();
andrewm@0: for(itPed = pedals_.begin(); itPed != pedals_.end(); itPed++)
andrewm@0: (*itPed)->clear();
andrewm@0: }
andrewm@0:
andrewm@0: // Provide a pointer to the graphical display class
andrewm@0:
andrewm@0: void PianoKeyboard::setGUI(KeyboardDisplay* gui) {
andrewm@0: gui_ = gui;
andrewm@0: if(gui_ != 0) {
andrewm@0: gui_->setKeyboardRange(lowestMidiNote_, highestMidiNote_);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Set the range of the keyboard in terms of MIDI notes. A standard
andrewm@0: // 88-key keyboard has a range of 21-108, but other setups may differ.
andrewm@0:
andrewm@0: void PianoKeyboard::setKeyboardGUIRange(int lowest, int highest) {
andrewm@0: lowestMidiNote_ = lowest;
andrewm@0: highestMidiNote_ = highest;
andrewm@0:
andrewm@0: // Sanity checks: enforce 0-127 range, high >= low
andrewm@0: if(lowestMidiNote_ < 0)
andrewm@0: lowestMidiNote_ = 0;
andrewm@0: if(highestMidiNote_ < 0)
andrewm@0: highestMidiNote_ = 0;
andrewm@0: if(lowestMidiNote_ > 127)
andrewm@0: lowestMidiNote_ = 127;
andrewm@0: if(highestMidiNote_ > 127)
andrewm@0: highestMidiNote_ = 127;
andrewm@0: if(lowestMidiNote_ > highestMidiNote_)
andrewm@0: highestMidiNote_ = lowestMidiNote_;
andrewm@0:
andrewm@0: /*
andrewm@0: // Free the existing PianoKey objects
andrewm@0: for(std::vector::iterator it = keys_.begin(); it != keys_.end(); ++it)
andrewm@0: delete (*it);
andrewm@0: keys_.clear();
andrewm@0:
andrewm@0: // Rebuild the key list
andrewm@0: for(int i = lowestMidiNote_; i <= highestMidiNote_; i++)
andrewm@0: keys_.push_back(new PianoKey(*this, i, kDefaultKeyHistoryLength));
andrewm@0: */
andrewm@0:
andrewm@0: if(gui_ != 0)
andrewm@0: gui_->setKeyboardRange(lowestMidiNote_, highestMidiNote_);
andrewm@0: }
andrewm@0:
andrewm@0: // Send a message by OSC (and potentially by other means depending on who's listening)
andrewm@0:
andrewm@0: void PianoKeyboard::sendMessage(const char * path, const char * type, ...) {
andrewm@0: //ScopedReadLock sl(oscListenerMutex_);
andrewm@0:
andrewm@0: //cout << "sendMessage: " << path << endl;
andrewm@0:
andrewm@0: // Initialize variable argument list for reading
andrewm@0: va_list v;
andrewm@0: va_start(v, type);
andrewm@0:
andrewm@0: // Make a new OSC message which we will use both internally and externally
andrewm@0: lo_message msg = lo_message_new();
andrewm@0: lo_message_add_varargs(msg, type, v);
andrewm@0: int argc = lo_message_get_argc(msg);
andrewm@0: lo_arg **argv = lo_message_get_argv(msg);
andrewm@0:
andrewm@0: // Internal handler lookup first
andrewm@0: // Lock the mutex so the list of listeners doesn't change midway through
andrewm@0:
andrewm@0: updateListeners();
andrewm@0:
andrewm@0: oscListenerMutex_.enter();
andrewm@0: //oscListenerMutex_.enterRead();
andrewm@0:
andrewm@0: // Now remove the global prefix and compare the rest of the message to the registered handlers.
andrewm@0: std::multimap::iterator it;
andrewm@0: std::pair::iterator,std::multimap::iterator> ret;
andrewm@0: ret = noteListeners_.equal_range((std::string)path);
andrewm@0:
andrewm@0: double timeInHandlers = 0;
andrewm@0: int numHandlers = 0; // DEBUG
andrewm@0: it = ret.first;
andrewm@0: while(it != ret.second) {
andrewm@0: OscHandler *object = (*it++).second;
andrewm@0:
andrewm@0: double before = Time::getMillisecondCounterHiRes();
andrewm@0: object->oscHandlerMethod(path, type, argc, argv, 0);
andrewm@0: timeInHandlers += Time::getMillisecondCounterHiRes() - before;
andrewm@0: numHandlers++; // DEBUG
andrewm@0: }
andrewm@0: //oscListenerMutex_.exitRead();
andrewm@0: oscListenerMutex_.exit();
andrewm@0:
andrewm@0: //if(timeInHandlers > 1.0)
andrewm@0: // cout << "sendMessage(): timeInHandlers = " << timeInHandlers << " for " << numHandlers << " handlers (msg " << path << ")\n";
andrewm@0:
andrewm@0: // Now send this message to any external OSC sources
andrewm@0: if(oscTransmitter_ != 0)
andrewm@0: oscTransmitter_->sendMessage(path, type, msg);
andrewm@0:
andrewm@0: lo_message_free(msg);
andrewm@0: va_end(v);
andrewm@0:
andrewm@0:
andrewm@0: }
andrewm@0:
andrewm@0: // Change number of pedals
andrewm@0:
andrewm@0: void PianoKeyboard::setNumberOfPedals(int number) {
andrewm@0: numberOfPedals_ = number;
andrewm@0: if(numberOfPedals_ < 0)
andrewm@0: numberOfPedals_ = 0;
andrewm@0: if(numberOfPedals_ > 127)
andrewm@0: numberOfPedals_ = 127;
andrewm@0:
andrewm@0: // Free the existing PianoPedal objects
andrewm@0: for(std::vector::iterator it = pedals_.begin(); it != pedals_.end(); ++it)
andrewm@0: delete (*it);
andrewm@0: pedals_.clear();
andrewm@0:
andrewm@0: // Rebuild the list of pedals
andrewm@0: for(int i = 0; i < numberOfPedals_; i++)
andrewm@0: pedals_.push_back(new PianoPedal(kDefaultPedalHistoryLength));
andrewm@0: }
andrewm@0:
andrewm@0: // Set color of RGB LED for a given key. note indicates the MIDI
andrewm@0: // note number of the key, and color can be specified in one of two
andrewm@0: // formats.
andrewm@0: void PianoKeyboard::setKeyLEDColorRGB(const int note, const float red, const float green, const float blue) {
andrewm@0: if(touchkeyDevice_ != 0) {
andrewm@0: touchkeyDevice_->rgbledSetColor(note, red, green, blue);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::setKeyLEDColorHSV(const int note, const float hue, const float saturation, const float value) {
andrewm@0: if(touchkeyDevice_ != 0) {
andrewm@0: touchkeyDevice_->rgbledSetColorHSV(note, hue, saturation, value);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::setAllKeyLEDsOff() {
andrewm@0: if(touchkeyDevice_ != 0) {
andrewm@0: touchkeyDevice_->rgbledAllOff();
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // ***** Mapping Methods *****
andrewm@0:
andrewm@0: // Add a new mapping identified by a MIDI note and an owner
andrewm@0: void PianoKeyboard::addMapping(int noteNumber, Mapping* mapping) {
andrewm@0: removeMapping(noteNumber); // Free any mapping that's already present on this note
andrewm@0: mappings_[noteNumber] = mapping;
andrewm@0: }
andrewm@0:
andrewm@0: // Remove an existing mapping identified by owner
andrewm@0: void PianoKeyboard::removeMapping(int noteNumber) {
andrewm@0: if(mappings_.count(noteNumber) == 0)
andrewm@0: return;
andrewm@0: Mapping* mapping = mappings_[noteNumber];
andrewm@0: delete mapping;
andrewm@0: mappings_.erase(noteNumber);
andrewm@0: }
andrewm@0:
andrewm@0: // Return a specific mapping by owner and note number
andrewm@0: Mapping* PianoKeyboard::mapping(int noteNumber) {
andrewm@0: if(mappings_.count(noteNumber) == 0)
andrewm@0: return 0;
andrewm@0: return mappings_[noteNumber];
andrewm@0: }
andrewm@0:
andrewm@0: // Return a list of all MIDI notes with active mappings. Some may have more than
andrewm@0: // one but we only want the list of active notes
andrewm@0: std::vector PianoKeyboard::activeMappings() {
andrewm@0: std::vector keys;
andrewm@0: std::map::iterator it = mappings_.begin();
andrewm@0: while(it != mappings_.end()) {
andrewm@0: int nextKey = (it++)->first;
andrewm@0: keys.push_back(nextKey);
andrewm@0: }
andrewm@0: return keys;
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::clearMappings() {
andrewm@0: std::map::iterator it = mappings_.begin();
andrewm@0:
andrewm@0: while(it != mappings_.end()) {
andrewm@0: // Delete everybody in the container
andrewm@0: Mapping *mapping = it->second;
andrewm@0: delete mapping;
andrewm@0: }
andrewm@0:
andrewm@0: // Now clear the container
andrewm@0: mappings_.clear();
andrewm@0: }
andrewm@0:
andrewm@0: // Mapping factory methods: tell each registered factory about these events if it listens to this particular note
andrewm@0: void PianoKeyboard::tellAllMappingFactoriesTouchBegan(int noteNumber, bool midiNoteIsOn, bool keyMotionActive,
andrewm@0: Node* touchBuffer,
andrewm@0: Node* positionBuffer,
andrewm@0: KeyPositionTracker* positionTracker) {
andrewm@0: ScopedReadLock sl(mappingFactoriesMutex_);
andrewm@0: std::map::iterator it;
andrewm@0: for(it = mappingFactories_.begin(); it != mappingFactories_.end(); it++) {
andrewm@0: if(it->first->respondsToNote(noteNumber))
andrewm@0: it->second->touchBegan(noteNumber, midiNoteIsOn, keyMotionActive, touchBuffer, positionBuffer, positionTracker);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::tellAllMappingFactoriesTouchEnded(int noteNumber, bool midiNoteIsOn, bool keyMotionActive,
andrewm@0: Node* touchBuffer,
andrewm@0: Node* positionBuffer,
andrewm@0: KeyPositionTracker* positionTracker) {
andrewm@0: ScopedReadLock sl(mappingFactoriesMutex_);
andrewm@0: std::map::iterator it;
andrewm@0: for(it = mappingFactories_.begin(); it != mappingFactories_.end(); it++) {
andrewm@0: if(it->first->respondsToNote(noteNumber))
andrewm@0: it->second->touchEnded(noteNumber, midiNoteIsOn, keyMotionActive, touchBuffer, positionBuffer, positionTracker);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::tellAllMappingFactoriesKeyMotionActive(int noteNumber, bool midiNoteIsOn, bool touchIsOn,
andrewm@0: Node* touchBuffer,
andrewm@0: Node* positionBuffer,
andrewm@0: KeyPositionTracker* positionTracker) {
andrewm@0: ScopedReadLock sl(mappingFactoriesMutex_);
andrewm@0: std::map::iterator it;
andrewm@0: for(it = mappingFactories_.begin(); it != mappingFactories_.end(); it++) {
andrewm@0: if(it->first->respondsToNote(noteNumber))
andrewm@0: it->second->keyMotionActive(noteNumber, midiNoteIsOn, touchIsOn, touchBuffer, positionBuffer, positionTracker);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: void PianoKeyboard::tellAllMappingFactoriesKeyMotionIdle(int noteNumber, bool midiNoteIsOn, bool touchIsOn,
andrewm@0: Node* touchBuffer,
andrewm@0: Node* positionBuffer,
andrewm@0: KeyPositionTracker* positionTracker) {
andrewm@0: ScopedReadLock sl(mappingFactoriesMutex_);
andrewm@0: std::map::iterator it;
andrewm@0: for(it = mappingFactories_.begin(); it != mappingFactories_.end(); it++) {
andrewm@0: if(it->first->respondsToNote(noteNumber))
andrewm@0: it->second->keyMotionIdle(noteNumber, midiNoteIsOn, touchIsOn, touchBuffer, positionBuffer, positionTracker);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Destructor
andrewm@0:
andrewm@0: PianoKeyboard::~PianoKeyboard() {
andrewm@0: // Remove all mappings
andrewm@0: clearMappings();
andrewm@0:
andrewm@0: // Delete any keys and pedals we've allocated
andrewm@0: for(std::vector::iterator it = keys_.begin(); it != keys_.end(); ++it)
andrewm@0: delete (*it);
andrewm@0: for(std::vector::iterator it = pedals_.begin(); it != pedals_.end(); ++it)
andrewm@0: delete (*it);
andrewm@0: mappingScheduler_->stop();
andrewm@0: delete mappingScheduler_;
andrewm@46: }