Skip to content

Commit

Permalink
Merge pull request #98 from lathoub/v.3.0.0rc
Browse files Browse the repository at this point in the history
V.3.0.0rc
  • Loading branch information
lathoub authored Dec 30, 2020
2 parents ba3db18 + 4dd5446 commit 4249589
Show file tree
Hide file tree
Showing 30 changed files with 1,080 additions and 563 deletions.
15 changes: 13 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@ cache:
install:
- pip install -U platformio
- platformio update
- platformio lib -g install 62@5.0.0 870 872
- platformio lib -g install 62@5.0.0 870 872 236

script:
- pio ci --board=uno --lib=. examples/EthernetShield_NoteOnOffEverySec/EthernetShield_NoteOnOffEverySec.ino;
- pio ci --board=uno --lib=. examples/AVR_Callbacks/AVR_Callbacks.ino
- pio ci --board=uno --lib=. examples/AVR_Directory/AVR_Directory.ino
- pio ci --board=uno --lib=. examples/AVR_Initiator/AVR_Initiator.ino
- pio ci --board=uno --lib=. examples/AVR_MinMemUsage/AVR_MinMemUsage.ino
- pio ci --board=uno --lib=. examples/AVR_MultipleSessions/AVR_MultipleSessions.ino
- pio ci --board=uno --lib=. examples/AVR_NonDefaultSession/AVR_NonDefaultSession.ino
- pio ci --board=uno --lib=. examples/AVR_NoteOnOffEverySec/AVR_NoteOnOffEverySec.ino
- pio ci --board=uno --lib=. examples/AVR_ReceivedRawMidiData/AVR_ReceivedRawMidiData.ino
- pio ci --board=uno --lib=. examples/AVR_SysEx/AVR_SysEx.ino
- pio ci --board=mkrzero --lib=. examples/SAMD_Bonjour/SAMD_Bonjour.ino
- pio ci --board=huzzah --lib=. examples/ESP8266_NoteOnOffEverySec/ESP8266_NoteOnOffEverySec.ino
- pio ci --board=featheresp32 --lib=. examples/ESP32_NoteOnOffEverySec/ESP32_NoteOnOffEverySec.ino
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ Enables an Arduino with IP/UDP capabilities (Ethernet shield, ESP8266, ESP32, ..

## Features
* Build on top of the popular [FortySevenEffects MIDI library](https://github.com/FortySevenEffects/arduino_midi_library)
* Tested with AppleMIDI on Mac OS (Catalina) and using [rtpMIDI](https://www.tobias-erichsen.de/software/rtpmidi.html) from Tobias Erichsen on Windows 10
* Tested with AppleMIDI on Mac OS (Big Sur) and using [rtpMIDI](https://www.tobias-erichsen.de/software/rtpmidi.html) from Tobias Erichsen on Windows 10
* Send and receive all MIDI messages
* Uses callbacks to receive MIDI commands (no need for polling)
* Automatic instantiation of AppleMIDI object (see at the end of 'AppleMidi.h')
* Compiles on Arduino, MacOS (XCode) and Windows (MSVS)

## New in 3.0.0
* Bug Fixes (long session names get cropped)
* Reduced memory footprint (see AVR_MinMemUsage example and note below)
* Extended and revised callbacks to receive AppleMIDI protocol feedback (see AVR_Callbacks example)
* Who may connect to me (Directory) (see AVR_Directory example)

## Installation
From the Arduino IDE Library Manager, search for AppleMIDI

Expand Down Expand Up @@ -57,21 +63,31 @@ More usages in the [examples](https://github.com/lathoub/Arduino-AppleMIDI-Libra
* ESP32 (Adafruit HUZZAH32 – ESP32 Feather Board)
* Teensy 3.2
* Adafruit Feather M0 WiFi - ATSAMD21 + ATWINC1500


## Network Shields
* Arduino Ethernet shield (Wiznet W5100 and W5500)
* Arduino Wifi R3 shield
* MKR ETH shield
* Teensy WIZ820io W5200

## Memory usage
Out of the box, this library has been setup to use a minimum amount of memory. Extended callbacks are not enabled by default, and can be anabled by USE_EXT_CALLBACKS. See the callback examamples.

This library is not using any dynamic memory allocation methods - all buffers have a fixed size, set in the `AppleMIDI_Settings.h` file, avoiding potential memory leaks and memory fragmentation.

The minimum buffer size (`MaxBufferSize`) should be set to 64 bytes (also the default). Setting it to a higher value will make sending larger SysEx messages more efficiant (large SysEx messages are chopped in pieces, the larger the buffer, the less pieces needed), at the price of a bigger memory footprint.

`MaxNumberOfParticipants` is another way to cut memory - each particpants uses approx 300 bytes. Default number of participants is 1 (using 2 sockets).
Beware: the number of sockets on the Arduino is limited. The W5100 support 4, the W5200 and W5500 based IP chips can use 8 sockets. (Each participant uses 2 sockets: port 5004 and 5004+1). (Base port can be set in `APPLEMIDI_CREATE_DEFAULT_INSTANCE`)

## Network Shields
* Arduino Ethernet shield (Wiznet W5100 and W5500)
* Arduino Wifi R3 shield
* MKR ETH shield
* Teensy WIZ820io W5200


Reduce the memory footprint by a further 500 bytes by `#define NO_SESSION_NAME` before `#include <AppleMIDI.h>`. This will leave out all the code to manage the optional session name. By default the session name is kept.

Even further reduce the memory footprint by `#define ONE_PARTICIPANT` limiting the number of particpants to just 1.
On an UNO the absolute minimum memory footprint is 21966 bytes (68%) and 945 global variables (46%). For a Leonardo that is 24906 bytes (86%) and 1111 bytes (43%) of global variables.

## Notes
Session names can get really long on Macs (eg 'Macbook Pro of Johann Gambolputty .. von Hautkopft of Ulm') and will be trunctated to the `MaxSessionNameLen` (as set in the settings file).

## Arduino IDE (arduino.cc)
* 1.8.13

Expand Down
151 changes: 151 additions & 0 deletions examples/AVR_Callbacks/AVR_Callbacks.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include <Ethernet.h>

#define USE_EXT_CALLBACKS
#define SerialMon Serial
#define APPLEMIDI_DEBUG SerialMon
#include <AppleMIDI.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned long t1 = millis();
int8_t isConnected = 0;

APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();

void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t&, const APPLEMIDI_NAMESPACE::Exception&, const int32_t);

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void setup()
{
DBG_SETUP(115200);
DBG("Das Booting");

if (Ethernet.begin(mac) == 0) {
DBG(F("Failed DHCP, check network cable & reboot"));
for (;;);
}

DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
DBG(F("Select and then press the Connect button"));
DBG(F("Then open a MIDI listener and monitor incoming notes"));

MIDI.begin();

// Normal callbacks - always available
// Stay informed on connection status
AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
isConnected++;
DBG(F("Connected to session"), ssrc, name);
});
AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
isConnected--;
DBG(F("Disconnected"), ssrc);
});

// Extended callback, only available when defining USE_EXT_CALLBACKS
AppleMIDI.setHandleSentRtp([](const APPLEMIDI_NAMESPACE::Rtp_t & rtp) {
DBG(F("an rtpMessage has been sent with sequenceNr"), rtp.sequenceNr);
});
AppleMIDI.setHandleSentRtpMidi([](const APPLEMIDI_NAMESPACE::RtpMIDI_t& rtpMidi) {
DBG(F("an rtpMidiMessage has been sent"), rtpMidi.flags);
});
AppleMIDI.setHandleReceivedRtp([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const APPLEMIDI_NAMESPACE::Rtp_t & rtp, const int32_t& latency) {
DBG(F("setHandleReceivedRtp"), ssrc, rtp.sequenceNr , "with", latency, "ms latency");
});
AppleMIDI.setHandleStartReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) {
DBG(F("setHandleStartReceivedMidi from SSRC"), ssrc);
});
AppleMIDI.setHandleReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, byte value) {
DBG(F("setHandleReceivedMidi from SSRC"), ssrc, ", value:", value);
});
AppleMIDI.setHandleEndReceivedMidi([](const APPLEMIDI_NAMESPACE::ssrc_t& ssrc) {
DBG(F("setHandleEndReceivedMidi from SSRC"), ssrc);
});
AppleMIDI.setHandleException(OnAppleMidiException);

MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
DBG(F("NoteOn"), note);
});
MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
DBG(F("NoteOff"), note);
});

DBG(F("Sending MIDI messages every second"));
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
// Listen to incoming notes
MIDI.read();

// send a note every second
// (dont cáll delay(1000) as it will stall the pipeline)
if ((isConnected > 0) && (millis() - t1) > 1000)
{
t1 = millis();

byte note = random(1, 127);
byte velocity = 55;
byte channel = 1;

DBG(F("\nsendNoteOn"), note, velocity, channel);
MIDI.sendNoteOn(note, velocity, channel);
//MIDI.sendNoteOff(note, velocity, channel);

}
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void OnAppleMidiException(const APPLEMIDI_NAMESPACE::ssrc_t& ssrc, const APPLEMIDI_NAMESPACE::Exception& e, const int32_t value ) {
switch (e)
{
case APPLEMIDI_NAMESPACE::Exception::BufferFullException:
DBG(F("*** BufferFullException"));
break;
case APPLEMIDI_NAMESPACE::Exception::ParseException:
DBG(F("*** ParseException"));
break;
case APPLEMIDI_NAMESPACE::Exception::TooManyParticipantsException:
DBG(F("*** TooManyParticipantsException"));
break;
case APPLEMIDI_NAMESPACE::Exception::UnexpectedInviteException:
DBG(F("*** UnexpectedInviteException"));
break;
case APPLEMIDI_NAMESPACE::Exception::ParticipantNotFoundException:
DBG(F("*** ParticipantNotFoundException"), value);
break;
case APPLEMIDI_NAMESPACE::Exception::ComputerNotInDirectory:
DBG(F("*** ComputerNotInDirectory"), value);
break;
case APPLEMIDI_NAMESPACE::Exception::NotAcceptingAnyone:
DBG(F("*** NotAcceptingAnyone"), value);
break;
case APPLEMIDI_NAMESPACE::Exception::ListenerTimeOutException:
DBG(F("*** ListenerTimeOutException"));
break;
case APPLEMIDI_NAMESPACE::Exception::MaxAttemptsException:
DBG(F("*** MaxAttemptsException"));
break;
case APPLEMIDI_NAMESPACE::Exception::NoResponseFromConnectionRequestException:
DBG(F("***:yyy did't respond to the connection request. Check the address and port, and any firewall or router settings. (time)"));
break;
case APPLEMIDI_NAMESPACE::Exception::SendPacketsDropped:
DBG(F("*** SendPacketsDropped"), value);
break;
case APPLEMIDI_NAMESPACE::Exception::ReceivedPacketsDropped:
DBG(F("*** ReceivedPacketsDropped"), value);
break;
}
}
86 changes: 86 additions & 0 deletions examples/AVR_Directory/AVR_Directory.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include <Ethernet.h>

#define USE_DIRECTORY
#define SerialMon Serial
#define APPLEMIDI_DEBUG SerialMon
#include <AppleMIDI.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned long t1 = millis();
int8_t isConnected = 0;

APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void setup()
{
DBG_SETUP(115200);
DBG("Booting");

if (Ethernet.begin(mac) == 0) {
DBG(F("Failed DHCP, check network cable & reboot"));
for (;;);
}

AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 63));
AppleMIDI.directory.push_back(IPAddress(192, 168, 1, 66));
// AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::None;
AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::OnlyComputersInMyDirectory;
// AppleMIDI.whoCanConnectToMe = APPLEMIDI_NAMESPACE::Anyone;

DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
DBG(F("Select and then press the Connect button"));
DBG(F("Then open a MIDI listener and monitor incoming notes"));

MIDI.begin();

// Stay informed on connection status
AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
isConnected++;
DBG(F("Connected to session"), ssrc, name);
});
AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
isConnected--;
DBG(F("Disconnected"), ssrc);
});

MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
DBG(F("NoteOn"), note);
});
MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
DBG(F("NoteOff"), note);
});

DBG(F("Sending MIDI messages every second"));
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
// Listen to incoming notes
MIDI.read();

// send a note every second
// (dont cáll delay(1000) as it will stall the pipeline)
if ((isConnected > 0) && (millis() - t1) > 1000)
{
t1 = millis();

byte note = random(1, 127);
byte velocity = 55;
byte channel = 1;

MIDI.sendNoteOn(note, velocity, channel);
// MIDI.sendNoteOff(note, velocity, channel);
}
}
79 changes: 79 additions & 0 deletions examples/AVR_E3_NoteOnOffEverySec/AVR_E3_NoteOnOffEverySec.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <Ethernet3.h> // from https://github.com/sstaub/Ethernet3

#define SerialMon Serial
#define APPLEMIDI_DEBUG SerialMon
#include <AppleMIDI.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned long t1 = millis();
int8_t isConnected = 0;

APPLEMIDI_CREATE_DEFAULTSESSION_INSTANCE();

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void setup()
{
DBG_SETUP(115200);
DBG("Booting");

if (Ethernet.begin(mac) == 0) {
DBG(F("Failed DHCP, check network cable & reboot"));
for (;;);
}

DBG(F("OK, now make sure you an rtpMIDI session that is Enabled"));
DBG(F("Add device named Arduino with Host"), Ethernet.localIP(), "Port", AppleMIDI.getPort(), "(Name", AppleMIDI.getName(), ")");
DBG(F("Select and then press the Connect button"));
DBG(F("Then open a MIDI listener and monitor incoming notes"));

MIDI.begin();

// Stay informed on connection status
AppleMIDI.setHandleConnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc, const char* name) {
isConnected++;
DBG(F("Connected to session"), ssrc, name);
});
AppleMIDI.setHandleDisconnected([](const APPLEMIDI_NAMESPACE::ssrc_t & ssrc) {
isConnected--;
DBG(F("Disconnected"), ssrc);
});

MIDI.setHandleNoteOn([](byte channel, byte note, byte velocity) {
DBG(F("NoteOn"), note);
});
MIDI.setHandleNoteOff([](byte channel, byte note, byte velocity) {
DBG(F("NoteOff"), note);
});

DBG(F("Sending MIDI messages every second"));
}

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------
void loop()
{
// Listen to incoming notes
MIDI.read();

// send a note every second
// (dont cáll delay(1000) as it will stall the pipeline)
if ((isConnected > 0) && (millis() - t1) > 1000)
{
t1 = millis();

byte note = random(1, 127);
byte velocity = 55;
byte channel = 1;

MIDI.sendNoteOn(note, velocity, channel);
// MIDI.sendNoteOff(note, velocity, channel);
}
}
Loading

0 comments on commit 4249589

Please sign in to comment.