2

I'm using an Arduino Leonardo for sending MIDI In and Out messages to Ableton Live.

Components include a Potentiometer, a Capacitive Touch Sensor, and a DC Motor, which I'm powering with a L298N Motor Driver.

Right now I have it set up so that I can record automation in by moving the potentiometer, send a MIDI note in when I'm touching the fader, and follow automation using the DC Motor.

The problem that I'm having is that when I go to play my tracks, the automation that I recorded with the potentiometer is deactivated, and therefore my motor doesn't respond. I believe this is because I'm sending MIDI Input and Output at the same time. Here's the current version of the code:

#include <Control_Surface.h>
#include <CapacitiveSensor.h>

int minimumCp = 200; int lastfaderPosition = 0; // previous state of the button

const byte touchSend = 7; const byte touchReceive = 8;

CapacitiveSensor touchSensor = CapacitiveSensor(touchReceive, touchSend); USBMIDI_Interface midi; CCPotentiometer faderPosition { A0, MIDI_CC::General_Purpose_Controller_1 };

int incomingChannel; int incomingNote; int incomingVelocity; const byte mainFaderPin = 0; const byte motorUp = 4; const byte motorDown = 5; const byte motorPWM = 6; byte motorSpeed = 150; byte tolerance = 40; double faderMax = 0; double faderMin = 0;

struct MyMIDI_Callbacks : FineGrainedMIDI_Callbacks<MyMIDI_Callbacks> {

void onControlChange(Channel channel, uint8_t controller, uint8_t value, Cable cable) { incomingNote = controller; incomingVelocity = value; } void onProgramChange(Channel channel, uint8_t program, Cable cable) { Serial << "Program Change: " << channel << ", program " << program << ", " << cable << endl; } void onAfterTouchChannel(Channel channel, uint8_t pressure, Cable cable) { Serial << "Channel Pressure: " << channel << ", pressure " << pressure << ", " << cable << endl; } void onPitchBend(Channel channel, uint16_t bend, Cable cable) { Serial << "Pitch Bend: " << channel << ", bend " << bend << ", " << cable << endl; } void onSystemExclusive(SysExMessage se) { Serial << F("System Exclusive: [") << se.length << "] " << AH::HexDump(se.data, se.length) << ", " << se.cable << endl; } void onTimeCodeQuarterFrame(uint8_t data, Cable cable) { Serial << "MTC Quarter Frame: " << data << ", " << cable << endl; } void onSongPosition(uint16_t beats, Cable cable) { Serial << "Song Position Pointer: " << beats << ", " << cable << endl; } void onSongSelect(uint8_t songnumber, Cable cable) { Serial << "Song Select: " << songnumber << ", " << cable << endl; } void onTuneRequest(Cable cable) { Serial << "Tune Request: " << cable << endl; } void onClock(Cable cable) { Serial << "Timing Clock: " << cable << endl; } void onStart(Cable cable) { Serial << "Start: " << cable << endl; } void onContinue(Cable cable) { Serial << "Continue: " << cable << endl; } void onStop(Cable cable) { Serial << "Stop: " << cable << endl; } void onActiveSensing(Cable cable) { Serial << "Active Sensing: " << cable << endl; } void onSystemReset(Cable cable) { Serial << "System Reset: " << cable << endl; }

} callback;

void setup() { touchSensor.set_CS_AutocaL_Millis(0xFFFFFFFF); midi.setCallbacks(callback); Control_Surface.begin(); pinMode(motorDown, OUTPUT); pinMode(motorUp, OUTPUT); pinMode(motorPWM, OUTPUT); Serial.begin(250000); calibrateFader(); }

void calibrateFader() { digitalWrite(motorUp, HIGH); analogWrite(motorPWM, motorSpeed); delay(300); digitalWrite(motorUp, LOW); faderMax = analogRead(mainFaderPin) - tolerance; digitalWrite(motorDown, HIGH); analogWrite(motorPWM, motorSpeed); delay(300); digitalWrite(motorDown, LOW); faderMin = analogRead(mainFaderPin) + tolerance; }

