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