Simple Midi Controller (1/2)

I just wanted a simple MIDI controller with some potentiometers, so I decided to build one with an Arduino, a case and some pots I had laying around.

The concept is very simple:

On the Arduino, I made a loop to read the potentiometers values, convert it to MIDI messages and sent it over UART.

On the computer, I made a, pretty rudimentar, UART to MIDI conversion software. I know there are some free tools that already do that. But, for some reason I was unable to get any of them working on my computer. I tried this one and some others. It did not work, so I made my own. If you want to try my solution, it is available here in my github. In the second part of this article, I will explain its implementation. (Edit: Link to the second part)

Making the case

Arduino Code

The most common way to implement a MIDI controller is the only send a MIDI message whenever a value change occurs. That is, if I am not turning the pot, there is no MIDI message being send. The DAW must retain the last send value.

But if you ever used potentiometers and ADCs you may already know that theirs reading are not “clean”. Even if I do not turn the pot, the read value will often change, because there is electrical, ADC conversion and even mechanical noise.

These reading changes will flood the UART interface with MIDI messages if not filtered. Fortunately, some simple filtering is enough to solve this issue. In short, a low pass filter, and a value change tolerance will be able to stop the flooding.

For the low pass filter, I coded a (very naive, but good enough for this project) average filtering. On top of that, the Arduino only sends messages if there is a change of, at least, 2 points in relation to the last sent message.

This is the final code, also available here on my github:

// Midi.ino

#define TOTAL_POTS (6)
#define TOTAL_SAMPLES (32)
#define CHANGE_TOLERANCE (2)

typedef struct {
    int pin;
    int val;
    int last_send_val;
    int samples[TOTAL_SAMPLES];
    int analog_val_start;
    int analog_val_end;
    uint8_t cc_number;
    uint8_t channel;
} st_pot;

st_pot pots[TOTAL_POTS];

void setup() {
    pots[0].pin = A5;
    pots[1].pin = A4;
    pots[2].pin = A3;
    pots[3].pin = A2;
    pots[4].pin = A1;
    pots[5].pin = A0;
    pots[0].analog_val_start = 0;
    pots[1].analog_val_start = 0;
    pots[2].analog_val_start = 0;
    pots[3].analog_val_start = 1024;
    pots[4].analog_val_start = 1024;
    pots[5].analog_val_start = 1024;
    pots[0].analog_val_end = 1024;
    pots[1].analog_val_end = 1024;
    pots[2].analog_val_end = 1024;
    pots[3].analog_val_end = 0;
    pots[4].analog_val_end = 0;
    pots[5].analog_val_end = 0;
    pots[0].cc_number = 14;
    pots[1].cc_number = 15;
    pots[2].cc_number = 16;
    pots[3].cc_number = 17;
    pots[4].cc_number = 18;
    pots[5].cc_number = 19;
    pots[0].channel = 0;
    pots[1].channel = 0;
    pots[2].channel = 0;
    pots[3].channel = 0;
    pots[4].channel = 0;
    pots[5].channel = 0;
    // Serial.begin(115200);
    Serial.begin(31250);
}

void loop() {
    for (int s=0; s<TOTAL_SAMPLES; s++) {
        for (int i=0; i<TOTAL_POTS; i++) {
            pots[i].samples[s] = analogRead(pots[i].pin);
        }
        delay(2);
    }
    // Calculate average and midi value
    for (int i=0; i<TOTAL_POTS; i++) {
        uint32_t avg = 0;
        for (int s=0; s<TOTAL_SAMPLES; s++) {
            avg += pots[i].samples[s];
        }
        avg = avg/TOTAL_SAMPLES;
        pots[i].val = map(
            avg,
            pots[i].analog_val_start,
            pots[i].analog_val_end,
            0,
            127
        );
    }

    // Send values that changed (by at least CHANGE_TOLERANCE)
    for (int i=0; i<TOTAL_POTS; i++) {
        int delta = pots[i].val - pots[i].last_send_val;
        delta = delta < 0 ? -delta : delta;
        bool has_changed = delta >= CHANGE_TOLERANCE;
        if (has_changed) {
            // Serial.print(pots[i].val);
            SendCC(pots[i].channel, pots[i].cc_number, pots[i].val);
            pots[i].last_send_val = pots[i].val;
        } else {
            // Serial.print("---");
        }
        // Serial.print(" | ");
    }
    // Serial.println("");
}

void SendCC(uint8_t ch, uint8_t cc_number, uint8_t val) {
    Serial.write(0xB0 + ch);
    Serial.write(cc_number);
    Serial.write(val);
}
Code language: C++ (cpp)

The MIDI protocol itself, and my use case, are so simple that there no need of any external library. As it can be seemed on the SendCC() function.

In the next part, I will document the UART to MIDI converter


Publicado

em

por

Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *