Вопрос, касающийся использования Arduino и MIDI

Я использую Arduino Leonardo для отправки MIDI-входящих и исходящих сообщений в Ableton Live.

Компоненты включают потенциометр, Емкостный сенсорный датчик и двигатель постоянного тока, который я включаю с помощью драйвера двигателя L298N.

Прямо сейчас я настроил его так, чтобы я мог записывать автоматизацию, перемещая потенциометр, отправлять MIDI-ноту, когда я касаюсь фейдера, и следить за автоматизацией с помощью двигателя постоянного тока.

Проблема, с которой я сталкиваюсь, заключается в том, что, когда я начинаю проигрывать свои треки, автоматизация, которую я записал с помощью потенциометра, отключена, и поэтому мой двигатель не реагирует. Я считаю, что это происходит потому, что я одновременно отправляю MIDI-ввод и вывод. Вот текущая версия кода:

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

int minimumCp = 200;
int lastfaderPosition = 0;     // предыдущее состояние кнопки

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) {     //Функция для перемещения фейдера в определенное положение между 0-1023, если его еще нет
  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) {}; //Зацикливается до тех пор, пока двигатель не завершит движение
    digitalWrite(motorUp, LOW);
  }
}

Обратите внимание, что когда я использую эту версию с Arduino UNO, все работает. Я пытался обсудить это с несколькими людьми, которые гораздо более опытны, чем я, но, к моему удивлению, они не смогли понять, почему.

#include <SoftwareSerial.h> // MIDI вход
#include <MIDI.h> // MIDI выход
#include <CapacitiveSensor.h>

#define rxPin 0 // Вход (серый)
#define txPin 1 // Выход (желтый)

SoftwareSerial midiSerial (rxPin, txPin);

const byte wiper        = 0; //Положение фейдера относительно GND (аналог 0)
const byte motorUp      = 4;
const byte motorDown    = 5;
const byte motorPWM     = 6;
const byte touchSend    = 7;//тправить вывод для схемы измерения емкости (цифровой 7)
const byte touchReceive = 8; //Приемный вывод для схемы измерения емкости (цифровой 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) {     //ункция для перемещения фейдера в определенное положение между 0-1023, если его еще нет
  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); } }

, 👍2

Обсуждение

Вызывается ли " updateFader ()", когда он должен быть вызван? updateFader () "вызывается только тогда, когда" faderPos == Последняя позиция. Это маловероятно, потому что в "int faderPos = analogRead (mainFaderPin)" будет шум(электрический из-за квантования и вибрации). Попробуйте что - нибудь вроде " если (abs(faderPos- последняя позиция) < some_threshold)...)`, @tim

Да, похоже, это работает довольно хорошо. Я добавил конденсаторы связи/развязки в схему ввода/вывода MIDI, и это исправило шум. Сейчас он на самом деле очень стабилен, @zRockafellow

Что произойдет, если "позиция" меньше, чем "фадерМин", например, "Доходный город" равен нулю? Или если "позиция" больше, чем "faderMax", например, "Доходный город" равен 1016 (или 127 * 8), когда faderMax составляет около 1023-40 = 983?, @tim

Привет, Тим, когда двигатель находится в пределах допустимой погрешности, он просто перестает двигаться. Без этого допуска на ошибку двигатель будет беспорядочно двигаться вперед и назад, пытаясь найти точное значение для позиционирования. Дайте ему терпимость, и все получится. Это необходимый шаг для моторизованных фейдеров, @zRockafellow

Вторая версия в вашем обновленном вопросе работает по счастливой случайности., @tim

Но первая версия вообще не работает, лол. Я заблудился, @zRockafellow

Я работаю над объяснением, но сейчас мне нужно идти. Я напишу позже., @tim


1 ответ


1

Во-первых, вам нужен надежный способ синхронизации и анализа MIDI-сообщений, которые могут быть размером 1 байт, 2 байта или 3 байта или более... Смотрите это Краткое описание сообщений MIDI 1.0, полученных ассоциациейMIDI.

Хороший способ достичь этого - использовать State Machine. Ниже я написал простое приложение, которое анализирует 3-байтовые MIDI-сообщения. Он может быть расширен для анализа различных типов команд.

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())  // Используйте оператор "if", чтобы получить следующий доступный байт.
    {
        byte incoming = midiSerial.read();

        // Синхронизируйте и проанализируйте 3-байтовый пакет MIDI-сообщений, т. е. 1nnnnnnn 0nnnnnn 0nnnnnn
        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))  // Дважды проверьте, нет ли команды несинхронизации.
            {
                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))  // Тройная проверка на несинхронизацию команды.
            {
                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)
                {
                    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;  // Для байтов MIDI-команд MSB равно 1.
}

