[time-nuts] metronome

folkert folkert at vanheusden.com
Sat Jul 3 20:00:19 UTC 2021


Hello,

As an aspiring musician I decided to build my own metronome.

Musical devices (keyboards, synthesizers) were and even are connected
via a MIDI interface. MIDI is a current loop, 5v, can be implemented
with an Arduino (or ESP8266 running Arduino) with only a resistor (and a
levelshifter in case of the ESP8266). MIDI then transmits and receives
serial data (1 start bit, 8 data bits and a stop bit) at the lightning
speed of 31250 bps (yes: this is 1981 technology). This all takes place
over thick cables connected to DIN-5 connectors.

As my musical undertakings only happen on electronic devices with MIDI,
I thought it would be fun to create a MIDI metronome. That's supposedly
only two hours works. I picked the ESP8266 (in a Wemos D1 mini setup)
so that I could add a nice little WiFi enabled configuration interface.

My metronome emits every beat two types of messages:
 - a clock message (0xf8)
 - a touch message (0x99 instrument velocity, where 0x99 means: play a
   tone on channel 9 which is usually the percussion channel (MIDI has
   16 channels))

Then I remembered that there's also MIDI-over-networks which uses
multicast UDP. Half an hour hacking and that was also implemented and
worked - nice!

Because I wanted to keep the regular ESP8266 UART for the ESP SDK
debug messages (and my own), I used the SoftwareSerial library to
produce a serial bit-stream on one of the other GPIO pins.

So then it was all finished I thought: maybe I can make it very precise.
Maybe even extremely precise. I doubt a regular human can notice delays
or jitter of less than, say, a few milliseconds but that doesn't stop
me.

To see how well it behaved (it sounded good (for my untrained ears)).
I took 4000 samples of 120bpm (beats per minute), calculated average
and standard deviation and an allan deviation plot:

n: 4065, average: 0.517166, std.dev.: 0.001250

(time is in seconds)

This is HORRIBLE. After 4k beats, the timing was a bit more than 17ms
off! (on average) I won't even bother show how the Allan plot looks.
Luckily that was only a stupid bug and overcomplicated way of tracking
when the next beat was due (I did not use a timer but determed how
long the main-loop iteration took (more on that later) and
compensating for rounding errors).

Next version used a regular timer on the ESP8266. With only the DIN-5
connection used (no multicast UDP over WiFi):
n: 4357, average: 0.500010, std.dev.: 0.000131

Looks promising!
Even the graph looks what I hoped for (see attached 1783.png).

But now I switched on the WiFi output, and took a bit more samples
(as I fell asleep behind the keyboard):

n: 152199, average: 0.520018, std.dev.: 0.040506

Not so good anymore (1783-wifi.png).

The problem here is that Arduino has a void loop() { } in which you
constantly should do your things but not in a loop: the loop() function
is your loop. This allows the Arduino environment to check if the WiFi
subsystem needs attention or that e.g. the serial ports need to be read.
The developer unfortunately has not control over what happens when
outside that 'loop()' function.

You need to transmit the beat-messages in the loop-function (can't do
(software-)serial nor wifi in the timer interrupt handler) so if you're
unlucky, the moment that you 'see' that a flag was set in the timer
interrupt function might be sometimes delayed milliseconds. On average
that is 0.02 seconds or 20 milliseconds!

My next step will be: move to the ESP32 platform. That platform can
still do everything the Arduino way, but the ESP32 also exports an RTOS
SDK and has 2 processors to spread your processing over. This
implementation will take a bit due to circumstances so don't hold your
breath.

I wrote this just to share my enthusiasm. I expected it to be maybe 2
hours of soldering and coding fun, but with this timing-optimizing this
promises fun for at least a week!

Maybe after reading this you have any suggestions? Noticed anything
silly? Please let me know.

Oh for the graphs I used "AllanTools" for Python:
https://pypi.org/project/AllanTools/ I used the example at the bottom of
that page put replaced mdev (modified allan deviation) by adev (the
supposedly regular one) as I only have a vague notion how to interprete
the regular one.

The sampling of the DIN-5 midi is performed by connecting my device to a
"ESI Audiotechnik GmbH ESI MIDIMATE eX" MIDI-to-USB interface and then
dumping the messages with a modified 'amidi' from 'alsa-utils' (I added
code to output timestamps with milliseconds resolution).

The WiFi output is sampled using a raspberry pi 4 with tcpdump (with
name resolving disasbled).


Regards,

Folkert van Heusden
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 1783.png
Type: image/png
Size: 23861 bytes
Desc: not available
URL: <http://febo.com/pipermail/time-nuts_lists.febo.com/attachments/20210703/fabe5be7/attachment.png>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 1783-wifi.png
Type: image/png
Size: 24693 bytes
Desc: not available
URL: <http://febo.com/pipermail/time-nuts_lists.febo.com/attachments/20210703/fabe5be7/attachment-0001.png>


More information about the Time-nuts_lists.febo.com mailing list