├── 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 [](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 |
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 |
--------------------------------------------------------------------------------