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) 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

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.