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);
unsigned int faderTotal = 0;
for (byte i = 0; i < num_samples; i++)
{
faderTotal += analogRead(wiper);
}
faderMin = faderTotal / num_samples;
Serial.println(faderMin);
digitalWrite(motorDown, HIGH);
analogWrite(motorPWM, motorSpeed);
delay(300);
digitalWrite(motorDown, LOW);
faderTotal = 0;
for (byte i = 0; i < num_samples; i++)
{
faderTotal += analogRead(wiper);
}
faderMax = faderTotal / num_samples;
Serial.println(faderMax);
}
- 699
- 6
- 15