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: MidiKeyboardSegment.h: handles incoming MIDI data and certain input-output andrewm@0: mappings for one segment of a keyboard. The keyboard may be divided up into andrewm@0: any number of segments with different behaviors. An important role of this andrewm@0: class is to manage the output channel allocation when using one MIDI channel andrewm@0: per note (for example, to handle polyphonic pitch bend). andrewm@0: */ andrewm@0: andrewm@0: andrewm@0: #ifndef __TouchKeys__MidiKeyboardSegment__ andrewm@0: #define __TouchKeys__MidiKeyboardSegment__ andrewm@0: andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include andrewm@0: #include "../JuceLibraryCode/JuceHeader.h" andrewm@0: #include "PianoKeyboard.h" andrewm@0: #include "../Mappings/MappingFactorySplitter.h" andrewm@0: andrewm@0: class OscMidiConverter; andrewm@0: andrewm@0: // This class handles the processing of MIDI input data for a particular andrewm@0: // segment of the keyboard. It defines the processing mode and stores certain andrewm@0: // state information about active notes for this particular part of the keyboard. andrewm@0: // The MidiInputController class will use one or more of these segments to define andrewm@0: // keyboard behavior. In the case of a split keyboard arrangement, MIDI channel andrewm@0: // or note number might determine which segment takes ownership of a particular note. andrewm@0: andrewm@0: class MidiKeyboardSegment : public OscHandler { andrewm@0: private: andrewm@0: static const int kMidiControllerDamperPedal; andrewm@53: static const int kMidiControllerSostenutoPedal; andrewm@0: static const int kPedalActiveValue; andrewm@0: andrewm@0: public: andrewm@0: // Operating modes for MIDI input on this segment andrewm@0: enum { andrewm@0: ModeOff = 0, andrewm@0: ModePassThrough, andrewm@0: ModeMonophonic, andrewm@53: ModePolyphonic, andrewm@53: ModeMPE andrewm@0: }; andrewm@0: andrewm@0: // The MIDI Pitch Wheel is not handled by control change like the others, andrewm@0: // but it is something we will want to map to. Use a special control number andrewm@0: // to designate mapping OSC to the Pitch Wheel. Use 14 bit values when mapping andrewm@0: // to this control. Similarly, we might want to map to channel aftertouch value. andrewm@0: // The mechanics here are identical to 7-bit controllers. andrewm@0: enum { andrewm@0: kControlDisabled = -1, andrewm@0: kControlPitchWheel = 128, andrewm@0: kControlChannelAftertouch, andrewm@0: kControlPolyphonicAftertouch, andrewm@0: kControlMax andrewm@0: }; andrewm@0: andrewm@0: enum { andrewm@0: kControlActionPassthrough = 0, andrewm@0: kControlActionBroadcast, andrewm@0: kControlActionSendToLatest, andrewm@0: kControlActionBlock andrewm@0: }; andrewm@0: andrewm@0: public: andrewm@0: // Constructor andrewm@0: MidiKeyboardSegment(PianoKeyboard& keyboard); andrewm@0: andrewm@0: // Destructor andrewm@0: ~MidiKeyboardSegment(); andrewm@0: andrewm@0: // Set/query the output controller andrewm@0: MidiOutputController* midiOutputController() { return midiOutputController_; } andrewm@0: void setMidiOutputController(MidiOutputController* ct) { midiOutputController_ = ct; } andrewm@0: andrewm@0: // Check whether this MIDI message is for this segment andrewm@0: bool respondsToMessage(const MidiMessage& message); andrewm@0: bool respondsToNote(int noteNumber); andrewm@0: andrewm@0: // Set which channels we listen to andrewm@0: void enableChannel(int channelNumber); andrewm@0: void enableAllChannels(); andrewm@0: void disableChannel(int channelNumber); andrewm@0: void disableAllChanels(); andrewm@0: void setChannelMask(int channelMask) { channelMask_ = channelMask; } andrewm@0: andrewm@0: // Set which notes we listen to andrewm@0: void setNoteRange(int minNote, int maxNote); andrewm@0: std::pair noteRange() { return std::pair(noteMin_, noteMax_); } andrewm@0: andrewm@0: // Set whether or not we use aftertouch, pitchwheel or other controls andrewm@0: // directly from the keyboard andrewm@0: bool usesKeyboardChannnelPressure() { return usesKeyboardChannelPressure_; } andrewm@0: void setUsesKeyboardChannelPressure(bool use) { andrewm@0: usesKeyboardChannelPressure_ = use; andrewm@0: // Reset to default if not using andrewm@0: if(!use) andrewm@0: controllerValues_[kControlChannelAftertouch] = 0; andrewm@0: } andrewm@0: andrewm@0: bool usesKeyboardPitchWheel() { return usesKeyboardPitchWheel_; } andrewm@0: void setUsesKeyboardPitchWheel(bool use) { andrewm@0: usesKeyboardPitchWheel_ = use; andrewm@0: // Reset to default if not using andrewm@0: if(!use) andrewm@0: controllerValues_[kControlPitchWheel] = 8192; andrewm@0: } andrewm@5: andrewm@5: bool usesKeyboardModWheel() { return usesKeyboardModWheel_; } andrewm@5: void setUsesKeyboardModWheel(bool use) { andrewm@5: usesKeyboardModWheel_ = use; andrewm@5: // Reset to default if not using andrewm@5: if(!use) { andrewm@5: controllerValues_[1] = 0; andrewm@5: } andrewm@5: } andrewm@5: andrewm@53: bool usesKeyboardPedals() { return usesKeyboardPedals_; } andrewm@53: void setUsesKeyboardPedals(bool use) { andrewm@53: usesKeyboardPedals_ = use; andrewm@53: // Reset to default if not using andrewm@53: if(!use) { andrewm@53: // MIDI CCs 64 to 69 are for pedals andrewm@53: for(int i = 64; i <= 69; i++) andrewm@53: controllerValues_[i] = 0; andrewm@53: } andrewm@53: } andrewm@0: andrewm@0: bool usesKeyboardMIDIControllers() { return usesKeyboardMidiControllers_; } andrewm@0: void setUsesKeyboardMIDIControllers(bool use) { andrewm@0: usesKeyboardMidiControllers_ = use; andrewm@0: // Reset to default if not using andrewm@0: if(!use) { andrewm@5: for(int i = 2; i < 128; i++) andrewm@0: controllerValues_[i] = 0; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Get or set the MIDI pitch wheel range in semitones, and optionally send an RPN andrewm@0: // message announcing its new value. andrewm@0: float midiPitchWheelRange() { return pitchWheelRange_; } andrewm@0: void setMidiPitchWheelRange(float semitones, bool send = false); andrewm@0: void sendMidiPitchWheelRange(); andrewm@0: andrewm@0: // TouchKeys standalone mode generates MIDI note onsets from touch data andrewm@0: // without needing a MIDI keyboard andrewm@0: void enableTouchkeyStandaloneMode(); andrewm@0: void disableTouchkeyStandaloneMode(); andrewm@0: bool touchkeyStandaloneModeEnabled() { return touchkeyStandaloneMode_; } andrewm@0: andrewm@0: // All Notes Off: can be sent by MIDI or controlled programmatically andrewm@0: void allNotesOff(); andrewm@0: andrewm@0: // Query the value of a controller andrewm@0: int controllerValue(int index) { andrewm@0: if(index < 0 || index >= kControlMax) andrewm@0: return 0; andrewm@0: return controllerValues_[index]; andrewm@0: } andrewm@0: andrewm@0: // Reset MIDI controller values to defaults andrewm@0: void resetControllerValues(); andrewm@0: andrewm@0: // Change or query the operating mode of the controller andrewm@0: int mode() { return mode_; } andrewm@0: void setMode(int mode); andrewm@0: void setModeOff(); andrewm@0: void setModePassThrough(); andrewm@0: void setModeMonophonic(); andrewm@0: void setModePolyphonic(); andrewm@53: void setModeMPE(); andrewm@0: andrewm@0: // Get/set polyphony and voice stealing for polyphonic mode andrewm@0: int polyphony() { return retransmitMaxPolyphony_; } andrewm@0: void setPolyphony(int polyphony); andrewm@0: bool voiceStealingEnabled() { return useVoiceStealing_; } andrewm@0: void setVoiceStealingEnabled(bool enable) { useVoiceStealing_ = enable; } andrewm@0: andrewm@0: // Get/set the number of the output port that messages on this segment should go to andrewm@0: int outputPort() { return outputPortNumber_; } andrewm@0: void setOutputPort(int port) { outputPortNumber_ = port; } andrewm@0: andrewm@0: // Set the minimum MIDI channel that should be used for output (0-15) andrewm@0: int outputChannelLowest() { return outputChannelLowest_; } andrewm@53: void setOutputChannelLowest(int ch); andrewm@0: andrewm@0: // Get set the output transposition in semitones, relative to input MIDI notes andrewm@0: int outputTransposition() { return outputTransposition_; } andrewm@0: void setOutputTransposition(int trans) { outputTransposition_ = trans; } andrewm@0: andrewm@0: // Whether the damper pedal is enabled in note channel allocation andrewm@0: bool damperPedalEnabled() { return damperPedalEnabled_; } andrewm@0: void setDamperPedalEnabled(bool enable); andrewm@0: andrewm@0: // MIDI handler routine andrewm@0: void midiHandlerMethod(MidiInput* source, const MidiMessage& message); andrewm@0: andrewm@0: // OSC method: used to get touch callback data from the keyboard andrewm@0: bool oscHandlerMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data); andrewm@0: andrewm@49: // OSC control method: called separately via the MidiInputController to manipulate andrewm@49: // control parameters of this object andrewm@49: OscMessage* oscControlMethod(const char *path, const char *types, int numValues, lo_arg **values, void *data); andrewm@49: andrewm@0: // **** Mapping-related methods ***** andrewm@0: andrewm@0: // OSC-MIDI converters: request and release methods. The acquire method andrewm@0: // will create a converter if it does not already exist, or return an existing andrewm@0: // one if it does. The release method will release the object when the andrewm@0: // acquirer no longer needs it. andrewm@0: OscMidiConverter* acquireOscMidiConverter(int controlId); andrewm@0: void releaseOscMidiConverter(int controlId); andrewm@0: andrewm@49: // *** Mapping methods *** andrewm@49: // Return the number of mapping factory types available andrewm@49: static int numberOfMappingFactories(); andrewm@49: andrewm@49: // Return the name of a given mapping factory type andrewm@49: static String mappingFactoryNameForIndex(int index); andrewm@49: andrewm@49: // Whether a given mapping is experimental andrewm@49: static bool mappingIsExperimental(int index); andrewm@49: andrewm@49: // Create a new mapping factory of the given type, attached to andrewm@49: // the supplied segment andrewm@49: MappingFactory* createMappingFactoryForIndex(int index); andrewm@49: andrewm@0: // Create a new mapping factory for this segment. A pointer should be passed in andrewm@0: // of a newly-allocated object. It will be released upon removal. andrewm@0: void addMappingFactory(MappingFactory* factory, bool autoGenerateName = false); andrewm@0: andrewm@0: // Remove a mapping factory, releasing the associated object. andrewm@0: void removeMappingFactory(MappingFactory* factory); andrewm@0: andrewm@0: // Remove all mapping factories, releasing each one andrewm@0: void removeAllMappingFactories(); andrewm@0: andrewm@0: // Return a list of current mapping factories. andrewm@0: vector const& mappingFactories(); andrewm@41: andrewm@41: // Return the specific index of this mapping factory andrewm@41: int indexOfMappingFactory(MappingFactory *factory); andrewm@0: andrewm@0: // Return a unique identifier of the mapping state, so we know when something has changed andrewm@0: int mappingFactoryUniqueIdentifier() { return mappingFactoryUniqueIdentifier_; } andrewm@33: andrewm@33: // **** Preset methods **** andrewm@33: andrewm@33: // Get an XML element describing current settings (for saving presets) andrewm@33: XmlElement* getPreset(); andrewm@33: andrewm@33: // Load settings from an XML element andrewm@33: bool loadPreset(XmlElement const* preset); andrewm@0: andrewm@0: private: andrewm@0: // Mode-specific MIDI input handlers andrewm@0: void modePassThroughHandler(MidiInput* source, const MidiMessage& message); andrewm@0: void modeMonophonicHandler(MidiInput* source, const MidiMessage& message); andrewm@0: andrewm@0: void modePolyphonicHandler(MidiInput* source, const MidiMessage& message); andrewm@0: void modePolyphonicNoteOn(unsigned char note, unsigned char velocity); andrewm@0: void modePolyphonicNoteOff(unsigned char note, bool forceOff = false); andrewm@53: void modePolyphonicMPENoteOnCallback(const char *path, const char *types, int numValues, lo_arg **values); andrewm@53: andrewm@53: void modeMPEHandler(MidiInput* source, const MidiMessage& message); andrewm@53: void modeMPENoteOn(unsigned char note, unsigned char velocity); andrewm@0: andrewm@0: // Helper functions for polyphonic mode andrewm@0: void modePolyphonicSetupHelper(); andrewm@0: int oldestNote(); andrewm@0: int oldestNoteInPedal(); andrewm@0: int newestNote(); andrewm@0: andrewm@0: // Methods for managing controllers andrewm@0: void handleControlChangeRetransit(int controllerNumber, const MidiMessage& message); andrewm@0: void setAllControllerActionsTo(int action); andrewm@0: andrewm@0: // Handle action of the damper pedal: when released, clear out any notes held there andrewm@0: void damperPedalWentOff(); andrewm@0: andrewm@0: // Send pitch wheel range to a specific channel andrewm@0: void sendMidiPitchWheelRangeHelper(int channel); andrewm@0: andrewm@0: // ***** Member Variables ***** andrewm@0: andrewm@0: PianoKeyboard& keyboard_; // Reference to main keyboard data andrewm@0: MidiOutputController *midiOutputController_; // Destination for MIDI output andrewm@0: int outputPortNumber_; // Which port to use on the output controller andrewm@0: vector mappingFactories_; // Collection of mappings for this segment andrewm@0: MappingFactorySplitter mappingFactorySplitter_; // ...and a splitter class to facilitate communication andrewm@0: int mappingFactoryUniqueIdentifier_; // Unique ID indicating mapping factory changes andrewm@0: andrewm@0: int mode_; // Current operating mode of the segment andrewm@0: unsigned int channelMask_; // Which channels we listen to (1 bit per channel) andrewm@0: int noteMin_, noteMax_; // Ranges of the notes we respond to andrewm@0: int outputChannelLowest_; // Lowest (or only) MIDI channel we send to andrewm@0: int outputTransposition_; // Transposition of notes at output andrewm@0: bool damperPedalEnabled_; // Whether to handle damper pedal events in allocating channels andrewm@0: bool touchkeyStandaloneMode_; // Whether we emulate MIDI data from TouchKeys andrewm@0: bool usesKeyboardChannelPressure_; // Whether this segment passes aftertouch from the keyboard andrewm@0: bool usesKeyboardPitchWheel_; // Whether this segment passes pitchwheel from the keyboard andrewm@5: bool usesKeyboardModWheel_; // Whether this segment passes CC 1 (mod wheel) from keyboard andrewm@53: bool usesKeyboardPedals_; // Whether this segment passes CCs 64-69 (pedals) from the keyboard andrewm@0: bool usesKeyboardMidiControllers_; // Whether this segment passes other controllers andrewm@0: float pitchWheelRange_; // Range of MIDI pitch wheel (in semitones) andrewm@0: andrewm@0: int controllerValues_[kControlMax]; // Values of MIDI controllers from input device andrewm@0: int controllerActions_[kControlMax]; // What to do with MIDI CCs when they come in andrewm@0: andrewm@0: // Mapping between input notes and output channels. Depending on the mode of operation, andrewm@0: // each note may be rebroadcast on its own MIDI channel. Need to keep track of what goes where. andrewm@0: // key is MIDI note #, value is output channel (0-15) andrewm@0: map retransmitChannelForNote_; andrewm@0: set retransmitChannelsAvailable_; andrewm@0: set retransmitNotesHeldInPedal_; andrewm@0: int retransmitMaxPolyphony_; andrewm@0: bool useVoiceStealing_; andrewm@0: timestamp_type noteOnsetTimestamps_[128]; // When each currently active note began, for stealing andrewm@0: andrewm@0: // OSC-MIDI conversion objects for use with data mapping. These are stored in each andrewm@0: // keyboard segment and specific mapping factories can request one when needed. andrewm@0: map oscMidiConverters_; andrewm@0: map oscMidiConverterReferenceCounts_; andrewm@0: }; andrewm@0: andrewm@0: #endif /* defined(__TouchKeys__MidiKeyboardSegment__) */