diff --git a/teensy_core/usb_midi/core_id.h b/teensy_core/usb_midi/core_id.h new file mode 100644 index 0000000..2da150d --- /dev/null +++ b/teensy_core/usb_midi/core_id.h @@ -0,0 +1,4 @@ +#define CORE_TEENSY_HID +#define CORE_TEENSY_MIDI +#define CORE_TEENSY_KEYBOARD +//#define CORE_TEENSY_JOYSTICK diff --git a/teensy_core/usb_midi/usb.c b/teensy_core/usb_midi/usb.c new file mode 100644 index 0000000..0a872d1 --- /dev/null +++ b/teensy_core/usb_midi/usb.c @@ -0,0 +1,597 @@ +/* USB Serial Example for Teensy USB Development Board + * http://www.pjrc.com/teensy/usb_serial.html + * Copyright (c) 2008 PJRC.COM, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "usb_common.h" +#include "usb_private.h" + + + +/************************************************************************** + * + * Endpoint Buffer Configuration + * + **************************************************************************/ + + +static const uint8_t PROGMEM endpoint_config_table[] = { + 1, EP_TYPE_INTERRUPT_IN, EP_SIZE(DEBUG_TX_SIZE) | DEBUG_TX_BUFFER, + 1, EP_TYPE_INTERRUPT_OUT, EP_SIZE(DEBUG_RX_SIZE) | DEBUG_RX_BUFFER, + 1, EP_TYPE_BULK_IN, EP_SIZE(MIDI_TX_SIZE) | MIDI_TX_BUFFER, + 1, EP_TYPE_BULK_OUT, EP_SIZE(MIDI_RX_SIZE) | MIDI_RX_BUFFER +}; + + +/************************************************************************** + * + * Descriptor Data + * + **************************************************************************/ + +// Descriptors are the data that your computer reads when it auto-detects +// this USB device (called "enumeration" in USB lingo). The most commonly +// changed items are editable at the top of this file. Changing things +// in here should only be done by those who've read chapter 9 of the USB +// spec and relevant portions of any USB class specifications! + +static uint8_t PROGMEM device_descriptor[] = { + 18, // bLength + 1, // bDescriptorType + 0x00, 0x02, // bcdUSB + 0, // bDeviceClass + 0, // bDeviceSubClass + 0, // bDeviceProtocol + ENDPOINT0_SIZE, // bMaxPacketSize0 + LSB(VENDOR_ID), MSB(VENDOR_ID), // idVendor + LSB(PRODUCT_ID), MSB(PRODUCT_ID), // idProduct + 0x00, 0x01, // bcdDevice + 0, // iManufacturer + 1, // iProduct + 0, // iSerialNumber + 1 // bNumConfigurations +}; + +static uint8_t PROGMEM debug_hid_report_desc[] = { + 0x06, 0xC9, 0xFF, // Usage Page 0xFFC9 (vendor defined) + 0x09, 0x04, // Usage 0x04 + 0xA1, 0x5C, // Collection 0x5C + 0x75, 0x08, // report size = 8 bits (global) + 0x15, 0x00, // logical minimum = 0 (global) + 0x26, 0xFF, 0x00, // logical maximum = 255 (global) + 0x95, DEBUG_TX_SIZE, // report count (global) + 0x09, 0x75, // usage (local) + 0x81, 0x02, // Input + 0x95, DEBUG_RX_SIZE, // report count (global) + 0x09, 0x76, // usage (local) + 0x91, 0x02, // Output + 0x95, 0x04, // report count (global) + 0x09, 0x76, // usage (local) + 0xB1, 0x02, // Feature + 0xC0 // end collection +}; + + + +#define CONFIG1_DESC_SIZE ( 9 + 74 + 32 ) +#define DEBUG_HID_DESC_OFFSET ( 9 + 74 + 9 ) + +static uint8_t PROGMEM config1_descriptor[CONFIG1_DESC_SIZE] = { + // configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10 + 9, // bLength; + 2, // bDescriptorType; + LSB(CONFIG1_DESC_SIZE), // wTotalLength + MSB(CONFIG1_DESC_SIZE), + NUM_INTERFACE, // bNumInterfaces + 1, // bConfigurationValue + 0, // iConfiguration + 0xC0, // bmAttributes + 50, // bMaxPower + + // This MIDI stuff is a copy of the example from the Audio Class + // MIDI spec 1.0 (Nov 1, 1999), Appendix B, pages 37 to 43. + + // Standard MS Interface Descriptor, + 9, // bLength + 4, // bDescriptorType + MIDI_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x01, // bInterfaceClass (0x01 = Audio) + 0x03, // bInterfaceSubClass (0x03 = MIDI) + 0x00, // bInterfaceProtocol (unused for MIDI) + 0, // iInterface + + // MIDI MS Interface Header, USB MIDI 6.1.2.1, page 21, Table 6-2 + 7, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x01, // bDescriptorSubtype = MS_HEADER + 0x00, 0x01, // bcdMSC = revision 01.00 + 0x41, 0x00, // wTotalLength + + // MIDI IN Jack Descriptor, B.4.3, Table B-7 (embedded), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x01, // bJackType = EMBEDDED + 1, // bJackID, ID = 1 + 0, // iJack + + // MIDI IN Jack Descriptor, B.4.3, Table B-8 (external), page 40 + 6, // bLength + 0x24, // bDescriptorType = CS_INTERFACE + 0x02, // bDescriptorSubtype = MIDI_IN_JACK + 0x02, // bJackType = EXTERNAL + 2, // bJackID, ID = 2 + 0, // iJack + + // MIDI OUT Jack Descriptor, B.4.4, Table B-9, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x01, // bJackType = EMBEDDED + 3, // bJackID, ID = 3 + 1, // bNrInputPins = 1 pin + 2, // BaSourceID(1) = 2 + 1, // BaSourcePin(1) = first pin + 0, // iJack + + // MIDI OUT Jack Descriptor, B.4.4, Table B-10, page 41 + 9, + 0x24, // bDescriptorType = CS_INTERFACE + 0x03, // bDescriptorSubtype = MIDI_OUT_JACK + 0x02, // bJackType = EXTERNAL + 4, // bJackID, ID = 4 + 1, // bNrInputPins = 1 pin + 1, // BaSourceID(1) = 1 + 1, // BaSourcePin(1) = first pin + 0, // iJack + + // Standard Bulk OUT Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_RX_ENDPOINT, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + MIDI_RX_SIZE, 0, // wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + + // Class-specific MS Bulk OUT Endpoint Descriptor, B.5.2, Table B-12, page 42 + 5, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + 1, // bNumEmbMIDIJack = 1 jack + 1, // BaAssocJackID(1) = jack ID #1 + + // Standard Bulk IN Endpoint Descriptor, B.5.1, Table B-11, pae 42 + 9, // bLength + 5, // bDescriptorType = ENDPOINT + MIDI_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x02, // bmAttributes (0x02=bulk) + MIDI_TX_SIZE, 0, // wMaxPacketSize + 0, // bInterval + 0, // bRefresh + 0, // bSynchAddress + + // Class-specific MS Bulk IN Endpoint Descriptor, B.5.2, Table B-12, page 42 + 5, // bLength + 0x25, // bDescriptorSubtype = CS_ENDPOINT + 0x01, // bJackType = MS_GENERAL + 1, // bNumEmbMIDIJack = 1 jack + 3, // BaAssocJackID(1) = jack ID #3 + + + // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 + 9, // bLength + 4, // bDescriptorType + DEBUG_INTERFACE, // bInterfaceNumber + 0, // bAlternateSetting + 2, // bNumEndpoints + 0x03, // bInterfaceClass (0x03 = HID) + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0, // iInterface + // HID interface descriptor, HID 1.11 spec, section 6.2.1 + 9, // bLength + 0x21, // bDescriptorType + 0x11, 0x01, // bcdHID + 0, // bCountryCode + 1, // bNumDescriptors + 0x22, // bDescriptorType + sizeof(debug_hid_report_desc), // wDescriptorLength + 0, + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + DEBUG_TX_ENDPOINT | 0x80, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + DEBUG_TX_SIZE, 0, // wMaxPacketSize + DEBUG_TX_INTERVAL, // bInterval + // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 + 7, // bLength + 5, // bDescriptorType + DEBUG_RX_ENDPOINT, // bEndpointAddress + 0x03, // bmAttributes (0x03=intr) + DEBUG_RX_SIZE, 0, // wMaxPacketSize + DEBUG_RX_INTERVAL, // bInterval + +}; + +// If you're desperate for a little extra code memory, these strings +// can be completely removed if iManufacturer, iProduct, iSerialNumber +// in the device desciptor are changed to zeros. +struct usb_string_descriptor_struct { + uint8_t bLength; + uint8_t bDescriptorType; + int16_t wString[]; +}; +static struct usb_string_descriptor_struct PROGMEM string0 = { + 4, + 3, + {0x0409} +}; +static struct usb_string_descriptor_struct PROGMEM string1 = { + sizeof(STR_PRODUCT), + 3, + STR_PRODUCT +}; + +// This table defines which descriptor data is sent for each specific +// request from the host (in wValue and wIndex). +static struct descriptor_list_struct { + uint16_t wValue; + uint16_t wIndex; + const uint8_t *addr; + uint8_t length; +} PROGMEM descriptor_list[] = { + {0x0100, 0x0000, device_descriptor, sizeof(device_descriptor)}, + {0x0200, 0x0000, config1_descriptor, sizeof(config1_descriptor)}, + {0x2200, DEBUG_INTERFACE, debug_hid_report_desc, sizeof(debug_hid_report_desc)}, + {0x2100, DEBUG_INTERFACE, config1_descriptor+DEBUG_HID_DESC_OFFSET, 9}, + {0x0300, 0x0000, (const uint8_t *)&string0, 4}, + {0x0301, 0x0409, (const uint8_t *)&string1, sizeof(STR_PRODUCT)}, +}; +#define NUM_DESC_LIST (sizeof(descriptor_list)/sizeof(struct descriptor_list_struct)) + + +/************************************************************************** + * + * Variables - these are the only non-stack RAM usage + * + **************************************************************************/ + +// zero when we are not configured, non-zero when enumerated +volatile uint8_t usb_configuration USBSTATE; +volatile uint8_t usb_suspended USBSTATE; + +// the time remaining before we transmit any partially full +// packet, or send a zero length packet. +volatile uint8_t debug_flush_timer USBSTATE; + + + + + +/************************************************************************** + * + * Public Functions - these are the API intended for the user + * + **************************************************************************/ + + + +// initialize USB serial +void usb_init(void) +{ + uint8_t u; + + u = USBCON; + if ((u & (1<= 8000000L) + // WAKEUPI does not work with USB clock freeze + // when CPU is running less than 8 MHz. + // Is this a hardware bug? + USB_FREEZE(); // shut off USB + PLLCSR = 0; // shut off PLL + #endif + // to properly meet the USB spec, current must + // reduce to less than 2.5 mA, which means using + // powerdown mode, but that breaks the Arduino + // user's paradigm.... + } + if (usb_suspended && (intbits & (1<= 8000000L) + PLL_CONFIG(); + while (!(PLLCSR & (1<= NUM_DESC_LIST) { + UECONX = (1< desc_length) len = desc_length; + list = desc_addr; + do { + // wait for host ready for IN packet + do { + i = UEINTX; + } while (!(i & ((1<= 1 && i <= MAX_ENDPOINT) { + usb_send_in(); + UENUM = i; + if (bRequest == SET_FEATURE) { + UECONX = (1< +#include +#include +#include "usb_common.h" +#include "usb_private.h" +#include "usb_api.h" +#include "wiring.h" + +#include "usb_midi.h" + + + +void usb_serial_class::begin(long speed) +{ + // make sure USB is initialized + usb_init(); + uint16_t begin_wait = (uint16_t)millis(); + while (1) { + if (usb_configuration) { + delay(200); // a little time for host to load a driver + return; + } + if (usb_suspended) { + uint16_t begin_suspend = (uint16_t)millis(); + while (usb_suspended) { + // must remain suspended for a while, because + // normal USB enumeration causes brief suspend + // states, typically under 0.1 second + if ((uint16_t)millis() - begin_suspend > 250) { + return; + } + } + } + // ... or a timout (powered by a USB power adaptor that + // wiggles the data lines to keep a USB device charging) + if ((uint16_t)millis() - begin_wait > 2500) return; + } +} + +void usb_serial_class::end(void) +{ + usb_shutdown(); + delay(25); +} + + +static volatile uint8_t prev_byte=0; + +// number of bytes available in the receive buffer +uint8_t usb_serial_class::available(void) +{ + uint8_t c; + + c = prev_byte; // assume 1 byte static volatile access is atomic + if (c) return 1; + c = readnext(); + if (c) { + prev_byte = c; + return 1; + } + return 0; +} + +// get the next character, or -1 if nothing received +int usb_serial_class::read(void) +{ + uint8_t c; + + c = prev_byte; + if (c) { + prev_byte = 0; + return c; + } + c = readnext(); + if (c) return c; + return -1; +} + +// get the next character, or 0 if nothing +uint8_t usb_serial_class::readnext(void) +{ + uint8_t c, c2, intr_state; + + // interrupts are disabled so these functions can be + // used from the main program or interrupt context, + // even both in the same program! + intr_state = SREG; + cli(); + if (!usb_configuration) { + SREG = intr_state; + return 0; + } + UENUM = DEBUG_RX_ENDPOINT; +try_again: + if (!(UEINTX & (1< + +#include "Print.h" + +class MIDI_Class; +extern MIDI_Class usbMIDI; + +class usb_serial_class : public Print +{ +public: + // standard Arduino functions + void begin(long); + void end(void); + uint8_t available(void); + int read(void); + void flush(void); + virtual void write(uint8_t); + // Teensy extensions + void send_now(void); + uint32_t baud(void); + uint8_t stopbits(void); + uint8_t paritytype(void); + uint8_t numbits(void); + uint8_t dtr(void); + uint8_t rts(void); +private: + uint8_t readnext(void); +}; + +extern usb_serial_class usbSerial; + + +#endif diff --git a/teensy_core/usb_midi/usb_midi.cpp b/teensy_core/usb_midi/usb_midi.cpp new file mode 100644 index 0000000..962e307 --- /dev/null +++ b/teensy_core/usb_midi/usb_midi.cpp @@ -0,0 +1,899 @@ +/*! + * @file usb_midi.cpp + * Project Teensy MIDI Core + * @brief MIDI Library for Teensy - USB side + * Version 3.1 + * @author Francois Best + * @date 28/04/11 + * License GPL Forty Seven Effects - 2011 + */ + +#include "usb_midi.h" +#include +#include "WConstants.h" +#include "usb_api.h" + +#define USE_SERIAL_PORT usbSerial + +/*! Main instance (the class comes pre-instantiated). */ +MIDI_Class usbMIDI; + + +/*! Default constructor for MIDI_Class. */ +MIDI_Class::MIDI_Class() { +#if USE_CALLBACKS + // Initialise callbacks to NULL pointer + mNoteOffCallback = NULL; + mNoteOnCallback = NULL; + mAfterTouchPolyCallback = NULL; + mControlChangeCallback = NULL; + mProgramChangeCallback = NULL; + mAfterTouchChannelCallback = NULL; + mPitchBendCallback = NULL; + mSystemExclusiveCallback = NULL; + mTimeCodeQuarterFrameCallback = NULL; + mSongPositionCallback = NULL; + mSongSelectCallback = NULL; + mTuneRequestCallback = NULL; + mClockCallback = NULL; + mStartCallback = NULL; + mContinueCallback = NULL; + mStopCallback = NULL; + mActiveSensingCallback = NULL; + mSystemResetCallback = NULL; +#endif +} +/*! Default destructor for MIDI_Class.\n + This is not really useful for the Arduino, as it is never called... + */ +MIDI_Class::~MIDI_Class() { } + + +/*! 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 + */ +void MIDI_Class::begin(const byte inChannel) { + + // Initialise the Serial port + USE_SERIAL_PORT.begin(MIDI_BAUDRATE); + + +#if COMPILE_MIDI_OUT + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif // USE_RUNNING_STATUS + +#endif // COMPILE_MIDI_OUT + + +#if COMPILE_MIDI_IN + + mInputChannel = inChannel; + mRunningStatus_RX = InvalidType; + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + + mMessage.valid = false; + mMessage.type = InvalidType; + mMessage.channel = 0; + mMessage.data1 = 0; + mMessage.data2 = 0; + +#endif // COMPILE_MIDI_IN + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + + mThruFilterMode = Full; + mThruActivated = true; + +#endif // Thru + +} + + +#if COMPILE_MIDI_OUT + +// Private method for generating a status byte from channel and type +const byte MIDI_Class::genstatus(const kMIDIType inType,const byte inChannel) { + return ((byte)inType | ((inChannel-1) & 0x0F)); +} + +/* Generate and send a custom MIDI mMessage. + \param type The message type (see type defines for reference) + \param data1 The first data byte. + \param data2 The second data byte (if the message contains only 1 data byte, set this one to 0). + \param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI. + */ +void MIDI_Class::send(kMIDIType type, byte data1, byte data2, byte channel) { + + // Then test if channel is valid + if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) { + +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif + + return; // Don't send anything + } + + if (type <= PitchBend) { + // Channel messages + + // Protection: remove MSBs on data + data1 &= 0x7F; + data2 &= 0x7F; + + byte statusbyte = genstatus(type,channel); + +#if USE_RUNNING_STATUS + // Check Running Status + if (mRunningStatus_TX != statusbyte) { + // New message, memorise and send header + mRunningStatus_TX = statusbyte; + USE_SERIAL_PORT.write(mRunningStatus_TX); + } +#else + // Don't care about running status, send the Control byte. + USE_SERIAL_PORT.write(statusbyte); +#endif + + // Then send data + USE_SERIAL_PORT.write(data1); + if (type != ProgramChange && type != AfterTouchChannel) { + USE_SERIAL_PORT.write(data2); + } + return; + } + if (type >= TuneRequest && type <= SystemReset) { + // System Real-time and 1 byte. + sendRealTime(type); + } + +} + +/*! Send a Note On message + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendNoteOn(byte NoteNumber,byte Velocity,byte Channel) { send(NoteOn,NoteNumber,Velocity,Channel); } + +/*! Send a Note Off message (a real Note Off, not a Note On with null velocity) + \param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n + \param Velocity Release velocity (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendNoteOff(byte NoteNumber,byte Velocity,byte Channel) { send(NoteOff,NoteNumber,Velocity,Channel); } + +/*! Send a Program Change message + \param ProgramNumber The Program to select (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendProgramChange(byte ProgramNumber,byte Channel) { send(ProgramChange,ProgramNumber,0,Channel); } + +/*! Send a Control Change message + \param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums + \param ControlValue The value for the specified controller (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue,byte Channel) { send(ControlChange,ControlNumber,ControlValue,Channel); } + +/*! Send a Polyphonic AfterTouch message (applies to only one specified note) + \param NoteNumber The note to apply AfterTouch to (0 to 127). + \param Pressure The amount of AfterTouch to apply (0 to 127). + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel) { send(AfterTouchPoly,NoteNumber,Pressure,Channel); } + +/*! Send a MonoPhonic AfterTouch message (applies to all notes) + \param Pressure The amount of AfterTouch to apply to all notes. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendAfterTouch(byte Pressure,byte Channel) { send(AfterTouchChannel,Pressure,0,Channel); } + +/*! Send a Pitch Bend message using an integer value. + \param PitchValue The amount of bend to send (in an integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPitchBend(unsigned int PitchValue,byte Channel) { + + send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel); + +} +/*! Send a Pitch Bend message using a floating point value. + \param PitchValue The amount of bend to send (in a floating point format), between -1 (maximum downwards bend) and +1 (max upwards bend), center value is 0. + \param Channel The channel on which the message will be sent (1 to 16). + */ +void MIDI_Class::sendPitchBend(double PitchValue,byte Channel) { + + unsigned int pitchval = (PitchValue+1.f)*8192; + if (pitchval > 16383) pitchval = 16383; // overflow protection + sendPitchBend(pitchval,Channel); + +} + +/*! Generate and send a System Exclusive frame. + \param length The size of the array to send + \param array The byte array containing the data to send + \param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array).\ + default value is set to 'false' for compatibility with previous versions of the library. + */ +void MIDI_Class::sendSysEx(byte length, byte * array, bool ArrayContainsBoundaries) { + if (!ArrayContainsBoundaries) USE_SERIAL_PORT.write(0xF0); + for (byte i=0;i> 7) & 0x7F); +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + +/*! Send a Song Select message */ +void MIDI_Class::sendSongSelect(byte SongNumber) { + + USE_SERIAL_PORT.write((byte)SongSelect); + USE_SERIAL_PORT.write(SongNumber & 0x7F); +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + +/*! Send a Real Time (one byte) message. \n You can also send a Tune Request with this method. + \param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset. + */ +void MIDI_Class::sendRealTime(kMIDIType Type) { + switch (Type) { + case TuneRequest: // Not really real-time, but one byte anyway. + case Clock: + case Start: + case Stop: + case Continue: + case ActiveSensing: + case SystemReset: + USE_SERIAL_PORT.write((byte)Type); + break; + default: + // Invalid Real Time marker + break; + } +#if USE_RUNNING_STATUS + mRunningStatus_TX = InvalidType; +#endif +} + +#endif // COMPILE_MIDI_OUT + + + +#if COMPILE_MIDI_IN + +/*! Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). \n + Returned value: true if any 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 messages matches the filter, it is sent back on the MIDI output. + */ +bool MIDI_Class::read() { + return read(mInputChannel); +} + +/*! Reading/thru-ing method, the same as read() with a given input channel to read on. */ +bool MIDI_Class::read(const byte inChannel) { + + if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. + + + if (parse(inChannel)) { + if (input_filter(inChannel)) { + +#if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) + thru_filter(inChannel); +#endif + +#if USE_CALLBACKS + launchCallback(); +#endif + + return true; + } + } + + return false; +} + +// Private method: MIDI parser +bool MIDI_Class::parse(byte inChannel) { + + // If the buffer is full -> Don't Panic! Call the Vogons to destroy it. + if (USE_SERIAL_PORT.available() == 128) { + USE_SERIAL_PORT.flush(); + } + + if (USE_SERIAL_PORT.available() <= 0) { + // No data available. + return false; + } + else { + + /* 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. + */ + + + byte extracted = USE_SERIAL_PORT.read(); + + if (mPendingMessageIndex == 0) { // Start a new pending message + mPendingMessage[0] = extracted; + + // Check for running status first + switch (getTypeFromStatusByte(mRunningStatus_RX)) { + // Only these types allow Running Status: + case NoteOff: + case NoteOn: + case AfterTouchPoly: + case ControlChange: + case ProgramChange: + case AfterTouchChannel: + case PitchBend: + + // 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. + + break; + + default: + // No running status + break; + } + + + 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; + reset_input_attributes(); + 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: + mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes + mRunningStatus_RX = InvalidType; + break; + + case InvalidType: + default: + // This is obviously wrong. Let's get the hell out'a here. + reset_input_attributes(); + return false; + break; + } + + // Then update the index of the pending message. + mPendingMessageIndex++; + + // And call the parser, again. + return parse(inChannel); + + } + 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: + + /* + This is tricky. 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 = (kMIDIType)extracted; + mMessage.data1 = 0; + mMessage.data2 = 0; + mMessage.channel = 0; + mMessage.valid = true; + return true; + + break; + + // End of Exclusive + case 0xF7: + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + + // Store System Exclusive array in midimsg structure + for (byte i=0;i= (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 MIDI_SYSEX_ARRAY_SIZE. + if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) { + reset_input_attributes(); + return false; + } + + + + + mMessage.type = getTypeFromStatusByte(mPendingMessage[0]); + mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message + + 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++; + + // And call the parser, again. + return parse(inChannel); + } + + } + + + } + + // What are our chances to fall here? + return false; +} + + +// Private method: check if the received message is on the listened channel +bool MIDI_Class::input_filter(byte 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 +void MIDI_Class::reset_input_attributes() { + mPendingMessageIndex = 0; + mPendingMessageExpectedLenght = 0; + mRunningStatus_RX = InvalidType; +} + +// Getters +/*! Getter method: access to the message type stored in the structure. \n Returns an enumerated type. */ +kMIDIType MIDI_Class::getType() { return mMessage.type; } +/*! Getter method: access to the channel of the message stored in the structure. */ +byte MIDI_Class::getChannel() { return mMessage.channel; } +/*! Getter method: access to the first data byte of the message stored in the structure. \n If the message is SysEx, the length of the array is stocked there. */ +byte MIDI_Class::getData1() { return mMessage.data1; } +/*! Getter method: access to the second data byte of the message stored in the structure. */ +byte MIDI_Class::getData2() { return mMessage.data2; } +/*! Getter method: access to the System Exclusive byte array. Array length is stocked in Data1. */ +byte * MIDI_Class::getSysExArray() { return mMessage.sysex_array; } +/*! Check if a valid message is stored in the structure. */ +bool MIDI_Class::check() { return mMessage.valid; } + +// Setters +/*! Set the value for the input MIDI channel + \param Channel 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 MIDI input. + */ +void MIDI_Class::setInputChannel(const byte Channel) { mInputChannel = Channel; } + + +#if USE_CALLBACKS + +void MIDI_Class::setHandleNoteOff(void (*fptr)(byte ch, byte note, byte vel)) { mNoteOffCallback = fptr; } +void MIDI_Class::setHandleNoteOn(void (*fptr)(byte ch, byte note, byte vel)) { mNoteOnCallback = fptr; } +void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte ch, byte note, byte vel)) { mAfterTouchPolyCallback = fptr; } +void MIDI_Class::setHandleControlChange(void (*fptr)(byte ch, byte, byte)) { mControlChangeCallback = fptr; } +void MIDI_Class::setHandleProgramChange(void (*fptr)(byte ch, byte)) { mProgramChangeCallback = fptr; } +void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte ch, byte)) { mAfterTouchChannelCallback = fptr; } +void MIDI_Class::setHandlePitchBend(void (*fptr)(byte ch, word)) { mPitchBendCallback = fptr; } +void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; } +void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; } +void MIDI_Class::setHandleSongPosition(void (*fptr)(word beats)) { mSongPositionCallback = fptr; } +void MIDI_Class::setHandleSongSelect(void (*fptr)(byte song_number)) { mSongSelectCallback = fptr; } +void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; } +void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; } +void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; } +void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; } +void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; } +void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; } +void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; } + + +/*! Detach an external function from the given type.\n + Use this method to cancel the effects of connectCallback. + \param Type The type of message to unbind. When a message of this type is received, no function will be called. + */ +void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) { + + switch (Type) { + case NoteOff: mNoteOffCallback = NULL; break; + case NoteOn: mNoteOnCallback = NULL; break; + case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break; + case ControlChange: mControlChangeCallback = NULL; break; + case ProgramChange: mProgramChangeCallback = NULL; break; + case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break; + case PitchBend: mPitchBendCallback = NULL; break; + case SystemExclusive: mSystemExclusiveCallback = NULL; break; + case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break; + case SongPosition: mSongPositionCallback = NULL; break; + case SongSelect: mSongSelectCallback = NULL; break; + case TuneRequest: mTuneRequestCallback = NULL; break; + case Clock: mClockCallback = NULL; break; + case Start: mStartCallback = NULL; break; + case Continue: mContinueCallback = NULL; break; + case Stop: mStopCallback = NULL; break; + case ActiveSensing: mActiveSensingCallback = NULL; break; + case SystemReset: mSystemResetCallback = NULL; break; + default: + break; + } + +} + +// Private - launch callback function based on received type. +void MIDI_Class::launchCallback() { + + // The order is mixed to allow frequent messages to trigger their callback faster. + + switch (mMessage.type) { + // Notes + case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + + // Real-time messages + case Clock: if (mClockCallback != NULL) mClockCallback(); break; + case Start: if (mStartCallback != NULL) mStartCallback(); break; + case Continue: if (mContinueCallback != NULL) mContinueCallback(); break; + case Stop: if (mStopCallback != NULL) mStopCallback(); break; + case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break; + + // Continuous controllers + case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break; + case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break; + + case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break; + case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break; + + // Occasional messages + case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break; + case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break; + case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break; + case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break; + + case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break; + case InvalidType: + default: + break; + } + +} + + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + + + +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + +/*! Set the filter for thru mirroring + \param inThruFilterMode a filter mode + See kThruFilterMode for detailed description. + */ +void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) { + mThruFilterMode = inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; +} +/*! Set the filter for thru mirroring + \param inThruFilterMode a filter mode + See kThruFilterMode for detailed description. \n + This method uses a byte parameter and is for compatibility only, please use kThruFilterMode for future programs. + */ +void MIDI_Class::setThruFilterMode(byte inThruFilterMode) { + mThruFilterMode = (kThruFilterMode)inThruFilterMode; + if (mThruFilterMode != Off) mThruActivated = true; + else mThruActivated = false; +} + + +/*! Setter method: turn message mirroring on. */ +void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) { + mThruActivated = true; + mThruFilterMode = inThruFilterMode; +} +/*! Setter method: turn message mirroring off. */ +void MIDI_Class::turnThruOff() { + mThruActivated = false; + mThruFilterMode = Off; +} + +// This method is called upon reception of a message and takes care of Thru filtering and sending. +void MIDI_Class::thru_filter(byte inChannel) { + + /* + This method handles Soft-Thru filtering. + + Soft-Thru filtering: + - 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 + + */ + +#if TEENSY_SUPPORT && TEENSY_MIDI_TO_USB + // Pass the message to the USB side if enabled + +#endif + + // 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) { + + + 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); + return; + break; + case SameChannel: + if (filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + break; + case DifferentChannel: + if (!filter_condition) { + send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel); + return; + } + case Off: + // Do nothing + break; + default: + break; + } + + } + else { + + // Send the message to the output + if (mThruFilterMode != Off) { + 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); + return; + break; + + case SystemExclusive: + // Send SysEx (0xF0 and 0xF7 are included in the buffer) + sendSysEx(mMessage.data1,mMessage.sysex_array,true); + return; + break; + + case SongSelect: + sendSongSelect(mMessage.data1); // TODO: check this + return; + break; + + case SongPosition: + sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); // TODO: check this + return; + break; + + case TimeCodeQuarterFrame: + sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); // TODO: check this + return; + break; + default: + break; + } + + } + + } + +} + + +#endif // Thru + + diff --git a/teensy_core/usb_midi/usb_midi.h b/teensy_core/usb_midi/usb_midi.h new file mode 100644 index 0000000..46f1211 --- /dev/null +++ b/teensy_core/usb_midi/usb_midi.h @@ -0,0 +1,302 @@ +/*! + * @file usb_midi.h + * Project Teensy MIDI Core + * @brief MIDI Library for Teensy - USB side + * Version 3.1 + * @author Francois Best + * @date 28/04/11 + * License GPL Forty Seven Effects - 2011 + */ + +#ifndef _TEENSY_LIB_MIDI_USB_FSE_H_ +#define _TEENSY_LIB_MIDI_USB_FSE_H_ + +#include + + +/* + ############################################################### + # # + # CONFIGURATION AREA # + # # + # Here are a few settings you can change to customize # + # the library for your own project. You can for example # + # choose to compile only parts of it so you gain flash # + # space and optimise the speed of your sketch. # + # # + ############################################################### + */ + +#define CONVERT_USB_TO_MIDI 1 // Set this to 1 to forward incoming messages on the USB MIDI to the UART. +#define CONVERT_MIDI_TO_USB 1 // Set this to 1 to forward incoming messages on the UART to the USB MIDI. + +#define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input. +#define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output. +#define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature + // Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1. + + +#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values + // of the same type and channel. + // Set to 0 if you have troubles with controlling you hardware. + + +#define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library). + // To use the callbacks, you need to have COMPILE_MIDI_IN set to 1 + + +// END OF CONFIGURATION AREA +// (do not modify anything under this line unless you know what you are doing) + + +#define MIDI_BAUDRATE 31250 + +#define MIDI_CHANNEL_OMNI 0 +#define MIDI_CHANNEL_OFF 17 // and over + +#define MIDI_SYSEX_ARRAY_SIZE 60 + +/*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/ +typedef uint8_t byte; +typedef uint16_t word; + +/*! Enumeration of MIDI types */ +enum kMIDIType { + 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 + InvalidType = 0x00 // For notifying errors +}; + +/*! Enumeration of Thru filter modes */ +enum kThruFilterMode { + 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. +}; + + +/*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */ +struct midimsg { + /*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */ + byte channel; + /*! The type of the message (see the define section for types reference) */ + kMIDIType type; + /*! The first data byte.\n Value goes from 0 to 127.\n If the message is SysEx, this byte contains the array length. */ + byte data1; + /*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */ + byte data2; + /*! System Exclusive dedicated byte array. \n Array length is stocked in data1. */ + byte sysex_array[MIDI_SYSEX_ARRAY_SIZE]; + /*! 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; +}; + + + + +/*! The main class for MIDI handling. + See member descriptions to know how to use it, + or check out the examples supplied with the library. + */ +class MIDI_Class { + + +public: + // Constructor and Destructor + MIDI_Class(); + ~MIDI_Class(); + + + void begin(const byte inChannel = 1); + + + + +/* ####### OUTPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_OUT + +public: + + void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel); + void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel); + void sendProgramChange(byte ProgramNumber,byte Channel); + void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel); + void sendPitchBend(unsigned int PitchValue,byte Channel); + void sendPitchBend(double PitchValue,byte Channel); + void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel); + void sendAfterTouch(byte Pressure,byte Channel); + void sendSysEx(byte length, byte * array,bool ArrayContainsBoundaries = false); + void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble); + void sendTimeCodeQuarterFrame(byte data); + void sendSongPosition(unsigned int Beats); + void sendSongSelect(byte SongNumber); + void sendTuneRequest(); + void sendRealTime(kMIDIType Type); + + +private: + + const byte genstatus(const kMIDIType inType,const byte inChannel); + void send(kMIDIType type, byte param1, byte param2, byte channel); + + // Attributes +#if USE_RUNNING_STATUS + byte mRunningStatus_TX; +#endif // USE_RUNNING_STATUS + +#endif // COMPILE_MIDI_OUT + + + +/* ####### INPUT COMPILATION BLOCK ####### */ +#if COMPILE_MIDI_IN + +public: + + bool read(); + bool read(const byte Channel); + + // Getters + kMIDIType getType(); + byte getChannel(); + byte getData1(); + byte getData2(); + byte * getSysExArray(); + bool check(); + + byte getInputChannel() { return mInputChannel; } + + // Setters + void setInputChannel(const byte Channel); + + +#if USE_CALLBACKS + + void setHandleNoteOff(void (*fptr)(byte ch, byte note, byte vel)); + void setHandleNoteOn(void (*fptr)(byte ch, byte note, byte vel)); + void setHandleAfterTouchPoly(void (*fptr)(byte ch, byte note, byte vel)); + void setHandleControlChange(void (*fptr)(byte ch, byte, byte)); + void setHandleProgramChange(void (*fptr)(byte ch, byte)); + void setHandleAfterTouchChannel(void (*fptr)(byte ch, byte)); + void setHandlePitchBend(void (*fptr)(byte ch, word)); + void setHandleSystemExclusive(void (*fptr)(byte * array, byte size)); + void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)); + void setHandleSongPosition(void (*fptr)(word beats)); + void setHandleSongSelect(void (*fptr)(byte song_number)); + void setHandleTuneRequest(void (*fptr)(void)); + void setHandleClock(void (*fptr)(void)); + void setHandleStart(void (*fptr)(void)); + void setHandleContinue(void (*fptr)(void)); + void setHandleStop(void (*fptr)(void)); + void setHandleActiveSensing(void (*fptr)(void)); + void setHandleSystemReset(void (*fptr)(void)); + + void disconnectCallbackFromType(kMIDIType Type); + +#endif // USE_CALLBACKS + +private: + + inline const kMIDIType getTypeFromStatusByte(const byte inStatus) { + if ((inStatus < 0x80) + || (inStatus == 0xF4) + || (inStatus == 0xF5) + || (inStatus == 0xF9) + || (inStatus == 0xFD)) return InvalidType; // data bytes and undefined. + if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble. + else return (kMIDIType)inStatus; + } + + bool input_filter(byte inChannel); + bool parse(byte inChannel); + void reset_input_attributes(); + + // Attributes + byte mRunningStatus_RX; + byte mInputChannel; + + byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE]; + byte mPendingMessageExpectedLenght; + byte mPendingMessageIndex; + + midimsg mMessage; + +#if USE_CALLBACKS + + void launchCallback(); + + void (*mNoteOffCallback)(byte ch, byte note, byte vel); + void (*mNoteOnCallback)(byte ch, byte note, byte vel); + void (*mAfterTouchPolyCallback)(byte ch, byte note, byte vel); + void (*mControlChangeCallback)(byte ch, byte, byte); + void (*mProgramChangeCallback)(byte ch, byte); + void (*mAfterTouchChannelCallback)(byte ch, byte); + void (*mPitchBendCallback)(byte ch, word); + void (*mSystemExclusiveCallback)(byte * array, byte size); + void (*mTimeCodeQuarterFrameCallback)(byte data); + void (*mSongPositionCallback)(word beats); + void (*mSongSelectCallback)(byte song_number); + void (*mTuneRequestCallback)(void); + void (*mClockCallback)(void); + void (*mStartCallback)(void); + void (*mContinueCallback)(void); + void (*mStopCallback)(void); + void (*mActiveSensingCallback)(void); + void (*mSystemResetCallback)(void); + +#endif // USE_CALLBACKS + + +#endif // COMPILE_MIDI_IN + + +/* ####### THRU COMPILATION BLOCK ####### */ +#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru + +public: + + // Getters + kThruFilterMode getFilterMode() { return mThruFilterMode; } + bool getThruState() { return mThruActivated; } + + + // Setters + void turnThruOn(kThruFilterMode inThruFilterMode = Full); + void turnThruOff(); + + void setThruFilterMode(const byte inThruFilterMode); // For compatibility only, avoid in future programs. + void setThruFilterMode(const kThruFilterMode inThruFilterMode); + + +private: + + void thru_filter(byte inChannel); + + bool mThruActivated; + kThruFilterMode mThruFilterMode; + +#endif // Thru + +}; + + +extern MIDI_Class usbMIDI; + +#endif // _TEENSY_LIB_MIDI_USB_H_ diff --git a/teensy_core/usb_midi/usb_private.h b/teensy_core/usb_midi/usb_private.h new file mode 100644 index 0000000..21ac6e7 --- /dev/null +++ b/teensy_core/usb_midi/usb_private.h @@ -0,0 +1,84 @@ +#ifndef usb_serial_h__ +#define usb_serial_h__ + +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/************************************************************************** + * + * Configurable Options + * + **************************************************************************/ + +#define VENDOR_ID 0x16C0 +#define PRODUCT_ID 0x0485 +#define TRANSMIT_FLUSH_TIMEOUT 4 /* in milliseconds */ +#define TRANSMIT_TIMEOUT 25 /* in milliseconds */ + + +/************************************************************************** + * + * Endpoint Buffer Configuration + * + **************************************************************************/ + +// These buffer sizes are best for most applications, but perhaps if you +// want more buffering on some endpoint at the expense of others, this +// is where you can make such changes. The AT90USB162 has only 176 bytes +// of DPRAM (USB buffers) and only endpoints 3 & 4 can double buffer. + + +// 0: control +// 1: debug IN +// 2: debug OUT +// 3: midi IN +// 4: midi OUT + +#if defined(__AVR_ATmega32U4__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) + +#define STR_PRODUCT L"Teensy MIDI" +#define ENDPOINT0_SIZE 64 + +#define DEBUG_INTERFACE 1 +#define DEBUG_TX_ENDPOINT 1 +#define DEBUG_TX_SIZE 64 +#define DEBUG_TX_BUFFER EP_DOUBLE_BUFFER +#define DEBUG_TX_INTERVAL 1 +#define DEBUG_RX_ENDPOINT 2 +#define DEBUG_RX_SIZE 32 +#define DEBUG_RX_BUFFER EP_DOUBLE_BUFFER +#define DEBUG_RX_INTERVAL 2 + +#define MIDI_INTERFACE 0 +#define MIDI_TX_ENDPOINT 3 +#define MIDI_TX_SIZE 64 +#define MIDI_TX_BUFFER EP_DOUBLE_BUFFER +#define MIDI_RX_ENDPOINT 4 +#define MIDI_RX_SIZE 64 +#define MIDI_RX_BUFFER EP_DOUBLE_BUFFER + +#define NUM_ENDPOINTS 5 +#define NUM_INTERFACE 2 + +#endif + + + +// setup +void usb_init(void); // initialize everything +void usb_shutdown(void); // shut off USB + +// variables +extern volatile uint8_t usb_configuration; +extern volatile uint8_t usb_suspended; +extern volatile uint8_t debug_flush_timer; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif