view Source/TouchKeys/MidiKeyboardSegment.cpp @ 13:c0c62ccf4bfd

Enable virtual MIDI output ports
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Sat, 23 Nov 2013 17:47:29 +0000
parents c6f30c1e2bda
children 61e3c9df4674
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/>.
 
  =====================================================================
 
  MidiKeyboardSegment.cpp: handles incoming MIDI data and certain input-output
  mappings for one segment of a keyboard. The keyboard may be divided up into
  any number of segments with different behaviors. An important role of this
  class is to manage the output channel allocation when using one MIDI channel
  per note (for example, to handle polyphonic pitch bend).
*/

#include "MidiKeyboardSegment.h"
#include "MidiOutputController.h"
#include "../Mappings/MappingFactory.h"
#include "OscMidiConverter.h"
#include <algorithm>
#include <string>
#include <sstream>

#define DEBUG_MIDI_KEYBOARD_SEGMENT

const int MidiKeyboardSegment::kMidiControllerDamperPedal = 64;
const int MidiKeyboardSegment::kPedalActiveValue = 64;

// Constructor
MidiKeyboardSegment::MidiKeyboardSegment(PianoKeyboard& keyboard)
: keyboard_(keyboard), outputPortNumber_(0), mappingFactorySplitter_(keyboard),
  mappingFactoryUniqueIdentifier_(0),
  mode_(ModeOff), channelMask_(0),
  noteMin_(0), noteMax_(127), outputChannelLowest_(0), outputTransposition_(0),
  damperPedalEnabled_(true), touchkeyStandaloneMode_(false),
  usesKeyboardChannelPressure_(false), usesKeyboardPitchWheel_(false),
  usesKeyboardModWheel_(false), usesKeyboardMidiControllers_(false),
  pitchWheelRange_(2.0), useVoiceStealing_(false)
{
	// Register for OSC messages from the internal keyboard source
	setOscController(&keyboard_);
    keyboard_.setMappingFactory(this, &mappingFactorySplitter_);
    
    setAllControllerActionsTo(kControlActionBlock);
    resetControllerValues();
    
    for(int i = 0; i < 128; i++)
        noteOnsetTimestamps_[i] = 0;
}

// Destructor
MidiKeyboardSegment::~MidiKeyboardSegment() {
    removeAllMappingFactories();
    keyboard_.removeMappingFactory(this);
}

bool MidiKeyboardSegment::respondsToMessage(const MidiMessage& message) {
    int channel = message.getChannel();

    // If the message is not something universal, check if it matches our channel
    if(channel > 0) {
        if(!(channelMask_ && (1 << (channel - 1))))
            return false;
    }
    
    // If the message has a note number, see if it's in range
    if(message.isNoteOn() || message.isNoteOff() || message.isAftertouch()) {
        int noteNumber = message.getNoteNumber();
        if(noteNumber < noteMin_ || noteNumber > noteMax_)
            return false;
    }
    
    return true;
}

bool MidiKeyboardSegment::respondsToNote(int noteNumber) {
    if(noteNumber < noteMin_ || noteNumber > noteMax_)
        return false;
    return true;
}

// Listen on a given MIDI channel
void MidiKeyboardSegment::enableChannel(int channelNumber) {
    if(channelNumber >= 0 && channelNumber < 16)
        channelMask_ |= (1 << channelNumber);
}

// Listen on all MIDI channels
void MidiKeyboardSegment::enableAllChannels() {
    channelMask_ = 0xFFFF;
}

// Disable listening to a specific MIDI channel
void MidiKeyboardSegment::disableChannel(int channelNumber) {
    if(channelNumber >= 0 && channelNumber < 16)
        channelMask_ &= ~(1 << channelNumber);
}

// Disable all MIDI channels
void MidiKeyboardSegment::disableAllChanels() {
    channelMask_ = 0;
}

// Set the range of notes we listen to. Sets the range to between
// minNote and maxNote, inclusive.
void MidiKeyboardSegment::setNoteRange(int minNote, int maxNote) {
    // Sanity check
    if(minNote > maxNote)
        return;
    if(minNote < 0)
        noteMin_ = 0;
    else if(minNote > 127)
        noteMin_ = 127;
    else
        noteMin_ = minNote;

    if(maxNote < 0)
        noteMax_ = 0;
    else if(maxNote > 127)
        noteMax_ = 127;
    else
        noteMax_ = maxNote;
    
}

// Set the MIDI pitch wheel range
void MidiKeyboardSegment::setMidiPitchWheelRange(float semitones, bool send) {
    if(semitones < 0)
        pitchWheelRange_ = 0;
    else if(semitones > 48.0)
        pitchWheelRange_ = 48.0;
    else
        pitchWheelRange_ = semitones;
    
    if(send)
        sendMidiPitchWheelRange();
}