void loop() { int faderPos = analogRead(mainFaderPin); int totalCp = touchSensor.capacitiveSensor(9); Control_Surface.loop();

if ((faderPos == lastfaderPosition) && (totalCp <= minimumCp) && (incomingNote == 16)) { updateFader(incomingVelocity * 8); }

if ((faderPos == lastfaderPosition) && (totalCp > minimumCp)) { midi.sendNoteOn({37, CHANNEL_2}, 127); digitalWrite(motorUp, LOW); digitalWrite(motorDown, LOW); }

if (((faderPos < lastfaderPosition - 1) || (faderPos > lastfaderPosition + 1)) && (totalCp > minimumCp)) { digitalWrite(motorUp, LOW); digitalWrite(motorDown, LOW); } if (((faderPos < lastfaderPosition - 1) || (faderPos > lastfaderPosition + 1)) && (totalCp <=minimumCp)) { // Stop Sending MIDI, and receive it only } lastfaderPosition = faderPos; }

void updateFader(int position) { //Function to move fader to a specific position between 0-1023 if it's not already there if (position < analogRead(mainFaderPin) - tolerance && position > faderMin) { digitalWrite(motorDown, HIGH); while (position < analogRead(mainFaderPin) - tolerance) {}; //Loops until motor is done moving digitalWrite(motorDown, LOW); } else if (position > analogRead(mainFaderPin) + tolerance && position < faderMax) { digitalWrite(motorUp, HIGH); while (position > analogRead(mainFaderPin) + tolerance) {}; //Loops until motor is done moving digitalWrite(motorUp, LOW); } }

Note that when I use this version with an Arduino UNO, the whole thing works. I've tried discussing this with a few who are far more experienced than I, but surprisingly they were unable to provide insight as to why.

#include <SoftwareSerial.h> // MIDI Input
#include <MIDI.h> // MIDI Output
#include <CapacitiveSensor.h>

#define rxPin 0 // Input (Grey) #define txPin 1 // Output (Yellow)

SoftwareSerial midiSerial (rxPin, txPin);

const byte wiper = 0; //Position of fader relative to GND (Analog 0) const byte motorUp = 4; const byte motorDown = 5; const byte motorPWM = 6; const byte touchSend = 7;//Send pin for Capacitance Sensing Circuit (Digital 7) const byte touchReceive = 8; //Receive pin for Capacitance Sensing Circuit (Digital 8) unsigned int incomingCommand; unsigned int incomingNote; unsigned int incomingVelocity; byte motorSpeed = 150; // Raise if the fader is too slow (0-255) byte minimumCp = 200; // Raise if the fader is too sensitive (0-16383) byte tolerance = 10; // Raise if the fader is too shaky (0-1023) double faderMax = 1023; //Value read by fader's maximum position (0-1023) double faderMin = 0; //Value read by fader's minimum position (0-1023) long lastDebounceTime = 0; // the last time the output pin was toggled long debounceDelay = 15; // the debounce time; increase if the output flickers int lastfaderValue = 0; int targetPosition; byte ch1Vol = 176; byte fullVel = 127; byte ch_1 = 1; byte Db_1 = 1;

CapacitiveSensor touch = CapacitiveSensor(touchReceive, touchSend); MIDI_CREATE_DEFAULT_INSTANCE();

void setup() { touch.set_CS_AutocaL_Millis(0xFFFFFFFF); midiSerial.begin(31250); MIDI.begin(); pinMode(motorDown, OUTPUT); pinMode(motorUp, OUTPUT); pinMode(motorPWM, OUTPUT); calibrateFader(); }

void calibrateFader() { digitalWrite(motorUp, HIGH); analogWrite(motorPWM, motorSpeed); delay(300); digitalWrite(motorUp, LOW); faderMax = analogRead(wiper) - tolerance; digitalWrite(motorDown, HIGH); analogWrite(motorPWM, motorSpeed); delay(300); digitalWrite(motorDown, LOW); faderMin = analogRead(wiper) + tolerance; }

void loop() { int faderPos = analogRead(wiper); int faderHiResMIDI = ((faderPos *16.0146627566) - 8191.5);

while ( midiSerial.available()) { incomingCommand = midiSerial.read(); incomingNote = midiSerial.read(); incomingVelocity = midiSerial.read()*8.05511811024; if ((incomingVelocity <=1023) && (incomingCommand >127) && (incomingNote <128)) { midiSerial.write(incomingCommand); midiSerial.write(incomingNote); midiSerial.write(incomingVelocity); } }

{ int totalCp = touch.capacitiveSensor(30); if ( (millis() - lastDebounceTime) > debounceDelay) { if (totalCp <= minimumCp) { //if (incomingCommand == ch1Vol) { updateFader(incomingVelocity); }// } // Not Touching fader if ((faderPos == lastfaderValue) && (totalCp > minimumCp)) { MIDI.sendNoteOn(Db_1, fullVel, ch_1); // Touching Fader digitalWrite(motorDown, LOW); digitalWrite(motorUp, LOW); } if (((faderPos > lastfaderValue+1) or (faderPos < lastfaderValue-1)) && (totalCp > minimumCp)) { // Moving Fader MIDI.sendPitchBend(faderHiResMIDI, ch_1); digitalWrite(motorDown, LOW); digitalWrite(motorUp, LOW); } lastfaderValue = faderPos;
lastDebounceTime = millis(); } } }

void updateFader(int position) { //Function to move fader to a specific position between 0-1023 if it's not already there if (position < analogRead(wiper) - tolerance && position > faderMin) { digitalWrite(motorDown, HIGH); while (position < analogRead(wiper) - tolerance) {}; //Loops until motor is done moving digitalWrite(motorDown, LOW); } else if (position > analogRead(wiper) + tolerance && position < faderMax) { digitalWrite(motorUp, HIGH); while (position > analogRead(wiper) + tolerance) {}; //Loops until motor is done moving digitalWrite(motorUp, LOW); } }

zRockafellow
  • 131
  • 7

1 Answers1

1

Firstly, you need a robust way to synchronise to and parse MIDI messages which can be 1 byte, 2 bytes or 3 bytes or more... See this Summary of MIDI 1.0 Messages by the MIDI Association.

A good way to achieve this is by using a state machine. I've written a simple one below that parses 3-byte MIDI messages. It could be expanded to parse different types of commands.

typedef enum State
{
    Idle,
    Command,
    Data1
};

State state = State::Idle;

const float INCOMING_VELOCITY_SCALE = 1023.0 / 127.0;

byte incomingCommand; byte incomingNote; byte incomingVelocity;

int targetPosition;

void loop() { if (midiSerial.available()) // Use the if statement to get the next available byte. { byte incoming = midiSerial.read();

    // Synchronise to and parse the 3-byte MIDI message packet, i.e. 1nnnnnnn 0nnnnnnn 0nnnnnnn
    switch (state)
    {
    case State::Idle:
        if (IsMidiCommand(incoming))
        {
            Serial.println(&quot;Received command byte.&quot;);
            incomingCommand = incoming;
            state = State::Command;
        }
        else if (IsMidiData(incoming))
        {
            Serial.println(&quot;Received data byte.&quot;);
        }
        break;
    case State::Command:
        if (IsMidiCommand(incoming))  // Double check for an out of sync command.
        {
            Serial.println(&quot;Received command byte.&quot;);
            incomingCommand = incoming;
            state = State::Command;
        }
        else if (IsMidiData(incoming))
        {
            Serial.println(&quot;Received data1 byte.&quot;);
            incomingNote = incoming;
            state = State::Data1;
        }
        break;
    case State::Data1:
        if (IsMidiCommand(incoming))  // Triple check for an out of sync command.
        {
            Serial.println(&quot;Received command byte.&quot;);
            incomingCommand = incoming;
            state = State::Command;
        }
        else if (IsMidiData(incoming))
        {
            Serial.println(&quot;Received data2 byte.&quot;);
            incomingVelocity = incoming;
            midiSerial.write(incomingCommand);
            midiSerial.write(incomingNote);
            midiSerial.write(incomingVelocity);
            Serial.write(incomingCommand);
            Serial.write(incomingNote);
            Serial.write(incomingVelocity);
            Serial.println();
            targetPosition = incomingVelocity * INCOMING_VELOCITY_SCALE;
            if (targetPosition &gt; faderMax)      // Clip if out of bounds.
            {
                targetPosition = faderMax;
            }
            else if (targetPosition &lt; faderMin)
            {
                targetPosition = faderMin;
            }
            state = State::Idle;
        }
        break;
    default:
        state = State::Idle;
        break;
    }
}
. . .
updateFader(targetPosition);
. . .

}

bool IsMidiCommand(const byte b) { return b >= 128; // For MIDI command bytes, MSB is 1. }

bool IsMidiData(const byte b) { return b <= 127; // For MIDI data bytes, MSB is 0. }

Secondly, there is an issue with the updateFader() function regarding the tolerance. It's 40 in your first version and 10 in your second version. Consider the following values for the variables:

// In setup()
digitalWrite(motorDown, HIGH);
analogWrite(motorPWM, motorSpeed);
delay(300);
faderMin = analogRead(wiper) + tolerance;
faderMin = 0                 + 40;         // Bottom of range.
faderMin = 40;

// In loop() incomingVelocity = midiSerial.read() * 8.05511811024; incomingVelocity = 1 * 8.05511811024; // A quiet note, e.g. between 0 and 5, will a give a value of 0 to 40. incomingVelocity = 8;

// In updateFader() if (position < analogRead(wiper) - tolerance && position > faderMin) if (8 < 512 - 40 && 8 > 40 ) if (8 < 472 && 8 > 40 ) if (true && false ) if (false )

Result: the fader will not budge if a quiet note is received that puts position below faderMin and will leave the fader jamming somewhere in the middle of its scale rather than boogieing on down to faderMin. This effect would be exaggerated if faderMin were higher than 40 when analogRead(wiper) reads greater than zero in setup().

Similarly, this also happens with very loud notes and faderMax at the other end of the scale:

// In updateFader()
else if (position > analogRead(wiper) + tolerance && position < faderMax)

A better way would be to clip the targetPosition to the valid range before calling updateFader() and remove the second condition from the if statements in updateFader():

targetPosition = incomingVelocity * INCOMING_VELOCITY_SCALE;
if (targetPosition > faderMax)      // Clip if out of bounds.
{
    targetPosition = faderMax;
}
else if (targetPosition < faderMin)
{
    targetPosition = faderMin;
}
. . .
updateFader(targetPosition);
. . .
if (position < analogRead(wiper) - tolerance)
. . .
else if (position > analogRead(wiper) + tolerance)
. . .

Thirdly, updateFader() blocks until the fader motor has finished moving. If notes are being received faster than the fader can move, the tempo will rallentando, so consider using a non-blocking style like Blink Without Delay.

Fourthly, faderMin and faderMax should be just that – the minimum and maximum extent of fader travel. But by adding/substracting tolerance from min/max it is unnecessarily reducing the range. The tolerance is effectively being doubled by adjusting for it in both setup() and adjustFader(). The extent of fader travel can be obtained by taking an average of several values in calibrateFader(). Then the tolerance can be adjusted for only once in adjustFader() as described above.

void calibrateFader()
{
    const byte NUM_SAMPLES = 10;
    digitalWrite(motorUp, HIGH);
    analogWrite(motorPWM, motorSpeed);
    delay(300);
    digitalWrite(motorUp, LOW);
    faderMax = AnalogueAverage(wiper, NUM_SAMPLES);
    Serial.println(faderMax);
    digitalWrite(motorDown, HIGH);
    analogWrite(motorPWM, motorSpeed);
    delay(300);
    digitalWrite(motorDown, LOW);
    faderMin = AnalogueAverage(wiper, NUM_SAMPLES);
    Serial.println(faderMin);
}

unsigned int AnalogueAverage(const byte pin, const byte num_samples) { const byte ROUND_OFF = num_samples / 2; unsigned long total = 0; for (byte i = 0; i < num_samples; i++) { total += analogRead(pin); } return (total + ROUND_OFF) / num_samples; }

On a different note...

...I've been reading through the documentation and source code for the libraries you referenced in your code, which I think are these ones:

  1. Control Surface
  2. Arduino MIDI Library

These already implement the MIDI message parsing using callbacks, e.g.:

That example stresses the importance of swift non-blocking code. This means that updateFader() should also be non-blocking. You have the option to write your own custom state machine parser or use the libraries' parsers.

Here is how your code could be re-written to use the MIDI library's parser with non-blocking callbacks and non-blocking updateFader(). You may want to double-check which pins you are using for midiSerial because pins 0 and 1 are used for the default Serial monitor or you could use MIDI.sendNoteOn() and MIDI.sendNoteOff().

#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

unsigned int faderMax = 0; unsigned int faderMin = 0; unsigned int faderTargetPosition = 0;

void doNoteOn(byte channel, byte note, byte velocity) { midiSerial.write(channel); midiSerial.write(note); midiSerial.write(velocity); faderTargetPosition = velocity * INCOMING_VELOCITY_SCALE;

// Clip if out of bounds.
if (faderTargetPosition &gt; faderMax)
{
    faderTargetPosition = faderMax;
}
else if (faderTargetPosition &lt; faderMin)
{
    faderTargetPosition = faderMin;
}

}

void doNoteOff(byte channel, byte note, byte velocity) { midiSerial.write(channel); midiSerial.write(note); midiSerial.write(velocity); }

void setup() { MIDI.setHandleNoteOn(doNoteOn); MIDI.setHandleNoteOff(doNoteOff); MIDI.begin(MIDI_CHANNEL_OMNI); . . . }

void loop() { // Parse the MIDI messages and call the callbacks. MIDI.read();

if ((millis() - lastDebounceTime) &gt; debounceDelay)
{
    int totalCp = touch.capacitiveSensor(30);
    int faderPos = analogRead(FADER_PIN);
    int faderHiResMIDI = faderPos * 16.0146627566 - 8191.5;

    if (totalCp &lt;= minimumCp)
    {
        // Not touching fader.
        //if (incomingCommand == ch1Vol)
        //{
        updateFader(faderTargetPosition);
        //}
    }
    else
    {
        if (faderPos == lastfaderValue)
        {
            // Touching fader.
            MIDI.sendNoteOn(Db_1, fullVel, ch_1);
            digitalWrite(MOTOR_DOWN_PIN, LOW);
            digitalWrite(MOTOR_UP_PIN, LOW);
        }
        else if ((faderPos &gt; lastfaderValue + 1) or (faderPos &lt; lastfaderValue - 1))
        {
            // Moving fader.
            MIDI.sendPitchBend(faderHiResMIDI, ch_1);
            digitalWrite(MOTOR_DOWN_PIN, LOW);
            digitalWrite(MOTOR_UP_PIN, LOW);
        }
    }
    lastfaderValue = faderPos;
    lastDebounceTime += debounceDelay;  // Constant interval.
}

}

void updateFader(const unsigned int& faderTargetPosition) { const unsigned int currentPosition = analogRead(FADER_PIN); if (faderTargetPosition < currentPosition - tolerance) // Below the tolerance band. { digitalWrite(MOTOR_UP_PIN, LOW); // Switch the up control off before switching the down control on. digitalWrite(MOTOR_DOWN_PIN, HIGH); } else if (faderTargetPosition > currentPosition + tolerance) // Above the tolerance band. { digitalWrite(MOTOR_DOWN_PIN, LOW); // Switch the down control off before switching the up control on. digitalWrite(MOTOR_UP_PIN, HIGH); } else // Within the tolerance band. { digitalWrite(MOTOR_DOWN_PIN, LOW); digitalWrite(MOTOR_UP_PIN, LOW); } }

tim
  • 699
  • 6
  • 15