├── src ├── AppleMIDI.cpp ├── AppleMIDI_PlatformEnd.h ├── AppleMIDI_Namespace.h ├── AppleMIDI_PlatformBegin.h ├── AppleMIDI_Debug.h ├── AppleMIDI_Participant.h ├── rtp_Defs.h ├── rtpMIDI_Clock.h ├── AppleMIDI_Settings.h ├── utility │ ├── endian.h │ └── Deque.h ├── rtpMIDI_Defs.h ├── AppleMIDI_Defs.h ├── rtpMIDI_Parser_JournalSection.hpp ├── rtpMIDI_Parser.h ├── rtpMIDI_Parser_CommandSection.hpp ├── AppleMIDI.h └── AppleMIDI_Parser.h ├── res ├── LogErrors.PNG ├── Install3-1.PNG └── latency │ ├── samd21.png │ ├── ESP32W5500.png │ ├── ESP32Wi-Fi.png │ └── atmega2560.png ├── .github ├── FUNDING.yml ├── workflows │ └── build.yml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── test ├── TestParser.vcxproj.user ├── rtpMidi.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ └── bart.xcuserdatad │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ └── IDEFindNavigatorScopes.plist │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcuserdata │ │ └── bart.xcuserdatad │ │ │ ├── xcschemes │ │ │ └── xcschememanagement.plist │ │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── rtpMidi.xcscheme │ └── project.pbxproj ├── IPAddress.h ├── TestParser.vcxproj.filters ├── TestParser.sln ├── Arduino.h ├── NoteOn.cpp └── TestParser.vcxproj ├── LICENSE.md ├── library.properties ├── keywords.txt ├── examples ├── wESP32_NoteOnOffEverySec │ ├── ETH_Helper.h │ └── wESP32_NoteOnOffEverySec.ino ├── SAMD_Bonjour │ └── SAMD_Bonjour.ino ├── AVR_MinMemUsage │ └── AVR_MinMemUsage.ino ├── ESP32_DynamicInstantiation │ ├── ESP32_dynamicClass.ino │ ├── midiHelpers.h │ └── ETH_helper.h ├── AVR_NonDefaultSession │ └── AVR_NonDefaultSession.ino ├── AVR_NoteOnOffEverySec │ └── AVR_NoteOnOffEverySec.ino ├── AVR_E3_NoteOnOffEverySec │ └── AVR_E3_NoteOnOffEverySec.ino ├── ESP32_NoteOnOffEverySec │ └── ESP32_NoteOnOffEverySec.ino ├── ESP8266_NoteOnOffEverySec │ └── ESP8266_NoteOnOffEverySec.ino ├── AVR_Initiator │ └── AVR_Initiator.ino ├── AVR_Directory │ └── AVR_Directory.ino ├── ESP32_W5500_Callbacks │ ├── ETH_Helper.h │ └── ESP32_W5500_Callbacks.ino ├── ESP8266_NoteOnOffEverySec_softAP_mDNS │ └── ESP8266_NoteOnOffEverySec_softAP_mDNS.ino ├── AVR_ReceivedRawMidiData │ └── AVR_ReceivedRawMidiData.ino ├── AVR_MultipleSessions │ └── AVR_MultipleSessions.ino ├── AVR_SysEx │ └── AVR_SysEx.ino ├── Teensy41_NoteOnOffEverySec │ └── Teensy41_NoteOnOffEverySec.ino └── AVR_Callbacks │ └── AVR_Callbacks.ino ├── .travis.yml ├── ci └── build-arduino.sh ├── README.md └── doc └── RFC6295_notes.md /src/AppleMIDI.cpp: -------------------------------------------------------------------------------- 1 | #include "AppleMIDI.h" 2 | -------------------------------------------------------------------------------- /res/LogErrors.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/LogErrors.PNG -------------------------------------------------------------------------------- /res/Install3-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/Install3-1.PNG -------------------------------------------------------------------------------- /res/latency/samd21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/latency/samd21.png -------------------------------------------------------------------------------- /res/latency/ESP32W5500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/latency/ESP32W5500.png -------------------------------------------------------------------------------- /res/latency/ESP32Wi-Fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/latency/ESP32Wi-Fi.png -------------------------------------------------------------------------------- /res/latency/atmega2560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/res/latency/atmega2560.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [lathoub] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | examples/.DS_Store 3 | src/.DS_Store 4 | test/.vs 5 | test/Debug 6 | src/.vscode 7 | test/x64 8 | .development 9 | examples/ESP32_Callbacks/arduino_secrets.h 10 | -------------------------------------------------------------------------------- /test/TestParser.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/AppleMIDI_PlatformEnd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Namespace.h" 4 | 5 | BEGIN_APPLEMIDI_NAMESPACE 6 | 7 | #ifdef WIN32 8 | #pragma pack(pop) 9 | #endif 10 | #undef PACKED 11 | 12 | END_APPLEMIDI_NAMESPACE 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The hardware is released under Creative Commons Share-alike 4.0. 2 | 3 | All other code is open source so please feel free to do anything you want with it; you buy me a beer if you use this and we meet someday (Beerware license). 4 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lathoub/Arduino-AppleMIDI-Library/HEAD/test/rtpMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [pull_request, push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v2 9 | - name: Build on Arduino CLI 10 | run: bash ci/build-arduino.sh 11 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/project.xcworkspace/xcuserdata/bart.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/lathoub/Arduino-AppleMIDI-Library/discussions 5 | about: Not a bug or a feature request ? Discuss your problem, ask for help or show what you've built in Discussions. 6 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/AppleMIDI_Namespace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define APPLEMIDI_NAMESPACE appleMidi 4 | #define BEGIN_APPLEMIDI_NAMESPACE \ 5 | namespace APPLEMIDI_NAMESPACE \ 6 | { 7 | #define END_APPLEMIDI_NAMESPACE } 8 | 9 | #define USING_NAMESPACE_APPLEMIDI using namespace APPLEMIDI_NAMESPACE; 10 | 11 | BEGIN_APPLEMIDI_NAMESPACE 12 | 13 | END_APPLEMIDI_NAMESPACE -------------------------------------------------------------------------------- /src/AppleMIDI_PlatformBegin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Namespace.h" 4 | 5 | #include "utility/endian.h" 6 | 7 | BEGIN_APPLEMIDI_NAMESPACE 8 | 9 | #ifdef _MSC_VER 10 | #pragma pack(push, 1) 11 | #define PACKED 12 | #else 13 | #define PACKED __attribute__((__packed__)) 14 | #endif 15 | 16 | struct DefaultPlatform 17 | { 18 | }; 19 | 20 | END_APPLEMIDI_NAMESPACE 21 | -------------------------------------------------------------------------------- /test/IPAddress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class IPAddress 4 | { 5 | public: 6 | IPAddress(){}; 7 | IPAddress(const IPAddress& from){}; 8 | IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet){}; 9 | IPAddress(uint32_t address) { } 10 | IPAddress(int address) { } 11 | IPAddress(const uint8_t *address) {}; 12 | 13 | bool operator==(const IPAddress&) const { return true; } 14 | bool operator!=(const IPAddress&) const { return true; } 15 | }; 16 | 17 | const IPAddress INADDR_NONE(0, 0, 0, 0); -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AppleMIDI 2 | version=3.3.0 3 | author=lathoub 4 | maintainer=lathoub 5 | sentence=AppleMIDI (aka rtpMIDI) MIDI I/Os for Arduino 6 | paragraph=AppleMIDI (aka rtpMIDI) is a protocol to transport MIDI messages within RTP (Real-time Protocol) packets over Ethernet and WiFi networks. This major rewrite is faster, more stable and uses less memory. Read the Wiki page when migrating 7 | category=Communication 8 | url=https://github.com/lathoub/Arduino-AppleMidi-Library 9 | architectures=* 10 | includes=AppleMIDI.h 11 | depends=MIDI Library 12 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | rtpMidi.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | CCE329B423C2037C00A197D1 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/AppleMIDI_Debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef SerialMon 4 | namespace { 5 | static void AM_DBG_SETUP(unsigned long baud) { 6 | SerialMon.begin(baud); 7 | while (!SerialMon); 8 | } 9 | 10 | template 11 | static void AM_DBG_PLAIN(T last) { 12 | SerialMon.println(last); 13 | } 14 | 15 | template 16 | static void AM_DBG_PLAIN(T head, Args... tail) { 17 | SerialMon.print(head); 18 | SerialMon.print(' '); 19 | AM_DBG_PLAIN(tail...); 20 | } 21 | 22 | template 23 | static void AM_DBG(Args... args) { 24 | AM_DBG_PLAIN(args...); 25 | } 26 | } // namespace 27 | #else 28 | #define AM_DBG_SETUP(...) 29 | #define AM_DBG_PLAIN(...) 30 | #define AM_DBG(...) 31 | #endif 32 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for AppleMIDI 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | AppleMidi KEYWORD1 9 | 10 | ####################################### 11 | # Methods and Functions (KEYWORD2) 12 | ####################################### 13 | setName KEYWORD2 14 | setPort KEYWORD2 15 | setHandleConnected KEYWORD2 16 | setHandleDisconnected KEYWORD2 17 | setHandleException KEYWORD2 18 | setHandleStartReceivedMidi KEYWORD2 19 | setHandleReceivedMidi KEYWORD2 20 | setHandleEndReceivedMidi KEYWORD2 21 | setHandleReceivedRtp KEYWORD2 22 | setHandleSendRtp KEYWORD2 23 | 24 | ####################################### 25 | # Instances (KEYWORD3) 26 | ####################################### 27 | AppleMIDI KEYWORD3 28 | 29 | ####################################### 30 | # Constants (LITERAL1) 31 | ####################################### 32 | 33 | APPLEMIDI_CREATE_INSTANCE LITERAL1 34 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE LITERAL1 35 | -------------------------------------------------------------------------------- /test/TestParser.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | 14 | 15 | Source Files 16 | 17 | 18 | 19 | 20 | Header Files 21 | 22 | 23 | Header Files 24 | 25 | 26 | Header Files 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/wESP32_NoteOnOffEverySec/ETH_Helper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static bool eth_connected = false; 5 | 6 | void WiFiEvent(WiFiEvent_t event) 7 | { 8 | switch (event) { 9 | case SYSTEM_EVENT_ETH_START: 10 | AM_DBG(F("ETH Started")); 11 | //set eth hostname here 12 | ETH.setHostname("esp32-ethernet"); 13 | break; 14 | case SYSTEM_EVENT_ETH_CONNECTED: 15 | AM_DBG(F("ETH Connected")); 16 | break; 17 | case SYSTEM_EVENT_ETH_GOT_IP: 18 | AM_DBG(F("ETH MAC:"), ETH.macAddress(), F("IPv4:"), ETH.localIP(), ETH.linkSpeed(), F("Mbps")); 19 | if (ETH.fullDuplex()) 20 | AM_DBG(F("FULL_DUPLEX")); 21 | eth_connected = true; 22 | break; 23 | case SYSTEM_EVENT_ETH_DISCONNECTED: 24 | AM_DBG(F("ETH Disconnected")); 25 | eth_connected = false; 26 | break; 27 | case SYSTEM_EVENT_ETH_STOP: 28 | AM_DBG(F("ETH Stopped")); 29 | eth_connected = false; 30 | break; 31 | default: 32 | break; 33 | } 34 | } 35 | 36 | bool ETH_startup() 37 | { 38 | WiFi.onEvent(WiFiEvent); 39 | ETH.begin(); 40 | 41 | AM_DBG(F("Getting IP address...")); 42 | 43 | while (!eth_connected) 44 | delay(100); 45 | 46 | return true; 47 | } 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | 5 | # Cache PlatformIO packages using Travis CI container-based infrastructure 6 | cache: 7 | directories: 8 | - "~/.platformio" 9 | 10 | install: 11 | - pip install -U platformio 12 | - platformio update 13 | - platformio lib -g install 62@5.0.0 870 872 236 14 | 15 | script: 16 | - pio ci --board=uno --lib=. examples/AVR_Callbacks/AVR_Callbacks.ino 17 | - pio ci --board=uno --lib=. examples/AVR_Directory/AVR_Directory.ino 18 | - pio ci --board=uno --lib=. examples/AVR_Initiator/AVR_Initiator.ino 19 | - pio ci --board=uno --lib=. examples/AVR_MinMemUsage/AVR_MinMemUsage.ino 20 | - pio ci --board=uno --lib=. examples/AVR_MultipleSessions/AVR_MultipleSessions.ino 21 | - pio ci --board=uno --lib=. examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino 22 | - pio ci --board=uno --lib=. examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino 23 | - pio ci --board=uno --lib=. examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino 24 | - pio ci --board=uno --lib=. examples/AVR_SysEx/AVR_SysEx.ino 25 | - pio ci --board=mkrzero --lib=. examples/SAMD_Bonjour/SAMD_Bonjour.ino 26 | - pio ci --board=huzzah --lib=. examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino 27 | - pio ci --board=featheresp32 --lib=. examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino 28 | -------------------------------------------------------------------------------- /src/AppleMIDI_Participant.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Defs.h" 4 | 5 | #include "AppleMIDI_Namespace.h" 6 | 7 | BEGIN_APPLEMIDI_NAMESPACE 8 | 9 | template 10 | struct Participant 11 | { 12 | ParticipantKind kind; 13 | ssrc_t ssrc = 0; 14 | IPAddress remoteIP = INADDR_NONE; 15 | uint16_t remotePort = 0; 16 | 17 | unsigned long receiverFeedbackStartTime; 18 | bool doReceiverFeedback = false; 19 | 20 | uint16_t sendSequenceNr = random(1, UINT16_MAX); // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header 21 | uint16_t receiveSequenceNr = 0; 22 | 23 | unsigned long lastSyncExchangeTime; 24 | 25 | #ifdef APPLEMIDI_INITIATOR 26 | uint8_t connectionAttempts = 0; 27 | uint32_t initiatorToken = 0; 28 | unsigned long lastInviteSentTime; 29 | InviteStatus invitationStatus = Initiating; 30 | 31 | uint8_t synchronizationHeartBeats = 0; 32 | uint8_t synchronizationCount = 0; 33 | bool synchronizing = false; 34 | #endif 35 | 36 | #ifdef USE_EXT_CALLBACKS 37 | bool firstMessageReceived = true; 38 | uint32_t offsetEstimate; 39 | #endif 40 | 41 | #ifdef KEEP_SESSION_NAME 42 | char sessionName[Settings::MaxSessionNameLen + 1]; 43 | #endif 44 | } ; 45 | 46 | END_APPLEMIDI_NAMESPACE 47 | -------------------------------------------------------------------------------- /examples/SAMD_Bonjour/SAMD_Bonjour.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include // https://github.com/TrippyLighting/EthernetBonjour 3 | 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 13 | 14 | // ----------------------------------------------------------------------------- 15 | // 16 | // ----------------------------------------------------------------------------- 17 | void setup() 18 | { 19 | if (Ethernet.begin(mac) == 0) 20 | for (;;); 21 | 22 | MIDI.begin(); 23 | 24 | // Initialize the Bonjour/MDNS library. You can now reach or ping this 25 | // Arduino via the host name "arduino.local", provided that your operating 26 | // system is Bonjour-enabled (such as MacOS X). 27 | // Always call this before any other method! 28 | EthernetBonjour.begin("arduino"); 29 | 30 | EthernetBonjour.addServiceRecord("Arduino._apple-midi", 31 | AppleMIDI.getPort(), 32 | MDNSServiceUDP); 33 | } 34 | 35 | // ----------------------------------------------------------------------------- 36 | // 37 | // ----------------------------------------------------------------------------- 38 | void loop() 39 | { 40 | // Listen to incoming notes 41 | MIDI.read(); 42 | 43 | EthernetBonjour.run(); 44 | } 45 | -------------------------------------------------------------------------------- /test/TestParser.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29306.81 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestParser", "TestParser.vcxproj", "{25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x64.ActiveCfg = Debug|x64 17 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x64.Build.0 = Debug|x64 18 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x86.ActiveCfg = Debug|Win32 19 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Debug|x86.Build.0 = Debug|Win32 20 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x64.ActiveCfg = Release|x64 21 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x64.Build.0 = Release|x64 22 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x86.ActiveCfg = Release|Win32 23 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {E91ABBB5-98C4-4D25-B603-CA43FE39B327} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/rtp_Defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Namespace.h" 4 | 5 | BEGIN_APPLEMIDI_NAMESPACE 6 | 7 | // 0 1 2 3 8 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | // | V |P|X| CC |M| PT | Sequence number | 11 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | 13 | #define RTP_VERSION_2 2 14 | 15 | // first octet 16 | #define RTP_P_FIELD 0x20 17 | #define RTP_X_FIELD 0x10 18 | #define RTP_CC_FIELD 0xF 19 | 20 | // second octet 21 | #define RTP_M_FIELD 0x80 22 | #define RTP_PT_FIELD 0x7F 23 | 24 | /* magic number */ 25 | #define PAYLOADTYPE_RTPMIDI 97 26 | 27 | /* Version is the first 2 bits of the first octet*/ 28 | #define RTP_VERSION(octet) ((octet) >> 6) 29 | 30 | /* Padding is the third bit; No need to shift, because true is any value 31 | other than 0! */ 32 | #define RTP_PADDING(octet) ((octet)&RTP_P_FIELD) 33 | 34 | /* Extension bit is the fourth bit */ 35 | #define RTP_EXTENSION(octet) ((octet)&RTP_X_FIELD) 36 | 37 | /* CSRC count is the last four bits */ 38 | #define RTP_CSRC_COUNT(octet) ((octet)&RTP_CC_FIELD) 39 | 40 | /* Marker is the first bit of the second octet */ 41 | #define RTP_MARKER(octet) ((octet)&RTP_M_FIELD) 42 | 43 | /* Payload type is the last 7 bits */ 44 | #define RTP_PAYLOAD_TYPE(octet) ((octet)&RTP_PT_FIELD) 45 | 46 | typedef struct PACKED Rtp 47 | { 48 | uint8_t vpxcc; 49 | uint8_t mpayload; 50 | uint16_t sequenceNr; 51 | uint32_t timestamp; 52 | ssrc_t ssrc; 53 | } Rtp_t; 54 | 55 | END_APPLEMIDI_NAMESPACE 56 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/xcuserdata/bart.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/rtpMIDI_Clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "AppleMIDI_Namespace.h" 6 | 7 | BEGIN_APPLEMIDI_NAMESPACE 8 | 9 | #define MSEC_PER_SEC 1000 10 | 11 | typedef struct rtpMidi_Clock 12 | { 13 | uint32_t clockRate_; 14 | 15 | uint64_t startTime_; 16 | uint64_t initialTimeStamp_; 17 | 18 | void Init(uint64_t initialTimeStamp, uint32_t clockRate) 19 | { 20 | initialTimeStamp_ = 0; 21 | clockRate_ = clockRate; 22 | 23 | if (clockRate_ == 0) 24 | { 25 | clockRate_ = MIDI_SAMPLING_RATE_DEFAULT; 26 | } 27 | 28 | startTime_ = Ticks(); 29 | } 30 | 31 | /// 32 | /// Returns an timestamp value suitable for inclusion in a RTP packet header. 33 | /// 34 | uint64_t Now() 35 | { 36 | return CalculateCurrentTimeStamp(); 37 | } 38 | 39 | private: 40 | uint64_t CalculateCurrentTimeStamp() 41 | { 42 | return (CalculateTimeSpent() * clockRate_) / MSEC_PER_SEC; 43 | } 44 | 45 | /// 46 | /// Returns the time spent since the initial clock timestamp value. 47 | /// The returned value is expressed in units of "clock pulsations", 48 | /// that are equivalent to seconds, scaled by the clock rate. 49 | /// i.e: 1 second difference will result in a delta value equals to the clock rate. 50 | /// 51 | uint64_t CalculateTimeSpent() 52 | { 53 | return Ticks() - startTime_; 54 | } 55 | 56 | /// 57 | /// millis() as a 64bit (not the default 32bit) 58 | /// this prevents wrap around. 59 | /// 60 | uint64_t Ticks() const 61 | { 62 | static uint32_t low32, high32; 63 | uint32_t new_low32 = millis(); 64 | if (new_low32 < low32) high32++; 65 | low32 = new_low32; 66 | return (uint64_t) high32 << 32 | low32; 67 | } 68 | 69 | 70 | } RtpMidiClock_t; 71 | 72 | END_APPLEMIDI_NAMESPACE 73 | -------------------------------------------------------------------------------- /src/AppleMIDI_Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Namespace.h" 4 | 5 | BEGIN_APPLEMIDI_NAMESPACE 6 | 7 | struct DefaultSettings 8 | { 9 | static const size_t UdpTxPacketMaxSize = 24; 10 | 11 | static const size_t MaxBufferSize = 64; 12 | 13 | static const size_t MaxSessionNameLen = 24; 14 | 15 | static const uint8_t MaxNumberOfParticipants = 2; 16 | 17 | static const uint8_t MaxNumberOfComputersInDirectory = 5; 18 | 19 | // The recovery journal mechanism requires that the receiver periodically 20 | // inform the sender of the sequence number of the most recently received packet. 21 | // This allows the sender to reduce the size of the recovery journal, to encapsulate 22 | // only those changes to the MIDI stream state occurring after the specified packet number. 23 | // 24 | // Each partner then sends cyclically to the other partner the RS message, indicating 25 | // the last sequence number received correctly, in other words, without any gap between 26 | // two sequence numbers. The sender can then free the memory containing old journalling data if necessary. 27 | static const unsigned long ReceiversFeedbackThreshold = 1000; 28 | 29 | // The initiator must initiate a new sync exchange at least once every 60 seconds 30 | // as in https://developer.apple.com/library/archive/documentation/Audio/Conceptual/MIDINetworkDriverProtocol/MIDI/MIDI.html 31 | static const unsigned long CK_MaxTimeOut = 61000; 32 | 33 | // when set to true, the lower 32-bits of the rtpClock ae send 34 | // when set to false, 0 will be set for immediate playout. 35 | static const bool TimestampRtpPackets = true; 36 | 37 | static const uint8_t MaxSessionInvitesAttempts = 13; 38 | 39 | static const uint8_t MaxSynchronizationCK0Attempts = 5; 40 | 41 | static const unsigned long SynchronizationHeartBeat = 10000; 42 | }; 43 | 44 | END_APPLEMIDI_NAMESPACE 45 | -------------------------------------------------------------------------------- /examples/AVR_MinMemUsage/AVR_MinMemUsage.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ONE_PARTICIPANT 4 | #define NO_SESSION_NAME 5 | #include 6 | 7 | // Enter a MAC address for your controller below. 8 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 9 | byte mac[] = { 10 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 11 | }; 12 | 13 | unsigned long t1 = millis(); 14 | int8_t isConnected = 0; 15 | 16 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 17 | 18 | // ----------------------------------------------------------------------------- 19 | // 20 | // ----------------------------------------------------------------------------- 21 | void setup() 22 | { 23 | pinMode(LED_BUILTIN, OUTPUT); 24 | digitalWrite(LED_BUILTIN, LOW); 25 | 26 | if (Ethernet.begin(mac) == 0) for (;;); 27 | 28 | MIDI.begin(); 29 | 30 | // Stay informed on connection status 31 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char*) { 32 | isConnected++; 33 | digitalWrite(LED_BUILTIN, HIGH); 34 | }); 35 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 36 | isConnected--; 37 | digitalWrite(LED_BUILTIN, LOW); 38 | }); 39 | 40 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 41 | digitalWrite(LED_BUILTIN, LOW); 42 | }); 43 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 44 | digitalWrite(LED_BUILTIN, HIGH); 45 | }); 46 | } 47 | 48 | // ----------------------------------------------------------------------------- 49 | // 50 | // ----------------------------------------------------------------------------- 51 | void loop() 52 | { 53 | // Listen to incoming notes 54 | MIDI.read(); 55 | 56 | // send a note every second 57 | // (dont cáll delay(1000) as it will stall the pipeline) 58 | if ((isConnected > 0) && (millis() - t1) > 1000) 59 | { 60 | t1 = millis(); 61 | 62 | MIDI.sendNoteOn(54, 100, 1); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ci/build-arduino.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Exit immediately if a command exits with a non-zero status. 3 | set -e 4 | # Enable the globstar shell option 5 | shopt -s globstar 6 | # Make sure we are inside the github workspace 7 | cd $GITHUB_WORKSPACE 8 | # Create directories 9 | mkdir $HOME/Arduino 10 | mkdir $HOME/Arduino/libraries 11 | # Install Arduino IDE 12 | export PATH=$PATH:$GITHUB_WORKSPACE/bin 13 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh 14 | arduino-cli config init 15 | arduino-cli config set library.enable_unsafe_install true 16 | arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json 17 | arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 18 | arduino-cli core update-index 19 | 20 | # Install Arduino AVR core 21 | arduino-cli core install arduino:avr 22 | arduino-cli core install arduino:samd 23 | # arduino-cli core install arduino:esp8266 24 | # arduino-cli core install esp32:esp32:esp32doit-devkit-v1 25 | 26 | # Link Arduino library 27 | ln -s $GITHUB_WORKSPACE $HOME/Arduino/libraries/CI_Test_Library 28 | 29 | arduino-cli lib install Ethernet 30 | arduino-cli lib install "MIDI library" 31 | arduino-cli lib install --git-url https://github.com/sstaub/Ethernet3.git 32 | arduino-cli lib install EthernetBonjour 33 | 34 | # Compile all *.ino files for the Arduino Uno 35 | for f in **/AVR_*.ino ; do 36 | arduino-cli compile -b arduino:avr:uno $f 37 | done 38 | 39 | # Compile all *.ino files for the Arduino Zero 40 | for f in **/SAMD_*.ino ; do 41 | arduino-cli compile -b arduino:samd:mkrzero $f 42 | done 43 | 44 | # Compile all *.ino files for the ESP8266 45 | # for f in **/ESP8266_*.ino ; do 46 | # arduino-cli compile -b arduino:esp8266:??? $f 47 | # done 48 | 49 | # Compile all *.ino files for the ESP32 50 | # for f in **/ESP32_*.ino ; do 51 | # arduino-cli compile -b arduino:esp32:??? $f 52 | # done 53 | -------------------------------------------------------------------------------- /examples/ESP32_DynamicInstantiation/ESP32_dynamicClass.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ETH_Helper.h" 4 | 5 | #define SerialMon Serial 6 | #include "midiHelpers.h" 7 | 8 | 9 | #define ethernet true; // set to false to demonstrate WiFi usage 10 | 11 | char ssid[] = "ssid"; // your network SSID (name) 12 | char pass[] = "pass"; // your network password (use for WPA, or use as key for WEP) 13 | 14 | MidiClient* midiClient; // generic class offered as an alternative of the MACRO 15 | 16 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); 17 | 18 | void WIFI_startup(){ 19 | WiFi.begin(ssid, pass); 20 | while (WiFi.status() != WL_CONNECTED) { 21 | delay(500); 22 | AM_DBG(F("Establishing connection to WiFi..")); 23 | } 24 | } 25 | 26 | void setup(){ 27 | 28 | AM_DBG_SETUP(115200); 29 | 30 | bool useEth = false; 31 | 32 | if (useEth){ 33 | ETH_startup(); 34 | midiClient = new AppleMidiWithInterfaceWrapper("APPLE_MIDIETHCLIENT",DEFAULT_CONTROL_PORT); 35 | }else{ 36 | WIFI_startup(); 37 | midiClient = new AppleMidiWithInterfaceWrapper("APPLE_MIDIWIFICLIENT",DEFAULT_CONTROL_PORT); 38 | } 39 | 40 | midiClient->begin(); 41 | 42 | midiClient->setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 43 | AM_DBG(F("Connected to session"), ssrc, name); 44 | }); 45 | midiClient->setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 46 | AM_DBG(F("Disconnected"), ssrc); 47 | }); 48 | 49 | AM_DBG(F("OK, now make sure you have an rtpMIDI session that is Enabled")); 50 | AM_DBG(F("Add device named Arduino with Host"), useEth?Ethernet.localIP():WiFi.localIP(), "Port", midiClient->getPort(), "(Name", midiClient->getName(), ")"); 51 | AM_DBG(F("Select and then press the Connect button")); 52 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 53 | } 54 | 55 | void loop(){ 56 | midiClient->read(); 57 | } -------------------------------------------------------------------------------- /examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | unsigned long t1 = millis(); 13 | int8_t isConnected = 0; 14 | 15 | // Non default portnr 16 | APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "MyNamedArduino", 5200); 17 | 18 | // ----------------------------------------------------------------------------- 19 | // 20 | // ----------------------------------------------------------------------------- 21 | void setup() 22 | { 23 | AM_DBG_SETUP(115200); 24 | AM_DBG(F("Booting")); 25 | 26 | if (Ethernet.begin(mac) == 0) { 27 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 28 | for (;;); 29 | } 30 | 31 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 32 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 33 | AM_DBG(F("Select and then press the Connect button")); 34 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 35 | 36 | MIDI.begin(); 37 | 38 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 39 | isConnected++; 40 | AM_DBG(F("Connected to session"), ssrc, name); 41 | }); 42 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 43 | isConnected--; 44 | AM_DBG(F("Disconnected"), ssrc); 45 | }); 46 | 47 | AM_DBG(F("Send MIDI messages every second")); 48 | } 49 | 50 | // ----------------------------------------------------------------------------- 51 | // 52 | // ----------------------------------------------------------------------------- 53 | void loop() 54 | { 55 | // Listen to incoming notes 56 | MIDI.read(); 57 | 58 | // send a note every second 59 | // (dont cáll delay(1000) as it will stall the pipeline) 60 | if ((isConnected > 0) && (millis() - t1) > 1000) 61 | { 62 | t1 = millis(); 63 | 64 | byte note = random(1, 127); 65 | byte velocity = 55; 66 | byte channel = 1; 67 | 68 | MIDI.sendNoteOn(note, velocity, channel); 69 | MIDI.sendNoteOff(note, velocity, channel); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report something that does not work as intended 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | ## Context 20 | 21 | Using the English language, please answer a few questions to help us understand your problem better and guide you to a solution: 22 | 23 | 29 | 30 | - What board are you using ? 31 | - `example: Arduino Leonardo` 32 | - _Please list any shields or other **relevant** hardware you're using_ 33 | - What version of the Arduino IDE are you using ? 34 | - `example: 1.8.5` 35 | - What Operating System are you using for the DAW 36 | - [ ] Windows 37 | - [ ] MacOS 38 | - [ ] Linux 39 | - [ ] Other (please specify) 40 | - Is your problem related to: 41 | - [ ] Setup 42 | - [ ] Connecting 43 | - [ ] AppleMIDI <> MIDI 44 | - [ ] Dropped messages 45 | - How comfortable are you with code ? 46 | - [ ] Complete beginner 47 | - [ ] I've done basic projects 48 | - [ ] I know my way around C/C++ 49 | - [ ] Advanced / professional 50 | 51 | ## Describe your project and what you expect to happen: 52 | 53 | 58 | 59 | ## Describe your problem (what does not work): 60 | 61 | 64 | 65 | ## Steps to reproduce 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/utility/endian.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(_BYTE_ORDER) 4 | 5 | #define _BIG_ENDIAN 4321 6 | #define _LITTLE_ENDIAN 1234 7 | 8 | #define TEST_LITTLE_ENDIAN (((union { unsigned x; unsigned char c; }){1}).c) 9 | 10 | #ifdef TEST_LITTLE_ENDIAN 11 | #define _BYTE_ORDER _LITTLE_ENDIAN 12 | #else 13 | #define _BYTE_ORDER _BIG_ENDIAN 14 | #endif 15 | 16 | #undef TEST_LITTLE_ENDIAN 17 | 18 | #include 19 | 20 | #ifdef __GNUC__ 21 | #define __bswap16(_x) __builtin_bswap16(_x) 22 | #define __bswap32(_x) __builtin_bswap32(_x) 23 | #define __bswap64(_x) __builtin_bswap64(_x) 24 | #else /* __GNUC__ */ 25 | 26 | static __inline __uint16_t 27 | __bswap16(__uint16_t _x) 28 | { 29 | return ((__uint16_t)((_x >> 8) | ((_x << 8) & 0xff00))); 30 | } 31 | 32 | static __inline __uint32_t 33 | __bswap32(__uint32_t _x) 34 | { 35 | return ((__uint32_t)((_x >> 24) | ((_x >> 8) & 0xff00) | 36 | ((_x << 8) & 0xff0000) | ((_x << 24) & 0xff000000))); 37 | } 38 | 39 | static __inline __uint64_t 40 | __bswap64(__uint64_t _x) 41 | { 42 | return ((__uint64_t)((_x >> 56) | ((_x >> 40) & 0xff00) | 43 | ((_x >> 24) & 0xff0000) | ((_x >> 8) & 0xff000000) | 44 | ((_x << 8) & ((__uint64_t)0xff << 32)) | 45 | ((_x << 24) & ((__uint64_t)0xff << 40)) | 46 | ((_x << 40) & ((__uint64_t)0xff << 48)) | ((_x << 56)))); 47 | } 48 | #endif /* !__GNUC__ */ 49 | 50 | #ifndef __machine_host_to_from_network_defined 51 | #if _BYTE_ORDER == _LITTLE_ENDIAN 52 | #define __ntohs(x) __bswap16(x) 53 | #define __htons(x) __bswap16(x) 54 | #define __ntohl(x) __bswap32(x) 55 | #define __htonl(x) __bswap32(x) 56 | #define __ntohll(x) __bswap64(x) 57 | #define __htonll(x) __bswap64(x) 58 | #else // BIG_ENDIAN 59 | #define __ntohl(x) ((uint32_t)(x)) 60 | #define __ntohs(x) ((uint16_t)(x)) 61 | #define __htonl(x) ((uint32_t)(x)) 62 | #define __htons(x) ((uint16_t)(x)) 63 | #define __ntohll(x) ((uint64_t)(x)) 64 | #define __htonll(x) ((uint64_t)(x)) 65 | #endif 66 | #endif /* __machine_host_to_from_network_defined */ 67 | 68 | #endif /* _BYTE_ORDER */ 69 | 70 | #ifndef __machine_host_to_from_network_defined 71 | #if _BYTE_ORDER == _LITTLE_ENDIAN 72 | #define __ntohll(x) __bswap64(x) 73 | #define __htonll(x) __bswap64(x) 74 | #else // BIG_ENDIAN 75 | #define __ntohll(x) ((uint64_t)(x)) 76 | #define __htonll(x) ((uint64_t)(x)) 77 | #endif 78 | #endif /* __machine_host_to_from_network_defined */ 79 | -------------------------------------------------------------------------------- /examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | unsigned long t1 = millis(); 13 | int8_t isConnected = 0; 14 | 15 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 16 | 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // ----------------------------------------------------------------------------- 20 | void setup() 21 | { 22 | AM_DBG_SETUP(115200); 23 | AM_DBG(F("Booting")); 24 | 25 | if (Ethernet.begin(mac) == 0) { 26 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 27 | for (;;); 28 | } 29 | 30 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 31 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 32 | AM_DBG(F("Select and then press the Connect button")); 33 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 34 | 35 | MIDI.begin(); 36 | 37 | // Stay informed on connection status 38 | AppleMIDI 39 | .setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 40 | isConnected++; 41 | AM_DBG(F("Connected to session"), ssrc, name); 42 | }) 43 | .setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 44 | isConnected--; 45 | AM_DBG(F("Disconnected"), ssrc); 46 | }); 47 | 48 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 49 | AM_DBG(F("NoteOn"), note); 50 | }); 51 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 52 | AM_DBG(F("NoteOff"), note); 53 | }); 54 | 55 | AM_DBG(F("Sending MIDI messages every second")); 56 | } 57 | 58 | // ----------------------------------------------------------------------------- 59 | // 60 | // ----------------------------------------------------------------------------- 61 | void loop() 62 | { 63 | // Listen to incoming notes 64 | MIDI.read(); 65 | 66 | // send a note every second 67 | // (dont cáll delay(1000) as it will stall the pipeline) 68 | if ((isConnected > 0) && (millis() - t1) > 1000) 69 | { 70 | t1 = millis(); 71 | 72 | byte note = random(1, 127); 73 | byte velocity = 55; 74 | byte channel = 1; 75 | 76 | MIDI.sendNoteOn(note, velocity, channel); 77 | // MIDI.sendNoteOff(note, velocity, channel); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #include // from https://github.com/sstaub/Ethernet3 2 | 3 | #define SerialMon Serial 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | unsigned long t1 = millis(); 13 | int8_t isConnected = 0; 14 | 15 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 16 | 17 | // ----------------------------------------------------------------------------- 18 | // 19 | // ----------------------------------------------------------------------------- 20 | void setup() 21 | { 22 | AM_DBG_SETUP(115200); 23 | AM_DBG(F("Booting")); 24 | 25 | if (Ethernet.begin(mac) == 0) { 26 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 27 | for (;;); 28 | } 29 | 30 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 31 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 32 | AM_DBG(F("Select and then press the Connect button")); 33 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 34 | 35 | MIDI.begin(); 36 | 37 | // Stay informed on connection status 38 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 39 | isConnected++; 40 | AM_DBG(F("Connected to session"), ssrc, name); 41 | }); 42 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 43 | isConnected--; 44 | AM_DBG(F("Disconnected"), ssrc); 45 | }); 46 | 47 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 48 | AM_DBG(F("NoteOn"), note); 49 | }); 50 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 51 | AM_DBG(F("NoteOff"), note); 52 | }); 53 | 54 | AM_DBG(F("Sending MIDI messages every second")); 55 | } 56 | 57 | // ----------------------------------------------------------------------------- 58 | // 59 | // ----------------------------------------------------------------------------- 60 | void loop() 61 | { 62 | // Listen to incoming notes 63 | MIDI.read(); 64 | 65 | // send a note every second 66 | // (dont cáll delay(1000) as it will stall the pipeline) 67 | if ((isConnected > 0) && (millis() - t1) > 1000) 68 | { 69 | t1 = millis(); 70 | 71 | byte note = random(1, 127); 72 | byte velocity = 55; 73 | byte channel = 1; 74 | 75 | MIDI.sendNoteOn(note, velocity, channel); 76 | // MIDI.sendNoteOff(note, velocity, channel); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define SerialMon Serial 6 | #include 7 | 8 | char ssid[] = "ssid"; // your network SSID (name) 9 | char pass[] = "password"; // your network password (use for WPA, or use as key for WEP) 10 | 11 | unsigned long t0 = millis(); 12 | int8_t isConnected = 0; 13 | 14 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 15 | 16 | // ----------------------------------------------------------------------------- 17 | // 18 | // ----------------------------------------------------------------------------- 19 | void setup() 20 | { 21 | AM_DBG_SETUP(115200); 22 | AM_DBG(F("Booting")); 23 | 24 | WiFi.begin(ssid, pass); 25 | 26 | while (WiFi.status() != WL_CONNECTED) { 27 | delay(500); 28 | AM_DBG(F("Establishing connection to WiFi..")); 29 | } 30 | AM_DBG(F("Connected to network")); 31 | 32 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 33 | AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 34 | AM_DBG(F("Select and then press the Connect button")); 35 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 36 | AM_DBG(F("Listen to incoming MIDI commands")); 37 | 38 | MIDI.begin(); 39 | 40 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 41 | isConnected++; 42 | AM_DBG(F("Connected to session"), ssrc, name); 43 | }); 44 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 45 | isConnected--; 46 | AM_DBG(F("Disconnected"), ssrc); 47 | }); 48 | 49 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 50 | AM_DBG(F("NoteOn"), note); 51 | }); 52 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 53 | AM_DBG(F("NoteOff"), note); 54 | }); 55 | 56 | AM_DBG(F("Sending NoteOn/Off of note 45, every second")); 57 | } 58 | 59 | // ----------------------------------------------------------------------------- 60 | // 61 | // ----------------------------------------------------------------------------- 62 | void loop() 63 | { 64 | // Listen to incoming notes 65 | MIDI.read(); 66 | 67 | // send a note every second 68 | // (dont cáll delay(1000) as it will stall the pipeline) 69 | if ((isConnected > 0) && (millis() - t0) > 1000) 70 | { 71 | t0 = millis(); 72 | 73 | byte note = 45; 74 | byte velocity = 55; 75 | byte channel = 1; 76 | 77 | MIDI.sendNoteOn(note, velocity, channel); 78 | MIDI.sendNoteOff(note, velocity, channel); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define SerialMon Serial 6 | #include 7 | 8 | char ssid[] = "ssid"; // your network SSID (name) 9 | char pass[] = "password"; // your network password (use for WPA, or use as key for WEP) 10 | 11 | unsigned long t0 = millis(); 12 | int8_t isConnected = 0; 13 | 14 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 15 | 16 | // ----------------------------------------------------------------------------- 17 | // 18 | // ----------------------------------------------------------------------------- 19 | void setup() 20 | { 21 | AM_DBG_SETUP(115200); 22 | AM_DBG(F("Booting")); 23 | 24 | WiFi.begin(ssid, pass); 25 | 26 | while (WiFi.status() != WL_CONNECTED) { 27 | delay(500); 28 | AM_DBG(F("Establishing connection to WiFi..")); 29 | } 30 | AM_DBG(F("Connected to network")); 31 | 32 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 33 | AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 34 | AM_DBG(F("Select and then press the Connect button")); 35 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 36 | AM_DBG(F("Listen to incoming MIDI commands")); 37 | 38 | MIDI.begin(); 39 | 40 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 41 | isConnected++; 42 | AM_DBG(F("Connected to session"), ssrc, name); 43 | }); 44 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 45 | isConnected--; 46 | AM_DBG(F("Disconnected"), ssrc); 47 | }); 48 | 49 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 50 | AM_DBG(F("NoteOn"), note); 51 | }); 52 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 53 | AM_DBG(F("NoteOff"), note); 54 | }); 55 | 56 | AM_DBG(F("Sending NoteOn/Off of note 45, every second")); 57 | } 58 | 59 | // ----------------------------------------------------------------------------- 60 | // 61 | // ----------------------------------------------------------------------------- 62 | void loop() 63 | { 64 | // Listen to incoming notes 65 | MIDI.read(); 66 | 67 | // send a note every second 68 | // (dont cáll delay(1000) as it will stall the pipeline) 69 | if ((isConnected > 0) && (millis() - t0) > 1000) 70 | { 71 | t0 = millis(); 72 | 73 | byte note = 45; 74 | byte velocity = 55; 75 | byte channel = 1; 76 | 77 | MIDI.sendNoteOn(note, velocity, channel); 78 | MIDI.sendNoteOff(note, velocity, channel); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /examples/AVR_Initiator/AVR_Initiator.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #define APPLEMIDI_INITIATOR 5 | #include 6 | 7 | // Enter a MAC address for your controller below. 8 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 9 | byte mac[] = { 10 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 11 | }; 12 | 13 | unsigned long t1 = millis(); 14 | int8_t isConnected = 0; 15 | 16 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 17 | 18 | // ----------------------------------------------------------------------------- 19 | // 20 | // ----------------------------------------------------------------------------- 21 | void setup() 22 | { 23 | AM_DBG_SETUP(115200); 24 | AM_DBG(F("Booting")); 25 | 26 | if (Ethernet.begin(mac) == 0) { 27 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 28 | for (;;); 29 | } 30 | 31 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 32 | 33 | MIDI.begin(); 34 | 35 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 36 | isConnected++; 37 | AM_DBG(F("Connected to session"), ssrc, name); 38 | }); 39 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 40 | isConnected--; 41 | AM_DBG(F("Disconnected"), ssrc); 42 | }); 43 | 44 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 45 | AM_DBG(F("NoteOn"), note); 46 | }); 47 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 48 | AM_DBG(F("NoteOff"), note); 49 | }); 50 | 51 | // Initiate the session 52 | IPAddress remote(192, 168, 1, 65); 53 | AppleMIDI.sendInvite(remote, DEFAULT_CONTROL_PORT); // port is 5004 by default 54 | 55 | AM_DBG(F("Connecting to "), remote, "Port", DEFAULT_CONTROL_PORT, "(Name", AppleMIDI.getName(), ")"); 56 | AM_DBG(F("Watch as this session is added to the Participants list")); 57 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 58 | 59 | AM_DBG(F("Sending a random NoteOn/Off every second")); 60 | } 61 | 62 | // ----------------------------------------------------------------------------- 63 | // 64 | // ----------------------------------------------------------------------------- 65 | void loop() 66 | { 67 | // Listen to incoming notes 68 | MIDI.read(); 69 | 70 | // send note on/off every second 71 | // (dont cáll delay(1000) as it will stall the pipeline) 72 | if ((isConnected > 0) && (millis() - t1) > 1000) 73 | { 74 | t1 = millis(); 75 | 76 | byte note = random(1, 127); 77 | byte velocity = 55; 78 | byte channel = 1; 79 | 80 | MIDI.sendNoteOn(note, velocity, channel); 81 | MIDI.sendNoteOff(note, velocity, channel); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/ESP32_DynamicInstantiation/midiHelpers.h: -------------------------------------------------------------------------------- 1 | #ifndef MIDIHELPERS_h 2 | #define MIDIHELPERS_h 3 | 4 | #define USE_EXT_CALLBACKS // as from example => required for MIDI callbacks 5 | #include //https://github.com/lathoub/Arduino-AppleMIDI-Library 6 | 7 | using namespace APPLEMIDI_NAMESPACE; 8 | 9 | /* 10 | class Utilities for creating a generic pointer to AppleMIDISession and MidiInterface 11 | from a WiFiUDP or EThernetUDP (template) and be able to manipulate it as a generic instance 12 | and manipulating the pointer generically 13 | */ 14 | 15 | class MidiClient { 16 | public: 17 | virtual void read() = 0; 18 | virtual ~MidiClient() {} 19 | virtual void setHandleConnected(void (*fptr)(const ssrc_t &, const char *))= 0; 20 | virtual void setHandleDisconnected(void (*fptr)(const ssrc_t &)) = 0; 21 | virtual void setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value))=0; 22 | virtual const char *getName() = 0; 23 | virtual const uint16_t getPort() = 0; 24 | virtual void begin() = 0; 25 | // all methods you need to be wrapped below 26 | virtual void sendNoteOn(byte note, byte velocity, byte channel) = 0; 27 | virtual void sendNoteOff(byte note, byte velocity, byte channel) = 0; 28 | // etc... 29 | 30 | }; 31 | 32 | template 33 | class AppleMidiWithInterfaceWrapper : public MidiClient { 34 | public: 35 | AppleMIDISession* session; 36 | MidiInterface, AppleMIDISettings>* midi; 37 | 38 | AppleMidiWithInterfaceWrapper(const char* sessionName, uint16_t port) { 39 | session = new AppleMIDISession(sessionName, port); 40 | midi = new MidiInterface, AppleMIDISettings>(*session); 41 | } 42 | 43 | virtual void begin(){ 44 | session->begin(); 45 | } 46 | 47 | virtual const char *getName(){ 48 | return session->getName(); 49 | } 50 | 51 | virtual const uint16_t getPort(){ 52 | return session->getPort(); 53 | } 54 | 55 | virtual void setHandleConnected(void (*fptr)(const ssrc_t &, const char *)){ 56 | session->setHandleConnected(fptr); 57 | } 58 | 59 | virtual void setHandleDisconnected(void (*fptr)(const ssrc_t &)){ 60 | session->setHandleDisconnected(fptr); 61 | } 62 | 63 | virtual void setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value)){ 64 | session->setHandleException(fptr); 65 | } 66 | 67 | 68 | void read() override { 69 | midi->read(); 70 | } 71 | 72 | void sendNoteOn(byte note, byte velocity, byte channel) override { 73 | midi->sendNoteOn(note, velocity, channel); 74 | } 75 | 76 | void sendNoteOff(byte note, byte velocity, byte channel) override { 77 | midi->sendNoteOff(note, velocity, channel); 78 | } 79 | 80 | 81 | ~AppleMidiWithInterfaceWrapper() { 82 | delete midi; 83 | delete session; 84 | } 85 | }; 86 | 87 | #endif -------------------------------------------------------------------------------- /examples/AVR_Directory/AVR_Directory.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #define USE_DIRECTORY 5 | #include 6 | 7 | // Enter a MAC address for your controller below. 8 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 9 | byte mac[] = { 10 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 11 | }; 12 | 13 | unsigned long t1 = millis(); 14 | int8_t isConnected = 0; 15 | 16 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 17 | 18 | // ----------------------------------------------------------------------------- 19 | // 20 | // ----------------------------------------------------------------------------- 21 | void setup() 22 | { 23 | AM_DBG_SETUP(115200); 24 | AM_DBG(F("Booting")); 25 | 26 | if (Ethernet.begin(mac) == 0) { 27 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 28 | for (;;); 29 | } 30 | 31 | AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 63)); 32 | AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 66)); 33 | // AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::None; 34 | AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::OnlyComputersInMyDirectory; 35 | // AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::Anyone; 36 | 37 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 38 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 39 | AM_DBG(F("Select and then press the Connect button")); 40 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 41 | 42 | MIDI.begin(); 43 | 44 | // Stay informed on connection status 45 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 46 | isConnected++; 47 | AM_DBG(F("Connected to session"), ssrc, name); 48 | }); 49 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 50 | isConnected--; 51 | AM_DBG(F("Disconnected"), ssrc); 52 | }); 53 | 54 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 55 | AM_DBG(F("NoteOn"), note); 56 | }); 57 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 58 | AM_DBG(F("NoteOff"), note); 59 | }); 60 | 61 | AM_DBG(F("Sending MIDI messages every second")); 62 | } 63 | 64 | // ----------------------------------------------------------------------------- 65 | // 66 | // ----------------------------------------------------------------------------- 67 | void loop() 68 | { 69 | // Listen to incoming notes 70 | MIDI.read(); 71 | 72 | // send a note every second 73 | // (dont cáll delay(1000) as it will stall the pipeline) 74 | if ((isConnected > 0) && (millis() - t1) > 1000) 75 | { 76 | t1 = millis(); 77 | 78 | byte note = random(1, 127); 79 | byte velocity = 55; 80 | byte channel = 1; 81 | 82 | MIDI.sendNoteOn(note, velocity, channel); 83 | // MIDI.sendNoteOff(note, velocity, channel); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/Arduino.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "IPAddress.h" 7 | 8 | #define HEX 0 9 | #define DEC 1 10 | 11 | class _serial 12 | { 13 | public: 14 | void print(const char a[]) { std::cout << a; }; 15 | void print(char a) { std::cout << a; }; 16 | void print(unsigned char a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << (int)a; }; 17 | void print(int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; 18 | void print(unsigned int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; 19 | void print(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; 20 | void print(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; 21 | void print(double a, int = 2) { std::cout << a; }; 22 | void print(struct tm * timeinfo, const char * format = nullptr) {}; 23 | void print(IPAddress) {}; 24 | 25 | void println(const char a[]) { std::cout << a << "\n"; }; 26 | void println(char a) { std::cout << a << "\n"; }; 27 | void println(unsigned char a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << (int)a << "\n"; }; 28 | void println(int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; 29 | void println(unsigned int a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; 30 | void println(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; 31 | void println(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; 32 | void println(double a, int format = 2) { std::cout << a << "\n"; }; 33 | void println(struct tm * timeinfo, const char * format = nullptr) {}; 34 | void println(IPAddress) {}; 35 | void println(void) { std::cout << "\n"; }; 36 | }; 37 | 38 | _serial Serial; 39 | 40 | #include 41 | typedef uint8_t byte; 42 | 43 | void begin(); 44 | void loop(); 45 | 46 | int main() 47 | { 48 | begin(); 49 | 50 | while (true) 51 | { 52 | loop(); 53 | } 54 | } 55 | 56 | // avoid strncpy security warning 57 | #pragma warning(disable:4996) 58 | 59 | #define __attribute__(A) /* do nothing */ 60 | 61 | #include "../src/utility/Deque.h" 62 | 63 | #include 64 | 65 | float analogRead(int pin) 66 | { 67 | return 0.0f; 68 | } 69 | 70 | void randomSeed(float) 71 | { 72 | srand(static_cast(time(0))); 73 | } 74 | 75 | unsigned long millis() 76 | { 77 | auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 78 | return (unsigned long)now; 79 | } 80 | 81 | int random(int min, int max) 82 | { 83 | return RAND_MAX % std::rand() % (max-min) + min; 84 | } 85 | 86 | template const T& min(const T& a, const T& b) { 87 | return !(b < a) ? a : b; // or: return !comp(b,a)?a:b; for version (2) 88 | } 89 | 90 | #define F(x) x 91 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/xcshareddata/xcschemes/rtpMidi.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/ESP32_W5500_Callbacks/ETH_Helper.h: -------------------------------------------------------------------------------- 1 | #ifdef ETHERNET3 2 | #include 3 | #else 4 | #include 5 | #include // https://github.com/TrippyLighting/EthernetBonjour 6 | #endif 7 | 8 | // to get the Mac address 9 | #include 10 | 11 | #define RESET_PIN 26 12 | #define CS_PIN 5 13 | 14 | // Enter a MAC address for your controller below. 15 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 16 | byte mac[] = { 17 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 18 | }; 19 | 20 | /* 21 | Wiz W5500 reset function. Change this for the specific reset 22 | sequence required for your particular board or module. 23 | */ 24 | void hardreset() { 25 | pinMode(RESET_PIN, OUTPUT); 26 | digitalWrite(RESET_PIN, HIGH); 27 | delay(150); 28 | 29 | digitalWrite(RESET_PIN, LOW); 30 | delay(500); 31 | digitalWrite(RESET_PIN, HIGH); 32 | delay(150); 33 | } 34 | 35 | bool ETH_startup() 36 | { 37 | #ifdef ETHERNET3 38 | Ethernet.setRstPin(RESET_PIN); 39 | Ethernet.setCsPin(CS_PIN); 40 | Ethernet.init(4); // maxSockNum = 4 Socket 0...3 -> RX/TX Buffer 4k 41 | Serial.println("Resetting Wiz W5500 Ethernet Board... "); 42 | Ethernet.hardreset(); 43 | #else 44 | Ethernet.init(CS_PIN); 45 | Serial.println("Resetting Wiz Ethernet Board... "); 46 | hardreset(); 47 | #endif 48 | 49 | esp_read_mac(mac, ESP_MAC_WIFI_STA); 50 | 51 | /* 52 | Network configuration - all except the MAC are optional. 53 | 54 | IMPORTANT NOTE - The mass-produced W5500 boards do -not- 55 | have a built-in MAC address (depite 56 | comments to the contrary elsewhere). You 57 | -must- supply a MAC address here. 58 | */ 59 | #ifdef ETHERNET3 60 | Serial.println("Starting Ethernet3 connection..."); 61 | #else 62 | Serial.println("Starting Ethernet connection..."); 63 | #endif 64 | 65 | Ethernet.begin(mac); 66 | Serial.print("Ethernet IP is: "); 67 | Serial.println(Ethernet.localIP()); 68 | 69 | /* 70 | Sanity checks for W5500 and cable connection. 71 | */ 72 | Serial.println("Checking connection."); 73 | bool rdy_flag = false; 74 | for (uint8_t i = 0; i <= 20; i++) { 75 | #ifdef ETHERNET3 76 | if ((Ethernet.link() == 0)) { 77 | #else 78 | if ((Ethernet.linkStatus() == Unknown)) { 79 | #endif 80 | Serial.print("."); 81 | rdy_flag = false; 82 | delay(80); 83 | } else { 84 | rdy_flag = true; 85 | break; 86 | } 87 | } 88 | if (rdy_flag == false) { 89 | Serial.println("\n\r\tHardware fault, or cable problem... cannot continue."); 90 | while (true) { 91 | delay(10); // Halt. 92 | } 93 | } else { 94 | Serial.println("OK"); 95 | } 96 | 97 | #ifndef ETHERNET3 98 | // Initialize the Bonjour/MDNS library. You can now reach or ping this 99 | // Arduino via the host name "arduino.local", provided that your operating 100 | // system is Bonjour-enabled (such as MacOS X). 101 | // Always call this before any other method! 102 | EthernetBonjour.begin("arduino"); 103 | 104 | EthernetBonjour.addServiceRecord("Arduino._apple-midi", 105 | 5004, 106 | MDNSServiceUDP); 107 | #endif 108 | 109 | return true; 110 | } 111 | -------------------------------------------------------------------------------- /examples/ESP8266_NoteOnOffEverySec_softAP_mDNS/ESP8266_NoteOnOffEverySec_softAP_mDNS.ino: -------------------------------------------------------------------------------- 1 | // Example to start ESP8266 in soft access point / hotspot mode 2 | // and also enable mDNS response on local network. This allows 3 | // client to discover the AppleMIDI service and connect to it 4 | // without having to type the IP address and port 5 | // Tested on iOS 9 (old iPad) and iOS 13 (iPhone 6) 6 | // On Win10 (rtpMIDI), ESP8266 did not show in directory, 7 | // but connects fine with default IP(192.168.4.1)/port(5004) 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define SerialMon Serial 15 | #include 16 | 17 | char ssid[] = "ssid"; // your network SSID (name) 18 | char pass[] = "password"; // your network password (use for WPA, or use as key for WEP) 19 | 20 | unsigned long t0 = millis(); 21 | int8_t isConnected = 0; 22 | 23 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 24 | 25 | // ----------------------------------------------------------------------------- 26 | // 27 | // ----------------------------------------------------------------------------- 28 | void setup() 29 | { 30 | AM_DBG_SETUP(115200); 31 | AM_DBG(F("Booting")); 32 | 33 | WiFi.softAP(ssid, pass); 34 | 35 | AM_DBG(F("Started soft access point:"), WiFi.softAPIP(), "Port", AppleMIDI.getPort()); 36 | AM_DBG(F("AppleMIDI device name:"), AppleMIDI.getName()); 37 | // Set up mDNS responder: 38 | if (!MDNS.begin(AppleMIDI.getName())) 39 | AM_DBG(F("Error setting up MDNS responder!")); 40 | char str[128] = ""; 41 | strcat(str, AppleMIDI.getName()); 42 | strcat(str,".local"); 43 | AM_DBG(F("mDNS responder started at:"), str); 44 | MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); 45 | AM_DBG(F("Open Wifi settings and connect to soft acess point using 'ssid'")); 46 | AM_DBG(F("Start MIDI Network app on iPhone/iPad or rtpMIDI on Windows")); 47 | AM_DBG(F("AppleMIDI-ESP8266 will show in the 'Directory' list (rtpMIDI) or")); 48 | AM_DBG(F("under 'Found on the network' list (iOS). Select and click 'Connect'")); 49 | 50 | MIDI.begin(); 51 | 52 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 53 | isConnected++; 54 | AM_DBG(F("Connected to session"), ssrc, name); 55 | }); 56 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 57 | isConnected--; 58 | AM_DBG(F("Disconnected"), ssrc); 59 | }); 60 | 61 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 62 | AM_DBG(F("NoteOn"), note); 63 | }); 64 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 65 | AM_DBG(F("NoteOff"), note); 66 | }); 67 | 68 | AM_DBG(F("Sending NoteOn/Off of note 45, every second")); 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | // 73 | // ----------------------------------------------------------------------------- 74 | void loop() 75 | { 76 | 77 | MDNS.update(); 78 | 79 | // Listen to incoming notes 80 | MIDI.read(); 81 | 82 | // send a note every second 83 | // (dont cáll delay(1000) as it will stall the pipeline) 84 | if ((isConnected > 0) && (millis() - t0) > 1000) 85 | { 86 | t0 = millis(); 87 | 88 | byte note = 45; 89 | byte velocity = 55; 90 | byte channel = 1; 91 | 92 | MIDI.sendNoteOn(note, velocity, channel); 93 | MIDI.sendNoteOff(note, velocity, channel); 94 | } 95 | } -------------------------------------------------------------------------------- /examples/ESP32_DynamicInstantiation/ETH_helper.h: -------------------------------------------------------------------------------- 1 | #ifdef ETHERNET3 2 | #include 3 | #else 4 | #include 5 | #include // https://github.com/TrippyLighting/EthernetBonjour 6 | #endif 7 | 8 | // to get the Mac address 9 | #include 10 | 11 | #define RESET_PIN D7 12 | #define CS_PIN D5 13 | 14 | // Enter a MAC address for your controller below. 15 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 16 | byte mac[] = { 17 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 18 | }; 19 | 20 | /* 21 | Wiz W5500 reset function. Change this for the specific reset 22 | sequence required for your particular board or module. 23 | */ 24 | void hardreset() { 25 | pinMode(RESET_PIN, OUTPUT); 26 | digitalWrite(RESET_PIN, HIGH); 27 | delay(150); 28 | 29 | digitalWrite(RESET_PIN, LOW); 30 | delay(500); 31 | digitalWrite(RESET_PIN, HIGH); 32 | delay(150); 33 | } 34 | 35 | bool ETH_startup() 36 | { 37 | #ifdef ETHERNET3 38 | Ethernet.setRstPin(RESET_PIN); 39 | Ethernet.setCsPin(CS_PIN); 40 | Ethernet.init(4); // maxSockNum = 4 Socket 0...3 -> RX/TX Buffer 4k 41 | Serial.println("Resetting Wiz W5500 Ethernet Board... "); 42 | Ethernet.hardreset(); 43 | #else 44 | Ethernet.init(CS_PIN); 45 | Serial.println("Resetting Wiz Ethernet Board... "); 46 | hardreset(); 47 | #endif 48 | 49 | esp_read_mac(mac, ESP_MAC_WIFI_STA); 50 | 51 | /* 52 | Network configuration - all except the MAC are optional. 53 | 54 | IMPORTANT NOTE - The mass-produced W5500 boards do -not- 55 | have a built-in MAC address (depite 56 | comments to the contrary elsewhere). You 57 | -must- supply a MAC address here. 58 | */ 59 | #ifdef ETHERNET3 60 | Serial.println("Starting Ethernet3 connection..."); 61 | #else 62 | Serial.println("Starting Ethernet connection..."); 63 | //if (Ethernet.hardwareStatus() == EthernetNoHardware) Serial.println("No EthernetHW"); 64 | #endif 65 | Ethernet.begin(mac); 66 | Serial.print("Ethernet IP is: "); 67 | Serial.println(Ethernet.localIP()); 68 | 69 | /* 70 | Sanity checks for W5500 and cable connection. 71 | */ 72 | Serial.println("Checking connection."); 73 | bool rdy_flag = false; 74 | for (uint8_t i = 0; i <= 20; i++) { 75 | #ifdef ETHERNET3 76 | if ((Ethernet.link() == 0)) { 77 | #else 78 | if ((Ethernet.linkStatus() == Unknown)) { 79 | #endif 80 | Serial.print("."); 81 | rdy_flag = false; 82 | delay(80); 83 | } else { 84 | rdy_flag = true; 85 | break; 86 | } 87 | } 88 | if (rdy_flag == false) { 89 | Serial.println("\n\r\tHardware fault, or cable problem... cannot continue."); 90 | while (true) { 91 | delay(10); // Halt. 92 | } 93 | } else { 94 | Serial.println("OK"); 95 | } 96 | 97 | #ifndef ETHERNET3 98 | // Initialize the Bonjour/MDNS library. You can now reach or ping this 99 | // Arduino via the host name "arduino.local", provided that your operating 100 | // system is Bonjour-enabled (such as MacOS X). 101 | // Always call this before any other method! 102 | EthernetBonjour.begin("arduino"); 103 | 104 | EthernetBonjour.addServiceRecord("apple-midi", //Arduino._apple-midi doesnt work 105 | 5004, 106 | MDNSServiceUDP); 107 | #endif 108 | 109 | return true; 110 | } -------------------------------------------------------------------------------- /examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #define USE_EXT_CALLBACKS 5 | #include 6 | 7 | // Enter a MAC address for your controller below. 8 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 9 | byte mac[] = { 10 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 11 | }; 12 | 13 | unsigned long t1 = millis(); 14 | int8_t isConnected = 0; 15 | 16 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 17 | 18 | void OnAppleMidiStartReceived(const APPLEMIDI_NAMESPACE::ssrc_t&); 19 | void OnAppleMidiReceivedByte(const APPLEMIDI_NAMESPACE::ssrc_t&, byte); 20 | void OnAppleMidiEndReceive(const APPLEMIDI_NAMESPACE::ssrc_t&); 21 | 22 | // ----------------------------------------------------------------------------- 23 | // 24 | // ----------------------------------------------------------------------------- 25 | void setup() 26 | { 27 | AM_DBG_SETUP(115200); 28 | AM_DBG(F("Booting")); 29 | 30 | if (Ethernet.begin(mac) == 0) { 31 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 32 | for (;;); 33 | } 34 | 35 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 36 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 37 | AM_DBG(F("Select and then press the Connect button")); 38 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 39 | 40 | MIDI.begin(); 41 | 42 | // check: zien we de connecttion binnenkomen?? Anders terug een ref van maken 43 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 44 | isConnected++; 45 | AM_DBG(F("Connected to session"), ssrc, name); 46 | }); 47 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 48 | isConnected--; 49 | AM_DBG(F("Disconnected"), ssrc); 50 | }); 51 | AppleMIDI.setHandleStartReceivedMidi(OnAppleMidiStartReceived); 52 | AppleMIDI.setHandleReceivedMidi(OnAppleMidiReceivedByte); 53 | AppleMIDI.setHandleEndReceivedMidi(OnAppleMidiEndReceive); 54 | 55 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 56 | AM_DBG(F("NoteOn"), note); 57 | }); 58 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 59 | AM_DBG(F("NoteOff"), note); 60 | }); 61 | } 62 | 63 | // ----------------------------------------------------------------------------- 64 | // 65 | // ----------------------------------------------------------------------------- 66 | void loop() 67 | { 68 | // Listen to incoming notes 69 | MIDI.read(); 70 | } 71 | 72 | // ==================================================================================== 73 | // Event handlers for incoming MIDI messages 74 | // ==================================================================================== 75 | 76 | // ----------------------------------------------------------------------------- 77 | // 78 | // ----------------------------------------------------------------------------- 79 | void OnAppleMidiStartReceived(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 80 | AM_DBG(F("Start receiving"), ssrc); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | // 85 | // ----------------------------------------------------------------------------- 86 | void OnAppleMidiReceivedByte(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, byte data) { 87 | SerialMon.println(data, HEX); 88 | } 89 | 90 | // ----------------------------------------------------------------------------- 91 | // 92 | // ----------------------------------------------------------------------------- 93 | void OnAppleMidiEndReceive(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 94 | AM_DBG(F("End receiving"), ssrc); 95 | } 96 | -------------------------------------------------------------------------------- /examples/AVR_MultipleSessions/AVR_MultipleSessions.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | unsigned long t1 = millis(); 13 | int8_t isConnected = 0; 14 | 15 | APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI1, "Arduino1", DEFAULT_CONTROL_PORT); 16 | APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI2, "Arduino2", DEFAULT_CONTROL_PORT + 2); 17 | 18 | void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t&, const char*); 19 | void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t &); 20 | void OnMidiNoteOn(byte channel, byte note, byte velocity); 21 | 22 | // ----------------------------------------------------------------------------- 23 | // 24 | // ----------------------------------------------------------------------------- 25 | void setup() 26 | { 27 | AM_DBG_SETUP(115200); 28 | AM_DBG(F("Booting")); 29 | 30 | if (Ethernet.begin(mac) == 0) { 31 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 32 | for (;;); 33 | } 34 | 35 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 36 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI1.getPort(), "(Name", AppleMIDI1.getName(), ")"); 37 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI2.getPort(), "(Name", AppleMIDI2.getName(), ")"); 38 | AM_DBG(F("Select and then press the Connect button")); 39 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 40 | 41 | // Listen for MIDI messages on channel 1 42 | MIDI1.begin(1); 43 | MIDI2.begin(2); 44 | 45 | // Stay informed on connection status 46 | AppleMIDI1.setHandleConnected(OnAppleMidiConnected); 47 | AppleMIDI1.setHandleDisconnected(OnAppleMidiDisconnected); 48 | AppleMIDI2.setHandleConnected(OnAppleMidiConnected); 49 | AppleMIDI2.setHandleDisconnected(OnAppleMidiDisconnected); 50 | 51 | // and let us know ehen notes come in 52 | MIDI1.setHandleNoteOn(OnMidiNoteOn); 53 | MIDI2.setHandleNoteOn(OnMidiNoteOn); 54 | 55 | AM_DBG(F("Every second, send a random NoteOn/Off, from multiple sessions")); 56 | } 57 | 58 | // ----------------------------------------------------------------------------- 59 | // 60 | // ----------------------------------------------------------------------------- 61 | void loop() 62 | { 63 | // Listen to incoming notes 64 | MIDI1.read(); 65 | MIDI2.read(); 66 | 67 | // send note on/off every second 68 | // (dont cáll delay(1000) as it will stall the pipeline) 69 | if ((isConnected > 0) && (millis() - t1) > 1000) 70 | { 71 | t1 = millis(); 72 | 73 | byte note = random(1, 127); 74 | byte velocity = 55; 75 | 76 | MIDI1.sendNoteOn(note, velocity, 1); 77 | MIDI2.sendNoteOn(note, velocity, 2); 78 | } 79 | } 80 | 81 | // ==================================================================================== 82 | // Event handlers for incoming MIDI messages 83 | // ==================================================================================== 84 | 85 | // ----------------------------------------------------------------------------- 86 | // rtpMIDI session. Device connected 87 | // ----------------------------------------------------------------------------- 88 | void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 89 | isConnected++; 90 | AM_DBG(F("Connected to session"), ssrc, name); 91 | } 92 | 93 | // ----------------------------------------------------------------------------- 94 | // rtpMIDI session. Device disconnected 95 | // ----------------------------------------------------------------------------- 96 | void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 97 | isConnected--; 98 | AM_DBG(F("Disconnected"), ssrc); 99 | } 100 | 101 | // ----------------------------------------------------------------------------- 102 | // 103 | // ----------------------------------------------------------------------------- 104 | static void OnMidiNoteOn(byte channel, byte note, byte velocity) { 105 | AM_DBG(F("in\tNote on"), note, " Velocity", velocity, "\t", channel); 106 | } 107 | -------------------------------------------------------------------------------- /examples/AVR_SysEx/AVR_SysEx.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #include 5 | 6 | // Enter a MAC address for your controller below. 7 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 8 | byte mac[] = { 9 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 10 | }; 11 | 12 | unsigned long t1 = millis(); 13 | int8_t isConnected = 0; 14 | 15 | byte sysex14[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0xF7 }; 16 | byte sysex15[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0x4D, 0xF7 }; 17 | byte sysex16[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x32, 0x50, 0x4D, 0xF7 }; 18 | byte sysexBig[] = { 0xF0, 0x41, 19 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 20 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 21 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 22 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 23 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 24 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 25 | 0x7a, 26 | 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 27 | 0xF7 28 | }; 29 | 30 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 31 | 32 | // ----------------------------------------------------------------------------- 33 | // 34 | // ----------------------------------------------------------------------------- 35 | void setup() 36 | { 37 | AM_DBG_SETUP(115200); 38 | AM_DBG(F("Booting")); 39 | 40 | if (Ethernet.begin(mac) == 0) { 41 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 42 | for (;;); 43 | } 44 | 45 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 46 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 47 | AM_DBG(F("Select and then press the Connect button")); 48 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 49 | 50 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 51 | isConnected++; 52 | AM_DBG(F("Connected to session"), ssrc, name); 53 | }); 54 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 55 | isConnected--; 56 | AM_DBG(F("Disconnected"), ssrc); 57 | }); 58 | 59 | MIDI.begin(); 60 | MIDI.setHandleSystemExclusive(OnMidiSysEx); 61 | 62 | AM_DBG(F("Send SysEx every second")); 63 | } 64 | 65 | // ----------------------------------------------------------------------------- 66 | // 67 | // ----------------------------------------------------------------------------- 68 | void loop() 69 | { 70 | // Listen to incoming notes 71 | MIDI.read(); 72 | 73 | // send a note every second 74 | // (dont cáll delay(1000) as it will stall the pipeline) 75 | if ((isConnected > 0) && (millis() - t1) > 1000) 76 | { 77 | // MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); 78 | t1 = millis(); 79 | } 80 | } 81 | 82 | // ==================================================================================== 83 | // Event handlers for incoming MIDI messages 84 | // ==================================================================================== 85 | 86 | void OnMidiSysEx(byte* data, unsigned length) { 87 | SerialMon.print(F("SYSEX: (")); 88 | SerialMon.print(getSysExStatus(data, length)); 89 | SerialMon.print(F(", ")); 90 | SerialMon.print(length); 91 | SerialMon.print(F(" bytes) ")); 92 | for (uint16_t i = 0; i < length; i++) 93 | { 94 | SerialMon.print(data[i], HEX); 95 | SerialMon.print(" "); 96 | } 97 | SerialMon.println(); 98 | } 99 | 100 | char getSysExStatus(const byte* data, uint16_t length) 101 | { 102 | if (data[0] == 0xF0 && data[length - 1] == 0xF7) 103 | return 'F'; // Full SysEx Command 104 | else if (data[0] == 0xF0 && data[length - 1] != 0xF7) 105 | return 'S'; // Start of SysEx-Segment 106 | else if (data[0] != 0xF0 && data[length - 1] != 0xF7) 107 | return 'M'; // Middle of SysEx-Segment 108 | else 109 | return 'E'; // End of SysEx-Segment 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppleMIDI (aka rtpMIDI) for Arduino [![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg)](http://creativecommons.org/licenses/by-sa/4.0/) 2 | 3 | Enables an Arduino with IP/UDP capabilities (Ethernet shield, ESP8266, ESP32, ...) to participate in an AppleMIDI session. 4 | 5 | **Important:** Please read the [note below](https://github.com/lathoub/Arduino-AppleMIDI-Library#ethernet-buffer-size) on enlarging the standard Ethernet library buffersize to avoid dropping MIDI messages! 6 | 7 | ## Features 8 | * Build on top of the popular [FortySevenEffects MIDI library](https://github.com/FortySevenEffects/arduino_midi_library) 9 | * Tested with AppleMIDI on Mac OS (Big Sur) and using [rtpMIDI](https://www.tobias-erichsen.de/software/rtpmidi.html) from Tobias Erichsen on Windows 10 10 | * Send and receive all MIDI messages 11 | * Uses callbacks to receive MIDI commands (no need for polling) 12 | * Automatic instantiation of AppleMIDI object (see at the end of 'AppleMidi.h') 13 | * Compiles on Arduino, MacOS (XCode) and Windows (MSVS) 14 | 15 | ### New in 3.2.0 16 | * Event chaining 17 | 18 | ### New in 3.3.0 19 | * Better parsing of large incoming MIDI messages with a small internal Arduino buffer 20 | 21 | ## Installation 22 | From the Arduino IDE Library Manager, search for AppleMIDI 23 | 24 | Installation 25 | 26 | This will also install [FortySevenEffects MIDI library](https://github.com/FortySevenEffects/arduino_midi_library) 27 | 28 | ## Basic Usage 29 | ```cpp 30 | #include 31 | #include 32 | 33 | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 34 | 35 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 36 | 37 | void setup() 38 | { 39 | Ethernet.begin(mac); 40 | 41 | MIDI.begin(); // listens on channel 1 42 | } 43 | 44 | void loop() 45 | { 46 | // Listen to incoming notes 47 | MIDI.read(); 48 | 49 | .... 50 | if (something) { 51 | // Send MIDI note 40 on, velocity 55 on channel 1 52 | MIDI.sendNoteOn(40, 55, 1); 53 | } 54 | } 55 | ``` 56 | 57 | More usages in the [examples](https://github.com/lathoub/Arduino-AppleMIDI-Library/tree/master/examples) folder and in the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki) 58 | 59 | ## Hardware 60 | * Arduino/Genuino (Mega, Uno, Arduino Ethernet, MKRZERO, ...) 61 | * ESP8266 (Adafruit HUZZAH ESP8266, Sparkfun ESP8266 Thing Dev) 62 | * ESP32 (Adafruit HUZZAH32 – ESP32 Feather Board) Wi-Fi 63 | * ESP32 with W5500 [Setup](https://github.com/lathoub/Arduino-AppleMIDI-Library/discussions/135) 64 | * Teensy 3.2 & 4.1 65 | * Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500 66 | 67 | ## Network Shields 68 | * Arduino Ethernet shield (Wiznet W5100 and W5500) 69 | * Arduino Wifi R3 shield 70 | * MKR ETH shield (W5500 and W6100 based) 71 | * Teensy WIZ820io W5200 72 | * Teensy 4.1 with [Ethernet Kit](https://www.pjrc.com/store/ethernet_kit.html) 73 | 74 | ## Notes 75 | 76 | ### Session names 77 | 78 | Session names can get really long on Macs (eg 'Macbook Pro of Johann Gambolputty .. von Hautkopft of Ulm') and will be truncated to the [`MaxSessionNameLen`](https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/af4c7bd9a960a90e09e211f0ea00db2d9832d1f7/src/AppleMIDI_Settings.h#L13) 79 | 80 | ### Memory footprint 81 | The memory footprint of the library can be lowered significantly, read the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Memory-footprint) 82 | 83 | ### Ethernet buffer size 84 | It's highly recommended to modify the [Ethernet library](https://github.com/arduino-libraries/Ethernet) or use the [Ethernet3 library](https://github.com/sstaub/Ethernet3) to avoid buffer overruns - [learn more](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Enlarge-Ethernet-buffer-size-to-avoid-dropping-UDP-packages) 85 | 86 | ### Latency 87 | Use wired Ethernet to reduce latency, Wi-Fi increases latency and latency varies. More of the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Keeping-Latency-under-control) 88 | 89 | ## Arduino IDE (arduino.cc) 90 | * 1.8.16 91 | 92 | ## Contributing 93 | I would love to include your enhancements or bug fixes! In lieu of a formal styleguide, please take care to maintain the existing coding style. Please test your code before sending a pull request. It would be very helpful if you include a detailed explanation of your changes in the pull request. 94 | -------------------------------------------------------------------------------- /examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #define SerialMon Serial 2 | #define USE_EXT_CALLBACKS 3 | #include 4 | 5 | #include "./ETH_Helper.h" 6 | 7 | unsigned long t0 = millis(); 8 | int8_t isConnected = 0; 9 | 10 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 11 | 12 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); 13 | 14 | // ----------------------------------------------------------------------------- 15 | // 16 | // ----------------------------------------------------------------------------- 17 | void setup() 18 | { 19 | AM_DBG_SETUP(115200); 20 | AM_DBG(F("Booting")); 21 | 22 | ETH_startup(); 23 | 24 | if (!MDNS.begin(AppleMIDI.getName())) 25 | AM_DBG(F("Error setting up MDNS responder")); 26 | 27 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 28 | AM_DBG(F("Add device named Arduino with Host"), ETH.localIP(), "Port", AppleMIDI.getPort()); 29 | AM_DBG(F("The device should also be visible in the directory as"), AppleMIDI.getName()); 30 | AM_DBG(F("Select and then press the Connect button")); 31 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 32 | 33 | MIDI.begin(); 34 | 35 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 36 | isConnected++; 37 | AM_DBG(F("Connected to session"), ssrc, name); 38 | }); 39 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 40 | isConnected--; 41 | AM_DBG(F("Disconnected"), ssrc); 42 | }); 43 | AppleMIDI.setHandleException(OnAppleMidiException); 44 | 45 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 46 | AM_DBG(F("NoteOn"), note); 47 | }); 48 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 49 | AM_DBG(F("NoteOff"), note); 50 | }); 51 | 52 | MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); 53 | } 54 | 55 | // ----------------------------------------------------------------------------- 56 | // 57 | // ----------------------------------------------------------------------------- 58 | void loop() 59 | { 60 | // Listen to incoming notes 61 | MIDI.read(); 62 | 63 | // send a note every second 64 | // (dont cáll delay(1000) as it will stall the pipeline) 65 | if ((isConnected > 0) && (millis() - t0) > 100) 66 | { 67 | t0 = millis(); 68 | 69 | byte note = random(15, 100); 70 | byte velocity = 55; 71 | byte channel = 1; 72 | 73 | MIDI.sendNoteOn(note, velocity, channel); 74 | // MIDI.sendNoteOff(note, velocity, channel); 75 | } 76 | } 77 | 78 | 79 | // ----------------------------------------------------------------------------- 80 | // 81 | // ----------------------------------------------------------------------------- 82 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { 83 | switch (e) 84 | { 85 | case APPLEMIDI_NAMESPACE::Exception::BufferFullException: 86 | AM_DBG(F("*** BufferFullException")); 87 | break; 88 | case APPLEMIDI_NAMESPACE::Exception::ParseException: 89 | AM_DBG(F("*** ParseException")); 90 | break; 91 | case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: 92 | AM_DBG(F("*** TooManyParticipantsException")); 93 | break; 94 | case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: 95 | AM_DBG(F("*** UnexpectedInviteException")); 96 | break; 97 | case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: 98 | AM_DBG(F("*** ParticipantNotFoundException"), value); 99 | break; 100 | case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: 101 | AM_DBG(F("*** ComputerNotInDirectory"), value); 102 | break; 103 | case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: 104 | AM_DBG(F("*** NotAcceptingAnyone"), value); 105 | break; 106 | case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: 107 | AM_DBG(F("*** ListenerTimeOutException")); 108 | break; 109 | case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: 110 | AM_DBG(F("*** MaxAttemptsException")); 111 | break; 112 | case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: 113 | AM_DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); 114 | break; 115 | case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped: 116 | AM_DBG(F("*** SendPacketsDropped"), value); 117 | break; 118 | case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: 119 | AM_DBG(F("*** ReceivedPacketsDropped"), value); 120 | break; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/utility/Deque.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | BEGIN_APPLEMIDI_NAMESPACE 4 | 5 | template 6 | class Deque { 7 | // class iterator; 8 | 9 | private: 10 | int _head, _tail; 11 | T _data[Size]; 12 | 13 | public: 14 | Deque() 15 | { 16 | clear(); 17 | }; 18 | 19 | size_t free(); 20 | const size_t size() const; 21 | const size_t max_size() const; 22 | T & front(); 23 | const T & front() const; 24 | T & back(); 25 | const T & back() const; 26 | void push_front(const T &); 27 | void push_back(const T &); 28 | void pop_front(); 29 | void pop_back(); 30 | 31 | T& operator[](size_t); 32 | const T& operator[](size_t) const; 33 | T& at(size_t); 34 | const T& at(size_t) const; 35 | 36 | void clear(); 37 | 38 | // iterator begin(); 39 | // iterator end(); 40 | 41 | void erase(size_t); 42 | void erase(size_t, size_t); 43 | 44 | bool empty() const { 45 | return size() == 0; 46 | } 47 | bool full() const { 48 | return (size() == Size); 49 | } 50 | }; 51 | 52 | template 53 | size_t Deque::free() 54 | { 55 | return Size - size(); 56 | } 57 | 58 | template 59 | const size_t Deque::size() const 60 | { 61 | if (_tail < 0) 62 | return 0; // empty 63 | else if (_head > _tail) 64 | return _head - _tail; 65 | else 66 | return Size - _tail + _head; 67 | } 68 | 69 | template 70 | const size_t Deque::max_size() const 71 | { 72 | return Size; 73 | } 74 | 75 | template 76 | T & Deque::front() 77 | { 78 | return _data[_tail]; 79 | } 80 | 81 | template 82 | const T & Deque::front() const 83 | { 84 | return _data[_tail]; 85 | } 86 | 87 | template 88 | T & Deque::back() 89 | { 90 | int idx = _head - 1; 91 | if (idx < 0) idx = Size - 1; 92 | return _data[idx]; 93 | } 94 | 95 | template 96 | const T & Deque::back() const 97 | { 98 | int idx = _head - 1; 99 | if (idx < 0) idx = Size - 1; 100 | return _data[idx]; 101 | } 102 | 103 | template 104 | void Deque::push_front(const T &value) 105 | { 106 | //if container is full, do nothing. 107 | if (free()){ 108 | if (--_tail < 0) 109 | _tail = Size - 1; 110 | _data[_tail] = value; 111 | } 112 | } 113 | 114 | template 115 | void Deque::push_back(const T &value) 116 | { 117 | //if container is full, do nothing. 118 | if (free()){ 119 | _data[_head] = value; 120 | if (empty()) 121 | _tail = _head; 122 | if (++_head >= Size) 123 | _head %= Size; 124 | } 125 | } 126 | 127 | template 128 | void Deque::pop_front() { 129 | if (empty()) // if empty, do nothing. 130 | return; 131 | if (++_tail >= Size) 132 | _tail %= Size; 133 | if (_tail == _head) 134 | clear(); 135 | } 136 | 137 | template 138 | void Deque::pop_back() { 139 | if (empty()) // if empty, do nothing. 140 | return; 141 | if (--_head < 0) 142 | _head = Size - 1; 143 | if (_head == _tail) //now buffer is empty 144 | clear(); 145 | } 146 | 147 | template 148 | void Deque::erase(size_t position) { 149 | if (position >= size()) // out-of-range! 150 | return; // do nothing. 151 | for (size_t i = position; i < size() - 1; i++){ 152 | at(i) = at(i + 1); 153 | } 154 | pop_back(); 155 | } 156 | 157 | template 158 | void Deque::erase(size_t first, size_t last) { 159 | if (first > last // invalid arguments 160 | || first >= size()) // out-of-range 161 | return; //do nothing. 162 | 163 | size_t tgt = first; 164 | for (size_t i = last + 1; i < size(); i++){ 165 | at(tgt++) = at(i); 166 | } 167 | for (size_t i = first; i <= last; i++){ 168 | pop_back(); 169 | } 170 | } 171 | 172 | template 173 | T& Deque::operator[](size_t index) 174 | { 175 | auto i = _tail + index; 176 | if (i >= Size) 177 | i %= Size; 178 | return _data[i]; 179 | } 180 | 181 | template 182 | const T& Deque::operator[](size_t index) const 183 | { 184 | auto i = _tail + index; 185 | if (i >= Size) 186 | i %= Size; 187 | return _data[i]; 188 | } 189 | 190 | template 191 | T& Deque::at(size_t index) 192 | { 193 | auto i = _tail + index; 194 | if (i >= Size) 195 | i %= Size; 196 | return _data[i]; 197 | } 198 | 199 | template 200 | const T& Deque::at(size_t index) const 201 | { 202 | auto i = _tail + index; 203 | if (i >= Size) 204 | i %= Size; 205 | return _data[i]; 206 | } 207 | 208 | template 209 | void Deque::clear() 210 | { 211 | _tail = -1; 212 | _head = 0; 213 | } 214 | 215 | END_APPLEMIDI_NAMESPACE 216 | -------------------------------------------------------------------------------- /src/rtpMIDI_Defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Namespace.h" 4 | 5 | BEGIN_APPLEMIDI_NAMESPACE 6 | 7 | /* used to mask the most significant bit, which flags the start of a new MIDI-command! */ 8 | #define RTP_MIDI_COMMAND_STATUS_FLAG 0x80 9 | 10 | /* used to mask the lower 7 bits of the single octets that make up the delta-time */ 11 | #define RTP_MIDI_DELTA_TIME_OCTET_MASK 0x7f 12 | /* used to mask the most significant bit, which flags the extension of the delta-time */ 13 | #define RTP_MIDI_DELTA_TIME_EXTENSION 0x80 14 | 15 | #define RTP_MIDI_CS_FLAG_B 0x80 16 | #define RTP_MIDI_CS_FLAG_J 0x40 17 | #define RTP_MIDI_CS_FLAG_Z 0x20 18 | #define RTP_MIDI_CS_FLAG_P 0x10 19 | #define RTP_MIDI_CS_MASK_SHORTLEN 0x0f 20 | #define RTP_MIDI_CS_MASK_LONGLEN 0x0fff 21 | 22 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_J 0x80 23 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_K 0x40 24 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_L 0x20 25 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_M 0x10 26 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_N 0x08 27 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_T 0x04 28 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_V 0x02 29 | #define RTP_MIDI_CJ_CHAPTER_M_FLAG_R 0x01 30 | 31 | #define RTP_MIDI_JS_FLAG_S 0x80 32 | #define RTP_MIDI_JS_FLAG_Y 0x40 33 | #define RTP_MIDI_JS_FLAG_A 0x20 34 | #define RTP_MIDI_JS_FLAG_H 0x10 35 | #define RTP_MIDI_JS_MASK_TOTALCHANNELS 0x0f 36 | 37 | #define RTP_MIDI_SJ_FLAG_S 0x8000 38 | #define RTP_MIDI_SJ_FLAG_D 0x4000 39 | #define RTP_MIDI_SJ_FLAG_V 0x2000 40 | #define RTP_MIDI_SJ_FLAG_Q 0x1000 41 | #define RTP_MIDI_SJ_FLAG_F 0x0800 42 | #define RTP_MIDI_SJ_FLAG_X 0x0400 43 | #define RTP_MIDI_SJ_MASK_LENGTH 0x03ff 44 | 45 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_S 0x80 46 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_B 0x40 47 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_G 0x20 48 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_H 0x10 49 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_J 0x08 50 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_K 0x04 51 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_Y 0x02 52 | #define RTP_MIDI_SJ_CHAPTER_D_FLAG_Z 0x01 53 | 54 | #define RTP_MIDI_SJ_CHAPTER_D_RESET_FLAG_S 0x80 55 | #define RTP_MIDI_SJ_CHAPTER_D_RESET_COUNT 0x7f 56 | #define RTP_MIDI_SJ_CHAPTER_D_TUNE_FLAG_S 0x80 57 | #define RTP_MIDI_SJ_CHAPTER_D_TUNE_COUNT 0x7f 58 | #define RTP_MIDI_SJ_CHAPTER_D_SONG_SEL_FLAG_S 0x80 59 | #define RTP_MIDI_SJ_CHAPTER_D_SONG_SEL_VALUE 0x7f 60 | 61 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_FLAG_S 0x8000 62 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_FLAG_C 0x4000 63 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_FLAG_V 0x2000 64 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_FLAG_L 0x1000 65 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_MASK_DSZ 0x0c00 66 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_MASK_LENGTH 0x03ff 67 | #define RTP_MIDI_SJ_CHAPTER_D_SYSCOM_MASK_COUNT 0xff 68 | 69 | #define RTP_MIDI_SJ_CHAPTER_D_SYSREAL_FLAG_S 0x80 70 | #define RTP_MIDI_SJ_CHAPTER_D_SYSREAL_FLAG_C 0x40 71 | #define RTP_MIDI_SJ_CHAPTER_D_SYSREAL_FLAG_L 0x20 72 | #define RTP_MIDI_SJ_CHAPTER_D_SYSREAL_MASK_LENGTH 0x1f 73 | #define RTP_MIDI_SJ_CHAPTER_D_SYSREAL_MASK_COUNT 0xff 74 | 75 | #define RTP_MIDI_SJ_CHAPTER_Q_FLAG_S 0x80 76 | #define RTP_MIDI_SJ_CHAPTER_Q_FLAG_N 0x40 77 | #define RTP_MIDI_SJ_CHAPTER_Q_FLAG_D 0x20 78 | #define RTP_MIDI_SJ_CHAPTER_Q_FLAG_C 0x10 79 | #define RTP_MIDI_SJ_CHAPTER_Q_FLAG_T 0x80 80 | #define RTP_MIDI_SJ_CHAPTER_Q_MASK_TOP 0x07 81 | #define RTP_MIDI_SJ_CHAPTER_Q_MASK_CLOCK 0x07ffff 82 | #define RTP_MIDI_SJ_CHAPTER_Q_MASK_TIMETOOLS 0xffffff 83 | 84 | #define RTP_MIDI_SJ_CHAPTER_F_FLAG_S 0x80 85 | #define RTP_MIDI_SJ_CHAPTER_F_FLAG_C 0x40 86 | #define RTP_MIDI_SJ_CHAPTER_F_FLAG_P 0x20 87 | #define RTP_MIDI_SJ_CHAPTER_F_FLAG_Q 0x10 88 | #define RTP_MIDI_SJ_CHAPTER_F_FLAG_D 0x08 89 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_POINT 0x07 90 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT0 0xf0000000 91 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT1 0x0f000000 92 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT2 0x00f00000 93 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT3 0x000f0000 94 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT4 0x0000f000 95 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT5 0x00000f00 96 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT6 0x000000f0 97 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MT7 0x0000000f 98 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_HR 0xff000000 99 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_MN 0x00ff0000 100 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_SC 0x0000ff00 101 | #define RTP_MIDI_SJ_CHAPTER_F_MASK_FR 0x000000ff 102 | 103 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_S 0x80 104 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_T 0x40 105 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_C 0x20 106 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_F 0x10 107 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_D 0x08 108 | #define RTP_MIDI_SJ_CHAPTER_X_FLAG_L 0x04 109 | #define RTP_MIDI_SJ_CHAPTER_X_MASK_STA 0x03 110 | #define RTP_MIDI_SJ_CHAPTER_X_MASK_TCOUNT 0xff 111 | #define RTP_MIDI_SJ_CHAPTER_X_MASK_COUNT 0xff 112 | 113 | #define RTP_MIDI_CJ_FLAG_S 0x800000 114 | #define RTP_MIDI_CJ_FLAG_H 0x040000 115 | #define RTP_MIDI_CJ_FLAG_P 0x000080 116 | #define RTP_MIDI_CJ_FLAG_C 0x000040 117 | #define RTP_MIDI_CJ_FLAG_M 0x000020 118 | #define RTP_MIDI_CJ_FLAG_W 0x000010 119 | #define RTP_MIDI_CJ_FLAG_N 0x000008 120 | #define RTP_MIDI_CJ_FLAG_E 0x000004 121 | #define RTP_MIDI_CJ_FLAG_T 0x000002 122 | #define RTP_MIDI_CJ_FLAG_A 0x000001 123 | #define RTP_MIDI_CJ_MASK_LENGTH 0x03ff00 124 | #define RTP_MIDI_CJ_MASK_CHANNEL 0x780000 125 | #define RTP_MIDI_CJ_CHANNEL_SHIFT 19 126 | 127 | #define RTP_MIDI_CJ_CHAPTER_M_MASK_LENGTH 0x3f 128 | 129 | #define RTP_MIDI_CJ_CHAPTER_N_MASK_LENGTH 0x7f00 130 | #define RTP_MIDI_CJ_CHAPTER_N_MASK_LOW 0x00f0 131 | #define RTP_MIDI_CJ_CHAPTER_N_MASK_HIGH 0x000f 132 | 133 | #define RTP_MIDI_CJ_CHAPTER_E_MASK_LENGTH 0x7f 134 | #define RTP_MIDI_CJ_CHAPTER_A_MASK_LENGTH 0x7f 135 | 136 | typedef struct PACKED RtpMIDI 137 | { 138 | uint8_t flags; 139 | } RtpMIDI_t; 140 | 141 | END_APPLEMIDI_NAMESPACE 142 | -------------------------------------------------------------------------------- /examples/Teensy41_NoteOnOffEverySec/Teensy41_NoteOnOffEverySec.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #define USE_EXT_CALLBACKS 5 | #include 6 | 7 | // Enter a MAC address for your controller below. 8 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 9 | byte mac[] = { 10 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 11 | }; 12 | 13 | unsigned long t1 = millis(); 14 | int8_t isConnected = 0; 15 | 16 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 17 | 18 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); 19 | 20 | // ----------------------------------------------------------------------------- 21 | // 22 | // ----------------------------------------------------------------------------- 23 | void setup() 24 | { 25 | AM_DBG_SETUP(115200); 26 | AM_DBG(F("Booting")); 27 | 28 | if (Ethernet.begin(mac) == 0) { 29 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 30 | for (;;); 31 | } 32 | 33 | // Check for Ethernet hardware present 34 | if (Ethernet.hardwareStatus() == EthernetNoHardware) { 35 | AM_DBG(F("Ethernet shield was not found. Sorry, can't run without hardware. :(")); 36 | while (true) { 37 | delay(1); // do nothing, no point running without Ethernet hardware 38 | } 39 | } 40 | while (Ethernet.linkStatus() == LinkOFF) { 41 | AM_DBG(F("Ethernet cable is not connected.")); 42 | delay(500); 43 | } 44 | 45 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 46 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 47 | AM_DBG(F("Select and then press the Connect button")); 48 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 49 | 50 | MIDI.begin(MIDI_CHANNEL_OMNI); 51 | 52 | // Stay informed on connection status 53 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 54 | isConnected++; 55 | AM_DBG(F("Connected to session"), ssrc, name); 56 | }); 57 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 58 | isConnected--; 59 | AM_DBG(F("Disconnected"), ssrc); 60 | }); 61 | 62 | AppleMIDI.setHandleException(OnAppleMidiException); 63 | 64 | MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { 65 | AM_DBG(F("ControlChange"), channel, v1, v2); 66 | }); 67 | MIDI.setHandleProgramChange([](Channel channel, byte v1) { 68 | AM_DBG(F("ProgramChange"), channel, v1); 69 | }); 70 | MIDI.setHandlePitchBend([](Channel channel, int v1) { 71 | AM_DBG(F("PitchBend"), channel, v1); 72 | }); 73 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 74 | AM_DBG(F("NoteOn"), channel, note, velocity); 75 | }); 76 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 77 | AM_DBG(F("NoteOff"), channel, note, velocity); 78 | }); 79 | 80 | AM_DBG(F("Sending MIDI messages every second")); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | // 85 | // ----------------------------------------------------------------------------- 86 | void loop() 87 | { 88 | // Listen to incoming notes 89 | MIDI.read(); 90 | 91 | // send a note every second 92 | // (dont cáll delay(1000) as it will stall the pipeline) 93 | if ((isConnected > 0) && (millis() - t1) > 1000) 94 | { 95 | t1 = millis(); 96 | 97 | byte note = random(1, 127); 98 | byte velocity = 55; 99 | byte channel = 1; 100 | 101 | MIDI.sendNoteOn(note, velocity, channel); 102 | // MIDI.sendNoteOff(note, velocity, channel); 103 | } 104 | } 105 | 106 | // ----------------------------------------------------------------------------- 107 | // 108 | // ----------------------------------------------------------------------------- 109 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { 110 | switch (e) 111 | { 112 | case APPLEMIDI_NAMESPACE::Exception::BufferFullException: 113 | AM_DBG(F("*** BufferFullException")); 114 | break; 115 | case APPLEMIDI_NAMESPACE::Exception::ParseException: 116 | AM_DBG(F("*** ParseException")); 117 | break; 118 | case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: 119 | AM_DBG(F("*** TooManyParticipantsException")); 120 | break; 121 | case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: 122 | AM_DBG(F("*** UnexpectedInviteException")); 123 | break; 124 | case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: 125 | AM_DBG(F("*** ParticipantNotFoundException"), value); 126 | break; 127 | case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: 128 | AM_DBG(F("*** ComputerNotInDirectory"), value); 129 | break; 130 | case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: 131 | AM_DBG(F("*** NotAcceptingAnyone"), value); 132 | break; 133 | case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: 134 | AM_DBG(F("*** ListenerTimeOutException")); 135 | break; 136 | case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: 137 | AM_DBG(F("*** MaxAttemptsException")); 138 | break; 139 | case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: 140 | AM_DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); 141 | break; 142 | case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped: 143 | AM_DBG(F("*** SendPacketsDropped"), value); 144 | break; 145 | case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: 146 | AM_DBG(F("*** ReceivedPacketsDropped"), value); 147 | break; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/NoteOn.cpp: -------------------------------------------------------------------------------- 1 | #include "Ethernet.h" 2 | 3 | #define DEBUG 7 4 | #define APPLEMIDI_INITIATOR 5 | 6 | #include "AppleMIDI.h" 7 | 8 | unsigned long t0 = millis(); 9 | bool isConnected = false; 10 | 11 | byte sysex14[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0xF7 }; 12 | byte sysex15[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0x4D, 0xF7 }; 13 | byte sysex16[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x32, 0x50, 0x4D, 0xF7 }; 14 | byte sysexBig[] = { 0xF0, 0x41, 15 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 16 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 17 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 18 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 19 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 20 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 21 | 0x80, 22 | 23 | 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 24 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 25 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 26 | 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 27 | 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 28 | 0xF7 }; 29 | 30 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 31 | 32 | // ----------------------------------------------------------------------------- 33 | // rtpMIDI session. Device connected 34 | // ----------------------------------------------------------------------------- 35 | void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 36 | isConnected = true; 37 | AM_DBG(F("Connected to session"), ssrc, name); 38 | } 39 | 40 | // ----------------------------------------------------------------------------- 41 | // rtpMIDI session. Device disconnected 42 | // ----------------------------------------------------------------------------- 43 | void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 44 | isConnected = false; 45 | AM_DBG(F("Disconnected"), ssrc); 46 | } 47 | 48 | // ----------------------------------------------------------------------------- 49 | // 50 | // ----------------------------------------------------------------------------- 51 | void OnAppleMidiByte(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, byte data) { 52 | AM_DBG(F("MIDI: ")); 53 | AM_DBG(data); 54 | } 55 | 56 | // ----------------------------------------------------------------------------- 57 | // 58 | // ----------------------------------------------------------------------------- 59 | static void OnMidiNoteOn(byte channel, byte note, byte velocity) { 60 | AM_DBG(F("in\tNote on"), note, " Velocity", velocity, "\t", channel); 61 | } 62 | 63 | // ----------------------------------------------------------------------------- 64 | // 65 | // ----------------------------------------------------------------------------- 66 | static void OnMidiNoteOff(byte channel, byte note, byte velocity) { 67 | AM_DBG(F("in\tNote off"), note, " Velocity", velocity, "\t", channel); 68 | } 69 | 70 | // ----------------------------------------------------------------------------- 71 | // 72 | // ----------------------------------------------------------------------------- 73 | char getSysExStatus(const byte* data, uint16_t length) 74 | { 75 | if (data[0] == 0xF0 && data[length - 1] == 0xF7) 76 | return 'F'; // Full SysEx Command 77 | else if (data[0] == 0xF0 && data[length - 1] != 0xF7) 78 | return 'S'; // Start of SysEx-Segment 79 | else if (data[0] != 0xF0 && data[length - 1] != 0xF7) 80 | return 'M'; // Middle of SysEx-Segment 81 | else 82 | return 'E'; // End of SysEx-Segment 83 | } 84 | 85 | static void OnMidiSystemExclusive(byte* array, unsigned size) { 86 | AM_DBG(F("Incoming SysEx: ")); 87 | AM_DBG(getSysExStatus(array, size)); 88 | unsigned i = 0; 89 | for (; i < size - 1; i++) 90 | { 91 | AM_DBG(F(" 0x")); 92 | AM_DBG(array[i], HEX); 93 | } 94 | AM_DBG(F(" 0x")); 95 | AM_DBG(array[i], HEX); 96 | AM_DBG(); 97 | } 98 | 99 | void begin() 100 | { 101 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 102 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 103 | AM_DBG(F("Select and then press the Connect button")); 104 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 105 | 106 | MIDI.begin(MIDI_CHANNEL_OMNI); 107 | 108 | AppleMIDI.setHandleConnected(OnAppleMidiConnected); 109 | AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); 110 | // AppleMIDI.setHandleReceivedMidi(OnAppleMidiByte); 111 | 112 | MIDI.setHandleNoteOn(OnMidiNoteOn); 113 | MIDI.setHandleNoteOff(OnMidiNoteOff); 114 | MIDI.setHandleSystemExclusive(OnMidiSystemExclusive); 115 | 116 | IPAddress remote(192, 168, 1, 156); 117 | // AppleMIDI.sendInvite(remote); 118 | } 119 | 120 | void loop() 121 | { 122 | MIDI.read(); 123 | 124 | // send a note every second 125 | // (dont cáll delay(1000) as it will stall the pipeline) 126 | if ((isConnected) && (millis() - t0) > 10000) 127 | { 128 | t0 = millis(); 129 | 130 | byte note = random(1, 127); 131 | byte velocity = 55; 132 | byte channel = 1; 133 | 134 | MIDI.sendNoteOn(note, velocity, channel); 135 | MIDI.sendNoteOff(note, velocity, channel); 136 | 137 | } 138 | // MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); 139 | } 140 | -------------------------------------------------------------------------------- /examples/ESP32_W5500_Callbacks/ESP32_W5500_Callbacks.ino: -------------------------------------------------------------------------------- 1 | #include "ETH_Helper.h" 2 | 3 | #define SerialMon Serial 4 | #define ONE_PARTICIPANT 5 | #define USE_EXT_CALLBACKS 6 | #include 7 | 8 | unsigned long t1 = millis(); 9 | int8_t isConnected = 0; 10 | 11 | APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "AppleMIDI-Arduino", DEFAULT_CONTROL_PORT); 12 | 13 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); 14 | 15 | // ----------------------------------------------------------------------------- 16 | // 17 | // ----------------------------------------------------------------------------- 18 | void setup() 19 | { 20 | AM_DBG_SETUP(115200); 21 | AM_DBG(F("Das Booting")); 22 | 23 | ETH_startup(); 24 | 25 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 26 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 27 | AM_DBG(F("Select and then press the Connect button")); 28 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 29 | 30 | MIDI.begin(MIDI_CHANNEL_OMNI); 31 | 32 | // Normal callbacks - always available 33 | // Stay informed on connection status 34 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 35 | isConnected++; 36 | AM_DBG(F("Connected to session"), ssrc, name); 37 | }); 38 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 39 | isConnected--; 40 | AM_DBG(F("Disconnected"), ssrc); 41 | }); 42 | 43 | // Extended callback, only available when defining USE_EXT_CALLBACKS 44 | AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) { 45 | // AM_DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr); 46 | }); 47 | AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) { 48 | AM_DBG(F("an rtpMidiMessage has been sent"), rtpMidi.flags); 49 | }); 50 | AppleMIDI.setHandleReceivedRtp([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const APPLEMIDI_NAMESPACE::Rtp_t & rtp, const int32_t& latency) { 51 | // AM_DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency"); 52 | }); 53 | AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { 54 | // AM_DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc); 55 | }); 56 | AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) { 57 | // AM_DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value); 58 | }); 59 | AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { 60 | // AM_DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc); 61 | }); 62 | AppleMIDI.setHandleException(OnAppleMidiException); 63 | 64 | MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { 65 | AM_DBG(F("ControlChange"), channel, v1, v2); 66 | }); 67 | MIDI.setHandleProgramChange([](Channel channel, byte v1) { 68 | AM_DBG(F("ProgramChange"), channel, v1); 69 | }); 70 | MIDI.setHandlePitchBend([](Channel channel, int v1) { 71 | AM_DBG(F("PitchBend"), channel, v1); 72 | }); 73 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 74 | AM_DBG(F("NoteOn"), channel, note, velocity); 75 | }); 76 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 77 | AM_DBG(F("NoteOff"), channel, note, velocity); 78 | }); 79 | 80 | AM_DBG(F("Sending MIDI messages every second")); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | // 85 | // ----------------------------------------------------------------------------- 86 | void loop() 87 | { 88 | // Listen to incoming notes 89 | MIDI.read(); 90 | 91 | // send a note every second 92 | // (dont cáll delay(1000) as it will stall the pipeline) 93 | if ((isConnected > 0) && (millis() - t1) > 100) 94 | { 95 | t1 = millis(); 96 | 97 | byte note = random(1, 127); 98 | byte velocity = 55; 99 | byte channel = 1; 100 | 101 | // AM_DBG(F("\nsendNoteOn"), note, velocity, channel); 102 | MIDI.sendNoteOn(note, velocity, channel); 103 | //MIDI.sendNoteOff(note, velocity, channel); 104 | } 105 | 106 | #ifndef ETHERNET3 107 | EthernetBonjour.run(); 108 | #endif 109 | } 110 | 111 | // ----------------------------------------------------------------------------- 112 | // 113 | // ----------------------------------------------------------------------------- 114 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { 115 | switch (e) 116 | { 117 | case APPLEMIDI_NAMESPACE::Exception::BufferFullException: 118 | AM_DBG(F("*** BufferFullException")); 119 | break; 120 | case APPLEMIDI_NAMESPACE::Exception::ParseException: 121 | AM_DBG(F("*** ParseException")); 122 | break; 123 | case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: 124 | AM_DBG(F("*** TooManyParticipantsException")); 125 | break; 126 | case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: 127 | AM_DBG(F("*** UnexpectedInviteException")); 128 | break; 129 | case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: 130 | AM_DBG(F("*** ParticipantNotFoundException"), value); 131 | break; 132 | case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: 133 | AM_DBG(F("*** ComputerNotInDirectory"), value); 134 | break; 135 | case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: 136 | AM_DBG(F("*** NotAcceptingAnyone"), value); 137 | break; 138 | case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: 139 | AM_DBG(F("*** ListenerTimeOutException")); 140 | break; 141 | case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: 142 | AM_DBG(F("*** MaxAttemptsException")); 143 | break; 144 | case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: 145 | AM_DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); 146 | break; 147 | case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped: 148 | AM_DBG(F("*** SendPacketsDropped"), value); 149 | break; 150 | case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: 151 | AM_DBG(F("******************************************** ReceivedPacketsDropped"), value); 152 | break; 153 | case APPLEMIDI_NAMESPACE::Exception::UdpBeginPacketFailed: 154 | AM_DBG(F("*** UdpBeginPacketFailed"), value); 155 | break; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/AVR_Callbacks/AVR_Callbacks.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SerialMon Serial 4 | #define ONE_PARTICIPANT 5 | #define USE_EXT_CALLBACKS 6 | #include 7 | 8 | // Enter a MAC address for your controller below. 9 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 10 | byte mac[] = { 11 | 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED 12 | }; 13 | 14 | unsigned long t1 = millis(); 15 | int8_t isConnected = 0; 16 | 17 | APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); 18 | 19 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); 20 | 21 | // ----------------------------------------------------------------------------- 22 | // 23 | // ----------------------------------------------------------------------------- 24 | void setup() 25 | { 26 | AM_DBG_SETUP(115200); 27 | AM_DBG(F("Das Booting")); 28 | 29 | if (Ethernet.begin(mac) == 0) { 30 | AM_DBG(F("Failed DHCP, check network cable & reboot")); 31 | for (;;); 32 | } 33 | 34 | AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); 35 | AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); 36 | AM_DBG(F("Select and then press the Connect button")); 37 | AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); 38 | 39 | MIDI.begin(MIDI_CHANNEL_OMNI); 40 | 41 | // Normal callbacks - always available 42 | // Stay informed on connection status 43 | AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { 44 | isConnected++; 45 | AM_DBG(F("Connected to session"), ssrc, name); 46 | }); 47 | AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { 48 | isConnected--; 49 | AM_DBG(F("Disconnected"), ssrc); 50 | }); 51 | /* 52 | // Extended callback, only available when defining USE_EXT_CALLBACKS 53 | AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) { 54 | // AM_DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr); 55 | }); 56 | AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) { 57 | // AM_DBG(F("an rtpMidiMessage has been sent"), rtpMidi.flags); 58 | }); 59 | AppleMIDI.setHandleReceivedRtp([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const APPLEMIDI_NAMESPACE::Rtp_t & rtp, const int32_t& latency) { 60 | // AM_DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency"); 61 | }); 62 | AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { 63 | // AM_DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc); 64 | }); 65 | AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) { 66 | // AM_DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value); 67 | }); 68 | AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { 69 | // AM_DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc); 70 | }); 71 | AppleMIDI.setHandleException(OnAppleMidiException); 72 | 73 | MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { 74 | AM_DBG(F("ControlChange"), channel, v1, v2); 75 | }); 76 | MIDI.setHandleProgramChange([](Channel channel, byte v1) { 77 | AM_DBG(F("ProgramChange"), channel, v1); 78 | }); 79 | MIDI.setHandlePitchBend([](Channel channel, int v1) { 80 | AM_DBG(F("PitchBend"), channel, v1); 81 | }); 82 | MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { 83 | AM_DBG(F("NoteOn"), channel, note, velocity); 84 | }); 85 | MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { 86 | AM_DBG(F("NoteOff"), channel, note, velocity); 87 | }); 88 | */ 89 | AM_DBG(F("Sending MIDI messages every second")); 90 | } 91 | 92 | // ----------------------------------------------------------------------------- 93 | // 94 | // ----------------------------------------------------------------------------- 95 | void loop() 96 | { 97 | // Listen to incoming notes 98 | MIDI.read(); 99 | 100 | // send a note every second 101 | // (dont cáll delay(1000) as it will stall the pipeline) 102 | if ((isConnected > 0) && (millis() - t1) > 100) 103 | { 104 | t1 = millis(); 105 | 106 | byte note = random(15, 80); 107 | byte velocity = random(55, 100); 108 | byte channel = 1; 109 | 110 | // AM_DBG(F("\nsendNoteOn"), note, velocity, channel); 111 | MIDI.sendNoteOn(note, velocity, channel); 112 | //MIDI.sendNoteOff(note, velocity, channel); 113 | } 114 | } 115 | 116 | // ----------------------------------------------------------------------------- 117 | // 118 | // ----------------------------------------------------------------------------- 119 | void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { 120 | switch (e) 121 | { 122 | case APPLEMIDI_NAMESPACE::Exception::BufferFullException: 123 | AM_DBG(F("*** BufferFullException")); 124 | break; 125 | case APPLEMIDI_NAMESPACE::Exception::ParseException: 126 | AM_DBG(F("*** ParseException")); 127 | break; 128 | case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: 129 | AM_DBG(F("*** TooManyParticipantsException")); 130 | break; 131 | case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: 132 | AM_DBG(F("*** UnexpectedInviteException")); 133 | break; 134 | case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: 135 | AM_DBG(F("*** ParticipantNotFoundException"), value); 136 | break; 137 | case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: 138 | AM_DBG(F("*** ComputerNotInDirectory"), value); 139 | break; 140 | case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: 141 | AM_DBG(F("*** NotAcceptingAnyone"), value); 142 | break; 143 | case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: 144 | AM_DBG(F("*** ListenerTimeOutException")); 145 | break; 146 | case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: 147 | AM_DBG(F("*** MaxAttemptsException")); 148 | break; 149 | case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: 150 | AM_DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); 151 | break; 152 | case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped: 153 | AM_DBG(F("*** SendPacketsDropped"), value); 154 | break; 155 | case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: 156 | AM_DBG(F("*** ReceivedPacketsDropped"), value); 157 | break; 158 | case APPLEMIDI_NAMESPACE::Exception::UdpBeginPacketFailed: 159 | AM_DBG(F("*** UdpBeginPacketFailed"), value); 160 | break; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/AppleMIDI_Defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Settings.h" 4 | #include "AppleMIDI_Namespace.h" 5 | 6 | BEGIN_APPLEMIDI_NAMESPACE 7 | 8 | #define APPLEMIDI_LIBRARY_VERSION 0x030000 9 | #define APPLEMIDI_LIBRARY_VERSION_MAJOR 3 10 | #define APPLEMIDI_LIBRARY_VERSION_MINOR 0 11 | #define APPLEMIDI_LIBRARY_VERSION_PATCH 0 12 | 13 | #define DEFAULT_CONTROL_PORT 5004 14 | 15 | typedef uint32_t ssrc_t; 16 | typedef uint32_t initiatorToken_t; 17 | typedef uint64_t timestamp_t; 18 | 19 | union conversionBuffer 20 | { 21 | uint8_t value8; 22 | uint16_t value16; 23 | uint32_t value32; 24 | uint64_t value64; 25 | byte buffer[8]; 26 | }; 27 | 28 | 29 | enum parserReturn: uint8_t 30 | { 31 | Processed, 32 | NotSureGiveMeMoreData, 33 | NotEnoughData, 34 | UnexpectedData, 35 | UnexpectedMidiData, 36 | UnexpectedJournalData, 37 | SessionNameVeryLong, 38 | }; 39 | 40 | #if defined(__AVR__) 41 | #define APPLEMIDI_PROGMEM PROGMEM 42 | typedef const __FlashStringHelper* AppleMIDIConstStr; 43 | #define GFP(x) (reinterpret_cast(x)) 44 | #define GF(x) F(x) 45 | #else 46 | #define APPLEMIDI_PROGMEM 47 | typedef const char* AppleMIDIConstStr; 48 | #define GFP(x) x 49 | #define GF(x) x 50 | #endif 51 | 52 | #define RtpBuffer_t Deque 53 | #define MidiBuffer_t Deque 54 | 55 | // #define USE_EXT_CALLBACKS 56 | // #define ONE_PARTICIPANT // memory optimization 57 | // #define USE_DIRECTORY 58 | 59 | // By defining NO_SESSION_NAME in the sketch, you can save 100 bytes 60 | #ifndef NO_SESSION_NAME 61 | #define KEEP_SESSION_NAME 62 | #endif 63 | 64 | #define MIDI_SAMPLING_RATE_176K4HZ 176400 65 | #define MIDI_SAMPLING_RATE_192KHZ 192000 66 | #define MIDI_SAMPLING_RATE_DEFAULT 10000 67 | 68 | struct Rtp; 69 | typedef Rtp Rtp_t; 70 | 71 | struct RtpMIDI; 72 | typedef RtpMIDI RtpMIDI_t; 73 | 74 | #ifdef USE_DIRECTORY 75 | enum WhoCanConnectToMe : uint8_t 76 | { 77 | None, 78 | OnlyComputersInMyDirectory, 79 | Anyone, 80 | }; 81 | #endif 82 | 83 | // from: https://en.wikipedia.org/wiki/RTP-MIDI 84 | // Apple decided to create their own protocol, imposing all parameters related to 85 | // synchronization like the sampling frequency. This session protocol is called "AppleMIDI" 86 | // in Wireshark software. Session management with AppleMIDI protocol requires two UDP ports, 87 | // the first one is called "Control Port", the second one is called "Data Port". When used 88 | // within a multithread implementation, only the Data port requires a "real-time" thread, 89 | // the other port can be controlled by a normal priority thread. These two ports must be 90 | // located at two consecutive locations (n / n+1); the first one can be any of the 65536 91 | // possible ports. 92 | enum amPortType : uint8_t 93 | { 94 | Control = 0, 95 | Data = 1, 96 | }; 97 | 98 | // from: https://en.wikipedia.org/wiki/RTP-MIDI 99 | // AppleMIDI implementation defines two kind of session controllers: session initiators 100 | // and session listeners. Session initiators are in charge of inviting the session listeners, 101 | // and are responsible of the clock synchronization sequence. Session initiators can generally 102 | // be session listeners, but some devices, such as iOS devices, can be session listeners only. 103 | enum ParticipantKind : uint8_t 104 | { 105 | Listener, 106 | Initiator, 107 | }; 108 | 109 | enum InviteStatus : uint8_t 110 | { 111 | Initiating, 112 | AwaitingControlInvitationAccepted, 113 | ControlInvitationAccepted, 114 | AwaitingDataInvitationAccepted, 115 | DataInvitationAccepted, 116 | Connected 117 | }; 118 | 119 | enum Exception : uint8_t 120 | { 121 | BufferFullException, 122 | ParseException, 123 | UnexpectedParseException, 124 | TooManyParticipantsException, 125 | ComputerNotInDirectory, 126 | NotAcceptingAnyone, 127 | UnexpectedInviteException, 128 | ParticipantNotFoundException, 129 | ListenerTimeOutException, 130 | MaxAttemptsException, 131 | NoResponseFromConnectionRequestException, 132 | SendPacketsDropped, 133 | ReceivedPacketsDropped, 134 | UdpBeginPacketFailed, 135 | }; 136 | 137 | using connectedCallback = void (*)(const ssrc_t&, const char *); 138 | using disconnectedCallback = void (*)(const ssrc_t&); 139 | #ifdef USE_EXT_CALLBACKS 140 | using startReceivedMidiByteCallback = void (*)(const ssrc_t&); 141 | using receivedMidiByteCallback = void (*)(const ssrc_t&, byte); 142 | using endReceivedMidiByteCallback = void (*)(const ssrc_t&); 143 | using receivedRtpCallback = void (*)(const ssrc_t&, const Rtp_t&, const int32_t&); 144 | using exceptionCallback = void (*)(const ssrc_t&, const Exception&, const int32_t value); 145 | using sentRtpCallback = void (*)(const Rtp_t&); 146 | using sentRtpMidiCallback = void (*)(const RtpMIDI_t&); 147 | #endif 148 | 149 | /* Signature "Magic Value" for Apple network MIDI session establishment */ 150 | const byte amSignature[] = {0xff, 0xff}; 151 | 152 | /* 2 (stored in network byte order (big-endian)) */ 153 | const byte amProtocolVersion[] = {0x00, 0x00, 0x00, 0x02}; 154 | 155 | /* Apple network MIDI valid commands */ 156 | const byte amInvitation[] = {'I', 'N'}; 157 | const byte amEndSession[] = {'B', 'Y'}; 158 | const byte amSynchronization[] = {'C', 'K'}; 159 | const byte amInvitationAccepted[] = {'O', 'K'}; 160 | const byte amInvitationRejected[] = {'N', 'O'}; 161 | const byte amReceiverFeedback[] = {'R', 'S'}; 162 | const byte amBitrateReceiveLimit[] = {'R', 'L'}; 163 | 164 | const uint8_t SYNC_CK0 = 0; 165 | const uint8_t SYNC_CK1 = 1; 166 | const uint8_t SYNC_CK2 = 2; 167 | 168 | typedef struct PACKED AppleMIDI_Invitation 169 | { 170 | initiatorToken_t initiatorToken; 171 | ssrc_t ssrc; 172 | 173 | #ifdef KEEP_SESSION_NAME 174 | char sessionName[DefaultSettings::MaxSessionNameLen + 1]; 175 | const size_t getLength() const 176 | { 177 | return sizeof(AppleMIDI_Invitation) - (DefaultSettings::MaxSessionNameLen) + strlen(sessionName); 178 | } 179 | #else 180 | const size_t getLength() const 181 | { 182 | return sizeof(AppleMIDI_Invitation); 183 | } 184 | #endif 185 | } AppleMIDI_Invitation_t, AppleMIDI_InvitationAccepted_t, AppleMIDI_InvitationRejected_t; 186 | 187 | typedef struct PACKED AppleMIDI_BitrateReceiveLimit 188 | { 189 | ssrc_t ssrc; 190 | uint32_t bitratelimit; 191 | } AppleMIDI_BitrateReceiveLimit_t; 192 | 193 | typedef struct PACKED AppleMIDI_Synchronization 194 | { 195 | ssrc_t ssrc; 196 | uint8_t count; 197 | uint8_t padding[3] = {0,0,0}; 198 | timestamp_t timestamps[3]; 199 | } AppleMIDI_Synchronization_t; 200 | 201 | typedef struct PACKED AppleMIDI_ReceiverFeedback 202 | { 203 | ssrc_t ssrc; 204 | uint16_t sequenceNr; 205 | uint16_t dummy; 206 | } AppleMIDI_ReceiverFeedback_t; 207 | 208 | typedef struct PACKED AppleMIDI_EndSession 209 | { 210 | initiatorToken_t initiatorToken; 211 | ssrc_t ssrc; 212 | } AppleMIDI_EndSession_t; 213 | 214 | END_APPLEMIDI_NAMESPACE 215 | -------------------------------------------------------------------------------- /test/TestParser.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {25F82AE6-0CAA-4AF7-A003-BB54DB5EBA5E} 24 | TestParser 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\test;C:\Users\bart\Documents\Arduino\libraries\arduino_midi_library-master\src;C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\src;%(AdditionalIncludeDirectories) 80 | 81 | 82 | Console 83 | 84 | 85 | 86 | 87 | Level3 88 | Disabled 89 | true 90 | true 91 | C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\src;C:\Users\bart\Documents\Arduino\libraries\arduino_midi_library-master\src;%(AdditionalIncludeDirectories) 92 | Default 93 | 94 | 95 | Console 96 | 97 | 98 | 99 | 100 | Level3 101 | MaxSpeed 102 | true 103 | true 104 | true 105 | true 106 | 107 | 108 | Console 109 | true 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | MaxSpeed 117 | true 118 | true 119 | true 120 | true 121 | 122 | 123 | Console 124 | true 125 | true 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/rtpMIDI_Parser_JournalSection.hpp: -------------------------------------------------------------------------------- 1 | // The recovery journal is the default resiliency tool for unreliable 2 | // transport. In this section, we normatively define the roles that 3 | // senders and receivers play in the recovery journal system. 4 | // 5 | // This section introduces the structure of the recovery journal and 6 | // defines the bitfields of recovery journal headers. Appendices A and 7 | // B complete the bitfield definition of the recovery journal. 8 | // 9 | // The recovery journal has a three-level structure: 10 | // 11 | // o Top-level header. 12 | // 13 | // o Channel and system journal headers. These headers encode recovery 14 | // information for a single voice channel (channel journal) or for 15 | // all system commands (system journal). 16 | // 17 | // o Chapters. Chapters describe recovery information for a single 18 | // MIDI command type. 19 | // 20 | parserReturn decodeJournalSection(RtpBuffer_t &buffer) 21 | { 22 | size_t minimumLen = 0; 23 | 24 | conversionBuffer cb; 25 | 26 | if (false == _journalSectionComplete) 27 | { 28 | size_t i = 0; 29 | 30 | // Minimum size for the Journal section is 3 31 | minimumLen += 3; 32 | if (buffer.size() < minimumLen) 33 | return parserReturn::NotEnoughData; 34 | 35 | /* lets get the main flags from the recovery journal header */ 36 | uint8_t flags = buffer[i++]; 37 | 38 | // The 16-bit Checkpoint Packet Seqnum header field codes the sequence 39 | // number of the checkpoint packet for this journal, in network byte 40 | // order (big-endian). The choice of the checkpoint packet sets the 41 | // depth of the checkpoint history for the journal (defined in Appendix A.1). 42 | // 43 | // Receivers may use the Checkpoint Packet Seqnum field of the packet 44 | // that ends a loss event to verify that the journal checkpoint history 45 | // covers the entire loss event. The checkpoint history covers the loss 46 | // event if the Checkpoint Packet Seqnum field is less than or equal to 47 | // one plus the highest RTP sequence number previously received on the 48 | // stream (modulo 2^16). 49 | cb.buffer[0] = buffer[i++]; 50 | cb.buffer[1] = buffer[i++]; 51 | // uint16_t checkPoint = __ntohs(cb.value16); ; // unused 52 | 53 | // (RFC 4695, 5 Recovery Journal Format) 54 | // If A and Y are both zero, the recovery journal only contains its 3- 55 | // octet header and is considered to be an "empty" journal. 56 | if ((flags & RTP_MIDI_JS_FLAG_Y) == 0 && (flags & RTP_MIDI_JS_FLAG_A) == 0) 57 | { 58 | // Big fixed by @hugbug 59 | while (minimumLen-- > 0) 60 | buffer.pop_front(); 61 | 62 | _journalSectionComplete = true; 63 | return parserReturn::Processed; 64 | } 65 | 66 | // By default, the payload format does not use enhanced Chapter C 67 | // encoding. In this default case, the H bit MUST be set to 0 for all 68 | // packets in the stream. 69 | if (flags & RTP_MIDI_JS_FLAG_H) 70 | { 71 | // The H bit indicates if MIDI channels in the stream have been 72 | // configured to use the enhanced Chapter C encoding 73 | } 74 | 75 | // The S (single-packet loss) bit appears in most recovery journal 76 | // structures, including the recovery journal header. The S bit helps 77 | // receivers efficiently parse the recovery journal in the common case 78 | // of the loss of a single packet. 79 | if (flags & RTP_MIDI_JS_FLAG_S) 80 | { 81 | // special encoding 82 | } 83 | 84 | // If the Y header bit is set to 1, the system journal appears in the 85 | // recovery journal, directly following the recovery journal header. 86 | if (flags & RTP_MIDI_JS_FLAG_Y) 87 | { 88 | minimumLen += 2; 89 | if (buffer.size() < minimumLen) 90 | { 91 | return parserReturn::NotEnoughData; 92 | } 93 | 94 | cb.buffer[0] = buffer[i++]; 95 | cb.buffer[1] = buffer[i++]; 96 | uint16_t systemflags = __ntohs(cb.value16); 97 | uint16_t sysjourlen = systemflags & RTP_MIDI_SJ_MASK_LENGTH; 98 | 99 | uint16_t remainingBytes = sysjourlen - 2; 100 | 101 | minimumLen += remainingBytes; 102 | if (buffer.size() < minimumLen) 103 | { 104 | return parserReturn::NotEnoughData; 105 | } 106 | 107 | i += remainingBytes; 108 | } 109 | 110 | // If the A header bit is set to 1, the recovery journal ends with a 111 | // list of (TOTCHAN + 1) channel journals (the 4-bit TOTCHAN header 112 | // field is interpreted as an unsigned integer). 113 | if (flags & RTP_MIDI_JS_FLAG_A) 114 | { 115 | /* At the same place we find the total channels encoded in the channel journal */ 116 | _journalTotalChannels = (flags & RTP_MIDI_JS_MASK_TOTALCHANNELS) + 1; 117 | } 118 | 119 | while (i-- > 0) // is that the same as while (i--) ?? 120 | buffer.pop_front(); 121 | 122 | _journalSectionComplete = true; 123 | } 124 | 125 | // iterate through all the channels specified in header 126 | while (_journalTotalChannels > 0) 127 | { 128 | if (false == _channelJournalSectionComplete) { 129 | 130 | if (buffer.size() < 3) 131 | return parserReturn::NotEnoughData; 132 | 133 | // 3 bytes for channel journal 134 | cb.buffer[0] = 0x00; 135 | cb.buffer[1] = buffer[0]; 136 | cb.buffer[2] = buffer[1]; 137 | cb.buffer[3] = buffer[2]; 138 | uint32_t chanflags = __ntohl(cb.value32); 139 | 140 | bool S_flag = (chanflags & RTP_MIDI_CJ_FLAG_S) == 1; 141 | uint8_t channelNr = (chanflags & RTP_MIDI_CJ_MASK_CHANNEL) >> RTP_MIDI_CJ_CHANNEL_SHIFT; 142 | bool H_flag = (chanflags & RTP_MIDI_CJ_FLAG_H) == 1; 143 | uint8_t chanjourlen = (chanflags & RTP_MIDI_CJ_MASK_LENGTH) >> 8; 144 | 145 | if ((chanflags & RTP_MIDI_CJ_FLAG_P)) { 146 | } 147 | if ((chanflags & RTP_MIDI_CJ_FLAG_C)) { 148 | } 149 | if ((chanflags & RTP_MIDI_CJ_FLAG_M)) { 150 | } 151 | if ((chanflags & RTP_MIDI_CJ_FLAG_W)) { 152 | } 153 | if ((chanflags & RTP_MIDI_CJ_FLAG_N)) { 154 | } 155 | if ((chanflags & RTP_MIDI_CJ_FLAG_E)) { 156 | } 157 | if ((chanflags & RTP_MIDI_CJ_FLAG_T)) { 158 | } 159 | if ((chanflags & RTP_MIDI_CJ_FLAG_A)) { 160 | } 161 | 162 | _bytesToFlush = chanjourlen; 163 | 164 | _channelJournalSectionComplete = true; 165 | } 166 | 167 | while (buffer.size() > 0 && _bytesToFlush > 0) { 168 | _bytesToFlush--; 169 | buffer.pop_front(); 170 | } 171 | 172 | if (_bytesToFlush > 0) { 173 | return parserReturn::NotEnoughData; 174 | } 175 | 176 | _journalTotalChannels--; 177 | } 178 | 179 | return parserReturn::Processed; 180 | } 181 | -------------------------------------------------------------------------------- /src/rtpMIDI_Parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utility/Deque.h" 4 | 5 | #include 6 | 7 | #include "rtpMIDI_Defs.h" 8 | #include "rtp_Defs.h" 9 | 10 | #include "AppleMIDI_Settings.h" 11 | #include "AppleMIDI_Namespace.h" 12 | 13 | BEGIN_APPLEMIDI_NAMESPACE 14 | 15 | template 16 | class AppleMIDISession; 17 | 18 | template 19 | class rtpMIDIParser 20 | { 21 | private: 22 | bool _rtpHeadersComplete = false; 23 | bool _journalSectionComplete = false; 24 | bool _channelJournalSectionComplete = false; 25 | uint16_t midiCommandLength; 26 | uint8_t _journalTotalChannels; 27 | uint8_t rtpMidi_Flags = 0; 28 | int cmdCount = 0; 29 | uint8_t runningstatus = 0; 30 | size_t _bytesToFlush = 0; 31 | 32 | protected: 33 | void debugPrintBuffer(RtpBuffer_t &buffer) 34 | { 35 | #ifdef DEBUG 36 | for (int i = 0; i < buffer.size(); i++) 37 | { 38 | SerialMon.print(" "); 39 | SerialMon.print(i); 40 | SerialMon.print(i < 10 ? " " : " "); 41 | } 42 | for (int i = 0; i < buffer.size(); i++) 43 | { 44 | SerialMon.print("0x"); 45 | SerialMon.print(buffer[i] < 16 ? "0" : ""); 46 | SerialMon.print(buffer[i], HEX); 47 | SerialMon.print(" "); 48 | } 49 | #endif 50 | } 51 | 52 | public: 53 | AppleMIDISession * session; 54 | 55 | // Parse the incoming string 56 | // return: 57 | // - return 0, when the parse does not have enough data 58 | // - return a negative number, when the parser encounters invalid or 59 | // unexpected data. The negative number indicates the amount of bytes 60 | // that were processed. They can be purged safely 61 | // - a positive number indicates the amount of valid bytes processed 62 | // 63 | parserReturn parse(RtpBuffer_t &buffer) 64 | { 65 | debugPrintBuffer(buffer); 66 | 67 | conversionBuffer cb; 68 | 69 | // [RFC3550] provides a complete description of the RTP header fields. 70 | // In this section, we clarify the role of a few RTP header fields for 71 | // MIDI applications. All fields are coded in network byte order (big- 72 | // endian). 73 | 74 | // 0 1 2 3 75 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 76 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 77 | // | V |P|X| CC |M| PT | Sequence number | 78 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 79 | // | Timestamp | 80 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 81 | // | SSRC | 82 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 83 | 84 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 85 | // | MIDI command section ... | 86 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 87 | // | Journal section ... | 88 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 89 | 90 | if (_rtpHeadersComplete == false) 91 | { 92 | auto minimumLen = sizeof(Rtp_t); 93 | if (buffer.size() < minimumLen) 94 | return parserReturn::NotSureGiveMeMoreData; 95 | 96 | size_t i = 0; // todo: rename to consumed 97 | 98 | Rtp_t rtp; 99 | rtp.vpxcc = buffer[i++]; 100 | rtp.mpayload = buffer[i++]; 101 | 102 | cb.buffer[0] = buffer[i++]; 103 | cb.buffer[1] = buffer[i++]; 104 | rtp.sequenceNr = __ntohs(cb.value16); 105 | cb.buffer[0] = buffer[i++]; 106 | cb.buffer[1] = buffer[i++]; 107 | cb.buffer[2] = buffer[i++]; 108 | cb.buffer[3] = buffer[i++]; 109 | rtp.timestamp = __ntohl(cb.value32); 110 | cb.buffer[0] = buffer[i++]; 111 | cb.buffer[1] = buffer[i++]; 112 | cb.buffer[2] = buffer[i++]; 113 | cb.buffer[3] = buffer[i++]; 114 | rtp.ssrc = __ntohl(cb.value32); 115 | 116 | uint8_t version = RTP_VERSION(rtp.vpxcc); 117 | #ifdef DEBUG 118 | bool padding = RTP_PADDING(rtp.vpxcc); 119 | bool extension = RTP_EXTENSION(rtp.vpxcc); 120 | uint8_t csrc_count = RTP_CSRC_COUNT(rtp.vpxcc); 121 | #endif 122 | 123 | if (RTP_VERSION_2 != version) 124 | { 125 | return parserReturn::UnexpectedData; 126 | } 127 | 128 | #ifdef DEBUG 129 | bool marker = RTP_MARKER(rtp.mpayload); 130 | #endif 131 | uint8_t payloadType = RTP_PAYLOAD_TYPE(rtp.mpayload); 132 | 133 | if (PAYLOADTYPE_RTPMIDI != payloadType) 134 | { 135 | return parserReturn::UnexpectedData; 136 | } 137 | 138 | session->ReceivedRtp(rtp); 139 | 140 | // Next byte is the flag 141 | minimumLen += 1; 142 | if (buffer.size() < minimumLen) 143 | return parserReturn::NotSureGiveMeMoreData; 144 | 145 | // 2.2. MIDI Payload (https://www.ietf.org/rfc/rfc4695.html#section-2.2) 146 | // The payload MUST begin with the MIDI command section. The 147 | // MIDI command section codes a (possibly empty) list of timestamped 148 | // MIDI commands and provides the essential service of the payload 149 | // format. 150 | 151 | /* RTP-MIDI starts with 4 bits of flags... */ 152 | rtpMidi_Flags = buffer[i++]; 153 | 154 | // ...followed by a length-field of at least 4 bits 155 | midiCommandLength = rtpMidi_Flags & RTP_MIDI_CS_MASK_SHORTLEN; 156 | 157 | /* see if we have small or large len-field */ 158 | if (rtpMidi_Flags & RTP_MIDI_CS_FLAG_B) 159 | { 160 | minimumLen += 1; 161 | if (buffer.size() < minimumLen) 162 | return parserReturn::NotSureGiveMeMoreData; 163 | 164 | // long header 165 | uint8_t octet = buffer[i++]; 166 | midiCommandLength = (midiCommandLength << 8) | octet; 167 | } 168 | 169 | cmdCount = 0; 170 | runningstatus = 0; 171 | 172 | while (i--) 173 | buffer.pop_front(); 174 | 175 | _rtpHeadersComplete = true; 176 | 177 | // initialize the Journal Section 178 | _journalSectionComplete = false; 179 | _channelJournalSectionComplete = false; 180 | _journalTotalChannels = 0; 181 | } 182 | 183 | // Always a MIDI section 184 | if (midiCommandLength > 0) 185 | { 186 | auto retVal = decodeMIDICommandSection(buffer); 187 | if (retVal != parserReturn::Processed) return retVal; 188 | } 189 | 190 | // The payload MAY also contain a journal section. The journal section 191 | // provides resiliency by coding the recent history of the stream. A 192 | // flag in the MIDI command section codes the presence of a journal 193 | // section in the payload. 194 | 195 | if (rtpMidi_Flags & RTP_MIDI_CS_FLAG_J) 196 | { 197 | auto retVal = decodeJournalSection(buffer); 198 | switch (retVal) { 199 | case parserReturn::Processed: 200 | break; 201 | case parserReturn::NotEnoughData: 202 | return parserReturn::NotEnoughData; 203 | case parserReturn::UnexpectedJournalData: 204 | _rtpHeadersComplete = false; 205 | default: 206 | return retVal; 207 | } 208 | } 209 | 210 | _rtpHeadersComplete = false; 211 | 212 | return parserReturn::Processed; 213 | } 214 | 215 | #include "rtpMIDI_Parser_JournalSection.hpp" 216 | 217 | #include "rtpMIDI_Parser_CommandSection.hpp" 218 | }; 219 | 220 | END_APPLEMIDI_NAMESPACE 221 | -------------------------------------------------------------------------------- /src/rtpMIDI_Parser_CommandSection.hpp: -------------------------------------------------------------------------------- 1 | // https://www.ietf.org/rfc/rfc4695.html#section-3 2 | 3 | parserReturn decodeMIDICommandSection(RtpBuffer_t &buffer) 4 | { 5 | debugPrintBuffer(buffer); 6 | 7 | // https://www.ietf.org/rfc/rfc4695.html#section-3.2 8 | // 9 | // The first MIDI channel command in the MIDI list MUST include a status 10 | // octet.Running status coding, as defined in[MIDI], MAY be used for 11 | // all subsequent MIDI channel commands in the list.As in[MIDI], 12 | // System Commonand System Exclusive messages(0xF0 ... 0xF7) cancel 13 | // the running status state, but System Real - time messages(0xF8 ... 14 | // 0xFF) do not affect the running status state. All System commands in 15 | // the MIDI list MUST include a status octet. 16 | 17 | // As we note above, the first channel command in the MIDI list MUST 18 | // include a status octet.However, the corresponding command in the 19 | // original MIDI source data stream might not have a status octet(in 20 | // this case, the source would be coding the command using running 21 | // status). If the status octet of the first channel command in the 22 | // MIDI list does not appear in the source data stream, the P(phantom) 23 | // header bit MUST be set to 1. In all other cases, the P bit MUST be 24 | // set to 0. 25 | // 26 | // Note that the P bit describes the MIDI source data stream, not the 27 | // MIDI list encoding; regardless of the state of the P bit, the MIDI 28 | // list MUST include the status octet. 29 | // 30 | // As receivers MUST be able to decode running status, sender 31 | // implementors should feel free to use running status to improve 32 | // bandwidth efficiency. However, senders SHOULD NOT introduce timing 33 | // jitter into an existing MIDI command stream through an inappropriate 34 | // use or removal of running status coding. This warning primarily 35 | // applies to senders whose RTP MIDI streams may be transcoded onto a 36 | // MIDI 1.0 DIN cable[MIDI] by the receiver : both the timestamps and 37 | // the command coding (running status or not) must comply with the 38 | // physical restrictions of implicit time coding over a slow serial 39 | // line. 40 | 41 | // (lathoub: RTP_MIDI_CS_FLAG_P((phantom) not implemented 42 | 43 | /* Multiple MIDI-commands might follow - the exact number can only be discovered by really decoding the commands! */ 44 | while (midiCommandLength) 45 | { 46 | /* for the first command we only have a delta-time if Z-Flag is set */ 47 | if ((cmdCount) || (rtpMidi_Flags & RTP_MIDI_CS_FLAG_Z)) 48 | { 49 | size_t consumed = 0; 50 | auto retVal = decodeTime(buffer, consumed); 51 | if (retVal != parserReturn::Processed) return retVal; 52 | 53 | midiCommandLength -= consumed; 54 | while (consumed--) 55 | buffer.pop_front(); 56 | } 57 | 58 | if (midiCommandLength > 0) 59 | { 60 | cmdCount++; 61 | 62 | size_t consumed = 0; 63 | auto retVal = decodeMidi(buffer, runningstatus, consumed); 64 | if (retVal == parserReturn::NotEnoughData) { 65 | cmdCount = 0; // avoid first command again 66 | return retVal; 67 | } 68 | 69 | midiCommandLength -= consumed; 70 | while (consumed--) 71 | buffer.pop_front(); 72 | } 73 | } 74 | 75 | return parserReturn::Processed; 76 | } 77 | 78 | parserReturn decodeTime(RtpBuffer_t &buffer, size_t &consumed) 79 | { 80 | debugPrintBuffer(buffer); 81 | 82 | uint32_t deltatime = 0; 83 | 84 | /* RTP-MIDI deltatime is "compressed" using only the necessary amount of octets */ 85 | for (uint8_t j = 0; j < 4; j++) 86 | { 87 | if (buffer.size() < 1) 88 | return parserReturn::NotEnoughData; 89 | 90 | uint8_t octet = buffer[consumed]; 91 | deltatime = (deltatime << 7) | (octet & RTP_MIDI_DELTA_TIME_OCTET_MASK); 92 | consumed++; 93 | 94 | if ((octet & RTP_MIDI_DELTA_TIME_EXTENSION) == 0) 95 | break; 96 | } 97 | 98 | return parserReturn::Processed; 99 | } 100 | 101 | parserReturn decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus, size_t &consumed) 102 | { 103 | debugPrintBuffer(buffer); 104 | 105 | if (buffer.size() < 1) 106 | return parserReturn::NotEnoughData; 107 | 108 | auto octet = buffer.front(); 109 | 110 | /* MIDI realtime-data -> one octet -- unlike serial-wired MIDI realtime-commands in RTP-MIDI will 111 | * not be intermingled with other MIDI-commands, so we handle this case right here and return */ 112 | if (octet >= 0xf8) 113 | { 114 | consumed = 1; 115 | 116 | session->StartReceivedMidi(); 117 | session->ReceivedMidi(octet); 118 | session->EndReceivedMidi(); 119 | 120 | return parserReturn::Processed; 121 | } 122 | 123 | /* see if this first octet is a status message */ 124 | if ((octet & RTP_MIDI_COMMAND_STATUS_FLAG) == 0) 125 | { 126 | /* if we have no running status yet -> error */ 127 | if (((runningstatus)&RTP_MIDI_COMMAND_STATUS_FLAG) == 0) 128 | { 129 | return parserReturn::Processed; 130 | } 131 | /* our first octet is "virtual" coming from a preceding MIDI-command, 132 | * so actually we have not really consumed anything yet */ 133 | octet = runningstatus; 134 | } 135 | else 136 | { 137 | /* Let's see how this octet influences our running-status */ 138 | /* if we have a "normal" MIDI-command then the new status replaces the current running-status */ 139 | if (octet < 0xf0) 140 | { 141 | runningstatus = octet; 142 | } 143 | else 144 | { 145 | /* system-realtime-commands maintain the current running-status 146 | * other system-commands clear the running-status, since we 147 | * already handled realtime, we can reset it here */ 148 | runningstatus = 0; 149 | } 150 | consumed++; 151 | } 152 | 153 | /* non-system MIDI-commands encode the command in the high nibble and the channel 154 | * in the low nibble - so we will take care of those cases next */ 155 | if (octet < 0xf0) 156 | { 157 | switch (octet & 0xf0) 158 | { 159 | case MIDI_NAMESPACE::MidiType::NoteOff: 160 | consumed += 2; 161 | break; 162 | case MIDI_NAMESPACE::MidiType::NoteOn: 163 | consumed += 2; 164 | break; 165 | case MIDI_NAMESPACE::MidiType::AfterTouchPoly: 166 | consumed += 2; 167 | break; 168 | case MIDI_NAMESPACE::MidiType::ControlChange: 169 | consumed += 2; 170 | break; 171 | case MIDI_NAMESPACE::MidiType::ProgramChange: 172 | consumed += 1; 173 | break; 174 | case MIDI_NAMESPACE::MidiType::AfterTouchChannel: 175 | consumed += 1; 176 | break; 177 | case MIDI_NAMESPACE::MidiType::PitchBend: 178 | consumed += 2; 179 | break; 180 | } 181 | 182 | if (buffer.size() < consumed) { 183 | return parserReturn::NotEnoughData; 184 | } 185 | 186 | session->StartReceivedMidi(); 187 | for (size_t j = 0; j < consumed; j++) 188 | session->ReceivedMidi(buffer[j]); 189 | session->EndReceivedMidi(); 190 | 191 | return parserReturn::Processed; 192 | } 193 | 194 | /* Here we catch the remaining system-common commands */ 195 | switch (octet) 196 | { 197 | case MIDI_NAMESPACE::MidiType::SystemExclusiveStart: 198 | case MIDI_NAMESPACE::MidiType::SystemExclusiveEnd: 199 | decodeMidiSysEx(buffer, consumed); 200 | break; 201 | case MIDI_NAMESPACE::MidiType::TimeCodeQuarterFrame: 202 | consumed += 1; 203 | break; 204 | case MIDI_NAMESPACE::MidiType::SongPosition: 205 | consumed += 2; 206 | break; 207 | case MIDI_NAMESPACE::MidiType::SongSelect: 208 | consumed += 1; 209 | break; 210 | case MIDI_NAMESPACE::MidiType::TuneRequest: 211 | break; 212 | } 213 | 214 | if (buffer.size() < consumed) 215 | return parserReturn::NotEnoughData; 216 | 217 | session->StartReceivedMidi(); 218 | for (size_t j = 0; j < consumed; j++) 219 | session->ReceivedMidi(buffer[j]); 220 | session->EndReceivedMidi(); 221 | 222 | return parserReturn::Processed; 223 | } 224 | 225 | parserReturn decodeMidiSysEx(RtpBuffer_t &buffer, size_t &consumed) 226 | { 227 | debugPrintBuffer(buffer); 228 | 229 | // consumed = 1; // beginning SysEx Token is not counted (as it could remain) 230 | size_t i = 1; // 0 = start of SysEx, so we can start with 1 231 | while (i < buffer.size()) 232 | { 233 | consumed++; 234 | auto octet = buffer[i++]; 235 | 236 | Serial.print("0x"); 237 | Serial.print(octet < 16 ? "0" : ""); 238 | Serial.print(octet, HEX); 239 | Serial.print(" "); 240 | 241 | if (octet == MIDI_NAMESPACE::MidiType::SystemExclusiveEnd) // Complete message 242 | { 243 | return parserReturn::Processed; 244 | } 245 | else if (octet == MIDI_NAMESPACE::MidiType::SystemExclusiveStart) // Start 246 | { 247 | return parserReturn::Processed; 248 | } 249 | } 250 | 251 | // begin of the SysEx is found, not the end. 252 | // so transmit what we have, add a stop-token at the end, 253 | // remove the bytes, modify the length and indicate 254 | // not-enough data, so we buffer gets filled with the remaining bytes. 255 | 256 | // to compensate for adding the sysex at the end. 257 | consumed--; 258 | 259 | // send MIDI data 260 | session->StartReceivedMidi(); 261 | for (size_t j = 0; j < consumed; j++) 262 | session->ReceivedMidi(buffer[j]); 263 | session->ReceivedMidi(MIDI_NAMESPACE::MidiType::SystemExclusiveStart); 264 | session->EndReceivedMidi(); 265 | 266 | // Remove the bytes that were submitted 267 | for (size_t j = 0; j < consumed; j++) 268 | buffer.pop_front(); 269 | // Start a new SysEx train 270 | buffer.push_front(MIDI_NAMESPACE::MidiType::SystemExclusiveEnd); 271 | 272 | midiCommandLength -= consumed; 273 | midiCommandLength += 1; // for adding the manual SysEx SystemExclusiveEnd in front 274 | 275 | // indicates split SysEx 276 | consumed = buffer.max_size() + 1; 277 | 278 | return parserReturn::Processed; 279 | } 280 | -------------------------------------------------------------------------------- /doc/RFC6295_notes.md: -------------------------------------------------------------------------------- 1 | # Basic RTPMIDI Cheat Sheet 2 | 3 | From https://tools.ietf.org/html/rfc6295 4 | 5 | ## RTP Header 6 | 7 | 0 1 2 3 8 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | | V |P|X| CC |M| PT | Sequence number | 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | | Timestamp | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | | SSRC | 15 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | 17 | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | | MIDI command section ... | 20 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | | Journal section ... | 22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | 24 | | Bit | pos | description | 25 | | --------------- | :---: | ------------------------------------------------------------------------------------------------------------------------ | 26 | | V | 0-1 | Version. 2. | 27 | | P | 2 | Has paddding. RTP needs it for encryption. 0. | 28 | | X | 3 | Header extension. 0. | 29 | | CC | 4-7 | CSRC count. 0. | 30 | | M | 8 | Has MIDI data. | 31 | | PT | 9-15 | Payload type. Always 0x61. MIDI. | 32 | | Sequence number | 16-31 | Starts random, increase one on each packet. (%2^16). There is an extended one with 32 bits and rollovers. | 33 | | Timestamp | 32-63 | Time this packet was generated. On Apple midi the unit is 0.1 ms. (1^-4 seconds). Real RTPMIDI is at session connection. | 34 | | SSRC | 64-96 | Random unique SSRC for this sender. Same for all the session. | 35 | 36 | Timestamp can be buffered to reduce jitter on the receiving end, creating a 37 | continuous lag of a specific length. 38 | 39 | ## MIDI Command section 40 | 41 | 0 1 2 3 42 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 43 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 44 | |B|J|Z|P|LEN... | MIDI list ... | 45 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 46 | 47 | | Bit | pos | description | 48 | | ------ | :-: | ---------------------------------------------------------------------------------------------------------------- | 49 | | B | 0 | Length is 12 bits. If true length at 4-7 is MSB, and one more byte. | 50 | | J | 1 | There is a journal | 51 | | Z | 2 | First midi command as is in MIDI section. No timestamp for first command. | 52 | | P | 3 | Phantom MIDI command. The first command is a running command from previous stream. | 53 | | length | 4-7 | How many bytes. May be extended with the B bit. | 54 | | MIDI | ... | MIDI data, then timestamp, MIDI data, timestamp and so on.. or timestamp, midi data and so on. Depends on Z bit. | 55 | 56 | Timestamps in running Length encoding. https://en.wikipedia.org/wiki/Run-length_encoding 57 | 58 | # Journal 59 | 60 | ## Journal Bits 61 | 62 | 0 1 2 63 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 64 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 65 | |S|Y|A|H|TOTCHAN| Checkpoint Packet Seqnum | 66 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 67 | 68 | | Bit | pos | description | 69 | | ------------------------ | :--: | ------------------------------------------------------------------------ | 70 | | S | 0 | Single packet loss. To indicate only one packet is described in journal. | 71 | | Y | 1 | Has system journal | 72 | | A | 2 | Has channel journals. Needs totchan. | 73 | | H | 3 | Enhanced Chapter C encoding. | 74 | | TOTCHAN | 4-7 | Nr channels -1 (has totchan + 1 channels) | 75 | | Checkpoint packet seqnum | 8-23 | Seq nr for this journal Normally the one before the current packet. | 76 | 77 | ## Channel Journal 78 | 79 | One for each (TOTCHAN + 1) 80 | 81 | 0 1 2 3 82 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 83 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 84 | |S| CHAN |H| LENGTH |P|C|M|W|N|E|T|A| Chapters ... | 85 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 86 | 87 | | Bit | pos | description | 88 | | ------ | :----: | ------------------------------------------------------------------------ | 89 | | S | 0 | Single packet loss. To indicate only one packet is described in journal. | 90 | | CHAN | 1-4 | Channel number | 91 | | H | 5 | Whether controllers are Enhanced Chapter C. | 92 | | LENGTH | 6-15 | Lenght of the journal | 93 | | P | 16 / 0 | Chapter P. Program Change. | 94 | | C | 17 / 1 | Chapter C. Control Change. | 95 | | M | 18 / 2 | Chapter M. Parameter System. | 96 | | W | 19 / 3 | Chapter W. Pitch Wheel. | 97 | | N | 20 / 4 | Chapter N. Note On/Off | 98 | | E | 21 / 5 | Chapter E. Note Command Extras | 99 | | T | 22 / 6 | Chapter T. After Touch. | 100 | | A | 23 / 7 | Chapter A. Poly Aftertouch. | 101 | 102 | ## Chapter P 103 | 104 | 0 1 2 105 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 106 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 107 | |S| PROGRAM |B| BANK-MSB |X| BANK-LSB | 108 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 109 | 110 | ## Chapter C 111 | 112 | 0 1 2 3 113 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 114 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 115 | |S| LEN |S| NUMBER |A| VALUE/ALT |S| NUMBER | 116 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 117 | |A| VALUE/ALT | .... | 118 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 119 | 120 | ## Chapter M 121 | 122 | 0 1 2 3 123 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 124 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 125 | |S| LEN |S| NUMBER |A| VALUE/ALT |S| NUMBER | 126 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 127 | |A| VALUE/ALT | .... | 128 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 129 | 130 | # Chapter W 131 | 132 | 0 1 133 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 134 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 135 | |S| FIRST |R| SECOND | 136 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 137 | 138 | ## Chapter N 139 | 140 | 0 1 2 3 141 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 142 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 143 | |B| LEN | LOW | HIGH |S| NOTENUM |Y| VELOCITY | 144 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 145 | |S| NOTENUM |Y| VELOCITY | .... | 146 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 147 | | OFFBITS | OFFBITS | .... | OFFBITS | 148 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 149 | 150 | | Bit | pos | description | 151 | | --- | :-----: | ------------------------------------ | 152 | | B | 0 | S-Style functionality. By default 1. | 153 | | S | 16n | If B is 0, all S are 0. | 154 | | Y | 16n + 8 | Recomendation to play. | 155 | 156 | ## Chapter T 157 | 158 | 0 159 | 0 1 2 3 4 5 6 7 160 | +-+-+-+-+-+-+-+-+ 161 | |S| PRESSURE | 162 | +-+-+-+-+-+-+-+-+ 163 | 164 | ## Chapter A 165 | 166 | 0 1 2 3 167 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 168 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 169 | |S| LEN |S| NOTENUM |X| PRESSURE |S| NOTENUM | 170 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 171 | |X| PRESSURE | .... | 172 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 173 | 174 | (original by https://github.com/davidmoreno/rtpmidid/blob/master/docs/RFC6295_notes.md) 175 | -------------------------------------------------------------------------------- /test/rtpMidi.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CCE329C223C2040200A197D1 /* NoteOn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CCE329BF23C2040200A197D1 /* NoteOn.cpp */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | CCE329B323C2037C00A197D1 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = /usr/share/man/man1/; 18 | dstSubfolderSpec = 0; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 1; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | CC1A7D5F23F9378200206908 /* IPAddress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = IPAddress.h; path = "/Users/bart/Documents/Arduino/libraries/Arduino-AppleMIDI-Library/test/IPAddress.h"; sourceTree = ""; }; 27 | CCD26B8D23DCD696004A7418 /* EthernetShield_NoteOnOffEverySec.ino */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = EthernetShield_NoteOnOffEverySec.ino; path = ../examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino; sourceTree = ""; }; 28 | CCE329B523C2037C00A197D1 /* rtpMidi */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rtpMidi; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | CCE329BF23C2040200A197D1 /* NoteOn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NoteOn.cpp; sourceTree = ""; }; 30 | CCE329C023C2040200A197D1 /* Arduino.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Arduino.h; sourceTree = ""; }; 31 | CCE329C123C2040200A197D1 /* Ethernet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ethernet.h; sourceTree = ""; }; 32 | CCE329D623C28E9F00A197D1 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../src; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | CCE329B223C2037C00A197D1 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | CCE329AC23C2037C00A197D1 = { 47 | isa = PBXGroup; 48 | children = ( 49 | CCD26B8D23DCD696004A7418 /* EthernetShield_NoteOnOffEverySec.ino */, 50 | CCE329D623C28E9F00A197D1 /* src */, 51 | CCE329C023C2040200A197D1 /* Arduino.h */, 52 | CCE329C123C2040200A197D1 /* Ethernet.h */, 53 | CC1A7D5F23F9378200206908 /* IPAddress.h */, 54 | CCE329BF23C2040200A197D1 /* NoteOn.cpp */, 55 | CCE329B623C2037C00A197D1 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | CCE329B623C2037C00A197D1 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | CCE329B523C2037C00A197D1 /* rtpMidi */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | CCE329B423C2037C00A197D1 /* rtpMidi */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = CCE329BC23C2037C00A197D1 /* Build configuration list for PBXNativeTarget "rtpMidi" */; 73 | buildPhases = ( 74 | CCE329B123C2037C00A197D1 /* Sources */, 75 | CCE329B223C2037C00A197D1 /* Frameworks */, 76 | CCE329B323C2037C00A197D1 /* CopyFiles */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = rtpMidi; 83 | productName = rtpMidi; 84 | productReference = CCE329B523C2037C00A197D1 /* rtpMidi */; 85 | productType = "com.apple.product-type.tool"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | CCE329AD23C2037C00A197D1 /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastUpgradeCheck = 1130; 94 | ORGANIZATIONNAME = "Bart De Lathouwer"; 95 | TargetAttributes = { 96 | CCE329B423C2037C00A197D1 = { 97 | CreatedOnToolsVersion = 11.3; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = CCE329B023C2037C00A197D1 /* Build configuration list for PBXProject "rtpMidi" */; 102 | compatibilityVersion = "Xcode 9.3"; 103 | developmentRegion = en; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | Base, 108 | ); 109 | mainGroup = CCE329AC23C2037C00A197D1; 110 | productRefGroup = CCE329B623C2037C00A197D1 /* Products */; 111 | projectDirPath = ""; 112 | projectRoot = ""; 113 | targets = ( 114 | CCE329B423C2037C00A197D1 /* rtpMidi */, 115 | ); 116 | }; 117 | /* End PBXProject section */ 118 | 119 | /* Begin PBXSourcesBuildPhase section */ 120 | CCE329B123C2037C00A197D1 /* Sources */ = { 121 | isa = PBXSourcesBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | CCE329C223C2040200A197D1 /* NoteOn.cpp in Sources */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | /* End PBXSourcesBuildPhase section */ 129 | 130 | /* Begin XCBuildConfiguration section */ 131 | CCE329BA23C2037C00A197D1 /* Debug */ = { 132 | isa = XCBuildConfiguration; 133 | buildSettings = { 134 | ALWAYS_SEARCH_USER_PATHS = NO; 135 | CLANG_ANALYZER_NONNULL = YES; 136 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 137 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_ENABLE_OBJC_WEAK = YES; 142 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 143 | CLANG_WARN_BOOL_CONVERSION = YES; 144 | CLANG_WARN_COMMA = YES; 145 | CLANG_WARN_CONSTANT_CONVERSION = YES; 146 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 147 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 148 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 149 | CLANG_WARN_EMPTY_BODY = YES; 150 | CLANG_WARN_ENUM_CONVERSION = YES; 151 | CLANG_WARN_INFINITE_RECURSION = YES; 152 | CLANG_WARN_INT_CONVERSION = YES; 153 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 154 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 155 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 156 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 157 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 158 | CLANG_WARN_STRICT_PROTOTYPES = YES; 159 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 160 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 161 | CLANG_WARN_UNREACHABLE_CODE = YES; 162 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 163 | COPY_PHASE_STRIP = NO; 164 | DEBUG_INFORMATION_FORMAT = dwarf; 165 | ENABLE_STRICT_OBJC_MSGSEND = YES; 166 | ENABLE_TESTABILITY = YES; 167 | GCC_C_LANGUAGE_STANDARD = gnu11; 168 | GCC_DYNAMIC_NO_PIC = NO; 169 | GCC_NO_COMMON_BLOCKS = YES; 170 | GCC_OPTIMIZATION_LEVEL = 0; 171 | GCC_PREPROCESSOR_DEFINITIONS = ( 172 | "DEBUG=1", 173 | "$(inherited)", 174 | ); 175 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 176 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 177 | GCC_WARN_UNDECLARED_SELECTOR = YES; 178 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 179 | GCC_WARN_UNUSED_FUNCTION = YES; 180 | GCC_WARN_UNUSED_VARIABLE = YES; 181 | HEADER_SEARCH_PATHS = ../src; 182 | MACOSX_DEPLOYMENT_TARGET = 10.15; 183 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 184 | MTL_FAST_MATH = YES; 185 | ONLY_ACTIVE_ARCH = YES; 186 | SDKROOT = macosx; 187 | "SYSTEM_HEADER_SEARCH_PATHS[arch=*]" = ( 188 | /Users/bart/Documents/Arduino/libraries/arduino_midi_library, 189 | /Users/bart/Documents/Arduino/libraries/arduino_midi_library/src, 190 | ); 191 | }; 192 | name = Debug; 193 | }; 194 | CCE329BB23C2037C00A197D1 /* Release */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_ANALYZER_NONNULL = YES; 199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 201 | CLANG_CXX_LIBRARY = "libc++"; 202 | CLANG_ENABLE_MODULES = YES; 203 | CLANG_ENABLE_OBJC_ARC = YES; 204 | CLANG_ENABLE_OBJC_WEAK = YES; 205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_COMMA = YES; 208 | CLANG_WARN_CONSTANT_CONVERSION = YES; 209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 212 | CLANG_WARN_EMPTY_BODY = YES; 213 | CLANG_WARN_ENUM_CONVERSION = YES; 214 | CLANG_WARN_INFINITE_RECURSION = YES; 215 | CLANG_WARN_INT_CONVERSION = YES; 216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 228 | ENABLE_NS_ASSERTIONS = NO; 229 | ENABLE_STRICT_OBJC_MSGSEND = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu11; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | HEADER_SEARCH_PATHS = ../src; 239 | MACOSX_DEPLOYMENT_TARGET = 10.15; 240 | MTL_ENABLE_DEBUG_INFO = NO; 241 | MTL_FAST_MATH = YES; 242 | SDKROOT = macosx; 243 | "SYSTEM_HEADER_SEARCH_PATHS[arch=*]" = ( 244 | /Users/bart/Documents/Arduino/libraries/arduino_midi_library, 245 | /Users/bart/Documents/Arduino/libraries/arduino_midi_library/src, 246 | ); 247 | }; 248 | name = Release; 249 | }; 250 | CCE329BD23C2037C00A197D1 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | CODE_SIGN_STYLE = Automatic; 254 | PRODUCT_NAME = "$(TARGET_NAME)"; 255 | }; 256 | name = Debug; 257 | }; 258 | CCE329BE23C2037C00A197D1 /* Release */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | CODE_SIGN_STYLE = Automatic; 262 | PRODUCT_NAME = "$(TARGET_NAME)"; 263 | }; 264 | name = Release; 265 | }; 266 | /* End XCBuildConfiguration section */ 267 | 268 | /* Begin XCConfigurationList section */ 269 | CCE329B023C2037C00A197D1 /* Build configuration list for PBXProject "rtpMidi" */ = { 270 | isa = XCConfigurationList; 271 | buildConfigurations = ( 272 | CCE329BA23C2037C00A197D1 /* Debug */, 273 | CCE329BB23C2037C00A197D1 /* Release */, 274 | ); 275 | defaultConfigurationIsVisible = 0; 276 | defaultConfigurationName = Release; 277 | }; 278 | CCE329BC23C2037C00A197D1 /* Build configuration list for PBXNativeTarget "rtpMidi" */ = { 279 | isa = XCConfigurationList; 280 | buildConfigurations = ( 281 | CCE329BD23C2037C00A197D1 /* Debug */, 282 | CCE329BE23C2037C00A197D1 /* Release */, 283 | ); 284 | defaultConfigurationIsVisible = 0; 285 | defaultConfigurationName = Release; 286 | }; 287 | /* End XCConfigurationList section */ 288 | }; 289 | rootObject = CCE329AD23C2037C00A197D1 /* Project object */; 290 | } 291 | -------------------------------------------------------------------------------- /src/AppleMIDI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AppleMIDI_Debug.h" 4 | 5 | // https://developer.apple.com/library/archive/documentation/Audio/Conceptual/MIDINetworkDriverProtocol/MIDI/MIDI.html 6 | 7 | #include 8 | using namespace MIDI_NAMESPACE; 9 | 10 | #include 11 | 12 | #include "AppleMIDI_PlatformBegin.h" 13 | #include "AppleMIDI_Defs.h" 14 | #include "AppleMIDI_Settings.h" 15 | 16 | #include "rtp_Defs.h" 17 | #include "rtpMIDI_Defs.h" 18 | #include "rtpMIDI_Clock.h" 19 | 20 | #include "AppleMIDI_Participant.h" 21 | 22 | #include "AppleMIDI_Parser.h" 23 | #include "rtpMIDI_Parser.h" 24 | 25 | #include "AppleMIDI_Namespace.h" 26 | 27 | BEGIN_APPLEMIDI_NAMESPACE 28 | 29 | static unsigned long now; 30 | 31 | struct AppleMIDISettings : public MIDI_NAMESPACE::DefaultSettings 32 | { 33 | // Packet based protocols prefer the entire message to be parsed 34 | // as a whole. 35 | static const bool Use1ByteParsing = false; 36 | }; 37 | 38 | template 39 | class AppleMIDISession 40 | { 41 | typedef _Settings Settings; 42 | typedef _Platform Platform; 43 | 44 | // Allow these internal classes access to our private members 45 | // to avoid access by the .ino to internal messages 46 | friend class AppleMIDIParser; 47 | friend class rtpMIDIParser; 48 | 49 | public: 50 | AppleMIDISession(const char *sessionName, const uint16_t port = DEFAULT_CONTROL_PORT) 51 | { 52 | this->port = port; 53 | #ifdef KEEP_SESSION_NAME 54 | strncpy(this->localName, sessionName, Settings::MaxSessionNameLen); 55 | #endif 56 | 57 | #ifdef ONE_PARTICIPANT 58 | participant.ssrc = 0; 59 | #endif 60 | }; 61 | 62 | virtual ~AppleMIDISession(){}; 63 | 64 | AppleMIDISession &setHandleConnected(void (*fptr)(const ssrc_t &, const char *)) 65 | { 66 | _connectedCallback = fptr; 67 | return *this; 68 | } 69 | AppleMIDISession &setHandleDisconnected(void (*fptr)(const ssrc_t &)) 70 | { 71 | _disconnectedCallback = fptr; 72 | return *this; 73 | } 74 | #ifdef USE_EXT_CALLBACKS 75 | AppleMIDISession &setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value)) 76 | { 77 | _exceptionCallback = fptr; 78 | return *this; 79 | } 80 | AppleMIDISession &setHandleReceivedRtp(void (*fptr)(const ssrc_t &, const Rtp_t &, const int32_t &)) 81 | { 82 | _receivedRtpCallback = fptr; 83 | return *this; 84 | } 85 | AppleMIDISession &setHandleStartReceivedMidi(void (*fptr)(const ssrc_t &)) 86 | { 87 | _startReceivedMidiByteCallback = fptr; 88 | return *this; 89 | } 90 | AppleMIDISession &setHandleReceivedMidi(void (*fptr)(const ssrc_t &, byte)) 91 | { 92 | _receivedMidiByteCallback = fptr; 93 | return *this; 94 | } 95 | AppleMIDISession &setHandleEndReceivedMidi(void (*fptr)(const ssrc_t &)) 96 | { 97 | _endReceivedMidiByteCallback = fptr; 98 | return *this; 99 | } 100 | AppleMIDISession &setHandleSentRtp(void (*fptr)(const Rtp_t &)) 101 | { 102 | _sentRtpCallback = fptr; 103 | return *this; 104 | } 105 | AppleMIDISession &setHandleSentRtpMidi(void (*fptr)(const RtpMIDI_t &)) 106 | { 107 | _sentRtpMidiCallback = fptr; 108 | return *this; 109 | } 110 | #endif 111 | 112 | #ifdef KEEP_SESSION_NAME 113 | const char *getName() const 114 | { 115 | return this->localName; 116 | }; 117 | AppleMIDISession &setName(const char *sessionName) 118 | { 119 | strncpy(this->localName, sessionName, Settings::MaxSessionNameLen); 120 | return *this; 121 | }; 122 | #else 123 | const char *getName() const 124 | { 125 | return nullptr; 126 | }; 127 | AppleMIDISession &setName(const char *sessionName) { return *this; }; 128 | #endif 129 | 130 | const uint16_t getPort() const 131 | { 132 | return this->port; 133 | }; 134 | 135 | // call this method *before* calling begin() 136 | AppleMIDISession & setPort(const uint16_t port) 137 | { 138 | this->port = port; 139 | return *this; 140 | } 141 | 142 | const ssrc_t getSynchronizationSource() const { return this->ssrc; }; 143 | 144 | #ifdef APPLEMIDI_INITIATOR 145 | bool sendInvite(IPAddress ip, uint16_t port = DEFAULT_CONTROL_PORT); 146 | #endif 147 | void sendEndSession(); 148 | 149 | public: 150 | // Override default thruActivated. Must be false for all packet based messages 151 | static const bool thruActivated = false; 152 | 153 | #ifdef USE_DIRECTORY 154 | Deque directory; 155 | WhoCanConnectToMe whoCanConnectToMe = Anyone; 156 | #endif 157 | 158 | void begin() 159 | { 160 | _appleMIDIParser.session = this; 161 | _rtpMIDIParser.session = this; 162 | 163 | // analogRead(0) is not available on all platforms. The use of millis() 164 | // as it preceded by network calls, so timing is variable and usable 165 | // for the random generator. 166 | randomSeed(millis()); 167 | 168 | // Each stream is distinguished by a unique SSRC value and has a unique sequence 169 | // number and RTP timestamp space. 170 | // this is our SSRC 171 | // 172 | // NOTE: Arduino random only goes to INT32_MAX (not UINT32_MAX) 173 | this->ssrc = random(1, INT32_MAX) * 2; 174 | 175 | controlPort.begin(port); 176 | dataPort.begin(port + 1); 177 | 178 | rtpMidiClock.Init(rtpMidiClock.Now(), MIDI_SAMPLING_RATE_DEFAULT); 179 | } 180 | 181 | void end() 182 | { 183 | #ifdef ONE_PARTICIPANT 184 | participant.ssrc = 0; 185 | #endif 186 | controlPort.stop(); 187 | dataPort.stop(); 188 | } 189 | 190 | bool beginTransmission(MIDI_NAMESPACE::MidiType) 191 | { 192 | // All MIDI commands queued up in the same cycle (during 1 loop execution) 193 | // are send in a single MIDI packet 194 | // (The actual sending happen in the available() method, called at the start of the 195 | // event loop() method. 196 | // 197 | // http://www.rfc-editor.org/rfc/rfc4696.txt 198 | // 199 | // 4.1. Queuing and Coding Incoming MIDI Data 200 | // ... 201 | // More sophisticated sending algorithms 202 | // [GRAME] improve efficiency by coding small groups of commands into a 203 | // single packet, at the expense of increasing the sender queuing 204 | // latency. 205 | // 206 | if (!outMidiBuffer.empty()) 207 | { 208 | // Check if there is still room for more - like for 3 bytes or so) 209 | if ((outMidiBuffer.size() + 1 + 3) > outMidiBuffer.max_size()) 210 | writeRtpMidiToAllParticipants(); 211 | else 212 | outMidiBuffer.push_back(0x00); // zero timestamp 213 | } 214 | 215 | // We can't start the writing process here, as we do not know the length 216 | // of what we are to send (The RtpMidi protocol start with writing the 217 | // length of the buffer). So we'll copy to a buffer in the 'write' method, 218 | // and actually serialize for real in the endTransmission method 219 | #ifndef ONE_PARTICIPANT 220 | return (dataPort.remoteIP() != (IPAddress)INADDR_NONE && participants.size() > 0); 221 | #else 222 | return (dataPort.remoteIP() != (IPAddress)INADDR_NONE && participant.ssrc != 0); 223 | #endif 224 | }; 225 | 226 | void write(byte byte) 227 | { 228 | // do we still have place in the buffer for 1 more character? 229 | if ((outMidiBuffer.size()) + 2 > outMidiBuffer.max_size()) 230 | { 231 | // buffer is almost full, only 1 more character 232 | if (MIDI_NAMESPACE::MidiType::SystemExclusive == outMidiBuffer.front()) 233 | { 234 | // Add Sysex at the end of this partial SysEx (in the last availble slot) ... 235 | outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveStart); 236 | 237 | writeRtpMidiToAllParticipants(); 238 | // and start again with a fresh continuation of 239 | // a next SysEx block. 240 | outMidiBuffer.clear(); 241 | outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveEnd); 242 | } 243 | else 244 | { 245 | #ifdef USE_EXT_CALLBACKS 246 | if (nullptr != _exceptionCallback) 247 | _exceptionCallback(ssrc, BufferFullException, 0); 248 | #endif 249 | } 250 | } 251 | 252 | // store in local buffer, as we do *not* know the length of the message prior to sending 253 | outMidiBuffer.push_back(byte); 254 | }; 255 | 256 | void endTransmission(){}; 257 | 258 | // first things MIDI.read() calls in this method 259 | // MIDI-read() must be called at the start of loop() 260 | unsigned available() 261 | { 262 | now = millis(); 263 | 264 | #ifdef APPLEMIDI_INITIATOR 265 | manageSessionInvites(); 266 | #endif 267 | 268 | // All MIDI commands queued up in the same cycle (during 1 loop execution) 269 | // are send in a single MIDI packet 270 | if (outMidiBuffer.size() > 0) 271 | writeRtpMidiToAllParticipants(); 272 | // assert(outMidiBuffer.size() == 0); // must be empty 273 | 274 | if (inMidiBuffer.size() > 0) 275 | return inMidiBuffer.size(); 276 | 277 | if (readDataPackets()) // from socket into dataBuffer 278 | parseDataPackets(); // from dataBuffer into inMidiBuffer 279 | 280 | if (readControlPackets()) // from socket into controlBuffer 281 | parseControlPackets(); // from controlBuffer to AppleMIDI 282 | 283 | manageReceiverFeedback(); 284 | manageSynchronization(); 285 | 286 | return inMidiBuffer.size(); 287 | }; 288 | 289 | byte read() 290 | { 291 | auto byte = inMidiBuffer.front(); 292 | inMidiBuffer.pop_front(); 293 | 294 | return byte; 295 | }; 296 | 297 | protected: 298 | UdpClass controlPort; 299 | UdpClass dataPort; 300 | 301 | private: 302 | RtpBuffer_t controlBuffer; 303 | RtpBuffer_t dataBuffer; 304 | 305 | byte packetBuffer[Settings::UdpTxPacketMaxSize]; 306 | 307 | AppleMIDIParser _appleMIDIParser; 308 | rtpMIDIParser _rtpMIDIParser; 309 | 310 | connectedCallback _connectedCallback = nullptr; 311 | disconnectedCallback _disconnectedCallback = nullptr; 312 | #ifdef USE_EXT_CALLBACKS 313 | startReceivedMidiByteCallback _startReceivedMidiByteCallback = nullptr; 314 | receivedMidiByteCallback _receivedMidiByteCallback = nullptr; 315 | endReceivedMidiByteCallback _endReceivedMidiByteCallback = nullptr; 316 | receivedRtpCallback _receivedRtpCallback = nullptr; 317 | sentRtpCallback _sentRtpCallback = nullptr; 318 | sentRtpMidiCallback _sentRtpMidiCallback = nullptr; 319 | exceptionCallback _exceptionCallback = nullptr; 320 | #endif 321 | // buffer for incoming and outgoing MIDI messages 322 | MidiBuffer_t inMidiBuffer; 323 | MidiBuffer_t outMidiBuffer; 324 | 325 | rtpMidi_Clock rtpMidiClock; 326 | 327 | ssrc_t ssrc = 0; 328 | uint16_t port = DEFAULT_CONTROL_PORT; 329 | #ifdef ONE_PARTICIPANT 330 | Participant participant; 331 | #else 332 | Deque, Settings::MaxNumberOfParticipants> participants; 333 | #endif 334 | 335 | #ifdef KEEP_SESSION_NAME 336 | char localName[Settings::MaxSessionNameLen + 1]; 337 | #endif 338 | 339 | private: 340 | size_t readControlPackets(); 341 | size_t readDataPackets(); 342 | 343 | void parseControlPackets(); 344 | void parseDataPackets(); 345 | 346 | void ReceivedInvitation(AppleMIDI_Invitation_t &, const amPortType &); 347 | void ReceivedControlInvitation(AppleMIDI_Invitation_t &); 348 | void ReceivedDataInvitation(AppleMIDI_Invitation_t &); 349 | void ReceivedSynchronization(AppleMIDI_Synchronization_t &); 350 | void ReceivedReceiverFeedback(AppleMIDI_ReceiverFeedback_t &); 351 | void ReceivedEndSession(AppleMIDI_EndSession_t &); 352 | void ReceivedBitrateReceiveLimit(AppleMIDI_BitrateReceiveLimit &); 353 | 354 | void ReceivedInvitationAccepted(AppleMIDI_InvitationAccepted_t &, const amPortType &); 355 | void ReceivedControlInvitationAccepted(AppleMIDI_InvitationAccepted_t &); 356 | void ReceivedDataInvitationAccepted(AppleMIDI_InvitationAccepted_t &); 357 | void ReceivedInvitationRejected(AppleMIDI_InvitationRejected_t &); 358 | 359 | // rtpMIDI callback from parser 360 | void ReceivedRtp(const Rtp_t &); 361 | void StartReceivedMidi(); 362 | void ReceivedMidi(byte data); 363 | void EndReceivedMidi(); 364 | 365 | // Helpers 366 | void writeInvitation(UdpClass &, const IPAddress &, const uint16_t &, AppleMIDI_Invitation_t &, const byte *command); 367 | void writeReceiverFeedback(const IPAddress &, const uint16_t &, AppleMIDI_ReceiverFeedback_t &); 368 | void writeSynchronization(const IPAddress &, const uint16_t &, AppleMIDI_Synchronization_t &); 369 | void writeEndSession(const IPAddress &, const uint16_t &, AppleMIDI_EndSession_t &); 370 | 371 | void sendEndSession(Participant *); 372 | 373 | void writeRtpMidiToAllParticipants(); 374 | void writeRtpMidiBuffer(Participant *); 375 | 376 | void manageReceiverFeedback(); 377 | 378 | void manageSessionInvites(); 379 | void manageSynchronization(); 380 | void manageSynchronizationInitiator(); 381 | void manageSynchronizationInitiatorHeartBeat(Participant *); 382 | void manageSynchronizationInitiatorInvites(size_t); 383 | 384 | void sendSynchronization(Participant *); 385 | 386 | #ifndef ONE_PARTICIPANT 387 | Participant *getParticipantBySSRC(const ssrc_t &); 388 | Participant *getParticipantByInitiatorToken(const uint32_t &initiatorToken); 389 | #endif 390 | #ifdef USE_DIRECTORY 391 | bool IsComputerInDirectory(IPAddress) const; 392 | #endif 393 | }; 394 | 395 | END_APPLEMIDI_NAMESPACE 396 | 397 | #include "AppleMIDI.hpp" 398 | 399 | #define APPLEMIDI_CREATE_INSTANCE(Type, Name, SessionName, Port) \ 400 | APPLEMIDI_NAMESPACE::AppleMIDISession Apple##Name(SessionName, Port); \ 401 | MIDI_NAMESPACE::MidiInterface, APPLEMIDI_NAMESPACE::AppleMIDISettings> Name((APPLEMIDI_NAMESPACE::AppleMIDISession &)Apple##Name); 402 | 403 | #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) 404 | #define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ 405 | APPLEMIDI_CREATE_INSTANCE(WiFiUDP, MIDI, "AppleMIDI-ESP32", DEFAULT_CONTROL_PORT); 406 | #elif defined(ESP8266) 407 | #define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ 408 | APPLEMIDI_CREATE_INSTANCE(WiFiUDP, MIDI, "AppleMIDI-ESP8266", DEFAULT_CONTROL_PORT); 409 | #else 410 | #define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ 411 | APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "AppleMIDI-Arduino", DEFAULT_CONTROL_PORT); 412 | #endif 413 | 414 | #include "AppleMIDI_PlatformEnd.h" 415 | -------------------------------------------------------------------------------- /src/AppleMIDI_Parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utility/Deque.h" 4 | 5 | #include "AppleMIDI_Defs.h" 6 | #include "AppleMIDI_Settings.h" 7 | #include "AppleMIDI_Namespace.h" 8 | 9 | BEGIN_APPLEMIDI_NAMESPACE 10 | 11 | template 12 | class AppleMIDISession; 13 | 14 | template 15 | class AppleMIDIParser 16 | { 17 | public: 18 | AppleMIDISession *session; 19 | 20 | parserReturn parse(RtpBuffer_t &buffer, const amPortType &portType) 21 | { 22 | conversionBuffer cb; 23 | 24 | byte signature[2]; // Signature "Magic Value" for Apple network MIDI session establishment 25 | byte command[2]; // 16-bit command identifier (two ASCII characters, first in high 8 bits, second in low 8 bits) 26 | 27 | size_t minimumLen = (sizeof(signature) + sizeof(command)); // Signature + Command 28 | if (buffer.size() < minimumLen) 29 | return parserReturn::NotSureGiveMeMoreData; 30 | 31 | size_t i = 0; 32 | 33 | signature[0] = buffer[i++]; 34 | signature[1] = buffer[i++]; 35 | if (0 != memcmp(signature, amSignature, sizeof(amSignature))) 36 | { 37 | return parserReturn::UnexpectedData; 38 | } 39 | 40 | command[0] = buffer[i++]; 41 | command[1] = buffer[i++]; 42 | 43 | if (0 == memcmp(command, amInvitation, sizeof(amInvitation))) 44 | { 45 | byte protocolVersion[4]; 46 | 47 | minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t)); 48 | if (buffer.size() < minimumLen) 49 | { 50 | return parserReturn::NotEnoughData; 51 | } 52 | 53 | // 2 (stored in network byte order (big-endian)) 54 | protocolVersion[0] = buffer[i++]; 55 | protocolVersion[1] = buffer[i++]; 56 | protocolVersion[2] = buffer[i++]; 57 | protocolVersion[3] = buffer[i++]; 58 | if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion))) 59 | { 60 | return parserReturn::UnexpectedData; 61 | } 62 | 63 | AppleMIDI_Invitation invitation; 64 | 65 | // A random number generated by the session's APPLEMIDI_INITIATOR. 66 | cb.buffer[0] = buffer[i++]; 67 | cb.buffer[1] = buffer[i++]; 68 | cb.buffer[2] = buffer[i++]; 69 | cb.buffer[3] = buffer[i++]; 70 | invitation.initiatorToken = __ntohl(cb.value32); 71 | 72 | // The sender's synchronization source identifier. 73 | cb.buffer[0] = buffer[i++]; 74 | cb.buffer[1] = buffer[i++]; 75 | cb.buffer[2] = buffer[i++]; 76 | cb.buffer[3] = buffer[i++]; 77 | invitation.ssrc = __ntohl(cb.value32); 78 | 79 | #ifdef KEEP_SESSION_NAME 80 | uint16_t bi = 0; 81 | while (i < buffer.size()) 82 | { 83 | if (bi < Settings::MaxSessionNameLen) 84 | invitation.sessionName[bi++] = buffer[i++]; 85 | else 86 | i++; 87 | } 88 | invitation.sessionName[bi++] = '\0'; 89 | #else 90 | while (i < buffer.size()) 91 | i++; 92 | #endif 93 | auto retVal = parserReturn::Processed; 94 | 95 | // when given a Session Name and the buffer has been fully processed and the 96 | // last character is not 'endl', then we got a very long sessionName. It will 97 | // continue in the next memory chunk of the packet. We don't care, so indicated 98 | // flush the remainder of the packet. 99 | // First part if the session name is kept, processing continues 100 | if (i > minimumLen) 101 | if (i == buffer.size() && buffer[i] != 0x00) 102 | retVal = parserReturn::SessionNameVeryLong; 103 | 104 | while (i--) 105 | buffer.pop_front(); // consume all the bytes that made up this message 106 | 107 | session->ReceivedInvitation(invitation, portType); 108 | 109 | return retVal; 110 | } 111 | else if (0 == memcmp(command, amEndSession, sizeof(amEndSession))) 112 | { 113 | byte protocolVersion[4]; 114 | 115 | minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t)); 116 | if (buffer.size() < minimumLen) 117 | return parserReturn::NotEnoughData; 118 | 119 | // 2 (stored in network byte order (big-endian)) 120 | protocolVersion[0] = buffer[i++]; 121 | protocolVersion[1] = buffer[i++]; 122 | protocolVersion[2] = buffer[i++]; 123 | protocolVersion[3] = buffer[i++]; 124 | if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion))) 125 | { 126 | return parserReturn::UnexpectedData; 127 | } 128 | 129 | AppleMIDI_EndSession_t endSession; 130 | 131 | // A random number generated by the session's APPLEMIDI_INITIATOR. 132 | cb.buffer[0] = buffer[i++]; 133 | cb.buffer[1] = buffer[i++]; 134 | cb.buffer[2] = buffer[i++]; 135 | cb.buffer[3] = buffer[i++]; 136 | endSession.initiatorToken = __ntohl(cb.value32); 137 | 138 | // The sender's synchronization source identifier. 139 | cb.buffer[0] = buffer[i++]; 140 | cb.buffer[1] = buffer[i++]; 141 | cb.buffer[2] = buffer[i++]; 142 | cb.buffer[3] = buffer[i++]; 143 | endSession.ssrc = __ntohl(cb.value32); 144 | 145 | while (i--) 146 | buffer.pop_front(); // consume all the bytes that made up this message 147 | 148 | session->ReceivedEndSession(endSession); 149 | 150 | return parserReturn::Processed; 151 | } 152 | else if (0 == memcmp(command, amSynchronization, sizeof(amSynchronization))) 153 | { 154 | AppleMIDI_Synchronization_t synchronization; 155 | 156 | // minimum amount : 4 bytes for sender SSRC, 1 byte for count, 3 bytes padding and 3 times 8 bytes 157 | minimumLen += (4 + 1 + 3 + (3 * 8)); 158 | if (buffer.size() < minimumLen) 159 | return parserReturn::NotEnoughData; 160 | 161 | // The sender's synchronization source identifier. 162 | cb.buffer[0] = buffer[i++]; 163 | cb.buffer[1] = buffer[i++]; 164 | cb.buffer[2] = buffer[i++]; 165 | cb.buffer[3] = buffer[i++]; 166 | synchronization.ssrc = __ntohl(cb.value32); 167 | 168 | synchronization.count = buffer[i++]; 169 | buffer[i++]; 170 | buffer[i++]; 171 | buffer[i++]; // padding, unused 172 | cb.buffer[0] = buffer[i++]; 173 | cb.buffer[1] = buffer[i++]; 174 | cb.buffer[2] = buffer[i++]; 175 | cb.buffer[3] = buffer[i++]; 176 | cb.buffer[4] = buffer[i++]; 177 | cb.buffer[5] = buffer[i++]; 178 | cb.buffer[6] = buffer[i++]; 179 | cb.buffer[7] = buffer[i++]; 180 | synchronization.timestamps[0] = __ntohll(cb.value64); 181 | 182 | cb.buffer[0] = buffer[i++]; 183 | cb.buffer[1] = buffer[i++]; 184 | cb.buffer[2] = buffer[i++]; 185 | cb.buffer[3] = buffer[i++]; 186 | cb.buffer[4] = buffer[i++]; 187 | cb.buffer[5] = buffer[i++]; 188 | cb.buffer[6] = buffer[i++]; 189 | cb.buffer[7] = buffer[i++]; 190 | synchronization.timestamps[1] = __ntohll(cb.value64); 191 | 192 | cb.buffer[0] = buffer[i++]; 193 | cb.buffer[1] = buffer[i++]; 194 | cb.buffer[2] = buffer[i++]; 195 | cb.buffer[3] = buffer[i++]; 196 | cb.buffer[4] = buffer[i++]; 197 | cb.buffer[5] = buffer[i++]; 198 | cb.buffer[6] = buffer[i++]; 199 | cb.buffer[7] = buffer[i++]; 200 | synchronization.timestamps[2] = __ntohll(cb.value64); 201 | 202 | while (i--) 203 | buffer.pop_front(); // consume all the bytes that made up this message 204 | 205 | session->ReceivedSynchronization(synchronization); 206 | 207 | return parserReturn::Processed; 208 | } 209 | else if (0 == memcmp(command, amReceiverFeedback, sizeof(amReceiverFeedback))) 210 | { 211 | AppleMIDI_ReceiverFeedback_t receiverFeedback; 212 | 213 | minimumLen += (sizeof(receiverFeedback.ssrc) + sizeof(receiverFeedback.sequenceNr) + sizeof(receiverFeedback.dummy)); 214 | if (buffer.size() < minimumLen) 215 | return parserReturn::NotEnoughData; 216 | 217 | cb.buffer[0] = buffer[i++]; 218 | cb.buffer[1] = buffer[i++]; 219 | cb.buffer[2] = buffer[i++]; 220 | cb.buffer[3] = buffer[i++]; 221 | receiverFeedback.ssrc = __ntohl(cb.value32); 222 | 223 | cb.buffer[0] = buffer[i++]; 224 | cb.buffer[1] = buffer[i++]; 225 | receiverFeedback.sequenceNr = __ntohs(cb.value16); 226 | cb.buffer[0] = buffer[i++]; 227 | cb.buffer[1] = buffer[i++]; 228 | receiverFeedback.dummy = __ntohs(cb.value16); 229 | 230 | while (i--) 231 | buffer.pop_front(); // consume all the bytes that made up this message 232 | 233 | session->ReceivedReceiverFeedback(receiverFeedback); 234 | 235 | return parserReturn::Processed; 236 | } 237 | #ifdef APPLEMIDI_INITIATOR 238 | else if (0 == memcmp(command, amInvitationAccepted, sizeof(amInvitationAccepted))) 239 | { 240 | byte protocolVersion[4]; 241 | 242 | minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t)); 243 | if (buffer.size() < minimumLen) 244 | { 245 | return parserReturn::NotEnoughData; 246 | } 247 | 248 | // 2 (stored in network byte order (big-endian)) 249 | protocolVersion[0] = buffer[i++]; 250 | protocolVersion[1] = buffer[i++]; 251 | protocolVersion[2] = buffer[i++]; 252 | protocolVersion[3] = buffer[i++]; 253 | if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion))) 254 | { 255 | return parserReturn::UnexpectedData; 256 | } 257 | 258 | AppleMIDI_InvitationAccepted_t invitationAccepted; 259 | 260 | // A random number generated by the session's APPLEMIDI_INITIATOR. 261 | cb.buffer[0] = buffer[i++]; 262 | cb.buffer[1] = buffer[i++]; 263 | cb.buffer[2] = buffer[i++]; 264 | cb.buffer[3] = buffer[i++]; 265 | invitationAccepted.initiatorToken = __ntohl(cb.value32); 266 | 267 | // The sender's synchronization source identifier. 268 | cb.buffer[0] = buffer[i++]; 269 | cb.buffer[1] = buffer[i++]; 270 | cb.buffer[2] = buffer[i++]; 271 | cb.buffer[3] = buffer[i++]; 272 | invitationAccepted.ssrc = __ntohl(cb.value32); 273 | 274 | #ifdef KEEP_SESSION_NAME 275 | uint16_t bi = 0; 276 | while (i < buffer.size()) 277 | { 278 | if (bi < Settings::MaxSessionNameLen) 279 | invitationAccepted.sessionName[bi++] = buffer[i++]; 280 | else 281 | i++; 282 | } 283 | invitationAccepted.sessionName[bi++] = '\0'; 284 | #else 285 | while (i < buffer.size()) 286 | i++; 287 | #endif 288 | 289 | auto retVal = parserReturn::Processed; 290 | 291 | // when given a Session Name and the buffer has been fully processed and the 292 | // last character is not 'endl', then we got a very long sessionName. It will 293 | // continue in the next memory chunk of the packet. We don't care, so indicated 294 | // flush the remainder of the packet. 295 | // First part if the session name is kept, processing continues 296 | if (i > minimumLen) 297 | if (i == buffer.size() && buffer[i] != 0x00) 298 | retVal = parserReturn::SessionNameVeryLong; 299 | 300 | while (i--) 301 | buffer.pop_front(); // consume all the bytes that made up this message 302 | 303 | session->ReceivedInvitationAccepted(invitationAccepted, portType); 304 | 305 | return retVal; 306 | } 307 | else if (0 == memcmp(command, amInvitationRejected, sizeof(amInvitationRejected))) 308 | { 309 | byte protocolVersion[4]; 310 | 311 | minimumLen += (sizeof(protocolVersion) + sizeof(initiatorToken_t) + sizeof(ssrc_t)); 312 | if (buffer.size() < minimumLen) 313 | { 314 | return parserReturn::NotEnoughData; 315 | } 316 | 317 | // 2 (stored in network byte order (big-endian)) 318 | protocolVersion[0] = buffer[i++]; 319 | protocolVersion[1] = buffer[i++]; 320 | protocolVersion[2] = buffer[i++]; 321 | protocolVersion[3] = buffer[i++]; 322 | if (0 != memcmp(protocolVersion, amProtocolVersion, sizeof(amProtocolVersion))) 323 | { 324 | return parserReturn::UnexpectedData; 325 | } 326 | 327 | AppleMIDI_InvitationRejected_t invitationRejected; 328 | 329 | // A random number generated by the session's APPLEMIDI_INITIATOR. 330 | cb.buffer[0] = buffer[i++]; 331 | cb.buffer[1] = buffer[i++]; 332 | cb.buffer[2] = buffer[i++]; 333 | cb.buffer[3] = buffer[i++]; 334 | invitationRejected.initiatorToken = __ntohl(cb.value32); 335 | 336 | // The sender's synchronization source identifier. 337 | cb.buffer[0] = buffer[i++]; 338 | cb.buffer[1] = buffer[i++]; 339 | cb.buffer[2] = buffer[i++]; 340 | cb.buffer[3] = buffer[i++]; 341 | invitationRejected.ssrc = __ntohl(cb.value32); 342 | 343 | #ifdef KEEP_SESSION_NAME 344 | uint16_t bi = 0; 345 | while ((i < buffer.size()) && (buffer[i] != 0x00)) 346 | { 347 | if (bi < Settings::MaxSessionNameLen) 348 | invitationRejected.sessionName[bi++] = buffer[i]; 349 | i++; 350 | } 351 | invitationRejected.sessionName[bi++] = '\0'; 352 | #else 353 | while ((i < buffer.size()) && (buffer[i] != 0x00)) 354 | i++; 355 | #endif 356 | // session name is optional. 357 | // If i > minimum size (16), then a sessionName was provided and must include 0x00 358 | if (i > minimumLen) 359 | if (i == buffer.size() || buffer[i++] != 0x00) 360 | return parserReturn::NotEnoughData; 361 | 362 | while (i--) 363 | buffer.pop_front(); // consume all the bytes that made up this message 364 | 365 | session->ReceivedInvitationRejected(invitationRejected); 366 | 367 | return parserReturn::Processed; 368 | } 369 | else if (0 == memcmp(command, amBitrateReceiveLimit, sizeof(amBitrateReceiveLimit))) 370 | { 371 | AppleMIDI_BitrateReceiveLimit bitrateReceiveLimit; 372 | 373 | minimumLen += (sizeof(bitrateReceiveLimit.ssrc) + sizeof(bitrateReceiveLimit.bitratelimit)); 374 | if (buffer.size() < minimumLen) 375 | return parserReturn::NotEnoughData; 376 | 377 | cb.buffer[0] = buffer[i++]; 378 | cb.buffer[1] = buffer[i++]; 379 | cb.buffer[2] = buffer[i++]; 380 | cb.buffer[3] = buffer[i++]; 381 | bitrateReceiveLimit.ssrc = __ntohl(cb.value32); 382 | 383 | cb.buffer[0] = buffer[i++]; 384 | cb.buffer[1] = buffer[i++]; 385 | cb.buffer[2] = buffer[i++]; 386 | cb.buffer[3] = buffer[i++]; 387 | bitrateReceiveLimit.bitratelimit = __ntohl(cb.value32); 388 | 389 | while (i--) 390 | buffer.pop_front(); // consume all the bytes that made up this message 391 | 392 | session->ReceivedBitrateReceiveLimit(bitrateReceiveLimit); 393 | 394 | return parserReturn::Processed; 395 | } 396 | #endif 397 | return parserReturn::UnexpectedData; 398 | } 399 | }; 400 | 401 | END_APPLEMIDI_NAMESPACE 402 | --------------------------------------------------------------------------------