// Send the MIDI pitch wheel range RPN
// If in polyphonic mode, send to all channels; otherwise send only
// to the channel in question.
void MidiKeyboardSegment::sendMidiPitchWheelRange() {
    if(mode_ == ModePolyphonic) {
        for(int i = outputChannelLowest_; i < outputChannelLowest_ + retransmitMaxPolyphony_; i++)
            sendMidiPitchWheelRangeHelper(i);
    }
    else
        sendMidiPitchWheelRangeHelper(outputChannelLowest_);
}


// Enable TouchKeys standalone mode (no MIDI input, touch triggers note)
void MidiKeyboardSegment::enableTouchkeyStandaloneMode() {
    if(touchkeyStandaloneMode_)
        return;
    
    addOscListener("/touchkeys/on");
    addOscListener("/touchkeys/off");
    touchkeyStandaloneMode_ = true;
}

// Disable TouchKeys standalone mode (no MIDI input, touch triggers note)
void MidiKeyboardSegment::disableTouchkeyStandaloneMode() {
    if(!touchkeyStandaloneMode_)
        return;

    removeOscListener("/touchkeys/on");
    removeOscListener("/touchkeys/off");
    touchkeyStandaloneMode_ = false;
}

// Disable any currently active notes
void MidiKeyboardSegment::allNotesOff() {
	// TODO: implement me
}

// Reset controller values to defaults
void MidiKeyboardSegment::resetControllerValues() {
    // Most controls default to 0
    for(int i = 0; i < kControlMax; i++)
        controllerValues_[i] = 0;
    // ...except pitch wheel, which defaults to center
    controllerValues_[kControlPitchWheel] = 8192;
}

// Set the operating mode of the controller.  The mode determines the behavior in
// response to incoming MIDI data.

void MidiKeyboardSegment::setMode(int mode) {
    if(mode == ModePassThrough)
        setModePassThrough();
    else if(mode == ModeMonophonic)
        setModeMonophonic();
    else if(mode == ModePolyphonic)
        setModePolyphonic();
    else
        setModeOff();
}

void MidiKeyboardSegment::setModeOff() {
	allNotesOff();
	removeAllOscListeners();
    setAllControllerActionsTo(kControlActionBlock);
	mode_ = ModeOff;
}

void MidiKeyboardSegment::setModePassThrough() {
	allNotesOff();
	removeAllOscListeners();
    setAllControllerActionsTo(kControlActionPassthrough);
	mode_ = ModePassThrough;
}

void MidiKeyboardSegment::setModeMonophonic() {
	allNotesOff();
	removeAllOscListeners();
    setAllControllerActionsTo(kControlActionPassthrough);
	mode_ = ModeMonophonic;
}

void MidiKeyboardSegment::setModePolyphonic() {
	// First turn off any notes in the current mode
	allNotesOff();
	removeAllOscListeners();
    setAllControllerActionsTo(kControlActionBroadcast);
	
	// Register a callback for touchkey data.  When we get a note-on message,
	// we request this callback occur once touch data is available.  In this mode,
	// we know the eventual channel before any touch data ever occurs: thus, we
	// only listen to the MIDI onset itself, which happens after all the touch
	// data is sent out.
	addOscListener("/midi/noteon");
    
	mode_ = ModePolyphonic;
	
    if(retransmitMaxPolyphony_ < 1)
        retransmitMaxPolyphony_ = 1;
    modePolyphonicSetupHelper();
}

// Set the maximum polyphony, affecting polyphonic mode only
void MidiKeyboardSegment::setPolyphony(int polyphony) {
    // First turn off any notes if this affects current polyphonic mode
    // (other modes unaffected so we can make these changes in background)
    if(mode_ == ModePolyphonic)
        allNotesOff();
    
    if(polyphony < 1)
        retransmitMaxPolyphony_ = 1;
    else if(polyphony > 16)
        retransmitMaxPolyphony_ = 16;
    else
        retransmitMaxPolyphony_ = polyphony;
    modePolyphonicSetupHelper();
}

// Set whether the damper pedal is enabled or not
void MidiKeyboardSegment::setDamperPedalEnabled(bool enable) {
    if(damperPedalEnabled_ && !enable) {
        // Pedal was enabled before, now it isn't. Clear out any notes
        // currently in the pedal so they can be retaken.
        damperPedalWentOff();
    }
    
    damperPedalEnabled_ = enable;
}

