diff --git a/.travis.yml b/.travis.yml index f189e73..0446ebf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,18 @@ cache: install: - pip install -U platformio - platformio update - - platformio lib -g install 62@5.0.0 870 872 + - platformio lib -g install 62@5.0.0 870 872 236 script: -- pio ci --board=uno --lib=. examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino; +- pio ci --board=uno --lib=. examples/AVR_Callbacks/AVR_Callbacks.ino +- pio ci --board=uno --lib=. examples/AVR_Directory/AVR_Directory.ino +- pio ci --board=uno --lib=. examples/AVR_Initiator/AVR_Initiator.ino +- pio ci --board=uno --lib=. examples/AVR_MinMemUsage/AVR_MinMemUsage.ino +- pio ci --board=uno --lib=. examples/AVR_MultipleSessions/AVR_MultipleSessions.ino +- pio ci --board=uno --lib=. examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino +- pio ci --board=uno --lib=. examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino +- pio ci --board=uno --lib=. examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino +- pio ci --board=uno --lib=. examples/AVR_SysEx/AVR_SysEx.ino +- pio ci --board=mkrzero --lib=. examples/SAMD_Bonjour/SAMD_Bonjour.ino +- pio ci --board=huzzah --lib=. examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino +- pio ci --board=featheresp32 --lib=. examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino diff --git a/README.md b/README.md index 89b3b0a..ab438c2 100755 --- a/README.md +++ b/README.md @@ -5,12 +5,18 @@ Enables an Arduino with IP/UDP capabilities (Ethernet shield, ESP8266, ESP32, .. ## Features * Build on top of the popular [FortySevenEffects MIDI library](https://github.com/FortySevenEffects/arduino_midi_library) -* Tested with AppleMIDI on Mac OS (Catalina) and using [rtpMIDI](https://www.tobias-erichsen.de/software/rtpmidi.html) from Tobias Erichsen on Windows 10 +* 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 * Send and receive all MIDI messages * Uses callbacks to receive MIDI commands (no need for polling) * Automatic instantiation of AppleMIDI object (see at the end of 'AppleMidi.h') * Compiles on Arduino, MacOS (XCode) and Windows (MSVS) +## New in 3.0.0 +* Bug Fixes (long session names get cropped) +* Reduced memory footprint (see AVR_MinMemUsage example and note below) +* Extended and revised callbacks to receive AppleMIDI protocol feedback (see AVR_Callbacks example) +* Who may connect to me (Directory) (see AVR_Directory example) + ## Installation From the Arduino IDE Library Manager, search for AppleMIDI @@ -57,21 +63,31 @@ More usages in the [examples](https://github.com/lathoub/Arduino-AppleMIDI-Libra * ESP32 (Adafruit HUZZAH32 – ESP32 Feather Board) * Teensy 3.2 * Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500 - + +## Network Shields +* Arduino Ethernet shield (Wiznet W5100 and W5500) +* Arduino Wifi R3 shield +* MKR ETH shield +* Teensy WIZ820io W5200 + ## Memory usage +Out of the box, this library has been setup to use a minimum amount of memory. Extended callbacks are not enabled by default, and can be anabled by USE_EXT_CALLBACKS. See the callback examamples. + This library is not using any dynamic memory allocation methods - all buffers have a fixed size, set in the `AppleMIDI_Settings.h` file, avoiding potential memory leaks and memory fragmentation. The minimum buffer size (`MaxBufferSize`) should be set to 64 bytes (also the default). Setting it to a higher value will make sending larger SysEx messages more efficiant (large SysEx messages are chopped in pieces, the larger the buffer, the less pieces needed), at the price of a bigger memory footprint. `MaxNumberOfParticipants` is another way to cut memory - each particpants uses approx 300 bytes. Default number of participants is 1 (using 2 sockets). Beware: the number of sockets on the Arduino is limited. The W5100 support 4, the W5200 and W5500 based IP chips can use 8 sockets. (Each participant uses 2 sockets: port 5004 and 5004+1). (Base port can be set in `APPLEMIDI_CREATE_DEFAULT_INSTANCE`) - -## Network Shields -* Arduino Ethernet shield (Wiznet W5100 and W5500) -* Arduino Wifi R3 shield -* MKR ETH shield -* Teensy WIZ820io W5200 - + +Reduce the memory footprint by a further 500 bytes by `#define NO_SESSION_NAME` before `#include `. This will leave out all the code to manage the optional session name. By default the session name is kept. + +Even further reduce the memory footprint by `#define ONE_PARTICIPANT` limiting the number of particpants to just 1. +On an UNO the absolute minimum memory footprint is 21966 bytes (68%) and 945 global variables (46%). For a Leonardo that is 24906 bytes (86%) and 1111 bytes (43%) of global variables. + +## Notes +Session names can get really long on Macs (eg 'Macbook Pro of Johann Gambolputty .. von Hautkopft of Ulm') and will be trunctated to the `MaxSessionNameLen` (as set in the settings file). + ## Arduino IDE (arduino.cc) * 1.8.13 diff --git a/examples/AVR_Callbacks/AVR_Callbacks.ino b/examples/AVR_Callbacks/AVR_Callbacks.ino new file mode 100644 index 0000000..6ca0634 --- /dev/null +++ b/examples/AVR_Callbacks/AVR_Callbacks.ino @@ -0,0 +1,151 @@ +#include + +#define USE_EXT_CALLBACKS +#define SerialMon Serial +#define APPLEMIDI_DEBUG SerialMon +#include + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; + +unsigned long t1 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + DBG_SETUP(115200); + DBG("Das Booting"); + + if (Ethernet.begin(mac) == 0) { + DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + DBG(F("Select and then press the Connect button")); + DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(); + + // Normal callbacks - always available + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + DBG(F("Disconnected"), ssrc); + }); + + // Extended callback, only available when defining USE_EXT_CALLBACKS + AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) { + DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr); + }); + AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) { + DBG(F("an rtpMidiMessage has been sent"), rtpMidi.flags); + }); + AppleMIDI.setHandleReceivedRtp([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const APPLEMIDI_NAMESPACE::Rtp_t & rtp, const int32_t& latency) { + DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency"); + }); + AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) { + DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value); + }); + AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleException(OnAppleMidiException); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + DBG(F("NoteOff"), note); + }); + + DBG(F("Sending MIDI messages every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send a note every second + // (dont cáll delay(1000) as it will stall the pipeline) + if ((isConnected > 0) && (millis() - t1) > 1000) + { + t1 = millis(); + + byte note = random(1, 127); + byte velocity = 55; + byte channel = 1; + + DBG(F("\nsendNoteOn"), note, velocity, channel); + MIDI.sendNoteOn(note, velocity, channel); + //MIDI.sendNoteOff(note, velocity, channel); + + } +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { + switch (e) + { + case APPLEMIDI_NAMESPACE::Exception::BufferFullException: + DBG(F("*** BufferFullException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParseException: + DBG(F("*** ParseException")); + break; + case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: + DBG(F("*** TooManyParticipantsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: + DBG(F("*** UnexpectedInviteException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: + DBG(F("*** ParticipantNotFoundException"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: + DBG(F("*** ComputerNotInDirectory"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: + DBG(F("*** NotAcceptingAnyone"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: + DBG(F("*** ListenerTimeOutException")); + break; + case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: + DBG(F("*** MaxAttemptsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: + DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); + break; + case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped: + DBG(F("*** SendPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: + DBG(F("*** ReceivedPacketsDropped"), value); + break; + } +} diff --git a/examples/AVR_Directory/AVR_Directory.ino b/examples/AVR_Directory/AVR_Directory.ino new file mode 100644 index 0000000..7d0f1b8 --- /dev/null +++ b/examples/AVR_Directory/AVR_Directory.ino @@ -0,0 +1,86 @@ +#include + +#define USE_DIRECTORY +#define SerialMon Serial +#define APPLEMIDI_DEBUG SerialMon +#include + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; + +unsigned long t1 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + DBG_SETUP(115200); + DBG("Booting"); + + if (Ethernet.begin(mac) == 0) { + DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 63)); + AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 66)); +// AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::None; + AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::OnlyComputersInMyDirectory; +// AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::Anyone; + + DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + DBG(F("Select and then press the Connect button")); + DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(); + + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + DBG(F("NoteOff"), note); + }); + + DBG(F("Sending MIDI messages every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send a note every second + // (dont cáll delay(1000) as it will stall the pipeline) + if ((isConnected > 0) && (millis() - t1) > 1000) + { + t1 = millis(); + + byte note = random(1, 127); + byte velocity = 55; + byte channel = 1; + + MIDI.sendNoteOn(note, velocity, channel); + // MIDI.sendNoteOff(note, velocity, channel); + } +} diff --git a/examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino b/examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino new file mode 100644 index 0000000..28517b4 --- /dev/null +++ b/examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino @@ -0,0 +1,79 @@ +#include // from https://github.com/sstaub/Ethernet3 + +#define SerialMon Serial +#define APPLEMIDI_DEBUG SerialMon +#include + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; + +unsigned long t1 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + DBG_SETUP(115200); + DBG("Booting"); + + if (Ethernet.begin(mac) == 0) { + DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + DBG(F("Select and then press the Connect button")); + DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(); + + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + DBG(F("NoteOff"), note); + }); + + DBG(F("Sending MIDI messages every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send a note every second + // (dont cáll delay(1000) as it will stall the pipeline) + if ((isConnected > 0) && (millis() - t1) > 1000) + { + t1 = millis(); + + byte note = random(1, 127); + byte velocity = 55; + byte channel = 1; + + MIDI.sendNoteOn(note, velocity, channel); +// MIDI.sendNoteOff(note, velocity, channel); + } +} diff --git a/examples/EthernetShield_Initiator/EthernetShield_Initiator.ino b/examples/AVR_Initiator/AVR_Initiator.ino similarity index 87% rename from examples/EthernetShield_Initiator/EthernetShield_Initiator.ino rename to examples/AVR_Initiator/AVR_Initiator.ino index 1201965..b116c22 100644 --- a/examples/EthernetShield_Initiator/EthernetShield_Initiator.ino +++ b/examples/AVR_Initiator/AVR_Initiator.ino @@ -1,8 +1,8 @@ #include +#define APPLEMIDI_INITIATOR #define SerialMon Serial #define APPLEMIDI_DEBUG SerialMon -#define APPLEMIDI_INITIATOR #include // Enter a MAC address for your controller below. @@ -12,7 +12,7 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); @@ -34,14 +34,14 @@ void setup() MIDI.begin(); AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); - + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { DBG(F("NoteOn"), note); }); @@ -70,7 +70,7 @@ void loop() // send note on/off every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) + if ((isConnected > 0) && (millis() - t1) > 1000) { t1 = millis(); @@ -78,7 +78,7 @@ void loop() byte velocity = 55; byte channel = 1; - // MIDI.sendNoteOn(note, velocity, channel); - // MIDI.sendNoteOff(note, velocity, channel); + MIDI.sendNoteOn(note, velocity, channel); + MIDI.sendNoteOff(note, velocity, channel); } } diff --git a/examples/AVR_MinMemUsage/AVR_MinMemUsage.ino b/examples/AVR_MinMemUsage/AVR_MinMemUsage.ino new file mode 100644 index 0000000..165a8cb --- /dev/null +++ b/examples/AVR_MinMemUsage/AVR_MinMemUsage.ino @@ -0,0 +1,64 @@ +#include + +#define ONE_PARTICIPANT +#define NO_SESSION_NAME +#include + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED +}; + +unsigned long t1 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + if (Ethernet.begin(mac) == 0) for (;;); + + MIDI.begin(); + + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char*) { + isConnected++; + digitalWrite(LED_BUILTIN, HIGH); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + digitalWrite(LED_BUILTIN, LOW); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + digitalWrite(LED_BUILTIN, LOW); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + digitalWrite(LED_BUILTIN, HIGH); + }); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send a note every second + // (dont cáll delay(1000) as it will stall the pipeline) + if ((isConnected > 0) && (millis() - t1) > 1000) + { + t1 = millis(); + + MIDI.sendNoteOn(54, 100, 1); + } +} diff --git a/examples/EthernetShield_MultipleSessions/EthernetShield_MultipleSessions.ino b/examples/AVR_MultipleSessions/AVR_MultipleSessions.ino similarity index 88% rename from examples/EthernetShield_MultipleSessions/EthernetShield_MultipleSessions.ino rename to examples/AVR_MultipleSessions/AVR_MultipleSessions.ino index 293d659..2f49232 100644 --- a/examples/EthernetShield_MultipleSessions/EthernetShield_MultipleSessions.ino +++ b/examples/AVR_MultipleSessions/AVR_MultipleSessions.ino @@ -11,11 +11,15 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI1, "Arduino1", DEFAULT_CONTROL_PORT); APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI2, "Arduino2", DEFAULT_CONTROL_PORT + 2); +void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t&, const char*); +void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t &); +void OnMidiNoteOn(byte channel, byte note, byte velocity); + // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- @@ -32,7 +36,7 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI1.getPort(), "(Name", AppleMIDI1.getName(), ")"); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI2.getPort(), "(Name", AppleMIDI2.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); // Listen for MIDI messages on channel 1 @@ -63,7 +67,7 @@ void loop() // send note on/off every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) + if ((isConnected > 0) && (millis() - t1) > 1000) { t1 = millis(); @@ -83,16 +87,16 @@ void loop() // rtpMIDI session. Device connected // ----------------------------------------------------------------------------- void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); } // ----------------------------------------------------------------------------- // rtpMIDI session. Device disconnected // ----------------------------------------------------------------------------- void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); } // ----------------------------------------------------------------------------- diff --git a/examples/EthernetShield_NonDefaultSession/EthernetShield_NonDefaultSession.ino b/examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino similarity index 87% rename from examples/EthernetShield_NonDefaultSession/EthernetShield_NonDefaultSession.ino rename to examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino index e97c356..fef1455 100644 --- a/examples/EthernetShield_NonDefaultSession/EthernetShield_NonDefaultSession.ino +++ b/examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino @@ -11,7 +11,7 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected = false; +int8_t isConnected = 0; // Non default portnr APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "MyNamedArduino", 5200); @@ -31,18 +31,18 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); MIDI.begin(); AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); DBG(F("Send MIDI messages every second")); @@ -58,7 +58,7 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) + if ((isConnected > 0) && (millis() - t1) > 1000) { t1 = millis(); diff --git a/examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino b/examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino similarity index 88% rename from examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino rename to examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino index 066fbbc..d5fd0b1 100644 --- a/examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino +++ b/examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino @@ -11,7 +11,7 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); @@ -30,19 +30,19 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); MIDI.begin(); // Stay informed on connection status AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { @@ -65,7 +65,7 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) + if ((isConnected > 0) && (millis() - t1) > 1000) { t1 = millis(); diff --git a/examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino b/examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino similarity index 87% rename from examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino rename to examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino index da139f0..e4c1bad 100644 --- a/examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino +++ b/examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino @@ -1,5 +1,6 @@ #include +#define USE_EXT_CALLBACKS #define SerialMon Serial #define APPLEMIDI_DEBUG SerialMon #include @@ -11,10 +12,14 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); +void OnAppleMidiStartReceived(const APPLEMIDI_NAMESPACE::ssrc_t&); +void OnAppleMidiReceivedByte(const APPLEMIDI_NAMESPACE::ssrc_t&, byte); +void OnAppleMidiEndReceive(const APPLEMIDI_NAMESPACE::ssrc_t&); + // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- @@ -30,19 +35,19 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); MIDI.begin(); // check: zien we de connecttion binnenkomen?? Anders terug een ref van maken AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); AppleMIDI.setHandleStartReceivedMidi(OnAppleMidiStartReceived); AppleMIDI.setHandleReceivedMidi(OnAppleMidiReceivedByte); diff --git a/examples/EthernetShield_SysEx/EthernetShield_SysEx.ino b/examples/AVR_SysEx/AVR_SysEx.ino similarity index 92% rename from examples/EthernetShield_SysEx/EthernetShield_SysEx.ino rename to examples/AVR_SysEx/AVR_SysEx.ino index 2296d8c..e258848 100644 --- a/examples/EthernetShield_SysEx/EthernetShield_SysEx.ino +++ b/examples/AVR_SysEx/AVR_SysEx.ino @@ -11,7 +11,7 @@ byte mac[] = { }; unsigned long t1 = millis(); -bool isConnected; +int8_t isConnected = 0; byte sysex14[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0xF7 }; byte sysex15[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0x4D, 0xF7 }; @@ -45,16 +45,16 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); MIDI.begin(); @@ -73,9 +73,9 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) + if ((isConnected > 0) && (millis() - t1) > 1000) { - MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); + // MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); t1 = millis(); } } diff --git a/examples/ESP32_Callbacks/ESP32_Callbacks.ino b/examples/ESP32_Callbacks/ESP32_Callbacks.ino deleted file mode 100644 index a431508..0000000 --- a/examples/ESP32_Callbacks/ESP32_Callbacks.ino +++ /dev/null @@ -1,187 +0,0 @@ -#include - -#define SerialMon Serial -#define APPLEMIDI_DEBUG SerialMon -#include - -#include "arduino_secrets.h" // contains SECRET_SSID and SECRET_PASS - -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -void setup() { - DBG_SETUP(115200); - DBG("Booting"); - - WiFi.begin(SECRET_SSID, SECRET_PASS); - while (WiFi.status() != WL_CONNECTED) { - delay(1000); - DBG("Establishing connection to WiFi.."); - } - DBG("Connected to network"); - - DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); - DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); - DBG(F("Then open a MIDI listener and monitor incoming notes")); - - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - AppleMIDI.setHandleError(OnAppleMidiError); - - MIDI.begin(); - - MIDI.setHandleNoteOff(OnMidiNoteOff); - MIDI.setHandleNoteOn(OnMidiNoteOn); - MIDI.setHandleAfterTouchPoly(OnAfterTouchPoly); - MIDI.setHandleControlChange(OnControlChange); - MIDI.setHandleProgramChange(OnProgramChange); - MIDI.setHandleAfterTouchChannel(OnAfterTouchChannel); - MIDI.setHandlePitchBend(OnPitchBend); - MIDI.setHandleSystemExclusive(OnSystemExclusive); - MIDI.setHandleTimeCodeQuarterFrame(OnTimeCodeQuarterFrame); - MIDI.setHandleSongPosition(OnSongPosition); - MIDI.setHandleSongSelect(OnSongSelect); - MIDI.setHandleTuneRequest(OnTuneRequest); - MIDI.setHandleClock(OnClock); - MIDI.setHandleStart(OnStart); - MIDI.setHandleContinue(OnContinue); - MIDI.setHandleStop(OnStop); - MIDI.setHandleActiveSensing(OnActiveSensing); - MIDI.setHandleSystemReset(OnSystemReset); - - DBG(F("Ready")); -} - -void loop() { - MIDI.read(); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; -DBG(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void OnAppleMidiError(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, int32_t err) { - switch (err) - { - case APPLEMIDI_NAMESPACE::Exception::BufferFullException: - DBG(F("*** BufferFullException")); - break; - case APPLEMIDI_NAMESPACE::Exception::ParseException: - DBG(F("*** ParseException")); - break; - case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: - DBG(F("*** TooManyParticipantsException")); - break; - case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: - DBG(F("*** UnexpectedInviteException")); - break; - case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: - DBG(F("*** ParticipantNotFoundException")); - break; - case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: - DBG(F("*** ListenerTimeOutException")); - break; - case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: - DBG(F("*** MaxAttemptsException")); - break; - case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: - DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)")); - break; - } -} - -//------ - -static void OnMidiNoteOff(byte channel, byte note, byte velocity) { - DBG(F("in\tNote off"), note, " Velocity", velocity, "\t", channel); -} - -static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - DBG(F("in\tNote on"), note, " Velocity", velocity, "\t", channel); -} - -static void OnAfterTouchPoly(byte channel, byte note, byte velocity) { - DBG(F("AfterTouchPoly. Channel:"), channel, " Note:", note, " Velocity:", velocity); -} - -static void OnControlChange(byte channel, byte note, byte velocity) { - DBG(F("ControlChange. Channel:"), channel, " Note:", note, " Velocity:", velocity); -} - -static void OnProgramChange(byte channel, byte note) { - DBG(F("ProgramChange. Channel:"), channel, " Note:", note); -} - -static void OnAfterTouchChannel(byte channel, byte note) { - DBG(F("AfterTouchChannel. Channel:"), channel, " Note:", note); -} - -static void OnPitchBend(byte channel, int note) { - DBG(F("PitchBend. Channel:"), channel, " Note:", note); -} - -static void OnSystemExclusive(byte* data, unsigned size) { - DBG(F("System exclusive")); - for (int i = 0; i < size; i++) { - SerialMon.print(F(" 0x")); - SerialMon.print(data[i], HEX); - } - SerialMon.println(); -} - -static void OnTimeCodeQuarterFrame(byte data) { - DBG(F("TimeCodeQuarterFrame")); -} - -static void OnSongPosition(unsigned beats) { - DBG(F("SongPosition:"), beats); -} - -static void OnSongSelect(byte songNumber) { - DBG(F("SongSelect"),songNumber); -} - -static void OnTuneRequest() { - DBG(F("Tune request")); -} - -static void OnClock() { - DBG(F("Clock")); -} - -static void OnStart() { - DBG(F("Start")); -} - -static void OnContinue() { - DBG(F("Continue")); -} - -static void OnStop() { - DBG(F("Stop")); -} - -static void OnActiveSensing() { - DBG(F("ActiveSensing")); -} - -static void OnSystemReset() { - DBG(F("SystemReset")); -} diff --git a/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino b/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino new file mode 100644 index 0000000..1fee9a5 --- /dev/null +++ b/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino @@ -0,0 +1,82 @@ +#include +#include +#include + +#define NO_SESSION_NAME +#define SerialMon Serial +#define APPLEMIDI_DEBUG SerialMon +#include + +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "password"; // your network password (use for WPA, or use as key for WEP) + +unsigned long t0 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + DBG_SETUP(115200); + DBG("Booting"); + + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + DBG("Establishing connection to WiFi.."); + } + DBG("Connected to network"); + + DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + DBG(F("Select and then press the Connect button")); + DBG(F("Then open a MIDI listener and monitor incoming notes")); + DBG(F("Listen to incoming MIDI commands")); + + MIDI.begin(); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + DBG(F("NoteOff"), note); + }); + + DBG(F("Sending NoteOn/Off of note 45, every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send a note every second + // (dont cáll delay(1000) as it will stall the pipeline) + if ((isConnected > 0) && (millis() - t0) > 1000) + { + t0 = millis(); + + byte note = 45; + byte velocity = 55; + byte channel = 1; + + MIDI.sendNoteOn(note, velocity, channel); + MIDI.sendNoteOff(note, velocity, channel); + } +} diff --git a/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino b/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino index 4552a6d..3385371 100644 --- a/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino +++ b/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino @@ -10,7 +10,7 @@ char ssid[] = "yourNetwork"; // your network SSID (name) char pass[] = "password"; // your network password (use for WPA, or use as key for WEP) unsigned long t0 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); @@ -32,19 +32,19 @@ void setup() DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); DBG(F("Listen to incoming MIDI commands")); MIDI.begin(); AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { @@ -67,7 +67,7 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t0) > 1000) + if ((isConnected > 0) && (millis() - t0) > 1000) { t0 = millis(); diff --git a/examples/MKR_ETH_Bonjour/MKR_ETH_Bonjour.ino b/examples/SAMD_Bonjour/SAMD_Bonjour.ino similarity index 100% rename from examples/MKR_ETH_Bonjour/MKR_ETH_Bonjour.ino rename to examples/SAMD_Bonjour/SAMD_Bonjour.ino diff --git a/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino b/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino index 195e12d..c7bc21d 100644 --- a/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino +++ b/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino @@ -2,10 +2,10 @@ #define APPLEMIDI_DEBUG SerialMon #include -#include "ETH_Helper.h" +#include "./ETH_Helper.h" unsigned long t0 = millis(); -bool isConnected = false; +int8_t isConnected = 0; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); @@ -19,24 +19,26 @@ void setup() ETH_startup(); - MDNS.begin(AppleMIDI.getName()); + if (!MDNS.begin(AppleMIDI.getName())) + DBG(F("Error setting up MDNS responder")); DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); - DBG(F("Add device named Arduino with Host"), ETH.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Add device named Arduino with Host"), ETH.localIP(), "Port", AppleMIDI.getPort()); + DBG(F("The device should also be visible in the directory as"), AppleMIDI.getName()); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); MIDI.begin(); AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); }); AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); }); - + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { DBG(F("NoteOn"), note); }); @@ -57,7 +59,7 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t0) > 1000) + if ((isConnected > 0) && (millis() - t0) > 1000) { t0 = millis(); @@ -66,6 +68,6 @@ void loop() byte channel = 1; MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); + // MIDI.sendNoteOff(note, velocity, channel); } } diff --git a/keywords.txt b/keywords.txt index 5f762b5..f25cf1d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -13,62 +13,17 @@ AppleMidi KEYWORD1 setHandleConnected KEYWORD2 setHandleDisconnected KEYWORD2 -setHandleError KEYWORD2 +setHandleException KEYWORD2 setHandleStartReceivedMidi KEYWORD2 setHandleReceivedMidi KEYWORD2 setHandleEndReceivedMidi KEYWORD2 setHandleReceivedRtp KEYWORD2 - -setHandleNoteOff KEYWORD2 -setHandleNoteOn KEYWORD2 -setHandleAfterTouchPoly KEYWORD2 -setHandleControlChange KEYWORD2 -setHandleProgramChange KEYWORD2 -setHandleAfterTouchChannel KEYWORD2 -setHandlePitchBend KEYWORD2 -setHandleSystemExclusive KEYWORD2 -setHandleTimeCodeQuarterFrame KEYWORD2 -setHandleSongPosition KEYWORD2 -setHandleSongSelect KEYWORD2 -setHandleTuneRequest KEYWORD2 -setHandleClock KEYWORD2 -setHandleStart KEYWORD2 -setHandleContinue KEYWORD2 -setHandleStop KEYWORD2 -setHandleActiveSensing KEYWORD2 -setHandleSystemReset KEYWORD2 - -begin KEYWORD2 -run KEYWORD2 -sendNoteOn KEYWORD2 -sendNoteOff KEYWORD2 -sendAfterTouchPoly KEYWORD2 -sendControlChange KEYWORD2 -sendProgramChange KEYWORD2 -sendAfterTouchChannel KEYWORD2 -sendPitchBend KEYWORD2 -sendSystemEx KEYWORD2 -sendTimeCodeQuarterFrame KEYWORD2 -sendSongPosition KEYWORD2 -sendSongSelect KEYWORD2 -sendTuneRequest KEYWORD2 -sendClock KEYWORD2 -sendStart KEYWORD2 -sendContinue KEYWORD2 -sendStop KEYWORD2 -sendActiveSensing KEYWORD2 -sendSystemReset KEYWORD2 -sendTimeCodeQuarterFrame KEYWORD2 -sendSysEx KEYWORD2 -sendAfterTouch KEYWORD2 -sendPolyPressure KEYWORD2 -sendTick KEYWORD2 +setHandleSendRtp KEYWORD2 ####################################### # Instances (KEYWORD3) ####################################### AppleMIDI KEYWORD3 -MIDI KEYWORD3 ####################################### # Constants (LITERAL1) diff --git a/library.properties b/library.properties index add0477..dc7f7f3 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=AppleMIDI -version=2.2.0 +version=3.0.0 author=lathoub maintainer=lathoub sentence=AppleMIDI (aka rtpMIDI) MIDI I/Os for Arduino diff --git a/src/AppleMIDI.h b/src/AppleMIDI.h index 86832a7..dc52ca4 100644 --- a/src/AppleMIDI.h +++ b/src/AppleMIDI.h @@ -50,22 +50,32 @@ class AppleMIDISession AppleMIDISession(const char *sessionName, const uint16_t port = DEFAULT_CONTROL_PORT) { this->port = port; +#ifdef KEEP_SESSION_NAME strncpy(this->localName, sessionName, DefaultSettings::MaxSessionNameLen); +#endif }; - void setHandleConnected(void (*fptr)(const ssrc_t&, const char*)) { _connectedCallback = fptr; } - void setHandleDisconnected(void (*fptr)(const ssrc_t&)) { _disconnectedCallback = fptr; } - void setHandleError(void (*fptr)(const ssrc_t&, int32_t)) { _exceptionCallback = fptr; } - void setHandleReceivedRtp(void (*fptr)(const ssrc_t&, const Rtp_t&, const int32_t&)) { _receivedRtpCallback = fptr; } - void setHandleStartReceivedMidi(void (*fptr)(const ssrc_t&)) { _startReceivedMidiByteCallback = fptr; } - void setHandleReceivedMidi(void (*fptr)(const ssrc_t&, byte)) { _receivedMidiByteCallback = fptr; } - void setHandleEndReceivedMidi(void (*fptr)(const ssrc_t&)) { _endReceivedMidiByteCallback = fptr; } - - const char* getName() { return this->localName; }; - const uint16_t getPort() { return this->port; }; - const ssrc_t getSynchronizationSource() { return this->ssrc; }; + void setHandleConnected (void (*fptr)(const ssrc_t&, const char*)) { _connectedCallback = fptr; } + void setHandleDisconnected (void (*fptr)(const ssrc_t&)) { _disconnectedCallback = fptr; } +#ifdef USE_EXT_CALLBACKS + void setHandleException (void (*fptr)(const ssrc_t&, const Exception&, const int32_t value)) { _exceptionCallback = fptr; } + void setHandleReceivedRtp (void (*fptr)(const ssrc_t&, const Rtp_t&, const int32_t&)) { _receivedRtpCallback = fptr; } + void setHandleStartReceivedMidi (void (*fptr)(const ssrc_t&)) { _startReceivedMidiByteCallback = fptr; } + void setHandleReceivedMidi (void (*fptr)(const ssrc_t&, byte)) { _receivedMidiByteCallback = fptr; } + void setHandleEndReceivedMidi (void (*fptr)(const ssrc_t&)) { _endReceivedMidiByteCallback = fptr; } + void setHandleSentRtp (void (*fptr)(const Rtp_t&)) { _sentRtpCallback = fptr; } + void setHandleSentRtpMidi (void (*fptr)(const RtpMIDI_t&)) { _sentRtpMidiCallback = fptr; } +#endif +#ifdef KEEP_SESSION_NAME + const char* getName() const { return this->localName; }; void setName(const char *sessionName) { strncpy(this->localName, sessionName, DefaultSettings::MaxSessionNameLen); }; +#else + const char* getName() const { return nullptr; }; + void setName(const char *sessionName) { }; +#endif + const uint16_t getPort() const { return this->port; }; + const ssrc_t getSynchronizationSource() const { return this->ssrc; }; #ifdef APPLEMIDI_INITIATOR bool sendInvite(IPAddress ip, uint16_t port = DEFAULT_CONTROL_PORT); @@ -73,8 +83,14 @@ class AppleMIDISession void sendEndSession(); public: + // Override default thruActivated. Must be false for all packet based messages static const bool thruActivated = false; +#ifdef USE_DIRECTORY + Deque directory; + WhoCanConnectToMe whoCanConnectToMe = Anyone; +#endif + void begin() { _appleMIDIParser.session = this; @@ -90,7 +106,6 @@ class AppleMIDISession // this is our SSRC // // NOTE: Arduino random only goes to INT32_MAX (not UINT32_MAX) - this->ssrc = random(1, INT32_MAX) * 2; controlPort.begin(port); @@ -128,7 +143,11 @@ class AppleMIDISession // of what we are to send (The RtpMidi protocol start with writing the // length of the buffer). So we'll copy to a buffer in the 'write' method, // and actually serialize for real in the endTransmission method - return (dataPort.remoteIP() != INADDR_NONE && participants.size() > 0); +#ifndef ONE_PARTICIPANT + return (dataPort.remoteIP() != (IPAddress)INADDR_NONE && participants.size() > 0); +#else + return (dataPort.remoteIP() != (IPAddress)INADDR_NONE && participant.ssrc != 0); +#endif }; void write(byte byte) @@ -150,8 +169,10 @@ class AppleMIDISession } else { - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, BufferFullException); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, BufferFullException, 0); +#endif } } @@ -182,13 +203,15 @@ class AppleMIDISession if (inMidiBuffer.size() > 0) return true; - // read packets from both UDP sockets - readDataPackets(); // from socket into dataBuffer - readControlPackets(); // from socket into controlBuffer + { + // read packets from both UDP sockets + readDataPackets(); // from socket into dataBuffer + readControlPackets(); // from socket into controlBuffer - // parses buffer and places MIDI into inMidiBuffer - parseDataPackets(); // from dataBuffer into inMidiBuffer - parseControlPackets(); // from controlBuffer + // parses buffer and places MIDI into inMidiBuffer + parseDataPackets(); // from dataBuffer into inMidiBuffer + parseControlPackets(); // from controlBuffer + } manageReceiverFeedback(); manageSynchronization(); @@ -209,7 +232,6 @@ class AppleMIDISession UdpClass dataPort; private: - // reading from the network RtpBuffer_t controlBuffer; RtpBuffer_t dataBuffer; @@ -219,13 +241,16 @@ class AppleMIDISession rtpMIDIParser _rtpMIDIParser; connectedCallback _connectedCallback = nullptr; + disconnectedCallback _disconnectedCallback = nullptr; +#ifdef USE_EXT_CALLBACKS startReceivedMidiByteCallback _startReceivedMidiByteCallback = nullptr; receivedMidiByteCallback _receivedMidiByteCallback = nullptr; endReceivedMidiByteCallback _endReceivedMidiByteCallback = nullptr; receivedRtpCallback _receivedRtpCallback = nullptr; - disconnectedCallback _disconnectedCallback = nullptr; + sentRtpCallback _sentRtpCallback = nullptr; + sentRtpMidiCallback _sentRtpMidiCallback = nullptr; exceptionCallback _exceptionCallback = nullptr; - +#endif // buffer for incoming and outgoing MIDI messages MidiBuffer_t inMidiBuffer; MidiBuffer_t outMidiBuffer; @@ -233,12 +258,17 @@ class AppleMIDISession rtpMidi_Clock rtpMidiClock; ssrc_t ssrc = 0; - char localName[DefaultSettings::MaxSessionNameLen + 1]; uint16_t port = DEFAULT_CONTROL_PORT; +#ifdef ONE_PARTICIPANT + Participant participant; +#else Deque, Settings::MaxNumberOfParticipants> participants; - int32_t latencyAdjustment = 0; - uint16_t sequenceNr = random(1, UINT16_MAX); - +#endif + +#ifdef KEEP_SESSION_NAME + char localName[DefaultSettings::MaxSessionNameLen + 1]; +#endif + private: void readControlPackets(); void readDataPackets(); @@ -266,7 +296,7 @@ class AppleMIDISession void EndReceivedMidi(); // Helpers - void writeInvitation (UdpClass &, IPAddress, uint16_t, AppleMIDI_Invitation_t &, const byte *command); + void writeInvitation (UdpClass &, const IPAddress &, const uint16_t &, AppleMIDI_Invitation_t &, const byte *command); void writeReceiverFeedback(const IPAddress &, const uint16_t &, AppleMIDI_ReceiverFeedback_t &); void writeSynchronization (const IPAddress &, const uint16_t &, AppleMIDI_Synchronization_t &); void writeEndSession (const IPAddress &, const uint16_t &, AppleMIDI_EndSession_t &); @@ -280,15 +310,19 @@ class AppleMIDISession void manageSessionInvites(); void manageSynchronization(); - void manageSynchronizationListener(size_t); void manageSynchronizationInitiator(); - void manageSynchronizationInitiatorHeartBeat(size_t); + void manageSynchronizationInitiatorHeartBeat(Participant*); void manageSynchronizationInitiatorInvites(size_t); void sendSynchronization(Participant*); - Participant* getParticipantBySSRC(const ssrc_t ssrc); - Participant* getParticipantByInitiatorToken(const uint32_t initiatorToken); +#ifndef ONE_PARTICIPANT + Participant* getParticipantBySSRC(const ssrc_t&); + Participant* getParticipantByInitiatorToken(const uint32_t& initiatorToken); +#endif +#ifdef USE_DIRECTORY + bool IsComputerInDirectory(IPAddress) const; +#endif }; END_APPLEMIDI_NAMESPACE diff --git a/src/AppleMIDI.hpp b/src/AppleMIDI.hpp index e9a041e..aa4d656 100644 --- a/src/AppleMIDI.hpp +++ b/src/AppleMIDI.hpp @@ -30,9 +30,10 @@ void AppleMIDISession::parseControlPackets() auto retVal = _appleMIDIParser.parse(controlBuffer, amPortType::Control); if (retVal == parserReturn::UnexpectedData) { - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, ParseException); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParseException, 0); +#endif controlBuffer.pop_front(); } } @@ -81,9 +82,10 @@ void AppleMIDISession::parseDataPackets() || retVal2 == parserReturn::NotSureGiveMeMoreData) break; // one or the other buffer does not have enough data - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, UnexpectedParseException); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UnexpectedParseException, 0); +#endif dataBuffer.pop_front(); } } @@ -91,7 +93,7 @@ void AppleMIDISession::parseDataPackets() template void AppleMIDISession::ReceivedInvitation(AppleMIDI_Invitation_t &invitation, const amPortType &portType) { - if (portType == amPortType::Control) + if (portType == amPortType::Control) ReceivedControlInvitation(invitation); else ReceivedDataInvitation(invitation); @@ -100,27 +102,59 @@ void AppleMIDISession::ReceivedInvitation(AppleMID template void AppleMIDISession::ReceivedControlInvitation(AppleMIDI_Invitation_t &invitation) { - // ignore invitation of a participant already in the participant list - if (NULL != getParticipantBySSRC(invitation.ssrc)) - return; - - // advertise our own session name #ifdef KEEP_SESSION_NAME strncpy(invitation.sessionName, localName, DefaultSettings::MaxSessionNameLen); invitation.sessionName[DefaultSettings::MaxSessionNameLen] = '\0'; #endif - - if (participants.full()) - { + +#ifdef USE_DIRECTORY + switch (whoCanConnectToMe) { + case None: writeInvitation(controlPort, controlPort.remoteIP(), controlPort.remotePort(), invitation, amInvitationRejected); - - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, TooManyParticipantsException); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, NotAcceptingAnyone, 0); +#endif + return; + case OnlyComputersInMyDirectory: + if (!IsComputerInDirectory(controlPort.remoteIP())) { + writeInvitation(controlPort, controlPort.remoteIP(), controlPort.remotePort(), invitation, amInvitationRejected); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ComputerNotInDirectory, 0); +#endif + return; + } + case Anyone: + break; + } +#endif + // ignore invitation of a participant already in the participant list +#ifndef ONE_PARTICIPANT + if (nullptr != getParticipantBySSRC(invitation.ssrc)) +#else + if (participant.ssrc == invitation.ssrc) +#endif + return; + +#ifndef ONE_PARTICIPANT + if (participants.full()) +#else + if (participant.ssrc != 0) +#endif + { + writeInvitation(controlPort, controlPort.remoteIP(), controlPort.remotePort(), invitation, amInvitationRejected); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, TooManyParticipantsException, 0); +#endif return; } +#ifndef ONE_PARTICIPANT Participant participant; +#endif participant.kind = Listener; participant.ssrc = invitation.ssrc; participant.remoteIP = controlPort.remoteIP(); @@ -130,7 +164,9 @@ void AppleMIDISession::ReceivedControlInvitation(A strncpy(participant.sessionName, invitation.sessionName, DefaultSettings::MaxSessionNameLen); #endif +#ifndef ONE_PARTICIPANT participants.push_back(participant); +#endif writeInvitation(controlPort, participant.remoteIP, participant.remotePort, invitation, amInvitationAccepted); } @@ -138,14 +174,19 @@ void AppleMIDISession::ReceivedControlInvitation(A template void AppleMIDISession::ReceivedDataInvitation(AppleMIDI_Invitation &invitation) { - auto participant = getParticipantBySSRC(invitation.ssrc); - if (NULL == participant) +#ifndef ONE_PARTICIPANT + auto pParticipant = getParticipantBySSRC(invitation.ssrc); +#else + auto pParticipant = (participant.ssrc == invitation.ssrc) ? &participant : nullptr; +#endif + if (nullptr == pParticipant) { writeInvitation(dataPort, dataPort.remoteIP(), dataPort.remotePort(), invitation, amInvitationRejected); - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, ParticipantNotFoundException); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParticipantNotFoundException, invitation.ssrc); +#endif return; } @@ -154,16 +195,16 @@ void AppleMIDISession::ReceivedDataInvitation(Appl // of the ssrc here. auto ssrc_ = invitation.ssrc; - writeInvitation(dataPort, participant->remoteIP, participant->remotePort + 1, invitation, amInvitationAccepted); + writeInvitation(dataPort, pParticipant->remoteIP, pParticipant->remotePort + 1, invitation, amInvitationAccepted); - participant->kind = Listener; + pParticipant->kind = Listener; // Inform that we have an established connection - if (NULL != _connectedCallback) + if (nullptr != _connectedCallback) #ifdef KEEP_SESSION_NAME _connectedCallback(ssrc_, invitation.sessionName); #else - _connectedCallback(ssrc_, NULL); + _connectedCallback(ssrc_, nullptr); #endif } @@ -172,6 +213,7 @@ void AppleMIDISession::ReceivedBitrateReceiveLimit { } +#ifdef APPLEMIDI_INITIATOR template void AppleMIDISession::ReceivedInvitationAccepted(AppleMIDI_InvitationAccepted_t &invitationAccepted, const amPortType &portType) { @@ -184,32 +226,40 @@ void AppleMIDISession::ReceivedInvitationAccepted( template void AppleMIDISession::ReceivedControlInvitationAccepted(AppleMIDI_InvitationAccepted_t &invitationAccepted) { - auto participant = this->getParticipantByInitiatorToken(invitationAccepted.initiatorToken); - if (NULL == participant) +#ifndef ONE_PARTICIPANT + auto pParticipant = this->getParticipantByInitiatorToken(invitationAccepted.initiatorToken); +#else + auto pParticipant = (participant.initiatorToken == invitationAccepted.initiatorToken) ? &participant : nullptr; +#endif + if (nullptr == pParticipant) { return; } - participant->ssrc = invitationAccepted.ssrc; - participant->lastInviteSentTime = now - 1000; // forces invite to be send - participant->connectionAttempts = 0; // reset back to 0 - participant->invitationStatus = ControlInvitationAccepted; // step it up + pParticipant->ssrc = invitationAccepted.ssrc; + pParticipant->lastInviteSentTime = now - 1000; // forces invite to be send + pParticipant->connectionAttempts = 0; // reset back to 0 + pParticipant->invitationStatus = ControlInvitationAccepted; // step it up #ifdef KEEP_SESSION_NAME - strncpy(participant->sessionName, invitationAccepted.sessionName, DefaultSettings::MaxSessionNameLen); - participant->sessionName[DefaultSettings::MaxSessionNameLen] = '\0'; + strncpy(pParticipant->sessionName, invitationAccepted.sessionName, DefaultSettings::MaxSessionNameLen); + pParticipant->sessionName[DefaultSettings::MaxSessionNameLen] = '\0'; #endif } template void AppleMIDISession::ReceivedDataInvitationAccepted(AppleMIDI_InvitationAccepted_t &invitationAccepted) { - auto participant = this->getParticipantByInitiatorToken(invitationAccepted.initiatorToken); - if (NULL == participant) +#ifndef ONE_PARTICIPANT + auto pParticipant = this->getParticipantByInitiatorToken(invitationAccepted.initiatorToken); +#else + auto pParticipant = (participant.initiatorToken == invitationAccepted.initiatorToken) ? &participant : nullptr; +#endif + if (nullptr == pParticipant) { return; } - participant->invitationStatus = DataInvitationAccepted; + pParticipant->invitationStatus = DataInvitationAccepted; } template @@ -219,12 +269,16 @@ void AppleMIDISession::ReceivedInvitationRejected( { if (invitationRejected.ssrc == participants[i].ssrc) { +#ifndef ONE_PARTICIPANT participants.erase(i); - +#else + participant.ssrc = 0; +#endif return; } } } +#endif /*! \brief . @@ -256,9 +310,18 @@ user to choose between a new connection attempt or closing the session. template void AppleMIDISession::ReceivedSynchronization(AppleMIDI_Synchronization_t &synchronization) { - auto participant = getParticipantBySSRC(synchronization.ssrc); - if (NULL == participant) +#ifndef ONE_PARTICIPANT + auto pParticipant = getParticipantBySSRC(synchronization.ssrc); +#else + auto pParticipant = (participant.ssrc == synchronization.ssrc) ? &participant : nullptr; +#endif + if (nullptr == pParticipant) { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParticipantNotFoundException, synchronization.ssrc); +#endif + return; } @@ -298,41 +361,28 @@ void AppleMIDISession::ReceivedSynchronization(App case SYNC_CK0: /* From session APPLEMIDI_INITIATOR */ synchronization.timestamps[SYNC_CK1] = rtpMidiClock.Now(); synchronization.count = SYNC_CK1; - writeSynchronization(participant->remoteIP, participant->remotePort + 1, synchronization); + writeSynchronization(pParticipant->remoteIP, pParticipant->remotePort + 1, synchronization); break; case SYNC_CK1: /* From session LISTENER */ #ifdef APPLEMIDI_INITIATOR synchronization.timestamps[SYNC_CK2] = rtpMidiClock.Now(); synchronization.count = SYNC_CK2; - writeSynchronization(participant->remoteIP, participant->remotePort + 1, synchronization); - participant->synchronizing = false; + writeSynchronization(pParticipant->remoteIP, pParticipant->remotePort + 1, synchronization); + pParticipant->synchronizing = false; #endif break; case SYNC_CK2: /* From session APPLEMIDI_INITIATOR */ -#ifdef LATENCY_CALCULATION +#ifdef USE_EXT_CALLBACKS // each party can estimate the offset between the two clocks using the following formula - participant->offsetEstimate = (uint32_t)(((synchronization.timestamps[2] + synchronization.timestamps[0]) / 2) - synchronization.timestamps[1]); -/* - uint64_t remoteAverage = ((synchronization.timestamps[2] + synchronization.timestamps[0]) / 2); - uint64_t localAverage = synchronization.timestamps[1]; - - static uint64_t oldRemoteAverage = 0; - static uint64_t oldLocalAverage = 0; - - uint64_t r = (remoteAverage - oldRemoteAverage); - uint64_t l = (localAverage - oldLocalAverage); - - oldRemoteAverage = remoteAverage; - oldLocalAverage = localAverage; -*/ + pParticipant->offsetEstimate = (uint32_t)(((synchronization.timestamps[2] + synchronization.timestamps[0]) / 2) - synchronization.timestamps[1]); #endif break; } // All particpants need to check in regularly, // failing to do so will result in a lost connection. - participant->lastSyncExchangeTime = now; + pParticipant->lastSyncExchangeTime = now; } // The recovery journal mechanism requires that the receiver periodically @@ -344,22 +394,50 @@ void AppleMIDISession::ReceivedSynchronization(App template void AppleMIDISession::ReceivedReceiverFeedback(AppleMIDI_ReceiverFeedback_t &receiverFeedback) { - // We do not keep any recovery journals, no command history, nothing! - // If we did, then we can flush the previous sent buffer until receiverFeedback.sequenceNr + // We do not keep any recovery journals, no command history, nothing! + // Here is where you would correct if packets are dropped (send them again) +#ifndef ONE_PARTICIPANT + auto pParticipant = getParticipantBySSRC(receiverFeedback.ssrc); +#else + auto pParticipant = (participant.ssrc == receiverFeedback.ssrc) ? &participant : nullptr; +#endif + if (nullptr == pParticipant) { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParticipantNotFoundException, receiverFeedback.ssrc); +#endif + return; + } + + if (pParticipant->sendSequenceNr < receiverFeedback.sequenceNr) + { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(pParticipant->ssrc, SendPacketsDropped, pParticipant->sendSequenceNr - receiverFeedback.sequenceNr); +#endif + } } template void AppleMIDISession::ReceivedEndSession(AppleMIDI_EndSession_t &endSession) { +#ifndef ONE_PARTICIPANT for (size_t i = 0; i < participants.size(); i++) { - if (endSession.ssrc == participants[i].ssrc) + auto participant = participants[i]; +#else + { +#endif + if (endSession.ssrc == participant.ssrc) { - auto ssrc = participants[i].ssrc; + auto ssrc = participant.ssrc; +#ifndef ONE_PARTICIPANT participants.erase(i); - - if (NULL != _disconnectedCallback) +#else + participant.ssrc = 0; +#endif + if (nullptr != _disconnectedCallback) _disconnectedCallback(ssrc); return; @@ -367,26 +445,39 @@ void AppleMIDISession::ReceivedEndSession(AppleMID } } +#ifdef USE_DIRECTORY +template +bool AppleMIDISession::IsComputerInDirectory(IPAddress remoteIP) const +{ + for (size_t i = 0; i < directory.size(); i++) + if (remoteIP == directory[i]) + return true; + return false; +} +#endif + +#ifndef ONE_PARTICIPANT template -Participant* AppleMIDISession::getParticipantBySSRC(const ssrc_t ssrc) +Participant* AppleMIDISession::getParticipantBySSRC(const ssrc_t& ssrc) { for (size_t i = 0; i < participants.size(); i++) if (ssrc == participants[i].ssrc) return &participants[i]; - return NULL; + return nullptr; } template -Participant* AppleMIDISession::getParticipantByInitiatorToken(const uint32_t initiatorToken) +Participant* AppleMIDISession::getParticipantByInitiatorToken(const uint32_t& initiatorToken) { for (auto i = 0; i < participants.size(); i++) if (initiatorToken == participants[i].initiatorToken) return &participants[i]; - return NULL; + return nullptr; } +#endif template -void AppleMIDISession::writeInvitation(UdpClass &port, IPAddress remoteIP, uint16_t remotePort, AppleMIDI_Invitation_t & invitation, const byte *command) +void AppleMIDISession::writeInvitation(UdpClass &port, const IPAddress& remoteIP, const uint16_t& remotePort, AppleMIDI_Invitation_t & invitation, const byte *command) { if (port.beginPacket(remoteIP, remotePort)) { @@ -465,16 +556,21 @@ void AppleMIDISession::writeEndSession(const IPAdd template void AppleMIDISession::writeRtpMidiToAllParticipants() { +#ifndef ONE_PARTICIPANT for (size_t i = 0; i < participants.size(); i++) { - auto participant = &participants[i]; - writeRtpMidiBuffer(participant); + auto pParticipant = &participants[i]; + + writeRtpMidiBuffer(pParticipant); } +#else + writeRtpMidiBuffer(&participant); +#endif outMidiBuffer.clear(); } template -void AppleMIDISession::writeRtpMidiBuffer(Participant * participant) +void AppleMIDISession::writeRtpMidiBuffer(Participant* participant) { const IPAddress remoteIP = participant->remoteIP; const uint16_t remotePort = participant->remotePort + 1; @@ -486,7 +582,6 @@ void AppleMIDISession::writeRtpMidiBuffer(Particip rtp.vpxcc = 0b10000000; // TODO: fun with flags rtp.mpayload = PAYLOADTYPE_RTPMIDI; // TODO: set or unset marker rtp.ssrc = ssrc; - rtp.ssrc = htonl(rtp.ssrc); // https://developer.apple.com/library/ios/documentation/CoreMidi/Reference/MIDIServices_Reference/#//apple_ref/doc/uid/TP40010316-CHMIDIServiceshFunctions-SW30 // The time at which the events occurred, if receiving MIDI, or, if sending MIDI, @@ -502,12 +597,20 @@ void AppleMIDISession::writeRtpMidiBuffer(Particip // have an option to not transmit messages with future timestamps, to accommodate hardware not // prepared to defer rendering the messages until the proper time.) // - rtp.timestamp = (Settings::TimestampRtpPackets) ? htonl(rtpMidiClock.Now()) : 0; + rtp.timestamp = (Settings::TimestampRtpPackets) ? rtpMidiClock.Now() : 0; - // - sequenceNr++; // (modulo 2^16) modulo is automatically done for us () + // increment the sequenceNr + participant->sendSequenceNr++; - rtp.sequenceNr = sequenceNr; + rtp.sequenceNr = participant->sendSequenceNr; + +#ifdef USE_EXT_CALLBACKS + if (_sentRtpCallback) + _sentRtpCallback(rtp); +#endif + + rtp.timestamp = htonl(rtp.timestamp); + rtp.ssrc = htonl(rtp.ssrc); rtp.sequenceNr = htons(rtp.sequenceNr); dataPort.write((uint8_t *)&rtp, sizeof(rtp)); @@ -540,8 +643,13 @@ void AppleMIDISession::writeRtpMidiBuffer(Particip // write out the MIDI Section for (size_t i = 0; i < bufferLen; i++) dataPort.write(outMidiBuffer[i]); - + // *No* journal section (Not supported) + +#ifdef USE_EXT_CALLBACKS + if (_sentRtpMidiCallback) + _sentRtpMidiCallback(rtpMidi); +#endif dataPort.endPacket(); dataPort.flush(); @@ -553,49 +661,52 @@ void AppleMIDISession::writeRtpMidiBuffer(Particip template void AppleMIDISession::manageSynchronization() { +#ifndef ONE_PARTICIPANT for (size_t i = 0; i < participants.size(); i++) +#endif { +#ifndef ONE_PARTICIPANT + auto pParticipant = &participants[i]; + if (pParticipant->ssrc == 0) continue; +#else + auto pParticipant = &participant; + if (pParticipant->ssrc == 0) return; +#endif #ifdef APPLEMIDI_INITIATOR - auto participant = &participants[i]; - - if (participant->invitationStatus != Connected) + if (pParticipant->invitationStatus != Connected) continue; // Only for Initiators that are Connected - if (participant->kind == Listener) + if (pParticipant->kind == Listener) { #endif - manageSynchronizationListener(i); + // The initiator must check in with the listener at least once every 60 seconds; + // otherwise the responder may assume that the initiator has died and terminate the session. + if (now - pParticipant->lastSyncExchangeTime > Settings::CK_MaxTimeOut) + { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ListenerTimeOutException, 0); +#endif + sendEndSession(pParticipant); +#ifndef ONE_PARTICIPANT + participants.erase(i); +#else + participant.ssrc = 0; +#endif + } #ifdef APPLEMIDI_INITIATOR } else { - (participant->synchronizing) ? manageSynchronizationInitiatorInvites(i) - : manageSynchronizationInitiatorHeartBeat(i); + (pParticipant->synchronizing) ? manageSynchronizationInitiatorInvites(i) + : manageSynchronizationInitiatorHeartBeat(pParticipant); } #endif } } -template -void AppleMIDISession::manageSynchronizationListener(size_t i) -{ - auto participant = &participants[i]; - - // The initiator must check in with the listener at least once every 60 seconds; - // otherwise the responder may assume that the initiator has died and terminate the session. - if (now - participant->lastSyncExchangeTime > Settings::CK_MaxTimeOut) - { - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, ListenerTimeOutException); - - sendEndSession(participant); - - participants.erase(i); - - return; - } -} +#ifdef APPLEMIDI_INITIATOR // // The initiator of the session polls if remote station is still alive. @@ -605,32 +716,30 @@ void AppleMIDISession::manageSynchronizationListen // otherwise the responder may assume that the initiator has died and terminate the session. // template -void AppleMIDISession::manageSynchronizationInitiatorHeartBeat(size_t i) +void AppleMIDISession::manageSynchronizationInitiatorHeartBeat(Participant* pParticipant) { - auto participant = &participants[i]; - // Note: During startup, the initiator should send synchronization exchanges more frequently; // empirical testing has determined that sending a few exchanges improves clock // synchronization accuracy. // (Here: twice every 0.5 seconds, then 6 times every 1.5 seconds, then every 10 seconds.) bool doSyncronize = false; - if (participant->synchronizationHeartBeats < 2) + if (pParticipant->synchronizationHeartBeats < 2) { - if (now - participant->lastInviteSentTime > 500) // 2 x every 0.5 seconds + if (now - pParticipant->lastInviteSentTime > 500) // 2 x every 0.5 seconds { - participant->synchronizationHeartBeats++; + pParticipant->synchronizationHeartBeats++; doSyncronize = true; } } - else if (participant->synchronizationHeartBeats < 7) + else if (pParticipant->synchronizationHeartBeats < 7) { - if (now - participant->lastInviteSentTime > 1500) // 5 x every 1.5 seconds + if (now - pParticipant->lastInviteSentTime > 1500) // 5 x every 1.5 seconds { - participant->synchronizationHeartBeats++; + pParticipant->synchronizationHeartBeats++; doSyncronize = true; } } - else if (now - participant->lastInviteSentTime > DefaultSettings::SynchronizationHeartBeat) + else if (now - pParticipant->lastInviteSentTime > DefaultSettings::SynchronizationHeartBeat) { doSyncronize = true; } @@ -638,33 +747,40 @@ void AppleMIDISession::manageSynchronizationInitia if (!doSyncronize) return; - participant->synchronizationCount = 0; - sendSynchronization(participant); + pParticipant->synchronizationCount = 0; + sendSynchronization(pParticipant); } // checks for template void AppleMIDISession::manageSynchronizationInitiatorInvites(size_t i) { - auto participant = &participants[i]; + auto pParticipant = &participants[i]; - if (now - participant->lastInviteSentTime > 10000) + if (now - pParticipant->lastInviteSentTime > 10000) { - if (participant->synchronizationCount > DefaultSettings::MaxSynchronizationCK0Attempts) + if (pParticipant->synchronizationCount > DefaultSettings::MaxSynchronizationCK0Attempts) { - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, MaxAttemptsException); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, MaxAttemptsException, 0); +#endif // After too many attempts, stop. - sendEndSession(participant); + sendEndSession(pParticipant); +#ifndef ONE_PARTICIPANT participants.erase(i); +#else + participant.ssrc = 0; +#endif return; } - sendSynchronization(participant); + sendSynchronization(pParticipant); } } +#endif + template void AppleMIDISession::sendSynchronization(Participant* participant) { @@ -684,66 +800,87 @@ void AppleMIDISession::sendSynchronization(Partici template void AppleMIDISession::manageSessionInvites() { +#ifndef ONE_PARTICIPANT for (auto i = 0; i < participants.size(); i++) +#endif { - auto participant = &participants[i]; +#ifndef ONE_PARTICIPANT + auto pParticipant = &participants[i]; +#else + auto pParticipant = &participant; +#endif - if (participant->kind == Listener) + if (pParticipant->kind == Listener) +#ifndef ONE_PARTICIPANT continue; - - if (participant->invitationStatus == DataInvitationAccepted) +#else + return; +#endif + if (pParticipant->invitationStatus == DataInvitationAccepted) { // Inform that we have an established connection - if (NULL != _connectedCallback) + if (nullptr != _connectedCallback) #ifdef KEEP_SESSION_NAME - _connectedCallback(participant->ssrc, participant->sessionName); + _connectedCallback(pParticipant->ssrc, pParticipant->sessionName); #else - _connectedCallback(participant->ssrc, NULL); + _connectedCallback(pParticipant->ssrc, nullptr); #endif - participant->invitationStatus = Connected; + pParticipant->invitationStatus = Connected; } - if (participant->invitationStatus == Connected) - continue; // We are done here + if (pParticipant->invitationStatus == Connected) +#ifndef ONE_PARTICIPANT + continue; +#else + return; +#endif // try to connect every 1 second (1000 ms) - if (now - participant->lastInviteSentTime > 1000) + if (now - pParticipant->lastInviteSentTime > 1000) { - if (participant->connectionAttempts >= DefaultSettings::MaxSessionInvitesAttempts) + if (pParticipant->connectionAttempts >= DefaultSettings::MaxSessionInvitesAttempts) { - if (NULL != _exceptionCallback) - _exceptionCallback(ssrc, NoResponseFromConnectionRequestException); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, NoResponseFromConnectionRequestException, 0); +#endif // After too many attempts, stop. - sendEndSession(participant); + sendEndSession(pParticipant); +#ifndef ONE_PARTICIPANT participants.erase(i); - +#else + participant.ssrc = 0; +#endif +#ifndef ONE_PARTICIPANT continue; +#else + return; +#endif } - participant->lastInviteSentTime = now; - participant->connectionAttempts++; + pParticipant->lastInviteSentTime = now; + pParticipant->connectionAttempts++; AppleMIDI_Invitation invitation; invitation.ssrc = this->ssrc; - invitation.initiatorToken = participant->initiatorToken; + invitation.initiatorToken = pParticipant->initiatorToken; #ifdef KEEP_SESSION_NAME strncpy(invitation.sessionName, this->localName, DefaultSettings::MaxSessionNameLen); invitation.sessionName[DefaultSettings::MaxSessionNameLen] = '\0'; #endif - if (participant->invitationStatus == Initiating - || participant->invitationStatus == AwaitingControlInvitationAccepted) + if (pParticipant->invitationStatus == Initiating + || pParticipant->invitationStatus == AwaitingControlInvitationAccepted) { - writeInvitation(controlPort, participant->remoteIP, participant->remotePort, invitation, amInvitation); - participant->invitationStatus = AwaitingControlInvitationAccepted; + writeInvitation(controlPort, pParticipant->remoteIP, pParticipant->remotePort, invitation, amInvitation); + pParticipant->invitationStatus = AwaitingControlInvitationAccepted; } else - if (participant->invitationStatus == ControlInvitationAccepted - || participant->invitationStatus == AwaitingDataInvitationAccepted) + if (pParticipant->invitationStatus == ControlInvitationAccepted + || pParticipant->invitationStatus == AwaitingDataInvitationAccepted) { - writeInvitation(dataPort, participant->remoteIP, participant->remotePort + 1, invitation, amInvitation); - participant->invitationStatus = AwaitingDataInvitationAccepted; + writeInvitation(dataPort, pParticipant->remoteIP, pParticipant->remotePort + 1, invitation, amInvitation); + pParticipant->invitationStatus = AwaitingDataInvitationAccepted; } } } @@ -759,22 +896,34 @@ void AppleMIDISession::manageSessionInvites() template void AppleMIDISession::manageReceiverFeedback() { - for (size_t i = 0; i < participants.size(); i++) +#ifndef ONE_PARTICIPANT + for (uint8_t i = 0; i < participants.size(); i++) +#endif { - auto participant = &participants[i]; - - if (participant->doReceiverFeedback == false) +#ifndef ONE_PARTICIPANT + auto pParticipant = &participants[i]; + if (pParticipant->ssrc == 0) continue; +#else + auto pParticipant = &participant; + if (pParticipant->ssrc == 0) return; +#endif + + if (pParticipant->doReceiverFeedback == false) +#ifndef ONE_PARTICIPANT continue; +#else + return; +#endif - if ((now - participant->receiverFeedbackStartTime) > Settings::ReceiversFeedbackThreshold) + if ((now - pParticipant->receiverFeedbackStartTime) > Settings::ReceiversFeedbackThreshold) { AppleMIDI_ReceiverFeedback_t rf; rf.ssrc = ssrc; - rf.sequenceNr = participant->sequenceNr; - writeReceiverFeedback(participant->remoteIP, participant->remotePort, rf); + rf.sequenceNr = pParticipant->receiveSequenceNr; + writeReceiverFeedback(pParticipant->remoteIP, pParticipant->remotePort, rf); // reset the clock. It is started when we receive MIDI - participant->doReceiverFeedback = false; + pParticipant->doReceiverFeedback = false; } } } @@ -784,21 +933,28 @@ void AppleMIDISession::manageReceiverFeedback() template bool AppleMIDISession::sendInvite(IPAddress ip, uint16_t port) { +#ifndef ONE_PARTICIPANT if (participants.full()) +#else + if (participant.ssrc != 0) +#endif { return false; } - + +#ifndef ONE_PARTICIPANT Participant participant; +#endif participant.kind = Initiator; participant.remoteIP = ip; participant.remotePort = port; participant.lastInviteSentTime = now - 1000; // forces invite to be send immediately participant.lastSyncExchangeTime = now; - participant.initiatorToken = random(1, INT32_MAX) * 2; // 0xb7062030; - participant.sequenceNr = random(1, UINT16_MAX); // // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header + participant.initiatorToken = random(1, INT32_MAX) * 2; +#ifndef ONE_PARTICIPANT participants.push_back(participant); +#endif return true; } @@ -808,6 +964,7 @@ bool AppleMIDISession::sendInvite(IPAddress ip, ui template void AppleMIDISession::sendEndSession() { +#ifndef ONE_PARTICIPANT while (participants.size() > 0) { auto participant = &participants.front(); @@ -815,6 +972,13 @@ void AppleMIDISession::sendEndSession() participants.pop_front(); } +#else + if (participant.src != 0) + { + sendEndSession(&participant); + participant.ssrc = 0; + } +#endif } template @@ -825,31 +989,43 @@ void AppleMIDISession::sendEndSession(Participant< endSession.ssrc = this->ssrc; writeEndSession(participant->remoteIP, participant->remotePort, endSession); - if (NULL != _disconnectedCallback) + if (nullptr != _disconnectedCallback) _disconnectedCallback(participant->ssrc); } template void AppleMIDISession::ReceivedRtp(const Rtp_t& rtp) { - auto participant = getParticipantBySSRC(rtp.ssrc); +#ifndef ONE_PARTICIPANT + auto pParticipant = getParticipantBySSRC(rtp.ssrc); +#else + auto pParticipant = (participant.ssrc == rtp.ssrc) ? &participant : nullptr; +#endif - if (NULL != participant) + if (nullptr != pParticipant) { - if (participant->doReceiverFeedback == false) - participant->receiverFeedbackStartTime = now; - participant->doReceiverFeedback = true; + if (pParticipant->doReceiverFeedback == false) + pParticipant->receiverFeedbackStartTime = now; + pParticipant->doReceiverFeedback = true; -#ifdef LATENCY_CALCULATION - auto offset = (rtp.timestamp - participant->offsetEstimate); +#ifdef USE_EXT_CALLBACKS + auto offset = (rtp.timestamp - pParticipant->offsetEstimate); auto latency = (int32_t)(rtpMidiClock.Now() - offset); -#else - auto latency = 0; #endif - participant->sequenceNr = rtp.sequenceNr; + if (pParticipant->receiveSequenceNr + 1 != rtp.sequenceNr) { + +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ReceivedPacketsDropped, pParticipant->receiveSequenceNr + 1 - rtp.sequenceNr); +#endif + } + + pParticipant->receiveSequenceNr = rtp.sequenceNr; - if (NULL != _receivedRtpCallback) - _receivedRtpCallback(0, rtp, latency); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _receivedRtpCallback) + _receivedRtpCallback(pParticipant->ssrc, rtp, latency); +#endif } else { @@ -860,15 +1036,19 @@ void AppleMIDISession::ReceivedRtp(const Rtp_t& rt template void AppleMIDISession::StartReceivedMidi() { - if (NULL != _startReceivedMidiByteCallback) - _startReceivedMidiByteCallback(0); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _startReceivedMidiByteCallback) + _startReceivedMidiByteCallback(ssrc); +#endif } template void AppleMIDISession::ReceivedMidi(byte value) { - if (NULL != _receivedMidiByteCallback) - _receivedMidiByteCallback(0, value); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _receivedMidiByteCallback) + _receivedMidiByteCallback(ssrc, value); +#endif inMidiBuffer.push_back(value); } @@ -876,8 +1056,10 @@ void AppleMIDISession::ReceivedMidi(byte value) template void AppleMIDISession::EndReceivedMidi() { - if (NULL != _endReceivedMidiByteCallback) - _endReceivedMidiByteCallback(0); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _endReceivedMidiByteCallback) + _endReceivedMidiByteCallback(ssrc); +#endif } END_APPLEMIDI_NAMESPACE diff --git a/src/AppleMIDI_Debug.h b/src/AppleMIDI_Debug.h index 3d65291..342cc81 100644 --- a/src/AppleMIDI_Debug.h +++ b/src/AppleMIDI_Debug.h @@ -2,7 +2,9 @@ namespace { static void DBG_SETUP(unsigned long baud) { APPLEMIDI_DEBUG.begin(baud); + #ifdef __AVR_ATmega32U4__ while (!APPLEMIDI_DEBUG); + #endif } template diff --git a/src/AppleMIDI_Defs.h b/src/AppleMIDI_Defs.h index 66b143a..0fc7541 100644 --- a/src/AppleMIDI_Defs.h +++ b/src/AppleMIDI_Defs.h @@ -5,9 +5,9 @@ BEGIN_APPLEMIDI_NAMESPACE -#define APPLEMIDI_LIBRARY_VERSION 0x020100 -#define APPLEMIDI_LIBRARY_VERSION_MAJOR 2 -#define APPLEMIDI_LIBRARY_VERSION_MINOR 2 +#define APPLEMIDI_LIBRARY_VERSION 0x030000 +#define APPLEMIDI_LIBRARY_VERSION_MAJOR 3 +#define APPLEMIDI_LIBRARY_VERSION_MINOR 0 #define APPLEMIDI_LIBRARY_VERSION_PATCH 0 #define DEFAULT_CONTROL_PORT 5004 @@ -40,9 +40,14 @@ typedef const char* AppleMIDIConstStr; #define RtpBuffer_t Deque #define MidiBuffer_t Deque -#define APPLEMIDI_LISTENER +// #define USE_EXT_CALLBACKS +// #define ONE_PARTICIPANT // memory optimization +// #define USE_DIRECTORY + +// By defining NO_SESSION_NAME in the sketch, you can save 100 bytes +#ifndef NO_SESSION_NAME #define KEEP_SESSION_NAME -#define LATENCY_CALCULATION +#endif #define MIDI_SAMPLING_RATE_176K4HZ 176400 #define MIDI_SAMPLING_RATE_192KHZ 192000 @@ -51,6 +56,18 @@ typedef const char* AppleMIDIConstStr; struct Rtp; typedef Rtp Rtp_t; +struct RtpMIDI; +typedef RtpMIDI RtpMIDI_t; + +#ifdef USE_DIRECTORY +enum WhoCanConnectToMe : uint8_t +{ + None, + OnlyComputersInMyDirectory, + Anyone, +}; +#endif + // from: https://en.wikipedia.org/wiki/RTP-MIDI // Apple decided to create their own protocol, imposing all parameters related to // synchronization like the sampling frequency. This session protocol is called "AppleMIDI" @@ -93,20 +110,28 @@ enum Exception : uint8_t ParseException, UnexpectedParseException, TooManyParticipantsException, + ComputerNotInDirectory, + NotAcceptingAnyone, UnexpectedInviteException, ParticipantNotFoundException, ListenerTimeOutException, MaxAttemptsException, NoResponseFromConnectionRequestException, + SendPacketsDropped, + ReceivedPacketsDropped, }; using connectedCallback = void (*)(const ssrc_t&, const char *); +using disconnectedCallback = void (*)(const ssrc_t&); +#ifdef USE_EXT_CALLBACKS using startReceivedMidiByteCallback = void (*)(const ssrc_t&); using receivedMidiByteCallback = void (*)(const ssrc_t&, byte); using endReceivedMidiByteCallback = void (*)(const ssrc_t&); using receivedRtpCallback = void (*)(const ssrc_t&, const Rtp_t&, const int32_t&); -using disconnectedCallback = void (*)(const ssrc_t&); -using exceptionCallback = void (*)(const ssrc_t&, int32_t); +using exceptionCallback = void (*)(const ssrc_t&, const Exception&, const int32_t value); +using sentRtpCallback = void (*)(const Rtp_t&); +using sentRtpMidiCallback = void (*)(const RtpMIDI_t&); +#endif /* Signature "Magic Value" for Apple network MIDI session establishment */ const byte amSignature[] = {0xff, 0xff}; @@ -131,12 +156,19 @@ typedef struct PACKED AppleMIDI_Invitation { initiatorToken_t initiatorToken; ssrc_t ssrc; - char sessionName[DefaultSettings::MaxSessionNameLen + 1]; +#ifdef KEEP_SESSION_NAME + char sessionName[DefaultSettings::MaxSessionNameLen + 1]; const size_t getLength() const { return sizeof(AppleMIDI_Invitation) - (DefaultSettings::MaxSessionNameLen) + strlen(sessionName); } +#else + const size_t getLength() const + { + return sizeof(AppleMIDI_Invitation); + } +#endif } AppleMIDI_Invitation_t, AppleMIDI_InvitationAccepted_t, AppleMIDI_InvitationRejected_t; typedef struct PACKED AppleMIDI_BitrateReceiveLimit diff --git a/src/AppleMIDI_Parser.h b/src/AppleMIDI_Parser.h index cf5e9b0..6c4dd8a 100644 --- a/src/AppleMIDI_Parser.h +++ b/src/AppleMIDI_Parser.h @@ -40,11 +40,7 @@ class AppleMIDIParser command[0] = buffer[i++]; command[1] = buffer[i++]; - if (false) - { - } -#ifdef APPLEMIDI_LISTENER - else if (0 == memcmp(command, amInvitation, sizeof(amInvitation))) + if (0 == memcmp(command, amInvitation, sizeof(amInvitation))) { byte protocolVersion[4]; @@ -84,7 +80,7 @@ class AppleMIDIParser uint16_t bi = 0; while ((i < buffer.size()) && (buffer[i] != 0x00)) { - if (bi <= DefaultSettings::MaxSessionNameLen) + if (bi < DefaultSettings::MaxSessionNameLen) invitation.sessionName[bi++] = buffer[i]; i++; } @@ -232,7 +228,6 @@ class AppleMIDIParser return parserReturn::Processed; } -#endif #ifdef APPLEMIDI_INITIATOR else if (0 == memcmp(command, amInvitationAccepted, sizeof(amInvitationAccepted))) { @@ -270,11 +265,11 @@ class AppleMIDIParser cb.buffer[3] = buffer[i++]; invitationAccepted.ssrc = ntohl(cb.value32); - #ifdef KEEP_SESSION_NAME +#ifdef KEEP_SESSION_NAME uint16_t bi = 0; while ((i < buffer.size()) && (buffer[i] != 0x00)) { - if (bi <= DefaultSettings::MaxSessionNameLen) + if (bi < DefaultSettings::MaxSessionNameLen) invitationAccepted.sessionName[bi++] = buffer[i]; i++; } @@ -336,7 +331,7 @@ class AppleMIDIParser uint16_t bi = 0; while ((i < buffer.size()) && (buffer[i] != 0x00)) { - if (bi <= DefaultSettings::MaxSessionNameLen) + if (bi < DefaultSettings::MaxSessionNameLen) invitationRejected.sessionName[bi++] = buffer[i]; i++; } diff --git a/src/AppleMIDI_Participant.h b/src/AppleMIDI_Participant.h index f1099cf..28dcfcf 100644 --- a/src/AppleMIDI_Participant.h +++ b/src/AppleMIDI_Participant.h @@ -10,13 +10,15 @@ template struct Participant { ParticipantKind kind; - ssrc_t ssrc; - IPAddress remoteIP; - uint16_t remotePort; - + ssrc_t ssrc = 0; + IPAddress remoteIP = INADDR_NONE; + uint16_t remotePort = 0; + unsigned long receiverFeedbackStartTime; bool doReceiverFeedback = false; - uint16_t sequenceNr; + + uint16_t sendSequenceNr = random(1, UINT16_MAX); // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header + uint16_t receiveSequenceNr; unsigned long lastSyncExchangeTime; #ifdef APPLEMIDI_INITIATOR @@ -30,7 +32,7 @@ struct Participant bool synchronizing = false; #endif -#ifdef LATENCY_CALCULATION +#ifdef USE_EXT_CALLBACKS uint32_t offsetEstimate; #endif diff --git a/src/AppleMIDI_Settings.h b/src/AppleMIDI_Settings.h index c5a3e07..31bc41a 100644 --- a/src/AppleMIDI_Settings.h +++ b/src/AppleMIDI_Settings.h @@ -13,6 +13,8 @@ struct DefaultSettings static const size_t MaxSessionNameLen = 24; static const uint8_t MaxNumberOfParticipants = 2; + + static const uint8_t MaxNumberOfComputersInDirectory = 5; // The recovery journal mechanism requires that the receiver periodically // inform the sender of the sequence number of the most recently received packet. diff --git a/src/rtpMIDI_Parser_JournalSection.hpp b/src/rtpMIDI_Parser_JournalSection.hpp index 41b0def..1606224 100644 --- a/src/rtpMIDI_Parser_JournalSection.hpp +++ b/src/rtpMIDI_Parser_JournalSection.hpp @@ -48,7 +48,7 @@ parserReturn decodeJournalSection(RtpBuffer_t &buffer) // stream (modulo 2^16). cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; - uint16_t checkPoint = ntohs(cb.value16); // unused + // uint16_t checkPoint = ntohs(cb.value16); ; // unused // (RFC 4695, 5 Recovery Journal Format) // If A and Y are both zero, the recovery journal only contains its 3- diff --git a/src/rtpMIDI_Parser_MidiCommandSection.hpp b/src/rtpMIDI_Parser_MidiCommandSection.hpp index 2f3711e..9063066 100644 --- a/src/rtpMIDI_Parser_MidiCommandSection.hpp +++ b/src/rtpMIDI_Parser_MidiCommandSection.hpp @@ -206,7 +206,7 @@ size_t decodeMidiSysEx(RtpBuffer_t &buffer) // to compensate for adding the sysex at the end. consumed--; - + // send MIDI data session->StartReceivedMidi(); for (size_t j = 0; j < consumed; j++) @@ -221,7 +221,7 @@ size_t decodeMidiSysEx(RtpBuffer_t &buffer) midiCommandLength -= consumed; midiCommandLength += 1; // adding the manual SysEx SystemExclusiveEnd - + // indicates split SysEx return buffer.max_size() + 1; } diff --git a/test/Arduino.h b/test/Arduino.h index afb5e0d..ccacad5 100644 --- a/test/Arduino.h +++ b/test/Arduino.h @@ -19,7 +19,7 @@ class _serial void print(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; void print(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a; }; void print(double a, int = 2) { std::cout << a; }; - void print(struct tm * timeinfo, const char * format = NULL) {}; + void print(struct tm * timeinfo, const char * format = nullptr) {}; void print(IPAddress) {}; void println(const char a[]) { std::cout << a << "\n"; }; @@ -30,7 +30,7 @@ class _serial void println(long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; void println(unsigned long a, int format = DEC) { std::cout << (format == DEC ? std::dec : std::hex) << a << "\n"; }; void println(double a, int format = 2) { std::cout << a << "\n"; }; - void println(struct tm * timeinfo, const char * format = NULL) {}; + void println(struct tm * timeinfo, const char * format = nullptr) {}; void println(IPAddress) {}; void println(void) { std::cout << "\n"; }; }; diff --git a/test/NoteOn.cpp b/test/NoteOn.cpp index b0cb7cc..ccec4d7 100644 --- a/test/NoteOn.cpp +++ b/test/NoteOn.cpp @@ -6,7 +6,7 @@ #include "AppleMIDI.h" unsigned long t0 = millis(); -bool isConnected = false; +bool isConnected--; byte sysex14[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0xF7 }; byte sysex15[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x50, 0x4D, 0xF7 }; @@ -33,16 +33,16 @@ APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); // rtpMIDI session. Device connected // ----------------------------------------------------------------------------- void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { - isConnected = true; - DBG(F("Connected to session"), name); + isConnected++; + DBG(F("Connected to session"), ssrc, name); } // ----------------------------------------------------------------------------- // rtpMIDI session. Device disconnected // ----------------------------------------------------------------------------- void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { - isConnected = false; - DBG(F("Disconnected")); + isConnected--; + DBG(F("Disconnected"), ssrc); } // ----------------------------------------------------------------------------- @@ -100,7 +100,7 @@ void begin() { DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); - DBG(F("Then press the Connect button")); + DBG(F("Select and then press the Connect button")); DBG(F("Then open a MIDI listener and monitor incoming notes")); MIDI.begin(); @@ -123,7 +123,7 @@ void loop() // send a note every second // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t0) > 10000) + if ((isConnected > 0) && (millis() - t0) > 10000) { t0 = millis();