#masterControl

Simple DIY Electronic Music Projectsdiyelectromusic.com@diyelectromusic.com
2024-12-14

Arduino MIDI Master Volume Control

This project uses an Arduino Uno or Nano with a potentiometer to create a MIDI Master Volume Control.

Warning! I strongly recommend using old or second hand equipment for your 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

A potentiometer is connected to A0, 5V and GND. To help smooth out the readings a 100nF ceramic capacitor can be connected between A0 and GND.

A MIDI OUT circuit is required. If full software MIDI THRU is required, then a MIDI IN circuit is also required, but it is not necessary to work. In fact a simple “two resistor” MIDI OUT interface would do the trick too (see DIY MIDI Interfaces).

I’ve used my Arduino Nano Mozzi Experimenter Shield PCB simply because it has a built-in MIDI interface and readily available potentiometers, but most of the additional circuitry is unused in this case.

The Code

This is a variation of the Arduino Serial MIDI Program and Control Messenger in principle, but in practise there are significant differences. Whilst the MIDI Control Change messages include a volume control, it has been long considered that this should be treated as a “channel message volume control” and not meant for “whole device” operation.

Consequently, at some point it was decided that one of the System Exclusive, System Real-time messages would be reserved as a “whole device” master volume control. That is what is required here.

The MIDI specification describes the require message as follows:

The Arduino MIDI library includes a sendSysEx function which has to be given a byte-sequence containing the required message. If the last parameter is “true” then it expects the SysEx start and end bytes to be included in the message.

Note that this MIDI message is using the common MIDI method for higher resolution values – it splits a 14-bit value (0 to 16363) into two “almost bytes” of 7-bits each so this has to be accounted for in the code.

Also, as the control will come from an Arduino analog INPUT port the starting values will be 0 to 1023, so they need to be scaled to the required MIDI range. This all happens in the function below.

void setMasterVolume (int volume) { 
uint16_t midivol = map(volume, 0, MAX_POT, 0, 16383);
byte sysex[9] = {0xF0, 0x7F, 0x7F, 0x04, 0x01, 0, 0, 0xF7, 0};
sysex[5] = midivol & 0x7F; // LSB
sysex[6] = (midivol >> 7) & 0x7F; // MSB
MIDI.sendSysEx(8, sysex, true);
}

I’ve also used, and slightly updated, the analog read averaging routine I used in Arduino PSS-680 Synth Editor- Part 2. This code maintains a “running average” reading to smooth out any jitters in the readings.

One thing I was finding though, especially at full-reading (1023), it was still quite jittery even with the smoothing, then I realised that this is because of integer arithmetic always rounding down to the last whole number. This means that no matter how many of the sampled readers were at the full value of 1023 if there was just a single reading of 1022 the rounding down would make the “average” also 1022.

The way around that was to use a single decimal place for each reading and then add 0.5 to the final averaged value to ensure it would also round to the nearest integer rather than round down.

To save having to move into floating point arithmetic I multiply the value by 10 giving me a pseudo single fixed-point decimal.

The complete averaging routine is thus as follows:

#define AVGEREADINGS 32
int avgepotvals[AVGEREADINGS];
int avgepotidx;
long avgepottotal;

int avgeAnalogRead (int pot) {
int reading = 10*analogRead(pot);
avgepottotal = avgepottotal - avgepotvals[avgepotidx];
avgepotvals[avgepotidx] = reading;
avgepottotal = avgepottotal + reading;
avgepotidx++;
if (avgepotidx >= AVGEREADINGS) avgepotidx = 0;
return (((avgepottotal / AVGEREADINGS) + 5) / 10);
}

Notice how the analogRead() is multiplied by 10 and then once the average is calculated in the last line 5 is added (or 0.5 when we know it is a fixed-single-point decimal) prior to the final divide by 10 to get back into the 0 to 1023 range.

Coupling this with a capacitor across the signal of the potentiometer as shown in the schematic, this gives me a much more stable reading, which is important for a MIDI controller as it means there is no MIDI messages being sent unless something has really changed.

The code is using the MIDI serial transport which has MIDI THRU enabled by default. This means that the board could be used within an existing MIDI link by connecting both MIDI OUT and IN. This would add Master Volume control alongside whatever else is being transmitting and as the MIDI library works on the basis of complete MIDI messages, it should work relatively intelligently while merging the two MIDI streams.

Find it on GitHub here.

Closing Thoughts

There are many directions this code could go next. The obvious ones are:

  • Include options for additional MIDI interfaces, especially USB MIDI.
  • Support Master Balance in addition to Master Volume.
  • Switch to a rotary encoder rather than a potentiometer.

Kevin

#arduinoNano #arduinoUno #masterControl #midi #potentiometer #sysex #systemExclusive

Client Info

Server: https://mastodon.social
Version: 2025.04
Repository: https://github.com/cyevgeniy/lmst