view Source/TouchKeys/MidiInputController.cpp @ 0:3580ffe87dc8

First commit of TouchKeys public pre-release.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Mon, 11 Nov 2013 18:19:35 +0000
parents
children 88287c1c2c92
line wrap: on
line source
/*
  TouchKeys: multi-touch musical keyboard control software
  Copyright (c) 2013 Andrew McPherson

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  =====================================================================
 
  MidiInputController.cpp: handles incoming MIDI data and manages input
  ports. Detailed processing is broken down by keyboard segment; see
  MidiKeyboardSegment.h/cpp for more.
*/


#include "MidiInputController.h"
#include "MidiOutputController.h"
#include "../Mappings/MappingFactory.h"

#undef MIDI_INPUT_CONTROLLER_DEBUG_RAW

// Constructor

MidiInputController::MidiInputController(PianoKeyboard& keyboard) 
: keyboard_(keyboard), midiOutputController_(0), segmentUniqueIdentifier_(0)
{    
    logFileCreated = false;
    loggingActive = false;

}

void MidiInputController::setMidiOutputController(MidiOutputController* ct) {
    midiOutputController_ = ct;
    
    // Propagate the change to the keyboard segments
    ScopedLock sl(segmentsMutex_);
    for(int i = 0; i < segments_.size(); i++) {
        segments_[i]->setMidiOutputController(ct);
    }
}

// ------------------------------------------------------
// create a new MIDI log file, ready to have data written to it
void MidiInputController::createLogFile(string midiLog_filename, string path)
{
    // indicate that we have created a log file (so we can close it later)
    logFileCreated = true;
    
    if (path.compare("") != 0)
    {
        path = path + "/";
    }
    
    midiLog_filename = path + midiLog_filename;
    
    char *fileName = (char*)midiLog_filename.c_str();
    
    // create output file
    midiLog.open (fileName, ios::out | ios::binary);
    midiLog.seekp(0);
}

// ------------------------------------------------------
// close the existing log file
void MidiInputController::closeLogFile()
{
    if (logFileCreated)
    {
        midiLog.close();
        logFileCreated = false;
    }
}

// ------------------------------------------------------
// start logging midi data
void MidiInputController::startLogging()
{
    loggingActive = true;
}

// ------------------------------------------------------
// stop logging midi data
void MidiInputController::stopLogging()
{
    loggingActive = false;
}

// Iterate through the available MIDI devices.  Return a vector containing
// indices and names for each device.  The index will later be passed back
// to indicate which device to open.

vector<pair<int, string> > MidiInputController::availableMidiDevices() {
	vector<pair<int, string> > deviceList;
    
	try {
        StringArray deviceStrings = MidiInput::getDevices();
		
		for(int i = 0; i < deviceStrings.size(); i++) {
			pair<int, string> p(i, string(deviceStrings[i].toUTF8()));
			deviceList.push_back(p);
		}
	}
	catch(...) {
		deviceList.clear();
	}
	
	return deviceList;
}

// Enable a new MIDI port according to its index (returned from availableMidiDevices())
// Returns true on success.

bool MidiInputController::enablePort(int portNumber) {
	if(portNumber < 0)
		return false;
	
    MidiInput *device = MidiInput::openDevice(portNumber, this);
        
    if(device == 0) {
        cout << "Failed to enable MIDI input port " << portNumber << ")\n";
        return false;
    }
    
    //cout << "Enabling MIDI input port " << portNumber << " (" << device->getName() << ")\n";
    device->start();

    // Save the device in the set of ports
    activePorts_[portNumber] = device;

	return true;
}

// Enable all current MIDI ports

bool MidiInputController::enableAllPorts() {
	bool enabledPort = false;
	vector<pair<int, string> > ports = availableMidiDevices();
	vector<pair<int, string> >::iterator it = ports.begin();
	
	while(it != ports.end()) {
		// Don't enable MIDI input from our own virtual output
		if(it->second != string(kMidiVirtualOutputName.toUTF8()))
			enabledPort |= enablePort((it++)->first);
		else
			it++;
	}
	
	return enabledPort;
}

// Remove a specific MIDI input source and free associated memory

void MidiInputController::disablePort(int portNumber) {
	if(activePorts_.count(portNumber) <= 0)
		return;
	
	MidiInput *device = activePorts_[portNumber];

    if(device == 0)
        return;
    
	//cout << "Disabling MIDI input port " << portNumber << " (" << device->getName() << ")\n";
    device->stop();
    delete device;
    
	activePorts_.erase(portNumber);
}

// Remove all MIDI input sources and free associated memory

void MidiInputController::disableAllPorts() {
	map<int, MidiInput*>::iterator it;
	
	//cout << "Disabling all MIDI input ports\n";
	
	it = activePorts_.begin();
	
	while(it != activePorts_.end()) {
        if(it->second == 0) {
            it++;
            continue;
        }
		it->second->stop();                     // disable port
		delete it->second;						// free MidiInputCallback
		it++;
	}
	
	activePorts_.clear();
}