bool IsMidiData(const byte b)
{
    return b <= 127;  // Для байтов MIDI-данных MSB равно 1.
}

Во-вторых, существует проблема с функцией updateFader (), связанная с допуском. Это 40 в вашей первой версии и 10 во второй версии. Рассмотрим следующие значения переменных:

// В setup()
digitalWrite(motorDown, HIGH);
analogWrite(motorPWM, motorSpeed);
delay(300);
faderMin = analogRead(wiper) + tolerance;
faderMin = 0                 + 40;         // Нижняя граница диапазона.
faderMin = 40;

// В loop()
incomingVelocity = midiSerial.read() * 8.05511811024;
incomingVelocity = 1                 * 8.05511811024;  //Тихая заметка, например, от 0 до 5, даст значение от 0 до 40.
incomingVelocity = 8;

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

Результат: фейдер не сдвинется с места, если будет получена тихая нота, которая ставит позицию ниже Фадермина и приведет к тому, что фейдер заклиниет где-то в середине шкалы, а не перейдет к Фадермину. Этот эффект был бы преувеличен, если бы faderMin был выше 40, когда analogRead(стеклоочиститель) считывает больше нуля в setup().

Точно так же это происходит и с очень громкими нотами и Фадермаксом на другом конце шкалы:

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

Лучшим способом было бы обрезать целевую позицию до допустимого диапазона перед вызовом функции updateFader() и удалить второе условие из операторов if в функции 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)
. . .

В-третьих, updateFader() блокируется до тех пор, пока двигатель фейдера не закончит движение. Если ноты принимаются быстрее, чем фейдер может двигаться, темп значительно возрастет, поэтому рассмотрите возможность использования неблокирующего стиля, такого как Мигание без задержки.

В – четвертых, faderMin и faderMax должны быть именно такими-минимальной и максимальной степенью путешествия фейдера. Но, добавляя/вычитая допуск из min/max, это излишне уменьшает диапазон. Допуск фактически удваивается, подстраиваясь под него как в setup (), так и в adjustFader(). Степень перемещения фейдера можно получить, приняв в среднем несколько значений в calibrateFader(). Затем допуск можно настроить только один раз в функции adjustFader (), как описано выше.

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;
}

На другой ноте...

...Я прочитал документацию и исходный код библиотек, на которые вы ссылаетесь в своем коде, и я думаю, что это следующие библиотеки:

  1. Контрольная поверхность
  2. Библиотека Arduino MIDI

Они уже реализуют анализ MIDI-сообщений с помощью обратных вызовов, например:

  • Использование обратных вызовов
  • Использование обратных вызовов .ino

Этот пример подчеркивает важность быстрого неблокирующего кода. Это означает, что функция updateFader() также должна быть неблокирующей. У вас есть возможность написать свой собственный парсер конечных автоматов или использовать парсеры библиотек.

Вот как можно переписать ваш код, чтобы использовать анализатор библиотеки MIDI с неблокирующими обратными вызовами и неблокирующим updateFader(). Возможно, вам захочется дважды проверить, какие контакты вы используете для midiSerial, поскольку контакты 0 и 1 используются для последовательного монитора по умолчанию, или вы можете использовать MIDI.sendNoteOn() и 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()
{
    // Анализируйте MIDI-сообщения и вызывайте обратные вызовы.
    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)
        {
            // Не трогаю фейдер.
            //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;  // Постоянный интервал.
    }
}

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);                        // Выключите регулятор "Вверх" перед включением регулятора "вниз".
        digitalWrite(MOTOR_DOWN_PIN, HIGH);
    }
    else if (faderTargetPosition > currentPosition + tolerance) // Выше диапазона допуска.band.
    {
        digitalWrite(MOTOR_DOWN_PIN, LOW);                      // Выключите регулятор "Вниз" перед включением регулятора "вверх".
        digitalWrite(MOTOR_UP_PIN, HIGH);
    }
    else                                                        // В пределах допустимого диапазона.
    {
        digitalWrite(MOTOR_DOWN_PIN, LOW);
        digitalWrite(MOTOR_UP_PIN, LOW);
    }
}
,