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: MidiInputController.cpp: handles incoming MIDI data and manages input andrewm@0: ports. Detailed processing is broken down by keyboard segment; see andrewm@0: MidiKeyboardSegment.h/cpp for more. andrewm@0: */ andrewm@0: andrewm@0: andrewm@0: #include "MidiInputController.h" andrewm@0: #include "MidiOutputController.h" andrewm@0: #include "../Mappings/MappingFactory.h" andrewm@0: andrewm@33: #undef DEBUG_MIDI_INPUT_CONTROLLER andrewm@0: #undef MIDI_INPUT_CONTROLLER_DEBUG_RAW andrewm@0: andrewm@0: // Constructor andrewm@0: andrewm@0: MidiInputController::MidiInputController(PianoKeyboard& keyboard) andrewm@31: : keyboard_(keyboard), midiOutputController_(0), primaryActivePort_(-1), andrewm@31: segmentUniqueIdentifier_(0) andrewm@0: { andrewm@0: logFileCreated = false; andrewm@0: loggingActive = false; andrewm@0: andrewm@0: } andrewm@0: andrewm@0: void MidiInputController::setMidiOutputController(MidiOutputController* ct) { andrewm@0: midiOutputController_ = ct; andrewm@0: andrewm@0: // Propagate the change to the keyboard segments andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: for(int i = 0; i < segments_.size(); i++) { andrewm@0: segments_[i]->setMidiOutputController(ct); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // create a new MIDI log file, ready to have data written to it andrewm@0: void MidiInputController::createLogFile(string midiLog_filename, string path) andrewm@0: { andrewm@0: // indicate that we have created a log file (so we can close it later) andrewm@0: logFileCreated = true; andrewm@0: andrewm@0: if (path.compare("") != 0) andrewm@0: { andrewm@0: path = path + "/"; andrewm@0: } andrewm@0: andrewm@0: midiLog_filename = path + midiLog_filename; andrewm@0: andrewm@0: char *fileName = (char*)midiLog_filename.c_str(); andrewm@0: andrewm@0: // create output file andrewm@0: midiLog.open (fileName, ios::out | ios::binary); andrewm@0: midiLog.seekp(0); andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // close the existing log file andrewm@0: void MidiInputController::closeLogFile() andrewm@0: { andrewm@0: if (logFileCreated) andrewm@0: { andrewm@0: midiLog.close(); andrewm@0: logFileCreated = false; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // start logging midi data andrewm@0: void MidiInputController::startLogging() andrewm@0: { andrewm@0: loggingActive = true; andrewm@0: } andrewm@0: andrewm@0: // ------------------------------------------------------ andrewm@0: // stop logging midi data andrewm@0: void MidiInputController::stopLogging() andrewm@0: { andrewm@0: loggingActive = false; andrewm@0: } andrewm@0: andrewm@0: // Iterate through the available MIDI devices. Return a vector containing andrewm@0: // indices and names for each device. The index will later be passed back andrewm@0: // to indicate which device to open. andrewm@0: andrewm@0: vector > MidiInputController::availableMidiDevices() { andrewm@0: vector > deviceList; andrewm@0: andrewm@0: try { andrewm@0: StringArray deviceStrings = MidiInput::getDevices(); andrewm@0: andrewm@0: for(int i = 0; i < deviceStrings.size(); i++) { andrewm@0: pair p(i, string(deviceStrings[i].toUTF8())); andrewm@0: deviceList.push_back(p); andrewm@0: } andrewm@0: } andrewm@0: catch(...) { andrewm@0: deviceList.clear(); andrewm@0: } andrewm@0: andrewm@0: return deviceList; andrewm@0: } andrewm@0: andrewm@0: // Enable a new MIDI port according to its index (returned from availableMidiDevices()) andrewm@0: // Returns true on success. andrewm@0: andrewm@31: bool MidiInputController::enablePort(int portNumber, bool isPrimary) { andrewm@0: if(portNumber < 0) andrewm@0: return false; andrewm@31: // If this is already the primary port, nothing to do andrewm@31: if(isPrimary && primaryActivePort_ == portNumber) andrewm@31: return true; andrewm@31: andrewm@31: // If this port is active already but we are not making it primary, andrewm@31: // then fail: can't override primary with aux andrewm@31: if(!isPrimary) andrewm@31: if(activePorts_.count(portNumber) > 0) andrewm@31: return false; andrewm@0: andrewm@31: // If there is already a (different) primary active port, disable it andrewm@31: if(isPrimary && primaryActivePort_ >= 0 && activePorts_.count(primaryActivePort_) > 0) andrewm@31: disablePort(primaryActivePort_); andrewm@31: andrewm@31: // Enable the port if it hasn't been already andrewm@31: if(activePorts_.count(portNumber) == 0) { andrewm@31: MidiInput *device = MidiInput::openDevice(portNumber, this); andrewm@31: andrewm@31: if(device == 0) { andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "Failed to enable MIDI input port " << portNumber << ")\n"; andrewm@31: #endif andrewm@31: return false; andrewm@31: } andrewm@0: andrewm@31: andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "Enabling MIDI input port " << portNumber << " (" << device->getName() << ")\n"; andrewm@31: #endif andrewm@31: device->start(); andrewm@31: andrewm@31: // Save the device in the set of ports andrewm@31: activePorts_[portNumber] = device; andrewm@31: } andrewm@31: else { andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "MIDI input port " << portNumber << " already enabled\n"; andrewm@31: #endif andrewm@0: } andrewm@0: andrewm@31: if(isPrimary) andrewm@31: primaryActivePort_ = portNumber; andrewm@31: andrewm@0: return true; andrewm@0: } andrewm@0: andrewm@0: // Enable all current MIDI ports andrewm@0: andrewm@31: bool MidiInputController::enableAllPorts(int primaryPortNumber) { andrewm@0: bool enabledPort = false; andrewm@0: vector > ports = availableMidiDevices(); andrewm@0: vector >::iterator it = ports.begin(); andrewm@0: andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "Enabling all MIDI input ports\n"; andrewm@31: #endif andrewm@31: andrewm@0: while(it != ports.end()) { andrewm@0: // Don't enable MIDI input from our own virtual output andrewm@0: if(it->second != string(kMidiVirtualOutputName.toUTF8())) andrewm@31: enabledPort |= enablePort((it++)->first, it->first == primaryPortNumber); andrewm@0: else andrewm@0: it++; andrewm@0: } andrewm@0: andrewm@0: return enabledPort; andrewm@0: } andrewm@0: andrewm@0: // Remove a specific MIDI input source and free associated memory andrewm@0: andrewm@0: void MidiInputController::disablePort(int portNumber) { andrewm@0: if(activePorts_.count(portNumber) <= 0) andrewm@0: return; andrewm@0: andrewm@0: MidiInput *device = activePorts_[portNumber]; andrewm@0: andrewm@0: if(device == 0) andrewm@0: return; andrewm@0: andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "Disabling MIDI input port " << portNumber << " (" << device->getName() << ")\n"; andrewm@31: #endif andrewm@31: andrewm@0: device->stop(); andrewm@0: delete device; andrewm@0: andrewm@0: activePorts_.erase(portNumber); andrewm@31: if(primaryActivePort_ == portNumber) andrewm@31: primaryActivePort_ = -1; andrewm@31: } andrewm@31: andrewm@31: // Remove the primary MIDI input source andrewm@31: andrewm@31: void MidiInputController::disablePrimaryPort() { andrewm@31: if(primaryActivePort_ < 0) andrewm@31: return; andrewm@31: disablePort(primaryActivePort_); andrewm@0: } andrewm@0: andrewm@0: // Remove all MIDI input sources and free associated memory andrewm@0: andrewm@31: void MidiInputController::disableAllPorts(bool auxiliaryOnly) { andrewm@0: map::iterator it; andrewm@31: MidiInput* primaryPort = 0; andrewm@0: andrewm@31: #ifdef DEBUG_MIDI_INPUT_CONTROLLER andrewm@31: cout << "Disabling all MIDI input ports\n"; andrewm@31: #endif andrewm@31: andrewm@0: it = activePorts_.begin(); andrewm@0: andrewm@0: while(it != activePorts_.end()) { andrewm@0: if(it->second == 0) { andrewm@0: it++; andrewm@0: continue; andrewm@0: } andrewm@31: andrewm@31: // Save primary port? andrewm@31: if(it->first == primaryActivePort_ && auxiliaryOnly) andrewm@31: primaryPort = it->second; andrewm@31: else { andrewm@31: it->second->stop(); // disable port andrewm@31: delete it->second; // free MidiInputCallback andrewm@31: } andrewm@0: it++; andrewm@0: } andrewm@0: andrewm@31: // Clear all ports including primary andrewm@31: activePorts_.clear(); andrewm@31: andrewm@31: // But did we save the priamry port? andrewm@31: if(auxiliaryOnly && primaryPort != 0) { andrewm@31: // Re-insert primary only andrewm@31: activePorts_[primaryActivePort_] = primaryPort; andrewm@31: } andrewm@31: else andrewm@31: primaryActivePort_ = -1; andrewm@0: } andrewm@0: andrewm@31: // Return the primary active port corresponding to the TK keyboard andrewm@0: andrewm@31: int MidiInputController::primaryActivePort() { andrewm@31: return primaryActivePort_; andrewm@31: } andrewm@31: andrewm@31: // Return a list of active ports other than the primary andrewm@31: andrewm@31: vector MidiInputController::auxiliaryActivePorts() { andrewm@0: vector ports; andrewm@0: andrewm@0: map::iterator it; andrewm@0: andrewm@0: for(it = activePorts_.begin(); it != activePorts_.end(); ++it) { andrewm@31: if(it->first == primaryActivePort_) andrewm@31: continue; andrewm@0: ports.push_back(it->first); andrewm@0: } andrewm@0: andrewm@0: return ports; andrewm@0: } andrewm@0: andrewm@41: // Get the name of a particular MIDI input port andrewm@41: String MidiInputController::deviceName(int portNumber) { andrewm@41: StringArray const& deviceStrings = MidiInput::getDevices(); andrewm@41: if(portNumber < 0 || portNumber >= deviceStrings.size()) andrewm@41: return ""; andrewm@41: return deviceStrings[portNumber]; andrewm@41: } andrewm@41: andrewm@41: // Find the index of a device with a given name; return -1 if not found andrewm@41: int MidiInputController::indexOfDeviceNamed(String const& name) { andrewm@41: StringArray const& deviceStrings = MidiInput::getDevices(); andrewm@41: andrewm@41: for(int i = 0; i < deviceStrings.size(); i++) { andrewm@41: if(name == deviceStrings[i]) andrewm@41: return i; andrewm@41: } andrewm@41: andrewm@41: return -1; andrewm@41: } andrewm@41: andrewm@0: // Add a new keyboard segment. Returns a pointer to the newly created segment andrewm@0: MidiKeyboardSegment* MidiInputController::addSegment(int outputPortNumber, andrewm@0: int noteMin, int noteMax, andrewm@0: int channelMask) { andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: andrewm@0: // Create a new segment and populate its values andrewm@0: MidiKeyboardSegment *segment = new MidiKeyboardSegment(keyboard_); andrewm@0: andrewm@0: segment->setMidiOutputController(midiOutputController_); andrewm@0: segment->setOutputPort(outputPortNumber); andrewm@0: segment->setNoteRange(noteMin, noteMax); andrewm@0: segment->setChannelMask(channelMask); andrewm@0: andrewm@0: // Add the segment to the vector and return the pointer andrewm@0: segments_.push_back(segment); andrewm@0: segmentUniqueIdentifier_++; andrewm@0: return segment; andrewm@0: } andrewm@0: andrewm@49: // Remove a segment by index or by object. Returns true if segment existed andrewm@49: bool MidiInputController::removeSegment(int index) { andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: andrewm@0: if(index < 0 || index >= segments_.size()) andrewm@49: return false; andrewm@0: andrewm@0: MidiKeyboardSegment* segment = segments_[index]; andrewm@0: delete segment; andrewm@0: segments_.erase(segments_.begin() + index); andrewm@0: segmentUniqueIdentifier_++; andrewm@49: return true; andrewm@0: } andrewm@0: andrewm@49: bool MidiInputController::removeSegment(MidiKeyboardSegment* segment) { andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@49: bool found = false; andrewm@0: andrewm@0: for(int i = 0; i < segments_.size(); i++) { andrewm@0: if(segments_[i] == segment) { andrewm@0: delete segment; andrewm@0: segments_.erase(segments_.begin() + i); andrewm@49: found = true; andrewm@0: break; andrewm@0: } andrewm@0: } andrewm@0: segmentUniqueIdentifier_++; andrewm@49: return found; andrewm@0: } andrewm@0: andrewm@0: void MidiInputController::removeAllSegments() { andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: andrewm@0: for(int i = 0; i < segments_.size(); i++) andrewm@0: delete segments_[i]; andrewm@0: segments_.clear(); andrewm@0: segmentUniqueIdentifier_++; andrewm@0: } andrewm@0: andrewm@0: // Disable any currently active notes andrewm@0: andrewm@0: void MidiInputController::allNotesOff() { andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: andrewm@0: for(int i = 0; i < segments_.size(); i++) andrewm@0: segments_[i]->allNotesOff(); andrewm@0: } andrewm@0: andrewm@33: // Return the current preset as an XmlElement, suitable for andrewm@33: // saving to file. This element will need to be deleted when finished andrewm@33: andrewm@33: XmlElement* MidiInputController::getSegmentPreset() { andrewm@33: ScopedLock sl(segmentsMutex_); andrewm@33: XmlElement* controllerElement = new XmlElement("KeyboardSegments"); andrewm@33: andrewm@33: // Add settings for each zone to the element andrewm@33: for(int i = 0; i < segments_.size(); i++) { andrewm@33: XmlElement* segmentElement = segments_[i]->getPreset(); andrewm@33: controllerElement->addChildElement(segmentElement); andrewm@33: } andrewm@33: andrewm@33: // Return the element andrewm@33: return controllerElement; andrewm@33: } andrewm@33: andrewm@33: // Load keyboard segments from a preset; returns true on success andrewm@33: andrewm@33: bool MidiInputController::loadSegmentPreset(XmlElement const* preset) { andrewm@33: ScopedLock sl(segmentsMutex_); andrewm@33: andrewm@33: for(int i = 0; i < segments_.size(); i++) andrewm@33: delete segments_[i]; andrewm@33: segments_.clear(); andrewm@33: andrewm@33: XmlElement *element = preset->getChildByName("Segment"); andrewm@33: while(element != 0) { andrewm@33: // Create a new segment and populate its values andrewm@33: MidiKeyboardSegment *segment = new MidiKeyboardSegment(keyboard_); andrewm@33: segment->setMidiOutputController(midiOutputController_); andrewm@33: andrewm@33: // Load settings for this particular segment andrewm@33: if(!segment->loadPreset(element)) { andrewm@33: delete segment; andrewm@33: for(int i = 0; i < segments_.size(); i++) andrewm@33: delete segments_[i]; andrewm@33: segments_.clear(); andrewm@33: return false; andrewm@33: } andrewm@33: andrewm@33: segments_.push_back(segment); andrewm@33: element = element->getNextElementWithTagName("Segment"); andrewm@33: } andrewm@33: andrewm@33: segmentUniqueIdentifier_++; andrewm@33: return true; andrewm@33: } andrewm@33: andrewm@49: // OSC handling for keyboard segments andrewm@49: OscMessage* MidiInputController::oscControlMessageForSegment(int segment, const char *path, const char *types, andrewm@49: int numValues, lo_arg **values, void *data) { andrewm@49: ScopedLock sl(segmentsMutex_); andrewm@49: if(segment < 0 || segment >= segments_.size()) andrewm@49: return 0; andrewm@49: andrewm@49: return segments_[segment]->oscControlMethod(path, types, numValues, values, data); andrewm@49: } andrewm@49: andrewm@0: // This gets called every time MIDI data becomes available on any input controller. source tells andrewm@0: // us where the message came from, and may be 0 if being called internally. andrewm@0: andrewm@0: void MidiInputController::handleIncomingMidiMessage(MidiInput* source, const MidiMessage& message) andrewm@0: //void MidiInputController::rtMidiCallback(double deltaTime, vector *message, int inputNumber) andrewm@0: { andrewm@0: // Juce will give us one MIDI command per callback, which makes processing easier for us. andrewm@0: andrewm@0: // Ignore sysex messages for now andrewm@0: if(message.isSysEx()) andrewm@0: return; andrewm@0: andrewm@0: // Pull out the raw bytes andrewm@0: int dataSize = message.getRawDataSize(); andrewm@0: if(dataSize <= 0) andrewm@0: return; andrewm@0: const unsigned char *messageData = message.getRawData(); andrewm@0: andrewm@0: // if logging is active andrewm@0: if (loggingActive) andrewm@0: { andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////// BEGIN LOGGING ///////////////////// andrewm@0: andrewm@0: int midi_channel = (int)(messageData[0]); andrewm@0: int midi_number = dataSize > 1 ? (int)(messageData[1]) : 0; andrewm@0: int midi_velocity = dataSize > 2 ? (int)(messageData[2]) : 0; andrewm@0: timestamp_type timestamp = keyboard_.schedulerCurrentTimestamp(); andrewm@0: andrewm@0: midiLog.write ((char*)×tamp, sizeof (timestamp_type)); andrewm@0: midiLog.write ((char*)&midi_channel, sizeof (int)); andrewm@0: midiLog.write ((char*)&midi_number, sizeof (int)); andrewm@0: midiLog.write ((char*)&midi_velocity, sizeof (int)); andrewm@0: andrewm@0: ///////////////////// END LOGGING ////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: //////////////////////////////////////////////////////// andrewm@0: } andrewm@0: andrewm@0: #ifdef MIDI_INPUT_CONTROLLER_DEBUG_RAW andrewm@0: if(source == 0) andrewm@0: cout << "MIDI Input [internal]: "; andrewm@0: else andrewm@0: cout << "MIDI Input [" << source->getName() << "]: "; andrewm@0: for(int debugPrint = 0; debugPrint < dataSize; debugPrint++) andrewm@0: printf("%x ", messageData[debugPrint]); andrewm@0: cout << endl; andrewm@0: #endif /* MIDI_INPUT_CONTROLLER_DEBUG_RAW */ andrewm@0: andrewm@0: ScopedLock ksl(keyboard_.performanceDataMutex_); andrewm@0: ScopedLock sl(segmentsMutex_); andrewm@0: for(int i = 0; i < segments_.size(); i++) { andrewm@0: if(segments_[i]->respondsToMessage(message)) andrewm@0: segments_[i]->midiHandlerMethod(source, message); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Destructor. Free any existing callbacks andrewm@0: MidiInputController::~MidiInputController() { andrewm@0: if(logFileCreated) { andrewm@0: midiLog.close(); andrewm@0: } andrewm@31: disableAllPorts(false); andrewm@0: removeAllSegments(); andrewm@0: }