MIDI-контроллер «Шарманка»

  • Платформы: Arduino Leonardo
  • Языки программирования: Arduino (C++)
  • Тэги: MIDI, шарманка, датчик линии, ноты, синтезатор

Что это?

Наверняка вы слышали про такой старинный музыкальный инструмент, как шарманка. Когда, вращая ручку, исполняется записанная заранее по нотам мелодия. В этой статье мы решили воссоздать забытый инструмент, используя новые технологии.

Мы решили написать для нашей шарманки два варианта работы:

  1. Подключать её к компьютеру в виде виртуальной клавиатуры и использовать в качестве синтезатора различные приложения.
  2. Подключать её к MIDI-синтезатору.

Что нам понадобится?

  1. MIDI-разъём
  2. Малярный валик
  3. Ролик для чистки одежды
  4. Картонная коробка. Мы использовали коробку из набора «Матрёшка»

Как собрать?

  1. Установите Troyka Shield на Arduino Leonardo.
  2. Вставьте MIDI-разъём в Breadboard Mini.
  3. Соедините MIDI-разъём с Troyka Shield. Обратите внимание на распиновку MIDI-разъёма. Питание 5V соедините через резистор 200 Ом к пину 4 MIDI-разъёма, землю GND к пину 2 MIDI-разъёма и цифровой пин 1 к пину 5 MIDI-разъёма.
  4. Возьмите 8 датчиков линии и закрепите их в один ряд на плоской поверхности. Для этого удобно использовать деревянную линейку или картон. Подключите датчики через стандартные 3-проводные шлейфы к Troyka Shield по порядку, с 4 по 11 пин.
  5. Пришло время нарисовать мелодию, которую будет проигрывать наша шарманка. Для удобства мы нарисуем табличку на листе бумаги. В табличке будет 8 столбцов. Каждый столбец соответствует определённой ноте. Количество строк зависит от длительности музыкальной композиции, мы нарисовали 32 строчки. В итоге наша мелодия будет иметь 8 различных нот и длиться 32 музыкальных такта. Нарисуйте мелодию, которую будет играть шарманка, закрашивая чёрным маркером нужные клетки. Когда чёрная клетка будет проходить под датчиком линии, зазвучит соответствующая нота. Вы можете сами нарисовать табличку или же скачать у нас по приведенной ссылке midiroller.pdf
  6. Далее надо сделать ручку и барабан для нашей шарманки. Возьмите малярный валик и выпрямите молотком один из изгибов его ручки. Теперь возьмите клейкий блок для чистки одежды, оденьте его поверх ролика. Наклейте лист с нотами на получившийся клейкий валик.
  7. После этого поместите всю эту конструкцию в корпус. Важная деталь, что сенсоры датчиков должны быть расположены на расстоянии 5–10 мм от барабана.
  8. В зависимости от режима работы, прошейте Arduino Leonardo одним из скетчей, приведённых ниже.

Алгоритм

В этом проекте мы используем Arduino Leonardo, потому что она распознается компьютером как HID-устройство и может притворяться компьютерной клавиатурой. Если у вас другая платформа, например Arduino UNO, вы можете использовать второй вариант с подключением к MIDI-синтезатору, поменяв в скетче Serial1 на Serial.

Для виртуальной клавиатуры

Каждые 10 миллисекунд опрашиваем по очереди каждый датчик на предмет изменения, если датчик изменил своё состояние и увидел чёрное, отправляем подсоединенному компьютеру сигнал о нажатии клавиши, которой соответствует данный датчик.

Для MIDI-синтезаторов

В случае с MIDI-синтезатором ситуация будет похожа на предыдущий случай. Также, каждые 10 миллисекунд опрашиваем по очереди каждый датчик на предмет изменения. Если датчик изменил своё состояние и увидел чёрное, отправляем на синтезатор MIDI-сообщение, в котором будет содержаться информация о нажатии ноты, которая закреплена за этим датчиком. Как только значения датчика опять вернётся к нулю, отправляем MIDI-сообщения, в котором будет содержаться информация о снятии ноты, которая закреплена за этим датчиком.

Исходный код

Для виртуальной клавиатуры

