implemented parser for incoming messages

This commit is contained in:
lathoub 2018-11-11 08:49:56 -05:00
parent e91e4209b1
commit 26c4973e12
2 changed files with 502 additions and 457 deletions

View File

@ -132,9 +132,9 @@ public:
inline void read() 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); inline void receive(uint8_t *buffer, uint8_t bufferSize);
void onConnected(void(*fptr)()) { void onConnected(void(*fptr)()) {
@ -219,6 +219,39 @@ bool BleMidiInterface::begin(const char* deviceName)
return true; 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) 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 MIDI messages. In the MIDI BLE protocol, the System Real-Time messages must be deinterleaved
from other messages except for System Exclusive messages. from other messages except for System Exclusive messages.
*/ */
Channel channel;
MidiType command;
//Pointers used to search through payload. //Pointers used to search through payload.
uint8_t lPtr = 0; uint8_t lPtr = 0;
uint8_t rPtr = 0; uint8_t rPtr = 0;
//lastStatus used to capture runningStatus
uint8_t lastStatus;
//Decode first packet -- SHALL be "Full MIDI message" //Decode first packet -- SHALL be "Full MIDI message"
lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status" lPtr = 2; //Start at first MIDI status -- SHALL be "MIDI status"
//While statement contains incrementing pointers and breaks when buffer size exceeded. //While statement contains incrementing pointers and breaks when buffer size exceeded.
while (1) { while(1){
//lastStatus used to capture runningStatus lastStatus = buffer[lPtr];
auto lastStatus = buffer[lPtr]; if( (buffer[lPtr] < 0x80) ){
if ( (buffer[lPtr] < 0x80) ) {
//Status message not present, bail //Status message not present, bail
return; return;
} }
command = getTypeFromStatusByte(lastStatus);
channel = getChannelFromStatusByte(lastStatus);
//Point to next non-data byte //Point to next non-data byte
rPtr = lPtr; rPtr = lPtr;
while ( (buffer[rPtr + 1] < 0x80) && (rPtr < (bufferSize - 1)) ) { while( (buffer[rPtr + 1] < 0x80)&&(rPtr < (bufferSize - 1)) ){
rPtr++; rPtr++;
} }
//look at l and r pointers and decode by size. //look at l and r pointers and decode by size.
if ( rPtr - lPtr < 1 ) { if( rPtr - lPtr < 1 ){
//Time code or system //Time code or system
// MIDI.send(command, 0, 0, channel); sendMIDI(lastStatus);
} else if ( rPtr - lPtr < 2 ) { } else if( rPtr - lPtr < 2 ) {
// MIDI.send(command, buffer[lPtr + 1], 0, channel); sendMIDI(lastStatus, buffer[lPtr + 1]);
} else if ( rPtr - lPtr < 3 ) { } else if( rPtr - lPtr < 3 ) {
sendMIDI(lastStatus, buffer[lPtr + 1], buffer[lPtr + 2]);
// 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);
} else { } else {
//Too much data //Too much data
//If not System Common or System Real-Time, send it as running status //If not System Common or System Real-Time, send it as running status
switch ( buffer[lPtr] & 0xF0 ) switch( buffer[lPtr] & 0xF0 )
{ {
case 0x80: case NoteOff:
case 0x90: case NoteOn:
case 0xA0: case AfterTouchPoly:
case 0xB0: case ControlChange:
case 0xE0: case PitchBend:
for (int i = lPtr; i < rPtr; i = i + 2) { for(int i = lPtr; i < rPtr; i = i + 2)
// MIDI.send(command, buffer[i + 1], buffer[i + 2], channel); sendMIDI(lastStatus, buffer[i + 1], buffer[i + 2]);
}
break; break;
case 0xC0: case ProgramChange:
case 0xD0: case AfterTouchChannel:
for (int i = lPtr; i < rPtr; i = i + 1) { for(int i = lPtr; i < rPtr; i = i + 1)
// MIDI.send(command, buffer[i + 1], 0, channel); sendMIDI(lastStatus, buffer[i + 1]);
}
break; break;
default: default:
break; break;
@ -316,11 +339,12 @@ if (_noteOnCallback) // if an attached function exisist, call it here
} }
//Point to next status //Point to next status
lPtr = rPtr + 2; lPtr = rPtr + 2;
if (lPtr >= bufferSize) { if(lPtr >= bufferSize){
//end of packet //end of packet
return; return;
} }
} }
} }
END_BLEMIDI_NAMESPACE END_BLEMIDI_NAMESPACE

View File

@ -1,5 +1,5 @@
/*! /*!
* @file AbstractMidiInterface.h * @file AbstractMidiInterface.h
*/ */
#pragma once #pragma once
@ -19,7 +19,7 @@ namespace Midi {
#define MIDI_SAMPLING_RATE_192KHZ 192000 #define MIDI_SAMPLING_RATE_192KHZ 192000
#define MIDI_SAMPLING_RATE_DEFAULT 10000 #define MIDI_SAMPLING_RATE_DEFAULT 10000
// Channel Voice Messages // Channel Voice Messages
#define MIDI_STATUS_NOTE_OFF 0x80 #define MIDI_STATUS_NOTE_OFF 0x80
#define MIDI_STATUS_NOTE_ON 0x90 #define MIDI_STATUS_NOTE_ON 0x90
#define MIDI_STATUS_POLYPHONIC_KEY_PRESSURE 0xA0 #define MIDI_STATUS_POLYPHONIC_KEY_PRESSURE 0xA0
@ -28,7 +28,7 @@ namespace Midi {
#define MIDI_STATUS_CHANNEL_PRESSURE 0xd0 #define MIDI_STATUS_CHANNEL_PRESSURE 0xd0
#define MIDI_STATUS_PITCH_WHEEL_CHANGE 0xe0 #define MIDI_STATUS_PITCH_WHEEL_CHANGE 0xe0
// MIDI Channel enumeration values // MIDI Channel enumeration values
#define MIDI_CHANNEL_OMNI 0x0 #define MIDI_CHANNEL_OMNI 0x0
#define MIDI_CHANNEL_1 0x0 #define MIDI_CHANNEL_1 0x0
#define MIDI_CHANNEL_2 0x1 #define MIDI_CHANNEL_2 0x1
@ -49,445 +49,466 @@ namespace Midi {
#define MIDI_CHANNEL_BASE 0x10 #define MIDI_CHANNEL_BASE 0x10
#define MIDI_CHANNEL_ALL 0x1f #define MIDI_CHANNEL_ALL 0x1f
#define MIDI_CHANNEL_OFF 0x1f #define MIDI_CHANNEL_OFF 0x1f
#define MIDI_LSB( v ) (v) & 0x7F #define MIDI_LSB( v ) (v) & 0x7F
#define MIDI_MSB( v ) ((v)>> 7) & 0x7F #define MIDI_MSB( v ) ((v)>> 7) & 0x7F
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Type definitions // 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
NoteOff = 0x80, ///< Note Off typedef uint8_t byte;
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 typedef byte StatusByte;
SystemExclusiveStart = SystemExclusive, typedef byte DataByte;
SystemExclusiveEnd = 0xF7, ///< System Exclusive End typedef byte Channel;
typedef byte FilterMode;
TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame typedef byte MIDI_CHANNEL;
SongPosition = 0xF2, ///< System Common - Song Position Pointer typedef byte MIDI_VELOCITY;
SongSelect = 0xF3, ///< System Common - Song Select typedef byte MIDI_PRESSURE;
TuneRequest = 0xF6, ///< System Common - Tune Request
Clock = 0xF8, ///< System Real Time - Timing Clock /*! Enumeration of MIDI types */
Tick = 0xF9, ///< System Real Time - Tick enum MidiType : uint8_t
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
{ {
Off = 0, ///< Thru disabled (nothing passes through). InvalidType = 0x00, ///< For notifying errors
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. NoteOff = 0x80, ///< Note Off
DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. 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 ---------------------------------------------------------------- /*! Enumeration of Thru filter modes */
Sustain = 64, struct Thru
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, enum Mode
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)
{ {
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 sendSystemCommonMessage(MidiType::SongPosition, data1, data2);
data1 &= 0x7f; }
data2 &= 0x7f;
void sendSongSelect(DataByte number) {
const StatusByte status = getStatus(type, channel); sendSystemCommonMessage(MidiType::SongSelect, number & 0x7f);
}
if (type != MidiType::ProgramChange && type != MidiType::AfterTouchChannel)
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 sendRealTimeMessage(MidiType type)
// realTime messages {
virtual void send(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:
};
} }