// Handle an incoming MIDI message
void MidiKeyboardSegment::midiHandlerMethod(MidiInput* source, const MidiMessage& message) {
    // Log the timestamps of note onsets and releases, regardless of the mode
    // of processing
    if(message.isNoteOn()) {
        if(message.getNoteNumber() >= 0 && message.getNoteNumber() < 128)
            noteOnsetTimestamps_[message.getNoteNumber()] = keyboard_.schedulerCurrentTimestamp();
    }
    else if(message.isNoteOff()) {
        // Remove the onset timestamp unless we have the specific condition:
        // (damper pedal enabled) && (pedal is down) && (polyphonic mode)
        // In this condition, onsets will be removed when note goes off
        if(message.getNoteNumber() >= 0 && message.getNoteNumber() < 128) {
            if(!damperPedalEnabled_ || controllerValues_[kMidiControllerDamperPedal] < kPedalActiveValue || mode_ != ModePolyphonic) {
                noteOnsetTimestamps_[message.getNoteNumber()] = 0;
            }
        }
    }
    else if(message.isAllNotesOff() || message.isAllSoundOff()) {
        for(int i = 0; i < 128; i++)
            noteOnsetTimestamps_[i] = 0;
    }
    
    // Log the values of incoming control changes in case mappings need to use them later
    if(message.isController() && !(message.isAllNotesOff() || message.isAllSoundOff())) {
        // Handle damper pedal specially: it may affect note allocation
        if(message.getControllerNumber() == kMidiControllerDamperPedal) {
            if(message.getControllerValue() < kPedalActiveValue &&
               controllerValues_[kMidiControllerDamperPedal] >= kPedalActiveValue) {
                damperPedalWentOff();
            }
        }
        
        if(message.getControllerNumber() >= 0 && message.getControllerNumber() < 128) {
            if((message.getControllerNumber() == 1 && usesKeyboardModWheel_) ||
               (message.getControllerNumber() != 1 && usesKeyboardMidiControllers_)) {
                controllerValues_[message.getControllerNumber()] = message.getControllerValue();
                handleControlChangeRetransit(message.getControllerNumber(), message);
            }
        }
    }
    else if(message.isChannelPressure()) {
        if(usesKeyboardChannelPressure_) {
            controllerValues_[kControlChannelAftertouch] = message.getChannelPressureValue();
            handleControlChangeRetransit(kControlChannelAftertouch, message);
        }
    }
    else if(message.isPitchWheel()) {
        if(usesKeyboardPitchWheel_) {
            controllerValues_[kControlPitchWheel] = message.getPitchWheelValue();
            handleControlChangeRetransit(kControlPitchWheel, message);
        }
    }
    else {
        // Process the message differently depending on the current mode
        switch(mode_) {
            case ModePassThrough:
                modePassThroughHandler(source, message);
                break;
            case ModeMonophonic:
                modeMonophonicHandler(source, message);
                break;
            case ModePolyphonic:
                modePolyphonicHandler(source, message);
                break;
            case ModeOff:
            default:
                // Ignore message
                break;
        }
    }
}

// OscHandler method which parses incoming OSC messages we've registered for.  In this case,
// we use OSC callbacks to find out about touch data for notes we want to trigger.

bool MidiKeyboardSegment::oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data) {
    if(touchkeyStandaloneMode_) {
        if(!strcmp(path, "/touchkeys/on") && numValues > 0) {
            int noteNumber = values[0]->i;
            if(noteNumber >= 0 && noteNumber < 128) {
                // Generate MIDI note on for this message
                MidiMessage msg(MidiMessage::noteOn(1, noteNumber, (uint8)64));
                midiHandlerMethod(0, msg);
            }
            return true;
        }
        else if(!strcmp(path, "/touchkeys/off") && numValues > 0) {
            int noteNumber = values[0]->i;
            if(noteNumber >= 0 && noteNumber < 128) {
                // Generate MIDI note off for this message
                MidiMessage msg(MidiMessage::noteOff(1, noteNumber));
                midiHandlerMethod(0, msg);
            }
            return true;
        }
    }
    
	if(mode_ == ModePolyphonic) {
		modePolyphonicNoteOnCallback(path, types, numValues, values);
	}
	
	return true;
}

// Acquire an OSC-MIDI converter. If a converter for this control already exists,
// return it. If not, create it. This method keeps track of how many objects have
// acquired the converter. When all acquirers have released ihe converter, it is
// destroyed.
OscMidiConverter* MidiKeyboardSegment::acquireOscMidiConverter(int controlId) {
    OscMidiConverter *converter;
    
    if(oscMidiConverters_.count(controlId) == 0) {
        converter = new OscMidiConverter(keyboard_, *this, controlId);
        converter->setMidiOutputController(midiOutputController_);
        
        oscMidiConverters_[controlId] = converter;
        oscMidiConverterReferenceCounts_[controlId] = 1;
    }
    else {
        if(oscMidiConverterReferenceCounts_.count(controlId) == 0) {
            cerr << "BUG: found a converter with missing reference counter\n";
        }
        else if(oscMidiConverterReferenceCounts_[controlId] <= 0) {
            cerr << "BUG: found a converter with no references\n";
            oscMidiConverterReferenceCounts_[controlId] = 1;
        }
        else
            oscMidiConverterReferenceCounts_[controlId]++;
        converter = oscMidiConverters_[controlId];
    }
    
    return converter;
}

