MIDI-box/MIDI-синтезатор

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

В режиме самоизоляции, наконец, дошли руки до конструкции, которую уже давно собирался сделать. 

На данный момент, правда, она еще не закончена, но уже практически завершен корпус, сделаны почти все платы и есть понимание, что хочется от него получить. И основа софта уже была разработана в проекте http://arduino.ru/forum/proekty/analog-analogovogo-sintezatora , который все никак не соберусь закончить публикацию. Но, еще жду с Али нефиксируемые кнопки, а особенность сборки конструкции такова, что фиксируемые кнопки можно установить только после всего остального. 

Тем более, на форуме уже неоднократно возникали вопросы MIDI-контроллеров.

Собственно, поэтому я и решил сделать у темы двойное название. А для меня лично это будет именно MIDI синтезатор, но с полноценным MIDI выходом. Т.е. его можно будет использовать и как MIDI-box, если к выходу синтезатора не подключать усилитель.

Итак, поехали...

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

В качестве звукового модуля синтезатора был использован Dream Blaster S1 на чипе SAM2195, который позиционируется в качестве основы профессиональных синтезаторов начального уровня (т.е. того, что принято продавать в музыкальных магазинах, а не магазинах игрушек). Куплен 2 года назад на Али примерно за $60.

Корпус выглядит так:

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

Заготовки для фальшпанели и лицевой панели:

Панели сделаны из пластика толщиной 1.5-1.7 мм, который в магазине назывался "Фартук для кухни".

На сегодня - все, завтра продолжу. А пока пойду собирать все это в корпус.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Работа несколько затормозилась из-за возникшей проблемы. Сама проблема описана здесь: http://arduino.ru/forum/apparatnye-voprosy/neponyatki-s-samovozbuzhdeniem-ili-pomekhami

Вкратце: плата MIDI-синтезатора эпизодически входила в самовозбуждение. Проблема (как я надеюсь) была решена при помощи синфазного дросселя, включенного в цепь питания синтезатора. Дроссель содержит 2х56 витков провода ПЭВ-2 на колечке, снятом с системной платы ПК: диаметр внешний - 12.7 мм, внутренний - 6.9 мм, высота - 5.0 мм, цвет - желтый.

В процессе решения проблемы было сделано:

- в плату синтезатора впаяны дополнительные керамические конденсаторы по 10 мкФ в цепи питания 5 и 3.3 В,

- в переходную плату добавлен конденсатор 1000 мкФ (тоже в питание - а куда же еще!),

- параллельно плате синтезатора на расстоянии 2-3 мм от нее установлен заземленный экран из фольгированного гетинакса,

- некоторые корпусные детали оклеены алюминиевой фольгой, соединенной с землей,

- внесены изменения в схему MIDI интерфейса с целью обострить фронты цифрового сигнала (после оптрона добавлен буфер на триггерах Шмитта),

- в цепь питания добавлен описанный выше дроссель.

После того, как проблема вроде бы была устранена (в течение нескольких часов признаков самовозбуждения не наблюдалось), все переделки были оставлены, хотя, вероятно, они и избыточны.

В настоящий момент устройство представляет собой "глухой" MIDI синтезатор, т.е. без органов управления - MIDI-интерфейс напрямую подключен к синтезатору, минуя контроллер:

 

Следующий этап - окончательно определиться с размещением контроллера (Mega 2560) в корпусе. Естественно, контроллер будет расположен параллельно лицевой панели. Но сама лицевая панель целиком занята органами управления, соответственно, контроллер будет расположен над ними (когда коробка перевернута для осуществления монтажа. В рабочем положении, естественно, контроллер располагается под органами управления). В проекте http://arduino.ru/forum/proekty/analog-analogovogo-sintezatora контроллер был размещен над 2 и 3 рядами потенциометров при том, что потенциометры своими выводами были развернуты в сторону промежутков между 1 и 2, а также 3 и 4 рядами. Таким образом, контроллер был расположен над "пустым" промежутком. В данном проекте рядов потенциометров всего 2. Естественно, между ними только один промежуток, поэтому если разместить контроллер над ним, то замена неисправного потенциометра потребует полной разборки устройства, т.к. будет сопряжена с отключением от контроллера всех устройств (а по моим прикидкам из 70 пинов Меги будет занято 67-68).

