Closed hugbug closed 4 years ago
Thanks @hugbug for bringing these issues to my attention - I'll look at them. (Very impressed by your Conpianist app!)
To start, I'll spin off a v2.1 branch, so that we can test without interfering with the master branch.
Branch v2.1.0 already addresses issues Participants, Restore Connection (Exception handling). Parse control already addressed in v.2.0.5. (Can you already test @hugbug ?)
Looking into the Settings issue
v2.1.0 works good.
Found another issue. The invitation rejected-responses do not contain peer name - see packet screenshots:
The parser however attempts to read or skip peer name: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/c834468dc35ae2fb9912b8f082d14b9b496b5020/src/AppleMIDI_Parser.h#L333-L347
If the buffer contains further packets the parser starts to parse them as the name.
Apple's documentation says the name-part maybe omitted. That's it. There is no header indicating if the name is presented or not.
Knowing the size of the UDP-packet could help. However the design of v2 was changed from "parse udp-packets" to "save all packets into a stream, then parse". It would be helpful for parser to know packet boundaries in the stream though. Especially if a parse error occurs the parser could skip the data up to the next packet, then continue parsing. With current design the parser attempts to parse next data, fails, tries to parse again, fails again and so on. Until it accidentally starts to parse at a position where packet starts.
Before #82 was fixed the parser sometime gone crazy and I received a lot of NoteOff-events (because 0x80 is an often byte in RTP-MIDI). That may took a lot (several minutes) until the program started to work correctly again, either by reconnecting or if parser found the right place in the stream.
First of all you need to know where the packets end when you read them. That is currently problematic because the data is read only in 24-bytes chunks. When using the library in my program I had to change the read data loop. The issue was that with standard sockets there is no function which would tell how many bytes there is to read (available
on Arduino).
Furthermore reading in small chunks (24 bytes) is also problematic. Unlike TCP-sockets the remaining data of the UDP-packet is discarded if it wasn't read. The next call to read from UDP-socket will read the next packet and not the data remained from the previous partial read. For that reason I changed the loop to read as much data as possible. And I of course also increased all buffers to much larger sizes (don't have memory constraints on desktop pc).
With my read loop version each read
returns one UDP-packet (so I hope) and it is possible to save the information about packet boundaries somewhere. I think the easiest would be to put the length of the packet in the data stream before the packet itself. Then we could easily scroll to the next packet if the parsing of current packet fails.
I don't know how to do that properly on Arduino with small chunks (24 bytes).
Anyway if you don't want to change the packet reading, I think you could fix the issue with peer name by checking the first byte of the name. If it's 0xff
then it's probably the next AppleMIDI-message and not the peer name. In my tests the next packet was always EndSession-packet which would be recognised by checking for 0xff
.
Well spotted @hugbug , I indeed did not parse for a missing sessionName. Fixed in latest commit in feat/v2.1.0
As for the parser, it written for devices with little memory (e.g. Arduino) .
The "save all packets into a stream, then parse" is not entirely true - it's "parse and execute as much as possible (even when all data is not yet received)" when the minimum size of data is received, even when the full UDP frame is not in (hence the _journalSectionComplete
and _rtpHeadersComplete
flags).
You should be able to mimic the Arduino Ethernet UDP socket behaviour in your MidiSocket
class and use the provided readControlPackets
(avoiding the #ifdef)
I guess I misinterpreted readControlPackets
loop then. Can you please clarify if the following statement is true:
controlBuffer
anddataBuffer
may contain one UDP-packet or a part of a packet but never more than one packet.
If that's true, then my version of the loop and MidiSocket may push multiple packets into the buffers and that's the reason why the parser cannot restore after parse errors. I do need to rewrite my MidiSocket class.
(BTW, this is the best documented Issue on Github I have seen - ever, hands down! 👍🥇)
Can you please clarify if the following statement is true:
controlBuffer
anddataBuffer
may contain one UDP-packet or a part of a packet but never more than one packet.
Correct
I reworked my MidiSocket class. Now using the library without changes in read packet-loops. Great!
UDP packet buffer is currently set to 24 bytes: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5bdddc5b4e5874cf958935bc03ecb266dd6c4d8b/src/AppleMIDI.hpp#L7
I'd like to use a much larger buffer. For that I do:
#define UDP_TX_PACKET_MAX_SIZE 2048
#include "AppleMIDI.h"
Although that works, it feels like a hack. A better design would be to be able to set the packet size via settings struct instead.
In my app I have a separate thread for RTP. In the thread loop I call midi.read()
, which in turn calls methods from AppleMIDISession
.
To avoid excessive CPU load I need to call sleep
periodically. Currently I do that after midi.read()
.
If I understand the workflow correctly one call to midi.read()
can process only one UDP-packet (or even less). In that case sleeping after each packet would be too much. What I want is to determine if midi.read()
resulted in any work done in AppleMIDISession
. Then I can sleep
if nothing was processed and go to the next loop execution without sleeping in other case.
I was able to implement this strategy by extending my MidiSocket
with the info if the data was read or written through the socket. The only issue was to get access to MidiSocket
from my thread loop. For that I needed to move declarations of controlPort
and dataPort
to protected
. Please see commit https://github.com/hugbug/conpianist/commit/d7da51c094099e6a130860a2b53579442da2a48b if you wants details.
So my question is: can you make them protected
or do you have a better idea?
MS Visual C++ doesn't support GCC extension __attribute__ ((packed))
. Instead it has pragma
for that. This commit https://github.com/hugbug/conpianist/commit/921144fba0ea9f85d56a97c6c1ec117857da1fd7
makes the library compatible with MS Visual C++. Can this be integrated upstream? I can provide a PR or please do the changes yourself. Whatever you prefer.
endian.h
has some strange declaration which looks like a forgotten debug code:
https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5bdddc5b4e5874cf958935bc03ecb266dd6c4d8b/src/utility/endian.h#L132-L136
Can't imagine having a function with name aa
in global namespace on purpose.
The following lines generate warnings (redeclared define) on Mac: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5bdddc5b4e5874cf958935bc03ecb266dd6c4d8b/src/utility/endian.h#L116-L129
I recommend to surround the declarations with a check for Apple platform:
#ifndef __APPLE__
...
#endif
Full example here.
These two defines produce warnings (redeclared defines) on Mac: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5bdddc5b4e5874cf958935bc03ecb266dd6c4d8b/src/rtpMIDI_Clock.h#L10-L11 Both of them are not used in the library. I suggest to remove them.
That's it. Thanks for the support!
Thanks @hugbug
UDP_TX_PACKET_MAX_SIZE Done
controlPort and dataPort to protected done
Removed aa
🙈
Removed USEC_PER_SEC
and NSEC_PER_SEC
Moved __attribute__((packed))
to platform specific header (AppleMIDI_Platform*.h
)
Working separately on fixing the endian.h warning - stay tuned.
All changes pushed into feat/v2.1.0
, pls check and test
Working separately on fixing the endian.h warning - stay tuned. done
All is good, great work!
Nice cleanup of "endian.h" 👍
The only remained issue is "Using own settings". With an easy fix on my side with "proteced->public" it's not a big issue for me.
Forget to mention: compiles and works on both Mac and Windows.
The only remained issue is "Using own settings". With an easy fix on my side with "proteced->public" it's not a big issue for me. Beat you to it :-)
Forget to mention: compiles and works on both Mac and Windows. Cool! 😎
I use Xcode to debug the code and try various input packets (emulating Arduino)
Everything is perfect! :thumbsup:
Thank you for quick support 🥇
Closing the issue :running:
I use the library in an (open source) desktop app. Excellent library! Moreover it's the only one open source library for RTP/Apple-MIDI out there. That's why I use it in a non-arduino project.
I started in spring 2018. That version worked very well but it had an issue with very long connecting time. I've recently updated to v2 and am reporting small issues I've encountered.
Using own settings
I want to change default settings. I also need to use my own UdpClass which communicates via standard sockets:
This code fails to compile when
MidiInterface
-class attempts to call methodsbeginTransmission
,write
and others from myAppleMIDISession
-subclass. That's because the friend declaration forMidiInterface
instantiates template without MidiSettings: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5ecc825098f28acdc48d34380911e41e8ab1c90f/src/AppleMIDI.h#L43As a workaround I've just declared the necessary methods as
public
but you'll sure find a better way.Participants
If there are more than one participants only the first one get MIDI-messages. Others get empty packets. https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5ecc825098f28acdc48d34380911e41e8ab1c90f/src/AppleMIDI.hpp#L461-L468
Because
writeRtpMidiBuffer
clears the buffer: https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/5ecc825098f28acdc48d34380911e41e8ab1c90f/src/AppleMIDI.hpp#L533-L539Restore connection
I'm connecting to (digital) piano by sending invite. If connections breaks I want it to be reestablished. I can catch
OnDisconnected
event and then send another invite.However if the invitation (after all attempts) failed I don't get a notification and don't know when to send invite again. I could increase the number of invite attempts to a ridiculously high number but there other code places where participants are removed and no notifications are sent.
Curently I'm doing the following. In my run loop I check if the
participants
is empty and then send an invite. For that I had to makeparticipants
protected
(that was enough to access it from my subclass).Is there a better way not requiring modifying of the library code?
parseControlPackets
If
_appleMIDIParser.parse
returnsNotSureGiveMeMoreData
orNotEnoughData
we stay in an endless loop. https://github.com/lathoub/Arduino-AppleMIDI-Library/blob/eddc2a6c965a518fac83dd81354c2929236f6737/src/AppleMIDI.hpp#L30-L40That happend to me before #82 but not after the fix. Anyway it's better to be prepared for all parse results, for example:
That's it. Thanks again for the library.