// Release a previously acquired OSC-MIDI converter. This call must be paired with
// acquireOscMidiConverter.
void MidiKeyboardSegment::releaseOscMidiConverter(int controlId) {
    if(oscMidiConverters_.count(controlId) == 0 ||
       oscMidiConverterReferenceCounts_.count(controlId) == 0) {
        cerr << "BUG: releasing a missing OSC-MIDI converter\n";
        return;
    }
    
    oscMidiConverterReferenceCounts_[controlId]--;
    if(oscMidiConverterReferenceCounts_[controlId] == 0) {
        // This was the last release of this converter. Delete it.
        OscMidiConverter *converter = oscMidiConverters_[controlId];
        delete converter;
        oscMidiConverters_.erase(controlId);
        oscMidiConverterReferenceCounts_.erase(controlId);
    }
}

// Helper predicate function for filtering strings
inline bool char_is_not_alphanumeric(int c) {
    return !std::isalnum(c);
}

// Create a new mapping factory for this segment. A pointer should be passed in
// of a newly-allocated object. It will be released upon removal.
void MidiKeyboardSegment::addMappingFactory(MappingFactory* factory, bool autoGenerateName) {
    // Check for duplicates-- can't add the same factory twice
    vector<MappingFactory*>::iterator it;
    for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
        if(*it == factory)
            return;
    }
    
    // If the name should autogenerate, find a unique name for this factory
    if(autoGenerateName) {
        std::string baseName = factory->factoryTypeName();
        
        // Remove spaces, newlines, other invalid characters, leaving only alphanumerics
        baseName.erase(std::remove_if(baseName.begin(), baseName.end(), char_is_not_alphanumeric),
                       baseName.end());
        std::transform(baseName.begin(), baseName.end(), baseName.begin(), ::tolower);

        // Now look for an OSC path with this name. If found, add a number to the end, incrementing
        // it until a unique name is found
        std::string name = baseName;
        bool isUnique = false;
        int appendDigit = 2;
        
        while(!isUnique) {
            isUnique = true;
            
            for(int i = 0; i < mappingFactories_.size(); i++) {
                std::string compareName = mappingFactories_[i]->getName();
                int lastSeparator = compareName.find_last_of('/');
                if((lastSeparator != std::string::npos) && (lastSeparator < compareName.size() - 1))
                    compareName = compareName.substr(lastSeparator + 1);
                
                if(name == compareName) {
                    isUnique = false;
                    
                    // Try a new name with a new digit at the end...
                    std::stringstream ss;
                    ss << baseName << appendDigit;
                    name = ss.str();
                    appendDigit++;
                    
                    break;
                }
            }
        }
        
        factory->setName(name);
    }
    
    // Add factory to internal vector, and add it to splitter class
    mappingFactories_.push_back(factory);
    mappingFactorySplitter_.addFactory(factory);
    mappingFactoryUniqueIdentifier_++;
}

// Remove a mapping factory, releasing the associated object.
void MidiKeyboardSegment::removeMappingFactory(MappingFactory* factory) {
    vector<MappingFactory*>::iterator it;
    
    for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
        if(*it == factory) {
            mappingFactorySplitter_.removeFactory(factory);
            delete factory;
            mappingFactories_.erase(it);
            break;
        }
    }
    
    mappingFactoryUniqueIdentifier_++;
}

// Remove all mapping factories, releasing each one
void MidiKeyboardSegment::removeAllMappingFactories() {
    vector<MappingFactory*>::iterator it;
    
    mappingFactorySplitter_.removeAllFactories();
    for(it = mappingFactories_.begin(); it != mappingFactories_.end(); ++it) {
        delete *it;
    }
    
    mappingFactories_.clear();
    mappingFactoryUniqueIdentifier_++;
}

// Return a list of current mapping factories.
vector<MappingFactory*> const& MidiKeyboardSegment::mappingFactories(){
    return mappingFactories_;
}

// Mode-specific MIDI handlers.  These methods handle incoming MIDI data according to the rules
// defined by a particular mode of operation.

// Pass-Through: Retransmit any input data to the output unmodified.
void MidiKeyboardSegment::modePassThroughHandler(MidiInput* source, const MidiMessage& message) {
    // Check if there is a note on or off, and update the keyboard class accordingly
    if(message.isNoteOn()) {
        int note = message.getNoteNumber();
        if(keyboard_.key(note) != 0)
            keyboard_.key(note)->midiNoteOn(this, message.getVelocity(), message.getChannel() - 1,
                                            keyboard_.schedulerCurrentTimestamp());
        
        // Retransmit, possibly with transposition
        if(midiOutputController_ != 0) {
            MidiMessage newMessage = MidiMessage::noteOn(message.getChannel(), message.getNoteNumber() + outputTransposition_, message.getVelocity());
            midiOutputController_->sendMessage(outputPortNumber_, newMessage);
        }
    }
    else if(message.isNoteOff()) {
        int note = message.getNoteNumber();
        if(keyboard_.key(note) != 0)
            keyboard_.key(note)->midiNoteOff(this, keyboard_.schedulerCurrentTimestamp());
        
        // Retransmit, possibly with transposition
        if(midiOutputController_ != 0) {
            MidiMessage newMessage = MidiMessage::noteOff(message.getChannel(), message.getNoteNumber() + outputTransposition_);
            midiOutputController_->sendMessage(outputPortNumber_, newMessage);
        }
    }
    else if(message.isAftertouch()) { // Polyphonic aftertouch: adjust to transposition
        if(midiOutputController_ != 0) {
            MidiMessage newMessage = MidiMessage::aftertouchChange(message.getChannel(), message.getNoteNumber() + outputTransposition_, message.getAfterTouchValue());
            midiOutputController_->sendMessage(outputPortNumber_, newMessage);
        }
    }
    else if(midiOutputController_ != 0) // Anything else goes through unchanged
        midiOutputController_->sendMessage(outputPortNumber_, message);
}

