diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..10221f5 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [lathoub] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..0aa3ff0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,69 @@ +--- +name: Bug report +about: Report something that does not work as intended +title: '' +labels: bug +assignees: '' + +--- + + + +## Context + +Using the English language, please answer a few questions to help us understand your problem better and guide you to a solution: + + + +- What board are you using ? + - `example: Arduino Leonardo` + - _Please list any shields or other **relevant** hardware you're using_ +- What version of the Arduino IDE are you using ? + - `example: 1.8.5` +- What Operating System are you using for the DAW + - [ ] Windows + - [ ] MacOS + - [ ] Linux + - [ ] Other (please specify) +- Is your problem related to: + - [ ] Setup + - [ ] Connecting + - [ ] AppleMIDI <> MIDI + - [ ] Dropped messages +- How comfortable are you with code ? + - [ ] Complete beginner + - [ ] I've done basic projects + - [ ] I know my way around C/C++ + - [ ] Advanced / professional + +## Describe your project and what you expect to happen: + + + +## Describe your problem (what does not work): + + + +## Steps to reproduce + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0261b2b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discussions + url: https://github.com/lathoub/Arduino-AppleMIDI-Library/discussions + about: Not a bug or a feature request ? Discuss your problem, ask for help or show what you've built in Discussions. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ffcdb43 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,10 @@ +name: build +on: [pull_request, push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Build on Arduino CLI + run: bash ci/build-arduino.sh diff --git a/.gitignore b/.gitignore index 2e3a85c..938aa14 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ examples/.DS_Store src/.DS_Store test/.vs test/Debug -examples/ESP32_NoteOnOffEverySec/config.h src/.vscode test/x64 .development -examples/ESP32_NoteOnOffEverySec/credentials.h +examples/ESP32_Callbacks/arduino_secrets.h diff --git a/.travis.yml b/.travis.yml index 99f8144..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: -- platformio 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 2b6c4ee..5488031 100755 --- a/README.md +++ b/README.md @@ -1,35 +1,44 @@ -# AppleMIDI (aka rtpMIDI) for Arduino -[![Build Status](https://travis-ci.org/lathoub/Arduino-AppleMIDI-Library.svg?branch=master)](https://travis-ci.org/lathoub/Arduino-AppleMIDI-Library) [![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg)](http://creativecommons.org/licenses/by-sa/4.0/) [![GitHub version](https://badge.fury.io/gh/lathoub%2FArduino-AppleMidi-Library.svg)](https://badge.fury.io/gh/lathoub%2FArduino-AppleMidi-Library) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c8be2ccc3f104e0588572a39f8106070)](https://app.codacy.com/app/lathoub/Arduino-AppleMIDI-Library?utm_source=github.com&utm_medium=referral&utm_content=lathoub/Arduino-AppleMIDI-Library&utm_campaign=Badge_Grade_Dashboard) +# AppleMIDI (aka rtpMIDI) for Arduino [![License: CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg)](http://creativecommons.org/licenses/by-sa/4.0/) -Enables an Arduino with IP/UDP capabilities (Ethernet shield, ESP8266, ESP32, ...) to particpate in an AppleMIDI session. +Enables an Arduino with IP/UDP capabilities (Ethernet shield, ESP8266, ESP32, ...) to participate in an AppleMIDI session. + +**Important:** Please read the [note below](https://github.com/lathoub/Arduino-AppleMIDI-Library#ethernet-buffer-size) on enlarging the standard Ethernet library buffersize to avoid dropping MIDI messages! ## 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.2.0 +* Event chaining + +### New in 3.3.0 +* Better parsing of large incoming MIDI messages with a small internal Arduino buffer ## Installation From the Arduino IDE Library Manager, search for AppleMIDI -Screenshot 2020-04-21 at 10 25 22 copy +Installation This will also install [FortySevenEffects MIDI library](https://github.com/FortySevenEffects/arduino_midi_library) ## Basic Usage -``` +```cpp #include -#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); void setup() { - MIDI.begin(1); - - // Optional - AppleMIDI.setHandleConnected(OnAppleMidiConnected); + Ethernet.begin(mac); + + MIDI.begin(); // listens on channel 1 } void loop() @@ -37,42 +46,48 @@ void loop() // Listen to incoming notes MIDI.read(); - // Send MIDI note 40 on, velocity 55 on channel 1 - MIDI.sendNoteOn(40, 55, 1); -} - -void OnAppleMidiConnected(uint32_t ssrc, const char* name) { + .... + if (something) { + // Send MIDI note 40 on, velocity 55 on channel 1 + MIDI.sendNoteOn(40, 55, 1); + } } ``` -`APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();` creates 2 instance: `MIDI` and `AppleMIDI`. `MIDI` is the instance that manages all MIDI interaction, `AppleMIDI` is the instance this manages the rtp transport layer. - - -More usages in the [examples](https://github.com/lathoub/Arduino-AppleMIDI-Library/tree/master/examples) folder +More usages in the [examples](https://github.com/lathoub/Arduino-AppleMIDI-Library/tree/master/examples) folder and in the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki) ## Hardware * Arduino/Genuino (Mega, Uno, Arduino Ethernet, MKRZERO, ...) * ESP8266 (Adafruit HUZZAH ESP8266, Sparkfun ESP8266 Thing Dev) -* ESP32 (Adafruit HUZZAH32 – ESP32 Feather Board) -* Teensy 3.2 +* ESP32 (Adafruit HUZZAH32 – ESP32 Feather Board) Wi-Fi +* ESP32 with W5500 [Setup](https://github.com/lathoub/Arduino-AppleMIDI-Library/discussions/135) +* Teensy 3.2 & 4.1 * Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500 - -## Memory usage -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 +* MKR ETH shield (W5500 and W6100 based) * Teensy WIZ820io W5200 - +* Teensy 4.1 with [Ethernet Kit](https://www.pjrc.com/store/ethernet_kit.html) + +## Notes + +### Session names + +Session names can get really long on Macs (eg 'Macbook Pro of Johann Gambolputty .. von Hautkopft of Ulm') and will be truncated to the [`MaxSessionNameLen`](https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/af4c7bd9a960a90e09e211f0ea00db2d9832d1f7/src/AppleMIDI_Settings.h#L13) + +### Memory footprint +The memory footprint of the library can be lowered significantly, read the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Memory-footprint) + +### Ethernet buffer size +It's highly recommended to modify the [Ethernet library](https://github.com/arduino-libraries/Ethernet) or use the [Ethernet3 library](https://github.com/sstaub/Ethernet3) to avoid buffer overruns - [learn more](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Enlarge-Ethernet-buffer-size-to-avoid-dropping-UDP-packages) + +### Latency +Use wired Ethernet to reduce latency, Wi-Fi increases latency and latency varies. More of the [wiki](https://github.com/lathoub/Arduino-AppleMIDI-Library/wiki/Keeping-Latency-under-control) + ## Arduino IDE (arduino.cc) -* 1.8.10 +* 1.8.16 ## Contributing I would love to include your enhancements or bug fixes! In lieu of a formal styleguide, please take care to maintain the existing coding style. Please test your code before sending a pull request. It would be very helpful if you include a detailed explanation of your changes in the pull request. diff --git a/ci/build-arduino.sh b/ci/build-arduino.sh new file mode 100644 index 0000000..6cf1222 --- /dev/null +++ b/ci/build-arduino.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Exit immediately if a command exits with a non-zero status. +set -e +# Enable the globstar shell option +shopt -s globstar +# Make sure we are inside the github workspace +cd $GITHUB_WORKSPACE +# Create directories +mkdir $HOME/Arduino +mkdir $HOME/Arduino/libraries +# Install Arduino IDE +export PATH=$PATH:$GITHUB_WORKSPACE/bin +curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh +arduino-cli config init +arduino-cli config set library.enable_unsafe_install true +arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json +arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json +arduino-cli core update-index + +# Install Arduino AVR core +arduino-cli core install arduino:avr +arduino-cli core install arduino:samd +# arduino-cli core install arduino:esp8266 +# arduino-cli core install esp32:esp32:esp32doit-devkit-v1 + +# Link Arduino library +ln -s $GITHUB_WORKSPACE $HOME/Arduino/libraries/CI_Test_Library + +arduino-cli lib install Ethernet +arduino-cli lib install "MIDI library" +arduino-cli lib install --git-url https://github.com/sstaub/Ethernet3.git +arduino-cli lib install EthernetBonjour + +# Compile all *.ino files for the Arduino Uno +for f in **/AVR_*.ino ; do + arduino-cli compile -b arduino:avr:uno $f +done + +# Compile all *.ino files for the Arduino Zero +for f in **/SAMD_*.ino ; do + arduino-cli compile -b arduino:samd:mkrzero $f +done + +# Compile all *.ino files for the ESP8266 +# for f in **/ESP8266_*.ino ; do +# arduino-cli compile -b arduino:esp8266:??? $f +# done + +# Compile all *.ino files for the ESP32 +# for f in **/ESP32_*.ino ; do +# arduino-cli compile -b arduino:esp32:??? $f +# done diff --git a/doc/RFC6295_notes.md b/doc/RFC6295_notes.md new file mode 100644 index 0000000..2e30779 --- /dev/null +++ b/doc/RFC6295_notes.md @@ -0,0 +1,174 @@ +# Basic RTPMIDI Cheat Sheet + +From https://tools.ietf.org/html/rfc6295 + +## RTP Header + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | V |P|X| CC |M| PT | Sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MIDI command section ... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Journal section ... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Bit | pos | description | +| --------------- | :---: | ------------------------------------------------------------------------------------------------------------------------ | +| V | 0-1 | Version. 2. | +| P | 2 | Has paddding. RTP needs it for encryption. 0. | +| X | 3 | Header extension. 0. | +| CC | 4-7 | CSRC count. 0. | +| M | 8 | Has MIDI data. | +| PT | 9-15 | Payload type. Always 0x61. MIDI. | +| Sequence number | 16-31 | Starts random, increase one on each packet. (%2^16). There is an extended one with 32 bits and rollovers. | +| Timestamp | 32-63 | Time this packet was generated. On Apple midi the unit is 0.1 ms. (1^-4 seconds). Real RTPMIDI is at session connection. | +| SSRC | 64-96 | Random unique SSRC for this sender. Same for all the session. | + +Timestamp can be buffered to reduce jitter on the receiving end, creating a +continuous lag of a specific length. + +## MIDI Command section + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |B|J|Z|P|LEN... | MIDI list ... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Bit | pos | description | +| ------ | :-: | ---------------------------------------------------------------------------------------------------------------- | +| B | 0 | Length is 12 bits. If true length at 4-7 is MSB, and one more byte. | +| J | 1 | There is a journal | +| Z | 2 | First midi command as is in MIDI section. No timestamp for first command. | +| P | 3 | Phantom MIDI command. The first command is a running command from previous stream. | +| length | 4-7 | How many bytes. May be extended with the B bit. | +| MIDI | ... | MIDI data, then timestamp, MIDI data, timestamp and so on.. or timestamp, midi data and so on. Depends on Z bit. | + +Timestamps in running Length encoding. https://en.wikipedia.org/wiki/Run-length_encoding + +# Journal + +## Journal Bits + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S|Y|A|H|TOTCHAN| Checkpoint Packet Seqnum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Bit | pos | description | +| ------------------------ | :--: | ------------------------------------------------------------------------ | +| S | 0 | Single packet loss. To indicate only one packet is described in journal. | +| Y | 1 | Has system journal | +| A | 2 | Has channel journals. Needs totchan. | +| H | 3 | Enhanced Chapter C encoding. | +| TOTCHAN | 4-7 | Nr channels -1 (has totchan + 1 channels) | +| Checkpoint packet seqnum | 8-23 | Seq nr for this journal Normally the one before the current packet. | + +## Channel Journal + +One for each (TOTCHAN + 1) + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| CHAN |H| LENGTH |P|C|M|W|N|E|T|A| Chapters ... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Bit | pos | description | +| ------ | :----: | ------------------------------------------------------------------------ | +| S | 0 | Single packet loss. To indicate only one packet is described in journal. | +| CHAN | 1-4 | Channel number | +| H | 5 | Whether controllers are Enhanced Chapter C. | +| LENGTH | 6-15 | Lenght of the journal | +| P | 16 / 0 | Chapter P. Program Change. | +| C | 17 / 1 | Chapter C. Control Change. | +| M | 18 / 2 | Chapter M. Parameter System. | +| W | 19 / 3 | Chapter W. Pitch Wheel. | +| N | 20 / 4 | Chapter N. Note On/Off | +| E | 21 / 5 | Chapter E. Note Command Extras | +| T | 22 / 6 | Chapter T. After Touch. | +| A | 23 / 7 | Chapter A. Poly Aftertouch. | + +## Chapter P + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| PROGRAM |B| BANK-MSB |X| BANK-LSB | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +## Chapter C + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| LEN |S| NUMBER |A| VALUE/ALT |S| NUMBER | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |A| VALUE/ALT | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +## Chapter M + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| LEN |S| NUMBER |A| VALUE/ALT |S| NUMBER | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |A| VALUE/ALT | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +# Chapter W + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| FIRST |R| SECOND | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +## Chapter N + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |B| LEN | LOW | HIGH |S| NOTENUM |Y| VELOCITY | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| NOTENUM |Y| VELOCITY | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | OFFBITS | OFFBITS | .... | OFFBITS | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Bit | pos | description | +| --- | :-----: | ------------------------------------ | +| B | 0 | S-Style functionality. By default 1. | +| S | 16n | If B is 0, all S are 0. | +| Y | 16n + 8 | Recomendation to play. | + +## Chapter T + + 0 + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |S| PRESSURE | + +-+-+-+-+-+-+-+-+ + +## Chapter A + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 8 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |S| LEN |S| NOTENUM |X| PRESSURE |S| NOTENUM | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |X| PRESSURE | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +(original by https://github.com/davidmoreno/rtpmidid/blob/master/docs/RFC6295_notes.md) diff --git a/examples/AVR_Callbacks/AVR_Callbacks.ino b/examples/AVR_Callbacks/AVR_Callbacks.ino new file mode 100644 index 0000000..241ae79 --- /dev/null +++ b/examples/AVR_Callbacks/AVR_Callbacks.ino @@ -0,0 +1,162 @@ +#include + +#define SerialMon Serial +#define ONE_PARTICIPANT +#define USE_EXT_CALLBACKS +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Das Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(MIDI_CHANNEL_OMNI); + + // Normal callbacks - always available + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); +/* + // Extended callback, only available when defining USE_EXT_CALLBACKS + AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) { + // AM_DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr); + }); + AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) { + // AM_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) { + // AM_DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency"); + }); + AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + // AM_DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) { + // AM_DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value); + }); + AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + // AM_DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleException(OnAppleMidiException); + + MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { + AM_DBG(F("ControlChange"), channel, v1, v2); + }); + MIDI.setHandleProgramChange([](Channel channel, byte v1) { + AM_DBG(F("ProgramChange"), channel, v1); + }); + MIDI.setHandlePitchBend([](Channel channel, int v1) { + AM_DBG(F("PitchBend"), channel, v1); + }); + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), channel, note, velocity); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), channel, note, velocity); + }); +*/ + AM_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) > 100) + { + t1 = millis(); + + byte note = random(15, 80); + byte velocity = random(55, 100); + byte channel = 1; + + // AM_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: + AM_DBG(F("*** BufferFullException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParseException: + AM_DBG(F("*** ParseException")); + break; + case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: + AM_DBG(F("*** TooManyParticipantsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: + AM_DBG(F("*** UnexpectedInviteException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: + AM_DBG(F("*** ParticipantNotFoundException"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: + AM_DBG(F("*** ComputerNotInDirectory"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: + AM_DBG(F("*** NotAcceptingAnyone"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: + AM_DBG(F("*** ListenerTimeOutException")); + break; + case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: + AM_DBG(F("*** MaxAttemptsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: + AM_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: + AM_DBG(F("*** SendPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: + AM_DBG(F("*** ReceivedPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::UdpBeginPacketFailed: + AM_DBG(F("*** UdpBeginPacketFailed"), value); + break; + } +} diff --git a/examples/AVR_Directory/AVR_Directory.ino b/examples/AVR_Directory/AVR_Directory.ino new file mode 100644 index 0000000..6f8c630 --- /dev/null +++ b/examples/AVR_Directory/AVR_Directory.ino @@ -0,0 +1,85 @@ +#include + +#define SerialMon Serial +#define USE_DIRECTORY +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_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; + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_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..aee6d15 --- /dev/null +++ b/examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino @@ -0,0 +1,78 @@ +#include // from https://github.com/sstaub/Ethernet3 + +#define SerialMon Serial +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_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_Initiator/AVR_Initiator.ino b/examples/AVR_Initiator/AVR_Initiator.ino new file mode 100644 index 0000000..90e8b2a --- /dev/null +++ b/examples/AVR_Initiator/AVR_Initiator.ino @@ -0,0 +1,83 @@ +#include + +#define SerialMon Serial +#define APPLEMIDI_INITIATOR +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + + MIDI.begin(); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + // Initiate the session + IPAddress remote(192, 168, 1, 65); + AppleMIDI.sendInvite(remote, DEFAULT_CONTROL_PORT); // port is 5004 by default + + AM_DBG(F("Connecting to "), remote, "Port", DEFAULT_CONTROL_PORT, "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Watch as this session is added to the Participants list")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + AM_DBG(F("Sending a random NoteOn/Off every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + // send note on/off 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_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 64% rename from examples/EthernetShield_MultipleSessions/EthernetShield_MultipleSessions.ino rename to examples/AVR_MultipleSessions/AVR_MultipleSessions.ino index 75ca6ab..6ee4689 100644 --- a/examples/EthernetShield_MultipleSessions/EthernetShield_MultipleSessions.ino +++ b/examples/AVR_MultipleSessions/AVR_MultipleSessions.ino @@ -1,7 +1,7 @@ #include +#define SerialMon Serial #include -USING_NAMESPACE_APPLEMIDI // Enter a MAC address for your controller below. // Newer Ethernet shields have a MAC address printed on a sticker on the shield @@ -10,36 +10,33 @@ 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); + // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- void setup() { - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); + AM_DBG(F("Failed DHCP, check network cable & reboot")); for (;;); } - Serial.print(F("IP address is ")); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI1.getPort(), "(Name", AppleMIDI1.getName(), ")"); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI2.getPort(), "(Name", AppleMIDI2.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); // Listen for MIDI messages on channel 1 MIDI1.begin(1); @@ -55,7 +52,7 @@ void setup() MIDI1.setHandleNoteOn(OnMidiNoteOn); MIDI2.setHandleNoteOn(OnMidiNoteOn); - Serial.println(F("Every second send a random NoteOn/Off")); + AM_DBG(F("Every second, send a random NoteOn/Off, from multiple sessions")); } // ----------------------------------------------------------------------------- @@ -69,10 +66,9 @@ 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(); - // Serial.print(F("."); byte note = random(1, 127); byte velocity = 55; @@ -89,31 +85,22 @@ void loop() // ----------------------------------------------------------------------------- // rtpMIDI session. Device connected // ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.print(name); - Serial.print(" ssrc 0x"); - Serial.println(ssrc, HEX); +void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); } // ----------------------------------------------------------------------------- // rtpMIDI session. Device disconnected // ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.print(F("Disconnected from ssrc 0x")); - Serial.println(ssrc, HEX); +void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); } // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); + AM_DBG(F("in\tNote on"), note, " Velocity", velocity, "\t", channel); } diff --git a/examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino b/examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino new file mode 100644 index 0000000..03abf63 --- /dev/null +++ b/examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino @@ -0,0 +1,71 @@ +#include + +#define SerialMon Serial +#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; + +// Non default portnr +APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "MyNamedArduino", 5200); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + AM_DBG(F("Send 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_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino b/examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino new file mode 100644 index 0000000..60d9012 --- /dev/null +++ b/examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino @@ -0,0 +1,79 @@ +#include + +#define SerialMon Serial +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }) + .setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_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_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino b/examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino new file mode 100644 index 0000000..2cf3512 --- /dev/null +++ b/examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino @@ -0,0 +1,95 @@ +#include + +#define SerialMon Serial +#define USE_EXT_CALLBACKS +#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 OnAppleMidiStartReceived(const APPLEMIDI_NAMESPACE::ssrc_t&); +void OnAppleMidiReceivedByte(const APPLEMIDI_NAMESPACE::ssrc_t&, byte); +void OnAppleMidiEndReceive(const APPLEMIDI_NAMESPACE::ssrc_t&); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + AppleMIDI.setHandleStartReceivedMidi(OnAppleMidiStartReceived); + AppleMIDI.setHandleReceivedMidi(OnAppleMidiReceivedByte); + AppleMIDI.setHandleEndReceivedMidi(OnAppleMidiEndReceive); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); +} + +// ==================================================================================== +// Event handlers for incoming MIDI messages +// ==================================================================================== + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiStartReceived(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + AM_DBG(F("Start receiving"), ssrc); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiReceivedByte(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, byte data) { + SerialMon.println(data, HEX); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiEndReceive(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + AM_DBG(F("End receiving"), ssrc); +} diff --git a/examples/AVR_SysEx/AVR_SysEx.ino b/examples/AVR_SysEx/AVR_SysEx.ino new file mode 100644 index 0000000..da76c5e --- /dev/null +++ b/examples/AVR_SysEx/AVR_SysEx.ino @@ -0,0 +1,110 @@ +#include + +#define SerialMon Serial +#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; + +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 }; +byte sysex16[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x32, 0x50, 0x4D, 0xF7 }; +byte sysexBig[] = { 0xF0, 0x41, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, + 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xF7 + }; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.begin(); + MIDI.setHandleSystemExclusive(OnMidiSysEx); + + AM_DBG(F("Send SysEx 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) + { + // MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); + t1 = millis(); + } +} + +// ==================================================================================== +// Event handlers for incoming MIDI messages +// ==================================================================================== + +void OnMidiSysEx(byte* data, unsigned length) { + SerialMon.print(F("SYSEX: (")); + SerialMon.print(getSysExStatus(data, length)); + SerialMon.print(F(", ")); + SerialMon.print(length); + SerialMon.print(F(" bytes) ")); + for (uint16_t i = 0; i < length; i++) + { + SerialMon.print(data[i], HEX); + SerialMon.print(" "); + } + SerialMon.println(); +} + +char getSysExStatus(const byte* data, uint16_t length) +{ + if (data[0] == 0xF0 && data[length - 1] == 0xF7) + return 'F'; // Full SysEx Command + else if (data[0] == 0xF0 && data[length - 1] != 0xF7) + return 'S'; // Start of SysEx-Segment + else if (data[0] != 0xF0 && data[length - 1] != 0xF7) + return 'M'; // Middle of SysEx-Segment + else + return 'E'; // End of SysEx-Segment +} diff --git a/examples/Basic_IO/Basic_IO.ino b/examples/Basic_IO/Basic_IO.ino deleted file mode 100644 index 65e8a7c..0000000 --- a/examples/Basic_IO/Basic_IO.ino +++ /dev/null @@ -1,37 +0,0 @@ -#include -#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 -}; - -// Simple tutorial on how to receive and send MIDI messages. -// Here, when receiving any message on channel 4, the Arduino -// will blink a led and play back a note for 1 second. - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -void setup() -{ - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - pinMode(LED_BUILTIN, OUTPUT); - MIDI.begin(4); // Launch MIDI and listen to channel 4 -} - -void loop() -{ - if (MIDI.read()) // If we have received a message - { - digitalWrite(LED_BUILTIN, HIGH); - MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) - delay(1000); // Wait for a second - MIDI.sendNoteOff(42, 0, 1); // Stop the note - digitalWrite(LED_BUILTIN, LOW); - } -} diff --git a/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec.ino b/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec.ino deleted file mode 100644 index 6f496c4..0000000 --- a/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec.ino +++ /dev/null @@ -1,115 +0,0 @@ -#include "ETH_Helper.h" - -#include -USING_NAMESPACE_APPLEMIDI - -unsigned long t0 = millis(); -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_ESP32_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - ETH_startup(); - - MDNS.begin(AppleMIDI.getName()); - - Serial.print("\nIP address is "); - Serial.println(ETH.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(ETH.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Create a session and wait for a remote host to connect to us - MIDI.begin(1); // listen on channel 1 - - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - MIDI.setHandleNoteOn(OnAppleMidiNoteOn); - MIDI.setHandleNoteOff(OnAppleMidiNoteOff); - - MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -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 && (millis() - t0) > 1000) - { - t0 = millis(); - // Serial.print(F("."); - - byte note = random(1, 127); - byte velocity = 55; - byte channel = 1; - - MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); - } -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} diff --git a/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ETH_Helper.h b/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ETH_Helper.h deleted file mode 100644 index 25b4532..0000000 --- a/examples/ESP32-Ethernet-Kit_A_v1.1_NoteOnOffEverySec/ETH_Helper.h +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include - -#define ETH_ADDR 1 -#define ETH_POWER_PIN 5 -#define ETH_MDC_PIN 23 -#define ETH_MDIO_PIN 18 -#define ETH_TYPE ETH_PHY_IP101 - -static bool eth_connected = false; - -void WiFiEvent(WiFiEvent_t event) -{ - switch (event) { - case SYSTEM_EVENT_ETH_START: - Serial.println("ETH Started"); - //set eth hostname here - ETH.setHostname("esp32-ethernet"); - break; - case SYSTEM_EVENT_ETH_CONNECTED: - Serial.println("ETH Connected"); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - Serial.print("ETH MAC: "); - Serial.print(ETH.macAddress()); - Serial.print(", IPv4: "); - Serial.print(ETH.localIP()); - if (ETH.fullDuplex()) { - Serial.print(", FULL_DUPLEX"); - } - Serial.print(", "); - Serial.print(ETH.linkSpeed()); - Serial.println("Mbps"); - eth_connected = true; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - Serial.println("ETH Disconnected"); - eth_connected = false; - break; - case SYSTEM_EVENT_ETH_STOP: - Serial.println("ETH Stopped"); - eth_connected = false; - break; - default: - break; - } -} - -bool ETH_startup() -{ - WiFi.onEvent(WiFiEvent); - ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE); - - Serial.println(F("Getting IP address...")); - - while (!eth_connected) - delay(100); - - return true; -} diff --git a/examples/ESP32_DynamicInstantiation/ESP32_dynamicClass.ino b/examples/ESP32_DynamicInstantiation/ESP32_dynamicClass.ino new file mode 100644 index 0000000..e1947e1 --- /dev/null +++ b/examples/ESP32_DynamicInstantiation/ESP32_dynamicClass.ino @@ -0,0 +1,57 @@ +#include + +#include "ETH_Helper.h" + +#define SerialMon Serial +#include "midiHelpers.h" + + +#define ethernet true; // set to false to demonstrate WiFi usage + +char ssid[] = "ssid"; // your network SSID (name) +char pass[] = "pass"; // your network password (use for WPA, or use as key for WEP) + +MidiClient* midiClient; // generic class offered as an alternative of the MACRO + +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); + +void WIFI_startup(){ + WiFi.begin(ssid, pass); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + AM_DBG(F("Establishing connection to WiFi..")); + } +} + +void setup(){ + + AM_DBG_SETUP(115200); + + bool useEth = false; + + if (useEth){ + ETH_startup(); + midiClient = new AppleMidiWithInterfaceWrapper("APPLE_MIDIETHCLIENT",DEFAULT_CONTROL_PORT); + }else{ + WIFI_startup(); + midiClient = new AppleMidiWithInterfaceWrapper("APPLE_MIDIWIFICLIENT",DEFAULT_CONTROL_PORT); + } + + midiClient->begin(); + + midiClient->setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + AM_DBG(F("Connected to session"), ssrc, name); + }); + midiClient->setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + AM_DBG(F("Disconnected"), ssrc); + }); + + AM_DBG(F("OK, now make sure you have an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), useEth?Ethernet.localIP():WiFi.localIP(), "Port", midiClient->getPort(), "(Name", midiClient->getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); +} + +void loop(){ + midiClient->read(); +} \ No newline at end of file diff --git a/examples/ESP32_DynamicInstantiation/ETH_helper.h b/examples/ESP32_DynamicInstantiation/ETH_helper.h new file mode 100644 index 0000000..11e5507 --- /dev/null +++ b/examples/ESP32_DynamicInstantiation/ETH_helper.h @@ -0,0 +1,110 @@ +#ifdef ETHERNET3 +#include +#else +#include +#include // https://github.com/TrippyLighting/EthernetBonjour +#endif + +// to get the Mac address +#include + +#define RESET_PIN D7 +#define CS_PIN D5 + +// 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 +}; + +/* + Wiz W5500 reset function. Change this for the specific reset + sequence required for your particular board or module. +*/ +void hardreset() { + pinMode(RESET_PIN, OUTPUT); + digitalWrite(RESET_PIN, HIGH); + delay(150); + + digitalWrite(RESET_PIN, LOW); + delay(500); + digitalWrite(RESET_PIN, HIGH); + delay(150); +} + +bool ETH_startup() +{ +#ifdef ETHERNET3 + Ethernet.setRstPin(RESET_PIN); + Ethernet.setCsPin(CS_PIN); + Ethernet.init(4); // maxSockNum = 4 Socket 0...3 -> RX/TX Buffer 4k + Serial.println("Resetting Wiz W5500 Ethernet Board... "); + Ethernet.hardreset(); +#else + Ethernet.init(CS_PIN); + Serial.println("Resetting Wiz Ethernet Board... "); + hardreset(); +#endif + + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + /* + Network configuration - all except the MAC are optional. + + IMPORTANT NOTE - The mass-produced W5500 boards do -not- + have a built-in MAC address (depite + comments to the contrary elsewhere). You + -must- supply a MAC address here. + */ +#ifdef ETHERNET3 + Serial.println("Starting Ethernet3 connection..."); +#else + Serial.println("Starting Ethernet connection..."); + //if (Ethernet.hardwareStatus() == EthernetNoHardware) Serial.println("No EthernetHW"); +#endif + Ethernet.begin(mac); + Serial.print("Ethernet IP is: "); + Serial.println(Ethernet.localIP()); + + /* + Sanity checks for W5500 and cable connection. + */ + Serial.println("Checking connection."); + bool rdy_flag = false; + for (uint8_t i = 0; i <= 20; i++) { +#ifdef ETHERNET3 + if ((Ethernet.link() == 0)) { +#else + if ((Ethernet.linkStatus() == Unknown)) { +#endif + Serial.print("."); + rdy_flag = false; + delay(80); + } else { + rdy_flag = true; + break; + } + } + if (rdy_flag == false) { + Serial.println("\n\r\tHardware fault, or cable problem... cannot continue."); + while (true) { + delay(10); // Halt. + } + } else { + Serial.println("OK"); + } + +#ifndef ETHERNET3 + // Initialize the Bonjour/MDNS library. You can now reach or ping this + // Arduino via the host name "arduino.local", provided that your operating + // system is Bonjour-enabled (such as MacOS X). + // Always call this before any other method! + EthernetBonjour.begin("arduino"); + + EthernetBonjour.addServiceRecord("apple-midi", //Arduino._apple-midi doesnt work + 5004, + MDNSServiceUDP); +#endif + + return true; +} \ No newline at end of file diff --git a/examples/ESP32_DynamicInstantiation/midiHelpers.h b/examples/ESP32_DynamicInstantiation/midiHelpers.h new file mode 100644 index 0000000..e9ee93e --- /dev/null +++ b/examples/ESP32_DynamicInstantiation/midiHelpers.h @@ -0,0 +1,87 @@ +#ifndef MIDIHELPERS_h +#define MIDIHELPERS_h + +#define USE_EXT_CALLBACKS // as from example => required for MIDI callbacks +#include //https://github.com/lathoub/Arduino-AppleMIDI-Library + +using namespace APPLEMIDI_NAMESPACE; + +/* +class Utilities for creating a generic pointer to AppleMIDISession and MidiInterface +from a WiFiUDP or EThernetUDP (template) and be able to manipulate it as a generic instance +and manipulating the pointer generically +*/ + +class MidiClient { +public: + virtual void read() = 0; + virtual ~MidiClient() {} + virtual void setHandleConnected(void (*fptr)(const ssrc_t &, const char *))= 0; + virtual void setHandleDisconnected(void (*fptr)(const ssrc_t &)) = 0; + virtual void setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value))=0; + virtual const char *getName() = 0; + virtual const uint16_t getPort() = 0; + virtual void begin() = 0; + // all methods you need to be wrapped below + virtual void sendNoteOn(byte note, byte velocity, byte channel) = 0; + virtual void sendNoteOff(byte note, byte velocity, byte channel) = 0; + // etc... + +}; + +template +class AppleMidiWithInterfaceWrapper : public MidiClient { +public: + AppleMIDISession* session; + MidiInterface, AppleMIDISettings>* midi; + + AppleMidiWithInterfaceWrapper(const char* sessionName, uint16_t port) { + session = new AppleMIDISession(sessionName, port); + midi = new MidiInterface, AppleMIDISettings>(*session); + } + + virtual void begin(){ + session->begin(); + } + + virtual const char *getName(){ + return session->getName(); + } + + virtual const uint16_t getPort(){ + return session->getPort(); + } + + virtual void setHandleConnected(void (*fptr)(const ssrc_t &, const char *)){ + session->setHandleConnected(fptr); + } + + virtual void setHandleDisconnected(void (*fptr)(const ssrc_t &)){ + session->setHandleDisconnected(fptr); + } + + virtual void setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value)){ + session->setHandleException(fptr); + } + + + void read() override { + midi->read(); + } + + void sendNoteOn(byte note, byte velocity, byte channel) override { + midi->sendNoteOn(note, velocity, channel); + } + + void sendNoteOff(byte note, byte velocity, byte channel) override { + midi->sendNoteOff(note, velocity, channel); + } + + + ~AppleMidiWithInterfaceWrapper() { + delete midi; + delete session; + } +}; + +#endif \ No newline at end of file diff --git a/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino b/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino new file mode 100644 index 0000000..6c506c3 --- /dev/null +++ b/examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino @@ -0,0 +1,80 @@ +#include +#include +#include + +#define SerialMon Serial +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + AM_DBG(F("Establishing connection to WiFi..")); + } + AM_DBG(F("Connected to network")); + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + AM_DBG(F("Listen to incoming MIDI commands")); + + MIDI.begin(); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_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/ESP32_W5500_Callbacks/ESP32_W5500_Callbacks.ino b/examples/ESP32_W5500_Callbacks/ESP32_W5500_Callbacks.ino new file mode 100644 index 0000000..cedb154 --- /dev/null +++ b/examples/ESP32_W5500_Callbacks/ESP32_W5500_Callbacks.ino @@ -0,0 +1,157 @@ +#include "ETH_Helper.h" + +#define SerialMon Serial +#define ONE_PARTICIPANT +#define USE_EXT_CALLBACKS +#include + +unsigned long t1 = millis(); +int8_t isConnected = 0; + +APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "AppleMIDI-Arduino", DEFAULT_CONTROL_PORT); + +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Das Booting")); + + ETH_startup(); + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(MIDI_CHANNEL_OMNI); + + // Normal callbacks - always available + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + // Extended callback, only available when defining USE_EXT_CALLBACKS + AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) { + // AM_DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr); + }); + AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) { + AM_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) { + // AM_DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency"); + }); + AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + // AM_DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) { + // AM_DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value); + }); + AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) { + // AM_DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc); + }); + AppleMIDI.setHandleException(OnAppleMidiException); + + MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { + AM_DBG(F("ControlChange"), channel, v1, v2); + }); + MIDI.setHandleProgramChange([](Channel channel, byte v1) { + AM_DBG(F("ProgramChange"), channel, v1); + }); + MIDI.setHandlePitchBend([](Channel channel, int v1) { + AM_DBG(F("PitchBend"), channel, v1); + }); + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), channel, note, velocity); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), channel, note, velocity); + }); + + AM_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) > 100) + { + t1 = millis(); + + byte note = random(1, 127); + byte velocity = 55; + byte channel = 1; + + // AM_DBG(F("\nsendNoteOn"), note, velocity, channel); + MIDI.sendNoteOn(note, velocity, channel); + //MIDI.sendNoteOff(note, velocity, channel); + } + +#ifndef ETHERNET3 + EthernetBonjour.run(); +#endif +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { + switch (e) + { + case APPLEMIDI_NAMESPACE::Exception::BufferFullException: + AM_DBG(F("*** BufferFullException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParseException: + AM_DBG(F("*** ParseException")); + break; + case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: + AM_DBG(F("*** TooManyParticipantsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: + AM_DBG(F("*** UnexpectedInviteException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: + AM_DBG(F("*** ParticipantNotFoundException"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: + AM_DBG(F("*** ComputerNotInDirectory"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: + AM_DBG(F("*** NotAcceptingAnyone"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: + AM_DBG(F("*** ListenerTimeOutException")); + break; + case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: + AM_DBG(F("*** MaxAttemptsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: + AM_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: + AM_DBG(F("*** SendPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: + AM_DBG(F("******************************************** ReceivedPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::UdpBeginPacketFailed: + AM_DBG(F("*** UdpBeginPacketFailed"), value); + break; + } +} diff --git a/examples/ESP32_W5500_Callbacks/ETH_Helper.h b/examples/ESP32_W5500_Callbacks/ETH_Helper.h new file mode 100644 index 0000000..7d0f0b8 --- /dev/null +++ b/examples/ESP32_W5500_Callbacks/ETH_Helper.h @@ -0,0 +1,110 @@ +#ifdef ETHERNET3 +#include +#else +#include +#include // https://github.com/TrippyLighting/EthernetBonjour +#endif + +// to get the Mac address +#include + +#define RESET_PIN 26 +#define CS_PIN 5 + +// 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 +}; + +/* + Wiz W5500 reset function. Change this for the specific reset + sequence required for your particular board or module. +*/ +void hardreset() { + pinMode(RESET_PIN, OUTPUT); + digitalWrite(RESET_PIN, HIGH); + delay(150); + + digitalWrite(RESET_PIN, LOW); + delay(500); + digitalWrite(RESET_PIN, HIGH); + delay(150); +} + +bool ETH_startup() +{ +#ifdef ETHERNET3 + Ethernet.setRstPin(RESET_PIN); + Ethernet.setCsPin(CS_PIN); + Ethernet.init(4); // maxSockNum = 4 Socket 0...3 -> RX/TX Buffer 4k + Serial.println("Resetting Wiz W5500 Ethernet Board... "); + Ethernet.hardreset(); +#else + Ethernet.init(CS_PIN); + Serial.println("Resetting Wiz Ethernet Board... "); + hardreset(); +#endif + + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + /* + Network configuration - all except the MAC are optional. + + IMPORTANT NOTE - The mass-produced W5500 boards do -not- + have a built-in MAC address (depite + comments to the contrary elsewhere). You + -must- supply a MAC address here. + */ +#ifdef ETHERNET3 + Serial.println("Starting Ethernet3 connection..."); +#else + Serial.println("Starting Ethernet connection..."); +#endif + + Ethernet.begin(mac); + Serial.print("Ethernet IP is: "); + Serial.println(Ethernet.localIP()); + + /* + Sanity checks for W5500 and cable connection. + */ + Serial.println("Checking connection."); + bool rdy_flag = false; + for (uint8_t i = 0; i <= 20; i++) { +#ifdef ETHERNET3 + if ((Ethernet.link() == 0)) { +#else + if ((Ethernet.linkStatus() == Unknown)) { +#endif + Serial.print("."); + rdy_flag = false; + delay(80); + } else { + rdy_flag = true; + break; + } + } + if (rdy_flag == false) { + Serial.println("\n\r\tHardware fault, or cable problem... cannot continue."); + while (true) { + delay(10); // Halt. + } + } else { + Serial.println("OK"); + } + +#ifndef ETHERNET3 + // Initialize the Bonjour/MDNS library. You can now reach or ping this + // Arduino via the host name "arduino.local", provided that your operating + // system is Bonjour-enabled (such as MacOS X). + // Always call this before any other method! + EthernetBonjour.begin("arduino"); + + EthernetBonjour.addServiceRecord("Arduino._apple-midi", + 5004, + MDNSServiceUDP); +#endif + + return true; +} diff --git a/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino b/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino new file mode 100644 index 0000000..082b62e --- /dev/null +++ b/examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino @@ -0,0 +1,80 @@ +#include +#include +#include + +#define SerialMon Serial +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + AM_DBG(F("Establishing connection to WiFi..")); + } + AM_DBG(F("Connected to network")); + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), WiFi.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + AM_DBG(F("Listen to incoming MIDI commands")); + + MIDI.begin(); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_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_softAP_mDNS/ESP8266_NoteOnOffEverySec_softAP_mDNS.ino b/examples/ESP8266_NoteOnOffEverySec_softAP_mDNS/ESP8266_NoteOnOffEverySec_softAP_mDNS.ino new file mode 100644 index 0000000..9ce9276 --- /dev/null +++ b/examples/ESP8266_NoteOnOffEverySec_softAP_mDNS/ESP8266_NoteOnOffEverySec_softAP_mDNS.ino @@ -0,0 +1,95 @@ +// Example to start ESP8266 in soft access point / hotspot mode +// and also enable mDNS response on local network. This allows +// client to discover the AppleMIDI service and connect to it +// without having to type the IP address and port +// Tested on iOS 9 (old iPad) and iOS 13 (iPhone 6) +// On Win10 (rtpMIDI), ESP8266 did not show in directory, +// but connects fine with default IP(192.168.4.1)/port(5004) + +#include +#include +#include +#include + +#define SerialMon Serial +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + WiFi.softAP(ssid, pass); + + AM_DBG(F("Started soft access point:"), WiFi.softAPIP(), "Port", AppleMIDI.getPort()); + AM_DBG(F("AppleMIDI device name:"), AppleMIDI.getName()); + // Set up mDNS responder: + if (!MDNS.begin(AppleMIDI.getName())) + AM_DBG(F("Error setting up MDNS responder!")); + char str[128] = ""; + strcat(str, AppleMIDI.getName()); + strcat(str,".local"); + AM_DBG(F("mDNS responder started at:"), str); + MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); + AM_DBG(F("Open Wifi settings and connect to soft acess point using 'ssid'")); + AM_DBG(F("Start MIDI Network app on iPhone/iPad or rtpMIDI on Windows")); + AM_DBG(F("AppleMIDI-ESP8266 will show in the 'Directory' list (rtpMIDI) or")); + AM_DBG(F("under 'Found on the network' list (iOS). Select and click 'Connect'")); + + MIDI.begin(); + + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); + + AM_DBG(F("Sending NoteOn/Off of note 45, every second")); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + + MDNS.update(); + + // 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); + } +} \ No newline at end of file diff --git a/examples/EthernetShield_Bonjour/EthernetShield_Bonjour.ino b/examples/EthernetShield_Bonjour/EthernetShield_Bonjour.ino deleted file mode 100644 index edcd6a9..0000000 --- a/examples/EthernetShield_Bonjour/EthernetShield_Bonjour.ino +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include // https://github.com/TrippyLighting/EthernetBonjour - -#define OPTIONAL_MDNS - -#include -USING_NAMESPACE_APPLEMIDI - -// 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(); -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print(F("IP address is ")); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Listen for MIDI messages on channel 1 - MIDI.begin(1); - - // Initialize the Bonjour/MDNS library. You can now reach or ping this - // Arduino via the host name "arduino.local", provided that your operating - // system is Bonjour-enabled (such as MacOS X). - // Always call this before any other method! - EthernetBonjour.begin("arduino"); - - EthernetBonjour.addServiceRecord("Arduino._apple-midi", - AppleMIDI.getPort(), - MDNSServiceUDP); - - // Stay informed on connection status - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - AppleMIDI.setHandleError(OnAppleMidiError); - - // and let us know ehen notes come in - MIDI.setHandleNoteOn(OnMidiNoteOn); - MIDI.setHandleNoteOff(OnMidiNoteOff); - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void loop() -{ - // Listen to incoming notes - MIDI.read(); - - EthernetBonjour.run(); - - // send note on/off every second - // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) - { - t1 = millis(); - // Serial.print(F("."); - - byte note = random(1, 127); - byte velocity = 55; - byte channel = 1; - - MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); - } -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Error occorded during processing -// ----------------------------------------------------------------------------- -void OnAppleMidiError(const ssrc_t & ssrc, int32_t errorCode) { - Serial.println(F("ERROR")); - exit(1); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} diff --git a/examples/EthernetShield_Initiator/EthernetShield_Initiator.ino b/examples/EthernetShield_Initiator/EthernetShield_Initiator.ino deleted file mode 100644 index f9c3f6f..0000000 --- a/examples/EthernetShield_Initiator/EthernetShield_Initiator.ino +++ /dev/null @@ -1,132 +0,0 @@ -#include - -#define APPLEMIDI_INITIATOR -#include -USING_NAMESPACE_APPLEMIDI - -// 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(); -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print(F("IP address is ")); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Listen for MIDI messages on channel 1 - MIDI.begin(1); - - // Stay informed on connection status - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - // and let us know ehen notes come in - MIDI.setHandleNoteOn(OnMidiNoteOn); - MIDI.setHandleNoteOff(OnMidiNoteOff); - - // Initiate the session - IPAddress remote(192, 168, 1, 156); - AppleMIDI.sendInvite(remote); // port is 5004 by default - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void loop() -{ - // Listen to incoming notes - MIDI.read(); - - // send note on/off every second - // (dont cáll delay(1000) as it will stall the pipeline) - if (isConnected && (millis() - t1) > 1000) - { - t1 = millis(); - // Serial.print(F("."); - - byte note = random(1, 127); - byte velocity = 55; - byte channel = 1; - - // MIDI.sendNoteOn(note, velocity, channel); - // MIDI.sendNoteOff(note, velocity, channel); - } -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.print(name); - Serial.print(F(" ssrc: 0x")); - Serial.println(ssrc, HEX); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.print (F("Disconnected from ssrc 0x")); - Serial.println(ssrc, HEX); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} diff --git a/examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino b/examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino deleted file mode 100644 index c211404..0000000 --- a/examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -USING_NAMESPACE_APPLEMIDI - -// 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(); -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print(F("IP address is ")); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - - // Listen for MIDI messages on channel 1 - MIDI.begin(1); - - // Stay informed on connection status - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - Serial.println(F("Send MIDI messages to this session and see the latency on the Serial Monitor")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -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 && (millis() - t1) > 1000) - { - t1 = millis(); - // Serial.print(F("."); - - byte note = random(1, 127); - byte velocity = 55; - byte channel = 1; - - MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); - } -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} diff --git a/examples/EthernetShield_RealTimeMessages/EthernetShield_RealTimeMessages.ino b/examples/EthernetShield_RealTimeMessages/EthernetShield_RealTimeMessages.ino deleted file mode 100644 index f6052cd..0000000 --- a/examples/EthernetShield_RealTimeMessages/EthernetShield_RealTimeMessages.ino +++ /dev/null @@ -1,162 +0,0 @@ -#include - -#include -USING_NAMESPACE_APPLEMIDI - -// 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 -}; - -bool isConnected; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print("IP address is "); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Create a session and wait for a remote host to connect to us - MIDI.begin(1); - - // check: zien we de connecttion binnenkomen?? Anders terug een ref van maken - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - AppleMIDI.setHandleError(OnAppleMidiError); - - MIDI.setHandleClock(OnMidiClock); - MIDI.setHandleStart(OnMidiStart); - MIDI.setHandleStop(OnMidiStop); - MIDI.setHandleContinue(OnMidiContinue); - MIDI.setHandleActiveSensing(OnMidiActiveSensing); - MIDI.setHandleSystemReset(OnMidiSystemReset); - MIDI.setHandleSongPosition(OnMidiSongPosition); - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void loop() -{ - // Listen to incoming notes - MIDI.read(); -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Error occorded during processing -// ----------------------------------------------------------------------------- -void OnAppleMidiError(const ssrc_t & ssrc, int32_t errorCode) { - Serial.println(F("ERROR")); - exit(1); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiClock() { - Serial.println(F("Clock")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiStart() { - Serial.println(F("Start")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiStop() { - Serial.println(F("Stop")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiContinue() { - Serial.println(F("Continue")); -} - -// ----------------------------------------------------------------------------- -// (https://www.midi.org/specifications/item/table-1-summary-of-midi-message) -// Active Sensing. -// This message is intended to be sent repeatedly to tell the receiver that a -// connection is alive. Use of this message is optional. When initially received, -// the receiver will expect to receive another Active Sensing message each 300ms (max), -// and if it does not then it will assume that the connection has been terminated. -// At termination, the receiver will turn off all voices and return to normal -// (non- active sensing) operation. -// ----------------------------------------------------------------------------- -static void OnMidiActiveSensing() { - Serial.println(F("ActiveSensing")); -} - -// ----------------------------------------------------------------------------- -// (https://www.midi.org/specifications/item/table-1-summary-of-midi-message) -// Reset. -// Reset all receivers in the system to power-up status. This should be used -// sparingly, preferably under manual control. In particular, it should not be -// sent on power-up. -// ----------------------------------------------------------------------------- -static void OnMidiSystemReset() { - Serial.println(F("SystemReset")); -} - -// ----------------------------------------------------------------------------- -// (https://www.midi.org/specifications/item/table-1-summary-of-midi-message) -// Song Position Pointer. -// This is an internal 14 bit register that holds the number of MIDI beats -// (1 beat= six MIDI clocks) since the start of the song. l is the LSB, m the MSB. -// ----------------------------------------------------------------------------- -static void OnMidiSongPosition(unsigned a) { - Serial.print (F("SongPosition: ")); - Serial.println(a); -} diff --git a/examples/EthernetShield_ReceiveMTC/EthernetShield_ReceiveMTC.ino b/examples/EthernetShield_ReceiveMTC/EthernetShield_ReceiveMTC.ino deleted file mode 100644 index 7bc351c..0000000 --- a/examples/EthernetShield_ReceiveMTC/EthernetShield_ReceiveMTC.ino +++ /dev/null @@ -1,90 +0,0 @@ -#include - -#include -USING_NAMESPACE_APPLEMIDI - -// 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 -}; - -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print("IP address is "); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Create a session and wait for a remote host to connect to us - MIDI.begin(1); - - // check: zien we de connecttion binnenkomen?? Anders terug een ref van maken - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - MIDI.setHandleTimeCodeQuarterFrame(OnMidiTimeCodeQuarterFrame); - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void loop() -{ - // Listen to incoming notes - MIDI.read(); -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiTimeCodeQuarterFrame(byte data) { - Serial.print("MTC: "); - Serial.println(data, HEX); -} diff --git a/examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino b/examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino deleted file mode 100644 index c3b026f..0000000 --- a/examples/EthernetShield_ReceivedRawMidiData/EthernetShield_ReceivedRawMidiData.ino +++ /dev/null @@ -1,117 +0,0 @@ -#include - -#include -USING_NAMESPACE_APPLEMIDI - -// 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(); -bool isConnected = false; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print(F("IP address is ")); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Create a session and wait for a remote host to connect to us - MIDI.begin(1); - - // check: zien we de connecttion binnenkomen?? Anders terug een ref van maken - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - AppleMIDI.setHandleReceivedMidi(OnAppleMidiReceivedByte); - - MIDI.setHandleNoteOn(OnMidiNoteOn); - MIDI.setHandleNoteOff(OnMidiNoteOff); - - Serial.println(F("Waiting for incoming MIDI messages")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void loop() -{ - // Listen to incoming notes - MIDI.read(); -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void OnAppleMidiReceivedByte(const ssrc_t & ssrc, byte data) { - Serial.println(data, HEX); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} diff --git a/examples/EthernetShield_SysEx/EthernetShield_SysEx.ino b/examples/EthernetShield_SysEx/EthernetShield_SysEx.ino deleted file mode 100644 index fdcb968..0000000 --- a/examples/EthernetShield_SysEx/EthernetShield_SysEx.ino +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#include -USING_NAMESPACE_APPLEMIDI - -// 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(); -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 }; -byte sysex16[] = { 0xF0, 0x43, 0x20, 0x7E, 0x4C, 0x4D, 0x20, 0x20, 0x38, 0x39, 0x37, 0x33, 0x32, 0x50, 0x4D, 0xF7 }; -byte sysexBig[] = { 0xF0, 0x41, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, - 0x7a, - - 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, - 0xF7 }; - -APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - Serial.println(F("Getting IP address...")); - - if (Ethernet.begin(mac) == 0) { - Serial.println(F("Failed DHCP, check network cable & reboot")); - for (;;); - } - - Serial.print("IP address is "); - Serial.println(Ethernet.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(Ethernet.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - // Create a session and wait for a remote host to connect to us - MIDI.begin(1); - - // check: zien we de connecttion binnenkomen?? Anders terug een ref van makenDw - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - MIDI.setHandleSystemExclusive(OnMidiSysEx); - - Serial.println(F("Every second send a random NoteOn/Off")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -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 && (millis() - t1) > 1000) - { - MIDI.sendSysEx(sizeof(sysexBig), sysexBig, true); - t1 = millis(); - } -} - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void OnMidiSysEx(byte* data, unsigned length) { - Serial.print(F("SYSEX: (")); - Serial.print(getSysExStatus(data, length)); - Serial.print(F(", ")); - Serial.print(length); - Serial.print(F(" bytes) ")); - for (uint16_t i = 0; i < length; i++) - { - Serial.print(data[i], HEX); - Serial.print(" "); - } - Serial.println(); -} - -char getSysExStatus(const byte* data, uint16_t length) -{ - if (data[0] == 0xF0 && data[length - 1] == 0xF7) - return 'F'; // Full SysEx Command - else if (data[0] == 0xF0 && data[length - 1] != 0xF7) - return 'S'; // Start of SysEx-Segment - else if (data[0] != 0xF0 && data[length - 1] != 0xF7) - return 'M'; // Middle of SysEx-Segment - else - return 'E'; // End of SysEx-Segment -} diff --git a/examples/SAMD_Bonjour/SAMD_Bonjour.ino b/examples/SAMD_Bonjour/SAMD_Bonjour.ino new file mode 100644 index 0000000..ae73f26 --- /dev/null +++ b/examples/SAMD_Bonjour/SAMD_Bonjour.ino @@ -0,0 +1,44 @@ +#include +#include // https://github.com/TrippyLighting/EthernetBonjour + +#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 +}; + +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void setup() +{ + if (Ethernet.begin(mac) == 0) + for (;;); + + MIDI.begin(); + + // Initialize the Bonjour/MDNS library. You can now reach or ping this + // Arduino via the host name "arduino.local", provided that your operating + // system is Bonjour-enabled (such as MacOS X). + // Always call this before any other method! + EthernetBonjour.begin("arduino"); + + EthernetBonjour.addServiceRecord("Arduino._apple-midi", + AppleMIDI.getPort(), + MDNSServiceUDP); +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void loop() +{ + // Listen to incoming notes + MIDI.read(); + + EthernetBonjour.run(); +} diff --git a/examples/Teensy41_NoteOnOffEverySec/Teensy41_NoteOnOffEverySec.ino b/examples/Teensy41_NoteOnOffEverySec/Teensy41_NoteOnOffEverySec.ino new file mode 100644 index 0000000..4a5278c --- /dev/null +++ b/examples/Teensy41_NoteOnOffEverySec/Teensy41_NoteOnOffEverySec.ino @@ -0,0 +1,149 @@ +#include + +#define SerialMon Serial +#define USE_EXT_CALLBACKS +#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() +{ + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); + + if (Ethernet.begin(mac) == 0) { + AM_DBG(F("Failed DHCP, check network cable & reboot")); + for (;;); + } + + // Check for Ethernet hardware present + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + AM_DBG(F("Ethernet shield was not found. Sorry, can't run without hardware. :(")); + while (true) { + delay(1); // do nothing, no point running without Ethernet hardware + } + } + while (Ethernet.linkStatus() == LinkOFF) { + AM_DBG(F("Ethernet cable is not connected.")); + delay(500); + } + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(MIDI_CHANNEL_OMNI); + + // Stay informed on connection status + AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { + isConnected++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + + AppleMIDI.setHandleException(OnAppleMidiException); + + MIDI.setHandleControlChange([](Channel channel, byte v1, byte v2) { + AM_DBG(F("ControlChange"), channel, v1, v2); + }); + MIDI.setHandleProgramChange([](Channel channel, byte v1) { + AM_DBG(F("ProgramChange"), channel, v1); + }); + MIDI.setHandlePitchBend([](Channel channel, int v1) { + AM_DBG(F("PitchBend"), channel, v1); + }); + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), channel, note, velocity); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), channel, note, velocity); + }); + + AM_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); + } +} + +// ----------------------------------------------------------------------------- +// +// ----------------------------------------------------------------------------- +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { + switch (e) + { + case APPLEMIDI_NAMESPACE::Exception::BufferFullException: + AM_DBG(F("*** BufferFullException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParseException: + AM_DBG(F("*** ParseException")); + break; + case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: + AM_DBG(F("*** TooManyParticipantsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: + AM_DBG(F("*** UnexpectedInviteException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: + AM_DBG(F("*** ParticipantNotFoundException"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: + AM_DBG(F("*** ComputerNotInDirectory"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: + AM_DBG(F("*** NotAcceptingAnyone"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: + AM_DBG(F("*** ListenerTimeOutException")); + break; + case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: + AM_DBG(F("*** MaxAttemptsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: + AM_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: + AM_DBG(F("*** SendPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: + AM_DBG(F("*** ReceivedPacketsDropped"), value); + break; + } +} diff --git a/examples/wESP32_NoteOnOffEverySec/ETH_Helper.h b/examples/wESP32_NoteOnOffEverySec/ETH_Helper.h index e997fad..cab1ca1 100644 --- a/examples/wESP32_NoteOnOffEverySec/ETH_Helper.h +++ b/examples/wESP32_NoteOnOffEverySec/ETH_Helper.h @@ -7,32 +7,25 @@ void WiFiEvent(WiFiEvent_t event) { switch (event) { case SYSTEM_EVENT_ETH_START: - Serial.println("ETH Started"); + AM_DBG(F("ETH Started")); //set eth hostname here ETH.setHostname("esp32-ethernet"); break; case SYSTEM_EVENT_ETH_CONNECTED: - Serial.println("ETH Connected"); + AM_DBG(F("ETH Connected")); break; case SYSTEM_EVENT_ETH_GOT_IP: - Serial.print("ETH MAC: "); - Serial.print(ETH.macAddress()); - Serial.print(", IPv4: "); - Serial.print(ETH.localIP()); - if (ETH.fullDuplex()) { - Serial.print(", FULL_DUPLEX"); - } - Serial.print(", "); - Serial.print(ETH.linkSpeed()); - Serial.println("Mbps"); + AM_DBG(F("ETH MAC:"), ETH.macAddress(), F("IPv4:"), ETH.localIP(), ETH.linkSpeed(), F("Mbps")); + if (ETH.fullDuplex()) + AM_DBG(F("FULL_DUPLEX")); eth_connected = true; break; case SYSTEM_EVENT_ETH_DISCONNECTED: - Serial.println("ETH Disconnected"); + AM_DBG(F("ETH Disconnected")); eth_connected = false; break; case SYSTEM_EVENT_ETH_STOP: - Serial.println("ETH Stopped"); + AM_DBG(F("ETH Stopped")); eth_connected = false; break; default: @@ -45,7 +38,7 @@ bool ETH_startup() WiFi.onEvent(WiFiEvent); ETH.begin(); - Serial.println(F("Getting IP address...")); + AM_DBG(F("Getting IP address...")); while (!eth_connected) delay(100); diff --git a/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino b/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino index a57c7a6..fe58e9d 100644 --- a/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino +++ b/examples/wESP32_NoteOnOffEverySec/wESP32_NoteOnOffEverySec.ino @@ -1,53 +1,55 @@ -#include "ETH_Helper.h" - +#define SerialMon Serial +#define USE_EXT_CALLBACKS #include -USING_NAMESPACE_APPLEMIDI + +#include "./ETH_Helper.h" unsigned long t0 = millis(); -bool isConnected = false; +int8_t isConnected = 0; -APPLEMIDI_CREATE_DEFAULTSESSION_ESP32_INSTANCE(); +APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); -//WiFiServer server(80); +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t); // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- void setup() { - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); + AM_DBG_SETUP(115200); + AM_DBG(F("Booting")); ETH_startup(); - // Start TCP (HTTP) server - // server.begin(); - - MDNS.begin(AppleMIDI.getName()); - - Serial.print("IP address is "); - Serial.println(ETH.localIP()); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(ETH.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - MIDI.begin(1); // listen on channel 1 - - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - MIDI.setHandleNoteOn(OnAppleMidiNoteOn); - MIDI.setHandleNoteOff(OnAppleMidiNoteOff); + if (!MDNS.begin(AppleMIDI.getName())) + AM_DBG(F("Error setting up MDNS responder")); + + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), ETH.localIP(), "Port", AppleMIDI.getPort()); + AM_DBG(F("The device should also be visible in the directory as"), AppleMIDI.getName()); + AM_DBG(F("Select and then press the Connect button")); + AM_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++; + AM_DBG(F("Connected to session"), ssrc, name); + }); + AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { + isConnected--; + AM_DBG(F("Disconnected"), ssrc); + }); + AppleMIDI.setHandleException(OnAppleMidiException); + + MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOn"), note); + }); + MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) { + AM_DBG(F("NoteOff"), note); + }); MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); - MDNS.addService("http", "tcp", 80); - - Serial.println(F("Every second send a random NoteOn/Off")); } // ----------------------------------------------------------------------------- @@ -60,61 +62,61 @@ 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) > 100) { t0 = millis(); - // Serial.print(F("."); - byte note = random(1, 127); + byte note = random(15, 100); byte velocity = 55; byte channel = 1; MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); + // MIDI.sendNoteOff(note, velocity, channel); } } -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); +void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) { + switch (e) + { + case APPLEMIDI_NAMESPACE::Exception::BufferFullException: + AM_DBG(F("*** BufferFullException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParseException: + AM_DBG(F("*** ParseException")); + break; + case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException: + AM_DBG(F("*** TooManyParticipantsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException: + AM_DBG(F("*** UnexpectedInviteException")); + break; + case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException: + AM_DBG(F("*** ParticipantNotFoundException"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory: + AM_DBG(F("*** ComputerNotInDirectory"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone: + AM_DBG(F("*** NotAcceptingAnyone"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException: + AM_DBG(F("*** ListenerTimeOutException")); + break; + case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException: + AM_DBG(F("*** MaxAttemptsException")); + break; + case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException: + AM_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: + AM_DBG(F("*** SendPacketsDropped"), value); + break; + case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped: + AM_DBG(F("*** ReceivedPacketsDropped"), value); + break; + } } diff --git a/examples/wESP32_NoteOnOffEverySec_Adv/AppleMIDITask.h b/examples/wESP32_NoteOnOffEverySec_Adv/AppleMIDITask.h deleted file mode 100644 index db700a8..0000000 --- a/examples/wESP32_NoteOnOffEverySec_Adv/AppleMIDITask.h +++ /dev/null @@ -1,102 +0,0 @@ -#include -USING_NAMESPACE_APPLEMIDI - -bool isConnected = false; - -// ==================================================================================== -// Event handlers for incoming MIDI messages -// ==================================================================================== - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device connected -// ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { - isConnected = true; - Serial.print(F("Connected to session ")); - Serial.println(name); -} - -// ----------------------------------------------------------------------------- -// rtpMIDI session. Device disconnected -// ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { - isConnected = false; - Serial.println(F("Disconnected")); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOn(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOn from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -static void OnAppleMidiNoteOff(byte channel, byte note, byte velocity) { - Serial.print(F("Incoming NoteOff from channel: ")); - Serial.print(channel); - Serial.print(F(", note: ")); - Serial.print(note); - Serial.print(F(", velocity: ")); - Serial.println(velocity); -} - -void TaskAppleMIDIcode(void* pvParameters) -{ - APPLEMIDI_CREATE_DEFAULTSESSION_ESP32_INSTANCE(); - - Serial.print("AppleMIDI running on core "); - Serial.println(xPortGetCoreID()); - - Serial.println(F("Every second send a random NoteOn/Off")); - - Serial.println(F("OK, now make sure you an rtpMIDI session that is Enabled")); - Serial.print(F("Add device named Arduino with Host/Port ")); - Serial.print(ETH.localIP()); - Serial.println(F(":5004")); - Serial.println(F("Then press the Connect button")); - Serial.println(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - AppleMIDI.setHandleConnected(OnAppleMidiConnected); - AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - - MIDI.setHandleNoteOn(OnAppleMidiNoteOn); - MIDI.setHandleNoteOff(OnAppleMidiNoteOff); - - MDNS.addService("apple-midi", "udp", AppleMIDI.getPort()); - - MIDI.begin(1); // listen on channel 1 - - auto t0 = millis(); - while (true) - { - // Listen to incoming notes - MIDI.read(); - - // send a note every second - // (dont cáll delay(1000) as it will stall the pipeline) - if ((millis() - t0) > 1000) - { - t0 = millis(); - - if (isConnected) - { - byte note = random(1, 127); - byte velocity = 55; - byte channel = 1; - - Serial.println("Sending notes"); - - MIDI.sendNoteOn(note, velocity, channel); - MIDI.sendNoteOff(note, velocity, channel); - } - } - } -} diff --git a/examples/wESP32_NoteOnOffEverySec_Adv/ETH_Helper.h b/examples/wESP32_NoteOnOffEverySec_Adv/ETH_Helper.h deleted file mode 100644 index e997fad..0000000 --- a/examples/wESP32_NoteOnOffEverySec_Adv/ETH_Helper.h +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include - -static bool eth_connected = false; - -void WiFiEvent(WiFiEvent_t event) -{ - switch (event) { - case SYSTEM_EVENT_ETH_START: - Serial.println("ETH Started"); - //set eth hostname here - ETH.setHostname("esp32-ethernet"); - break; - case SYSTEM_EVENT_ETH_CONNECTED: - Serial.println("ETH Connected"); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - Serial.print("ETH MAC: "); - Serial.print(ETH.macAddress()); - Serial.print(", IPv4: "); - Serial.print(ETH.localIP()); - if (ETH.fullDuplex()) { - Serial.print(", FULL_DUPLEX"); - } - Serial.print(", "); - Serial.print(ETH.linkSpeed()); - Serial.println("Mbps"); - eth_connected = true; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - Serial.println("ETH Disconnected"); - eth_connected = false; - break; - case SYSTEM_EVENT_ETH_STOP: - Serial.println("ETH Stopped"); - eth_connected = false; - break; - default: - break; - } -} - -bool ETH_startup() -{ - WiFi.onEvent(WiFiEvent); - ETH.begin(); - - Serial.println(F("Getting IP address...")); - - while (!eth_connected) - delay(100); - - return true; -} diff --git a/examples/wESP32_NoteOnOffEverySec_Adv/WebServerTask.h b/examples/wESP32_NoteOnOffEverySec_Adv/WebServerTask.h deleted file mode 100644 index 932206e..0000000 --- a/examples/wESP32_NoteOnOffEverySec_Adv/WebServerTask.h +++ /dev/null @@ -1,63 +0,0 @@ -void TaskWebServercode(void* pvParameters) -{ - Serial.print("WebServer running on core "); - Serial.println(xPortGetCoreID()); - - MDNS.addService("http", "tcp", 80); - - WiFiServer server(80); - server.begin(); - - while (true) - { - delay(100); - // Check if a client has connected - WiFiClient client = server.available(); - if (!client) - continue; - - Serial.println(""); - Serial.println("New client"); - - // Wait for data from client to become available - while (client.connected() && !client.available()) { - delay(1); - } - - // Read the first line of HTTP request - String req = client.readStringUntil('\r'); - - // First line of HTTP request looks like "GET /path HTTP/1.1" - // Retrieve the "/path" part by finding the spaces - int addr_start = req.indexOf(' '); - int addr_end = req.indexOf(' ', addr_start + 1); - if (addr_start == -1 || addr_end == -1) { - Serial.print("Invalid request: "); - Serial.println(req); - return; - } - req = req.substring(addr_start + 1, addr_end); - Serial.print("Request: "); - Serial.println(req); - - String s; - if (req == "/") - { - IPAddress ip = WiFi.localIP(); - String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); - s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n\r\nHello from ESP32 at "; - s += ipStr; - s += "\r\n\r\n"; - Serial.println("Sending 200"); - } - else - { - s = "HTTP/1.1 404 Not Found\r\n\r\n"; - Serial.println("Sending 404"); - } - client.print(s); - - client.stop(); - Serial.println("Done with client"); - } -} diff --git a/examples/wESP32_NoteOnOffEverySec_Adv/wESP32_NoteOnOffEverySec_Adv.ino b/examples/wESP32_NoteOnOffEverySec_Adv/wESP32_NoteOnOffEverySec_Adv.ino deleted file mode 100644 index 708e9b4..0000000 --- a/examples/wESP32_NoteOnOffEverySec_Adv/wESP32_NoteOnOffEverySec_Adv.ino +++ /dev/null @@ -1,42 +0,0 @@ -#include "ETH_Helper.h" - -#include "WebServerTask.h" -#include "AppleMIDITask.h" - -TaskHandle_t TaskWebServer; -TaskHandle_t TaskAppleMIDI; - -void setup() -{ - Serial.begin(115200); - while (!Serial); - Serial.println("Booting"); - - ETH_startup(); - - MDNS.begin("ESP32"); - - //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1 - xTaskCreatePinnedToCore( - TaskWebServercode, // Task function. - "WebServer", // name of task. - 10000, // Stack size of task - NULL, // parameter of the task - tskIDLE_PRIORITY, // priority of the task - &TaskWebServer, // Task handle to keep track of created task - 0); // pin task to core 0 - - //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0 - xTaskCreatePinnedToCore( - TaskAppleMIDIcode, // Task function. - "AppleMIDI", // name of task. - 10000, // Stack size of task - NULL, // parameter of the task - tskIDLE_PRIORITY + 1, // priority of the task - &TaskAppleMIDI, // Task handle to keep track of created task - 1); // pin task to core 1 -} - -void loop() -{ -} diff --git a/keywords.txt b/keywords.txt index 0a0a68d..e1ff976 100644 --- a/keywords.txt +++ b/keywords.txt @@ -10,68 +10,25 @@ AppleMidi KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### - +setName KEYWORD2 +setPort KEYWORD2 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) ####################################### APPLEMIDI_CREATE_INSTANCE LITERAL1 -APPLEMIDI_CREATE_DEFAULTSESSION_ESP32_INSTANCE LITERAL1 APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE LITERAL1 diff --git a/library.properties b/library.properties index 2c9865d..8673196 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=AppleMIDI -version=2.0.4 +version=3.3.0 author=lathoub maintainer=lathoub -sentence=AppleMIDI (rtpMIDI) protocol for Arduino -paragraph=AppleMIDI (also known as rtpMIDI) is a protocol to transport MIDI messages within RTP (Real-time Protocol) packets over Ethernet and WiFi networks. Major rewrite of the library to be faster and use less memory. Read the Wiki page when migrating from v1.* to v2.* (API changes) +sentence=AppleMIDI (aka rtpMIDI) MIDI I/Os for Arduino +paragraph=AppleMIDI (aka rtpMIDI) is a protocol to transport MIDI messages within RTP (Real-time Protocol) packets over Ethernet and WiFi networks. This major rewrite is faster, more stable and uses less memory. Read the Wiki page when migrating category=Communication url=https://github.com/lathoub/Arduino-AppleMidi-Library architectures=* diff --git a/res/Install3-1.PNG b/res/Install3-1.PNG new file mode 100644 index 0000000..fc52487 Binary files /dev/null and b/res/Install3-1.PNG differ diff --git a/res/LogErrors.PNG b/res/LogErrors.PNG new file mode 100644 index 0000000..7d00163 Binary files /dev/null and b/res/LogErrors.PNG differ diff --git a/res/latency/ESP32W5500.png b/res/latency/ESP32W5500.png new file mode 100644 index 0000000..7fbb2d0 Binary files /dev/null and b/res/latency/ESP32W5500.png differ diff --git a/res/latency/ESP32Wi-Fi.png b/res/latency/ESP32Wi-Fi.png new file mode 100644 index 0000000..6f39b9e Binary files /dev/null and b/res/latency/ESP32Wi-Fi.png differ diff --git a/res/latency/atmega2560.png b/res/latency/atmega2560.png new file mode 100644 index 0000000..343043b Binary files /dev/null and b/res/latency/atmega2560.png differ diff --git a/res/latency/samd21.png b/res/latency/samd21.png new file mode 100644 index 0000000..5f9c42a Binary files /dev/null and b/res/latency/samd21.png differ diff --git a/src/AppleMIDI.h b/src/AppleMIDI.h index d638273..acd1d9a 100644 --- a/src/AppleMIDI.h +++ b/src/AppleMIDI.h @@ -1,15 +1,17 @@ #pragma once +#include "AppleMIDI_Debug.h" + // https://developer.apple.com/library/archive/documentation/Audio/Conceptual/MIDINetworkDriverProtocol/MIDI/MIDI.html #include using namespace MIDI_NAMESPACE; -#include "IPAddress.h" +#include +#include "AppleMIDI_PlatformBegin.h" #include "AppleMIDI_Defs.h" #include "AppleMIDI_Settings.h" -#include "AppleMIDI_Platform.h" #include "rtp_Defs.h" #include "rtpMIDI_Defs.h" @@ -22,78 +24,171 @@ using namespace MIDI_NAMESPACE; #include "AppleMIDI_Namespace.h" -#ifndef UDP_TX_PACKET_MAX_SIZE -#define UDP_TX_PACKET_MAX_SIZE 24 -#endif - BEGIN_APPLEMIDI_NAMESPACE static unsigned long now; -template +struct AppleMIDISettings : public MIDI_NAMESPACE::DefaultSettings +{ + // Packet based protocols prefer the entire message to be parsed + // as a whole. + static const bool Use1ByteParsing = false; +}; + +template class AppleMIDISession { typedef _Settings Settings; typedef _Platform Platform; - // Allow these internal classes access to our private members - // to avoid access by the .ino to internal messages - friend class AppleMIDIParser; - friend class rtpMIDIParser; - friend class MIDI_NAMESPACE::MidiInterface>; + // Allow these internal classes access to our private members + // to avoid access by the .ino to internal messages + friend class AppleMIDIParser; + friend class rtpMIDIParser; public: - AppleMIDISession(const char *name, const uint16_t port = DEFAULT_CONTROL_PORT) - { - this->port = port; - strncpy(this->localName, name, DefaultSettings::MaxSessionNameLen); - }; - - 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)) { _errorCallback = 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; }; - + AppleMIDISession(const char *sessionName, const uint16_t port = DEFAULT_CONTROL_PORT) + { + this->port = port; +#ifdef KEEP_SESSION_NAME + strncpy(this->localName, sessionName, Settings::MaxSessionNameLen); +#endif + +#ifdef ONE_PARTICIPANT + participant.ssrc = 0; +#endif + }; + + virtual ~AppleMIDISession(){}; + + AppleMIDISession &setHandleConnected(void (*fptr)(const ssrc_t &, const char *)) + { + _connectedCallback = fptr; + return *this; + } + AppleMIDISession &setHandleDisconnected(void (*fptr)(const ssrc_t &)) + { + _disconnectedCallback = fptr; + return *this; + } +#ifdef USE_EXT_CALLBACKS + AppleMIDISession &setHandleException(void (*fptr)(const ssrc_t &, const Exception &, const int32_t value)) + { + _exceptionCallback = fptr; + return *this; + } + AppleMIDISession &setHandleReceivedRtp(void (*fptr)(const ssrc_t &, const Rtp_t &, const int32_t &)) + { + _receivedRtpCallback = fptr; + return *this; + } + AppleMIDISession &setHandleStartReceivedMidi(void (*fptr)(const ssrc_t &)) + { + _startReceivedMidiByteCallback = fptr; + return *this; + } + AppleMIDISession &setHandleReceivedMidi(void (*fptr)(const ssrc_t &, byte)) + { + _receivedMidiByteCallback = fptr; + return *this; + } + AppleMIDISession &setHandleEndReceivedMidi(void (*fptr)(const ssrc_t &)) + { + _endReceivedMidiByteCallback = fptr; + return *this; + } + AppleMIDISession &setHandleSentRtp(void (*fptr)(const Rtp_t &)) + { + _sentRtpCallback = fptr; + return *this; + } + AppleMIDISession &setHandleSentRtpMidi(void (*fptr)(const RtpMIDI_t &)) + { + _sentRtpMidiCallback = fptr; + return *this; + } +#endif + +#ifdef KEEP_SESSION_NAME + const char *getName() const + { + return this->localName; + }; + AppleMIDISession &setName(const char *sessionName) + { + strncpy(this->localName, sessionName, Settings::MaxSessionNameLen); + return *this; + }; +#else + const char *getName() const + { + return nullptr; + }; + AppleMIDISession &setName(const char *sessionName) { return *this; }; +#endif + + const uint16_t getPort() const + { + return this->port; + }; + + // call this method *before* calling begin() + AppleMIDISession & setPort(const uint16_t port) + { + this->port = port; + return *this; + } + + const ssrc_t getSynchronizationSource() const { return this->ssrc; }; + #ifdef APPLEMIDI_INITIATOR bool sendInvite(IPAddress ip, uint16_t port = DEFAULT_CONTROL_PORT); #endif void sendEndSession(); - -protected: - static const bool thruActivated = false; - void begin() - { +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; - _rtpMIDIParser.session = this; + _rtpMIDIParser.session = this; // analogRead(0) is not available on all platforms. The use of millis() // as it preceded by network calls, so timing is variable and usable // for the random generator. randomSeed(millis()); - - // Each stream is distinguished by a unique SSRC value and has a unique sequence - // number and RTP timestamp space. - // this is our SSRC + + // Each stream is distinguished by a unique SSRC value and has a unique sequence + // number and RTP timestamp space. + // this is our SSRC // // NOTE: Arduino random only goes to INT32_MAX (not UINT32_MAX) - - this->ssrc = random(1, INT32_MAX) * 2; + this->ssrc = random(1, INT32_MAX) * 2; - controlPort.begin(port); - dataPort.begin(port + 1); + controlPort.begin(port); + dataPort.begin(port + 1); - rtpMidiClock.Init(rtpMidiClock.Now(), MIDI_SAMPLING_RATE_DEFAULT); + rtpMidiClock.Init(rtpMidiClock.Now(), MIDI_SAMPLING_RATE_DEFAULT); } - bool beginTransmission(MIDI_NAMESPACE::MidiType) - { + void end() + { +#ifdef ONE_PARTICIPANT + participant.ssrc = 0; +#endif + controlPort.stop(); + dataPort.stop(); + } + + bool beginTransmission(MIDI_NAMESPACE::MidiType) + { // All MIDI commands queued up in the same cycle (during 1 loop execution) // are send in a single MIDI packet // (The actual sending happen in the available() method, called at the start of the @@ -116,52 +211,56 @@ class AppleMIDISession else outMidiBuffer.push_back(0x00); // zero timestamp } - - // We can't start the writing process here, as we do not know the length - // 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() != 0 && participants.size() > 0); - }; - - void write(byte byte) - { + + // We can't start the writing process here, as we do not know the length + // 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 +#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) + { // do we still have place in the buffer for 1 more character? - if ((outMidiBuffer.size()) + 2 > outMidiBuffer.max_size()) - { - // buffer is almost full, only 1 more character - if (MIDI_NAMESPACE::MidiType::SystemExclusive == outMidiBuffer.front()) - { - // Add Sysex at the end of this partial SysEx (in the last availble slot) ... - outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveStart); - + if ((outMidiBuffer.size()) + 2 > outMidiBuffer.max_size()) + { + // buffer is almost full, only 1 more character + if (MIDI_NAMESPACE::MidiType::SystemExclusive == outMidiBuffer.front()) + { + // Add Sysex at the end of this partial SysEx (in the last availble slot) ... + outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveStart); + writeRtpMidiToAllParticipants(); - // and start again with a fresh continuation of - // a next SysEx block. + // and start again with a fresh continuation of + // a next SysEx block. outMidiBuffer.clear(); - outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveEnd); + outMidiBuffer.push_back(MIDI_NAMESPACE::MidiType::SystemExclusiveEnd); + } + else + { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, BufferFullException, 0); +#endif } - else - { - if (NULL != _errorCallback) - _errorCallback(ssrc, -1); - } - } + } - // store in local buffer, as we do *not* know the length of the message prior to sending - outMidiBuffer.push_back(byte); - }; + // store in local buffer, as we do *not* know the length of the message prior to sending + outMidiBuffer.push_back(byte); + }; - void endTransmission() - { - }; + void endTransmission(){}; // first things MIDI.read() calls in this method // MIDI-read() must be called at the start of loop() - unsigned available() - { + unsigned available() + { now = millis(); - + #ifdef APPLEMIDI_INITIATOR manageSessionInvites(); #endif @@ -171,126 +270,145 @@ class AppleMIDISession if (outMidiBuffer.size() > 0) writeRtpMidiToAllParticipants(); // assert(outMidiBuffer.size() == 0); // must be empty - + if (inMidiBuffer.size() > 0) - return true; - - // read packets from both UDP sockets - readDataPackets(); // from socket into dataBuffer - readControlPackets(); // from socket into controlBuffer + return inMidiBuffer.size(); + + if (readDataPackets()) // from socket into dataBuffer + parseDataPackets(); // from dataBuffer into inMidiBuffer - // parses buffer and places MIDI into inMidiBuffer - parseDataPackets(); // from dataBuffer into inMidiBuffer - parseControlPackets(); // from controlBuffer + if (readControlPackets()) // from socket into controlBuffer + parseControlPackets(); // from controlBuffer to AppleMIDI - manageReceiverFeedback(); + manageReceiverFeedback(); manageSynchronization(); - return false; - }; + return inMidiBuffer.size(); + }; byte read() { auto byte = inMidiBuffer.front(); inMidiBuffer.pop_front(); - + return byte; }; +protected: + UdpClass controlPort; + UdpClass dataPort; + private: - UdpClass controlPort; - UdpClass dataPort; + RtpBuffer_t controlBuffer; + RtpBuffer_t dataBuffer; - // reading from the network - RtpBuffer_t controlBuffer; - RtpBuffer_t dataBuffer; + byte packetBuffer[Settings::UdpTxPacketMaxSize]; - AppleMIDIParser _appleMIDIParser; - rtpMIDIParser _rtpMIDIParser; + AppleMIDIParser _appleMIDIParser; + 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; - errorCallback _errorCallback = nullptr; - - // buffer for incoming and outgoing MIDI messages - MidiBuffer_t inMidiBuffer; - MidiBuffer_t outMidiBuffer; - - rtpMidi_Clock rtpMidiClock; - - ssrc_t ssrc = 0; - char localName[DefaultSettings::MaxSessionNameLen + 1]; - uint16_t port = DEFAULT_CONTROL_PORT; + sentRtpCallback _sentRtpCallback = nullptr; + sentRtpMidiCallback _sentRtpMidiCallback = nullptr; + exceptionCallback _exceptionCallback = nullptr; +#endif + // buffer for incoming and outgoing MIDI messages + MidiBuffer_t inMidiBuffer; + MidiBuffer_t outMidiBuffer; + + rtpMidi_Clock rtpMidiClock; + + ssrc_t ssrc = 0; + uint16_t port = DEFAULT_CONTROL_PORT; +#ifdef ONE_PARTICIPANT + Participant participant; +#else Deque, Settings::MaxNumberOfParticipants> participants; - int32_t latencyAdjustment = 0; - +#endif + +#ifdef KEEP_SESSION_NAME + char localName[Settings::MaxSessionNameLen + 1]; +#endif + private: - void readControlPackets(); - void readDataPackets(); - + size_t readControlPackets(); + size_t readDataPackets(); + void parseControlPackets(); void parseDataPackets(); - - void ReceivedInvitation (AppleMIDI_Invitation_t &, const amPortType &); - void ReceivedControlInvitation (AppleMIDI_Invitation_t &); - void ReceivedDataInvitation (AppleMIDI_Invitation_t &); - void ReceivedSynchronization (AppleMIDI_Synchronization_t &); - void ReceivedReceiverFeedback (AppleMIDI_ReceiverFeedback_t &); - void ReceivedEndSession (AppleMIDI_EndSession_t &); - void ReceivedBitrateReceiveLimit (AppleMIDI_BitrateReceiveLimit &); - - void ReceivedInvitationAccepted (AppleMIDI_InvitationAccepted_t &, const amPortType &); + + void ReceivedInvitation(AppleMIDI_Invitation_t &, const amPortType &); + void ReceivedControlInvitation(AppleMIDI_Invitation_t &); + void ReceivedDataInvitation(AppleMIDI_Invitation_t &); + void ReceivedSynchronization(AppleMIDI_Synchronization_t &); + void ReceivedReceiverFeedback(AppleMIDI_ReceiverFeedback_t &); + void ReceivedEndSession(AppleMIDI_EndSession_t &); + void ReceivedBitrateReceiveLimit(AppleMIDI_BitrateReceiveLimit &); + + void ReceivedInvitationAccepted(AppleMIDI_InvitationAccepted_t &, const amPortType &); void ReceivedControlInvitationAccepted(AppleMIDI_InvitationAccepted_t &); - void ReceivedDataInvitationAccepted (AppleMIDI_InvitationAccepted_t &); - void ReceivedInvitationRejected (AppleMIDI_InvitationRejected_t &); - - // rtpMIDI callback from parser + void ReceivedDataInvitationAccepted(AppleMIDI_InvitationAccepted_t &); + void ReceivedInvitationRejected(AppleMIDI_InvitationRejected_t &); + + // rtpMIDI callback from parser void ReceivedRtp(const Rtp_t &); void StartReceivedMidi(); void ReceivedMidi(byte data); void EndReceivedMidi(); - // Helpers - void writeInvitation (UdpClass &, IPAddress, uint16_t, AppleMIDI_Invitation_t &, const byte *command); + // Helpers + 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 &); + void writeSynchronization(const IPAddress &, const uint16_t &, AppleMIDI_Synchronization_t &); + void writeEndSession(const IPAddress &, const uint16_t &, AppleMIDI_EndSession_t &); + + void sendEndSession(Participant *); - void sendEndSession(Participant*); - void writeRtpMidiToAllParticipants(); - void writeRtpMidiBuffer(Participant*); + void writeRtpMidiBuffer(Participant *); void manageReceiverFeedback(); - + 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); -}; + void sendSynchronization(Participant *); -#define APPLEMIDI_CREATE_INSTANCE(Type, Name, SessionName, Port) \ - APPLEMIDI_NAMESPACE::AppleMIDISession Apple##Name(SessionName, Port); \ - MIDI_NAMESPACE::MidiInterface> Name((APPLEMIDI_NAMESPACE::AppleMIDISession&)Apple##Name); - -#define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ -APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "Arduino", DEFAULT_CONTROL_PORT); - -#define APPLEMIDI_CREATE_DEFAULTSESSION_ESP32_INSTANCE() \ -APPLEMIDI_CREATE_INSTANCE(WiFiUDP, MIDI, "ESP32", DEFAULT_CONTROL_PORT); +#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 #include "AppleMIDI.hpp" +#define APPLEMIDI_CREATE_INSTANCE(Type, Name, SessionName, Port) \ + APPLEMIDI_NAMESPACE::AppleMIDISession Apple##Name(SessionName, Port); \ + MIDI_NAMESPACE::MidiInterface, APPLEMIDI_NAMESPACE::AppleMIDISettings> Name((APPLEMIDI_NAMESPACE::AppleMIDISession &)Apple##Name); + +#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) +#define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ + APPLEMIDI_CREATE_INSTANCE(WiFiUDP, MIDI, "AppleMIDI-ESP32", DEFAULT_CONTROL_PORT); +#elif defined(ESP8266) +#define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ + APPLEMIDI_CREATE_INSTANCE(WiFiUDP, MIDI, "AppleMIDI-ESP8266", DEFAULT_CONTROL_PORT); +#else +#define APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE() \ + APPLEMIDI_CREATE_INSTANCE(EthernetUDP, MIDI, "AppleMIDI-Arduino", DEFAULT_CONTROL_PORT); +#endif + +#include "AppleMIDI_PlatformEnd.h" diff --git a/src/AppleMIDI.hpp b/src/AppleMIDI.hpp index b90f8ef..54e860d 100644 --- a/src/AppleMIDI.hpp +++ b/src/AppleMIDI.hpp @@ -4,10 +4,8 @@ BEGIN_APPLEMIDI_NAMESPACE -static byte packetBuffer[UDP_TX_PACKET_MAX_SIZE]; - template -void AppleMIDISession::readControlPackets() +size_t AppleMIDISession::readControlPackets() { size_t packetSize = controlPort.available(); if (packetSize == 0) @@ -22,6 +20,8 @@ void AppleMIDISession::readControlPackets() for (auto i = 0; i < bytesRead; i++) controlBuffer.push_back(packetBuffer[i]); } + + return controlBuffer.size(); } template @@ -30,18 +30,30 @@ void AppleMIDISession::parseControlPackets() while (controlBuffer.size() > 0) { auto retVal = _appleMIDIParser.parse(controlBuffer, amPortType::Control); - if (retVal == parserReturn::UnexpectedData) + if (retVal == parserReturn::Processed + || retVal == parserReturn::NotEnoughData + || retVal == parserReturn::NotSureGiveMeMoreData) { - if (NULL != _errorCallback) - _errorCallback(ssrc, -2); - + break; + } + else if (retVal == parserReturn::UnexpectedData) + { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParseException, 0); +#endif controlBuffer.pop_front(); } + else if (retVal == parserReturn::SessionNameVeryLong) + { + // purge the rest of the data in controlPort + while (controlPort.read() >= 0) {} + } } } template -void AppleMIDISession::readDataPackets() +size_t AppleMIDISession::readDataPackets() { size_t packetSize = dataPort.available(); if (packetSize == 0) @@ -56,6 +68,8 @@ void AppleMIDISession::readDataPackets() for (auto i = 0; i < bytesRead; i++) dataBuffer.push_back(packetBuffer[i]); } + + return dataBuffer.size(); } template @@ -82,12 +96,11 @@ void AppleMIDISession::parseDataPackets() if (retVal1 == parserReturn::NotSureGiveMeMoreData || retVal2 == parserReturn::NotSureGiveMeMoreData) break; // one or the other buffer does not have enough data - - // TODO can we ever get here??? - if (NULL != _errorCallback) - _errorCallback(ssrc, -3); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UnexpectedParseException, 0); +#endif dataBuffer.pop_front(); } } @@ -95,7 +108,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); @@ -104,34 +117,72 @@ void AppleMIDISession::ReceivedInvitation(AppleMID template void AppleMIDISession::ReceivedControlInvitation(AppleMIDI_Invitation_t &invitation) { - // advertise our own session name -#ifdef KEEP_SESSION_NAME - strncpy(invitation.sessionName, localName, DefaultSettings::MaxSessionNameLen); - invitation.sessionName[DefaultSettings::MaxSessionNameLen] = '\0'; + // 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); - - if (NULL != _errorCallback) - _errorCallback(ssrc, -33); - + 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(); participant.remotePort = controlPort.remotePort(); participant.lastSyncExchangeTime = now; - participant.sequenceNr = random(1, UINT16_MAX); // // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header #ifdef KEEP_SESSION_NAME - strncpy(participant.sessionName, invitation.sessionName, DefaultSettings::MaxSessionNameLen); + strncpy(participant.sessionName, invitation.sessionName, Settings::MaxSessionNameLen); #endif - + +#ifdef KEEP_SESSION_NAME + // Re-use the invitation for acceptance. Overwrite sessionName with ours + strncpy(invitation.sessionName, localName, Settings::MaxSessionNameLen); + invitation.sessionName[Settings::MaxSessionNameLen] = '\0'; +#endif + +#ifdef USE_DIRECTORY + switch (whoCanConnectToMe) { + case None: + writeInvitation(controlPort, controlPort.remoteIP(), controlPort.remotePort(), invitation, amInvitationRejected); +#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 + +#ifndef ONE_PARTICIPANT participants.push_back(participant); +#endif writeInvitation(controlPort, participant.remoteIP, participant.remotePort, invitation, amInvitationAccepted); } @@ -139,32 +190,43 @@ 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 != _errorCallback) - _errorCallback(ssrc, -4); - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ParticipantNotFoundException, invitation.ssrc); +#endif return; } +#ifdef KEEP_SESSION_NAME + // Re-use the invitation for acceptance. Overwrite sessionName with ours + strncpy(invitation.sessionName, localName, Settings::MaxSessionNameLen); + invitation.sessionName[Settings::MaxSessionNameLen] = '\0'; +#endif + // writeInvitation will alter the values of the invitation, // in order to safe memory and computing cycles its easier to make a copy // 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); + _connectedCallback(ssrc_, pParticipant->sessionName); #else - _connectedCallback(ssrc_, NULL); + _connectedCallback(ssrc_, nullptr); #endif } @@ -173,6 +235,7 @@ void AppleMIDISession::ReceivedBitrateReceiveLimit { } +#ifdef APPLEMIDI_INITIATOR template void AppleMIDISession::ReceivedInvitationAccepted(AppleMIDI_InvitationAccepted_t &invitationAccepted, const amPortType &portType) { @@ -185,32 +248,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, Settings::MaxSessionNameLen); + pParticipant->sessionName[Settings::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 @@ -220,11 +291,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 +332,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 +383,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,146 +416,240 @@ void AppleMIDISession::ReceivedSynchronization(App template void AppleMIDISession::ReceivedReceiverFeedback(AppleMIDI_ReceiverFeedback_t &receiverFeedback) { - // As we do not keep any recovery journals, no command history, nothing! + // 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 = participant.ssrc; + +#ifndef ONE_PARTICIPANT participants.erase(i); - - if (NULL != _disconnectedCallback) - _disconnectedCallback(endSession.ssrc); +#else + participant.ssrc = 0; +#endif + if (nullptr != _disconnectedCallback) + _disconnectedCallback(ssrc); return; } } } +#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)) + if (!port.beginPacket(remoteIP, remotePort)) { - port.write((uint8_t *)amSignature, sizeof(amSignature)); - - port.write((uint8_t *)command, sizeof(amInvitation)); - port.write((uint8_t *)amProtocolVersion, sizeof(amProtocolVersion)); - invitation.initiatorToken = htonl(invitation.initiatorToken); - invitation.ssrc = htonl(ssrc); - port.write(reinterpret_cast(&invitation), invitation.getLength()); - - port.endPacket(); - port.flush(); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UdpBeginPacketFailed, 1); +#endif + return; } + + port.write((uint8_t *)amSignature, sizeof(amSignature)); + + port.write((uint8_t *)command, sizeof(amInvitation)); + port.write((uint8_t *)amProtocolVersion, sizeof(amProtocolVersion)); + invitation.initiatorToken = __htonl(invitation.initiatorToken); + invitation.ssrc = ssrc; + invitation.ssrc = __htonl(invitation.ssrc); + port.write(reinterpret_cast(&invitation), invitation.getLength()); + + port.endPacket(); + port.flush(); } template void AppleMIDISession::writeReceiverFeedback(const IPAddress& remoteIP, const uint16_t & remotePort, AppleMIDI_ReceiverFeedback_t & receiverFeedback) { - if (controlPort.beginPacket(remoteIP, remotePort)) + if (!controlPort.beginPacket(remoteIP, remotePort)) { - controlPort.write((uint8_t *)amSignature, sizeof(amSignature)); - - controlPort.write((uint8_t *)amReceiverFeedback, sizeof(amReceiverFeedback)); - - receiverFeedback.ssrc = htonl(receiverFeedback.ssrc); - receiverFeedback.sequenceNr = htons(receiverFeedback.sequenceNr); - - controlPort.write(reinterpret_cast(&receiverFeedback), sizeof(AppleMIDI_ReceiverFeedback)); - - controlPort.endPacket(); - controlPort.flush(); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UdpBeginPacketFailed, 2); +#endif + return; } + + controlPort.write((uint8_t *)amSignature, sizeof(amSignature)); + + controlPort.write((uint8_t *)amReceiverFeedback, sizeof(amReceiverFeedback)); + + receiverFeedback.ssrc = __htonl(receiverFeedback.ssrc); + receiverFeedback.sequenceNr = __htons(receiverFeedback.sequenceNr); + + controlPort.write(reinterpret_cast(&receiverFeedback), sizeof(AppleMIDI_ReceiverFeedback)); + + controlPort.endPacket(); + controlPort.flush(); } template void AppleMIDISession::writeSynchronization(const IPAddress& remoteIP, const uint16_t & remotePort, AppleMIDI_Synchronization_t &synchronization) { - if (dataPort.beginPacket(remoteIP, remotePort)) + if (!dataPort.beginPacket(remoteIP, remotePort)) { - dataPort.write((uint8_t *)amSignature, sizeof(amSignature)); - dataPort.write((uint8_t *)amSynchronization, sizeof(amSynchronization)); - synchronization.ssrc = htonl(this->ssrc); - - synchronization.timestamps[0] = htonll(synchronization.timestamps[0]); - synchronization.timestamps[1] = htonll(synchronization.timestamps[1]); - synchronization.timestamps[2] = htonll(synchronization.timestamps[2]); - dataPort.write(reinterpret_cast(&synchronization), sizeof(synchronization)); - - dataPort.endPacket(); - dataPort.flush(); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UdpBeginPacketFailed, 3); +#endif + return; } + + dataPort.write((uint8_t *)amSignature, sizeof(amSignature)); + dataPort.write((uint8_t *)amSynchronization, sizeof(amSynchronization)); + synchronization.ssrc = ssrc; + synchronization.ssrc = __htonl(synchronization.ssrc); + + synchronization.timestamps[0] = __htonll(synchronization.timestamps[0]); + synchronization.timestamps[1] = __htonll(synchronization.timestamps[1]); + synchronization.timestamps[2] = __htonll(synchronization.timestamps[2]); + dataPort.write(reinterpret_cast(&synchronization), sizeof(synchronization)); + + dataPort.endPacket(); + dataPort.flush(); } template void AppleMIDISession::writeEndSession(const IPAddress& remoteIP, const uint16_t & remotePort, AppleMIDI_EndSession_t &endSession) { - if (controlPort.beginPacket(remoteIP, remotePort)) + if (!controlPort.beginPacket(remoteIP, remotePort)) { - controlPort.write((uint8_t *)amSignature, sizeof(amSignature)); - controlPort.write((uint8_t *)amEndSession, sizeof(amEndSession)); - controlPort.write((uint8_t *)amProtocolVersion, sizeof(amProtocolVersion)); +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UdpBeginPacketFailed, 4); +#endif + return; + } - endSession.initiatorToken = htonl(endSession.initiatorToken); - endSession.ssrc = htonl(endSession.ssrc); + controlPort.write((uint8_t *)amSignature, sizeof(amSignature)); + controlPort.write((uint8_t *)amEndSession, sizeof(amEndSession)); + controlPort.write((uint8_t *)amProtocolVersion, sizeof(amProtocolVersion)); - controlPort.write(reinterpret_cast(&endSession), sizeof(endSession)); - - controlPort.endPacket(); - controlPort.flush(); - } + endSession.initiatorToken = __htonl(endSession.initiatorToken); + endSession.ssrc = __htonl(endSession.ssrc); + + controlPort.write(reinterpret_cast(&endSession), sizeof(endSession)); + + controlPort.endPacket(); + controlPort.flush(); } 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) -{ - const IPAddress remoteIP = participant->remoteIP; - const uint16_t remotePort = participant->remotePort + 1; - - if (!dataPort.beginPacket(remoteIP, remotePort)) - { - return; - } +void AppleMIDISession::writeRtpMidiBuffer(Participant* participant) +{ + const auto bufferLen = outMidiBuffer.size(); - participant->sequenceNr++; // (modulo 2^16) modulo is automatically done for us () - Rtp rtp; - rtp.vpxcc = 0b10000000; // TODO: fun with flags - rtp.mpayload = PAYLOADTYPE_RTPMIDI; // TODO: set or unset marker - rtp.ssrc = htonl(ssrc); + + // First octet + rtp.vpxcc = ((RTP_VERSION_2) << 6); // RTP version 2 + rtp.vpxcc &= ~RTP_P_FIELD; // no padding + rtp.vpxcc &= ~RTP_X_FIELD; // no extension + // No CSRC + + // second octet + rtp.mpayload = PAYLOADTYPE_RTPMIDI; + +/* + // The behavior of the 1-bit M field depends on the media type of the + // stream. For native streams, the M bit MUST be set to 1 if the MIDI + // command section has a non-zero LEN field and MUST be set to 0 + // otherwise. For mpeg4-generic streams, the M bit MUST be set to 1 for + // all packets (to conform to [RFC3640]). + if (bufferLen != 0) + rtp.mpayload |= RTP_M_FIELD; + else + rtp.mpayload &= ~RTP_M_FIELD; +*/ + // Both https://developer.apple.com/library/archive/documentation/Audio/Conceptual/MIDINetworkDriverProtocol/MIDI/MIDI.html + // and https://tools.ietf.org/html/rfc6295#section-2.1 indicate that the M field needs to be set + // if the len in the MIDI section is NON-ZERO. + // However, doing so on, MacOS does not take the given MIDI commands + // Clear the M field + rtp.mpayload &= ~RTP_M_FIELD; + + rtp.ssrc = 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, @@ -499,49 +665,75 @@ 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.sequenceNr = htons(participant->sequenceNr); - dataPort.write((uint8_t *)&rtp, sizeof(rtp)); + rtp.timestamp = (Settings::TimestampRtpPackets) ? rtpMidiClock.Now() : 0; + + // increment the sequenceNr + participant->sendSequenceNr++; + + 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); + + if (!dataPort.beginPacket(participant->remoteIP, participant->remotePort + 1)) + { +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, UdpBeginPacketFailed, 5); +#endif + return; + } - // only now the length is known - auto bufferLen = outMidiBuffer.size(); + // write rtp header + dataPort.write((uint8_t *)&rtp, sizeof(rtp)); + // Write rtpMIDI section RtpMIDI_t rtpMidi; + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |B|J|Z|P|LEN... | MIDI list ... | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + rtpMidi.flags = 0; + rtpMidi.flags &= ~RTP_MIDI_CS_FLAG_J; // no journal, clear J-FLAG + rtpMidi.flags &= ~RTP_MIDI_CS_FLAG_Z; // no Delta Time 0 field, clear Z flag + rtpMidi.flags &= ~RTP_MIDI_CS_FLAG_P; // no phantom flag + if (bufferLen <= 0x0F) { // Short header - rtpMidi.flags = (uint8_t)bufferLen; - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_B; // TODO: set or clear these flags (no journal) - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_J; - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_Z; - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_P; + rtpMidi.flags |= (uint8_t)bufferLen; + rtpMidi.flags &= ~RTP_MIDI_CS_FLAG_B; // short header, clear B-FLAG dataPort.write(rtpMidi.flags); } else { // Long header - rtpMidi.flags = (uint8_t)(bufferLen >> 8); - rtpMidi.flags |= RTP_MIDI_CS_FLAG_B; // set B-FLAG for long header - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_J; - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_Z; - // rtpMidi.flags &= RTP_MIDI_CS_FLAG_P; + rtpMidi.flags |= (uint8_t)(bufferLen >> 8); + rtpMidi.flags |= RTP_MIDI_CS_FLAG_B; // set B-FLAG for long header dataPort.write(rtpMidi.flags); dataPort.write((uint8_t)(bufferLen)); } - // MIDI Section - while (!outMidiBuffer.empty()) - { - auto byte = outMidiBuffer.front(); - outMidiBuffer.pop_front(); - - dataPort.write(byte); - } - + // write out the MIDI Section + for (size_t i = 0; i < bufferLen; i++) + dataPort.write(outMidiBuffer[i]); + // *No* journal section (Not supported) - + dataPort.endPacket(); dataPort.flush(); + +#ifdef USE_EXT_CALLBACKS + if (_sentRtpMidiCallback) + _sentRtpMidiCallback(rtpMidi); +#endif } // @@ -550,46 +742,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 initiate a new sync exchange 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) - { - sendEndSession(participant); - - participants.erase(i); - - return; - } -} +#ifdef APPLEMIDI_INITIATOR // // The initiator of the session polls if remote station is still alive. @@ -599,32 +797,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 > Settings::SynchronizationHeartBeat) { doSyncronize = true; } @@ -632,30 +828,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 > Settings::MaxSynchronizationCK0Attempts) { +#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) { @@ -675,61 +881,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 >= Settings::MaxSessionInvitesAttempts) { - // too many attempts, give up - indicate this participant slot is free - +#ifdef USE_EXT_CALLBACKS + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, NoResponseFromConnectionRequestException, 0); +#endif + // After too many attempts, stop. + 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'; + strncpy(invitation.sessionName, this->localName, Settings::MaxSessionNameLen); + invitation.sessionName[Settings::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; } } } @@ -745,22 +977,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; } } } @@ -770,21 +1014,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; } @@ -794,14 +1045,21 @@ bool AppleMIDISession::sendInvite(IPAddress ip, ui template void AppleMIDISession::sendEndSession() { +#ifndef ONE_PARTICIPANT while (participants.size() > 0) { - auto participant = &participants[0]; - + auto participant = &participants.front(); sendEndSession(participant); - participants.erase(0); + participants.pop_front(); } +#else + if (participant.src != 0) + { + sendEndSession(&participant); + participant.ssrc = 0; + } +#endif } template @@ -812,31 +1070,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; + + if (pParticipant->firstMessageReceived == true) + // avoids first message to generate sequence exception + // as we do not know the last sequenceNr received. + pParticipant->firstMessageReceived = false; + else if (rtp.sequenceNr - pParticipant->receiveSequenceNr - 1 != 0) { + if (nullptr != _exceptionCallback) + _exceptionCallback(ssrc, ReceivedPacketsDropped, rtp.sequenceNr - pParticipant->receiveSequenceNr - 1); + } + + if (nullptr != _receivedRtpCallback) + _receivedRtpCallback(pParticipant->ssrc, rtp, latency); #endif - participant->sequenceNr = rtp.sequenceNr; - - if (NULL != _receivedRtpCallback) - _receivedRtpCallback(0, rtp, latency); + + pParticipant->receiveSequenceNr = rtp.sequenceNr; } else { @@ -847,15 +1117,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); } @@ -863,8 +1137,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 new file mode 100644 index 0000000..1b63ca1 --- /dev/null +++ b/src/AppleMIDI_Debug.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef SerialMon +namespace { +static void AM_DBG_SETUP(unsigned long baud) { + SerialMon.begin(baud); + while (!SerialMon); +} + +template +static void AM_DBG_PLAIN(T last) { + SerialMon.println(last); +} + +template +static void AM_DBG_PLAIN(T head, Args... tail) { + SerialMon.print(head); + SerialMon.print(' '); + AM_DBG_PLAIN(tail...); +} + +template +static void AM_DBG(Args... args) { + AM_DBG_PLAIN(args...); +} +} // namespace +#else +#define AM_DBG_SETUP(...) +#define AM_DBG_PLAIN(...) +#define AM_DBG(...) +#endif diff --git a/src/AppleMIDI_Defs.h b/src/AppleMIDI_Defs.h index eb8ee01..d87e7b8 100644 --- a/src/AppleMIDI_Defs.h +++ b/src/AppleMIDI_Defs.h @@ -1,13 +1,12 @@ #pragma once #include "AppleMIDI_Settings.h" - #include "AppleMIDI_Namespace.h" BEGIN_APPLEMIDI_NAMESPACE -#define APPLEMIDI_LIBRARY_VERSION 0x020000 -#define APPLEMIDI_LIBRARY_VERSION_MAJOR 2 +#define APPLEMIDI_LIBRARY_VERSION 0x030000 +#define APPLEMIDI_LIBRARY_VERSION_MAJOR 3 #define APPLEMIDI_LIBRARY_VERSION_MINOR 0 #define APPLEMIDI_LIBRARY_VERSION_PATCH 0 @@ -17,12 +16,50 @@ typedef uint32_t ssrc_t; typedef uint32_t initiatorToken_t; typedef uint64_t timestamp_t; +union conversionBuffer +{ + uint8_t value8; + uint16_t value16; + uint32_t value32; + uint64_t value64; + byte buffer[8]; +}; + + +enum parserReturn: uint8_t +{ + Processed, + NotSureGiveMeMoreData, + NotEnoughData, + UnexpectedData, + UnexpectedMidiData, + UnexpectedJournalData, + SessionNameVeryLong, +}; + +#if defined(__AVR__) +#define APPLEMIDI_PROGMEM PROGMEM +typedef const __FlashStringHelper* AppleMIDIConstStr; +#define GFP(x) (reinterpret_cast(x)) +#define GF(x) F(x) +#else +#define APPLEMIDI_PROGMEM +typedef const char* AppleMIDIConstStr; +#define GFP(x) x +#define GF(x) x +#endif + #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 @@ -31,14 +68,83 @@ typedef uint64_t timestamp_t; 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" +// in Wireshark software. Session management with AppleMIDI protocol requires two UDP ports, +// the first one is called "Control Port", the second one is called "Data Port". When used +// within a multithread implementation, only the Data port requires a "real-time" thread, +// the other port can be controlled by a normal priority thread. These two ports must be +// located at two consecutive locations (n / n+1); the first one can be any of the 65536 +// possible ports. +enum amPortType : uint8_t +{ + Control = 0, + Data = 1, +}; + +// from: https://en.wikipedia.org/wiki/RTP-MIDI +// AppleMIDI implementation defines two kind of session controllers: session initiators +// and session listeners. Session initiators are in charge of inviting the session listeners, +// and are responsible of the clock synchronization sequence. Session initiators can generally +// be session listeners, but some devices, such as iOS devices, can be session listeners only. +enum ParticipantKind : uint8_t +{ + Listener, + Initiator, +}; + +enum InviteStatus : uint8_t +{ + Initiating, + AwaitingControlInvitationAccepted, + ControlInvitationAccepted, + AwaitingDataInvitationAccepted, + DataInvitationAccepted, + Connected +}; + +enum Exception : uint8_t +{ + BufferFullException, + ParseException, + UnexpectedParseException, + TooManyParticipantsException, + ComputerNotInDirectory, + NotAcceptingAnyone, + UnexpectedInviteException, + ParticipantNotFoundException, + ListenerTimeOutException, + MaxAttemptsException, + NoResponseFromConnectionRequestException, + SendPacketsDropped, + ReceivedPacketsDropped, + UdpBeginPacketFailed, +}; + 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 errorCallback = 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}; @@ -47,91 +153,62 @@ const byte amSignature[] = {0xff, 0xff}; const byte amProtocolVersion[] = {0x00, 0x00, 0x00, 0x02}; /* Apple network MIDI valid commands */ -const byte amInvitation[] = {'I', 'N'}; -const byte amEndSession[] = {'B', 'Y'}; -const byte amSynchronization[] = {'C', 'K'}; -const byte amInvitationAccepted[] = {'O', 'K'}; -const byte amInvitationRejected[] = {'N', 'O'}; -const byte amReceiverFeedback[] = {'R', 'S'}; +const byte amInvitation[] = {'I', 'N'}; +const byte amEndSession[] = {'B', 'Y'}; +const byte amSynchronization[] = {'C', 'K'}; +const byte amInvitationAccepted[] = {'O', 'K'}; +const byte amInvitationRejected[] = {'N', 'O'}; +const byte amReceiverFeedback[] = {'R', 'S'}; const byte amBitrateReceiveLimit[] = {'R', 'L'}; const uint8_t SYNC_CK0 = 0; const uint8_t SYNC_CK1 = 1; const uint8_t SYNC_CK2 = 2; -typedef struct __attribute__((packed)) AppleMIDI_Invitation +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 __attribute__((packed)) AppleMIDI_BitrateReceiveLimit +typedef struct PACKED AppleMIDI_BitrateReceiveLimit { ssrc_t ssrc; uint32_t bitratelimit; } AppleMIDI_BitrateReceiveLimit_t; -typedef struct __attribute__((packed)) AppleMIDI_Synchronization +typedef struct PACKED AppleMIDI_Synchronization { ssrc_t ssrc; uint8_t count; - uint8_t padding[3]; + uint8_t padding[3] = {0,0,0}; timestamp_t timestamps[3]; } AppleMIDI_Synchronization_t; -typedef struct __attribute__((packed)) AppleMIDI_ReceiverFeedback +typedef struct PACKED AppleMIDI_ReceiverFeedback { ssrc_t ssrc; uint16_t sequenceNr; uint16_t dummy; } AppleMIDI_ReceiverFeedback_t; -typedef struct __attribute__((packed)) AppleMIDI_EndSession +typedef struct PACKED AppleMIDI_EndSession { initiatorToken_t initiatorToken; ssrc_t ssrc; } AppleMIDI_EndSession_t; -// 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" -// in Wireshark software. Session management with AppleMIDI protocol requires two UDP ports, -// the first one is called "Control Port", the second one is called "Data Port". When used -// within a multithread implementation, only the Data port requires a "real-time" thread, -// the other port can be controlled by a normal priority thread. These two ports must be -// located at two consecutive locations (n / n+1); the first one can be any of the 65536 -// possible ports. -enum amPortType : uint8_t -{ - Control = 0, - Data = 1, -}; - -// from: https://en.wikipedia.org/wiki/RTP-MIDI -// AppleMIDI implementation defines two kind of session controllers: session initiators -// and session listeners. Session initiators are in charge of inviting the session listeners, -// and are responsible of the clock synchronization sequence. Session initiators can generally -// be session listeners, but some devices, such as iOS devices, can be session listeners only. -enum ParticipantKind : uint8_t -{ - Listener, - Initiator, -}; - -enum InviteStatus : uint8_t -{ - Initiating, - AwaitingControlInvitationAccepted, - ControlInvitationAccepted, - AwaitingDataInvitationAccepted, - DataInvitationAccepted, - Connected -}; - END_APPLEMIDI_NAMESPACE diff --git a/src/AppleMIDI_Parser.h b/src/AppleMIDI_Parser.h index 9a6bd54..c956161 100644 --- a/src/AppleMIDI_Parser.h +++ b/src/AppleMIDI_Parser.h @@ -1,13 +1,9 @@ #pragma once #include "utility/Deque.h" -#include "utility/endian.h" #include "AppleMIDI_Defs.h" - #include "AppleMIDI_Settings.h" -#include "AppleMIDI_Platform.h" - #include "AppleMIDI_Namespace.h" BEGIN_APPLEMIDI_NAMESPACE @@ -44,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]; @@ -75,37 +67,46 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitation.initiatorToken = ntohl(cb.value32); + invitation.initiatorToken = __ntohl(cb.value32); // The sender's synchronization source identifier. cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitation.ssrc = ntohl(cb.value32); + invitation.ssrc = __ntohl(cb.value32); #ifdef KEEP_SESSION_NAME uint16_t bi = 0; - while ((i < buffer.size()) && (buffer[i] != 0x00)) + while (i < buffer.size()) { - if (bi <= DefaultSettings::MaxSessionNameLen) - invitation.sessionName[bi++] = buffer[i]; - i++; + if (bi < Settings::MaxSessionNameLen) + invitation.sessionName[bi++] = buffer[i++]; + else + i++; } invitation.sessionName[bi++] = '\0'; #else - while ((i < buffer.size()) && (buffer[i] != 0x00)) + while (i < buffer.size()) i++; #endif - if (i == buffer.size() || buffer[i++] != 0x00) - return parserReturn::NotEnoughData; + auto retVal = parserReturn::Processed; + + // when given a Session Name and the buffer has been fully processed and the + // last character is not 'endl', then we got a very long sessionName. It will + // continue in the next memory chunk of the packet. We don't care, so indicated + // flush the remainder of the packet. + // First part if the session name is kept, processing continues + if (i > minimumLen) + if (i == buffer.size() && buffer[i] != 0x00) + retVal = parserReturn::SessionNameVeryLong; while (i--) buffer.pop_front(); // consume all the bytes that made up this message session->ReceivedInvitation(invitation, portType); - return parserReturn::Processed; + return retVal; } else if (0 == memcmp(command, amEndSession, sizeof(amEndSession))) { @@ -132,14 +133,14 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - endSession.initiatorToken = ntohl(cb.value32); + endSession.initiatorToken = __ntohl(cb.value32); // The sender's synchronization source identifier. cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - endSession.ssrc = ntohl(cb.value32); + endSession.ssrc = __ntohl(cb.value32); while (i--) buffer.pop_front(); // consume all the bytes that made up this message @@ -162,7 +163,7 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - synchronization.ssrc = ntohl(cb.value32); + synchronization.ssrc = __ntohl(cb.value32); synchronization.count = buffer[i++]; buffer[i++]; @@ -176,7 +177,7 @@ class AppleMIDIParser cb.buffer[5] = buffer[i++]; cb.buffer[6] = buffer[i++]; cb.buffer[7] = buffer[i++]; - synchronization.timestamps[0] = ntohll(cb.value64); + synchronization.timestamps[0] = __ntohll(cb.value64); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; @@ -186,7 +187,7 @@ class AppleMIDIParser cb.buffer[5] = buffer[i++]; cb.buffer[6] = buffer[i++]; cb.buffer[7] = buffer[i++]; - synchronization.timestamps[1] = ntohll(cb.value64); + synchronization.timestamps[1] = __ntohll(cb.value64); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; @@ -196,7 +197,7 @@ class AppleMIDIParser cb.buffer[5] = buffer[i++]; cb.buffer[6] = buffer[i++]; cb.buffer[7] = buffer[i++]; - synchronization.timestamps[2] = ntohll(cb.value64); + synchronization.timestamps[2] = __ntohll(cb.value64); while (i--) buffer.pop_front(); // consume all the bytes that made up this message @@ -217,14 +218,14 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - receiverFeedback.ssrc = ntohl(cb.value32); + receiverFeedback.ssrc = __ntohl(cb.value32); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; - receiverFeedback.sequenceNr = ntohs(cb.value16); + receiverFeedback.sequenceNr = __ntohs(cb.value16); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; - receiverFeedback.dummy = ntohs(cb.value16); + receiverFeedback.dummy = __ntohs(cb.value16); while (i--) buffer.pop_front(); // consume all the bytes that made up this message @@ -233,7 +234,6 @@ class AppleMIDIParser return parserReturn::Processed; } -#endif #ifdef APPLEMIDI_INITIATOR else if (0 == memcmp(command, amInvitationAccepted, sizeof(amInvitationAccepted))) { @@ -262,37 +262,47 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitationAccepted.initiatorToken = ntohl(cb.value32); + invitationAccepted.initiatorToken = __ntohl(cb.value32); // The sender's synchronization source identifier. cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitationAccepted.ssrc = ntohl(cb.value32); + invitationAccepted.ssrc = __ntohl(cb.value32); - #ifdef KEEP_SESSION_NAME +#ifdef KEEP_SESSION_NAME uint16_t bi = 0; - while ((i < buffer.size()) && (buffer[i] != 0x00)) + while (i < buffer.size()) { - if (bi <= DefaultSettings::MaxSessionNameLen) - invitationAccepted.sessionName[bi++] = buffer[i]; - i++; + if (bi < Settings::MaxSessionNameLen) + invitationAccepted.sessionName[bi++] = buffer[i++]; + else + i++; } invitationAccepted.sessionName[bi++] = '\0'; #else - while ((i < buffer.size()) && (buffer[i] != 0x00)) + while (i < buffer.size()) i++; #endif - if (i == buffer.size() || buffer[i++] != 0x00) - return parserReturn::NotEnoughData; + + auto retVal = parserReturn::Processed; + + // when given a Session Name and the buffer has been fully processed and the + // last character is not 'endl', then we got a very long sessionName. It will + // continue in the next memory chunk of the packet. We don't care, so indicated + // flush the remainder of the packet. + // First part if the session name is kept, processing continues + if (i > minimumLen) + if (i == buffer.size() && buffer[i] != 0x00) + retVal = parserReturn::SessionNameVeryLong; while (i--) buffer.pop_front(); // consume all the bytes that made up this message session->ReceivedInvitationAccepted(invitationAccepted, portType); - return parserReturn::Processed; + return retVal; } else if (0 == memcmp(command, amInvitationRejected, sizeof(amInvitationRejected))) { @@ -321,20 +331,20 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitationRejected.initiatorToken = ntohl(cb.value32); + invitationRejected.initiatorToken = __ntohl(cb.value32); // The sender's synchronization source identifier. cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - invitationRejected.ssrc = ntohl(cb.value32); + invitationRejected.ssrc = __ntohl(cb.value32); #ifdef KEEP_SESSION_NAME uint16_t bi = 0; while ((i < buffer.size()) && (buffer[i] != 0x00)) { - if (bi <= DefaultSettings::MaxSessionNameLen) + if (bi < Settings::MaxSessionNameLen) invitationRejected.sessionName[bi++] = buffer[i]; i++; } @@ -343,8 +353,11 @@ class AppleMIDIParser while ((i < buffer.size()) && (buffer[i] != 0x00)) i++; #endif - if (i == buffer.size() || buffer[i++] != 0x00) - return parserReturn::NotEnoughData; + // session name is optional. + // If i > minimum size (16), then a sessionName was provided and must include 0x00 + if (i > minimumLen) + if (i == buffer.size() || buffer[i++] != 0x00) + return parserReturn::NotEnoughData; while (i--) buffer.pop_front(); // consume all the bytes that made up this message @@ -365,13 +378,13 @@ class AppleMIDIParser cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - bitrateReceiveLimit.ssrc = ntohl(cb.value32); + bitrateReceiveLimit.ssrc = __ntohl(cb.value32); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - bitrateReceiveLimit.bitratelimit = ntohl(cb.value32); + bitrateReceiveLimit.bitratelimit = __ntohl(cb.value32); while (i--) buffer.pop_front(); // consume all the bytes that made up this message diff --git a/src/AppleMIDI_Participant.h b/src/AppleMIDI_Participant.h index 859c807..2a01c40 100644 --- a/src/AppleMIDI_Participant.h +++ b/src/AppleMIDI_Participant.h @@ -10,13 +10,16 @@ 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 = random(1, UINT16_MAX); // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header + + uint16_t sendSequenceNr = random(1, UINT16_MAX); // http://www.rfc-editor.org/rfc/rfc6295.txt , 2.1. RTP Header + uint16_t receiveSequenceNr = 0; + unsigned long lastSyncExchangeTime; #ifdef APPLEMIDI_INITIATOR @@ -30,12 +33,13 @@ struct Participant bool synchronizing = false; #endif -#ifdef LATENCY_CALCULATION +#ifdef USE_EXT_CALLBACKS + bool firstMessageReceived = true; uint32_t offsetEstimate; #endif #ifdef KEEP_SESSION_NAME - char sessionName[DefaultSettings::MaxSessionNameLen + 1]; + char sessionName[Settings::MaxSessionNameLen + 1]; #endif } ; diff --git a/src/AppleMIDI_PlatformBegin.h b/src/AppleMIDI_PlatformBegin.h new file mode 100644 index 0000000..9cb35e2 --- /dev/null +++ b/src/AppleMIDI_PlatformBegin.h @@ -0,0 +1,20 @@ +#pragma once + +#include "AppleMIDI_Namespace.h" + +#include "utility/endian.h" + +BEGIN_APPLEMIDI_NAMESPACE + +#ifdef _MSC_VER +#pragma pack(push, 1) +#define PACKED +#else +#define PACKED __attribute__((__packed__)) +#endif + +struct DefaultPlatform +{ +}; + +END_APPLEMIDI_NAMESPACE diff --git a/src/AppleMIDI_Platform.h b/src/AppleMIDI_PlatformEnd.h similarity index 65% rename from src/AppleMIDI_Platform.h rename to src/AppleMIDI_PlatformEnd.h index bda143b..fec7b9f 100644 --- a/src/AppleMIDI_Platform.h +++ b/src/AppleMIDI_PlatformEnd.h @@ -4,8 +4,9 @@ BEGIN_APPLEMIDI_NAMESPACE -struct ArduinoPlatform -{ -}; +#ifdef WIN32 +#pragma pack(pop) +#endif +#undef PACKED END_APPLEMIDI_NAMESPACE diff --git a/src/AppleMIDI_Settings.h b/src/AppleMIDI_Settings.h index 85cd5b2..1d03693 100644 --- a/src/AppleMIDI_Settings.h +++ b/src/AppleMIDI_Settings.h @@ -6,11 +6,15 @@ BEGIN_APPLEMIDI_NAMESPACE struct DefaultSettings { + static const size_t UdpTxPacketMaxSize = 24; + static const size_t MaxBufferSize = 64; 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. @@ -22,10 +26,9 @@ struct DefaultSettings // two sequence numbers. The sender can then free the memory containing old journalling data if necessary. static const unsigned long ReceiversFeedbackThreshold = 1000; - // Should not be lower than 11000 (15s) - // MacOS sends CK messages every 10 seconds - // rtpMIDI on Windows sends CK messages every x seconds - static const unsigned long CK_MaxTimeOut = 45000; + // The initiator must initiate a new sync exchange at least once every 60 seconds + // as in https://developer.apple.com/library/archive/documentation/Audio/Conceptual/MIDINetworkDriverProtocol/MIDI/MIDI.html + static const unsigned long CK_MaxTimeOut = 61000; // when set to true, the lower 32-bits of the rtpClock ae send // when set to false, 0 will be set for immediate playout. @@ -38,12 +41,4 @@ struct DefaultSettings static const unsigned long SynchronizationHeartBeat = 10000; }; -enum parserReturn: uint8_t -{ - Processed, - NotSureGiveMeMoreData, - NotEnoughData, - UnexpectedData, -}; - END_APPLEMIDI_NAMESPACE diff --git a/src/rtpMIDI_Clock.h b/src/rtpMIDI_Clock.h index 68898d2..649d84a 100644 --- a/src/rtpMIDI_Clock.h +++ b/src/rtpMIDI_Clock.h @@ -7,8 +7,6 @@ BEGIN_APPLEMIDI_NAMESPACE #define MSEC_PER_SEC 1000 -#define USEC_PER_SEC 1000000 -#define NSEC_PER_SEC 1000000000 typedef struct rtpMidi_Clock { diff --git a/src/rtpMIDI_Defs.h b/src/rtpMIDI_Defs.h index d81326b..4d2aa58 100644 --- a/src/rtpMIDI_Defs.h +++ b/src/rtpMIDI_Defs.h @@ -133,7 +133,7 @@ BEGIN_APPLEMIDI_NAMESPACE #define RTP_MIDI_CJ_CHAPTER_E_MASK_LENGTH 0x7f #define RTP_MIDI_CJ_CHAPTER_A_MASK_LENGTH 0x7f -typedef struct __attribute__((packed)) RtpMIDI +typedef struct PACKED RtpMIDI { uint8_t flags; } RtpMIDI_t; diff --git a/src/rtpMIDI_Parser.h b/src/rtpMIDI_Parser.h index 9053bf4..914179a 100644 --- a/src/rtpMIDI_Parser.h +++ b/src/rtpMIDI_Parser.h @@ -1,7 +1,6 @@ #pragma once #include "utility/Deque.h" -#include "utility/endian.h" #include @@ -9,8 +8,6 @@ #include "rtp_Defs.h" #include "AppleMIDI_Settings.h" -#include "AppleMIDI_Platform.h" - #include "AppleMIDI_Namespace.h" BEGIN_APPLEMIDI_NAMESPACE @@ -24,9 +21,33 @@ class rtpMIDIParser private: bool _rtpHeadersComplete = false; bool _journalSectionComplete = false; + bool _channelJournalSectionComplete = false; uint16_t midiCommandLength; uint8_t _journalTotalChannels; uint8_t rtpMidi_Flags = 0; + int cmdCount = 0; + uint8_t runningstatus = 0; + size_t _bytesToFlush = 0; + +protected: + void debugPrintBuffer(RtpBuffer_t &buffer) + { +#ifdef DEBUG + for (int i = 0; i < buffer.size(); i++) + { + SerialMon.print(" "); + SerialMon.print(i); + SerialMon.print(i < 10 ? " " : " "); + } + for (int i = 0; i < buffer.size(); i++) + { + SerialMon.print("0x"); + SerialMon.print(buffer[i] < 16 ? "0" : ""); + SerialMon.print(buffer[i], HEX); + SerialMon.print(" "); + } +#endif + } public: AppleMIDISession * session; @@ -41,6 +62,8 @@ class rtpMIDIParser // parserReturn parse(RtpBuffer_t &buffer) { + debugPrintBuffer(buffer); + conversionBuffer cb; // [RFC3550] provides a complete description of the RTP header fields. @@ -78,17 +101,17 @@ class rtpMIDIParser cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; - rtp.sequenceNr = ntohs(cb.value16); + rtp.sequenceNr = __ntohs(cb.value16); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - rtp.timestamp = ntohl(cb.value32); + rtp.timestamp = __ntohl(cb.value32); cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; cb.buffer[2] = buffer[i++]; cb.buffer[3] = buffer[i++]; - rtp.ssrc = ntohl(cb.value32); + rtp.ssrc = __ntohl(cb.value32); uint8_t version = RTP_VERSION(rtp.vpxcc); #ifdef DEBUG @@ -97,7 +120,7 @@ class rtpMIDIParser uint8_t csrc_count = RTP_CSRC_COUNT(rtp.vpxcc); #endif - if (2 != version) + if (RTP_VERSION_2 != version) { return parserReturn::UnexpectedData; } @@ -119,6 +142,7 @@ class rtpMIDIParser if (buffer.size() < minimumLen) return parserReturn::NotSureGiveMeMoreData; + // 2.2. MIDI Payload (https://www.ietf.org/rfc/rfc4695.html#section-2.2) // The payload MUST begin with the MIDI command section. The // MIDI command section codes a (possibly empty) list of timestamped // MIDI commands and provides the essential service of the payload @@ -142,6 +166,9 @@ class rtpMIDIParser midiCommandLength = (midiCommandLength << 8) | octet; } + cmdCount = 0; + runningstatus = 0; + while (i--) buffer.pop_front(); @@ -149,15 +176,15 @@ class rtpMIDIParser // initialize the Journal Section _journalSectionComplete = false; + _channelJournalSectionComplete = false; _journalTotalChannels = 0; } // Always a MIDI section if (midiCommandLength > 0) { - auto retVal = decodeMidiSection(buffer); - if (retVal != parserReturn::Processed) - return retVal; + auto retVal = decodeMIDICommandSection(buffer); + if (retVal != parserReturn::Processed) return retVal; } // The payload MAY also contain a journal section. The journal section @@ -168,8 +195,16 @@ class rtpMIDIParser if (rtpMidi_Flags & RTP_MIDI_CS_FLAG_J) { auto retVal = decodeJournalSection(buffer); - if (retVal != parserReturn::Processed) + switch (retVal) { + case parserReturn::Processed: + break; + case parserReturn::NotEnoughData: + return parserReturn::NotEnoughData; + case parserReturn::UnexpectedJournalData: + _rtpHeadersComplete = false; + default: return retVal; + } } _rtpHeadersComplete = false; @@ -179,7 +214,7 @@ class rtpMIDIParser #include "rtpMIDI_Parser_JournalSection.hpp" - #include "rtpMIDI_Parser_MidiCommandSection.hpp" + #include "rtpMIDI_Parser_CommandSection.hpp" }; END_APPLEMIDI_NAMESPACE diff --git a/src/rtpMIDI_Parser_MidiCommandSection.hpp b/src/rtpMIDI_Parser_CommandSection.hpp similarity index 54% rename from src/rtpMIDI_Parser_MidiCommandSection.hpp rename to src/rtpMIDI_Parser_CommandSection.hpp index 3cbf44a..143cc63 100644 --- a/src/rtpMIDI_Parser_MidiCommandSection.hpp +++ b/src/rtpMIDI_Parser_CommandSection.hpp @@ -1,8 +1,44 @@ -parserReturn decodeMidiSection(RtpBuffer_t &buffer) +// https://www.ietf.org/rfc/rfc4695.html#section-3 + +parserReturn decodeMIDICommandSection(RtpBuffer_t &buffer) { - int cmdCount = 0; + debugPrintBuffer(buffer); + + // https://www.ietf.org/rfc/rfc4695.html#section-3.2 + // + // The first MIDI channel command in the MIDI list MUST include a status + // octet.Running status coding, as defined in[MIDI], MAY be used for + // all subsequent MIDI channel commands in the list.As in[MIDI], + // System Commonand System Exclusive messages(0xF0 ... 0xF7) cancel + // the running status state, but System Real - time messages(0xF8 ... + // 0xFF) do not affect the running status state. All System commands in + // the MIDI list MUST include a status octet. + + // As we note above, the first channel command in the MIDI list MUST + // include a status octet.However, the corresponding command in the + // original MIDI source data stream might not have a status octet(in + // this case, the source would be coding the command using running + // status). If the status octet of the first channel command in the + // MIDI list does not appear in the source data stream, the P(phantom) + // header bit MUST be set to 1. In all other cases, the P bit MUST be + // set to 0. + // + // Note that the P bit describes the MIDI source data stream, not the + // MIDI list encoding; regardless of the state of the P bit, the MIDI + // list MUST include the status octet. + // + // As receivers MUST be able to decode running status, sender + // implementors should feel free to use running status to improve + // bandwidth efficiency. However, senders SHOULD NOT introduce timing + // jitter into an existing MIDI command stream through an inappropriate + // use or removal of running status coding. This warning primarily + // applies to senders whose RTP MIDI streams may be transcoded onto a + // MIDI 1.0 DIN cable[MIDI] by the receiver : both the timestamps and + // the command coding (running status or not) must comply with the + // physical restrictions of implicit time coding over a slow serial + // line. - uint8_t runningstatus = 0; + // (lathoub: RTP_MIDI_CS_FLAG_P((phantom) not implemented /* Multiple MIDI-commands might follow - the exact number can only be discovered by really decoding the commands! */ while (midiCommandLength) @@ -10,53 +46,47 @@ parserReturn decodeMidiSection(RtpBuffer_t &buffer) /* for the first command we only have a delta-time if Z-Flag is set */ if ((cmdCount) || (rtpMidi_Flags & RTP_MIDI_CS_FLAG_Z)) { - auto consumed = decodeTime(buffer); + size_t consumed = 0; + auto retVal = decodeTime(buffer, consumed); + if (retVal != parserReturn::Processed) return retVal; midiCommandLength -= consumed; - while (consumed--) buffer.pop_front(); - - if (midiCommandLength > 0 && 0 >= buffer.size()) - return parserReturn::NotEnoughData; } if (midiCommandLength > 0) { - /* Decode a MIDI-command - if 0 is returned something went wrong */ - size_t consumed = decodeMidi(buffer, runningstatus); - if (consumed == 0) - { - } - else if (consumed > buffer.size()) - { - // sysex split in decodeMidi - return parserReturn::NotEnoughData; + cmdCount++; + + size_t consumed = 0; + auto retVal = decodeMidi(buffer, runningstatus, consumed); + if (retVal == parserReturn::NotEnoughData) { + cmdCount = 0; // avoid first command again + return retVal; } midiCommandLength -= consumed; - while (consumed--) buffer.pop_front(); - - if (midiCommandLength > 0 && 0 >= buffer.size()) - return parserReturn::NotEnoughData; - - cmdCount++; } } return parserReturn::Processed; } -size_t decodeTime(RtpBuffer_t &buffer) +parserReturn decodeTime(RtpBuffer_t &buffer, size_t &consumed) { - uint8_t consumed = 0; + debugPrintBuffer(buffer); + uint32_t deltatime = 0; /* RTP-MIDI deltatime is "compressed" using only the necessary amount of octets */ for (uint8_t j = 0; j < 4; j++) { + if (buffer.size() < 1) + return parserReturn::NotEnoughData; + uint8_t octet = buffer[consumed]; deltatime = (deltatime << 7) | (octet & RTP_MIDI_DELTA_TIME_OCTET_MASK); consumed++; @@ -64,21 +94,30 @@ size_t decodeTime(RtpBuffer_t &buffer) if ((octet & RTP_MIDI_DELTA_TIME_EXTENSION) == 0) break; } - return consumed; + + return parserReturn::Processed; } -size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) +parserReturn decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus, size_t &consumed) { - size_t consumed = 0; + debugPrintBuffer(buffer); - auto octet = buffer[0]; - bool using_rs; + if (buffer.size() < 1) + return parserReturn::NotEnoughData; + + auto octet = buffer.front(); /* MIDI realtime-data -> one octet -- unlike serial-wired MIDI realtime-commands in RTP-MIDI will * not be intermingled with other MIDI-commands, so we handle this case right here and return */ if (octet >= 0xf8) { - return 1; + consumed = 1; + + session->StartReceivedMidi(); + session->ReceivedMidi(octet); + session->EndReceivedMidi(); + + return parserReturn::Processed; } /* see if this first octet is a status message */ @@ -87,17 +126,14 @@ size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) /* if we have no running status yet -> error */ if (((runningstatus)&RTP_MIDI_COMMAND_STATUS_FLAG) == 0) { - return 0; + return parserReturn::Processed; } /* our first octet is "virtual" coming from a preceding MIDI-command, * so actually we have not really consumed anything yet */ octet = runningstatus; - using_rs = true; } else { - /* We have a "real" status-byte */ - using_rs = false; /* Let's see how this octet influences our running-status */ /* if we have a "normal" MIDI-command then the new status replaces the current running-status */ if (octet < 0xf0) @@ -117,10 +153,8 @@ size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) /* non-system MIDI-commands encode the command in the high nibble and the channel * in the low nibble - so we will take care of those cases next */ if (octet < 0xf0) - { - uint8_t type = (octet & 0xf0); - - switch (type) + { + switch (octet & 0xf0) { case MIDI_NAMESPACE::MidiType::NoteOff: consumed += 2; @@ -145,12 +179,16 @@ size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) break; } + if (buffer.size() < consumed) { + return parserReturn::NotEnoughData; + } + session->StartReceivedMidi(); for (size_t j = 0; j < consumed; j++) session->ReceivedMidi(buffer[j]); session->EndReceivedMidi(); - return consumed; + return parserReturn::Processed; } /* Here we catch the remaining system-common commands */ @@ -158,9 +196,7 @@ size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) { case MIDI_NAMESPACE::MidiType::SystemExclusiveStart: case MIDI_NAMESPACE::MidiType::SystemExclusiveEnd: - consumed = decodeMidiSysEx(buffer); - if (consumed > buffer.max_size()) - return consumed; + decodeMidiSysEx(buffer, consumed); break; case MIDI_NAMESPACE::MidiType::TimeCodeQuarterFrame: consumed += 1; @@ -175,38 +211,51 @@ size_t decodeMidi(RtpBuffer_t &buffer, uint8_t &runningstatus) break; } + if (buffer.size() < consumed) + return parserReturn::NotEnoughData; + session->StartReceivedMidi(); for (size_t j = 0; j < consumed; j++) session->ReceivedMidi(buffer[j]); session->EndReceivedMidi(); - return consumed; + return parserReturn::Processed; } -size_t decodeMidiSysEx(RtpBuffer_t &buffer) +parserReturn decodeMidiSysEx(RtpBuffer_t &buffer, size_t &consumed) { - size_t consumed = 1; // beginning SysEx Token is not counted (as it could remain) - size_t i = 0; - auto octet = buffer[++i]; + debugPrintBuffer(buffer); +// consumed = 1; // beginning SysEx Token is not counted (as it could remain) + size_t i = 1; // 0 = start of SysEx, so we can start with 1 while (i < buffer.size()) { consumed++; - octet = buffer[i++]; + auto octet = buffer[i++]; + + Serial.print("0x"); + Serial.print(octet < 16 ? "0" : ""); + Serial.print(octet, HEX); + Serial.print(" "); + if (octet == MIDI_NAMESPACE::MidiType::SystemExclusiveEnd) // Complete message - return consumed; + { + return parserReturn::Processed; + } else if (octet == MIDI_NAMESPACE::MidiType::SystemExclusiveStart) // Start - return consumed; + { + return parserReturn::Processed; + } } - + // begin of the SysEx is found, not the end. // so transmit what we have, add a stop-token at the end, - // remove the byes, modify the length and indicate + // remove the bytes, modify the length and indicate // not-enough data, so we buffer gets filled with the remaining bytes. // to compensate for adding the sysex at the end. consumed--; - + // send MIDI data session->StartReceivedMidi(); for (size_t j = 0; j < consumed; j++) @@ -217,11 +266,14 @@ size_t decodeMidiSysEx(RtpBuffer_t &buffer) // Remove the bytes that were submitted for (size_t j = 0; j < consumed; j++) buffer.pop_front(); + // Start a new SysEx train buffer.push_front(MIDI_NAMESPACE::MidiType::SystemExclusiveEnd); midiCommandLength -= consumed; - midiCommandLength += 1; // adding the manual SysEx SystemExclusiveEnd - + midiCommandLength += 1; // for adding the manual SysEx SystemExclusiveEnd in front + // indicates split SysEx - return buffer.max_size() + 1; + consumed = buffer.max_size() + 1; + + return parserReturn::Processed; } diff --git a/src/rtpMIDI_Parser_JournalSection.hpp b/src/rtpMIDI_Parser_JournalSection.hpp index 4669471..c0041b1 100644 --- a/src/rtpMIDI_Parser_JournalSection.hpp +++ b/src/rtpMIDI_Parser_JournalSection.hpp @@ -27,23 +27,14 @@ parserReturn decodeJournalSection(RtpBuffer_t &buffer) { size_t i = 0; - minimumLen += 1; + // Minimum size for the Journal section is 3 + minimumLen += 3; if (buffer.size() < minimumLen) return parserReturn::NotEnoughData; /* lets get the main flags from the recovery journal header */ uint8_t flags = buffer[i++]; - // sequenceNr - minimumLen += 2; - if (buffer.size() < minimumLen) - return parserReturn::NotEnoughData; - - if ((flags & RTP_MIDI_JS_FLAG_Y) == 0 && (flags & RTP_MIDI_JS_FLAG_A) == 0) - { - return parserReturn::Processed; - } - // The 16-bit Checkpoint Packet Seqnum header field codes the sequence // number of the checkpoint packet for this journal, in network byte // order (big-endian). The choice of the checkpoint packet sets the @@ -57,8 +48,21 @@ 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); - + // 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- + // octet header and is considered to be an "empty" journal. + if ((flags & RTP_MIDI_JS_FLAG_Y) == 0 && (flags & RTP_MIDI_JS_FLAG_A) == 0) + { + // Big fixed by @hugbug + while (minimumLen-- > 0) + buffer.pop_front(); + + _journalSectionComplete = true; + return parserReturn::Processed; + } + // By default, the payload format does not use enhanced Chapter C // encoding. In this default case, the H bit MUST be set to 0 for all // packets in the stream. @@ -83,18 +87,22 @@ parserReturn decodeJournalSection(RtpBuffer_t &buffer) { minimumLen += 2; if (buffer.size() < minimumLen) + { return parserReturn::NotEnoughData; + } cb.buffer[0] = buffer[i++]; cb.buffer[1] = buffer[i++]; - uint16_t systemflags = ntohs(cb.value16); + uint16_t systemflags = __ntohs(cb.value16); uint16_t sysjourlen = systemflags & RTP_MIDI_SJ_MASK_LENGTH; - + uint16_t remainingBytes = sysjourlen - 2; - + minimumLen += remainingBytes; if (buffer.size() < minimumLen) + { return parserReturn::NotEnoughData; + } i += remainingBytes; } @@ -106,45 +114,67 @@ parserReturn decodeJournalSection(RtpBuffer_t &buffer) { /* At the same place we find the total channels encoded in the channel journal */ _journalTotalChannels = (flags & RTP_MIDI_JS_MASK_TOTALCHANNELS) + 1; - - // do channel reading below, so we can already purge bytes here } - + while (i-- > 0) // is that the same as while (i--) ?? buffer.pop_front(); - + _journalSectionComplete = true; } // iterate through all the channels specified in header while (_journalTotalChannels > 0) { - if (buffer.size() < 3) - return parserReturn::NotEnoughData; + if (false == _channelJournalSectionComplete) { - cb.buffer[0] = 0x00; - cb.buffer[1] = buffer[0]; - cb.buffer[2] = buffer[1]; - cb.buffer[3] = buffer[2]; - uint32_t chanflags = ntohl(cb.value32); - - uint16_t chanjourlen = (chanflags & RTP_MIDI_CJ_MASK_LENGTH) >> 8; - uint16_t channelNr = (chanflags & RTP_MIDI_CJ_MASK_CHANNEL) >> RTP_MIDI_CJ_CHANNEL_SHIFT; + if (buffer.size() < 3) + return parserReturn::NotEnoughData; - // We have the most important bit of information - the length of the channel information - // no more need to further parse. + // 3 bytes for channel journal + cb.buffer[0] = 0x00; + cb.buffer[1] = buffer[0]; + cb.buffer[2] = buffer[1]; + cb.buffer[3] = buffer[2]; + uint32_t chanflags = __ntohl(cb.value32); + + bool S_flag = (chanflags & RTP_MIDI_CJ_FLAG_S) == 1; + uint8_t channelNr = (chanflags & RTP_MIDI_CJ_MASK_CHANNEL) >> RTP_MIDI_CJ_CHANNEL_SHIFT; + bool H_flag = (chanflags & RTP_MIDI_CJ_FLAG_H) == 1; + uint8_t chanjourlen = (chanflags & RTP_MIDI_CJ_MASK_LENGTH) >> 8; + + if ((chanflags & RTP_MIDI_CJ_FLAG_P)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_C)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_M)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_W)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_N)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_E)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_T)) { + } + if ((chanflags & RTP_MIDI_CJ_FLAG_A)) { + } + + _bytesToFlush = chanjourlen; + + _channelJournalSectionComplete = true; + } - if (buffer.size() < chanjourlen) - { + while (buffer.size() > 0 && _bytesToFlush > 0) { + _bytesToFlush--; + buffer.pop_front(); + } + + if (_bytesToFlush > 0) { return parserReturn::NotEnoughData; } - + _journalTotalChannels--; - - // advance the pointer - while (chanjourlen--) - buffer.pop_front(); - } - + } + return parserReturn::Processed; } diff --git a/src/rtp_Defs.h b/src/rtp_Defs.h index d9a90d5..85609cd 100644 --- a/src/rtp_Defs.h +++ b/src/rtp_Defs.h @@ -4,6 +4,23 @@ BEGIN_APPLEMIDI_NAMESPACE +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | V |P|X| CC |M| PT | Sequence number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +#define RTP_VERSION_2 2 + +// first octet +#define RTP_P_FIELD 0x20 +#define RTP_X_FIELD 0x10 +#define RTP_CC_FIELD 0xF + +// second octet +#define RTP_M_FIELD 0x80 +#define RTP_PT_FIELD 0x7F + /* magic number */ #define PAYLOADTYPE_RTPMIDI 97 @@ -12,21 +29,21 @@ BEGIN_APPLEMIDI_NAMESPACE /* Padding is the third bit; No need to shift, because true is any value other than 0! */ -#define RTP_PADDING(octet) ((octet)&0x20) +#define RTP_PADDING(octet) ((octet)&RTP_P_FIELD) /* Extension bit is the fourth bit */ -#define RTP_EXTENSION(octet) ((octet)&0x10) +#define RTP_EXTENSION(octet) ((octet)&RTP_X_FIELD) /* CSRC count is the last four bits */ -#define RTP_CSRC_COUNT(octet) ((octet)&0xF) +#define RTP_CSRC_COUNT(octet) ((octet)&RTP_CC_FIELD) /* Marker is the first bit of the second octet */ -#define RTP_MARKER(octet) ((octet)&0x80) +#define RTP_MARKER(octet) ((octet)&RTP_M_FIELD) /* Payload type is the last 7 bits */ -#define RTP_PAYLOAD_TYPE(octet) ((octet)&0x7F) +#define RTP_PAYLOAD_TYPE(octet) ((octet)&RTP_PT_FIELD) -typedef struct __attribute__((packed)) Rtp +typedef struct PACKED Rtp { uint8_t vpxcc; uint8_t mpayload; diff --git a/src/utility/Deque.h b/src/utility/Deque.h index afc0eac..03848a0 100644 --- a/src/utility/Deque.h +++ b/src/utility/Deque.h @@ -1,5 +1,7 @@ #pragma once +BEGIN_APPLEMIDI_NAMESPACE + template class Deque { // class iterator; @@ -32,8 +34,6 @@ class Deque { const T& at(size_t) const; void clear(); - - // iterator begin(); // iterator end(); @@ -211,3 +211,5 @@ void Deque::clear() _tail = -1; _head = 0; } + +END_APPLEMIDI_NAMESPACE diff --git a/src/utility/endian.h b/src/utility/endian.h index c853ade..b4de809 100644 --- a/src/utility/endian.h +++ b/src/utility/endian.h @@ -1,138 +1,78 @@ #pragma once -#ifndef BYTE_ORDER - -#ifndef BIG_ENDIAN -#define BIG_ENDIAN 4321 -#endif -#ifndef LITTLE_ENDIAN -#define LITTLE_ENDIAN 1234 -#endif - -#define TEST_LITTLE_ENDIAN (((union { unsigned x; unsigned char c; }){1}).c) - -#ifdef TEST_LITTLE_ENDIAN -#define BYTE_ORDER LITTLE_ENDIAN -#else -#define BYTE_ORDER BIG_ENDIAN -#endif - -#undef TEST_LITTLE_ENDIAN -#endif - -#include - -#ifndef __bswap16 -#define __bswap16(x) ((uint16_t)((((uint16_t)(x)&0xff00) >> 8) | (((uint16_t)(x)&0x00ff) << 8))) -#endif - -#ifndef __bswap32 -#define __bswap32(x) \ - ((uint32_t)((((uint32_t)(x)&0xff000000) >> 24) | (((uint32_t)(x)&0x00ff0000) >> 8) | \ - (((uint32_t)(x)&0x0000ff00) << 8) | (((uint32_t)(x)&0x000000ff) << 24))) -#endif - -#ifndef __bswap64 -#define __bswap64(x) \ - ((uint64_t)((((uint64_t)(x)&0xff00000000000000ULL) >> 56) | \ - (((uint64_t)(x)&0x00ff000000000000ULL) >> 40) | \ - (((uint64_t)(x)&0x0000ff0000000000ULL) >> 24) | \ - (((uint64_t)(x)&0x000000ff00000000ULL) >> 8) | \ - (((uint64_t)(x)&0x00000000ff000000ULL) << 8) | \ - (((uint64_t)(x)&0x0000000000ff0000ULL) << 24) | \ - (((uint64_t)(x)&0x000000000000ff00ULL) << 40) | \ - (((uint64_t)(x)&0x00000000000000ffULL) << 56))) -#endif - -union conversionBuffer -{ - uint8_t value8; - uint16_t value16; - uint32_t value32; - uint64_t value64; - byte buffer[8]; -}; - -#if BYTE_ORDER == LITTLE_ENDIAN - -// Definitions from musl libc -#define htobe16(x) __bswap16(x) -#define be16toh(x) __bswap16(x) -#define betoh16(x) __bswap16(x) -#define htobe32(x) __bswap32(x) -#define be32toh(x) __bswap32(x) -#define betoh32(x) __bswap32(x) -#define htobe64(x) __bswap64(x) -#define be64toh(x) __bswap64(x) -#define betoh64(x) __bswap64(x) -#define htole16(x) (uint16_t)(x) -#define le16toh(x) (uint16_t)(x) -#define letoh16(x) (uint16_t)(x) -#define htole32(x) (uint32_t)(x) -#define le32toh(x) (uint32_t)(x) -#define letoh32(x) (uint32_t)(x) -#define htole64(x) (uint64_t)(x) -#define le64toh(x) (uint64_t)(x) -#define letoh64(x) (uint64_t)(x) - -// From Apple Open Source Libc -#define ntohs(x) __bswap16(x) -#define htons(x) __bswap16(x) -#define ntohl(x) __bswap32(x) -#define htonl(x) __bswap32(x) -#define ntohll(x) __bswap64(x) -#define htonll(x) __bswap64(x) - -#define NTOHL(x) (x) = ntohl((uint32_t)x) -#define NTOHS(x) (x) = ntohs((uint16_t)x) -#define NTOHLL(x) (x) = ntohll((uint64_t)x) -#define HTONL(x) (x) = htonl((uint32_t)x) -#define HTONS(x) (x) = htons((uint16_t)x) -#define HTONLL(x) (x) = htonll((uint64_t)x) - +#if !defined(_BYTE_ORDER) + + #define _BIG_ENDIAN 4321 + #define _LITTLE_ENDIAN 1234 + + #define TEST_LITTLE_ENDIAN (((union { unsigned x; unsigned char c; }){1}).c) + + #ifdef TEST_LITTLE_ENDIAN + #define _BYTE_ORDER _LITTLE_ENDIAN + #else + #define _BYTE_ORDER _BIG_ENDIAN + #endif + + #undef TEST_LITTLE_ENDIAN + + #include + + #ifdef __GNUC__ + #define __bswap16(_x) __builtin_bswap16(_x) + #define __bswap32(_x) __builtin_bswap32(_x) + #define __bswap64(_x) __builtin_bswap64(_x) + #else /* __GNUC__ */ + + static __inline __uint16_t + __bswap16(__uint16_t _x) + { + return ((__uint16_t)((_x >> 8) | ((_x << 8) & 0xff00))); + } + + static __inline __uint32_t + __bswap32(__uint32_t _x) + { + return ((__uint32_t)((_x >> 24) | ((_x >> 8) & 0xff00) | + ((_x << 8) & 0xff0000) | ((_x << 24) & 0xff000000))); + } + + static __inline __uint64_t + __bswap64(__uint64_t _x) + { + return ((__uint64_t)((_x >> 56) | ((_x >> 40) & 0xff00) | + ((_x >> 24) & 0xff0000) | ((_x >> 8) & 0xff000000) | + ((_x << 8) & ((__uint64_t)0xff << 32)) | + ((_x << 24) & ((__uint64_t)0xff << 40)) | + ((_x << 40) & ((__uint64_t)0xff << 48)) | ((_x << 56)))); + } + #endif /* !__GNUC__ */ + + #ifndef __machine_host_to_from_network_defined + #if _BYTE_ORDER == _LITTLE_ENDIAN + #define __ntohs(x) __bswap16(x) + #define __htons(x) __bswap16(x) + #define __ntohl(x) __bswap32(x) + #define __htonl(x) __bswap32(x) + #define __ntohll(x) __bswap64(x) + #define __htonll(x) __bswap64(x) + #else // BIG_ENDIAN + #define __ntohl(x) ((uint32_t)(x)) + #define __ntohs(x) ((uint16_t)(x)) + #define __htonl(x) ((uint32_t)(x)) + #define __htons(x) ((uint16_t)(x)) + #define __ntohll(x) ((uint64_t)(x)) + #define __htonll(x) ((uint64_t)(x)) + #endif + #endif /* __machine_host_to_from_network_defined */ + +#endif /* _BYTE_ORDER */ + +#ifndef __machine_host_to_from_network_defined +#if _BYTE_ORDER == _LITTLE_ENDIAN +#define __ntohll(x) __bswap64(x) +#define __htonll(x) __bswap64(x) #else // BIG_ENDIAN - -// Definitions from musl libc - -#define htobe16(x) (uint16_t)(x) -#define be16toh(x) (uint16_t)(x) -#define betoh16(x) (uint16_t)(x) -#define htobe32(x) (uint32_t)(x) -#define be32toh(x) (uint32_t)(x) -#define betoh32(x) (uint32_t)(x) -#define htobe64(x) (uint64_t)(x) -#define be64toh(x) (uint64_t)(x) -#define betoh64(x) (uint64_t)(x) -#define htole16(x) __bswap16(x) -#define le16toh(x) __bswap16(x) -#define letoh16(x) __bswap16(x) -#define htole32(x) __bswap32(x) -#define le32toh(x) __bswap32(x) -#define letoh32(x) __bswap32(x) -#define htole64(x) __bswap64(x) -#define le64toh(x) __bswap64(x) -#define letoh64(x) __bswap64(x) - -// From Apple Open Source libc -#define ntohl(x) ((uint32_t)(x)) -#define ntohs(x) ((uint16_t)(x)) -#define htonl(x) ((uint32_t)(x)) -#define htons(x) ((uint16_t)(x)) -#define ntohll(x) ((uint64_t)(x)) -#define htonll(x) ((uint64_t)(x)) - -#define NTOHL(x) (x) -#define NTOHS(x) (x) -#define NTOHLL(x) (x) -#define HTONL(x) (x) -#define HTONS(x) (x) -#define HTONLL(x) (x) - - -void aa(uint64_t value) -{ - if ( value >= 10 ) - aa(value / 10); -} - +#define __ntohll(x) ((uint64_t)(x)) +#define __htonll(x) ((uint64_t)(x)) #endif +#endif /* __machine_host_to_from_network_defined */ 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/Ethernet.h b/test/Ethernet.h index 59d9f3b..086caa2 100644 --- a/test/Ethernet.h +++ b/test/Ethernet.h @@ -3,6 +3,8 @@ #include "Arduino.h" +#define DEFAULT_CONTROL_PORT 5004 + class EthernetUDP { Deque _buffer; @@ -13,22 +15,71 @@ class EthernetUDP EthernetUDP() { _port = 0; + + + + } void begin(uint16_t port) { _port = port; - if (port == 5004 && true) + if (port == DEFAULT_CONTROL_PORT && true) { // AppleMIDI messages + byte okSessionName[] = { + 0xff, 0xff, 0x49, 0x4e, 0x00, 0x00, 0x00, 0x02, 0x4e, 0x27, 0x95, 0x9e, 0x00, 0x00, 0xec, 0xf9, + 0x6c, 0x61, 0x70, 0x70, 0x69, 0x65, 0x6d, 0x63, 0x74, 0x6f, 0x70, 0x66, 0x61, 0x63, 0x65, 0x00 + }; + + byte notOKSessionName[] = { + 0xff, 0xff, 0x49, 0x4e, 0x00, 0x00, 0x00, 0x02, 0xcc, 0x0f, 0x6c, 0x49, 0x00, 0x00, 0xa4, 0x9b, + 0x6c, 0x61, 0x70, 0x70, 0x69, 0x65, 0x6d, 0x63, 0x74, 0x6f, 0x70, 0x66, 0x61, 0x63, 0x65, 0x2f, + 0x46, 0x4c, 0x55, 0x49, 0x44, 0x20, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x28, 0x36, 0x34, 0x37, + 0x38, 0x29, 0x2d, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x70, + 0x6f, 0x72, 0x74, 0x20, 0x28, 0x36, 0x34, 0x37, 0x38, 0x3a, 0x30, 0x29, 0x00 + }; + + write(okSessionName, sizeof(okSessionName)); + } - if (port == 5005 && true) + if (port == (DEFAULT_CONTROL_PORT + 1) && true) { // rtp-MIDI and AppleMIDI messages - byte aa[] = { + byte rr1[] = { + 0x80, 0x61, 0x19, 0x07, 0x00, 0x16, + 0xd5, 0x87, 0xf2, 0x2d, 0x56, 0xc3, 0xc0, 0x14, 0x92, 0x71, 0x00, 0x8f, 0xff, 0xff, 0xfe, 0x00, + 0x94, 0x64, 0x00, 0x81, 0x5a, 0x90, 0x64, 0x00, 0x02, 0x92, 0x64, 0x00, 0x23, 0x18, 0xf7, 0x00, + 0x19, 0x08, 0x89, 0xcd, 0x64, 0x7f, 0xf0, 0x78, 0xf1, 0x78, 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, + 0xf5, 0x78, 0xf6, 0x78, 0xf7, 0x78, 0xf0, 0x0f, 0x10, 0x13, 0x08, 0x86, 0xcd, 0x64, 0x7f, 0xf0, + 0x78, 0x71, 0x7f, 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, 0xf0, 0x0f, 0x20, 0x15, 0x08, 0x87, 0xcd, + 0x64, 0x7f, 0xf0, 0x78, 0xf1, 0x7f, 0xf4, 0x78, 0xf5, 0x7f, 0xf8, 0x78, 0xf9, 0x7f, 0xf0, 0x0f, + 0xd0, 0x07, 0x08, 0x81, 0xf1, 0xe0, 0x78 + }; + + byte rr2[] = { + 0x80, 0x61, 0x19, 0x14, 0x00, 0x16, + 0xf2, 0xd3, 0xf2, 0x2d, 0x56, 0xc3, 0x4b, 0x92, 0x71, 0x00, 0x8f, 0xff, 0xff, 0xfe, 0x00, 0x94, + 0x6a, 0x00, 0x23, 0x19, 0x04, 0x00, 0x19, 0x08, 0x89, 0xcd, 0x6a, 0x7f, 0xf0, 0x78, 0xf1, 0x78, + 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, 0xf5, 0x78, 0xf6, 0x78, 0xf7, 0x78, 0x1f, 0xc0, 0x10, 0x13, + 0x08, 0x86, 0xcd, 0x6a, 0x7f, 0xf0, 0x78, 0x71, 0x7f, 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, 0x1f, + 0xc0, 0x20, 0x15, 0x08, 0x87, 0xcd, 0x6a, 0x7f, 0xf0, 0x78, 0xf1, 0x7f, 0xf4, 0x78, 0xf5, 0x7f, + 0xf8, 0x78, 0xf9, 0x7f, 0x1f, 0xc0, 0xd0, 0x07, 0x08, 0x81, 0xf1, 0xe0, 0x78 + }; + + byte rr[] = { + 0x80, 0x61, 0x19, 0x05, 0x00, 0x16, + 0xcf, 0xe0, 0xf2, 0x2d, 0x56, 0xc3, 0x4c, 0x94, 0x63, 0x00, 0x81, 0x6a, 0x90, 0x63, 0x00, 0x02, + 0x92, 0x63, 0x00, 0x23, 0x18, 0xf5, 0x00, 0x19, 0x08, 0x89, 0xcd, 0x63, 0x7f, 0xf0, 0x78, 0xf1, + 0x78, 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, 0xf5, 0x78, 0xf6, 0x78, 0xf7, 0x78, 0xe0, 0x1f, 0x10, + 0x11, 0x08, 0x85, 0xcd, 0x63, 0x7f, 0xf0, 0x78, 0xf2, 0x78, 0xf3, 0x78, 0xf4, 0x78, 0xe0, 0x1f, + 0x20, 0x15, 0x08, 0x87, 0xcd, 0x63, 0x7f, 0xf0, 0x78, 0xf1, 0x7f, 0xf4, 0x78, 0xf5, 0x7f, 0xf8, + 0x78, 0xf9, 0x7f, 0xe0, 0x1f, 0xd0, 0x07, 0x08, 0x81, 0xf1, 0xe0, 0x78 }; + + byte aa[] = { 0x80, 0x61, 0xbf, 0xa2, 0x12, 0xb, 0x5a, 0xf7, 0xaa, 0x34, 0x96, 0x4a, 0xc0, 0x2b, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x0, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x00, 0xf8, 0x0, @@ -277,10 +328,8 @@ class EthernetUDP byte slecht[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; - // write(noteOnOff, sizeof(noteOnOff)); + // write(rr2, sizeof(rr2)); } - - if (port == 5005 && true) { @@ -309,12 +358,25 @@ class EthernetUDP return _buffer.size(); }; + int read() + { + if (_buffer.size() == 0) + return -1; + + byte value = _buffer.front(); + _buffer.pop_front(); + + return value; + } + size_t read(byte* buffer, size_t size) { size = min(size, _buffer.size()); - for (size_t i = 0; i < size; i++) - buffer[i] = _buffer.pop_front(); + for (size_t i = 0; i < size; i++) { + buffer[i] = _buffer.front(); + _buffer.pop_front(); + } return size; }; @@ -331,72 +393,13 @@ class EthernetUDP }; void endPacket() { }; + void flush() { - if (_port == 5004) - { - if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N') - { - _buffer.clear(); - - - byte u[] = { - 0xff, 0xff, - 0x4f, 0x4b, - 0x00, 0x00, 0x00, 0x02, - 0xb7, 0x06, 0x20, 0x30, - 0xda, 0x8d, 0xc5, 0x8a, - 0x4d, 0x61, 0x63, 0x62, 0x6f, 0x6f, 0x6b, 0x20, 0x50, 0x72, 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x53, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x20, 0x56, 0x65, 0x72, 0x62, 0x65, 0x6b, 0x65, 0x6e, 0x20, 0x28, 0x32, 0x29, 0x00 }; - - - - - - byte r[] = { 0xff, 0xff, - 0x4f, 0x4b, - 0x00, 0x0, 0x00, 0x02, - 0xb7, 0x06, 0x20, 0x30, - 0xda, 0x8d, 0xc5, 0x8a, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 }; - write(u, sizeof(u)); - } - } - if (_port == 5005) - { - if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'I' &&_buffer[3] == 'N') - { - _buffer.clear(); - byte r[] = { 0xff, 0xff, - 0x4f, 0x4b, - 0x00, 0x0, 0x00, 0x02, - 0xb7, 0x06, 0x20, 0x30, - 0xda, 0x8d, 0xc5, 0x8a, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6, 0x6e, 0x31, 0x2d, 0x42, 0x00 }; - write(r, sizeof(r)); - } - else if (_buffer[0] == 0xff && _buffer[1] == 0xff && _buffer[2] == 'C' &&_buffer[3] == 'K') - { - if (_buffer[8] == 0x00) - { - _buffer.clear(); - byte r[] = { 0xff, 0xff, - 0x43, 0x4b, - 0xda, 0x8d, 0xc5, 0x8a, - 0x01, - 0x65, 0x73, 0x73, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x34, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x6c, 0x83, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - write(r, sizeof(r)); - } - else - _buffer.clear(); - } - } }; void stop() { _buffer.clear(); }; - uint32_t remoteIP() { return 1; } - uint16_t remotePort() { return _port; } + IPAddress remoteIP() { return IPAddress(127,0,0,1); } + uint16_t remotePort() { return _port; } }; diff --git a/test/IPAddress.h b/test/IPAddress.h index bcb1d0e..1f403a7 100644 --- a/test/IPAddress.h +++ b/test/IPAddress.h @@ -9,4 +9,9 @@ class IPAddress IPAddress(uint32_t address) { } IPAddress(int address) { } IPAddress(const uint8_t *address) {}; + + bool operator==(const IPAddress&) const { return true; } + bool operator!=(const IPAddress&) const { return true; } }; + +const IPAddress INADDR_NONE(0, 0, 0, 0); \ No newline at end of file diff --git a/test/NoteOn.cpp b/test/NoteOn.cpp index a4ab27b..57d8191 100644 --- a/test/NoteOn.cpp +++ b/test/NoteOn.cpp @@ -4,7 +4,6 @@ #define APPLEMIDI_INITIATOR #include "AppleMIDI.h" -USING_NAMESPACE_APPLEMIDI unsigned long t0 = millis(); bool isConnected = false; @@ -33,50 +32,39 @@ APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE(); // ----------------------------------------------------------------------------- // rtpMIDI session. Device connected // ----------------------------------------------------------------------------- -void OnAppleMidiConnected(const ssrc_t & ssrc, const char* name) { +void OnAppleMidiConnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) { isConnected = true; - N_DEBUG_PRINT(F("Connected to session ")); - N_DEBUG_PRINTLN(name); + AM_DBG(F("Connected to session"), ssrc, name); } // ----------------------------------------------------------------------------- // rtpMIDI session. Device disconnected // ----------------------------------------------------------------------------- -void OnAppleMidiDisconnected(const ssrc_t & ssrc) { +void OnAppleMidiDisconnected(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) { isConnected = false; - N_DEBUG_PRINTLN(F("Disconnected")); + AM_DBG(F("Disconnected"), ssrc); } // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- -void OnAppleMidiByte(const ssrc_t & ssrc, byte data) { - N_DEBUG_PRINT(F("MIDI: ")); - N_DEBUG_PRINTLN(data); +void OnAppleMidiByte(const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, byte data) { + AM_DBG(F("MIDI: ")); + AM_DBG(data); } // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- static void OnMidiNoteOn(byte channel, byte note, byte velocity) { - N_DEBUG_PRINT(F("Incoming NoteOn from channel: ")); - N_DEBUG_PRINT(channel); - N_DEBUG_PRINT(F(", note: ")); - N_DEBUG_PRINT(note); - N_DEBUG_PRINT(F(", velocity: ")); - N_DEBUG_PRINTLN(velocity); + AM_DBG(F("in\tNote on"), note, " Velocity", velocity, "\t", channel); } // ----------------------------------------------------------------------------- // // ----------------------------------------------------------------------------- static void OnMidiNoteOff(byte channel, byte note, byte velocity) { - N_DEBUG_PRINT(F("Incoming NoteOff from channel: ")); - N_DEBUG_PRINT(channel); - N_DEBUG_PRINT(F(", note: ")); - N_DEBUG_PRINT(note); - N_DEBUG_PRINT(F(", velocity: ")); - N_DEBUG_PRINTLN(velocity); + AM_DBG(F("in\tNote off"), note, " Velocity", velocity, "\t", channel); } // ----------------------------------------------------------------------------- @@ -95,33 +83,31 @@ char getSysExStatus(const byte* data, uint16_t length) } static void OnMidiSystemExclusive(byte* array, unsigned size) { - N_DEBUG_PRINT(F("Incoming SysEx: ")); - N_DEBUG_PRINT(getSysExStatus(array, size)); + AM_DBG(F("Incoming SysEx: ")); + AM_DBG(getSysExStatus(array, size)); unsigned i = 0; for (; i < size - 1; i++) { - N_DEBUG_PRINT(F(" 0x")); - N_DEBUG_PRINT(array[i], HEX); + AM_DBG(F(" 0x")); + AM_DBG(array[i], HEX); } - N_DEBUG_PRINT(F(" 0x")); - N_DEBUG_PRINT(array[i], HEX); - N_DEBUG_PRINTLN(); + AM_DBG(F(" 0x")); + AM_DBG(array[i], HEX); + AM_DBG(); } void begin() { - V_DEBUG_PRINTLN(F("OK, now make sure you an rtpMIDI session that is Enabled")); - V_DEBUG_PRINT(F("Add device named Arduino with Host/Port ")); - // V_DEBUG_PRINT(Ethernet.localIP()); - V_DEBUG_PRINTLN(F(":5004")); - V_DEBUG_PRINTLN(F("Then press the Connect button")); - V_DEBUG_PRINTLN(F("Then open a MIDI listener (eg MIDI-OX) and monitor incoming notes")); - - MIDI.begin(1); + AM_DBG(F("OK, now make sure you an rtpMIDI session that is Enabled")); + AM_DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")"); + AM_DBG(F("Select and then press the Connect button")); + AM_DBG(F("Then open a MIDI listener and monitor incoming notes")); + + MIDI.begin(MIDI_CHANNEL_OMNI); AppleMIDI.setHandleConnected(OnAppleMidiConnected); AppleMIDI.setHandleDisconnected(OnAppleMidiDisconnected); - AppleMIDI.setHandleReceivedMidi(OnAppleMidiByte); + // AppleMIDI.setHandleReceivedMidi(OnAppleMidiByte); MIDI.setHandleNoteOn(OnMidiNoteOn); MIDI.setHandleNoteOff(OnMidiNoteOff); @@ -137,10 +123,9 @@ 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) && (millis() - t0) > 10000) { t0 = millis(); - // Serial.print(F("."); byte note = random(1, 127); byte velocity = 55; diff --git a/test/TestParser.vcxproj b/test/TestParser.vcxproj index 05d5db0..6ff98d0 100644 --- a/test/TestParser.vcxproj +++ b/test/TestParser.vcxproj @@ -76,6 +76,7 @@ Disabled true true + C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\test;C:\Users\bart\Documents\Arduino\libraries\arduino_midi_library-master\src;C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\src;%(AdditionalIncludeDirectories) Console @@ -87,8 +88,7 @@ Disabled true true - - + C:\Users\bart\Documents\Arduino\libraries\Arduino-AppleMIDI-Library\src;C:\Users\bart\Documents\Arduino\libraries\arduino_midi_library-master\src;%(AdditionalIncludeDirectories) Default @@ -131,6 +131,7 @@ + diff --git a/test/TestParser.vcxproj.filters b/test/TestParser.vcxproj.filters index 58698e7..d47dcab 100644 --- a/test/TestParser.vcxproj.filters +++ b/test/TestParser.vcxproj.filters @@ -22,5 +22,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/test/rtpMidi.xcodeproj/xcshareddata/xcschemes/rtpMidi.xcscheme b/test/rtpMidi.xcodeproj/xcshareddata/xcschemes/rtpMidi.xcscheme index 7b96d37..cdfd28e 100644 --- a/test/rtpMidi.xcodeproj/xcshareddata/xcschemes/rtpMidi.xcscheme +++ b/test/rtpMidi.xcodeproj/xcshareddata/xcschemes/rtpMidi.xcscheme @@ -1,6 +1,6 @@