Пока в качестве основного варианта рассматривается размещение контроллера над энкодерами. Но для этого нужно определиться со схемой включения энкодеров. До сих пор я применял схему, включающую 4 резистора и 2 конденсатора. Но на форуме недавно обсуждался скетч, не требующий подключения к энкодерам внешних элементов. В общем, надо разобраться и переделать его на Мегу (если это возможно, у 2560, в отличие от 328 pcint есть далеко не на всех ногах. Т.е. следующий этап работы - определиться с подключением энкодеров.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Следующий этап - плата энкодеров. Ручки энкодеров будут выходить на переднюю панель, соответственно, плата будет расположена параллельно панели. 

Плата вырезана из макетки 15х9. выемки по верхней и нижней стороне - для других органов управления.

К обратной стороне платы крепится контроллер, который будет принимать внешний MIDI сигнал, опрашивать органы управления, добавлять в поток собственные MIDI команды и выдавать "обогащенную" последовательность команд по выбору:

- на встроенный синтезатор,

- на MIDI OUT,

- и туда, и туда.

Для обработки энкодеров было решено использовать код "от Леонида Ивановича". Указанный код требует по два PCINT на энкодер. В отличие от кода, который ориентирован на внешние цепи подавления дребезга. В отличие от 328 у 2560 PCINT есть далеко не на всех ногах. Мне хватило буквально впритык. Если бы по проекту было 6 энкодеров, от кода Л.И. пришлось бы отказаться, т.к. все 16 аналоговых входов (на которых тоже есть PCINT) заняты под "крутилки".

Ниже код, которым проверял работу платы и энкодеров. Скажу сразу - код одноразовый, т.е. только для проверки платы, а не для постоянной работы. Но, учитывая, что адаптация "кода от Леонида Ивановича" для Меги мне не попадалась, решил все-таки опубликовать.

void encoders_Init() {
  pinMode(53,INPUT_PULLUP);
  pinMode(52,INPUT_PULLUP);
  pinMode(51,INPUT_PULLUP);
  pinMode(50,INPUT_PULLUP);
  pinMode(10,INPUT_PULLUP);
  pinMode(11,INPUT_PULLUP);
  pinMode(12,INPUT_PULLUP);
  pinMode(13,INPUT_PULLUP);
  pinMode(14,INPUT_PULLUP);
  pinMode(15,INPUT_PULLUP);
  PCIFR = PCIFR; // обнуляем все прерывания, которые могли иметь место ранее
  PCICR |= (1 << PCIE0) | (1 << PCIE1); // [PCIE0, PCIE2] разрешить прерывание
  PCMSK0  = 0xFF; // используются все 8 первых PCINT с PCINT0 по PCINT7
  PCMSK1 |= (1 << PCINT9) | (1<<PCINT10); // [PCINT0..PCINT23] выбрать вход на котором сработает прерывание 
  pinMode(29,INPUT_PULLUP);
  pinMode(27,INPUT_PULLUP);
  pinMode(28,INPUT_PULLUP);
  pinMode(26,INPUT_PULLUP);
  pinMode( 9,INPUT_PULLUP);
}

volatile int enc_B[5];
int counter0 = 0;
int counter1 = 0;

void setup(){                
  Serial.begin(115200);
/ encoders_Init();
  Serial.println("Start");
  Serial.print(PB0);
  Serial.print('\t');
  Serial.print(PB1);
  Serial.print('\t');
  Serial.print(PB2);
  Serial.print('\t');
  Serial.print(PB3);
  Serial.print('\t');
  Serial.print(PB4);
  Serial.print('\t');
  Serial.print(PB5);
  Serial.print('\t');
  Serial.print(PB6);
  Serial.print('\t');
  Serial.println(PB7);
  Serial.print(PCINT0);
  Serial.print('\t');
  Serial.print(PCINT1);
  Serial.print('\t');
  Serial.print(PCINT2);
  Serial.print('\t');
  Serial.print(PCINT3);
  Serial.print('\t');
  Serial.print(PCINT4);
  Serial.print('\t');
  Serial.print(PCINT5);
  Serial.print('\t');
  Serial.print(PCINT6);
  Serial.print('\t');
  Serial.println(PCINT7);
  Serial.print(PCIE0);
  Serial.print('\t');
  Serial.print(PCIE1);
  Serial.print('\t');
  Serial.println(PCIE2);
  Serial.println(PCMSK0, HEX);
  delay(5000);
}

ISR(PCINT0_vect){
  counter0++;
  const byte bPC_msk[4] = {(1 << PCINT0) | (1<<PCINT1), (1 << PCINT2) | (1<<PCINT3), 
                           (1 << PCINT4) | (1<<PCINT5), (1 << PCINT6) | (1<<PCINT7)};
  const byte bPC_pA[4] = {PB0, PB2, PB4, PB6};
  const byte bPC_pB[4] = {PB1, PB3, PB5, PB7};
  static byte EncPrev[4] = {0,0,0,0};      //предыдущее состояние энкодера
  static byte EncPrevPrev[4] = {0,0,0,0};  //пред-предыдущее состояние энкодера
  for (byte i = 0; i < 4; i++) {
    byte EncCur = 0;
    if(PCMSK0 & (bPC_msk[i])) {
/     if(!(PINB & (1 << bPC_pA[i]))) { EncCur  = 1;} //опрос фазы A энкодера
      if(!(PINB & (1 << bPC_pB[i]))) { EncCur |= 2;} //опрос фазы B энкодера
      if(EncCur != EncPrev[i])             //если состояние изменилось,
      {
        if(EncPrev[i] == 3 &&        //если предыдущее состояние 3
          EncCur != EncPrevPrev[i] )      //и текущее и пред-предыдущее не равны,
        {
          if(EncCur == 2)          //если текущее состояние 2,
            enc_B[i]++;            //шаг вверх
          else                          //иначе
            enc_B[i]--;            //шаг вниз
        }
        EncPrevPrev[i] = EncPrev[i];          //сохранение пред-предыдущего состояния
        EncPrev[i] = EncCur;               //сохранение предыдущего состояния
      }
    }
  }
}

ISR(PCINT1_vect){
  counter1++;
  const byte bPC_msk = (1 << PCINT9) | (1<<PCINT10);
  const byte bPC_pA = PJ0;
  const byte bPC_pB = PJ1;
  static char EncPrev = 0;      //предыдущее состояние энкодера
  static char EncPrevPrev = 0;  //пред-предыдущее состояние энкодера
  char EncCur = 0;
  if(!(PINJ & (1 << bPC_pA))) { EncCur  = 1;} //опрос фазы A энкодера
  if(!(PINJ & (1 << bPC_pB))) { EncCur |= 2;} //опрос фазы B энкодера
  if(EncCur != EncPrev)             //если состояние изменилось,
  {
    if(EncPrev == 3 &&        //если предыдущее состояние 3
      EncCur != EncPrevPrev )      //и текущее и пред-предыдущее не равны,
    {
      if(EncCur == 2)          //если текущее состояние 2,
        enc_B[4]++;            //шаг вверх
      else                          //иначе
        enc_B[4]--;            //шаг вниз
    }
    EncPrevPrev = EncPrev;          //сохранение пред-предыдущего состояния
    EncPrev = EncCur;               //сохранение предыдущего состояния
  }
}



void loop() {
  Serial.print(counter0); 
  Serial.print('\t'); 
  Serial.print(enc_B[0]); 
  Serial.print('\t'); 
  Serial.print(enc_B[1]); 
  Serial.print('\t'); 
  Serial.print(enc_B[2]); 
  Serial.print('\t'); 
  Serial.print(enc_B[3]); 
  Serial.print('\t'); 
  Serial.print(enc_B[4]); 
  Serial.print('\t'); 
  Serial.print(counter1); 
  Serial.print("\t\t"); 
  Serial.print(digitalRead(29)); 
  Serial.print('\t'); 
  Serial.print(digitalRead(27)); 
  Serial.print('\t'); 
  Serial.print(digitalRead(28)); 
  Serial.print('\t'); 
  Serial.print(digitalRead(26)); 
  Serial.print('\t'); 
  Serial.println(digitalRead(9)); 
}

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Закончил последнюю плату. К ней будут подключаться 2 блока потенциометров по 8 шт, дисплей и MIDI-плата. В принципе, предусмотрен разъем и для платы синтезатора, но, скорее всего, я оставлю ее запитанной напрямую от стабилизатора.

Плата изготовлена из заготовки шилда для Меги.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

После существенной доработки решил выделить меню, используемое в проекте, в отдельный проект: http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya

Видео: https://youtu.be/PyOdQMH7JxU

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Работа продолжается.

Постепенно буду выкладывать код.

Обработка MIDI сообщений:

файл MIDIfn.h:


#ifndef MIDIFN_H   // базовый класс MIDI
#define MIDIFN_H

#include <Arduino.h>
#include "comm_ctrl.h"

#define MAX_RUNSTATE_INTERVAL 82 // максимальное время действия RunState, мс // 75 : +0.6%, 76 : 0.0%, 88 : 0.0%, 89 : -0.1%, 

#define Debug_Send false    // эхо в Serial команд send1(), send2(), send3()
#define Debug_Check false   // эхо в Serial команды check()

#define SEmax 32 // максимальная длина SysEx команды
class MIDIengine
{
public:
    // команды реализации
    MIDIengine();
    ~MIDIengine();
    void Check();
    virtual void AllNotesOff() {};
    virtual void AS_Check() {  digitalWrite(LED_ACTSEN_PIN, (millis() - lastMIDI) < 150); }; // проверка на Active Sensing и гашение светодиода
    // MIDI-команды (с номерами)
    virtual void NoteOff      (uint8_t note,                   uint8_t channel);    //***//
    virtual void NoteOn       (uint8_t note, uint8_t vel,      uint8_t channel);    //***//
    virtual void KeyPressure  (uint8_t note, uint8_t pressure, uint8_t channel)    { Serial.print(F("KeyPressure"));       Serial.println(pressure);  };
    virtual void ControlChange(uint8_t control, uint8_t value, uint8_t channel);    //***//
    virtual void ProgramChange(uint8_t program,                uint8_t channel)    { Serial.print(F("Program Change "));   Serial.println(program);  };
    virtual void AfterTouch   (uint8_t value,                  uint8_t channel)    { Serial.print(F("AfterTouch"));      Serial.println(value);  };
    virtual void PitchWheel   (uint8_t loByte, uint8_t hiByte, uint8_t channel);    //***//
    virtual void QuarterFrame (uint8_t value)                    { Serial.println(F("Quarter Frame"));   Serial.println(value);  };
    virtual void SongPointer  (uint8_t hiByte, uint8_t loByte)    { Serial.println(F("Sound Pointer"));   Serial.print(hiByte);  Serial.print(F(", "));  Serial.println(loByte); };
    virtual void SongSelect   (uint8_t song)                       { Serial.println(F("Song Select"));     Serial.println(song);  };
    virtual void TuneRequest()                                  { Serial.println(F("Tune Request"));  };
    virtual void SystemExclusive();                                //***//   (буфер SysEx заполнен) вызывается по команде F7 и любой другой команде кроме RealTime
    virtual void ActSenAction()                                 {}; // реакция на сообщение ActiveSensing - по умолчанию отсутствует
    void AddSysExBuffer(uint8_t data)                           {  if(SElength < SEmax) SEbuffer[SElength++] = data; };
    void send3(byte cmd, byte first, byte second);                //***//
    void send2(byte cmd, byte value);                             //***//
    void send1(byte cmd);                                         //***//
    virtual void send(byte value);
    void stat_ON()  { do_stat = true; t0 = micros(); }
    void stat_OFF() { do_stat = false; } 
    void printStat();
protected:
    void ErrorMessage(const __FlashStringHelper* str);
    void ErrorMessage(const __FlashStringHelper* str, uint8_t value);
    void statistic();      // процедура подсчета стстистики по периодичности вызовов check()
    // основные переменные
    bool actSenFlag;   // факт обнаружения в потоке команды ActiveSensing FE
    bool byte3flag;    // в 3-байтовой команде получен первый байт данных, ждем последний байт
    bool sysEx;        // режим приема SysEx - все байты данных складываются в буфер (либо игнорируются)
    bool checkData;    // получен код команды, требующий наличия данных - ждем данные
    uint8_t runState;  // последний запомненный код команды Runing Status
    uint8_t firstByte; // ячейка для хранения 1-го байта данных 3-байтовой команды
    uint32_t lastMIDI;   // время (мс) получения последней команды MIDI
    uint8_t SEbuffer[SEmax]; // буфер для SysEx сообщений
    uint8_t SElength; //    длина заполненной части SysEx
    uint32_t numSent; // количество посланных байтов (кроме Active Sensing)
    uint32_t numRcvd; // количество принятых байтов (кроме Active Sensing)
    uint8_t lastCmd; // последний код команды
    uint32_t num2; // количество 2-байтовых команд
    uint32_t num3; // количество 3-байтовых команд
    uint32_t last3bytesTime; // время последней посылки 3-байтовой команды
    uint16_t stat[256];    // интервал между вызовами MIDIengine::Check()    |
    uint32_t stat_counter; // количество вызовов check()                     |  для статистики
    uint32_t t0;           // время последнего вызова                        |
    bool do_stat;          // подсчитывать ли статистику                     |
};

inline void MIDIengine::NoteOff(uint8_t note, uint8_t channel) {
  Serial.print(F("MIDIengine::NoteOff ")); Serial.print(note);
  Serial.print(F(", channel: ")); Serial.print(channel);
  Serial.print(F(", Rcv: "));  Serial.print(numRcvd);
  Serial.print(F(", Snt: "));  Serial.print(numSent);
  Serial.print(F(", 2: "));    Serial.print(num2); 
  Serial.print(F(", 3: "));    Serial.println(num3); 
}

inline void MIDIengine::NoteOn(uint8_t note, uint8_t vel, uint8_t channel) {
  Serial.print(F("MIDIengine::NoteOn "));  Serial.print(note);   
  Serial.print(F(", "));       Serial.print(vel); 
  Serial.print(F(", channel: ")); Serial.print(channel);
  Serial.print(F(", Rcv: "));  Serial.print(numRcvd); 
  Serial.print(F(", Snt: "));  Serial.print(numSent);
  Serial.print(F(", 2: "));    Serial.print(num2); 
  Serial.print(F(", 3: "));    Serial.println(num3); 
}

inline void MIDIengine::ControlChange(uint8_t control, uint8_t value, uint8_t channel) {
  Serial.print(F("MIDIengine::Ctrl Ch ")); Serial.print(control);
  Serial.print(F(", "));       Serial.print(value); 
  Serial.print(F(", channel: ")); Serial.print(channel);
  Serial.print(F(", Rcv: "));  Serial.print(numRcvd);
  Serial.print(F(", Snt: "));  Serial.print(numSent);
  Serial.print(F(", 2: "));    Serial.print(num2); 
  Serial.print(F(", 3: "));    Serial.println(num3); 
}

inline void MIDIengine::PitchWheel(uint8_t loByte, uint8_t hiByte, uint8_t channel) {
  Serial.print(F("MIDIengine::Pitch: "));  Serial.print(hiByte);  
  Serial.print(F(", "));       Serial.print(loByte); 
  Serial.print(F(", channel: ")); Serial.print(channel);
  Serial.print(F(", Rcv: "));  Serial.print(numRcvd);
  Serial.print(F(", Snt: "));  Serial.print(numSent);
  Serial.print(F(", 2: "));    Serial.print(num2); 
  Serial.print(F(", 3: "));    Serial.println(num3); 
}

inline void MIDIengine::SystemExclusive() {
  if(SElength) { 
    for(byte i = 0; i < SElength; i++) { 
      Serial.print(SEbuffer[i], HEX); 
      Serial.print(' '); 
    } 
    Serial.println("F7"); 
    SElength = 0; 
    sysEx = false; 
  } 
}; // (буфер SysEx заполнен) вызывается по команде F7 и любой другой команде кроме RealTime

inline void MIDIengine::send3(byte cmd, byte first, byte second) {
  uint32_t currTime = millis();
  if ((cmd != lastCmd) || ((currTime - last3bytesTime) > MAX_RUNSTATE_INTERVAL)) { // 100 : -1.2%, 90 : -1.0%,  80 : 0.0%,  70 : +1.3%, 
    last3bytesTime = currTime;
    send(cmd);
    lastCmd = cmd; 
    numSent += 3; 
    num3++; 
  } else { 
    numSent += 2; 
    num2++; 
  }
  send(first);
  send(second);
  if (Debug_Send) {
    Serial.print(F("MIDIengine::send3: 0x"));   Serial.print(cmd, HEX); 
    Serial.print(F(", "));         Serial.print(first); 
    Serial.print(F(", "));         Serial.println(second); 
  }
}
      
inline void MIDIengine::send2(byte cmd, byte value) {
  send(cmd);
  send(value);
  lastCmd = cmd; 
  numSent += 2; 
  num2++; 
  if (Debug_Send) {
    Serial.print(F("MIDIengine::send2: 0x"));   Serial.print(cmd, HEX); 
    Serial.print(F(", "));         Serial.println(value); 
  }
}

inline void MIDIengine::send1(byte cmd) {  
  send(cmd);
  lastCmd = cmd; 
  if(cmd != 0xFE) {
    if (Debug_Send) {
      Serial.print(F("MIDIengine::send1: 0x"));   Serial.println(cmd, HEX); 
    }
  }
}

inline void MIDIengine::send(byte value) {
  MIDIport.write(value);  
}

#endif

файл MIDIfn.cpp:


#include <avr/pgmspace.h>
#include "MIDIfn.h"

#define DEBUG_1

const uint8_t lenComm[256] PROGMEM = {  // длина команда MIDI в байтах
// 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  00       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  10       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  20       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  30       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  40       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  50       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  60       Data
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //  70       Data
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  //  80  128  Note Off
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  //  90  144  Note On
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  //  A0  160  Polyphonic aftertouch(Note number, Pressure)
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  //  B0  176  Control change(Control number, Data)
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  //  C0  192  Programm change(Program number)
   2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  //  D0  208  Channel aftertouch(Pressure)
   3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  //  E0  224  Pitsh wheel(LSbyte, MSbyte)
 255,  2,  3,  2,  0,  0,  1,  1,  1,  0,  1,  1,  1,  0,  1,  1}; //  F0  240  SysEx...

MIDIengine::MIDIengine() {
    actSenFlag = false;   // факт обнаружения в потоке команды ActiveSensing FE
    byte3flag = false;    // в 3-байтовой команде получен первый байт данных, ждем последний байт
    sysEx = false;        // режим приема SysEx - все байты данных складываются в буфер (либо игнорируются)
    checkData = false;    // получен код команды, требующий наличия данных - ждем данные
    runState = 0;  // последний запомненный код команды Runing Status
    firstByte = 0; // ячейка для хранения 1-го байта данных 3-юайтовой команды
    lastMIDI = 0;   // время (мс) получения последней команды MIDI
    SElength = 0; //    длина заполненной части SysEx
    numSent = 0; // количество посланных байтов (кроме Active Sensing)
    numRcvd = 0; // количество принятых байтов (кроме Active Sensing)
    num2 = 0; // количество 2-байтовых команд
    num3 = 0; // количество 3-байтовых команд
    last3bytesTime = 0; // время последней посылки 3-байтовой команды
    do_stat = false;  // статистику не подсчитывать
}

MIDIengine::~MIDIengine() {
}

#define usConst 16

void MIDIengine::statistic() {
  stat_counter++;
  uint32_t t1 = micros();
  uint16_t dt = (t1-t0)/usConst;
  if(dt > 255)dt = 255;
  if (stat[dt] < 65535) stat[dt]++;
  t0 = t1;
}

void MIDIengine::printStat() {
  Serial.print(F("   MIDIengine.statistic, calls: "));
  Serial.println(stat_counter);
  Serial.print(F("stat (us): "));
  Serial.println(millis());
  for(int i = 0; i < 256; i++) {
    if(stat[i] > 0) {
      Serial.print(i*usConst);
      Serial.print('\t');
      Serial.println(stat[i]);
    }
  }
}

void MIDIengine::Check() {
    if (do_stat) statistic();
    if(!MIDIport.available()) { // эта ветка - отработка "холостого" опроса - MIDI данные не поступали
        if(actSenFlag) {
            if ((millis() - lastMIDI) > 150) {
                digitalWrite(LED_ACTSEN_PIN, LED_OFF);
                if ((millis() - lastMIDI) > 330) {
                    AllNotesOff();
                    actSenFlag = false;
                    ErrorMessage(F("MIDI disconnected"));
                }
            }
        }
    } else {     // эта ветка - разбор пришедшего по MIDI байта
        uint8_t current = MIDIport.read();
        if ((current != 0xFE) && Debug_Check) { 
          Serial.print(F("MIDI Check: "));
          Serial.println(current, (current & 0x80) ? HEX : DEC); 
        }
        lastMIDI = millis();
        digitalWrite(LED_ACTSEN_PIN, LED_ON);
        if(current == 0xFE) {                      //// обработка FE - Active Sensing
            actSenFlag = true;
            ActSenAction();
        } else { // все данные и команды и кроме Active Sensing
            numRcvd++;
            if(current & 0x80) { // разбор команды
                if(current >= 0xF0) {              //// обработка F0-FF - System Message
                    if(current >= 0xF8) {          //// обработка F8-FF - Real Time System Message
                        return; // сюда добавить при необходимости разбор Real Time команд
                    } else {                       //// обработка F0-F7 - System Common+Exclusive
                        if(checkData) ErrorMessage(F("   !!! ERROR !!! System Command while wait Data"));
                        if(current == 0xF0) {      //// обработка F0 - Start System Exclusive
                            sysEx = true;
                            return;
                        } else {                   //// обработка F1-F7
                            runState = current;
                            checkData = pgm_read_byte_near(lenComm + current) > 1;
                            byte3flag = 0;
                            SystemExclusive();
                        } // конец обработки F0-F7
                    }
                } else { // (* < F0)              //// обработка 80-EF - Channel Message
                    if(checkData) {
                      ErrorMessage(F("   !!! ERROR !!! Channel Command while wait Data current: "), current);
                      ErrorMessage(F("   !!! ERROR !!! Channel Command while wait Data byte3flag: "), byte3flag);
                      ErrorMessage(F("   !!! ERROR !!! Channel Command while wait Data checkData: "), checkData);
                      ErrorMessage(F("   !!! ERROR !!! Channel Command while wait Data runState: "), runState);
                      ErrorMessage(F("   !!! ERROR !!! Channel Command while wait Data firstByte: "),firstByte);
                    }
                    runState = current;
                    checkData = pgm_read_byte_near(lenComm + current) > 1;
                    byte3flag = 0;
                    SystemExclusive();
                } // конец обработки 80-EF - Channel Message
            } else { //  конец разбора команд, начало разбора данных
                if(sysEx) {                       //// обработка данных SysEx (F0)
                    AddSysExBuffer(current);
                } else { // ввод данных (кроме SysEx)
                    if(runState == 0) {
                        ErrorMessage(F("   !!!   ERROR !!! Data without Command"));
                    } else { // в runState содержится код команды
                        if(byte3flag) {           //// обработка последнего байта данных 3-байтовых команд (80,90,A0,B0,E0,F2)
                            checkData = 0;
                            byte3flag = 0;
                            byte rs = runState & 0xF0;
                            if(rs == 0x90) { //// обработка данных Note On (90)
                                if(current == 0) {
                                    NoteOff(firstByte, runState & 0x0F);
                                } else {
                                    NoteOn(firstByte, current, runState & 0x0F);
                                }
                            } else if(rs == 0x80) {
                                NoteOff(firstByte, runState & 0x0F);
                            } else if(rs == 0xE0) {
                                PitchWheel(firstByte, current, runState & 0x0F);
                            } else if(rs == 0xB0) {
                                ControlChange(firstByte, current, runState & 0x0F);
                            } else if(rs == 0xA0) {
                                KeyPressure(firstByte, current, runState & 0x0F);
                            } else if(runState == 0xF2) {
                                SongPointer(firstByte, current);
                                runState = 0;
                            } else ErrorMessage(F("   !!!   ERROR !!! Not Valid 3-bytes Command"), runState);
                        } else {                  //// обработка второго байта всех команд с данными (80,90,A0,B0,E0,F2;C0,D0,F1,F3)
                            if(runState < 0xC0) { // трехбайтовые команды: 80, 90, A0, B0
                                firstByte = current;
                                byte3flag = true;
                            } else {              // двухбайтовые команды, а также E0, F2
                                if(runState < 0xE0) { // двухбайтовые: C0, D0
                                    checkData = false;
                                    if((runState & 0xF0) == 0xC0) {
                                        ProgramChange(current, runState & 0x0F);
                                    } else {
                                        AfterTouch(current, runState & 0x0F);
                                    }
                                } else {            // все, что >=E0
                                    if(runState < 0xF0) { // только E0
                                        firstByte = current;
                                        byte3flag = true;
                                    } else {             // >=F0
                                        if(runState == 0xF2) { // только F2
                                            firstByte = current;
                                            byte3flag = true;
                                        } else { // остались только двухбайтовые команды
                                            checkData = false;
                                            if(runState == 0xF1) {
                                                runState = 0;
                                                QuarterFrame(current);
                                            } else {
                                                if(runState == 0xF3) {
                                                    runState = 0;
                                                    SongSelect(current);
                                                } else {
                                                    runState = 0;
                                                    ErrorMessage(F("   !!!   ERROR  !!!  Excess Data"));
                                                }
                                            } // if(runState == 0xF1)
                                        } // if(runState == 0xF2)
                                    } // if(runState < 0xF0)
                                } // if(runState < 0xE0)
                            }  // if(runState < 0xC0)
                        } // if(byte3flag)
                    } // конец разбора данных кроме SysEx, когде в runState - код команды
                } // конец разбора данных кроме SysEx
            }  // конец разбора данных
        } // else - все данные и команды кроме Active Sensing
    } // if(MIDIport.available())
}

void MIDIengine::ErrorMessage(const __FlashStringHelper* str) {
    Serial.println(str);
}

void MIDIengine::ErrorMessage(const __FlashStringHelper* str, uint8_t value) {
    Serial.print(str);
    Serial.println(value, HEX);
}

#undef DEBUG_1

эти файлы требуют заголовочного файла comm_ctrl.h, в котором определены константы. Имена констант говорящие, так что, думаю, восстановить их не будет большой проблемой, ну, например, для "MIDIport" следует взять "Serial1" или "Serial2".

 

PS. Почему-то рисунок ужался до 625 пикселей, хотя я специально подгонял его под разрешенные на форуме 780.

 

PPS. Добавлено 06.05: Описанная в следующем сообщении идея - параллельно с этим проектом разработать еще одно устройство, специализированное как раз для отладки MIDI устройств, оказалась вполне плодотворной: удалось обнаружить и исправить ошибки, которые не были замечены на протяжении порядка двух лет (примерно таков "возраст" опубликованных в настоящем сообщении модулей). В настоящий момент в сообщении помещен код с исправленными ошибками.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

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

Ну да ладно...

Параллельно оказывается, что не совсем хватает вспомогательных устройств для отладки. Так что созрела идея сделать реалтаймовый MIDI-плеер. В смысле, не тот, который воспроизводит MIDI-файлы, а тот, который воспроизводит подаваемое на вход реалтаймовое MIDI. Ну и заодно выполняет несколько вспомогательных функций: индицирует сигнал подключения (Active Sensing), показывает коды нескольких последних команд и т.п. Нет, все это можно сделать в виде программы на ПК (и даже такая программа уже есть), но как-то не очень удобно это все.