// Monophonic: all MIDI messages pass through to the output, which is assumed to be a monosynth.
// However the most recent key which determines the currently sounding note will have its mapping
// active; all others are suspended.
void MidiKeyboardSegment::modeMonophonicHandler(MidiInput* source, const MidiMessage& message) {
    if(message.isNoteOn()) {
        // First suspend any other mappings. This might include the current note if the touch
        // data has caused a mapping to be created.
        if(keyboard_.mappingFactory(this) != 0) {
            keyboard_.mappingFactory(this)->suspendAllMappings();
        }
        
        // And turn on note on MIDI controller
        if(midiOutputController_ != 0) {
            MidiMessage newMessage = MidiMessage::noteOn(message.getChannel(), message.getNoteNumber() + outputTransposition_, message.getVelocity());
            midiOutputController_->sendMessage(outputPortNumber_, newMessage);
        }
        
        // Now start the next one
        int note = message.getNoteNumber();
        if(keyboard_.key(note) != 0)
            keyboard_.key(note)->midiNoteOn(this, message.getVelocity(),
                                            message.getChannel() - 1, keyboard_.schedulerCurrentTimestamp());
        
        // Now resume the current note's mapping
        if(keyboard_.mappingFactory(this) != 0) {
            keyboard_.mappingFactory(this)->resumeMapping(note, true);
        }
    }
    else if(message.isNoteOff()) {
        // First stop this note
        int note = message.getNoteNumber();
        if(keyboard_.key(note) != 0)
            keyboard_.key(note)->midiNoteOff(this, keyboard_.schedulerCurrentTimestamp());
        
        // Then reactivate the most recent note's mappings
        if(keyboard_.mappingFactory(this) != 0) {
            int newest = newestNote();
            if(newest >= 0)
                keyboard_.mappingFactory(this)->resumeMapping(newest, true);
        }
        
        // And turn off note on MIDI controller
        if(midiOutputController_ != 0) {
            MidiMessage newMessage = MidiMessage::noteOff(message.getChannel(), message.getNoteNumber() + outputTransposition_, message.getVelocity());
            midiOutputController_->sendMessage(outputPortNumber_, newMessage);
        }
    }
}

// Polyphonic: Each incoming note gets its own unique MIDI channel so its controllers
// can be manipulated separately (e.g. by touchkey data).  Keep track of available channels
// and currently active notes.
void MidiKeyboardSegment::modePolyphonicHandler(MidiInput* source, const MidiMessage& message) {
	
    if(message.getRawDataSize() <= 0)
        return;
    const unsigned char *rawData = message.getRawData();
    
	if(rawData[0] == kMidiMessageReset) {
		// Reset state and pass along to all relevant channels
		
		retransmitChannelForNote_.clear();	// Clear current note information
		retransmitChannelsAvailable_.clear();
        retransmitNotesHeldInPedal_.clear();
		for(int i = 0; i < retransmitMaxPolyphony_; i++) {
			retransmitChannelsAvailable_.insert(i);
		}
		if(midiOutputController_ != 0)
			midiOutputController_->sendReset(outputPortNumber_);
	}
    else if(message.isNoteOn()) {
        if(retransmitChannelForNote_.count(message.getNoteNumber()) > 0
           && retransmitNotesHeldInPedal_.count(message.getNoteNumber()) == 0) {
            // Case (2)-- retrigger an existing note
            if(midiOutputController_ != 0) {
                midiOutputController_->sendNoteOn(outputPortNumber_, retransmitChannelForNote_[message.getNoteNumber()],
                                                  message.getNoteNumber() + outputTransposition_, message.getVelocity());
            }
        }
        else {
            // New note
            modePolyphonicNoteOn(message.getNoteNumber(), message.getVelocity());
        }
    }
    else if(message.isNoteOff()) {
        modePolyphonicNoteOff(message.getNoteNumber());
    }
    else if(message.isAllNotesOff() || message.isAllSoundOff()) {
        retransmitChannelForNote_.clear();	// Clear current note information
        retransmitChannelsAvailable_.clear();
        retransmitNotesHeldInPedal_.clear();
        for(int i = 0; i < retransmitMaxPolyphony_; i++)
            retransmitChannelsAvailable_.insert(i);
    }
    else if(message.isAftertouch()) { // polyphonic aftertouch
        if(retransmitChannelForNote_.count(message.getNoteNumber()) > 0) {
            int retransmitChannel = retransmitChannelForNote_[message.getNoteNumber()];
            if(midiOutputController_ != 0) {
                midiOutputController_->sendAftertouchPoly(outputPortNumber_, retransmitChannel,
                                                          message.getNoteNumber() + outputTransposition_, message.getAfterTouchValue());
            }
        }
    }
}

