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:
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);
}
}
- 699
- 6
- 15