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) заняты под "крутилки".
Ниже код, которым проверял работу платы и энкодеров. Скажу сразу - код одноразовый, т.е. только для проверки платы, а не для постоянной работы. Но, учитывая, что адаптация "кода от Леонида Ивановича" для Меги мне не попадалась, решил все-таки опубликовать.
001
void
encoders_Init() {
002
pinMode(53,INPUT_PULLUP);
003
pinMode(52,INPUT_PULLUP);
004
pinMode(51,INPUT_PULLUP);
005
pinMode(50,INPUT_PULLUP);
006
pinMode(10,INPUT_PULLUP);
007
pinMode(11,INPUT_PULLUP);
008
pinMode(12,INPUT_PULLUP);
009
pinMode(13,INPUT_PULLUP);
010
pinMode(14,INPUT_PULLUP);
011
pinMode(15,INPUT_PULLUP);
012
PCIFR = PCIFR;
// обнуляем все прерывания, которые могли иметь место ранее
013
PCICR |= (1 << PCIE0) | (1 << PCIE1);
// [PCIE0, PCIE2] разрешить прерывание
014
PCMSK0 = 0xFF;
// используются все 8 первых PCINT с PCINT0 по PCINT7
015
PCMSK1 |= (1 << PCINT9) | (1<<PCINT10);
// [PCINT0..PCINT23] выбрать вход на котором сработает прерывание
016
pinMode(29,INPUT_PULLUP);
017
pinMode(27,INPUT_PULLUP);
018
pinMode(28,INPUT_PULLUP);
019
pinMode(26,INPUT_PULLUP);
020
pinMode( 9,INPUT_PULLUP);
021
}
022
023
volatile
int
enc_B[5];
024
int
counter0 = 0;
025
int
counter1 = 0;
026
027
void
setup
(){
028
Serial
.begin(115200);
029
/ encoders_Init();
030
Serial
.println(
"Start"
);
031
Serial
.print(PB0);
032
Serial
.print(
'\t'
);
033
Serial
.print(PB1);
034
Serial
.print(
'\t'
);
035
Serial
.print(PB2);
036
Serial
.print(
'\t'
);
037
Serial
.print(PB3);
038
Serial
.print(
'\t'
);
039
Serial
.print(PB4);
040
Serial
.print(
'\t'
);
041
Serial
.print(PB5);
042
Serial
.print(
'\t'
);
043
Serial
.print(PB6);
044
Serial
.print(
'\t'
);
045
Serial
.println(PB7);
046
Serial
.print(PCINT0);
047
Serial
.print(
'\t'
);
048
Serial
.print(PCINT1);
049
Serial
.print(
'\t'
);
050
Serial
.print(PCINT2);
051
Serial
.print(
'\t'
);
052
Serial
.print(PCINT3);
053
Serial
.print(
'\t'
);
054
Serial
.print(PCINT4);
055
Serial
.print(
'\t'
);
056
Serial
.print(PCINT5);
057
Serial
.print(
'\t'
);
058
Serial
.print(PCINT6);
059
Serial
.print(
'\t'
);
060
Serial
.println(PCINT7);
061
Serial
.print(PCIE0);
062
Serial
.print(
'\t'
);
063
Serial
.print(PCIE1);
064
Serial
.print(
'\t'
);
065
Serial
.println(PCIE2);
066
Serial
.println(PCMSK0, HEX);
067
delay(5000);
068
}
069
070
ISR(PCINT0_vect){
071
counter0++;
072
const
byte
bPC_msk[4] = {(1 << PCINT0) | (1<<PCINT1), (1 << PCINT2) | (1<<PCINT3),
073
(1 << PCINT4) | (1<<PCINT5), (1 << PCINT6) | (1<<PCINT7)};
074
const
byte
bPC_pA[4] = {PB0, PB2, PB4, PB6};
075
const
byte
bPC_pB[4] = {PB1, PB3, PB5, PB7};
076
static
byte
EncPrev[4] = {0,0,0,0};
//предыдущее состояние энкодера
077
static
byte
EncPrevPrev[4] = {0,0,0,0};
//пред-предыдущее состояние энкодера
078
for
(
byte
i = 0; i < 4; i++) {
079
byte
EncCur = 0;
080
if
(PCMSK0 & (bPC_msk[i])) {
081
/
if
(!(PINB & (1 << bPC_pA[i]))) { EncCur = 1;}
//опрос фазы A энкодера
082
if
(!(PINB & (1 << bPC_pB[i]))) { EncCur |= 2;}
//опрос фазы B энкодера
083
if
(EncCur != EncPrev[i])
//если состояние изменилось,
084
{
085
if
(EncPrev[i] == 3 &&
//если предыдущее состояние 3
086
EncCur != EncPrevPrev[i] )
//и текущее и пред-предыдущее не равны,
087
{
088
if
(EncCur == 2)
//если текущее состояние 2,
089
enc_B[i]++;
//шаг вверх
090
else
//иначе
091
enc_B[i]--;
//шаг вниз
092
}
093
EncPrevPrev[i] = EncPrev[i];
//сохранение пред-предыдущего состояния
094
EncPrev[i] = EncCur;
//сохранение предыдущего состояния
095
}
096
}
097
}
098
}
099
100
ISR(PCINT1_vect){
101
counter1++;
102
const
byte
bPC_msk = (1 << PCINT9) | (1<<PCINT10);
103
const
byte
bPC_pA = PJ0;
104
const
byte
bPC_pB = PJ1;
105
static
char
EncPrev = 0;
//предыдущее состояние энкодера
106
static
char
EncPrevPrev = 0;
//пред-предыдущее состояние энкодера
107
char
EncCur = 0;
108
if
(!(PINJ & (1 << bPC_pA))) { EncCur = 1;}
//опрос фазы A энкодера
109
if
(!(PINJ & (1 << bPC_pB))) { EncCur |= 2;}
//опрос фазы B энкодера
110
if
(EncCur != EncPrev)
//если состояние изменилось,
111
{
112
if
(EncPrev == 3 &&
//если предыдущее состояние 3
113
EncCur != EncPrevPrev )
//и текущее и пред-предыдущее не равны,
114
{
115
if
(EncCur == 2)
//если текущее состояние 2,
116
enc_B[4]++;
//шаг вверх
117
else
//иначе
118
enc_B[4]--;
//шаг вниз
119
}
120
EncPrevPrev = EncPrev;
//сохранение пред-предыдущего состояния
121
EncPrev = EncCur;
//сохранение предыдущего состояния
122
}
123
}
124
125
126
127
void
loop
() {
128
Serial
.print(counter0);
129
Serial
.print(
'\t'
);
130
Serial
.print(enc_B[0]);
131
Serial
.print(
'\t'
);
132
Serial
.print(enc_B[1]);
133
Serial
.print(
'\t'
);
134
Serial
.print(enc_B[2]);
135
Serial
.print(
'\t'
);
136
Serial
.print(enc_B[3]);
137
Serial
.print(
'\t'
);
138
Serial
.print(enc_B[4]);
139
Serial
.print(
'\t'
);
140
Serial
.print(counter1);
141
Serial
.print(
"\t\t"
);
142
Serial
.print(digitalRead(29));
143
Serial
.print(
'\t'
);
144
Serial
.print(digitalRead(27));
145
Serial
.print(
'\t'
);
146
Serial
.print(digitalRead(28));
147
Serial
.print(
'\t'
);
148
Serial
.print(digitalRead(26));
149
Serial
.print(
'\t'
);
150
Serial
.println(digitalRead(9));
151
}
Закончил последнюю плату. К ней будут подключаться 2 блока потенциометров по 8 шт, дисплей и MIDI-плата. В принципе, предусмотрен разъем и для платы синтезатора, но, скорее всего, я оставлю ее запитанной напрямую от стабилизатора.
Плата изготовлена из заготовки шилда для Меги.
После существенной доработки решил выделить меню, используемое в проекте, в отдельный проект: http://arduino.ru/forum/proekty/menyu-dlya-dvukhstrochnogo-displeya
Видео: https://youtu.be/PyOdQMH7JxU
Работа продолжается.
Постепенно буду выкладывать код.
Обработка MIDI сообщений:
файл MIDIfn.h:
001
#ifndef MIDIFN_H // базовый класс MIDI
002
#define MIDIFN_H
003
004
#include <Arduino.h>
005
#include "comm_ctrl.h"
006
007
#define MAX_RUNSTATE_INTERVAL 82 // максимальное время действия RunState, мс // 75 : +0.6%, 76 : 0.0%, 88 : 0.0%, 89 : -0.1%,
008
009
#define Debug_Send false // эхо в Serial команд send1(), send2(), send3()
010
#define Debug_Check false // эхо в Serial команды check()
011
012
#define SEmax 32 // максимальная длина SysEx команды
013
class
MIDIengine
014
{
015
public
:
016
// команды реализации
017
MIDIengine();
018
~MIDIengine();
019
void
Check();
020
virtual
void
AllNotesOff() {};
021
virtual
void
AS_Check() { digitalWrite(LED_ACTSEN_PIN, (millis() - lastMIDI) < 150); };
// проверка на Active Sensing и гашение светодиода
022
// MIDI-команды (с номерами)
023
virtual
void
NoteOff (uint8_t note, uint8_t channel);
//***//
024
virtual
void
NoteOn (uint8_t note, uint8_t vel, uint8_t channel);
//***//
025
virtual
void
KeyPressure (uint8_t note, uint8_t pressure, uint8_t channel) {
Serial
.print(F(
"KeyPressure"
));
Serial
.println(pressure); };
026
virtual
void
ControlChange(uint8_t control, uint8_t value, uint8_t channel);
//***//
027
virtual
void
ProgramChange(uint8_t program, uint8_t channel) {
Serial
.print(F(
"Program Change "
));
Serial
.println(program); };
028
virtual
void
AfterTouch (uint8_t value, uint8_t channel) {
Serial
.print(F(
"AfterTouch"
));
Serial
.println(value); };
029
virtual
void
PitchWheel (uint8_t loByte, uint8_t hiByte, uint8_t channel);
//***//
030
virtual
void
QuarterFrame (uint8_t value) {
Serial
.println(F(
"Quarter Frame"
));
Serial
.println(value); };
031
virtual
void
SongPointer (uint8_t hiByte, uint8_t loByte) {
Serial
.println(F(
"Sound Pointer"
));
Serial
.print(hiByte);
Serial
.print(F(
", "
));
Serial
.println(loByte); };
032
virtual
void
SongSelect (uint8_t song) {
Serial
.println(F(
"Song Select"
));
Serial
.println(song); };
033
virtual
void
TuneRequest() {
Serial
.println(F(
"Tune Request"
)); };
034
virtual
void
SystemExclusive();
//***// (буфер SysEx заполнен) вызывается по команде F7 и любой другой команде кроме RealTime
035
virtual
void
ActSenAction() {};
// реакция на сообщение ActiveSensing - по умолчанию отсутствует
036
void
AddSysExBuffer(uint8_t data) {
if
(SElength < SEmax) SEbuffer[SElength++] = data; };
037
void
send3(
byte
cmd,
byte
first,
byte
second);
//***//
038
void
send2(
byte
cmd,
byte
value);
//***//
039
void
send1(
byte
cmd);
//***//
040
virtual
void
send(
byte
value);
041
void
stat_ON() { do_stat =
true
; t0 = micros(); }
042
void
stat_OFF() { do_stat =
false
; }
043
void
printStat();
044
protected
:
045
void
ErrorMessage(
const
__FlashStringHelper* str);
046
void
ErrorMessage(
const
__FlashStringHelper* str, uint8_t value);
047
void
statistic();
// процедура подсчета стстистики по периодичности вызовов check()
048
// основные переменные
049
bool
actSenFlag;
// факт обнаружения в потоке команды ActiveSensing FE
050
bool
byte3flag;
// в 3-байтовой команде получен первый байт данных, ждем последний байт
051
bool
sysEx;
// режим приема SysEx - все байты данных складываются в буфер (либо игнорируются)
052
bool
checkData;
// получен код команды, требующий наличия данных - ждем данные
053
uint8_t runState;
// последний запомненный код команды Runing Status
054
uint8_t firstByte;
// ячейка для хранения 1-го байта данных 3-байтовой команды
055
uint32_t lastMIDI;
// время (мс) получения последней команды MIDI
056
uint8_t SEbuffer[SEmax];
// буфер для SysEx сообщений
057
uint8_t SElength;
// длина заполненной части SysEx
058
uint32_t numSent;
// количество посланных байтов (кроме Active Sensing)
059
uint32_t numRcvd;
// количество принятых байтов (кроме Active Sensing)
060
uint8_t lastCmd;
// последний код команды
061
uint32_t num2;
// количество 2-байтовых команд
062
uint32_t num3;
// количество 3-байтовых команд
063
uint32_t last3bytesTime;
// время последней посылки 3-байтовой команды
064
uint16_t stat[256];
// интервал между вызовами MIDIengine::Check() |
065
uint32_t stat_counter;
// количество вызовов check() | для статистики
066
uint32_t t0;
// время последнего вызова |
067
bool
do_stat;
// подсчитывать ли статистику |
068
};
069
070
inline
void
MIDIengine::NoteOff(uint8_t note, uint8_t channel) {
071
Serial
.print(F(
"MIDIengine::NoteOff "
));
Serial
.print(note);
072
Serial
.print(F(
", channel: "
));
Serial
.print(channel);
073
Serial
.print(F(
", Rcv: "
));
Serial
.print(numRcvd);
074
Serial
.print(F(
", Snt: "
));
Serial
.print(numSent);
075
Serial
.print(F(
", 2: "
));
Serial
.print(num2);
076
Serial
.print(F(
", 3: "
));
Serial
.println(num3);
077
}
078
079
inline
void
MIDIengine::NoteOn(uint8_t note, uint8_t vel, uint8_t channel) {
080
Serial
.print(F(
"MIDIengine::NoteOn "
));
Serial
.print(note);
081
Serial
.print(F(
", "
));
Serial
.print(vel);
082
Serial
.print(F(
", channel: "
));
Serial
.print(channel);
083
Serial
.print(F(
", Rcv: "
));
Serial
.print(numRcvd);
084
Serial
.print(F(
", Snt: "
));
Serial
.print(numSent);
085
Serial
.print(F(
", 2: "
));
Serial
.print(num2);
086
Serial
.print(F(
", 3: "
));
Serial
.println(num3);
087
}
088
089
inline
void
MIDIengine::ControlChange(uint8_t control, uint8_t value, uint8_t channel) {
090
Serial
.print(F(
"MIDIengine::Ctrl Ch "
));
Serial
.print(control);
091
Serial
.print(F(
", "
));
Serial
.print(value);
092
Serial
.print(F(
", channel: "
));
Serial
.print(channel);
093
Serial
.print(F(
", Rcv: "
));
Serial
.print(numRcvd);
094
Serial
.print(F(
", Snt: "
));
Serial
.print(numSent);
095
Serial
.print(F(
", 2: "
));
Serial
.print(num2);
096
Serial
.print(F(
", 3: "
));
Serial
.println(num3);
097
}
098
099
inline
void
MIDIengine::PitchWheel(uint8_t loByte, uint8_t hiByte, uint8_t channel) {
100
Serial
.print(F(
"MIDIengine::Pitch: "
));
Serial
.print(hiByte);
101
Serial
.print(F(
", "
));
Serial
.print(loByte);
102
Serial
.print(F(
", channel: "
));
Serial
.print(channel);
103
Serial
.print(F(
", Rcv: "
));
Serial
.print(numRcvd);
104
Serial
.print(F(
", Snt: "
));
Serial
.print(numSent);
105
Serial
.print(F(
", 2: "
));
Serial
.print(num2);
106
Serial
.print(F(
", 3: "
));
Serial
.println(num3);
107
}
108
109
inline
void
MIDIengine::SystemExclusive() {
110
if
(SElength) {
111
for
(
byte
i = 0; i < SElength; i++) {
112
Serial
.print(SEbuffer[i], HEX);
113
Serial
.print(
' '
);
114
}
115
Serial
.println(
"F7"
);
116
SElength = 0;
117
sysEx =
false
;
118
}
119
};
// (буфер SysEx заполнен) вызывается по команде F7 и любой другой команде кроме RealTime
120
121
inline
void
MIDIengine::send3(
byte
cmd,
byte
first,
byte
second) {
122
uint32_t currTime = millis();
123
if
((cmd != lastCmd) || ((currTime - last3bytesTime) > MAX_RUNSTATE_INTERVAL)) {
// 100 : -1.2%, 90 : -1.0%, 80 : 0.0%, 70 : +1.3%,
124
last3bytesTime = currTime;
125
send(cmd);
126
lastCmd = cmd;
127
numSent += 3;
128
num3++;
129
}
else
{
130
numSent += 2;
131
num2++;
132
}
133
send(first);
134
send(second);
135
if
(Debug_Send) {
136
Serial
.print(F(
"MIDIengine::send3: 0x"
));
Serial
.print(cmd, HEX);
137
Serial
.print(F(
", "
));
Serial
.print(first);
138
Serial
.print(F(
", "
));
Serial
.println(second);
139
}
140
}
141
142
inline
void
MIDIengine::send2(
byte
cmd,
byte
value) {
143
send(cmd);
144
send(value);
145
lastCmd = cmd;
146
numSent += 2;
147
num2++;
148
if
(Debug_Send) {
149
Serial
.print(F(
"MIDIengine::send2: 0x"
));
Serial
.print(cmd, HEX);
150
Serial
.print(F(
", "
));
Serial
.println(value);
151
}
152
}
153
154
inline
void
MIDIengine::send1(
byte
cmd) {
155
send(cmd);
156
lastCmd = cmd;
157
if
(cmd != 0xFE) {
158
if
(Debug_Send) {
159
Serial
.print(F(
"MIDIengine::send1: 0x"
));
Serial
.println(cmd, HEX);
160
}
161
}
162
}
163
164
inline
void
MIDIengine::send(
byte
value) {
165
MIDIport.write(value);
166
}
167
168
#endif
файл MIDIfn.cpp:
001
#include <avr/pgmspace.h>
002
#include "MIDIfn.h"
003
004
#define DEBUG_1
005
006
const
uint8_t lenComm[256] PROGMEM = {
// длина команда MIDI в байтах
007
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
008
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 00 Data
009
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 10 Data
010
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 20 Data
011
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 30 Data
012
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 40 Data
013
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 50 Data
014
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 60 Data
015
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 70 Data
016
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// 80 128 Note Off
017
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// 90 144 Note On
018
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// A0 160 Polyphonic aftertouch(Note number, Pressure)
019
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// B0 176 Control change(Control number, Data)
020
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// C0 192 Programm change(Program number)
021
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// D0 208 Channel aftertouch(Pressure)
022
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// E0 224 Pitsh wheel(LSbyte, MSbyte)
023
255, 2, 3, 2, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1};
// F0 240 SysEx...
024
025
MIDIengine::MIDIengine() {
026
actSenFlag =
false
;
// факт обнаружения в потоке команды ActiveSensing FE
027
byte3flag =
false
;
// в 3-байтовой команде получен первый байт данных, ждем последний байт
028
sysEx =
false
;
// режим приема SysEx - все байты данных складываются в буфер (либо игнорируются)
029
checkData =
false
;
// получен код команды, требующий наличия данных - ждем данные
030
runState = 0;
// последний запомненный код команды Runing Status
031
firstByte = 0;
// ячейка для хранения 1-го байта данных 3-юайтовой команды
032
lastMIDI = 0;
// время (мс) получения последней команды MIDI
033
SElength = 0;
// длина заполненной части SysEx
034
numSent = 0;
// количество посланных байтов (кроме Active Sensing)
035
numRcvd = 0;
// количество принятых байтов (кроме Active Sensing)
036
num2 = 0;
// количество 2-байтовых команд
037
num3 = 0;
// количество 3-байтовых команд
038
last3bytesTime = 0;
// время последней посылки 3-байтовой команды
039
do_stat =
false
;
// статистику не подсчитывать
040
}
041
042
MIDIengine::~MIDIengine() {
043
}
044
045
#define usConst 16
046
047
void
MIDIengine::statistic() {
048
stat_counter++;
049
uint32_t t1 = micros();
050
uint16_t dt = (t1-t0)/usConst;
051
if
(dt > 255)dt = 255;
052
if
(stat[dt] < 65535) stat[dt]++;
053
t0 = t1;
054
}
055
056
void
MIDIengine::printStat() {
057
Serial
.print(F(
" MIDIengine.statistic, calls: "
));
058
Serial
.println(stat_counter);
059
Serial
.print(F(
"stat (us): "
));
060
Serial
.println(millis());
061
for
(
int
i = 0; i < 256; i++) {
062
if
(stat[i] > 0) {
063
Serial
.print(i*usConst);
064
Serial
.print(
'\t'
);
065
Serial
.println(stat[i]);
066
}
067
}
068
}
069
070
void
MIDIengine::Check() {
071
if
(do_stat) statistic();
072
if
(!MIDIport.available()) {
// эта ветка - отработка "холостого" опроса - MIDI данные не поступали
073
if
(actSenFlag) {
074
if
((millis() - lastMIDI) > 150) {
075
digitalWrite(LED_ACTSEN_PIN, LED_OFF);
076
if
((millis() - lastMIDI) > 330) {
077
AllNotesOff();
078
actSenFlag =
false
;
079
ErrorMessage(F(
"MIDI disconnected"
));
080
}
081
}
082
}
083
}
else
{
// эта ветка - разбор пришедшего по MIDI байта
084
uint8_t current = MIDIport.read();
085
if
((current != 0xFE) && Debug_Check) {
086
Serial
.print(F(
"MIDI Check: "
));
087
Serial
.println(current, (current & 0x80) ? HEX : DEC);
088
}
089
lastMIDI = millis();
090
digitalWrite(LED_ACTSEN_PIN, LED_ON);
091
if
(current == 0xFE) {
//// обработка FE - Active Sensing
092
actSenFlag =
true
;
093
ActSenAction();
094
}
else
{
// все данные и команды и кроме Active Sensing
095
numRcvd++;
096
if
(current & 0x80) {
// разбор команды
097
if
(current >= 0xF0) {
//// обработка F0-FF - System Message
098
if
(current >= 0xF8) {
//// обработка F8-FF - Real Time System Message
099
return
;
// сюда добавить при необходимости разбор Real Time команд
100
}
else
{
//// обработка F0-F7 - System Common+Exclusive
101
if
(checkData) ErrorMessage(F(
" !!! ERROR !!! System Command while wait Data"
));
102
if
(current == 0xF0) {
//// обработка F0 - Start System Exclusive
103
sysEx =
true
;
104
return
;
105
}
else
{
//// обработка F1-F7
106
runState = current;
107
checkData = pgm_read_byte_near(lenComm + current) > 1;
108
byte3flag = 0;
109
SystemExclusive();
110
}
// конец обработки F0-F7
111
}
112
}
else
{
// (* < F0) //// обработка 80-EF - Channel Message
113
if
(checkData) {
114
ErrorMessage(F(
" !!! ERROR !!! Channel Command while wait Data current: "
), current);
115
ErrorMessage(F(
" !!! ERROR !!! Channel Command while wait Data byte3flag: "
), byte3flag);
116
ErrorMessage(F(
" !!! ERROR !!! Channel Command while wait Data checkData: "
), checkData);
117
ErrorMessage(F(
" !!! ERROR !!! Channel Command while wait Data runState: "
), runState);
118
ErrorMessage(F(
" !!! ERROR !!! Channel Command while wait Data firstByte: "
),firstByte);
119
}
120
runState = current;
121
checkData = pgm_read_byte_near(lenComm + current) > 1;
122
byte3flag = 0;
123
SystemExclusive();
124
}
// конец обработки 80-EF - Channel Message
125
}
else
{
// конец разбора команд, начало разбора данных
126
if
(sysEx) {
//// обработка данных SysEx (F0)
127
AddSysExBuffer(current);
128
}
else
{
// ввод данных (кроме SysEx)
129
if
(runState == 0) {
130
ErrorMessage(F(
" !!! ERROR !!! Data without Command"
));
131
}
else
{
// в runState содержится код команды
132
if
(byte3flag) {
//// обработка последнего байта данных 3-байтовых команд (80,90,A0,B0,E0,F2)
133
checkData = 0;
134
byte3flag = 0;
135
byte
rs = runState & 0xF0;
136
if
(rs == 0x90) {
//// обработка данных Note On (90)
137
if
(current == 0) {
138
NoteOff(firstByte, runState & 0x0F);
139
}
else
{
140
NoteOn(firstByte, current, runState & 0x0F);
141
}
142
}
else
if
(rs == 0x80) {
143
NoteOff(firstByte, runState & 0x0F);
144
}
else
if
(rs == 0xE0) {
145
PitchWheel(firstByte, current, runState & 0x0F);
146
}
else
if
(rs == 0xB0) {
147
ControlChange(firstByte, current, runState & 0x0F);
148
}
else
if
(rs == 0xA0) {
149
KeyPressure(firstByte, current, runState & 0x0F);
150
}
else
if
(runState == 0xF2) {
151
SongPointer(firstByte, current);
152
runState = 0;
153
}
else
ErrorMessage(F(
" !!! ERROR !!! Not Valid 3-bytes Command"
), runState);
154
}
else
{
//// обработка второго байта всех команд с данными (80,90,A0,B0,E0,F2;C0,D0,F1,F3)
155
if
(runState < 0xC0) {
// трехбайтовые команды: 80, 90, A0, B0
156
firstByte = current;
157
byte3flag =
true
;
158
}
else
{
// двухбайтовые команды, а также E0, F2
159
if
(runState < 0xE0) {
// двухбайтовые: C0, D0
160
checkData =
false
;
161
if
((runState & 0xF0) == 0xC0) {
162
ProgramChange(current, runState & 0x0F);
163
}
else
{
164
AfterTouch(current, runState & 0x0F);
165
}
166
}
else
{
// все, что >=E0
167
if
(runState < 0xF0) {
// только E0
168
firstByte = current;
169
byte3flag =
true
;
170
}
else
{
// >=F0
171
if
(runState == 0xF2) {
// только F2
172
firstByte = current;
173
byte3flag =
true
;
174
}
else
{
// остались только двухбайтовые команды
175
checkData =
false
;
176
if
(runState == 0xF1) {
177
runState = 0;
178
QuarterFrame(current);
179
}
else
{
180
if
(runState == 0xF3) {
181
runState = 0;
182
SongSelect(current);
183
}
else
{
184
runState = 0;
185
ErrorMessage(F(
" !!! ERROR !!! Excess Data"
));
186
}
187
}
// if(runState == 0xF1)
188
}
// if(runState == 0xF2)
189
}
// if(runState < 0xF0)
190
}
// if(runState < 0xE0)
191
}
// if(runState < 0xC0)
192
}
// if(byte3flag)
193
}
// конец разбора данных кроме SysEx, когде в runState - код команды
194
}
// конец разбора данных кроме SysEx
195
}
// конец разбора данных
196
}
// else - все данные и команды кроме Active Sensing
197
}
// if(MIDIport.available())
198
}
199
200
void
MIDIengine::ErrorMessage(
const
__FlashStringHelper* str) {
201
Serial
.println(str);
202
}
203
204
void
MIDIengine::ErrorMessage(
const
__FlashStringHelper* str, uint8_t value) {
205
Serial
.print(str);
206
Serial
.println(value, HEX);
207
}
208
209
#undef DEBUG_1
эти файлы требуют заголовочного файла comm_ctrl.h, в котором определены константы. Имена констант говорящие, так что, думаю, восстановить их не будет большой проблемой, ну, например, для "MIDIport" следует взять "Serial1" или "Serial2".
PS. Почему-то рисунок ужался до 625 пикселей, хотя я специально подгонял его под разрешенные на форуме 780.
PPS. Добавлено 06.05: Описанная в следующем сообщении идея - параллельно с этим проектом разработать еще одно устройство, специализированное как раз для отладки MIDI устройств, оказалась вполне плодотворной: удалось обнаружить и исправить ошибки, которые не были замечены на протяжении порядка двух лет (примерно таков "возраст" опубликованных в настоящем сообщении модулей). В настоящий момент в сообщении помещен код с исправленными ошибками.
Что-то по факту проект уходит в не совсем ожиданную сторону - вместо универсального дивайса получается пульт, заточенный под конкретный синтезатор. В общем, читаю дэйташит на чип и реализую описанные там команды.
Ну да ладно...
Параллельно оказывается, что не совсем хватает вспомогательных устройств для отладки. Так что созрела идея сделать реалтаймовый MIDI-плеер. В смысле, не тот, который воспроизводит MIDI-файлы, а тот, который воспроизводит подаваемое на вход реалтаймовое MIDI. Ну и заодно выполняет несколько вспомогательных функций: индицирует сигнал подключения (Active Sensing), показывает коды нескольких последних команд и т.п. Нет, все это можно сделать в виде программы на ПК (и даже такая программа уже есть), но как-то не очень удобно это все.