// Handle note on message in polyphonic mode.  Allocate a new channel
// for this note and rebroadcast it.
void MidiKeyboardSegment::modePolyphonicNoteOn(unsigned char note, unsigned char velocity) {
    int newChannel = -1;
    
    if(retransmitNotesHeldInPedal_.count(note) > 0) {
        // For notes that are still sounding in the pedal, reuse the same MIDI channel
        // they had before.
        if(retransmitChannelForNote_.count(note) > 0)
            newChannel = retransmitChannelForNote_[note];
        else {
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
            cout << "BUG: note " << note << " held in pedal but has no channel\n";
#endif
            retransmitNotesHeldInPedal_.erase(note);
            return;
        }
        
        // No longer held in pedal: it will be an active note again with the same channel
        retransmitNotesHeldInPedal_.erase(note);
    }
    else {
        // Otherwise, allocate a new channel to this note
        if(retransmitChannelsAvailable_.size() == 0) {
            if(damperPedalEnabled_) {
                // First priority is always to take a note that is being sustained
                // in the pedal but not actively held. This is true whether or not
                // voice stealing is enabled.
                int oldNote = oldestNoteInPedal();
                int oldChannel = -1;
                if(retransmitChannelForNote_.count(oldNote) > 0)
                    oldChannel = retransmitChannelForNote_[oldNote];
                if(oldNote >= 0) {
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
                    cout << "Stealing note " << oldNote << " from pedal for note " << (int)note << endl;
#endif
                    modePolyphonicNoteOff(oldNote, true);
                    if(oldChannel >= 0) {
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllNotesOff, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllSoundOff, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal,
                        //                                         controllerValues_[kMidiControllerDamperPedal]);
                    }
                }
            }
            
            // Now try again...
            if(retransmitChannelsAvailable_.size() == 0) {
                if(useVoiceStealing_) {
                    // Find the voice with the oldest timestamp and turn it off
                    int oldNote = oldestNote();
                    int oldChannel = -1;
                    if(retransmitChannelForNote_.count(oldNote) > 0)
                        oldChannel = retransmitChannelForNote_[oldNote];
                    if(oldNote < 0) {
                        // Shouldn't happen...
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
                        cout << "No notes present, but no MIDI output channel available for note " << (int)note << endl;
#endif
                        return;
                    }
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
                    cout << "Stealing note " << oldNote << " for note " << (int)note << endl;
#endif
                    modePolyphonicNoteOff(oldNote, true);
                    if(oldChannel >= 0) {
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllNotesOff, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControlAllSoundOff, 0);
                        //midiOutputController_->sendControlChange(outputPortNumber_, oldChannel, kMidiControllerDamperPedal,
                        //                                         controllerValues_[kMidiControllerDamperPedal]);
                    }
                }
                else {
                    // No channels available.  Print a warning and finish
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
                    cout << "No MIDI output channel available for note " << (int)note << endl;
#endif
                    return;
                }
            }
        }
        
        // Request the first available channel
        newChannel = *retransmitChannelsAvailable_.begin();
        retransmitChannelsAvailable_.erase(newChannel);
        retransmitChannelForNote_[note] = newChannel;
	}
    
	if(keyboard_.key(note) != 0) {
		keyboard_.key(note)->midiNoteOn(this, velocity, newChannel, keyboard_.schedulerCurrentTimestamp());
	}
	
	// The above function will cause a callback to be generated, which in turn will generate
	// the Note On message.
}

// Handle note off message in polyphonic mode.  Release any channel
// associated with this note.
void MidiKeyboardSegment::modePolyphonicNoteOff(unsigned char note, bool forceOff) {    
	// If no channel associated with this note, ignore it
	if(retransmitChannelForNote_.count(note) == 0) {
        if(note >= 0 && note < 128)
            noteOnsetTimestamps_[note] = 0;
		return;
    }
    
	if(keyboard_.key(note) != 0) {
		keyboard_.key(note)->midiNoteOff(this, keyboard_.schedulerCurrentTimestamp());
	}
	
	// Send a Note Off message to the appropriate channel
	if(midiOutputController_ != 0) {
		midiOutputController_->sendNoteOff(outputPortNumber_, retransmitChannelForNote_[note], note + outputTransposition_);
	}
	
    // If the pedal is enabled and currently active, don't re-enable this channel
    // just yet. Instead, let the note continue ringing until we have to steal it later.
    if(damperPedalEnabled_ && controllerValues_[kMidiControllerDamperPedal] >= kPedalActiveValue && !forceOff) {
        retransmitNotesHeldInPedal_.insert(note);
    }
    else {
        // Otherwise release the channel mapping associated with this note
        if(retransmitNotesHeldInPedal_.count(note) > 0)
            retransmitNotesHeldInPedal_.erase(note);
        retransmitChannelsAvailable_.insert(retransmitChannelForNote_[note]);
        retransmitChannelForNote_.erase(note);
        if(note >= 0 && note < 128)
            noteOnsetTimestamps_[note] = 0;
    }
}