// Return a list of active ports

vector<int> MidiInputController::activePorts() {
    vector<int> ports;
    
	map<int, MidiInput*>::iterator it;
    
    for(it = activePorts_.begin(); it != activePorts_.end(); ++it) {
        ports.push_back(it->first);
    }
    
    return ports;
}

// Add a new keyboard segment. Returns a pointer to the newly created segment
MidiKeyboardSegment* MidiInputController::addSegment(int outputPortNumber,
                                                          int noteMin, int noteMax,
                                                          int channelMask) {
    ScopedLock sl(segmentsMutex_);
    
    // Create a new segment and populate its values
    MidiKeyboardSegment *segment = new MidiKeyboardSegment(keyboard_);
    
    segment->setMidiOutputController(midiOutputController_);
    segment->setOutputPort(outputPortNumber);
    segment->setNoteRange(noteMin, noteMax);
    segment->setChannelMask(channelMask);
    
    // Add the segment to the vector and return the pointer
    segments_.push_back(segment);
    segmentUniqueIdentifier_++;
    return segment;
}

// Remove a segment by index or by object
void MidiInputController::removeSegment(int index) {
    ScopedLock sl(segmentsMutex_);
    
    if(index < 0 || index >= segments_.size())
        return;
    
    MidiKeyboardSegment* segment = segments_[index];
    delete segment;
    segments_.erase(segments_.begin() + index);
    segmentUniqueIdentifier_++;
}

void MidiInputController::removeSegment(MidiKeyboardSegment* segment) {
    ScopedLock sl(segmentsMutex_);
    
    for(int i = 0; i < segments_.size(); i++) {
        if(segments_[i] == segment) {
            delete segment;
            segments_.erase(segments_.begin() + i);
            break;
        }
    }
    segmentUniqueIdentifier_++;
}

void MidiInputController::removeAllSegments() {
    ScopedLock sl(segmentsMutex_);

    for(int i = 0; i < segments_.size(); i++)
        delete segments_[i];
    segments_.clear();
    segmentUniqueIdentifier_++;
}

// Disable any currently active notes

void MidiInputController::allNotesOff() {
    ScopedLock sl(segmentsMutex_);
    
    for(int i = 0; i < segments_.size(); i++)
        segments_[i]->allNotesOff();
}

// This gets called every time MIDI data becomes available on any input controller. source tells
// us where the message came from, and may be 0 if being called internally.

void MidiInputController::handleIncomingMidiMessage(MidiInput* source, const MidiMessage& message)
//void MidiInputController::rtMidiCallback(double deltaTime, vector<unsigned char> *message, int inputNumber)
{
	// Juce will give us one MIDI command per callback, which makes processing easier for us.

    // Ignore sysex messages for now
    if(message.isSysEx())
        return;
    
    // Pull out the raw bytes
    int dataSize = message.getRawDataSize();
    if(dataSize <= 0)
        return;
    const unsigned char *messageData = message.getRawData();
	
    // if logging is active
    if (loggingActive)
    {
        ////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////
        //////////////////// BEGIN LOGGING /////////////////////
        
        int midi_channel = (int)(messageData[0]);
        int midi_number = dataSize > 1 ? (int)(messageData[1]) : 0;
        int midi_velocity = dataSize > 2 ? (int)(messageData[2]) : 0;
        timestamp_type timestamp = keyboard_.schedulerCurrentTimestamp();
        
        midiLog.write ((char*)&timestamp, sizeof (timestamp_type));
        midiLog.write ((char*)&midi_channel, sizeof (int));
        midiLog.write ((char*)&midi_number, sizeof (int));
        midiLog.write ((char*)&midi_velocity, sizeof (int));
        
        ///////////////////// END LOGGING //////////////////////
        ////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////
    }
        
#ifdef MIDI_INPUT_CONTROLLER_DEBUG_RAW
    if(source == 0)
        cout << "MIDI Input [internal]: ";
    else
        cout << "MIDI Input [" << source->getName() << "]: ";
	for(int debugPrint = 0; debugPrint < dataSize; debugPrint++)
		printf("%x ", messageData[debugPrint]);
	cout << endl;
#endif /* MIDI_INPUT_CONTROLLER_DEBUG_RAW */
    
    ScopedLock ksl(keyboard_.performanceDataMutex_);
    ScopedLock sl(segmentsMutex_);
    for(int i = 0; i < segments_.size(); i++) {
        if(segments_[i]->respondsToMessage(message))
            segments_[i]->midiHandlerMethod(source, message);
    }
}

// Destructor.  Free any existing callbacks
MidiInputController::~MidiInputController() {
    if(logFileCreated) {
        midiLog.close();
    }
	disableAllPorts();
    removeAllSegments();
}