diff --git a/ReadMe.md b/ReadMe.md
index 70dffd068..e7d8e42a1 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -17,6 +17,7 @@
- [Barcode generator: rfct, ux improvements, implement EAN-8. #154 (By msvsergey)](https://github.com/DarkFlippers/unleashed-firmware/pull/154)
- Settings: Rename from App [(Thanks to E_Surge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/409)
- Added: [Wii EC Analyser (By csBlueChip)](https://github.com/csBlueChip/FlipperZero_WiiEC)
+- Added: [USB Midi (By DrZlo13)](https://github.com/DrZlo13/flipper-zero-usb-midi)
This software is for experimental purposes only and is not meant for any illegal activity/purposes. We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. |
diff --git a/applications/plugins/usb_midi/.gitattributes b/applications/plugins/usb_midi/.gitattributes
new file mode 100644
index 000000000..dfe077042
--- /dev/null
+++ b/applications/plugins/usb_midi/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/applications/plugins/usb_midi/.gitignore b/applications/plugins/usb_midi/.gitignore
new file mode 100644
index 000000000..c6127b38c
--- /dev/null
+++ b/applications/plugins/usb_midi/.gitignore
@@ -0,0 +1,52 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/applications/plugins/usb_midi/application.fam b/applications/plugins/usb_midi/application.fam
new file mode 100644
index 000000000..6aa9d507d
--- /dev/null
+++ b/applications/plugins/usb_midi/application.fam
@@ -0,0 +1,14 @@
+App(
+ appid="USB_Midi",
+ name="USB Midi",
+ apptype=FlipperAppType.PLUGIN,
+ entry_point="usb_midi_app",
+ requires=[
+ "gui",
+ ],
+ stack_size=4 * 1024,
+ order=20,
+ fap_icon="usb_midi.png",
+ fap_category="Music",
+ fap_icon_assets="icons",
+)
diff --git a/applications/plugins/usb_midi/midi/config.h b/applications/plugins/usb_midi/midi/config.h
new file mode 100644
index 000000000..c62c1b1ef
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/config.h
@@ -0,0 +1,3 @@
+#include
+
+#define SYSEX_BUFFER_LEN 16
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/message.c b/applications/plugins/usb_midi/midi/message.c
new file mode 100644
index 000000000..7bee9816a
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/message.c
@@ -0,0 +1,144 @@
+#include "message.h"
+
+/** Returns the data within the MidiEvent as a NoteOffEvent struct */
+NoteOffEvent AsNoteOff(MidiEvent* event) {
+ NoteOffEvent m;
+ m.channel = event->channel;
+ m.note = event->data[0];
+ m.velocity = event->data[1];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a NoteOnEvent struct */
+NoteOnEvent AsNoteOn(MidiEvent* event) {
+ NoteOnEvent m;
+ m.channel = event->channel;
+ m.note = event->data[0];
+ m.velocity = event->data[1];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a PolyphonicKeyPressureEvent struct */
+PolyphonicKeyPressureEvent AsPolyphonicKeyPressure(MidiEvent* event) {
+ PolyphonicKeyPressureEvent m;
+ m.channel = event->channel;
+ m.note = event->data[0];
+ m.pressure = event->data[1];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a ControlChangeEvent struct.*/
+ControlChangeEvent AsControlChange(MidiEvent* event) {
+ ControlChangeEvent m;
+ m.channel = event->channel;
+ m.control_number = event->data[0];
+ m.value = event->data[1];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/
+ProgramChangeEvent AsProgramChange(MidiEvent* event) {
+ ProgramChangeEvent m;
+ m.channel = event->channel;
+ m.program = event->data[0];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/
+ChannelPressureEvent AsChannelPressure(MidiEvent* event) {
+ ChannelPressureEvent m;
+ m.channel = event->channel;
+ m.pressure = event->data[0];
+ return m;
+}
+
+/** Returns the data within the MidiEvent as a PitchBendEvent struct.*/
+PitchBendEvent AsPitchBend(MidiEvent* event) {
+ PitchBendEvent m;
+ m.channel = event->channel;
+ m.value = ((uint16_t)(event->data[1]) << 7) + (event->data[0] - 8192);
+ return m;
+}
+
+SystemExclusiveEvent AsSystemExclusive(MidiEvent* event) {
+ SystemExclusiveEvent m;
+ m.length = event->sysex_message_len;
+ for(int i = 0; i < SYSEX_BUFFER_LEN; i++) {
+ m.data[i] = 0;
+ if(i < m.length) {
+ m.data[i] = event->sysex_data[i];
+ }
+ }
+ return m;
+}
+
+MTCQuarterFrameEvent AsMTCQuarterFrame(MidiEvent* event) {
+ MTCQuarterFrameEvent m;
+ m.message_type = (event->data[0] & 0x70) >> 4;
+ m.value = (event->data[0]) & 0x0f;
+ return m;
+}
+
+SongPositionPointerEvent AsSongPositionPointer(MidiEvent* event) {
+ SongPositionPointerEvent m;
+ m.position = ((uint16_t)(event->data[1]) << 7) | (event->data[0]);
+ return m;
+}
+
+SongSelectEvent AsSongSelect(MidiEvent* event) {
+ SongSelectEvent m;
+ m.song = event->data[0];
+ return m;
+}
+
+AllSoundOffEvent AsAllSoundOff(MidiEvent* event) {
+ AllSoundOffEvent m;
+ m.channel = event->channel;
+ return m;
+}
+
+ResetAllControllersEvent AsResetAllControllers(MidiEvent* event) {
+ ResetAllControllersEvent m;
+ m.channel = event->channel;
+ m.value = event->data[1];
+ return m;
+}
+
+LocalControlEvent AsLocalControl(MidiEvent* event) {
+ LocalControlEvent m;
+ m.channel = event->channel;
+ m.local_control_off = (event->data[1] == 0);
+ m.local_control_on = (event->data[1] == 127);
+ return m;
+}
+
+AllNotesOffEvent AsAllNotesOff(MidiEvent* event) {
+ AllNotesOffEvent m;
+ m.channel = event->channel;
+ return m;
+}
+
+OmniModeOffEvent AsOmniModeOff(MidiEvent* event) {
+ OmniModeOffEvent m;
+ m.channel = event->channel;
+ return m;
+}
+
+OmniModeOnEvent AsOmniModeOn(MidiEvent* event) {
+ OmniModeOnEvent m;
+ m.channel = event->channel;
+ return m;
+}
+
+MonoModeOnEvent AsMonoModeOn(MidiEvent* event) {
+ MonoModeOnEvent m;
+ m.channel = event->channel;
+ m.num_channels = event->data[1];
+ return m;
+}
+
+PolyModeOnEvent AsPolyModeOn(MidiEvent* event) {
+ PolyModeOnEvent m;
+ m.channel = event->channel;
+ return m;
+}
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/message.h b/applications/plugins/usb_midi/midi/message.h
new file mode 100644
index 000000000..88402c4a4
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/message.h
@@ -0,0 +1,251 @@
+#pragma once
+#include
+#include
+#include "config.h"
+
+typedef enum {
+ NoteOff, /**< & */
+ NoteOn, /**< & */
+ PolyphonicKeyPressure, /**< & */
+ ControlChange, /**< & */
+ ProgramChange, /**< & */
+ ChannelPressure, /**< & */
+ PitchBend, /**< & */
+ SystemCommon, /**< & */
+ SystemRealTime, /**< & */
+ ChannelMode, /**< & */
+ MessageLast, /**< & */
+} MidiMessageType;
+
+typedef enum {
+ SystemExclusive, /**< & */
+ MTCQuarterFrame, /**< & */
+ SongPositionPointer, /**< & */
+ SongSelect, /**< & */
+ SCUndefined0, /**< & */
+ SCUndefined1, /**< & */
+ TuneRequest, /**< & */
+ SysExEnd, /**< & */
+ SystemCommonLast, /**< & */
+} SystemCommonType;
+
+typedef enum {
+ TimingClock, /**< & */
+ SRTUndefined0, /**< & */
+ Start, /**< & */
+ Continue, /**< & */
+ Stop, /**< & */
+ SRTUndefined1, /**< & */
+ ActiveSensing, /**< & */
+ Reset, /**< & */
+ SystemRealTimeLast, /**< & */
+} SystemRealTimeType;
+
+typedef enum {
+ AllSoundOff, /**< & */
+ ResetAllControllers, /**< & */
+ LocalControl, /**< & */
+ AllNotesOff, /**< & */
+ OmniModeOff, /**< & */
+ OmniModeOn, /**< & */
+ MonoModeOn, /**< & */
+ PolyModeOn, /**< & */
+ ChannelModeLast, /**< & */
+} ChannelModeType;
+
+/** Struct containing note, and velocity data for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t note; /**< & */
+ uint8_t velocity; /**< & */
+} NoteOffEvent;
+
+/** Struct containing note, and velocity data for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t note; /**< & */
+ uint8_t velocity; /**< & */
+} NoteOnEvent;
+
+/** Struct containing note, and pressure data for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel;
+ uint8_t note;
+ uint8_t pressure;
+} PolyphonicKeyPressureEvent;
+
+/** Struct containing control number, and value for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t control_number; /**< & */
+ uint8_t value; /**< & */
+} ControlChangeEvent;
+
+/** Struct containing new program number, for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t program; /**< & */
+} ProgramChangeEvent;
+
+/** Struct containing pressure (aftertouch), for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t pressure; /**< & */
+} ChannelPressureEvent;
+
+/** Struct containing pitch bend value for a given channel.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ int16_t value; /**< & */
+} PitchBendEvent;
+
+/** Struct containing sysex data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int length;
+ uint8_t data[SYSEX_BUFFER_LEN]; /**< & */
+} SystemExclusiveEvent;
+
+/** Struct containing QuarterFrame data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ uint8_t message_type; /**< & */
+ uint8_t value; /**< & */
+} MTCQuarterFrameEvent;
+
+/** Struct containing song position data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ uint16_t position; /**< & */
+} SongPositionPointerEvent;
+
+/** Struct containing song select data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ uint8_t song; /**< & */
+} SongSelectEvent;
+
+/** Struct containing sound off data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+} AllSoundOffEvent;
+
+/** Struct containing ResetAllControllersEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t value; /**< & */
+} ResetAllControllersEvent;
+
+/** Struct containing LocalControlEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ bool local_control_off; /**< & */
+ bool local_control_on; /**< & */
+} LocalControlEvent;
+
+/** Struct containing AllNotesOffEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+} AllNotesOffEvent;
+
+/** Struct containing OmniModeOffEvent data.
+ * Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+} OmniModeOffEvent;
+
+/** Struct containing OmniModeOnEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+} OmniModeOnEvent;
+
+/** Struct containing MonoModeOnEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+ uint8_t num_channels; /**< & */
+} MonoModeOnEvent;
+
+/** Struct containing PolyModeOnEvent data.
+Can be made from MidiEvent
+*/
+typedef struct {
+ int channel; /**< & */
+} PolyModeOnEvent;
+
+/** Simple MidiEvent with message type, channel, and data[2] members.
+*/
+typedef struct {
+ MidiMessageType type;
+ int channel;
+ uint8_t data[2];
+ uint8_t sysex_data[SYSEX_BUFFER_LEN];
+ uint8_t sysex_message_len;
+ SystemCommonType sc_type;
+ SystemRealTimeType srt_type;
+ ChannelModeType cm_type;
+} MidiEvent;
+
+/** Returns the data within the MidiEvent as a NoteOffEvent struct */
+NoteOffEvent AsNoteOff(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a NoteOnEvent struct */
+NoteOnEvent AsNoteOn(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a PolyphonicKeyPressureEvent struct */
+PolyphonicKeyPressureEvent AsPolyphonicKeyPressure(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a ControlChangeEvent struct.*/
+ControlChangeEvent AsControlChange(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/
+ProgramChangeEvent AsProgramChange(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a ProgramChangeEvent struct.*/
+ChannelPressureEvent AsChannelPressure(MidiEvent* event);
+
+/** Returns the data within the MidiEvent as a PitchBendEvent struct.*/
+PitchBendEvent AsPitchBend(MidiEvent* event);
+
+SystemExclusiveEvent AsSystemExclusive(MidiEvent* event);
+MTCQuarterFrameEvent AsMTCQuarterFrame(MidiEvent* event);
+SongPositionPointerEvent AsSongPositionPointer(MidiEvent* event);
+SongSelectEvent AsSongSelect(MidiEvent* event);
+AllSoundOffEvent AsAllSoundOff(MidiEvent* event);
+ResetAllControllersEvent AsResetAllControllers(MidiEvent* event);
+LocalControlEvent AsLocalControl(MidiEvent* event);
+AllNotesOffEvent AsAllNotesOff(MidiEvent* event);
+OmniModeOffEvent AsOmniModeOff(MidiEvent* event);
+OmniModeOnEvent AsOmniModeOn(MidiEvent* event);
+MonoModeOnEvent AsMonoModeOn(MidiEvent* event);
+PolyModeOnEvent AsPolyModeOn(MidiEvent* event);
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/parser.c b/applications/plugins/usb_midi/midi/parser.c
new file mode 100644
index 000000000..86120e007
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/parser.c
@@ -0,0 +1,149 @@
+#include
+#include "parser.h"
+
+typedef enum {
+ ParserEmpty,
+ ParserHasStatus,
+ ParserHasData0,
+ ParserSysEx,
+} ParserState;
+
+const uint8_t kStatusByteMask = 0x80;
+const uint8_t kMessageMask = 0x70;
+const uint8_t kDataByteMask = 0x7F;
+const uint8_t kSystemCommonMask = 0xF0;
+const uint8_t kChannelMask = 0x0F;
+const uint8_t kRealTimeMask = 0xF8;
+const uint8_t kSystemRealTimeMask = 0x07;
+
+struct MidiParser {
+ MidiMessageType status;
+ ParserState state;
+ MidiEvent incoming_message;
+};
+
+MidiParser* midi_parser_alloc(void) {
+ MidiParser* parser = malloc(sizeof(MidiParser));
+ parser->incoming_message.type = MessageLast;
+ parser->state = ParserEmpty;
+ return parser;
+}
+
+void midi_parser_free(MidiParser* parser) {
+ free(parser);
+}
+
+bool midi_parser_parse(MidiParser* parser, uint8_t byte) {
+ bool parsed = false;
+ MidiEvent* event = &parser->incoming_message;
+
+ switch(parser->state) {
+ case ParserEmpty:
+ // check byte for valid Status Byte
+ if(byte & kStatusByteMask) {
+ // Get MessageType, and Channel
+ event->channel = byte & kChannelMask;
+ event->type = (MidiMessageType)((byte & kMessageMask) >> 4);
+
+ // Validate, and move on.
+ if(event->type < MessageLast) {
+ parser->state = ParserHasStatus;
+ // Mark this status byte as running_status
+ parser->status = event->type;
+
+ if(parser->status == SystemCommon) {
+ event->channel = 0;
+ //system real time = 1111 1xxx
+ if(byte & 0x08) {
+ event->type = SystemRealTime;
+ parser->status = SystemRealTime;
+ event->srt_type = (SystemRealTimeType)(byte & kSystemRealTimeMask);
+
+ //short circuit to start
+ parser->state = ParserEmpty;
+ //queue_.push(incoming_message_);
+ parsed = true;
+ }
+ //system common
+ else {
+ event->sc_type = (SystemCommonType)(byte & 0x07);
+ //sysex
+ if(event->sc_type == SystemExclusive) {
+ parser->state = ParserSysEx;
+ event->sysex_message_len = 0;
+ }
+ //short circuit
+ else if(event->sc_type > SongSelect) {
+ parser->state = ParserEmpty;
+ //queue_.push(incoming_message_);
+ parsed = true;
+ }
+ }
+ }
+ }
+ // Else we'll keep waiting for a valid incoming status byte
+ } else {
+ // Handle as running status
+ event->type = parser->status;
+ event->data[0] = byte & kDataByteMask;
+ parser->state = ParserHasData0;
+ }
+ break;
+ case ParserHasStatus:
+ if((byte & kStatusByteMask) == 0) {
+ event->data[0] = byte & kDataByteMask;
+ if(parser->status == ChannelPressure || parser->status == ProgramChange ||
+ event->sc_type == MTCQuarterFrame || event->sc_type == SongSelect) {
+ //these are just one data byte, so we short circuit back to start
+ parser->state = ParserEmpty;
+ //queue_.push(incoming_message_);
+ parsed = true;
+ } else {
+ parser->state = ParserHasData0;
+ }
+
+ //ChannelModeMessages (reserved Control Changes)
+ if(parser->status == ControlChange && event->data[0] > 119) {
+ event->type = ChannelMode;
+ parser->status = ChannelMode;
+ event->cm_type = (ChannelModeType)(event->data[0] - 120);
+ }
+ } else {
+ // invalid message go back to start ;p
+ parser->state = ParserEmpty;
+ }
+ break;
+ case ParserHasData0:
+ if((byte & kStatusByteMask) == 0) {
+ event->data[1] = byte & kDataByteMask;
+ // At this point the message is valid, and we can add this MidiEvent to the queue
+ //queue_.push(incoming_message_);
+ parsed = true;
+ }
+ // Regardless, of whether the data was valid or not we go back to empty
+ // because either the message is queued for handling or its not.
+ parser->state = ParserEmpty;
+ break;
+ case ParserSysEx:
+ // end of sysex
+ if(byte == 0xf7) {
+ parser->state = ParserEmpty;
+ //queue_.push(incoming_message_);
+ parsed = true;
+ } else {
+ if(event->sysex_message_len < SYSEX_BUFFER_LEN) {
+ event->sysex_data[event->sysex_message_len] = byte;
+ event->sysex_message_len++;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return parsed;
+}
+
+MidiEvent* midi_parser_get_message(MidiParser* parser) {
+ return &parser->incoming_message;
+}
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/parser.h b/applications/plugins/usb_midi/midi/parser.h
new file mode 100644
index 000000000..93630f026
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/parser.h
@@ -0,0 +1,14 @@
+#pragma once
+#include
+#include
+#include "message.h"
+
+typedef struct MidiParser MidiParser;
+
+MidiParser* midi_parser_alloc(void);
+
+void midi_parser_free(MidiParser* parser);
+
+bool midi_parser_parse(MidiParser* parser, uint8_t data);
+
+MidiEvent* midi_parser_get_message(MidiParser* parser);
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/usb_message.c b/applications/plugins/usb_midi/midi/usb_message.c
new file mode 100644
index 000000000..b6844c5f4
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/usb_message.c
@@ -0,0 +1,40 @@
+#include "usb_message.h"
+
+CodeIndex code_index_from_data(uint8_t data) {
+ return (CodeIndex)(data & 0b00001111);
+}
+
+uint8_t cable_id_from_data(uint8_t data) {
+ return (data >> 4) & 0b00001111;
+}
+
+uint8_t usb_message_data_size(CodeIndex code_index) {
+ uint8_t data_size = 0;
+ switch(code_index) {
+ case CodeIndexCommon1Byte:
+ /* case CodeIndexSysExEnd1Byte: */
+ case CodeIndexSingleByte:
+ data_size = 1;
+ break;
+ case CodeIndexSysEx2Byte:
+ case CodeIndexSysExEnd2Byte:
+ case CodeIndexProgramChange:
+ case CodeIndexChannelPressure:
+ data_size = 2;
+ break;
+ case CodeIndexSysEx3Byte:
+ case CodeIndexSysExStart:
+ case CodeIndexSysExEnd3Byte:
+ case CodeIndexNoteOff:
+ case CodeIndexNoteOn:
+ case CodeIndexPolyKeyPress:
+ case CodeIndexControlChange:
+ case CodeIndexPitchBendChange:
+ data_size = 3;
+ break;
+ default:
+ break;
+ }
+
+ return data_size;
+}
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/midi/usb_message.h b/applications/plugins/usb_midi/midi/usb_message.h
new file mode 100644
index 000000000..852e9cb4f
--- /dev/null
+++ b/applications/plugins/usb_midi/midi/usb_message.h
@@ -0,0 +1,28 @@
+#pragma once
+#include
+
+typedef enum {
+ CodeIndexMisc = 0x0, /**< Reserved, MIDI Size: 1, 2, 3 */
+ CodeIndexCableEvent = 0x1, /**< Reserved, MIDI Size: 1, 2, 3 */
+ CodeIndexSysEx2Byte = 0x2, /**< MIDI Size: 2 */
+ CodeIndexSysEx3Byte = 0x3, /**< MIDI Size: 3 */
+ CodeIndexSysExStart = 0x4, /**< MIDI Size: 3 */
+ CodeIndexCommon1Byte = 0x5, /**< MIDI Size: 1 */
+ CodeIndexSysExEnd1Byte = 0x5, /**< MIDI Size: 1 */
+ CodeIndexSysExEnd2Byte = 0x6, /**< MIDI Size: 2 */
+ CodeIndexSysExEnd3Byte = 0x7, /**< MIDI Size: 3 */
+ CodeIndexNoteOff = 0x8, /**< MIDI Size: 3 */
+ CodeIndexNoteOn = 0x9, /**< MIDI Size: 3 */
+ CodeIndexPolyKeyPress = 0xA, /**< MIDI Size: 3 */
+ CodeIndexControlChange = 0xB, /**< MIDI Size: 3 */
+ CodeIndexProgramChange = 0xC, /**< MIDI Size: 2 */
+ CodeIndexChannelPressure = 0xD, /**< MIDI Size: 2 */
+ CodeIndexPitchBendChange = 0xE, /**< MIDI Size: 3 */
+ CodeIndexSingleByte = 0xF, /**< MIDI Size: 1 */
+} CodeIndex;
+
+CodeIndex code_index_from_data(uint8_t data);
+
+uint8_t cable_id_from_data(uint8_t data);
+
+uint8_t usb_message_data_size(CodeIndex code_index);
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/usb/cm3_usb_audio.h b/applications/plugins/usb_midi/usb/cm3_usb_audio.h
new file mode 100644
index 000000000..3c767f929
--- /dev/null
+++ b/applications/plugins/usb_midi/usb/cm3_usb_audio.h
@@ -0,0 +1,234 @@
+/** @defgroup usb_audio_defines USB Audio Type Definitions
+
+@brief Defined Constants and Types for the USB Audio Type Definitions
+
+@ingroup USB_defines
+
+@version 1.0.0
+
+@author @htmlonly © @endhtmlonly 2014
+Daniel Thompson
+Seb Holzapfel
+
+@date 19 April 2014
+
+LGPL License Terms @ref lgpl_license
+*/
+
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2014 Daniel Thompson
+ * Copyright (C) 2018 Seb Holzapfel
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see .
+ */
+
+/**@{*/
+
+#ifndef LIBOPENCM3_USB_AUDIO_H
+#define LIBOPENCM3_USB_AUDIO_H
+
+#include
+
+/*
+ * Definitions from the USB_AUDIO_ or usb_audio_ namespace come from:
+ * "Universal Serial Bus Class Definitions for Audio Devices, Revision 1.0"
+ */
+
+/* Table A-1: Audio Interface Class Code */
+#define USB_CLASS_AUDIO 0x01
+
+/* Table A-2: Audio Interface Subclass Codes */
+#define USB_AUDIO_SUBCLASS_UNDEFINED 0x00
+#define USB_AUDIO_SUBCLASS_CONTROL 0x01
+#define USB_AUDIO_SUBCLASS_AUDIOSTREAMING 0x02
+#define USB_AUDIO_SUBCLASS_MIDISTREAMING 0x03
+
+/* Table A-4: Audio Class-specific Descriptor Types */
+#define USB_AUDIO_DT_CS_UNDEFINED 0x20
+#define USB_AUDIO_DT_CS_DEVICE 0x21
+#define USB_AUDIO_DT_CS_CONFIGURATION 0x22
+#define USB_AUDIO_DT_CS_STRING 0x23
+#define USB_AUDIO_DT_CS_INTERFACE 0x24
+#define USB_AUDIO_DT_CS_ENDPOINT 0x25
+
+/* Table A-5: Audio Class-Specific AC Interface Descriptor Subtypes */
+#define USB_AUDIO_TYPE_AC_DESCRIPTOR_UNDEFINED 0x00
+#define USB_AUDIO_TYPE_HEADER 0x01
+#define USB_AUDIO_TYPE_INPUT_TERMINAL 0x02
+#define USB_AUDIO_TYPE_OUTPUT_TERMINAL 0x03
+#define USB_AUDIO_TYPE_MIXER_UNIT 0x04
+#define USB_AUDIO_TYPE_SELECTOR_UNIT 0x05
+#define USB_AUDIO_TYPE_FEATURE_UNIT 0x06
+#define USB_AUDIO_TYPE_PROCESSING_UNIT 0x07
+#define USB_AUDIO_TYPE_EXTENSION_UNIT 0x08
+
+/* Table 4-2: Class-Specific AC Interface Header Descriptor (head) */
+struct usb_audio_header_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint16_t bcdADC;
+ uint16_t wTotalLength;
+ uint8_t bInCollection;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 4-2: Class-Specific AC Interface Header Descriptor (body) */
+struct usb_audio_header_descriptor_body {
+ /* ... */
+ uint8_t baInterfaceNr;
+} __attribute__((packed));
+
+/* Table 4-3: Input Terminal Descriptor */
+struct usb_audio_input_terminal_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bTerminalID;
+ uint16_t wTerminalType;
+ uint8_t bAssocTerminal;
+ uint8_t bNrChannels;
+ uint16_t wChannelConfig;
+ uint8_t iChannelNames;
+ uint8_t iTerminal;
+} __attribute__((packed));
+
+/* Table 4-3: Output Terminal Descriptor */
+struct usb_audio_output_terminal_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bTerminalID;
+ uint16_t wTerminalType;
+ uint8_t bAssocTerminal;
+ uint8_t bSourceID;
+ uint8_t iTerminal;
+} __attribute__((packed));
+
+/* Table 4-7: Feature Unit Descriptor (head) */
+struct usb_audio_feature_unit_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bUnitID;
+ uint8_t bSourceID;
+ uint8_t bControlSize;
+ uint16_t bmaControlMaster; /* device can assume 16-bit, given highest
+ * defined bit in spec is bit #9.
+ * (it is thus required bControlSize=2) */
+ /* ... */
+} __attribute__((packed));
+
+/* Table 4-7: Feature Unit Descriptor (body) */
+struct usb_audio_feature_unit_descriptor_body {
+ /* ... */
+ uint16_t bmaControl;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 4-7: Feature Unit Descriptor (tail) */
+struct usb_audio_feature_unit_descriptor_tail {
+ /* ... */
+ uint8_t iFeature;
+} __attribute__((packed));
+
+/* Table 4-7: Feature Unit Descriptor (2-channel)
+ *
+ * This structure is a convenience covering the (common) case where
+ * there are 2 channels associated with the feature unit
+ */
+struct usb_audio_feature_unit_descriptor_2ch {
+ struct usb_audio_feature_unit_descriptor_head head;
+ struct usb_audio_feature_unit_descriptor_body channel_control[2];
+ struct usb_audio_feature_unit_descriptor_tail tail;
+} __attribute__((packed));
+
+/* Table 4-19: Class-Specific AS Interface Descriptor */
+struct usb_audio_stream_interface_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bTerminalLink;
+ uint8_t bDelay;
+ uint16_t wFormatTag;
+} __attribute__((packed));
+
+/* Table 4-20: Standard AS Isochronous Audio Data Endpoint Descriptor */
+struct usb_audio_stream_endpoint_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bEndpointAddress;
+ uint8_t bmAttributes;
+ uint16_t wMaxPacketSize;
+ uint8_t bInterval;
+ uint8_t bRefresh;
+ uint8_t bSynchAddress;
+} __attribute__((packed));
+
+/* Table 4-21: Class-Specific AS Isochronous Audio Data Endpoint Descriptor */
+struct usb_audio_stream_audio_endpoint_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bmAttributes;
+ uint8_t bLockDelayUnits;
+ uint16_t wLockDelay;
+} __attribute__((packed));
+
+/*
+ * Definitions from the USB_AUDIO_FORMAT_ or usb_audio_format_ namespace come from:
+ * "Universal Serial Bus Device Class Definition for Audio Data Formats, Revision 1.0"
+ */
+
+/* Table 2-1: Type I Format Type Descriptor (head) */
+struct usb_audio_format_type1_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bFormatType;
+ uint8_t bNrChannels;
+ uint8_t bSubFrameSize;
+ uint8_t bBitResolution;
+ uint8_t bSamFreqType;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 2-2: Continuous Sampling Frequency */
+struct usb_audio_format_continuous_sampling_frequency {
+ /* ... */
+ uint32_t tLowerSamFreq : 24;
+ uint32_t tUpperSamFreq : 24;
+} __attribute__((packed));
+
+/* Table 2-3: Discrete Number of Sampling Frequencies */
+struct usb_audio_format_discrete_sampling_frequency {
+ /* ... */
+ uint32_t tSamFreq : 24;
+} __attribute__((packed));
+
+/* Table 2-1: Type I Format Type Descriptor (1 sampling frequency)
+ *
+ * This structure is a convenience covering the (common) case where
+ * only 1 discrete sampling frequency is used
+ */
+struct usb_audio_format_type1_descriptor_1freq {
+ struct usb_audio_format_type1_descriptor_head head;
+ struct usb_audio_format_discrete_sampling_frequency freqs[1];
+} __attribute__((packed));
+
+#endif
+
+/**@}*/
diff --git a/applications/plugins/usb_midi/usb/cm3_usb_midi.h b/applications/plugins/usb_midi/usb/cm3_usb_midi.h
new file mode 100644
index 000000000..8435c883e
--- /dev/null
+++ b/applications/plugins/usb_midi/usb/cm3_usb_midi.h
@@ -0,0 +1,190 @@
+/** @defgroup usb_audio_defines USB MIDI Type Definitions
+
+@brief Defined Constants and Types for the USB MIDI Type Definitions
+
+@ingroup USB_defines
+
+@version 1.0.0
+
+@author @htmlonly © @endhtmlonly 2014
+Daniel Thompson
+
+@date 19 April 2014
+
+LGPL License Terms @ref lgpl_license
+*/
+
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2014 Daniel Thompson
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see .
+ */
+
+/**@{*/
+
+#ifndef LIBOPENCM3_USB_MIDI_H
+#define LIBOPENCM3_USB_MIDI_H
+
+#include
+
+/*
+ * Definitions from the USB_MIDI_ or usb_midi_ namespace come from:
+ * "Universal Serial Bus Class Definitions for MIDI Devices, Revision 1.0"
+ */
+
+/* Appendix A.1: MS Class-Specific Interface Descriptor Subtypes */
+#define USB_MIDI_SUBTYPE_MS_DESCRIPTOR_UNDEFINED 0x00
+#define USB_MIDI_SUBTYPE_MS_HEADER 0x01
+#define USB_MIDI_SUBTYPE_MIDI_IN_JACK 0x02
+#define USB_MIDI_SUBTYPE_MIDI_OUT_JACK 0x03
+#define USB_MIDI_SUBTYPE_MIDI_ELEMENT 0x04
+
+/* Appendix A.2: MS Class-Specific Endpoint Descriptor Subtypes */
+#define USB_MIDI_SUBTYPE_DESCRIPTOR_UNDEFINED 0x00
+#define USB_MIDI_SUBTYPE_MS_GENERAL 0x01
+
+/* Appendix A.3: MS MIDI IN and OUT Jack types */
+#define USB_MIDI_JACK_TYPE_UNDEFINED 0x00
+#define USB_MIDI_JACK_TYPE_EMBEDDED 0x01
+#define USB_MIDI_JACK_TYPE_EXTERNAL 0x02
+
+/* Appendix A.5.1 Endpoint Control Selectors */
+#define USB_MIDI_EP_CONTROL_UNDEFINED 0x00
+#define USB_MIDI_ASSOCIATION_CONTROL 0x01
+
+/* Table 6-2: Class-Specific MS Interface Header Descriptor */
+struct usb_midi_header_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint16_t bcdMSC;
+ uint16_t wTotalLength;
+} __attribute__((packed));
+
+/* Table 6-3: MIDI IN Jack Descriptor */
+struct usb_midi_in_jack_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bJackType;
+ uint8_t bJackID;
+ uint8_t iJack;
+} __attribute__((packed));
+
+/* Table 6-4: MIDI OUT Jack Descriptor (head) */
+struct usb_midi_out_jack_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bJackType;
+ uint8_t bJackID;
+ uint8_t bNrInputPins;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 6.4: MIDI OUT Jack Descriptor (body) */
+struct usb_midi_out_jack_descriptor_body {
+ /* ... */
+ uint8_t baSourceID;
+ uint8_t baSourcePin;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 6.4: MIDI OUT Jack Descriptor (tail) */
+struct usb_midi_out_jack_descriptor_tail {
+ /* ... */
+ uint8_t iJack;
+} __attribute__((packed));
+
+/* Table 6.4: MIDI OUT Jack Descriptor (single)
+ *
+ * This structure is a convenience covering the (normal) case where
+ * there is only one input pin.
+ */
+struct usb_midi_out_jack_descriptor {
+ struct usb_midi_out_jack_descriptor_head head;
+ struct usb_midi_out_jack_descriptor_body source[1];
+ struct usb_midi_out_jack_descriptor_tail tail;
+} __attribute__((packed));
+
+/* Table 6-5: MIDI Element Descriptor (head) */
+struct usb_midi_element_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bElementID;
+ uint8_t bNrInputPins;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 6-5: MIDI Element Descriptor (body) */
+struct usb_midi_element_descriptor_body {
+ /* ... */
+ uint8_t baSourceID;
+ uint8_t baSourcePin;
+ /* ... */
+} __attribute__((packed));
+
+/* Table 6-5: MIDI Element Descriptor (tail) */
+struct usb_midi_element_descriptor_tail {
+ /* ... */
+ uint8_t bNrOutputPins;
+ uint8_t bInTerminalLink;
+ uint8_t bOutTerminalLink;
+ uint8_t bElCapsSize;
+ uint16_t bmElementCaps; /* host cannot assume this is 16-bit but device
+ can (since highest defined bitmap value in
+ v1.0 is bit 11) */
+ uint8_t iElement;
+} __attribute__((packed));
+
+/* Table 6-5: MIDI Element Descriptor (single)
+ *
+ * This structure is a convenience covering the (common) case where
+ * there is only one input pin.
+ */
+struct usb_midi_element_descriptor {
+ struct usb_midi_element_descriptor_head head;
+ struct usb_midi_element_descriptor_body source[1];
+ struct usb_midi_element_descriptor_tail tail;
+} __attribute__((packed));
+
+/* Table 6-7: Class-specific MS Bulk Data Endpoint Descriptor (head) */
+struct usb_midi_endpoint_descriptor_head {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubType;
+ uint8_t bNumEmbMIDIJack;
+} __attribute__((packed));
+
+/* Table 6-7: Class-specific MS Bulk Data Endpoint Descriptor (body) */
+struct usb_midi_endpoint_descriptor_body {
+ uint8_t baAssocJackID;
+} __attribute__((packed));
+
+/* Table 6.7: Class-specific MS Bulk Data Endpoint Descriptor (single)
+ *
+ * This structure is a convenience covering the (normal) case where
+ * there is only one input pin.
+ */
+struct usb_midi_endpoint_descriptor {
+ struct usb_midi_endpoint_descriptor_head head;
+ struct usb_midi_endpoint_descriptor_body jack[1];
+} __attribute__((packed));
+
+#endif
+
+/**@}*/
diff --git a/applications/plugins/usb_midi/usb/usb_midi_driver.c b/applications/plugins/usb_midi/usb/usb_midi_driver.c
new file mode 100644
index 000000000..9abf77d12
--- /dev/null
+++ b/applications/plugins/usb_midi/usb/usb_midi_driver.c
@@ -0,0 +1,428 @@
+#include
+#include
+#include
+#include
+
+#include "usb_midi_driver.h"
+#include "cm3_usb_audio.h"
+#include "cm3_usb_midi.h"
+
+// Appendix B. "Example: Simple MIDI Adapter" from "Universal Serial Bus Device Class Definition for MIDI Devices", Revision 1.0
+
+#define USB_VID 0x6666
+#define USB_PID 0x5119
+
+#define USB_EP0_SIZE 8
+
+#define USB_MIDI_EP_SIZE 64
+#define USB_MIDI_EP_IN 0x81
+#define USB_MIDI_EP_OUT 0x01
+
+#define EP_CFG_DECONFIGURE 0
+#define EP_CFG_CONFIGURE 1
+
+enum {
+ USB_STR_ZERO,
+ USB_STR_MANUFACTURER,
+ USB_STR_PRODUCT,
+ USB_STR_SERIAL_NUMBER,
+};
+
+/*
+ B.1 Device Descriptor
+*/
+static const struct usb_device_descriptor device_descriptor = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = USB_DTYPE_DEVICE,
+ .bcdUSB = VERSION_BCD(2, 0, 0), // was 0x0110, 1.10 - current revision of USBspecification.
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+ .bDeviceSubClass = USB_SUBCLASS_NONE,
+ .bDeviceProtocol = USB_PROTO_NONE,
+ .bMaxPacketSize0 = USB_EP0_SIZE,
+ .idVendor = USB_VID,
+ .idProduct = USB_PID,
+ .bcdDevice = VERSION_BCD(1, 0, 0),
+ .iManufacturer = USB_STR_MANUFACTURER,
+ .iProduct = USB_STR_PRODUCT,
+ .iSerialNumber = USB_STR_SERIAL_NUMBER,
+ .bNumConfigurations = 1,
+};
+
+struct usb_audio_header_descriptor {
+ struct usb_audio_header_descriptor_head head;
+ struct usb_audio_header_descriptor_body body;
+} __attribute__((packed));
+
+struct usb_midi_jacks_descriptor {
+ struct usb_midi_header_descriptor header;
+ struct usb_midi_in_jack_descriptor in_embedded;
+ struct usb_midi_in_jack_descriptor in_external;
+ struct usb_midi_out_jack_descriptor out_embedded;
+ struct usb_midi_out_jack_descriptor out_external;
+} __attribute__((packed));
+
+struct MidiConfigDescriptor {
+ /*
+ B.2 Configuration Descriptor
+ */
+ struct usb_config_descriptor config;
+
+ /*
+ B.3 AudioControl Interface Descriptors
+
+ The AudioControl interface describes the device structure (audio function topology)
+ and is used to manipulate the Audio Controls. This device has no audio function incorporated.
+ However, the AudioControl interface is mandatory and therefore both the standard AC interface
+ descriptor and the classspecific AC interface descriptor must be present.
+ The class-specific AC interface descriptor only contains the header descriptor.
+ */
+ // B.3.1 Standard AC Interface Descriptor
+ struct usb_interface_descriptor audio_control_iface;
+ // B.3.2 Class-specific AC Interface Descriptor
+ struct usb_audio_header_descriptor audio_control_header;
+
+ /*
+ B.4 MIDIStreaming Interface Descriptors
+ */
+ // B.4.1 Standard MS Interface Descriptor
+ struct usb_interface_descriptor midi_streaming_iface;
+ // B.4.2 Class-specific MS Interface Descriptor
+ // B.4.3 MIDI IN Jack Descriptor
+ // B.4.4 MIDI OUT Jack Descriptor
+ struct usb_midi_jacks_descriptor midi_jacks;
+
+ /*
+ B.5 Bulk OUT Endpoint Descriptors
+ */
+ // B.5.1 Standard Bulk OUT Endpoint Descriptor
+ struct usb_endpoint_descriptor bulk_out;
+ // B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor
+ struct usb_midi_endpoint_descriptor midi_bulk_out;
+
+ /*
+ B.6 Bulk IN Endpoint Descriptors
+ */
+ // B.6.1 Standard Bulk IN Endpoint Descriptor
+ struct usb_endpoint_descriptor bulk_in;
+ // B.6.2 Class-specific MS Bulk IN Endpoint Descriptor
+ struct usb_midi_endpoint_descriptor midi_bulk_in;
+} __attribute__((packed));
+
+static const struct MidiConfigDescriptor config_descriptor = {
+ .config =
+ {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = USB_DTYPE_CONFIGURATION,
+ .wTotalLength = sizeof(struct MidiConfigDescriptor),
+ .bNumInterfaces = 2, /* control and data */
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = USB_CFG_ATTR_RESERVED,
+ .bMaxPower = USB_CFG_POWER_MA(100),
+ },
+ .audio_control_iface =
+ {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = USB_DTYPE_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_AUDIO_SUBCLASS_CONTROL,
+ .bInterfaceProtocol = USB_PROTO_NONE,
+ .iInterface = 0,
+ },
+ .audio_control_header =
+ {
+ .head =
+ {
+ .bLength = sizeof(struct usb_audio_header_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_AUDIO_TYPE_HEADER,
+ .bcdADC = VERSION_BCD(1, 0, 0),
+ .wTotalLength = sizeof(struct usb_audio_header_descriptor),
+ .bInCollection = 1,
+ },
+ .body =
+ {
+ .baInterfaceNr = 1,
+ },
+ },
+ .midi_streaming_iface =
+ {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = USB_DTYPE_INTERFACE,
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_AUDIO_SUBCLASS_MIDISTREAMING,
+ .bInterfaceProtocol = USB_PROTO_NONE,
+ .iInterface = 0,
+ },
+ .midi_jacks =
+ {
+ .header =
+ {
+ .bLength = sizeof(struct usb_midi_header_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_MIDI_SUBTYPE_MS_HEADER,
+ .bcdMSC = VERSION_BCD(1, 0, 0),
+ .wTotalLength = sizeof(struct usb_midi_jacks_descriptor),
+ },
+ .in_embedded =
+ {
+ .bLength = sizeof(struct usb_midi_in_jack_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK,
+ .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED,
+ .bJackID = 0x01,
+ .iJack = 0x00,
+ },
+ .in_external =
+ {
+ .bLength = sizeof(struct usb_midi_in_jack_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK,
+ .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL,
+ .bJackID = 0x02,
+ .iJack = 0x00,
+ },
+ .out_embedded =
+ {
+ .head =
+ {
+ .bLength = sizeof(struct usb_midi_out_jack_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK,
+ .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED,
+ .bJackID = 0x03,
+ .bNrInputPins = 1,
+ },
+ .source[0] =
+ {
+ .baSourceID = 0x02,
+ .baSourcePin = 0x01,
+ },
+ .tail =
+ {
+ .iJack = 0x00,
+ },
+ },
+ .out_external =
+ {
+ .head =
+ {
+ .bLength = sizeof(struct usb_midi_out_jack_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE,
+ .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK,
+ .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL,
+ .bJackID = 0x04,
+ .bNrInputPins = 1,
+ },
+ .source[0] =
+ {
+ .baSourceID = 0x01,
+ .baSourcePin = 0x01,
+ },
+ .tail =
+ {
+ .iJack = 0x00,
+ },
+ },
+ },
+ .bulk_out =
+ {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = USB_DTYPE_ENDPOINT,
+ .bEndpointAddress = USB_MIDI_EP_OUT,
+ .bmAttributes = USB_EPTYPE_BULK,
+ .wMaxPacketSize = USB_MIDI_EP_SIZE,
+ .bInterval = 0,
+ },
+ .midi_bulk_out =
+ {
+ .head =
+ {
+ .bLength = sizeof(struct usb_midi_endpoint_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT,
+ .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL,
+ .bNumEmbMIDIJack = 1,
+ },
+ .jack[0] =
+ {
+ .baAssocJackID = 0x01,
+ },
+ },
+ .bulk_in =
+ {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = USB_DTYPE_ENDPOINT,
+ .bEndpointAddress = USB_MIDI_EP_IN,
+ .bmAttributes = USB_EPTYPE_BULK,
+ .wMaxPacketSize = USB_MIDI_EP_SIZE,
+ .bInterval = 0,
+ },
+ .midi_bulk_in =
+ {
+ .head =
+ {
+ .bLength = sizeof(struct usb_midi_endpoint_descriptor),
+ .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT,
+ .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL,
+ .bNumEmbMIDIJack = 1,
+ },
+ .jack[0] =
+ {
+ .baAssocJackID = 0x03,
+ },
+ },
+};
+
+static const struct usb_string_descriptor dev_manufacturer_string =
+ USB_STRING_DESC("Flipper Devices Inc.");
+
+static const struct usb_string_descriptor dev_product_string =
+ USB_STRING_DESC("Flipper MIDI Device");
+
+static const struct usb_string_descriptor dev_serial_number_string =
+ USB_STRING_DESC("Serial Number");
+
+static void midi_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx);
+static void midi_deinit(usbd_device* dev);
+static void midi_on_wakeup(usbd_device* dev);
+static void midi_on_suspend(usbd_device* dev);
+static usbd_respond midi_ep_config(usbd_device* dev, uint8_t cfg);
+static usbd_respond midi_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
+
+FuriHalUsbInterface midi_usb_interface = {
+ .init = midi_init,
+ .deinit = midi_deinit,
+ .wakeup = midi_on_wakeup,
+ .suspend = midi_on_suspend,
+ .dev_descr = (struct usb_device_descriptor*)&device_descriptor,
+ .cfg_descr = (void*)&config_descriptor,
+};
+
+typedef struct {
+ usbd_device* dev;
+ MidiRxCallback rx_callback;
+ void* context;
+ FuriSemaphore* semaphore_tx;
+ bool connected;
+} MidiUsb;
+
+static MidiUsb midi_usb;
+
+void midi_usb_set_context(void* context) {
+ midi_usb.context = context;
+}
+
+void midi_usb_set_rx_callback(MidiRxCallback callback) {
+ midi_usb.rx_callback = callback;
+}
+
+size_t midi_usb_rx(uint8_t* buffer, size_t size) {
+ size_t len = usbd_ep_read(midi_usb.dev, USB_MIDI_EP_OUT, buffer, size);
+ return len;
+}
+
+size_t midi_usb_tx(uint8_t* buffer, uint8_t size) {
+ if((midi_usb.semaphore_tx == NULL) || (midi_usb.connected == false)) return 0;
+
+ furi_check(furi_semaphore_acquire(midi_usb.semaphore_tx, FuriWaitForever) == FuriStatusOk);
+
+ if(midi_usb.connected) {
+ int32_t len = usbd_ep_write(midi_usb.dev, USB_MIDI_EP_IN, buffer, size);
+ return len;
+ } else {
+ return 0;
+ }
+}
+
+static void midi_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
+ UNUSED(intf);
+ UNUSED(ctx);
+
+ midi_usb_interface.str_manuf_descr = (void*)&dev_manufacturer_string;
+ midi_usb_interface.str_prod_descr = (void*)&dev_product_string;
+ midi_usb_interface.str_serial_descr = (void*)&dev_serial_number_string;
+ midi_usb_interface.dev_descr->idVendor = USB_VID;
+ midi_usb_interface.dev_descr->idProduct = USB_PID;
+
+ midi_usb.dev = dev;
+ if(midi_usb.semaphore_tx == NULL) midi_usb.semaphore_tx = furi_semaphore_alloc(1, 1);
+
+ usbd_reg_config(dev, midi_ep_config);
+ usbd_reg_control(dev, midi_control);
+
+ usbd_connect(dev, true);
+}
+
+static void midi_deinit(usbd_device* dev) {
+ midi_usb.connected = false;
+ midi_usb.dev = NULL;
+ furi_semaphore_free(midi_usb.semaphore_tx);
+
+ usbd_reg_config(dev, NULL);
+ usbd_reg_control(dev, NULL);
+}
+
+static void midi_on_wakeup(usbd_device* dev) {
+ UNUSED(dev);
+ if(!midi_usb.connected) {
+ midi_usb.connected = true;
+ }
+}
+
+static void midi_on_suspend(usbd_device* dev) {
+ UNUSED(dev);
+ if(midi_usb.connected) {
+ midi_usb.connected = false;
+ }
+}
+
+static void midi_tx_rx(usbd_device* dev, uint8_t event, uint8_t ep) {
+ UNUSED(dev);
+ UNUSED(ep);
+
+ switch(event) {
+ case usbd_evt_eptx:
+ furi_semaphore_release(midi_usb.semaphore_tx);
+ break;
+ case usbd_evt_eprx:
+ if(midi_usb.rx_callback != NULL) {
+ midi_usb.rx_callback(midi_usb.context);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static usbd_respond midi_ep_config(usbd_device* dev, uint8_t cfg) {
+ switch(cfg) {
+ case EP_CFG_DECONFIGURE:
+ usbd_ep_deconfig(dev, USB_MIDI_EP_OUT);
+ usbd_ep_deconfig(dev, USB_MIDI_EP_IN);
+ usbd_reg_endpoint(dev, USB_MIDI_EP_OUT, NULL);
+ usbd_reg_endpoint(dev, USB_MIDI_EP_IN, NULL);
+ return usbd_ack;
+ case EP_CFG_CONFIGURE:
+ usbd_ep_config(dev, USB_MIDI_EP_OUT, USB_EPTYPE_BULK, USB_MIDI_EP_SIZE);
+ usbd_ep_config(dev, USB_MIDI_EP_IN, USB_EPTYPE_BULK, USB_MIDI_EP_SIZE);
+ usbd_reg_endpoint(dev, USB_MIDI_EP_OUT, midi_tx_rx);
+ usbd_reg_endpoint(dev, USB_MIDI_EP_IN, midi_tx_rx);
+ return usbd_ack;
+ default:
+ return usbd_fail;
+ }
+}
+
+static usbd_respond midi_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
+ UNUSED(dev);
+ UNUSED(req);
+ UNUSED(callback);
+
+ return usbd_fail;
+}
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/usb/usb_midi_driver.h b/applications/plugins/usb_midi/usb/usb_midi_driver.h
new file mode 100644
index 000000000..d385efcb5
--- /dev/null
+++ b/applications/plugins/usb_midi/usb/usb_midi_driver.h
@@ -0,0 +1,14 @@
+#pragma once
+#include
+
+extern FuriHalUsbInterface midi_usb_interface;
+
+typedef void (*MidiRxCallback)(void* context);
+
+void midi_usb_set_context(void* context);
+
+void midi_usb_set_rx_callback(MidiRxCallback callback);
+
+size_t midi_usb_rx(uint8_t* buffer, size_t size);
+
+size_t midi_usb_tx(uint8_t* buffer, uint8_t size);
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/usb_midi.c b/applications/plugins/usb_midi/usb_midi.c
new file mode 100644
index 000000000..b1149a176
--- /dev/null
+++ b/applications/plugins/usb_midi/usb_midi.c
@@ -0,0 +1,80 @@
+#include
+#include
+#include "usb/usb_midi_driver.h"
+#include "midi/parser.h"
+#include "midi/usb_message.h"
+#include
+
+float note_to_frequency(int note) {
+ float a = 440;
+ return (a / 32) * powf(2, ((note - 9) / 12.0));
+}
+
+typedef enum {
+ MidiThreadEventStop = (1 << 0),
+ MidiThreadEventRx = (1 << 1),
+ MidiThreadEventAll = MidiThreadEventStop | MidiThreadEventRx,
+} MidiThreadEvent;
+
+static void midi_rx_callback(void* context) {
+ furi_assert(context);
+ FuriThreadId thread_id = (FuriThreadId)context;
+ furi_thread_flags_set(thread_id, MidiThreadEventRx);
+}
+
+int32_t usb_midi_app(void* p) {
+ UNUSED(p);
+
+ FuriHalUsbInterface* usb_config_prev;
+ usb_config_prev = furi_hal_usb_get_config();
+ midi_usb_set_context(furi_thread_get_id(furi_thread_get_current()));
+ midi_usb_set_rx_callback(midi_rx_callback);
+ furi_hal_usb_set_config(&midi_usb_interface, NULL);
+
+ MidiParser* parser = midi_parser_alloc();
+ uint32_t events;
+ uint8_t current_note = 255;
+
+ while(1) {
+ events = furi_thread_flags_wait(MidiThreadEventAll, FuriFlagWaitAny, FuriWaitForever);
+
+ if(!(events & FuriFlagError)) {
+ if(events & MidiThreadEventRx) {
+ uint8_t buffer[64];
+ size_t size = midi_usb_rx(buffer, sizeof(buffer));
+ // loopback
+ // midi_usb_tx(buffer, size);
+ size_t start = 0;
+ while(start < size) {
+ CodeIndex code_index = code_index_from_data(buffer[start]);
+ uint8_t data_size = usb_message_data_size(code_index);
+ if(data_size == 0) break;
+
+ start += 1;
+ for(size_t j = 0; j < data_size; j++) {
+ if(midi_parser_parse(parser, buffer[start + j])) {
+ MidiEvent* event = midi_parser_get_message(parser);
+ if(event->type == NoteOn) {
+ NoteOnEvent note_on = AsNoteOn(event);
+ current_note = note_on.note;
+ furi_hal_speaker_start(
+ note_to_frequency(note_on.note), note_on.velocity / 127.0f);
+ } else if(event->type == NoteOff) {
+ NoteOffEvent note_off = AsNoteOff(event);
+ if(note_off.note == current_note) {
+ furi_hal_speaker_stop();
+ }
+ }
+ }
+ }
+ start += data_size;
+ }
+ }
+ }
+ }
+
+ midi_parser_free(parser);
+ furi_hal_usb_set_config(usb_config_prev, NULL);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/applications/plugins/usb_midi/usb_midi.png b/applications/plugins/usb_midi/usb_midi.png
new file mode 100644
index 000000000..6d0ac6fed
Binary files /dev/null and b/applications/plugins/usb_midi/usb_midi.png differ