// Callback function after we request a note on.  PianoKey class will respond
// with touch data (if available within a specified timeout), or with a frame
// indicating an absence of touch data.  Once we receive this, we can send the
// MIDI note on message.

void MidiKeyboardSegment::modePolyphonicNoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values) {
	if(numValues < 3)	// Sanity check: first 3 values hold MIDI information
		return;
	if(types[0] != 'i' || types[1] != 'i' || types[2] != 'i')
		return;
	
	int midiNote = values[0]->i;
	int midiChannel = values[1]->i;
	int midiVelocity = values[2]->i;
	
	if(midiNote < 0 || midiNote > 127)
		return;
    // If there are multiple segments of the keyboard active, there may be OSC
    // messages generated from keys that didn't come from us. Don't grab them by mistake.
    // FIXME: the real fix here is to include a source ID with the OSC message
    if(!respondsToNote(midiNote))
        return;
	
	// Send the Note On message to the correct channel
	if(midiOutputController_ != 0) {
		midiOutputController_->sendNoteOn(outputPortNumber_, midiChannel, midiNote + outputTransposition_, midiVelocity);
	}
}

// Private helper method to handle changes in polyphony
void MidiKeyboardSegment::modePolyphonicSetupHelper() {
    if(retransmitMaxPolyphony_ > 16)
		retransmitMaxPolyphony_ = 16;	// Limit polyphony to 16 (number of MIDI channels
    retransmitChannelsAvailable_.clear();
	for(int i = outputChannelLowest_; i < outputChannelLowest_ + retransmitMaxPolyphony_; i++)
		retransmitChannelsAvailable_.insert(i);
	retransmitChannelForNote_.clear();
    retransmitNotesHeldInPedal_.clear();
}

// Find the oldest onset of the currently playing notes. Used for voice stealing.
// Returns -1 if no notes are playing.
int MidiKeyboardSegment::oldestNote() {
    int oldestNoteNumber = -1;
    timestamp_type oldestTimestamp = missing_value<timestamp_type>::missing();
    
    for(int i = 0; i < 128; i++) {
        if(missing_value<timestamp_type>::isMissing(oldestTimestamp) && noteOnsetTimestamps_[i] != 0) {
            oldestNoteNumber = i;
            oldestTimestamp = noteOnsetTimestamps_[i];
        }
        else if(noteOnsetTimestamps_[i] < oldestTimestamp && noteOnsetTimestamps_[i] != 0) {
            oldestNoteNumber = i;
            oldestTimestamp = noteOnsetTimestamps_[i];
        }
    }
    
    return oldestNoteNumber;
}

// Finds the oldest onset of the notes currently finished but sustaining in the pedal.
// Used for voice stealing. Returns -1 if no notes are held in the pedal.
int MidiKeyboardSegment::oldestNoteInPedal() {
    if(!damperPedalEnabled_ || retransmitNotesHeldInPedal_.empty())
        return -1;
    
    set<int>::iterator it;
    int oldestNoteNumber = -1;
    timestamp_type oldestTimestamp = missing_value<timestamp_type>::missing();
    
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
    cout << "notes in pedal: ";
#endif
    
    for(it = retransmitNotesHeldInPedal_.begin(); it != retransmitNotesHeldInPedal_.end(); ++it) {
        int note = *it;
        timestamp_type timestamp;
        if(noteOnsetTimestamps_[note] != 0)
            timestamp = noteOnsetTimestamps_[note];
        else
            timestamp = 0; // Why is there a note held in pedal with no onset? Steal it!
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
        cout << note << " (" << timestamp << ") ";
#endif
        
        if(missing_value<timestamp_type>::isMissing(oldestTimestamp)) {
            oldestNoteNumber = note;
            oldestTimestamp = timestamp;
        }
        else if(timestamp < oldestTimestamp) {
            oldestNoteNumber = note;
            oldestTimestamp = timestamp;
        }
    }
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
    cout << endl;
#endif
    
    return oldestNoteNumber;
}

// Find the newest onset of the currently playing notes. Used for monophonic mode.
// Returns -1 if no notes are playing.
int MidiKeyboardSegment::newestNote() {
    int newestNoteNumber = -1;
    timestamp_type newestTimestamp = missing_value<timestamp_type>::missing();
    
    for(int i = 0; i < 128; i++) {
        if(missing_value<timestamp_type>::isMissing(newestTimestamp) && noteOnsetTimestamps_[i] != 0) {
            newestNoteNumber = i;
            newestTimestamp = noteOnsetTimestamps_[i];
        }
        else if(noteOnsetTimestamps_[i] > newestTimestamp && noteOnsetTimestamps_[i] != 0) {
            newestNoteNumber = i;
            newestTimestamp = noteOnsetTimestamps_[i];
        }
    }
    
    return newestNoteNumber;
}

// Given a controller number (including special "controllers" channel-pressure and pitch-wheel),
// retransit or not to outgoing MIDI channels depending on the current behaviour defined in
// controllerActions_.
void MidiKeyboardSegment::handleControlChangeRetransit(int controllerNumber, const MidiMessage& message) {
    if(midiOutputController_ == 0)
        return;
    if(controllerActions_[controllerNumber] == kControlActionPassthrough) {
        // Tell OSC-MIDI converter to resend if present, otherwise pass through
        if(oscMidiConverters_.count(controllerNumber) != 0) {
            oscMidiConverters_[controllerNumber]->resend(message.getChannel() - 1);
        }
        else {
            // KLUDGE
            if(controllerNumber == 64) {
                MidiMessage newMessage = MidiMessage::controllerEvent(message.getChannel(), 67, 127 - message.getControllerValue());
                midiOutputController_->sendMessage(outputPortNumber_, newMessage);
            }
            else {
                // Send this control change through unchanged
                midiOutputController_->sendMessage(outputPortNumber_, message);
            }
        }
    }
    else if(controllerActions_[controllerNumber] == kControlActionBroadcast) {
        // Send this control change to all active channels
        MidiMessage newMessage(message); // Modifiable copy of the original message
        
        if(oscMidiConverters_.count(controllerNumber) != 0) {
            for(int i = 0; i < retransmitMaxPolyphony_; i++)
                oscMidiConverters_[controllerNumber]->resend(i);
        }
        else {
            for(int i = 0; i < retransmitMaxPolyphony_; i++) {
                newMessage.setChannel(i + 1); // Juce uses 1-16, we use 0-15
                midiOutputController_->sendMessage(outputPortNumber_, newMessage);
            }
        }
    }
    else if(controllerActions_[controllerNumber] == kControlActionSendToLatest) {
        // Send this control change to the channel of the most recent note
        int noteNumber = newestNote();
        if(noteNumber < 0)
            return;
        if(keyboard_.key(noteNumber) != 0) {
            int channel = keyboard_.key(noteNumber)->midiChannel();
            
            if(oscMidiConverters_.count(controllerNumber) != 0)
                oscMidiConverters_[controllerNumber]->resend(channel);
            else {
                MidiMessage newMessage(message); // Modifiable copy of the original message
                newMessage.setChannel(channel + 1);  // Juce uses 1-16, we use 0-15
                midiOutputController_->sendMessage(outputPortNumber_, newMessage);
            }
        }
    }
    else {} // Block or unknown action
}

// Set all controllers to behave a particular way when messages received
void MidiKeyboardSegment::setAllControllerActionsTo(int action) {
    for(int i = 0; i < kControlMax; i++)
        controllerActions_[i] = action;
}

// Pedal went off. If we're saving notes in the pedal, release them
void MidiKeyboardSegment::damperPedalWentOff() {
    if(!damperPedalEnabled_)
        return;
    // Go through a list of any notes currently in the damper pedal and release them
    set<int>::iterator it;
    for(it = retransmitNotesHeldInPedal_.begin(); it != retransmitNotesHeldInPedal_.end(); ++it) {
        int note = *it;
#ifdef DEBUG_MIDI_KEYBOARD_SEGMENT
        cout << "releasing note " << note << " on channel " << retransmitChannelForNote_[note] << endl;
#endif
        retransmitChannelsAvailable_.insert(retransmitChannelForNote_[note]);
        retransmitChannelForNote_.erase(note);
        noteOnsetTimestamps_[note] = 0;
    }
    retransmitNotesHeldInPedal_.clear();
}

// Handle the actual sending of the pitch wheel range RPN to a specific channel
void MidiKeyboardSegment::sendMidiPitchWheelRangeHelper(int channel) {
    if(midiOutputController_ == 0)
        return;
    
    // Find number of semitones and cents
    int majorRange = (int)floorf(pitchWheelRange_);
    int minorRange = (int)(100.0 * (pitchWheelRange_ - floorf(pitchWheelRange_)));
    
    // Set RPN controller = 0
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 101, 0);
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 100, 0);
    // Set data value MSB/LSB for bend range in semitones
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 6, majorRange);
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 38, minorRange);
    // Set RPN controller back to 16383
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 101, 127);
    midiOutputController_->sendControlChange(outputPortNumber_, channel, 100, 127);
}