Arduino MIDI Delay

As mentioned when I made my MIDI In-Out Half-Shield, there are some neat opportunities for MIDI related effects, so this is my MIDI delay implementation.

This is marked “beginner” from the point of view of “just download the code and use it”, but if you want to dig into the code it is really in the “advanced” category for the contents.  But you don’t need to worry about the internals to start playing with it.

Warning! I strongly recommend using an old or second hand keyboard for your MIDI experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

Parts list

The Circuit

There is no circuit as such.  I used my DIY MIDI shield again as you can see here.

2020-08-11 17.13.55

As you can see, I also used my Mini USB-MIDI to MIDI converter (powered from the Arduino 5V and GND pins) so I could play it with my Korg miniKeys.

The Code

So, the “beginner” level “all you need to know” about the code is as follows.

This implements a MIDI delay function by recording which notes are being played and replaying the back after a fixed delay. It can store up to 16 notes, but that is configurable (I’ve only tested it with 16).  In order for it to work, the code will disable the automatic MIDI THRU functionality of the Arduino MIDI library.

There are a number of values at the top of the file you can use to set the parameters for your delay.  There are comments explaining their use as follows.

// The MIDI channel to listen on
// Set it to MIDI_CHANNEL_OMNI to listen on all channels
#define MIDI_CHANNEL 1

// Number of echoes to play for each note
#define ECHOS 5

// And the delay in milliseconds between them
#define ECHO_DELAY 300

// We can reduce the velocity on each echo if we wish
#define ECHO_DECR  30

// You can change the pitch of an echo with this value
// For example, set to 1 to rise a semitone on each echo
// or to -1 to fall a semitone.
#define ECHO_SHIFT 0

Each echo (5 in the above example) will come back after a set period of time (300 mS in the example) and the volume will be reduced slightly (by 30 “steps” above).

The handling of the delay is fairly crude so there will be circumstances where it doesn’t quite work properly, but hopefully these should be relatively rare!

Find it on GitHub here.

The fuller “advanced” discussion of the code now follows!

There are probably many ways in which a delay function could be implemented, but all have to consider the following:

  • To replay a note you have to both send a note On and later a note off MIDI event.
  • You have to think about overlapping notes – some notes will play before the last is finished – e.g. to play chords.
  • You need some way of both remembering which notes have been played and also how many echos of the note have been played.
  • You can’t use the Arduino built-in delay() functions as all other code execution stops – so you won’t be able to listen for new (overlapping or otherwise) notes.
  • Notes are likely to be played at irregular (compared to nice round time intervals) times and that has to be preserved in the replaying of the notes.
  • You need to decide what to do with other MIDI messages – are they forwarded on or ignored?

In order to give the code some sense of timing, so I could count out delays “by hand”, I decided to use the TimerOne library again.  This allows you to specify a function to be called on a regular “tick” that you define.  I decided all handling of the echo counting and delay measuring could be done there. I set up the code for a 1kHz “tick” – i.e. to run every millisecond.

The basic logic I went for goes like this.

When a note On is received:
   Play the note over MIDI
   Store the note in a table
   Store the time we received the note

Every "tick" of the code:
   Look through all stored notes and:
      IF a note still has echoes left to play AND its time is up THEN
         play the note over MIDI
         decrement the count of echoes left to play

I used the Arduino millis() function again to record the time at which a note was received.  This records the number of milliseconds since the Arduino was powered on and will wrap around after around 50 years.  Actually, I record the time at which I want the echo to be played, so it actually stores millis() + ECHO_DELAY.

Some complications included what to do about note Off events.  In the end I opted for a second millis() time measurement to record the time at which I then want to send out the note Off event too.  Again this is stored as millis() + ECHO_DELAY.

To keep track of the notes to be played I set up a structure as follows.

// Need to keep track of which notes are in various stages of delay
#define NUMNOTES 16
struct delayNotes_s {
  unsigned long milliOn;
  unsigned long milliOff;
  byte note;
  byte velocity; // Only storing NoteOn velocity here so no "after touch"
  byte channel;
  byte count;
};
delayNotes_s delayNotes[NUMNOTES];

“count” is initially set to ECHOES and an echo will be played whilst this is greater than zero.  It will be decremented every time an echo is played.

Another issue to be wary of is that notes are added to this structure within the main thread of the programme, albeit via the MIDI callback functions, but notes are removed by the “tick” routine.  This means there is the potential for concurrency issues.  Now this is an entire topic in its own right, but basically it means I don’t want a tick to come in whilst I’m deciding one of my “note slots” is free and starting to fill it in otherwise the tick might see a part filled structure.  The way to do that is to temporarily disable interrupts, and hence the tick function, whilst I fill in the structure – that way the tick will only ever see a complete structure.

Other things to be conscious of:

  • I need the tick routine to be as quick as possible otherwise no processing of the main thread of code can happen.  One way of doing this is not having it look through all 16 note slots every time, but instead only do one note slot per tick.
  • I find the next free slot in my list of structures by looking for entries where “note” is zero.
  • I can’t fill in the times for milliOn and milliOff at the same time – I have to wait until I’ve received a note Off message in order to set milliOff.  But I need to set milliOff to something otherwise the tick’s checking logic might go awry.  So I set milliOff to 0xFFFFFFFF – i.e. the largest unsigned long value possible – until I receive the note Off event, when I can set it properly.
  • Once I’ve found a free slot and added the new note to it, I have to be sure to stop looking through the structure (I forgot this and as soon as one note was played, it filled the structure up with the same note!).
  • It might be possible that the pairing of note on and note off timings gets confused if there are lots of the same note played at the same time.  In theory this shouldn’t happen as you would have to stop playing a note in order to play it again, but if a sequencer or other mechanism allowed several “note on” events for the same note before any “note off” events, it might get confused… (and so would I).
  • Finally I should probably note that I don’t decrement the “count” value for the number of remaining echoes until I’ve played the “note off” for the corresponding echo.

Think that is pretty much everything worth mentioning I think.

Closing Thoughts

This is probably one of the more complex effects to attempt to code up.  Other options include an arpeggiator, which could be a variant of the previous code.

It is also possible to do fancy things with channels – e.g. merging or splitting, but these days you’d probably just get a computer to manage that kind of thing for you.

Kevin

Leave a comment