diff --git a/main/firmware/config.cpp b/main/firmware/config.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/main/firmware/config.cpp @@ -0,0 +1 @@ + diff --git a/main/firmware/config.h b/main/firmware/config.h new file mode 100644 index 0000000..24fb519 --- /dev/null +++ b/main/firmware/config.h @@ -0,0 +1,123 @@ +#ifndef Config_h +#define Config_h + + +// Include Configuration +// If you don't want a feature, switch it of with a '0' +#define USE_SDCARD 1 +#define USE_LCD 1 +#define USE_AXEFX 1 + + +// ============== Pin Configuration ============== +// +// LCD-Pin Definitions +#if USE_LCD==1 + #define LCD_D7 44 + #define LCD_D6 45 + #define LCD_D5 46 + #define LCD_D4 47 + #define LCD_E 48 + #define LCD_RS 49 +#endif + + +// SD-Card-Pin Definitons +#if USE_SDCARD==1 + #define SDC_MISO 50 + #define SDC_MOSI 51 + #define SDC_SCK 52 + #define SDC_CS 53 +#endif + + +// LED und Switch Pins +#define SWT01 2 +#define LED01 3 +#define SWT02 4 +#define LED02 5 +#define SWT03 6 +#define LED03 7 +#define SWT04 8 +#define LED04 9 +#define SWT05 10 +#define LED05 11 +#define SWT06 12 +#define LED06 13 + +#define SWT07 22 +#define LED07 24 +#define SWT08 26 +#define LED08 28 +#define SWT09 30 +#define LED09 32 +#define SWT10 34 +#define LED10 36 +#define SWT11 38 +#define LED11 40 + +#define SWT12 23 +#define LED12 25 +#define SWT13 27 +#define LED13 29 +#define SWT14 31 +#define LED14 33 +#define SWT15 35 +#define LED15 37 +#define SWT16 39 +#define LED16 41 + +#define SWT17 42 +#define LED17 43 +#define SWT18 A8 +#define LED18 A9 +#define SWT19 A10 +#define LED19 A11 +#define SWT20 A12 +#define LED20 A13 +#define SWT21 A14 +#define LED21 A15 + +#define SWT22 A6 +#define LED22 A7 +#define SWT23 14 +#define LED23 15 + +// Not used if you have SD-Card +#if USE_SDCARD==0 + #define SWT24 16 + #define LED24 17 + #define SWT25 20 + #define LED25 21 + #define SWT26 50 + #define LED26 51 + #define SWT27 52 + #define LED27 99 + #define SWT28 53 + #define LED28 99 +#endif + + +// Expression-Pedal Pins +#define EXP01 A0 +#define EXP02 A1 + +// Layer-LEDs +#define LAY_LED01 A2 +#define LAY_LED02 A3 +#define LAY_LED03 A4 +#define LAY_LED04 A5 + + + + +// ============== Standard Board Configurtation =============== +// +// ...will be overwritten by SD-Card Config if you use SD-Cards! +// If there is no SD-Card or SD-Card-Config-File present +// it will use the Standard Configuration! +// + + + +#endif diff --git a/main/firmware/firmware.ino b/main/firmware/firmware.ino new file mode 100644 index 0000000..d06e67c --- /dev/null +++ b/main/firmware/firmware.ino @@ -0,0 +1,45 @@ +/** +* @file firmware.ino +* Project axefx.de MIDI Borad TWO +* @brief Main Firmware-File for MIDI-Board +* @version 1.0.0 +* @author Bastian Buehrig +* @date 13/02/15 +* license GPL axefx.de - 2015 +*/ + + +#include + +// Standard-Config +#include "config.h" + + +// SD-Card includes +#if USE_SDCARD==1 + #include + #include +#endif + + +// LCD-Display includes +#if USE_LCD==1 + #include + LiquidCrystal lcd(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7); +#endif + + + +// MIDI includes +#include +MIDI_CREATE_DEFAULT_INSTANCE(); + + +void setup() { + // put your setup code here, to run once: +} + +void loop() { + // put your main code here, to run repeatedly: + +} diff --git a/main/libraries/MIDI/.DS_Store b/main/libraries/MIDI/.DS_Store new file mode 100644 index 0000000..5e0ccbd Binary files /dev/null and b/main/libraries/MIDI/.DS_Store differ diff --git a/main/libraries/MIDI/MIDI.cpp b/main/libraries/MIDI/MIDI.cpp new file mode 100644 index 0000000..0ed34b9 --- /dev/null +++ b/main/libraries/MIDI/MIDI.cpp @@ -0,0 +1,98 @@ +/*! + * @file MIDI.cpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#include "MIDI.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +/*! \brief Encode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to convert the + data you want to send. + \param inData The data to encode. + \param outSysEx The output buffer where to store the encoded message. + \param inLength The lenght of the input buffer. + \return The lenght of the encoded output buffer. + @see decodeSysEx + Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com + */ +unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLength) +{ + unsigned outLength = 0; // Num bytes in output array. + byte count = 0; // Num 7bytes in a block. + outSysEx[0] = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + const byte data = inData[i]; + const byte msb = data >> 7; + const byte body = data & 0x7f; + + outSysEx[0] |= (msb << count); + outSysEx[1 + count] = body; + + if (count++ == 6) + { + outSysEx += 8; + outLength += 8; + outSysEx[0] = 0; + count = 0; + } + } + return outLength + count + (count != 0 ? 1 : 0); +} + +/*! \brief Decode System Exclusive messages. + SysEx messages are encoded to guarantee transmission of data bytes higher than + 127 without breaking the MIDI protocol. Use this static method to reassemble + your received message. + \param inSysEx The SysEx data received from MIDI in. + \param outData The output buffer where to store the decrypted message. + \param inLength The lenght of the input buffer. + \return The lenght of the output buffer. + @see encodeSysEx @see getSysExArrayLength + Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com + */ +unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLength) +{ + unsigned count = 0; + byte msbStorage = 0; + + for (unsigned i = 0; i < inLength; ++i) + { + if ((i % 8) == 0) + { + msbStorage = inSysEx[i]; + } + else + { + outData[count++] = inSysEx[i] | ((msbStorage & 1) << 7); + msbStorage >>= 1; + } + } + return count; +} + +END_MIDI_NAMESPACE diff --git a/main/libraries/MIDI/MIDI.h b/main/libraries/MIDI/MIDI.h new file mode 100644 index 0000000..d627c38 --- /dev/null +++ b/main/libraries/MIDI/MIDI.h @@ -0,0 +1,224 @@ +/*! + * @file MIDI.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +#include "midi_Defs.h" +#include "midi_Settings.h" +#include "midi_Message.h" + +// ----------------------------------------------------------------------------- + +BEGIN_MIDI_NAMESPACE + +/*! \brief The main class for MIDI handling. +It is templated over the type of serial port to provide abstraction from +the hardware interface, meaning you can use HardwareSerial, SoftwareSerial +or ak47's Uart classes. The only requirement is that the class implements +the begin, read, write and available methods. + */ +template +class MidiInterface +{ +public: + inline MidiInterface(SerialPort& inSerial); + inline ~MidiInterface(); + +public: + void begin(Channel inChannel = 1); + + // ------------------------------------------------------------------------- + // MIDI Output + +public: + inline void sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel); + + inline void sendProgramChange(DataByte inProgramNumber, + Channel inChannel); + + inline void sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel); + + inline void sendPitchBend(int inPitchValue, Channel inChannel); + inline void sendPitchBend(double inPitchValue, Channel inChannel); + + inline void sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel); + + inline void sendAfterTouch(DataByte inPressure, + Channel inChannel); + + inline void sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries = false); + + inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble); + inline void sendTimeCodeQuarterFrame(DataByte inData); + + inline void sendSongPosition(unsigned inBeats); + inline void sendSongSelect(DataByte inSongNumber); + inline void sendTuneRequest(); + inline void sendRealTime(MidiType inType); + +public: + void send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel); + + // ------------------------------------------------------------------------- + // MIDI Input + +public: + inline bool read(); + inline bool read(Channel inChannel); + +public: + inline MidiType getType() const; + inline Channel getChannel() const; + inline DataByte getData1() const; + inline DataByte getData2() const; + inline const byte* getSysExArray() const; + inline unsigned getSysExArrayLength() const; + inline bool check() const; + +public: + inline Channel getInputChannel() const; + inline void setInputChannel(Channel inChannel); + +public: + static inline MidiType getTypeFromStatusByte(byte inStatus); + static inline Channel getChannelFromStatusByte(byte inStatus); + static inline bool isChannelMessage(MidiType inType); + + + // ------------------------------------------------------------------------- + // Input Callbacks + +public: + inline void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)); + inline void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)); + inline void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)); + inline void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)); + inline void setHandleProgramChange(void (*fptr)(byte channel, byte number)); + inline void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)); + inline void setHandlePitchBend(void (*fptr)(byte channel, int bend)); + inline void setHandleSystemExclusive(void (*fptr)(byte * array, unsigned size)); + inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + inline void setHandleSongPosition(void (*fptr)(unsigned beats)); + inline void setHandleSongSelect(void (*fptr)(byte songnumber)); + inline void setHandleTuneRequest(void (*fptr)(void)); + inline void setHandleClock(void (*fptr)(void)); + inline void setHandleStart(void (*fptr)(void)); + inline void setHandleContinue(void (*fptr)(void)); + inline void setHandleStop(void (*fptr)(void)); + inline void setHandleActiveSensing(void (*fptr)(void)); + inline void setHandleSystemReset(void (*fptr)(void)); + + inline void disconnectCallbackFromType(MidiType inType); + +private: + void launchCallback(); + + void (*mNoteOffCallback)(byte channel, byte note, byte velocity); + void (*mNoteOnCallback)(byte channel, byte note, byte velocity); + void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity); + void (*mControlChangeCallback)(byte channel, byte, byte); + void (*mProgramChangeCallback)(byte channel, byte); + void (*mAfterTouchChannelCallback)(byte channel, byte); + void (*mPitchBendCallback)(byte channel, int); + void (*mSystemExclusiveCallback)(byte * array, unsigned size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(unsigned beats); + void (*mSongSelectCallback)(byte songnumber); + void (*mTuneRequestCallback)(void); + void (*mClockCallback)(void); + void (*mStartCallback)(void); + void (*mContinueCallback)(void); + void (*mStopCallback)(void); + void (*mActiveSensingCallback)(void); + void (*mSystemResetCallback)(void); + + // ------------------------------------------------------------------------- + // MIDI Soft Thru + +public: + inline MidiFilterMode getFilterMode() const; + inline bool getThruState() const; + + inline void turnThruOn(MidiFilterMode inThruFilterMode = Full); + inline void turnThruOff(); + inline void setThruFilterMode(MidiFilterMode inThruFilterMode); + +private: + void thruFilter(byte inChannel); + +private: + bool parse(); + inline void handleNullVelocityNoteOnAsNoteOff(); + inline bool inputFilter(Channel inChannel); + inline void resetInput(); + +private: + bool mThruActivated : 1; + MidiFilterMode mThruFilterMode : 7; + +private: + typedef Message MidiMessage; + +private: + StatusByte mRunningStatus_RX; + StatusByte mRunningStatus_TX; + Channel mInputChannel; + byte mPendingMessage[3]; + unsigned mPendingMessageExpectedLenght; + unsigned mPendingMessageIndex; + MidiMessage mMessage; + +private: + inline StatusByte getStatus(MidiType inType, + Channel inChannel) const; + +private: + SerialPort& mSerial; +}; + +// ----------------------------------------------------------------------------- + +unsigned encodeSysEx(const byte* inData, byte* outSysEx, unsigned inLenght); +unsigned decodeSysEx(const byte* inSysEx, byte* outData, unsigned inLenght); + +END_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +#include "MIDI.hpp" diff --git a/main/libraries/MIDI/MIDI.hpp b/main/libraries/MIDI/MIDI.hpp new file mode 100644 index 0000000..7e79d3c --- /dev/null +++ b/main/libraries/MIDI/MIDI.hpp @@ -0,0 +1,1204 @@ +/*! + * @file MIDI.hpp + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Inline implementations + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +BEGIN_MIDI_NAMESPACE + +/// \brief Constructor for MidiInterface. +template +inline MidiInterface::MidiInterface(SerialPort& inSerial) + : mSerial(inSerial) +{ + mNoteOffCallback = 0; + mNoteOnCallback = 0; + mAfterTouchPolyCallback = 0; + mControlChangeCallback = 0; + mProgramChangeCallback = 0; + mAfterTouchChannelCallback = 0; + mPitchBendCallback = 0; + mSystemExclusiveCallback = 0; + mTimeCodeQuarterFrameCallback = 0; + mSongPositionCallback = 0; + mSongSelectCallback = 0; + mTuneRequestCallback = 0; + mClockCallback = 0; + mStartCallback = 0; + mContinueCallback = 0; + mStopCallback = 0; + mActiveSensingCallback = 0; + mSystemResetCallback = 0; +} + +/*! \brief Destructor for MidiInterface. + + This is not really useful for the Arduino, as it is never called... + */ +template +inline MidiInterface::~MidiInterface() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Call the begin method in the setup() function of the Arduino. + + All parameters are set to their default values: + - Input channel set to 1 if no value is specified + - Full thru mirroring + */ +template +void MidiInterface::begin(Channel inChannel) +{ + // Initialise the Serial port +#if defined(FSE_AVR) + mSerial. template open(); +#else + mSerial.begin(Settings::BaudRate); +#endif + + mInputChannel = inChannel; + mRunningStatus_TX = InvalidType; + mRunningStatus_RX = InvalidType; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + + mThruFilterMode = Full; + mThruActivated = true; +} + +// ----------------------------------------------------------------------------- +// Output +// ----------------------------------------------------------------------------- + +/*! \addtogroup output + @{ + */ + +/*! \brief Generate and send a MIDI message from the values given. + \param inType The message type (see type defines for reference) + \param inData1 The first data byte. + \param inData2 The second data byte (if the message contains only 1 data byte, + set this one to 0). + \param inChannel The output channel on which the message will be sent + (values from 1 to 16). Note: you cannot send to OMNI. + + This is an internal method, use it only if you need to send raw data + from your code, at your own risks. + */ +template +void MidiInterface::send(MidiType inType, + DataByte inData1, + DataByte inData2, + Channel inChannel) +{ + // Then test if channel is valid + if (inChannel >= MIDI_CHANNEL_OFF || + inChannel == MIDI_CHANNEL_OMNI || + inType < 0x80) + { + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } + return; // Don't send anything + } + + if (inType <= PitchBend) // Channel messages + { + // Protection: remove MSBs on data + inData1 &= 0x7f; + inData2 &= 0x7f; + + const StatusByte status = getStatus(inType, inChannel); + + if (Settings::UseRunningStatus) + { + if (mRunningStatus_TX != status) + { + // New message, memorise and send header + mRunningStatus_TX = status; + mSerial.write(mRunningStatus_TX); + } + } + else + { + // Don't care about running status, send the status byte. + mSerial.write(status); + } + + // Then send data + mSerial.write(inData1); + if (inType != ProgramChange && inType != AfterTouchChannel) + { + mSerial.write(inData2); + } + } + else if (inType >= TuneRequest && inType <= SystemReset) + { + sendRealTime(inType); // System Real-time and 1 byte. + } +} + +// ----------------------------------------------------------------------------- + +/*! \brief Send a Note On message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity + is considered as a NoteOff. + \param inChannel The channel on which the message will be sent (1 to 16). + + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +template +void MidiInterface::sendNoteOn(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOn, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Note Off message + \param inNoteNumber Pitch value in the MIDI format (0 to 127). + \param inVelocity Release velocity (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + + Note: you can send NoteOn with zero velocity to make a NoteOff, this is based + on the Running Status principle, to avoid sending status messages and thus + sending only NoteOn data. sendNoteOff will always send a real NoteOff message. + Take a look at the values, names and frequencies of notes here: + http://www.phys.unsw.edu.au/jw/notes.html + */ +template +void MidiInterface::sendNoteOff(DataByte inNoteNumber, + DataByte inVelocity, + Channel inChannel) +{ + send(NoteOff, inNoteNumber, inVelocity, inChannel); +} + +/*! \brief Send a Program Change message + \param inProgramNumber The Program to select (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendProgramChange(DataByte inProgramNumber, + Channel inChannel) +{ + send(ProgramChange, inProgramNumber, 0, inChannel); +} + +/*! \brief Send a Control Change message + \param inControlNumber The controller number (0 to 127). + \param inControlValue The value for the specified controller (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + @see MidiControlChangeNumber + */ +template +void MidiInterface::sendControlChange(DataByte inControlNumber, + DataByte inControlValue, + Channel inChannel) +{ + send(ControlChange, inControlNumber, inControlValue, inChannel); +} + +/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note) + \param inNoteNumber The note to apply AfterTouch to (0 to 127). + \param inPressure The amount of AfterTouch to apply (0 to 127). + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPolyPressure(DataByte inNoteNumber, + DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchPoly, inNoteNumber, inPressure, inChannel); +} + +/*! \brief Send a MonoPhonic AfterTouch message (applies to all notes) + \param inPressure The amount of AfterTouch to apply to all notes. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendAfterTouch(DataByte inPressure, + Channel inChannel) +{ + send(AfterTouchChannel, inPressure, 0, inChannel); +} + +/*! \brief Send a Pitch Bend message using a signed integer value. + \param inPitchValue The amount of bend to send (in a signed integer format), + between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX, + center value is 0. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(int inPitchValue, + Channel inChannel) +{ + const unsigned bend = inPitchValue - MIDI_PITCHBEND_MIN; + send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel); +} + + +/*! \brief Send a Pitch Bend message using a floating point value. + \param inPitchValue The amount of bend to send (in a floating point format), + between -1.0f (maximum downwards bend) + and +1.0f (max upwards bend), center value is 0.0f. + \param inChannel The channel on which the message will be sent (1 to 16). + */ +template +void MidiInterface::sendPitchBend(double inPitchValue, + Channel inChannel) +{ + const int value = inPitchValue * MIDI_PITCHBEND_MAX * Settings::Toto; + sendPitchBend(value, inChannel); +} + +/*! \brief Generate and send a System Exclusive frame. + \param inLength The size of the array to send + \param inArray The byte array containing the data to send + \param inArrayContainsBoundaries When set to 'true', 0xf0 & 0xf7 bytes + (start & stop SysEx) will NOT be sent + (and therefore must be included in the array). + default value for ArrayContainsBoundaries is set to 'false' for compatibility + with previous versions of the library. + */ +template +void MidiInterface::sendSysEx(unsigned inLength, + const byte* inArray, + bool inArrayContainsBoundaries) +{ + const bool writeBeginEndBytes = !inArrayContainsBoundaries; + + if (writeBeginEndBytes) + { + mSerial.write(0xf0); + } + + for (unsigned i = 0; i < inLength; ++i) + { + mSerial.write(inArray[i]); + } + + if (writeBeginEndBytes) + { + mSerial.write(0xf7); + } + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Tune Request message. + + When a MIDI unit receives this message, + it should tune its oscillators (if equipped with any). + */ +template +void MidiInterface::sendTuneRequest() +{ + sendRealTime(TuneRequest); +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + \param inTypeNibble MTC type + \param inValuesNibble MTC data + See MIDI Specification for more information. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble, + DataByte inValuesNibble) +{ + const byte data = (((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f)); + sendTimeCodeQuarterFrame(data); +} + +/*! \brief Send a MIDI Time Code Quarter Frame. + + See MIDI Specification for more information. + \param inData if you want to encode directly the nibbles in your program, + you can send the byte here. + */ +template +void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData) +{ + mSerial.write((byte)TimeCodeQuarterFrame); + mSerial.write(inData); + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Song Position Pointer message. + \param inBeats The number of beats since the start of the song. + */ +template +void MidiInterface::sendSongPosition(unsigned inBeats) +{ + mSerial.write((byte)SongPosition); + mSerial.write(inBeats & 0x7f); + mSerial.write((inBeats >> 7) & 0x7f); + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Song Select message */ +template +void MidiInterface::sendSongSelect(DataByte inSongNumber) +{ + mSerial.write((byte)SongSelect); + mSerial.write(inSongNumber & 0x7f); + + if (Settings::UseRunningStatus) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! \brief Send a Real Time (one byte) message. + + \param inType The available Real Time types are: + Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + You can also send a Tune Request with this method. + @see MidiType + */ +template +void MidiInterface::sendRealTime(MidiType inType) +{ + switch (inType) + { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + mSerial.write((byte)inType); + break; + default: + // Invalid Real Time marker + break; + } + + // Do not cancel Running Status for real-time messages as they can be + // interleaved within any message. Though, TuneRequest can be sent here, + // and as it is a System Common message, it must reset Running Status. + if (Settings::UseRunningStatus && inType == TuneRequest) + { + mRunningStatus_TX = InvalidType; + } +} + +/*! @} */ // End of doc group MIDI Output + +// ----------------------------------------------------------------------------- + +template +StatusByte MidiInterface::getStatus(MidiType inType, + Channel inChannel) const +{ + return ((byte)inType | ((inChannel - 1) & 0x0f)); +} + +// ----------------------------------------------------------------------------- +// Input +// ----------------------------------------------------------------------------- + +/*! \addtogroup input + @{ +*/ + +/*! \brief Read messages from the serial port using the main input channel. + + \return True if a valid message has been stored in the structure, false if not. + A valid message is a message that matches the input channel. \n\n + If the Thru is enabled and the message matches the filter, + it is sent back on the MIDI output. + @see see setInputChannel() + */ +template +inline bool MidiInterface::read() +{ + return read(mInputChannel); +} + +/*! \brief Read messages on a specified channel. + */ +template +inline bool MidiInterface::read(Channel inChannel) +{ + if (inChannel >= MIDI_CHANNEL_OFF) + return false; // MIDI Input disabled. + + if (!parse()) + return false; + + handleNullVelocityNoteOnAsNoteOff(); + const bool channelMatch = inputFilter(inChannel); + + if (channelMatch) + { + launchCallback(); + } + + thruFilter(inChannel); + + return channelMatch; +} + +// ----------------------------------------------------------------------------- + +// Private method: MIDI parser +template +bool MidiInterface::parse() +{ + if (mSerial.available() == 0) + // No data available. + return false; + + // Parsing algorithm: + // Get a byte from the serial buffer. + // If there is no pending message to be recomposed, start a new one. + // - Find type and channel (if pertinent) + // - Look for other bytes in buffer, call parser recursively, + // until the message is assembled or the buffer is empty. + // Else, add the extracted byte to the pending message, and check validity. + // When the message is done, store it. + + const byte extracted = mSerial.read(); + + if (mPendingMessageIndex == 0) + { + // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX))) + { + // Only these types allow Running Status + + // If the status byte is not received, prepend it + // to the pending message + if (extracted < 0x80) + { + mPendingMessage[0] = mRunningStatus_RX; + mPendingMessage[1] = extracted; + mPendingMessageIndex = 1; + } + // Else: well, we received another status byte, + // so the running status does not apply here. + // It will be updated upon completion of this message. + } + + switch (getTypeFromStatusByte(mPendingMessage[0])) + { + // 1 byte messages + case Start: + case Continue: + case Stop: + case Clock: + case ActiveSensing: + case SystemReset: + case TuneRequest: + // Handle the message type directly here. + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.valid = true; + + // \fix Running Status broken when receiving Clock messages. + // Do not reset all input attributes, Running Status must remain unchanged. + //resetInput(); + + // We still need to reset these + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + return true; + break; + + // 2 bytes messages + case ProgramChange: + case AfterTouchChannel: + case TimeCodeQuarterFrame: + case SongSelect: + mPendingMessageExpectedLenght = 2; + break; + + // 3 bytes messages + case NoteOn: + case NoteOff: + case ControlChange: + case PitchBend: + case AfterTouchPoly: + case SongPosition: + mPendingMessageExpectedLenght = 3; + break; + + case SystemExclusive: + // The message can be any lenght + // between 3 and MidiMessage::sSysExMaxSize bytes + mPendingMessageExpectedLenght = MidiMessage::sSysExMaxSize; + mRunningStatus_RX = InvalidType; + mMessage.sysexArray[0] = SystemExclusive; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + resetInput(); + return false; + break; + } + + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + { + // Reception complete + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mMessage.valid = true; + return true; + } + else + { + // Waiting for more data + mPendingMessageIndex++; + } + + if (Settings::Use1ByteParsing) + { + // Message is not complete. + return false; + } + else + { + // Call the parser recursively + // to parse the rest of the message. + return parse(); + } + } + else + { + // First, test if this is a status byte + if (extracted >= 0x80) + { + // Reception of status bytes in the middle of an uncompleted message + // are allowed only for interleaved Real Time message or EOX + switch (extracted) + { + case Clock: + case Start: + case Continue: + case Stop: + case ActiveSensing: + case SystemReset: + + // Here we will have to extract the one-byte message, + // pass it to the structure for being read outside + // the MIDI class, and recompose the message it was + // interleaved into. Oh, and without killing the running status.. + // This is done by leaving the pending message as is, + // it will be completed on next calls. + + mMessage.type = (MidiType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xf7: + if (mMessage.sysexArray[0] == SystemExclusive) + { + // Store the last byte (EOX) + mMessage.sysexArray[mPendingMessageIndex++] = 0xf7; + mMessage.type = SystemExclusive; + + // Get length + mMessage.data1 = mPendingMessageIndex & 0xff; // LSB + mMessage.data2 = mPendingMessageIndex >> 8; // MSB + mMessage.channel = 0; + mMessage.valid = true; + + resetInput(); + return true; + } + else + { + // Well well well.. error. + resetInput(); + return false; + } + + break; + default: + break; + } + } + + // Add extracted data byte to pending message + if (mPendingMessage[0] == SystemExclusive) + mMessage.sysexArray[mPendingMessageIndex] = extracted; + else + mPendingMessage[mPendingMessageIndex] = extracted; + + // Now we are going to check if we have reached the end of the message + if (mPendingMessageIndex >= (mPendingMessageExpectedLenght - 1)) + { + // "FML" case: fall down here with an overflown SysEx.. + // This means we received the last possible data byte that can fit + // the buffer. If this happens, try increasing MidiMessage::sSysExMaxSize. + if (mPendingMessage[0] == SystemExclusive) + { + resetInput(); + return false; + } + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + + if (isChannelMessage(mMessage.type)) + mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]); + else + mMessage.channel = 0; + + mMessage.data1 = mPendingMessage[1]; + + // Save data2 only if applicable + if (mPendingMessageExpectedLenght == 3) + mMessage.data2 = mPendingMessage[2]; + else + mMessage.data2 = 0; + + // Reset local variables + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = true; + + // Activate running status (if enabled for the received type) + switch (mMessage.type) + { + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + // Running status enabled: store it from received message + mRunningStatus_RX = mPendingMessage[0]; + break; + + default: + // No running status + mRunningStatus_RX = InvalidType; + break; + } + return true; + } + else + { + // Then update the index of the pending message. + mPendingMessageIndex++; + + if (Settings::Use1ByteParsing) + { + // Message is not complete. + return false; + } + else + { + // Call the parser recursively to parse the rest of the message. + return parse(); + } + } + } +} + +// Private method, see midi_Settings.h for documentation +template +inline void MidiInterface::handleNullVelocityNoteOnAsNoteOff() +{ + if (Settings::HandleNullVelocityNoteOnAsNoteOff && + getType() == NoteOn && getData2() == 0) + { + mMessage.type = NoteOff; + } +} + +// Private method: check if the received message is on the listened channel +template +inline bool MidiInterface::inputFilter(Channel inChannel) +{ + // This method handles recognition of channel + // (to know if the message is destinated to the Arduino) + + if (mMessage.type == InvalidType) + return false; + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + // Then we need to know if we listen to it + if ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)) + { + return true; + } + else + { + // We don't listen to this channel + return false; + } + } + else + { + // System messages are always received + return true; + } +} + +// Private method: reset input attributes +template +inline void MidiInterface::resetInput() +{ + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Get the last received message's type + + Returns an enumerated type. @see MidiType + */ +template +inline MidiType MidiInterface::getType() const +{ + return mMessage.type; +} + +/*! \brief Get the channel of the message stored in the structure. + + \return Channel range is 1 to 16. + For non-channel messages, this will return 0. + */ +template +inline Channel MidiInterface::getChannel() const +{ + return mMessage.channel; +} + +/*! \brief Get the first data byte of the last received message. */ +template +inline DataByte MidiInterface::getData1() const +{ + return mMessage.data1; +} + +/*! \brief Get the second data byte of the last received message. */ +template +inline DataByte MidiInterface::getData2() const +{ + return mMessage.data2; +} + +/*! \brief Get the System Exclusive byte array. + + @see getSysExArrayLength to get the array's length in bytes. + */ +template +inline const byte* MidiInterface::getSysExArray() const +{ + return mMessage.sysexArray; +} + +/*! \brief Get the lenght of the System Exclusive array. + + It is coded using data1 as LSB and data2 as MSB. + \return The array's length, in bytes. + */ +template +inline unsigned MidiInterface::getSysExArrayLength() const +{ + return mMessage.getSysExSize(); +} + +/*! \brief Check if a valid message is stored in the structure. */ +template +inline bool MidiInterface::check() const +{ + return mMessage.valid; +} + +// ----------------------------------------------------------------------------- + +template +inline Channel MidiInterface::getInputChannel() const +{ + return mInputChannel; +} + +/*! \brief Set the value for the input MIDI channel + \param inChannel the channel value. Valid values are 1 to 16, MIDI_CHANNEL_OMNI + if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable input. + */ +template +inline void MidiInterface::setInputChannel(Channel inChannel) +{ + mInputChannel = inChannel; +} + +// ----------------------------------------------------------------------------- + +/*! \brief Extract an enumerated MIDI type from a status byte. + + This is a utility static method, used internally, + made public so you can handle MidiTypes more easily. + */ +template +MidiType MidiInterface::getTypeFromStatusByte(byte inStatus) +{ + if ((inStatus < 0x80) || + (inStatus == 0xf4) || + (inStatus == 0xf5) || + (inStatus == 0xf9) || + (inStatus == 0xfD)) + { + // Data bytes and undefined. + return InvalidType; + } + if (inStatus < 0xf0) + { + // Channel message, remove channel nibble. + return MidiType(inStatus & 0xf0); + } + + return MidiType(inStatus); +} + +/*! \brief Returns channel in the range 1-16 + */ +template +inline Channel MidiInterface::getChannelFromStatusByte(byte inStatus) +{ + return (inStatus & 0x0f) + 1; +} + +template +bool MidiInterface::isChannelMessage(MidiType inType) +{ + return (inType == NoteOff || + inType == NoteOn || + inType == ControlChange || + inType == AfterTouchPoly || + inType == AfterTouchChannel || + inType == PitchBend || + inType == ProgramChange); +} + +// ----------------------------------------------------------------------------- + +/*! \addtogroup callbacks + @{ + */ + +template void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; } +template void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; } +template void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; } +template void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; } +template void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; } +template void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; } +template void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; } +template void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, unsigned size)) { mSystemExclusiveCallback = fptr; } +template void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +template void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned beats)) { mSongPositionCallback = fptr; } +template void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; } +template void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +template void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +template void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +template void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +template void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +template void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +template void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } + +/*! \brief Detach an external function from the given type. + + Use this method to cancel the effects of setHandle********. + \param inType The type of message to unbind. + When a message of this type is received, no function will be called. + */ +template +void MidiInterface::disconnectCallbackFromType(MidiType inType) +{ + switch (inType) + { + case NoteOff: mNoteOffCallback = 0; break; + case NoteOn: mNoteOnCallback = 0; break; + case AfterTouchPoly: mAfterTouchPolyCallback = 0; break; + case ControlChange: mControlChangeCallback = 0; break; + case ProgramChange: mProgramChangeCallback = 0; break; + case AfterTouchChannel: mAfterTouchChannelCallback = 0; break; + case PitchBend: mPitchBendCallback = 0; break; + case SystemExclusive: mSystemExclusiveCallback = 0; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break; + case SongPosition: mSongPositionCallback = 0; break; + case SongSelect: mSongSelectCallback = 0; break; + case TuneRequest: mTuneRequestCallback = 0; break; + case Clock: mClockCallback = 0; break; + case Start: mStartCallback = 0; break; + case Continue: mContinueCallback = 0; break; + case Stop: mStopCallback = 0; break; + case ActiveSensing: mActiveSensingCallback = 0; break; + case SystemReset: mSystemResetCallback = 0; break; + default: + break; + } +} + +/*! @} */ // End of doc group MIDI Callbacks + +// Private - launch callback function based on received type. +template +void MidiInterface::launchCallback() +{ + // The order is mixed to allow frequent messages to trigger their callback faster. + switch (mMessage.type) + { + // Notes + case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != 0) mClockCallback(); break; + case Start: if (mStartCallback != 0) mStartCallback(); break; + case Continue: if (mContinueCallback != 0) mContinueCallback(); break; + case Stop: if (mStopCallback != 0) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel, (int)((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this + case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel, mMessage.data1, mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel, mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel, mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysexArray, mMessage.getSysExSize()); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)); break; + case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } +} + +/*! @} */ // End of doc group MIDI Input + +// ----------------------------------------------------------------------------- +// Thru +// ----------------------------------------------------------------------------- + +/*! \addtogroup thru + @{ + */ + +/*! \brief Set the filter for thru mirroring + \param inThruFilterMode a filter mode + + @see MidiFilterMode + */ +template +void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode) +{ + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) + mThruActivated = true; + else + mThruActivated = false; +} + +template +MidiFilterMode MidiInterface::getFilterMode() const +{ + return mThruFilterMode; +} + +template +bool MidiInterface::getThruState() const +{ + return mThruActivated; +} + +template +void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode) +{ + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} + +template +void MidiInterface::turnThruOff() +{ + mThruActivated = false; + mThruFilterMode = Off; +} + +/*! @} */ // End of doc group MIDI Thru + +// This method is called upon reception of a message +// and takes care of Thru filtering and sending. +// - All system messages (System Exclusive, Common and Real Time) are passed +// to output unless filter is set to Off. +// - Channel messages are passed to the output whether their channel +// is matching the input channel and the filter setting +template +void MidiInterface::thruFilter(Channel inChannel) +{ + // If the feature is disabled, don't do anything. + if (!mThruActivated || (mThruFilterMode == Off)) + return; + + // First, check if the received message is Channel + if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) + { + const bool filter_condition = ((mMessage.channel == mInputChannel) || + (mInputChannel == MIDI_CHANNEL_OMNI)); + + // Now let's pass it to the output + switch (mThruFilterMode) + { + case Full: + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + break; + + case SameChannel: + if (filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + case DifferentChannel: + if (!filter_condition) + { + send(mMessage.type, + mMessage.data1, + mMessage.data2, + mMessage.channel); + } + break; + + case Off: + // Do nothing. + // Technically it's impossible to get there because + // the case was already tested earlier. + break; + + default: + break; + } + } + else + { + // Send the message to the output + switch (mMessage.type) + { + // Real Time and 1 byte + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + case TuneRequest: + sendRealTime(mMessage.type); + break; + + case SystemExclusive: + // Send SysEx (0xf0 and 0xf7 are included in the buffer) + sendSysEx(getSysExArrayLength(), getSysExArray(), true); + break; + + case SongSelect: + sendSongSelect(mMessage.data1); + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2 << 7)); + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); + break; + default: + break; + } + } +} + +END_MIDI_NAMESPACE diff --git a/main/libraries/MIDI/examples/.DS_Store b/main/libraries/MIDI/examples/.DS_Store new file mode 100644 index 0000000..926acb1 Binary files /dev/null and b/main/libraries/MIDI/examples/.DS_Store differ diff --git a/main/libraries/MIDI/examples/MIDI_Basic_IO/MIDI_Basic_IO.ino b/main/libraries/MIDI/examples/MIDI_Basic_IO/MIDI_Basic_IO.ino new file mode 100644 index 0000000..f6efee2 --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_Basic_IO/MIDI_Basic_IO.ino @@ -0,0 +1,27 @@ +#include + +// Simple tutorial on how to receive and send MIDI messages. +// Here, when receiving any message on channel 4, the Arduino +// will blink a led and play back a note for 1 second. + +MIDI_CREATE_DEFAULT_INSTANCE(); + +#define LED 13 // LED pin on Arduino Uno + +void setup() +{ + pinMode(LED, OUTPUT); + MIDI.begin(4); // Launch MIDI and listen to channel 4 +} + +void loop() +{ + if (MIDI.read()) // If we have received a message + { + digitalWrite(LED,HIGH); + MIDI.sendNoteOn(42,127,1); // Send a Note (pitch 42, velo 127 on channel 1) + delay(1000); // Wait for a second + MIDI.sendNoteOff(42,0,1); // Stop the note + digitalWrite(LED,LOW); + } +} diff --git a/main/libraries/MIDI/examples/MIDI_Bench/MIDI_Bench.ino b/main/libraries/MIDI/examples/MIDI_Bench/MIDI_Bench.ino new file mode 100644 index 0000000..a97eaaf --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_Bench/MIDI_Bench.ino @@ -0,0 +1,85 @@ +#include + +// This program will measure the time needed to receive, parse and process a +// NoteOn message. +// For it to work, please connect RX and TX on the MIDI port: +// Due, Leonardo and other USB-native Arduinos: Serial1 +// All other Arduinos: Connect pins 2 and 3. +// The program will then wait for 100 loops and print the results. + +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) + // Print through USB and bench with Hardware serial + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiBench); +#else + #include + SoftwareSerial midiSerial(2,3); + MIDI_CREATE_INSTANCE(SoftwareSerial, midiSerial, midiBench); +#endif + +// ----------------------------------------------------------------------------- + +unsigned long gTime_start = 0; +unsigned long gTime_stop = 0; +unsigned gCounter = 0; +unsigned long gTime_sum = 0; +unsigned long gTime_min = -1; +unsigned long gTime_max = 0; + +// ----------------------------------------------------------------------------- + +void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) +{ + gTime_stop = micros(); + + const unsigned long diff = gTime_stop - gTime_start; + gTime_sum += diff; + + if (diff > gTime_max) gTime_max = diff; + if (diff < gTime_min) gTime_min = diff; + + if (gCounter++ >= 1000) + { + const unsigned long average = gTime_sum / (float)gCounter; + + Serial.println("Time to receive NoteOn: "); + + Serial.print("Average: "); + Serial.print(average); + Serial.println(" microsecs"); + + Serial.print("Min: "); + Serial.print(gTime_min); + Serial.println(" microsecs"); + + Serial.print("Max: "); + Serial.print(gTime_max); + Serial.println(" microsecs"); + + gCounter = 0; + gTime_sum = 0; + gTime_max = 0; + gTime_min = -1; + + midiBench.turnThruOff(); + } +} + +// ----------------------------------------------------------------------------- + +void setup() +{ + midiBench.setHandleNoteOn(handleNoteOn); + midiBench.begin(); + + while(!Serial); + Serial.begin(115200); + Serial.println("Arduino Ready"); + + midiBench.sendNoteOn(69,127,1); +} + +void loop() +{ + gTime_start = micros(); + midiBench.read(); +} diff --git a/main/libraries/MIDI/examples/MIDI_Callbacks/MIDI_Callbacks.ino b/main/libraries/MIDI/examples/MIDI_Callbacks/MIDI_Callbacks.ino new file mode 100644 index 0000000..642223b --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_Callbacks/MIDI_Callbacks.ino @@ -0,0 +1,51 @@ +#include + +MIDI_CREATE_DEFAULT_INSTANCE(); + +// ----------------------------------------------------------------------------- + +// This function will be automatically called when a NoteOn is received. +// It must be a void-returning function with the correct parameters, +// see documentation here: +// http://arduinomidilib.fortyseveneffects.com/a00022.html + +void handleNoteOn(byte channel, byte pitch, byte velocity) +{ + // Do whatever you want when a note is pressed. + + // Try to keep your callbacks short (no delays ect) + // otherwise it would slow down the loop() and have a bad impact + // on real-time performance. +} + +void handleNoteOff(byte channel, byte pitch, byte velocity) +{ + // Do something when the note is released. + // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. +} + +// ----------------------------------------------------------------------------- + +void setup() +{ + // Connect the handleNoteOn function to the library, + // so it is called upon reception of a NoteOn. + MIDI.setHandleNoteOn(handleNoteOn); // Put only the name of the function + + // Do the same for NoteOffs + MIDI.setHandleNoteOff(handleNoteOff); + + // Initiate MIDI communications, listen to all channels + MIDI.begin(MIDI_CHANNEL_OMNI); +} + +void loop() +{ + // Call MIDI.read the fastest you can for real-time performance. + MIDI.read(); + + // There is no need to check if there are messages incoming + // if they are bound to a Callback function. + // The attached method will be called automatically + // when the corresponding message has been received. +} diff --git a/main/libraries/MIDI/examples/MIDI_DualMerger/MIDI_DualMerger.ino b/main/libraries/MIDI/examples/MIDI_DualMerger/MIDI_DualMerger.ino new file mode 100644 index 0000000..740fa5b --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_DualMerger/MIDI_DualMerger.ino @@ -0,0 +1,47 @@ +#include + +// This example shows how to create two instances of the library to create a merger. +// There are two MIDI couples of IO, A and B, each using thru and merging with the +// input from the other node. The result is the following: +// A out = A in + B in +// B out = B in + A in + +#ifdef ARDUINO_SAM_DUE + MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); +#else + #include + SoftwareSerial softSerial(2,3); + MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); + MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB); +#endif + +void setup() +{ + // Initiate MIDI communications, listen to all channels + midiA.begin(MIDI_CHANNEL_OMNI); + midiB.begin(MIDI_CHANNEL_OMNI); +} + +void loop() +{ + if (midiA.read()) + { + // Thru on A has already pushed the input message to out A. + // Forward the message to out B as well. + midiB.send(midiA.getType(), + midiA.getData1(), + midiA.getData2(), + midiA.getChannel()); + } + + if (midiB.read()) + { + // Thru on B has already pushed the input message to out B. + // Forward the message to out A as well. + midiA.send(midiB.getType(), + midiB.getData1(), + midiB.getData2(), + midiB.getChannel()); + } +} diff --git a/main/libraries/MIDI/examples/MIDI_Input/MIDI_Input.ino b/main/libraries/MIDI/examples/MIDI_Input/MIDI_Input.ino new file mode 100644 index 0000000..4bdcb02 --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_Input/MIDI_Input.ino @@ -0,0 +1,49 @@ +#include + +MIDI_CREATE_DEFAULT_INSTANCE(); + +// ----------------------------------------------------------------------------- + +// This example shows the old way of checking for input messages. +// It's simpler to use the callbacks now, check out the dedicated example. + +#define LED 13 // LED pin on Arduino Uno + +// ----------------------------------------------------------------------------- + +void BlinkLed(byte num) // Basic blink function +{ + for (byte i=0;i +#include "noteList.h" +#include "pitches.h" + +MIDI_CREATE_DEFAULT_INSTANCE(); + +#ifdef ARDUINO_SAM_DUE // Due has no tone function (yet), overriden to prevent build errors. +#define tone(...) +#define noTone(...) +#endif + +// This example shows how to make a simple synth out of an Arduino, using the +// tone() function. It also outputs a gate signal for controlling external +// analog synth components (like envelopes). + +static const unsigned sGatePin = 13; +static const unsigned sAudioOutPin = 10; +static const unsigned sMaxNumNotes = 16; +MidiNoteList midiNotes; + +// ----------------------------------------------------------------------------- + +inline void handleGateChanged(bool inGateActive) +{ + digitalWrite(sGatePin, inGateActive ? HIGH : LOW); +} + +inline void pulseGate() +{ + handleGateChanged(false); + delay(1); + handleGateChanged(true); +} + +// ----------------------------------------------------------------------------- + +void handleNotesChanged(bool isFirstNote = false) +{ + if (midiNotes.empty()) + { + handleGateChanged(false); + noTone(sAudioOutPin); + } + else + { + // Possible playing modes: + // Mono Low: use midiNotes.getLow + // Mono High: use midiNotes.getHigh + // Mono Last: use midiNotes.getLast + + byte currentNote = 0; + if (midiNotes.getLast(currentNote)) + { + tone(sAudioOutPin, sNotePitches[currentNote]); + + if (isFirstNote) + { + handleGateChanged(true); + } + else + { + pulseGate(); // Retrigger envelopes. Remove for legato effect. + } + } + } +} + +// ----------------------------------------------------------------------------- + +void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) +{ + const bool firstNote = midiNotes.empty(); + midiNotes.add(MidiNote(inNote, inVelocity)); + handleNotesChanged(firstNote); +} + +void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) +{ + midiNotes.remove(inNote); + handleNotesChanged(); +} + +// ----------------------------------------------------------------------------- + +void setup() +{ + pinMode(sGatePin, OUTPUT); + pinMode(sAudioOutPin, OUTPUT); + MIDI.setHandleNoteOn(handleNoteOn); + MIDI.setHandleNoteOff(handleNoteOff); + MIDI.begin(); +} + +void loop() +{ + MIDI.read(); +} diff --git a/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.cpp b/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.cpp new file mode 100644 index 0000000..7f1afe3 --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.cpp @@ -0,0 +1,21 @@ +/*! + * \file synth-core_NoteList.h + * \author Francois Best + * \date 24/05/2013 + * \license GPL v3.0 - Copyright Forty Seven Effects 2013 + * + * 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 . + */ + +#include "noteList.h" diff --git a/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.h b/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.h new file mode 100644 index 0000000..18ef829 --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_SimpleSynth/noteList.h @@ -0,0 +1,391 @@ +/*! + * \file noteList.h + * \author Francois Best + * \date 24/05/2013 + * \brief Linked list of notes, for Low, Last & High playing modes. + * \license GPL v3.0 - Copyright Forty Seven Effects 2013 + * + * 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 . + */ + +#pragma once + +#include + +typedef uint8_t byte; + +// ----------------------------------------------------------------------------- + +struct MidiNote +{ + inline MidiNote(); + inline MidiNote(byte inPitch, byte inVelocity); + inline MidiNote(const MidiNote& inOther); + inline MidiNote& operator= (const MidiNote& inOther); + + byte pitch; + byte velocity; +}; + +// ----------------------------------------------------------------------------- + +template +class MidiNoteList +{ +private: + struct Cell + { + inline Cell(); + inline Cell(const Cell& inOther); + inline Cell& operator= (const Cell& inOther); + + MidiNote note; + bool active; + Cell* next; + Cell* prev; + }; + +public: + inline MidiNoteList(); + inline ~MidiNoteList(); + +public: + inline void add(const MidiNote& inNote); + inline void remove(byte inPitch); + +public: + inline bool get(byte inIndex, byte& outPitch) const; + inline bool getLast(byte& outPitch) const; + inline bool getHigh(byte& outPitch) const; + inline bool getLow(byte& outPitch) const; + +public: + inline bool empty() const; + inline byte size() const; + +private: + inline Cell* getFirstEmptyCell(); + inline void print() const; + +private: + Cell mArray[Size]; + Cell* mHead; + Cell* mTail; + byte mSize; +}; + +// ########################################################################## // +// Inline implementation + +inline MidiNote::MidiNote() + : pitch(0) + , velocity(0) +{ +} + +inline MidiNote::MidiNote(byte inPitch, byte inVelocity) + : pitch(inPitch) + , velocity(inVelocity) +{ +} + +inline MidiNote::MidiNote(const MidiNote& inOther) + : pitch(inOther.pitch) + , velocity(inOther.velocity) +{ +} + +inline MidiNote& MidiNote::operator= (const MidiNote& inOther) +{ + pitch = inOther.pitch; + velocity = inOther.velocity; + return *this; +} + +// ########################################################################## // + +template +inline MidiNoteList::Cell::Cell() + : note() + , active(false) + , next(0) + , prev(0) +{ +} + +template +inline MidiNoteList::Cell::Cell(const Cell& inOther) + : note(inOther.note) + , active(inOther.active) + , next(inOther.next) + , prev(inOther.prev) +{ +} + +template +inline typename MidiNoteList::Cell& MidiNoteList::Cell::operator= (const Cell& inOther) +{ + note = inOther.note; + active = inOther.active; + next = inOther.next; + prev = inOther.prev; + return *this; +} + +// ########################################################################## // + +template +inline MidiNoteList::MidiNoteList() +{ +} + +template +inline MidiNoteList::~MidiNoteList() +{ +} + +// ----------------------------------------------------------------------------- + +/*! \brief Add a note, sorting it by time. + Call this when receiving a NoteOn event. This will add the new note as the tail + of the list. + */ +template +inline void MidiNoteList::add(const MidiNote& inNote) +{ + if (mHead == 0) + { + mArray[0].note = inNote; + mArray[0].active = true; + mArray[0].next = 0; + mArray[0].prev = 0; + mHead = mArray; + mTail = mArray; + } + else + { + // Find the first inactive cell, and use it as tail. + Cell* const oldTail = mTail; + Cell* const newTail = getFirstEmptyCell(); + + newTail->active = true; + newTail->note = inNote; + + oldTail->next = newTail; + newTail->prev = oldTail; + newTail->next = 0; + mTail = newTail; + } + mSize++; + print(); +} + +/*! \brief Remove a note + Call this when receiving a NoteOff event. + */ +template +inline void MidiNoteList::remove(byte inPitch) +{ + if (mTail != 0) + { + for (Cell* it = mTail; it != 0; it = it->prev) + { + if (it->note.pitch == inPitch) + { + Cell* const prev = it->prev; + Cell* const next = it->next; + + it->active = false; + it->next = 0; + it->prev = 0; + + // Reconnect both ends + if (it == mHead) + { + //AVR_ASSERT(prev == 0); + mHead = next; + } + else + { + //AVR_ASSERT(prev != 0); + prev->next = next; + } + + if (it == mTail) + { + //AVR_ASSERT(next == 0); + mTail = prev; + } + else + { + //AVR_ASSERT(next != 0); + next->prev = prev; + } + + mSize--; + break; + } + } + } + print(); +} + +// ----------------------------------------------------------------------------- + +/*! \brief Get a note at an arbitrary position + This can be interesting for duo/multi/polyphony operations. + */ +template +inline bool MidiNoteList::get(byte inIndex, byte& outPitch) const +{ + if (mTail) + { + const Cell* it = mTail; + for (byte i = 0; i < inIndex; ++i) + { + if (it->prev) + { + it = it->prev; + } + } + + print(); + //AVR_LOG("Index " << inIndex << ": " << it->note.pitch); + + outPitch = it->note.pitch; + return true; + } + return false; +} + +/*! \brief Get the last active note played + This implements the Mono Last playing mode. + */ +template +inline bool MidiNoteList::getLast(byte& outPitch) const +{ + if (!mTail) + { + return false; + } + + outPitch = mTail->note.pitch; + return true; +} + +/*! \brief Get the highest pitched active note + This implements the Mono High playing mode. + */ +template +inline bool MidiNoteList::getHigh(byte& outPitch) const +{ + if (!mTail) + { + return false; + } + + outPitch = 0; + const Cell* it = mTail; + for (byte i = 0; i < mSize; ++i) + { + if (it->note.pitch > outPitch) + { + outPitch = it->note.pitch; + } + + if (it->prev) + { + it = it->prev; + } + } + return true; +} + +/*! \brief Get the lowest pitched active note + This implements the Mono Low playing mode. + */ +template +inline bool MidiNoteList::getLow(byte& outPitch) const +{ + if (!mTail) + { + return false; + } + + outPitch = 0xff; + const Cell* it = mTail; + for (byte i = 0; i < mSize; ++i) + { + if (it->note.pitch < outPitch) + { + outPitch = it->note.pitch; + } + + if (it->prev) + { + it = it->prev; + } + } + return true; +} + +// ----------------------------------------------------------------------------- + +template +inline bool MidiNoteList::empty() const +{ + return mSize == 0; +} + +/*! \brief Get the number of active notes. + */ +template +inline byte MidiNoteList::size() const +{ + return mSize; +} + +// ----------------------------------------------------------------------------- +// Private implementations, for internal use only. + +template +inline typename MidiNoteList::Cell* MidiNoteList::getFirstEmptyCell() +{ + for (byte i = 0; i < Size; ++i) + { + if (mArray[i].active == false) + { + return mArray + i; + } + } + return 0; +} + +template +inline void MidiNoteList::print() const +{ +//#ifndef NDEBUG +// AVR_DBG("Note List: [ "); +// if (mHead) +// { +// for (const Cell* it = mHead; it != 0; it = it->next) +// { +// AVR_DBG(it->note.pitch); +// if (it->next) +// AVR_DBG(" -> "); +// } +// } +// AVR_LOG(" ]"); +//#endif +} diff --git a/main/libraries/MIDI/examples/MIDI_SimpleSynth/pitches.h b/main/libraries/MIDI/examples/MIDI_SimpleSynth/pitches.h new file mode 100644 index 0000000..284c14d --- /dev/null +++ b/main/libraries/MIDI/examples/MIDI_SimpleSynth/pitches.h @@ -0,0 +1,108 @@ +/************************************************* + * Public Constants + *************************************************/ +#include + +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 + +static const uint16_t sNotePitches[] = { + NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, + NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, + NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, + NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, + NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, + NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, + NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, + NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, + NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, + NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, + NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, +}; diff --git a/main/libraries/MIDI/keywords.txt b/main/libraries/MIDI/keywords.txt new file mode 100644 index 0000000..d415b4d --- /dev/null +++ b/main/libraries/MIDI/keywords.txt @@ -0,0 +1,112 @@ +####################################### +# Syntax Coloring Map For Test +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +MIDI KEYWORD1 +MIDI.h KEYWORD1 +MidiInterface KEYWORD1 +DefaultSettings KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +send KEYWORD2 +sendNoteOn KEYWORD2 +sendNoteOff KEYWORD2 +sendProgramChange KEYWORD2 +sendControlChange KEYWORD2 +sendPitchBend KEYWORD2 +sendPolyPressure KEYWORD2 +sendAfterTouch KEYWORD2 +sendSysEx KEYWORD2 +sendTimeCodeQuarterFrame KEYWORD2 +sendSongPosition KEYWORD2 +sendSongSelect KEYWORD2 +sendTuneRequest KEYWORD2 +sendRealTime KEYWORD2 +begin KEYWORD2 +read KEYWORD2 +getType KEYWORD2 +getChannel KEYWORD2 +getData1 KEYWORD2 +getData2 KEYWORD2 +getSysExArray KEYWORD2 +getFilterMode KEYWORD2 +getThruState KEYWORD2 +getInputChannel KEYWORD2 +check KEYWORD2 +delMsg KEYWORD2 +delSysEx KEYWORD2 +setInputChannel KEYWORD2 +setStatus KEYWORD2 +turnThruOn KEYWORD2 +turnThruOff KEYWORD2 +setThruFilterMode KEYWORD2 +disconnectCallbackFromType KEYWORD2 +setHandleNoteOff KEYWORD2 +setHandleNoteOn KEYWORD2 +setHandleAfterTouchPoly KEYWORD2 +setHandleControlChange KEYWORD2 +setHandleProgramChange KEYWORD2 +setHandleAfterTouchChannel KEYWORD2 +setHandlePitchBend KEYWORD2 +setHandleSystemExclusive KEYWORD2 +setHandleTimeCodeQuarterFrame KEYWORD2 +setHandleSongPosition KEYWORD2 +setHandleSongSelect KEYWORD2 +setHandleTuneRequest KEYWORD2 +setHandleClock KEYWORD2 +setHandleStart KEYWORD2 +setHandleContinue KEYWORD2 +setHandleStop KEYWORD2 +setHandleActiveSensing KEYWORD2 +setHandleSystemReset KEYWORD2 +getTypeFromStatusByte KEYWORD2 +encodeSysEx KEYWORD2 +decodeSysEx KEYWORD2 + + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + +# Namespace, considering it as a literal +midi LITERAL1 + +NoteOff LITERAL1 +NoteOn LITERAL1 +AfterTouchPoly LITERAL1 +ControlChange LITERAL1 +ProgramChange LITERAL1 +AfterTouchChannel LITERAL1 +PitchBend LITERAL1 +SystemExclusive LITERAL1 +TimeCodeQuarterFrame LITERAL1 +SongPosition LITERAL1 +SongSelect LITERAL1 +TuneRequest LITERAL1 +Clock LITERAL1 +Start LITERAL1 +Stop LITERAL1 +Continue LITERAL1 +ActiveSensing LITERAL1 +SystemReset LITERAL1 +InvalidType LITERAL1 +Off LITERAL1 +Full LITERAL1 +SameChannel LITERAL1 +DifferentChannel LITERAL1 +MIDI_CHANNEL_OMNI LITERAL1 +MIDI_CHANNEL_OFF LITERAL1 +MIDI_CREATE_INSTANCE LITERAL1 +MIDI_CREATE_DEFAULT_INSTANCE LITERAL1 +MIDI_CREATE_CUSTOM_INSTANCE LITERAL1 diff --git a/main/libraries/MIDI/midi_Defs.h b/main/libraries/MIDI/midi_Defs.h new file mode 100644 index 0000000..3404797 --- /dev/null +++ b/main/libraries/MIDI/midi_Defs.h @@ -0,0 +1,193 @@ +/*! + * @file midi_Defs.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Definitions + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +#include "midi_Namespace.h" + +#if ARDUINO +#include +#else +#include +typedef uint8_t byte; +#endif + +BEGIN_MIDI_NAMESPACE + +// ----------------------------------------------------------------------------- + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_PITCHBEND_MIN -8192 +#define MIDI_PITCHBEND_MAX 8191 + +// ----------------------------------------------------------------------------- +// Type definitions + +typedef byte StatusByte; +typedef byte DataByte; +typedef byte Channel; +typedef byte FilterMode; + +// ----------------------------------------------------------------------------- + +/*! Enumeration of MIDI types */ +enum MidiType +{ + 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 + 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 + 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 */ +enum MidiFilterMode +{ + 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 +{ + // 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 +}; + +// ----------------------------------------------------------------------------- + +/*! \brief Create an instance of the library attached to a serial port. + You can use HardwareSerial or SoftwareSerial for the serial port. + Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); + Then call midi2.begin(), midi2.read() etc.. + */ +#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ + midi::MidiInterface Name((Type&)SerialPort); + +#if defined(ARDUINO_SAM_DUE) || defined(USBCON) + // Leonardo, Due and other USB boards use Serial1 by default. + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); +#else + /*! \brief Create an instance of the library with default name, serial port + and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib, + or if you don't bother using custom names, serial port or settings. + */ + #define MIDI_CREATE_DEFAULT_INSTANCE() \ + MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); +#endif + +/*! \brief Create an instance of the library attached to a serial port with + custom settings. + @see DefaultSettings + @see MIDI_CREATE_INSTANCE + */ +#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ + midi::MidiInterface Name((Type&)SerialPort); + +END_MIDI_NAMESPACE diff --git a/main/libraries/MIDI/midi_Message.h b/main/libraries/MIDI/midi_Message.h new file mode 100644 index 0000000..31ee3d1 --- /dev/null +++ b/main/libraries/MIDI/midi_Message.h @@ -0,0 +1,81 @@ +/*! + * @file midi_Message.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Message struct definition + * @version 4.2 + * @author Francois Best + * @date 11/06/14 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +#include "midi_Namespace.h" +#include "midi_Defs.h" + +BEGIN_MIDI_NAMESPACE + +/*! The Message structure contains decoded data of a MIDI message + read from the serial port with read() + */ +template +struct Message +{ + /*! The maximum size for the System Exclusive array. + */ + static const unsigned sSysExMaxSize = SysExMaxSize; + + /*! The MIDI channel on which the message was recieved. + \n Value goes from 1 to 16. + */ + Channel channel; + + /*! The type of the message + (see the MidiType enum for types reference) + */ + MidiType type; + + /*! The first data byte. + \n Value goes from 0 to 127. + */ + DataByte data1; + + /*! The second data byte. + If the message is only 2 bytes long, this one is null. + \n Value goes from 0 to 127. + */ + DataByte data2; + + /*! System Exclusive dedicated byte array. + \n Array length is stocked on 16 bits, + in data1 (LSB) and data2 (MSB) + */ + DataByte sysexArray[sSysExMaxSize]; + + /*! This boolean indicates if the message is valid or not. + There is no channel consideration here, + validity means the message respects the MIDI norm. + */ + bool valid; + + inline unsigned getSysExSize() const + { + const unsigned size = unsigned(data2) << 8 | data1; + return size > sSysExMaxSize ? sSysExMaxSize : size; + } +}; + +END_MIDI_NAMESPACE diff --git a/main/libraries/MIDI/midi_Namespace.h b/main/libraries/MIDI/midi_Namespace.h new file mode 100644 index 0000000..4b93929 --- /dev/null +++ b/main/libraries/MIDI/midi_Namespace.h @@ -0,0 +1,34 @@ +/*! + * @file midi_Namespace.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Namespace declaration + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +#define MIDI_NAMESPACE midi +#define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE { +#define END_MIDI_NAMESPACE } + +#define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE; + +BEGIN_MIDI_NAMESPACE + +END_MIDI_NAMESPACE diff --git a/main/libraries/MIDI/midi_Settings.h b/main/libraries/MIDI/midi_Settings.h new file mode 100644 index 0000000..cf9a3dc --- /dev/null +++ b/main/libraries/MIDI/midi_Settings.h @@ -0,0 +1,76 @@ +/*! + * @file midi_Settings.h + * Project Arduino MIDI Library + * @brief MIDI Library for the Arduino - Settings + * @version 4.2 + * @author Francois Best + * @date 24/02/11 + * @license GPL v3.0 - Copyright Forty Seven Effects 2014 + * + * 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 . + */ + +#pragma once + +#include "midi_Defs.h" + +BEGIN_MIDI_NAMESPACE + +/*! \brief Default Settings for the MIDI Library. + + To change the default settings, don't edit them there, create a subclass and + override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE + macro to create your instance. The settings you don't override will keep their + default value. Eg: + \code{.cpp} + struct MySettings : public midi::DefaultSettings + { + static const bool UseRunningStatus = false; // Messes with my old equipment! + }; + + MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, midi, MySettings); + \endcode + */ +struct DefaultSettings +{ + /*! Running status enables short messages when sending multiple values + of the same type and channel.\n + Set to 0 if you have troubles controlling your hardware. + */ + static const bool UseRunningStatus = true; + + /* NoteOn with 0 velocity should be handled as NoteOf.\n + Set to 1 to get NoteOff events when receiving null-velocity NoteOn messages.\n + Set to 0 to get NoteOn events when receiving null-velocity NoteOn messages. + */ + static const bool HandleNullVelocityNoteOnAsNoteOff = true; + + // Setting this to 1 will make MIDI.read parse only one byte of data for each + // call when data is available. This can speed up your application if receiving + // a lot of traffic, but might induce MIDI Thru and treatment latency. + static const bool Use1ByteParsing = true; + + /*! Override the default MIDI baudrate to transmit over USB serial, to + a decoding program such as Hairless MIDI (set baudrate to 115200)\n + http://projectgus.github.io/hairless-midiserial/ + */ + static const long BaudRate = 31250; + + /*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect + to receive SysEx, or adjust accordingly. + */ + static const unsigned SysExMaxSize = 128; +}; + +END_MIDI_NAMESPACE diff --git a/tools/testing/config.h b/tools/testing/config.h new file mode 100644 index 0000000..764c63c --- /dev/null +++ b/tools/testing/config.h @@ -0,0 +1,115 @@ +/** +* @file config.h +* Project axefx.de MIDI Borad TWO +* @brief Config-File for Testing-Tool. +* Here you have to modify your used Arduino-Pins +* and if you like to use the LCD-Display. +* @version 1.0.0 +* @author Bastian Buehrig +* @date 15/02/15 +* license GPL axefx.de - 2015 +*/ + +#ifndef Config_h +#define Config_h + + +// Include Configuration +// If you don't want a feature, switch it of with a '0' +#define USE_LCD 1 + + +// ============== Pin Configuration ============== +// +// LCD-Pin Definitions +#if USE_LCD==1 + #define LCD_D7 44 + #define LCD_D6 45 + #define LCD_D5 46 + #define LCD_D4 47 + #define LCD_E 48 + #define LCD_RS 49 +#endif + + +// LED und Switch Pins +#define SWT_MAX 23 // How many switches (and LEDs) does your MIDI-Board have? + +#define SWT01 2 +#define LED01 3 +#define SWT02 4 +#define LED02 5 +#define SWT03 6 +#define LED03 7 +#define SWT04 8 +#define LED04 9 +#define SWT05 10 +#define LED05 11 +#define SWT06 12 +#define LED06 13 + +#define SWT07 22 +#define LED07 24 +#define SWT08 26 +#define LED08 28 +#define SWT09 30 +#define LED09 32 +#define SWT10 34 +#define LED10 36 +#define SWT11 38 +#define LED11 40 + +#define SWT12 23 +#define LED12 25 +#define SWT13 27 +#define LED13 29 +#define SWT14 31 +#define LED14 33 +#define SWT15 35 +#define LED15 37 +#define SWT16 39 +#define LED16 41 + +#define SWT17 42 +#define LED17 43 +#define SWT18 A8 +#define LED18 A9 +#define SWT19 A10 +#define LED19 A11 +#define SWT20 A12 +#define LED20 A13 +#define SWT21 A14 +#define LED21 A15 + +#define SWT22 A6 +#define LED22 A7 +#define SWT23 14 +#define LED23 15 + +#define SWT24 16 +#define LED24 17 +#define SWT25 20 +#define LED25 21 +#define SWT26 50 +#define LED26 51 +#define SWT27 52 +#define LED27 99 +#define SWT28 53 +#define LED28 99 + + +// Expression-Pedal Pins +#define EXP01 A0 +#define EXP02 A1 + +// Layer-LEDs +#define LAY_LED01 A2 +#define LAY_LED02 A3 +#define LAY_LED03 A4 +#define LAY_LED04 A5 + + + + + +#endif diff --git a/tools/testing/testing.ino b/tools/testing/testing.ino index b236bb4..6f808f3 100644 --- a/tools/testing/testing.ino +++ b/tools/testing/testing.ino @@ -1,24 +1,105 @@ +/** +* @file testing.ino +* Project axefx.de MIDI Borad TWO +* @brief Testing-Tool for test all your switches, leds and +* LCD-Display (if you'll use one). +* It will turn on and of all your LEDs. After it, +* you can test your switches to light up the LEDs manually. +* Additionally you can see some Informations on your LCD-Display +* and it writes the same text in the serial monitor. +* @version 1.0.0 +* @author Bastian Buehrig +* @date 16/02/15 +* license GPL axefx.de - 2015 +*/ + +#include "config.h" + + +// LCD-Display includes +#if USE_LCD==1 + #include + LiquidCrystal lcd(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7); +#endif + + +// Pin-Definitions - you have to modify this list, if you like to use more or less than 23 Switches! +byte swt_pins[SWT_MAX] = { SWT01, SWT02, SWT03, SWT04, SWT05, SWT06, SWT07, SWT08, SWT09, SWT10, + SWT11, SWT12, SWT13, SWT14, SWT15, SWT16, SWT17, SWT18, SWT19, SWT20, + SWT21, SWT22, SWT23 }; // SWT24, SWT25, SWT26, SWT27, SWT28 + +byte led_pins[SWT_MAX] = { LED01, LED02, LED03, LED04, LED05, LED06, LED07, LED08, LED09, LED10, + LED11, LED12, LED13, LED14, LED15, LED16, LED17, LED18, LED19, LED20, + LED21, LED22, LED23 }; // LED24, LED25, LED26, LED27, LED28 + + + +/** + * Function to initialize the pin-modes and + * switch on and off shortly all LEDs. + * + */ void setup() { + // Init Serial Monitor for Informations... Serial.begin(9600); - for(int i=22; i <= 36; i=i+2) { - pinMode(i, INPUT); - pinMode(i+1, OUTPUT); + + + #if USE_LCD==1 + // set up the LCD's number of columns and rows: + lcd.begin(16, 2); + #endif + + printInfo("Initialisation..."); + + + // Setting Pin-Modes + for(byte i=0; i < SWT_MAX; i=i+1) { + pinMode(swt_pins[i], INPUT_PULLUP); + pinMode(led_pins[i], OUTPUT); } - for(int i=22; i <= 36; i=i+2) { - digitalWrite(i+1, HIGH); // turn the LED on (HIGH is the voltage level) + + // Turn LED on, then off + for(byte i=0; i < SWT_MAX; i=i+1) { + printInfo("LED " + String(i+1)); + + digitalWrite(led_pins[i], HIGH); // turn the LED on (HIGH is the voltage level) delay(100); // wait for a second - digitalWrite(i+1, LOW); // turn the LED off by making the voltage LOW + digitalWrite(led_pins[i], LOW); // turn the LED off by making the voltage LOW delay(100); } } + + +/** + * Main Loop-Function. Read out the Switch-States and + * if you press a switch, the Switch LED will be switch on + * during you are pressing the switch. + * + */ void loop() { - for(int i=22; i <= 36; i=i+2) { - byte actState = digitalRead(i); - digitalWrite(i+1, actState); + for(byte i=0; i < SWT_MAX; i=i+1) { + byte actState = digitalRead(swt_pins[i]); + digitalWrite(led_pins[i], !actState); - + if(!actState) { + printInfo("Switch " + String(i+1) + " pressed!"); + } } } + + +/** + * Function to print out informations to Serial-Monitor and LCD-Dsiplay + * + * @param infoText Text to print out + */ +void printInfo(String infoText) { + Serial.println(infoText); + + #if USE_LCD==1 + lcd.print(infoText); + #endif +}