diff --git a/src/Ble_esp32.h b/src/Ble_esp32.h index c410367..7d0781e 100644 --- a/src/Ble_esp32.h +++ b/src/Ble_esp32.h @@ -132,9 +132,9 @@ public: inline void read() { - // n/a, data comes in async (see onWrite callbacks) } + inline void sendMIDI(StatusByte, DataByte data1 = 0, DataByte data2 = 0); inline void receive(uint8_t *buffer, uint8_t bufferSize); void onConnected(void(*fptr)()) { @@ -219,6 +219,39 @@ bool BleMidiInterface::begin(const char* deviceName) return true; } +void BleMidiInterface::sendMIDI(StatusByte status, DataByte data1, DataByte data2) +{ + MidiType type = getTypeFromStatusByte(status); + Channel channel = getChannelFromStatusByte(status); + + switch (type) { + case NoteOff: + if (_noteOffCallback) _noteOffCallback(channel, data1, data2); + break; + case NoteOn: + if (_noteOnCallback) _noteOnCallback(channel, data1, data2); + break; + case AfterTouchPoly: + if (_afterTouchPolyCallback) _afterTouchPolyCallback(channel, data1, data2); + break; + case ControlChange: + if (_controlChangeCallback) _controlChangeCallback(channel, data1, data2); + break; + case ProgramChange: + if (_programChangeCallback) _programChangeCallback(channel, data1); + break; + case AfterTouchChannel: + if (_afterTouchChannelCallback) _afterTouchChannelCallback(channel, data1); + break; + case PitchBend: + if (_pitchBendCallback) { + int value = (int) ((data1 & 0x7f) | ((data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN; + _pitchBendCallback(channel, value); + } + break; + } +} + void BleMidiInterface::receive(uint8_t *buffer, uint8_t bufferSize) { /* @@ -251,64 +284,54 @@ void BleMidiInterface::receive(uint8_t *buffer, uint8_t bufferSize) MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved from other messages – except for System Exclusive messages. */ - Channel channel; - MidiType command; //Pointers used to search through payload. uint8_t lPtr = 0; uint8_t rPtr = 0; + + //lastStatus used to capture runningStatus + uint8_t lastStatus; + //Decode first packet -- SHALL be "Full MIDI message" lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" + //While statement contains incrementing pointers and breaks when buffer size exceeded. - while (1) { - //lastStatus used to capture runningStatus - auto lastStatus = buffer[lPtr]; - if ( (buffer[lPtr] < 0x80) ) { + while(1){ + lastStatus = buffer[lPtr]; + if( (buffer[lPtr] < 0x80) ){ //Status message not present, bail return; } - - command = getTypeFromStatusByte(lastStatus); - channel = getChannelFromStatusByte(lastStatus); - //Point to next non-data byte rPtr = lPtr; - while ( (buffer[rPtr + 1] < 0x80) && (rPtr < (bufferSize - 1)) ) { + while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){ rPtr++; } //look at l and r pointers and decode by size. - if ( rPtr - lPtr < 1 ) { + if( rPtr - lPtr < 1 ){ //Time code or system - // MIDI.send(command, 0, 0, channel); - } else if ( rPtr - lPtr < 2 ) { - // MIDI.send(command, buffer[lPtr + 1], 0, channel); - } else if ( rPtr - lPtr < 3 ) { - - // TODO: switch for type - -if (_noteOnCallback) // if an attached function exisist, call it here - _noteOnCallback(0, 1, 2); - - // MIDI.send(command, buffer[lPtr + 1], buffer[lPtr + 2], channel); + sendMIDI(lastStatus); + } else if( rPtr - lPtr < 2 ) { + sendMIDI(lastStatus, buffer[lPtr + 1]); + } else if( rPtr - lPtr < 3 ) { + sendMIDI(lastStatus, buffer[lPtr + 1], buffer[lPtr + 2]); } else { //Too much data //If not System Common or System Real-Time, send it as running status - switch ( buffer[lPtr] & 0xF0 ) + switch( buffer[lPtr] & 0xF0 ) { - case 0x80: - case 0x90: - case 0xA0: - case 0xB0: - case 0xE0: - for (int i = lPtr; i < rPtr; i = i + 2) { - // MIDI.send(command, buffer[i + 1], buffer[i + 2], channel); - } + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case PitchBend: + for(int i = lPtr; i < rPtr; i = i + 2) + sendMIDI(lastStatus, buffer[i + 1], buffer[i + 2]); break; - case 0xC0: - case 0xD0: - for (int i = lPtr; i < rPtr; i = i + 1) { - // MIDI.send(command, buffer[i + 1], 0, channel); - } + case ProgramChange: + case AfterTouchChannel: + for(int i = lPtr; i < rPtr; i = i + 1) + sendMIDI(lastStatus, buffer[i + 1]); break; default: break; @@ -316,11 +339,12 @@ if (_noteOnCallback) // if an attached function exisist, call it here } //Point to next status lPtr = rPtr + 2; - if (lPtr >= bufferSize) { + if(lPtr >= bufferSize){ //end of packet return; } } + } END_BLEMIDI_NAMESPACE diff --git a/src/utility/AbstractMidiInterface.h b/src/utility/AbstractMidiInterface.h index ca9093f..26a0071 100644 --- a/src/utility/AbstractMidiInterface.h +++ b/src/utility/AbstractMidiInterface.h @@ -1,5 +1,5 @@ /*! - * @file AbstractMidiInterface.h + * @file AbstractMidiInterface.h */ #pragma once @@ -19,7 +19,7 @@ namespace Midi { #define MIDI_SAMPLING_RATE_192KHZ 192000 #define MIDI_SAMPLING_RATE_DEFAULT 10000 -// Channel Voice Messages + // Channel Voice Messages #define MIDI_STATUS_NOTE_OFF 0x80 #define MIDI_STATUS_NOTE_ON 0x90 #define MIDI_STATUS_POLYPHONIC_KEY_PRESSURE 0xA0 @@ -28,7 +28,7 @@ namespace Midi { #define MIDI_STATUS_CHANNEL_PRESSURE 0xd0 #define MIDI_STATUS_PITCH_WHEEL_CHANGE 0xe0 -// MIDI Channel enumeration values + // MIDI Channel enumeration values #define MIDI_CHANNEL_OMNI 0x0 #define MIDI_CHANNEL_1 0x0 #define MIDI_CHANNEL_2 0x1 @@ -49,445 +49,466 @@ namespace Midi { #define MIDI_CHANNEL_BASE 0x10 #define MIDI_CHANNEL_ALL 0x1f #define MIDI_CHANNEL_OFF 0x1f - + #define MIDI_LSB( v ) (v) & 0x7F #define MIDI_MSB( v ) ((v)>> 7) & 0x7F -// ----------------------------------------------------------------------------- -// Type definitions - -typedef uint8_t byte; - -typedef byte StatusByte; -typedef byte DataByte; -typedef byte Channel; -typedef byte FilterMode; - -typedef byte MIDI_CHANNEL; -typedef byte MIDI_VELOCITY; -typedef byte MIDI_PRESSURE; - -/*! Enumeration of MIDI types */ -enum MidiType : uint8_t -{ - InvalidType = 0x00, ///< For notifying errors + // ----------------------------------------------------------------------------- + // Type definitions - NoteOff = 0x80, ///< Note Off - NoteOn = 0x90, ///< Note On - AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch - ControlChange = 0xB0, ///< Control Change / Channel Mode - ProgramChange = 0xC0, ///< Program Change - AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch - PitchBend = 0xE0, ///< Pitch Bend + typedef uint8_t byte; - SystemExclusive = 0xF0, ///< System Exclusive - SystemExclusiveStart = SystemExclusive, - SystemExclusiveEnd = 0xF7, ///< System Exclusive End + typedef byte StatusByte; + typedef byte DataByte; + typedef byte Channel; + typedef byte FilterMode; - TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame - SongPosition = 0xF2, ///< System Common - Song Position Pointer - SongSelect = 0xF3, ///< System Common - Song Select - TuneRequest = 0xF6, ///< System Common - Tune Request + typedef byte MIDI_CHANNEL; + typedef byte MIDI_VELOCITY; + typedef byte MIDI_PRESSURE; - Clock = 0xF8, ///< System Real Time - Timing Clock - Tick = 0xF9, ///< System Real Time - Tick - Start = 0xFA, ///< System Real Time - Start - Continue = 0xFB, ///< System Real Time - Continue - Stop = 0xFC, ///< System Real Time - Stop - ActiveSensing = 0xFE, ///< System Real Time - Active Sensing - SystemReset = 0xFF, ///< System Real Time - System Reset -}; - -/*! Enumeration of Thru filter modes */ -struct Thru -{ - enum Mode + /*! Enumeration of MIDI types */ + enum MidiType : uint8_t { - Off = 0, ///< Thru disabled (nothing passes through). - Full = 1, ///< Fully enabled Thru (every incoming message is sent back). - SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. - DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. + InvalidType = 0x00, ///< For notifying errors + + NoteOff = 0x80, ///< Note Off + NoteOn = 0x90, ///< Note On + AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch + ControlChange = 0xB0, ///< Control Change / Channel Mode + ProgramChange = 0xC0, ///< Program Change + AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch + PitchBend = 0xE0, ///< Pitch Bend + + SystemExclusive = 0xF0, ///< System Exclusive + SystemExclusiveStart = SystemExclusive, + SystemExclusiveEnd = 0xF7, ///< System Exclusive End + + TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame + SongPosition = 0xF2, ///< System Common - Song Position Pointer + SongSelect = 0xF3, ///< System Common - Song Select + TuneRequest = 0xF6, ///< System Common - Tune Request + + Clock = 0xF8, ///< System Real Time - Timing Clock + Tick = 0xF9, ///< System Real Time - Tick + Start = 0xFA, ///< System Real Time - Start + Continue = 0xFB, ///< System Real Time - Continue + Stop = 0xFC, ///< System Real Time - Stop + ActiveSensing = 0xFE, ///< System Real Time - Active Sensing + SystemReset = 0xFF, ///< System Real Time - System Reset }; -}; - -/*! \brief Enumeration of Control Change command numbers. - See the detailed controllers numbers & description here: - http://www.somascape.org/midi/tech/spec.html#ctrlnums - */ -enum MidiControlChangeNumber : uint8_t -{ - // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- - BankSelect = 0, - ModulationWheel = 1, - BreathController = 2, - // CC3 undefined - FootController = 4, - PortamentoTime = 5, - DataEntry = 6, - ChannelVolume = 7, - Balance = 8, - // CC9 undefined - Pan = 10, - ExpressionController = 11, - EffectControl1 = 12, - EffectControl2 = 13, - // CC14 undefined - // CC15 undefined - GeneralPurposeController1 = 16, - GeneralPurposeController2 = 17, - GeneralPurposeController3 = 18, - GeneralPurposeController4 = 19, - // Switches ---------------------------------------------------------------- - Sustain = 64, - Portamento = 65, - Sostenuto = 66, - SoftPedal = 67, - Legato = 68, - Hold = 69, - - // Low resolution continuous controllers ----------------------------------- - SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off - SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off - SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off - SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off - SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off - SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off - SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off - SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off - SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off - SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off - GeneralPurposeController5 = 80, - GeneralPurposeController6 = 81, - GeneralPurposeController7 = 82, - GeneralPurposeController8 = 83, - PortamentoControl = 84, - // CC85 to CC90 undefined - Effects1 = 91, ///< Reverb send level - Effects2 = 92, ///< Tremolo depth - Effects3 = 93, ///< Chorus send level - Effects4 = 94, ///< Celeste depth - Effects5 = 95, ///< Phaser depth - - // Channel Mode messages --------------------------------------------------- - AllSoundOff = 120, - ResetAllControllers = 121, - LocalControl = 122, - AllNotesOff = 123, - OmniModeOff = 124, - OmniModeOn = 125, - MonoModeOn = 126, - PolyModeOn = 127 -}; - -struct RPN -{ - enum RegisteredParameterNumbers + /*! Enumeration of Thru filter modes */ + struct Thru { - PitchBendSensitivity = 0x0000, - ChannelFineTuning = 0x0001, - ChannelCoarseTuning = 0x0002, - SelectTuningProgram = 0x0003, - SelectTuningBank = 0x0004, - ModulationDepthRange = 0x0005, - NullFunction = (0x7f << 7) + 0x7f, - }; -}; - -/*! \brief Extract an enumerated MIDI type from a status byte - */ -static MidiType getTypeFromStatusByte(byte status) -{ - if ((status < 0x80) || - (status == 0xf4) || - (status == 0xf5) || - (status == 0xf9) || - (status == 0xfD)) - { - // Data bytes and undefined. - return MidiType::InvalidType; - } - if (status < 0xf0) - { - // Channel message, remove channel nibble. - return MidiType(status & 0xf0); - } - - return MidiType(status); -} - -/*! \brief Returns type + channel - */ -static StatusByte getStatus(MidiType type, Channel channel) -{ - return ( type & 0xf0) | ((channel - 1) & 0x0f); -} - -/*! \brief Returns channel in the range 1-16 - */ -static Channel getChannelFromStatusByte(byte status) -{ - return Channel((status & 0x0f) + 1); -} - -/*! \brief check if channel is in the range 1-16 - */ -static bool isChannelMessage(MidiType type) -{ - return (type == MidiType::NoteOff || - type == MidiType::NoteOn || - type == MidiType::ControlChange || - type == MidiType::AfterTouchPoly || - type == MidiType::AfterTouchChannel || - type == MidiType::PitchBend || - type == MidiType::ProgramChange); -} - -class AbstractMidiInterface -{ -protected: - int _runningStatus; - bool _thruActivated; - -public: - AbstractMidiInterface() - { - } - -protected: - void (*_noteOnCallback)(byte channel, byte note, byte velocity) = NULL; - void (*_noteOffCallback)(byte channel, byte note, byte velocity) = NULL; - void (*_afterTouchPolyCallback)(byte channel, byte note, byte velocity) = NULL; - void (*_controlChangeCallback)(byte channel, byte, byte) = NULL; - void (*_programChangeCallback)(byte channel, byte) = NULL; - void (*_afterTouchChannelCallback)(byte channel, byte) = NULL; - void (*_pitchBendCallback)(byte channel, int) = NULL; - void (*_songPositionCallback)(unsigned short beats) = NULL; - void (*_songSelectCallback)(byte songnumber) = NULL; - void (*_tuneRequestCallback)(void) = NULL; - void (*_timeCodeQuarterFrameCallback)(byte data) = NULL; - void (*_sysExCallback)(const byte* array, uint16_t size) = NULL; - void (*_clockCallback)(void) = NULL; - void (*_startCallback)(void) = NULL; - void (*_continueCallback)(void) = NULL; - void (*_stopCallback)(void) = NULL; - void (*_activeSensingCallback)(void) = NULL; - void (*_resetCallback)(void) = NULL; - -public: - // sending - void sendNoteOn(DataByte note, DataByte velocity, Channel channel) { - send(MidiType::NoteOn, channel, note, velocity); - } - - void sendNoteOff(DataByte note, DataByte velocity, Channel channel) { - send(MidiType::NoteOff, channel, note, velocity); - } - - void sendProgramChange(DataByte number, Channel channel) { - send(MidiType::ProgramChange, number, 0, channel); - } - - void sendControlChange(DataByte number, DataByte value, Channel channel) { - send(MidiType::ControlChange, number, value, channel); - } - - void sendPitchBend(int value, Channel channel) { - const unsigned bend = unsigned(value - int(MIDI_PITCHBEND_MIN)); - send(MidiType::PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, channel); - } - - void sendPitchBend(double pitchValue, Channel channel) { - const int scale = pitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; - const int value = int(pitchValue * double(scale)); - sendPitchBend(value, channel); - } - - void sendPolyPressure(DataByte note, DataByte pressure, Channel channel) { - send(MidiType::AfterTouchPoly, note, pressure, channel); - } - - void sendAfterTouch(DataByte pressure, Channel channel) { - send(MidiType::AfterTouchChannel, pressure, 0, channel); - } - - void sendAfterTouch(DataByte note, DataByte pressure, Channel channel) { - send(MidiType::AfterTouchChannel, note, pressure, channel); - } - - - void sendSysEx(const byte*, uint16_t inLength) { - // TODO - } - - - void sendTimeCodeQuarterFrame(DataByte typeNibble, DataByte valuesNibble) { - // TODO f(typeNibble, valuesNibble); - send(MidiType::TimeCodeQuarterFrame); - } - - void sendTimeCodeQuarterFrame(DataByte data) { - send(MidiType::TimeCodeQuarterFrame, data); - } - - void sendSongPosition(unsigned short beats) { - send(MidiType::SongPosition, beats); - } - - void sendSongSelect(DataByte number) { - send(MidiType::SongSelect, number); - } - - void sendTuneRequest() { - send(MidiType::TuneRequest); - } - - void sendActiveSensing() { - send(MidiType::ActiveSensing); - } - - void sendStart() { - send(MidiType::Start); - } - - void sendContinue() { - send(MidiType::Continue); - } - - void sendStop() { - send(MidiType::Stop); - } - - void sendClock() { - send(MidiType::Clock); - } - - void sendTick() { - send(MidiType::Tick); - } - - void sendReset() { - send(MidiType::SystemReset); - } - - - //receiving - void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { - _noteOnCallback = fptr; - } - void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { - _noteOffCallback = fptr; - } - void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { - _afterTouchPolyCallback = fptr; - } - void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { - _controlChangeCallback = fptr; - } - void setHandleProgramChange(void (*fptr)(byte channel, byte number)) { - _programChangeCallback = fptr; - } - void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { - _afterTouchChannelCallback = fptr; - } - void setHandlePitchBend(void (*fptr)(byte channel, int bend)) { - _pitchBendCallback = fptr; - } - void setHandleSysEx(void (*fptr)(const byte * data, uint16_t size)) { - _sysExCallback = fptr; - } - void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { - _timeCodeQuarterFrameCallback = fptr; - } - void setHandleSongPosition(void (*fptr)(unsigned short beats)) { - _songPositionCallback = fptr; - } - void setHandleSongSelect(void (*fptr)(byte songnumber)) { - _songSelectCallback = fptr; - } - void setHandleTuneRequest(void (*fptr)(void)) { - _tuneRequestCallback = fptr; - } - void setHandleClock(void (*fptr)(void)) { - _clockCallback = fptr; - } - void setHandleStart(void (*fptr)(void)) { - _startCallback = fptr; - } - void setHandleContinue(void (*fptr)(void)) { - _continueCallback = fptr; - } - void setHandleStop(void (*fptr)(void)) { - _stopCallback = fptr; - } - void setHandleActiveSensing(void (*fptr)(void)) { - _activeSensingCallback = fptr; - } - void setHandleReset(void (*fptr)(void)) { - _resetCallback = fptr; - } - -protected: - // Channel messages - virtual void send(MidiType type, DataByte data1, DataByte data2, Channel channel) - { - // Then test if channel is valid - if (channel >= MIDI_CHANNEL_OFF || - channel == MIDI_CHANNEL_OMNI || - type < 0x80) + enum Mode { - return; // Don't send anything + Off = 0, ///< Thru disabled (nothing passes through). + Full = 1, ///< Fully enabled Thru (every incoming message is sent back). + SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. + DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. + }; + }; + + /*! \brief Enumeration of Control Change command numbers. + See the detailed controllers numbers & description here: + http://www.somascape.org/midi/tech/spec.html#ctrlnums + */ + enum MidiControlChangeNumber : uint8_t + { + // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- + BankSelect = 0, + ModulationWheel = 1, + BreathController = 2, + // CC3 undefined + FootController = 4, + PortamentoTime = 5, + DataEntry = 6, + ChannelVolume = 7, + Balance = 8, + // CC9 undefined + Pan = 10, + ExpressionController = 11, + EffectControl1 = 12, + EffectControl2 = 13, + // CC14 undefined + // CC15 undefined + GeneralPurposeController1 = 16, + GeneralPurposeController2 = 17, + GeneralPurposeController3 = 18, + GeneralPurposeController4 = 19, + + // Switches ---------------------------------------------------------------- + Sustain = 64, + Portamento = 65, + Sostenuto = 66, + SoftPedal = 67, + Legato = 68, + Hold = 69, + + // Low resolution continuous controllers ----------------------------------- + SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off + SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off + SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off + SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off + SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off + SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off + SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off + SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off + SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off + SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off + GeneralPurposeController5 = 80, + GeneralPurposeController6 = 81, + GeneralPurposeController7 = 82, + GeneralPurposeController8 = 83, + PortamentoControl = 84, + // CC85 to CC90 undefined + Effects1 = 91, ///< Reverb send level + Effects2 = 92, ///< Tremolo depth + Effects3 = 93, ///< Chorus send level + Effects4 = 94, ///< Celeste depth + Effects5 = 95, ///< Phaser depth + + // Channel Mode messages --------------------------------------------------- + AllSoundOff = 120, + ResetAllControllers = 121, + LocalControl = 122, + AllNotesOff = 123, + OmniModeOff = 124, + OmniModeOn = 125, + MonoModeOn = 126, + PolyModeOn = 127 + }; + + struct RPN + { + enum RegisteredParameterNumbers + { + PitchBendSensitivity = 0x0000, + ChannelFineTuning = 0x0001, + ChannelCoarseTuning = 0x0002, + SelectTuningProgram = 0x0003, + SelectTuningBank = 0x0004, + ModulationDepthRange = 0x0005, + NullFunction = (0x7f << 7) + 0x7f, + }; + }; + + /*! \brief Extract an enumerated MIDI type from a status byte + */ + static MidiType getTypeFromStatusByte(byte status) + { + if ((status < 0x80) || + (status == 0xf4) || + (status == 0xf5) || + (status == 0xf9) || + (status == 0xfD)) + { + // Data bytes and undefined. + return MidiType::InvalidType; + } + if (status < 0xf0) + { + // Channel message, remove channel nibble. + return MidiType(status & 0xf0); } - if (type <= MidiType::PitchBend) + return MidiType(status); + } + + /*! \brief Returns type + channel + */ + static StatusByte getStatus(MidiType type, Channel channel) + { + return ( type & 0xf0) | ((channel - 1) & 0x0f); + } + + /*! \brief Returns channel in the range 1-16 + */ + static Channel getChannelFromStatusByte(byte status) + { + return Channel((status & 0x0f) + 1); + } + + /*! \brief check if channel is in the range 1-16 + */ + static bool isChannelMessage(MidiType type) + { + return (type == MidiType::NoteOff || + type == MidiType::NoteOn || + type == MidiType::ControlChange || + type == MidiType::AfterTouchPoly || + type == MidiType::AfterTouchChannel || + type == MidiType::PitchBend || + type == MidiType::ProgramChange); + } + + class AbstractMidiInterface + { + protected: + int _runningStatus; + bool _thruActivated; + + public: + AbstractMidiInterface() { - // Channel messages + } + + protected: + void (*_noteOnCallback)(byte channel, byte note, byte velocity) = NULL; + void (*_noteOffCallback)(byte channel, byte note, byte velocity) = NULL; + void (*_afterTouchPolyCallback)(byte channel, byte note, byte velocity) = NULL; + void (*_controlChangeCallback)(byte channel, byte, byte) = NULL; + void (*_programChangeCallback)(byte channel, byte) = NULL; + void (*_afterTouchChannelCallback)(byte channel, byte) = NULL; + void (*_pitchBendCallback)(byte channel, int) = NULL; + void (*_songPositionCallback)(unsigned short beats) = NULL; + void (*_songSelectCallback)(byte songnumber) = NULL; + void (*_tuneRequestCallback)(void) = NULL; + void (*_timeCodeQuarterFrameCallback)(byte data) = NULL; + void (*_sysExCallback)(const byte* array, uint16_t size) = NULL; + void (*_clockCallback)(void) = NULL; + void (*_startCallback)(void) = NULL; + void (*_continueCallback)(void) = NULL; + void (*_stopCallback)(void) = NULL; + void (*_activeSensingCallback)(void) = NULL; + void (*_resetCallback)(void) = NULL; + + public: + // sending + void sendNoteOn(DataByte note, DataByte velocity, Channel channel) { + sendChannelMessage(MidiType::NoteOn, channel, note, velocity); + } + + void sendNoteOff(DataByte note, DataByte velocity, Channel channel) { + sendChannelMessage(MidiType::NoteOff, channel, note, velocity); + } + + void sendProgramChange(DataByte number, Channel channel) { + sendChannelMessage(MidiType::ProgramChange, number, 0, channel); + } + + void sendControlChange(DataByte number, DataByte value, Channel channel) { + sendChannelMessage(MidiType::ControlChange, number, value, channel); + } + + void sendPitchBend(int value, Channel channel) { + const unsigned bend = unsigned(value - int(MIDI_PITCHBEND_MIN)); + sendChannelMessage(MidiType::PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, channel); + } + + void sendPitchBend(double pitchValue, Channel channel) { + const int scale = pitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN; + const int value = int(pitchValue * double(scale)); + sendPitchBend(value, channel); + } + + void sendPolyPressure(DataByte note, DataByte pressure, Channel channel) { + sendChannelMessage(MidiType::AfterTouchPoly, note, pressure, channel); + } + + void sendAfterTouch(DataByte pressure, Channel channel) { + sendChannelMessage(MidiType::AfterTouchChannel, pressure, 0, channel); + } + + void sendAfterTouch(DataByte note, DataByte pressure, Channel channel) { + sendChannelMessage(MidiType::AfterTouchChannel, note, pressure, channel); + } + + + void sendSysEx(const byte*, uint16_t inLength) { + // TODO + } + + + void sendTimeCodeQuarterFrame(DataByte typeNibble, DataByte valuesNibble) { + const byte data = byte((((typeNibble & 0x07) << 4) | (valuesNibble & 0x0f))); + sendTimeCodeQuarterFrame(data); + } + + void sendTimeCodeQuarterFrame(DataByte data) { + sendSystemCommonMessage(MidiType::TimeCodeQuarterFrame, data); + } + + void sendSongPosition(unsigned short beats) { + byte data1 = beats & 0x7f; + byte data2 = (beats >> 7) & 0x7f; - // Protection: remove MSBs on data - data1 &= 0x7f; - data2 &= 0x7f; - - const StatusByte status = getStatus(type, channel); - - if (type != MidiType::ProgramChange && type != MidiType::AfterTouchChannel) + sendSystemCommonMessage(MidiType::SongPosition, data1, data2); + } + + void sendSongSelect(DataByte number) { + sendSystemCommonMessage(MidiType::SongSelect, number & 0x7f); + } + + void sendTuneRequest() { + sendSystemCommonMessage(MidiType::TuneRequest); + } + + void sendActiveSensing() { + sendSystemCommonMessage(MidiType::ActiveSensing); + } + + + void sendStart() { + sendRealTimeMessage(MidiType::Start); + } + + void sendContinue() { + sendRealTimeMessage(MidiType::Continue); + } + + void sendStop() { + sendRealTimeMessage(MidiType::Stop); + } + + void sendClock() { + sendRealTimeMessage(MidiType::Clock); + } + + void sendTick() { + sendRealTimeMessage(MidiType::Tick); + } + + void sendReset() { + sendRealTimeMessage(MidiType::SystemReset); + } + + + //receiving + void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { + _noteOffCallback = fptr; + } + void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { + _noteOnCallback = fptr; + } + void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { + _afterTouchPolyCallback = fptr; + } + void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { + _controlChangeCallback = fptr; + } + void setHandleProgramChange(void (*fptr)(byte channel, byte number)) { + _programChangeCallback = fptr; + } + void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { + _afterTouchChannelCallback = fptr; + } + void setHandlePitchBend(void (*fptr)(byte channel, int bend)) { + _pitchBendCallback = fptr; + } + void setHandleSysEx(void (*fptr)(const byte * data, uint16_t size)) { + _sysExCallback = fptr; + } + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { + _timeCodeQuarterFrameCallback = fptr; + } + void setHandleSongPosition(void (*fptr)(unsigned short beats)) { + _songPositionCallback = fptr; + } + void setHandleSongSelect(void (*fptr)(byte songnumber)) { + _songSelectCallback = fptr; + } + void setHandleTuneRequest(void (*fptr)(void)) { + _tuneRequestCallback = fptr; + } + void setHandleClock(void (*fptr)(void)) { + _clockCallback = fptr; + } + void setHandleStart(void (*fptr)(void)) { + _startCallback = fptr; + } + void setHandleContinue(void (*fptr)(void)) { + _continueCallback = fptr; + } + void setHandleStop(void (*fptr)(void)) { + _stopCallback = fptr; + } + void setHandleActiveSensing(void (*fptr)(void)) { + _activeSensingCallback = fptr; + } + void setHandleReset(void (*fptr)(void)) { + _resetCallback = fptr; + } + + protected: + + // Channel messages + virtual void sendChannelMessage(MidiType type, DataByte data1, DataByte data2, Channel channel) + { + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || + channel == MIDI_CHANNEL_OMNI || + type < 0x80) { - serialize(status, data1, data2); + return; // Don't send anything } - else + + if (type <= MidiType::PitchBend) { - serialize(status, data1); + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7f; + data2 &= 0x7f; + + const StatusByte status = getStatus(type, channel); + + if (type != MidiType::ProgramChange && type != MidiType::AfterTouchChannel) + { + serialize(status, data1, data2); + } + else + { + serialize(status, data1); + } + } + else if (type >= MidiType::Clock && type <= MidiType::SystemReset) + { + sendRealTimeMessage(type); // System Real-time and 1 byte. } } - else if (type >= MidiType::Clock && type <= MidiType::SystemReset) + + // SystemCommon message + virtual void sendSystemCommonMessage(MidiType type, DataByte data1 = 0, DataByte data2 = 0) { - send(type); // System Real-time and 1 byte. + } - } - - // SystemCommon message - virtual void send(MidiType type, DataByte data1) - { - } - - // realTime messages - virtual void send(MidiType type) - { + // RealTime messages + virtual void sendRealTimeMessage(MidiType type) + { + // Do not invalidate Running Status for real-time messages + // as they can be interleaved within any message. + + switch (type) + { + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + serialize(type); + break; + default: + // Invalid Real Time marker + break; + } + } - } - - virtual bool begin(const char*) = 0; + virtual bool begin(const char*) = 0; + + // serialize from the hardware + virtual void read() = 0; + + // serialize towards to hardware + virtual void serialize(DataByte) = 0; + virtual void serialize(DataByte, DataByte) = 0; + virtual void serialize(DataByte, DataByte, DataByte) = 0; + + protected: + }; - virtual void read() = 0; - - // serialize from the hardware - - // serialize towards to hardware - virtual void serialize(DataByte) = 0; - virtual void serialize(DataByte, DataByte) = 0; - virtual void serialize(DataByte, DataByte, DataByte) = 0; - -protected: -}; - } +