MIDI-box/MIDI-синтезатор
- Войдите на сайт для отправки комментариев
В режиме самоизоляции, наконец, дошли руки до конструкции, которую уже давно собирался сделать.
На данный момент, правда, она еще не закончена, но уже практически завершен корпус, сделаны почти все платы и есть понимание, что хочется от него получить. И основа софта уже была разработана в проекте http://arduino.ru/forum/proekty/analog-analogovogo-sintezatora , который все никак не соберусь закончить публикацию. Но, еще жду с Али нефиксируемые кнопки, а особенность сборки конструкции такова, что фиксируемые кнопки можно установить только после всего остального.
Тем более, на форуме уже неоднократно возникали вопросы MIDI-контроллеров.
Собственно, поэтому я и решил сделать у темы двойное название. А для меня лично это будет именно MIDI синтезатор, но с полноценным MIDI выходом. Т.е. его можно будет использовать и как MIDI-box, если к выходу синтезатора не подключать усилитель.
Итак, поехали...
В качестве звукового модуля синтезатора был использован Dream Blaster S1 на чипе SAM2195, который позиционируется в качестве основы профессиональных синтезаторов начального уровня (т.е. того, что принято продавать в музыкальных магазинах, а не магазинах игрушек). Куплен 2 года назад на Али примерно за $60.
Корпус выглядит так:
В качестве материала для него была использована бамбуковая разделочная доска и обрезки паркетной доски, оставшиеся после ремонта.
Заготовки для фальшпанели и лицевой панели:
Панели сделаны из пластика толщиной 1.5-1.7 мм, который в магазине назывался "Фартук для кухни".
На сегодня - все, завтра продолжу. А пока пойду собирать все это в корпус.
Работа несколько затормозилась из-за возникшей проблемы. Сама проблема описана здесь: 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 есть далеко не на всех ногах. Т.е. следующий этап работы - определиться с подключением энкодеров.
Следующий этап - плата энкодеров. Ручки энкодеров будут выходить на переднюю панель, соответственно, плата будет расположена параллельно панели.
Плата вырезана из макетки 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)); }Закончил последнюю плату. К ней будут подключаться 2 блока потенциометров по 8 шт, дисплей и MIDI-плата. В принципе, предусмотрен разъем и для платы синтезатора, но, скорее всего, я оставлю ее запитанной напрямую от стабилизатора.
Плата изготовлена из заготовки шилда для Меги.
После существенной доработки решил выделить меню, используемое в проекте, в отдельный проект: http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya
Видео: https://youtu.be/PyOdQMH7JxU
Работа продолжается.
Постепенно буду выкладывать код.
Обработка 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 устройств, оказалась вполне плодотворной: удалось обнаружить и исправить ошибки, которые не были замечены на протяжении порядка двух лет (примерно таков "возраст" опубликованных в настоящем сообщении модулей). В настоящий момент в сообщении помещен код с исправленными ошибками.
Что-то по факту проект уходит в не совсем ожиданную сторону - вместо универсального дивайса получается пульт, заточенный под конкретный синтезатор. В общем, читаю дэйташит на чип и реализую описанные там команды.
Ну да ладно...
Параллельно оказывается, что не совсем хватает вспомогательных устройств для отладки. Так что созрела идея сделать реалтаймовый MIDI-плеер. В смысле, не тот, который воспроизводит MIDI-файлы, а тот, который воспроизводит подаваемое на вход реалтаймовое MIDI. Ну и заодно выполняет несколько вспомогательных функций: индицирует сигнал подключения (Active Sensing), показывает коды нескольких последних команд и т.п. Нет, все это можно сделать в виде программы на ПК (и даже такая программа уже есть), но как-то не очень удобно это все.