musicbox_uart.ino
//  датчики подключены по порядку в числовой последовательности
//  даём разумное имя для пина к которому подключен первый датчик
#define START_PIN 4
 
//  Массив состояний каждого из датчиков
bool state[8];
 
//  Массив кнопок клавиатуры
char keys[] = {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i'};
 
void setup()
{
  //  Начало работы эмуляции клавиатуры (только на платах Leanardo или Due)
  Keyboard.begin();
}
 
void loop()
{
  //  проверяем по очереди каждый датчик
  for (int i = 0; i < 8; ++i) {
    //  считываем состояние каждого из датчикок, где
    //  i — номер датчика с которого идёт считывание
    bool curState = digitalRead(START_PIN + i);
    if (curState != state[i]) {
      //  если датчик изменил своё состояние
      //  присваиваем ему новое состояние
      state[i] = curState;
      if (curState)
        //  если новое состояние истино
        //  отправляем подсоединенному компьютеру
        //  сигнал о нажатии клавиши, где
        //  i — номер кнопки
        Keyboard.write(keys[i]);
    }
  }
 
  delay(10);
}

Для MIDI-синтезаторов

musicbox.ino
//  даём разумное имя для каждой ноты из MIDI таблицы (0–127)
#define NOTE_1 29    //  Фа первой октавы
#define NOTE_2 31    //  Соль первой октавы
#define NOTE_3 33    //  Ля первой октавы
#define NOTE_4 35    //  Си  первой октавы
#define NOTE_5 36    //  До первой октавы
#define NOTE_6 38    //  Ре второй октавы
#define NOTE_7 40    //  Ми второй октавы
#define NOTE_8 41    //  Фа второй октавы
 
//  сила нажатия(громкость)
#define VELOCITY 100
 
//  тип MIDI сообщения — статус байт
//  1001 взять ноту (Note On)
//  0000 номер канала
#define NOTE_ON 0b10010000
 
//  датчики подключены по порядку в числовой последовательности
//  даём разумное имя для пина к которому подключен первый датчик
#define START_PIN 4
 
//  массив нот
int note[8] = {
  NOTE_1,
  NOTE_2,
  NOTE_3,
  NOTE_4,
  NOTE_5,
  NOTE_6,
  NOTE_7,
  NOTE_8
};
 
//  массив состояний каждого из датчиков
bool state[8];
 
void setup()
{
  // открываем последовательный порт
  // задаем скорость передачи данных 31250 кбит/с для MIDI
  Serial1.begin(31250);
}
 
void loop()
{
  //  проверяем по очереди каждый датчик
  for (int i = 0; i < 8; ++i) {
    //  считываем состояние каждого из датчикок, где
    //  i — номер датчика с которого идёт считывание
    bool curState = digitalRead(START_PIN + i);
    if (curState != state[i]) {
      //  если датчик изменил своё состояние
      //  присваиваем ему новое состояние
      state[i] = curState;
      if (curState)
        //  если новое состояние истино
        //  воспроизводим ноту, которая закреплена за датчиком
        //  для воспроизведения ноты и других функций протокола MIDI,
        //  необходимо вызвать функцию и передать ей 3 параметра:
        //  тип MIDI-сообщения, ноту и силу нажатия.
        sendMidiMessage(NOTE_ON, note[i], VELOCITY);
      else
        //  воспроизведение ноты нужно остановить,
        //  иначе она так и будет звучать
        //  в нашем случае высталяем громкость равной 0
        sendMidiMessage(NOTE_ON, note[i], 0);
    }
  }
  delay(10);
}
 
//  функция отправки MIDI сообщений через Serial1 порт
void sendMidiMessage(int cmd, int data_1, int data_2)
{
  Serial1.write(cmd);
  Serial1.write(data_1);
  Serial1.write(data_2);
}

Демонстрация работы устройства

Что дальше?

Вы можете увеличить/уменьшить количество воспроизводимых нот путём увеличения/уменьшение количество датчиков. Длительность мелодии зависит от диаметра барабана. Так же можно автоматизировать процесс путём подключение вместо ручки вращение шаговый двигатель или сервопривод.