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