Skip to main content
Arduino

Return to Revisions

4 of 4
Added alternative example making use of MIDI library callbacks.
tim
  • 699
  • 6
  • 15

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("Received command byte.");
 incomingCommand = incoming;
 state = State::Command;
 }
 else if (IsMidiData(incoming))
 {
 Serial.println("Received data byte.");
 }
 break;
 case State::Command:
 if (IsMidiCommand(incoming)) // Double check for an out of sync command.
 {
 Serial.println("Received command byte.");
 incomingCommand = incoming;
 state = State::Command;
 }
 else if (IsMidiData(incoming))
 {
 Serial.println("Received data1 byte.");
 incomingNote = incoming;
 state = State::Data1;
 }
 break;
 case State::Data1:
 if (IsMidiCommand(incoming)) // Triple check for an out of sync command.
 {
 Serial.println("Received command byte.");
 incomingCommand = incoming;
 state = State::Command;
 }
 else if (IsMidiData(incoming))
 {
 Serial.println("Received data2 byte.");
 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 > faderMax) // Clip if out of bounds.
 {
 targetPosition = faderMax;
 }
 else if (targetPosition < 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 > faderMax)
 {
 faderTargetPosition = faderMax;
 }
 else if (faderTargetPosition < 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) > debounceDelay)
 {
 int totalCp = touch.capacitiveSensor(30);
 int faderPos = analogRead(FADER_PIN);
 int faderHiResMIDI = faderPos * 16.0146627566 - 8191.5;
 if (totalCp <= 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 > lastfaderValue + 1) or (faderPos < 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

AltStyle によって変換されたページ (->オリジナル) /