Главная » Программирование звука » События MIDI

0

Событие  MIDI   это  пакет  данных,  который  определяет  некое  музыкальное действие, такое как нажатие или отпускание клавиши. Первый байт пакета 6aйm cmamyca, определяющий тип события и, в соответствующих случаях, канал. B байтах статуса старший бит всегда установлен в 1. Остальные байты данных, в которых  старший  бит  никогда  не  устанавливается.  Эта  отличительная  особенность весьма важна.

B  табл.  22.2  приводится  сводная  информация  по  сообщениям  MIDI,  в  фай-

лах  Standard  MIDI.  Заметьте,  что  события  с  кодами  от  0x80 до  0xEF используют младшие четыре бита для задания канала. События 0xF0, 0xF7 и 0xFF имеют специальное  назначение,  которое  я  поясню  позднее.  Также  обратите  внимание  на то,  что  эти  события  неидентичны  тем,  что  применяются  в  канальном  протоколе MIDI.  B  частности,  канальный  протокол  по-другому  использует  события  0xF7 и  0xFF и  определяет  несколько  других  байтов  статуса  для  событий  с  кодами выше 0xF0.

Соглашения по нумерации

По традиции каналы MIDI нумеруются от 1 до 16, а инструменты MIDI от 1

до 128. Однако  числовые коды, соответственно, варьируются в диапазоне от 0 до

15 и от 0 до 127.

При  взаимных  преобразованиях  численных  кодов  и  условных  значений  языка

MIDI необходимо добавить или вычесть единицу.

Приведу  конкретный  пример:  событие  MIDI  0xC0 0x00 выбирает  инструмент

1 (не 0) на канале 1 (не 0). Аналогичным образом, для выбора инструмента 128 на канале 16 вы посылаете событие 0xCF 0xFF.

Статус выполнения

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

Приведу  конкретный  пример.  Шестнадцатеричные  значения  0x90 0x3C 0x40

формируют событие включения для ноты 0x3C (среднее До) со средним значением

Таблица 22.2. Коды событий MIDI, используемые в файлах Standard MIDI

Код                                       Описание

(шестнадцатеричный)

8c nn vv                                 Выключение ноты nn со скоростью vv на канале с

9c nn vv                                 Включение ноты nn со скоростью vv на канале с

Ac nn vv                                Полифонический ключ воздействия послекасания

На w изменяет воздействие (обычно вибрато) на ноту nn (которая уже проигрывается) на канале с

Bc mm ss                              Меняет режим канала с с mm на vv

Cc ii                                       Изменение программы

Выбирает звучание инструмента ii для канала с

Dc vv                                     Изменяет воздействие на канале

Изменяет на и/ воздействие для всех нот, проигрываемых на канале с

Ec ff cc                                  Регулятор изменения высоты тона

Изменяет высоту всех нот, проигрываемых на канале с

по определенному соотношению; ft содержит младшие 7 бит,

cc старший бит

F0 длина данных                 Системное эксклюзивное сообщение (SOX). Длина целое значение переменной длины, определяющее длину последующих данных.

F7 длина данных                 Специальное системное эксклюзивное сообщение

Длина целое значение переменной длины, определяющее длину последующих данных.

FF tt длина данных              Метасобытие типа tt

Длина целое значение переменной длины, определяющее длину последующих данных.

скорости. Если следующее событие начинается с 0x40, то, поскольку это байт данных, вы заново используете значение статуса 0x90. Это соответствует еще одному событию включения  ноты. Пропуск общих значений статуса позволяет обрабатывать  аккорды  быстрее.  Данный  метод  широко  используется  в  файлах  формата Standard MIDI, так как позволяет экономить место на диске.

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

Управление событиями MIDI

Особенностью  записанных  в  формате  MIDI  данных  является  присущая  им  зависимость  синхронизации  от  того,  как  данные  считываются.  B  частности,  файлы  MIDI  часто  хранятся  как  несколько  независимых  дорожек.  Хотя  события  на каждой дорожке находятся в хронологическом порядке, проигрываемый поток событий представляет собой комбинацию всех этих дорожек. K тому же события на одной  дорожке  (такие  как  изменения  темпа)  влияют  на  воспроизведение  событий на других дорожках.

B  результате  прежде  чем  проигрывать  файл  первого  типа,  необходимо  считать в память все события. C этой целью я предлагаю использовать один связанный список событий MIDI.

Листинг 22.6. Переменные списка событий MIDI

private:

MidiEvent *_events;       // Список всех  событий в песне

MidiEvent *_currentEvent; // Обрабатываемое текущее  событие

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

Некоторые  специальные  события  MIDI  могут  содержать  произвольные  объемы  данных,  поэтому  я  описал  дополнительную  структуру,  предназначенную  для хранения таких данных в случае необходимости.

Листинг 22.7. Структура события MIDI

struct MidiExtendedEventData {

long length; AudioByte *data;

// Конструктор  и  деструктор.

MidiExtendedEventData() { data = 0; }

~MidiExtendedEventData() { if (data) delete [] data; }

};

struct MidiEvent { MidiEvent *next;

unsigned long delay;      // Задержка

// относительно  предыдущего события.

unsigned char track;      // Количество  дорожек

// для этого  события.

unsigned char status;    // Байт статуса  события.  unsigned char data[2];   // Данные   события  MIDI. MidiExtendedEventData *metaData;     // Область  для  длинных

// данных типа  системных

// сообщений  и  т.д.

MidiEvent() {            // Конструктор.

next = 0;

metaData = 0;

delay = track = status = 0;

}

~MidiEvent() {           // Деструктор. if(metaData)

delete metaData;

}

};

Чтение событий MIDI

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

Листинг 22.8. Чтение события MIDI

{ static const char eventLength[] = {

2, 2,

2, 2,

2,

2, 2,

2,

2,

2,

2,

2, 2,

2,

2,

2,

//

0x80

0x8F

2, 2,

2, 2,

2,

2, 2,

2,

2,

2,

2,

2, 2,

2,

2,

2,

//

0x90

0x9F

2, 2,

2, 2,

2,

2, 2,

2,

2,

2,

2,

2, 2,

2,

2,

2,

//

0xA0

0xAF

2, 2,

2, 2,

2,

2, 2,

2,

2,

2,

2,

2, 2,

2,

2,

2,

//

0xB0

0xBF

1, 1,

1, 1,

1,

1, 1,

1,

1,

1,

1,

1, 1,

1,

1,

1,

//

0xC0

0xCF

1, 1,

1, 1,

1,

1, 1,

1,

1,

1,

1,

1, 1,

1,

1,

1,

//

0xD0

0xDF

2, 2,

2, 2,

2,

2, 2,

2,

2,

2,

2,

2, 2,

2,

2,

2,

//

0xE0

0xEF

0, 0,

2, 1,

0,

0, 0,

0,

0,

0,

0,

0, 0,

0,

0,

0,

//

0xF0

0xFF

};

int sizeRead;

pEvent->delay = ReadVarInt(_stream,&sizeRead); bytesRemaining = sizeRead;    // Учитываем  дельта-время, int dataRead = 0;               // Количество прочитанных на

// текущий  момент  байтов.

int byte = _stream.get();

bytesRemaining-—;               // Учитываем этот  байт.

if (byte >= 0x80) {             // Это   новый   статус.

pEvent->status = byte;

} else {                        // Непрерывный  статус.

pEvent->status = pLastEvent->status;   // Заново используем

// предыдущее

// значение статуса.

pEvent->data[dataRead++] = byte; // Это   первый байт  данных.

}

while(dataRead < eventLength[pEvent->status 0x80]) { pEvent->data[dataRead++] = _stream.get(); bytesRemaining–;

}

}

Эксклюзивные системные сообщения

Событие  0xF0 используется  для  передачи  ряда  специальных  команд,  называемых   эксклюзивными   системными   сообщениями   (System-Exclusive   messages   или sysex events). Их общий формат приведен в табл. 22.3.

Эти  события  обрабатываются  в  файлах  Standard  MIDI  не  так,  как  в  канальном  протоколе.  B  файле  Standard  MIDI,  как  показано  в  табл.  22.2,  за  статусным значением   0xF0 следует   поле   длины   и   соответствующее   количество   данных. B  результате  эксклюзивное  системное  сообщение  может  содержать  любое  количество байтов.

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

B  MIDI-файле  событие  0xF7 представляет  собой  средство  организации  переходов,  благодаря  которым  мы  можем  включать  в  файл  любые  двоичные  данные. Внутренняя  структура  аналогична  структуре  системного  эксклюзивного  сообщения  0xF0.  При  посылке  файла  MIDI  на  синтезатор  в  последний  должна  сбрасываться  область  данных  события  0xF7.  Сам  статусный  байт  0xF7 не  передается. Это  удобно,  если  вы  работаете  с  синтезаторами,  которые  предъявляют  требования  по  синхронизации  эксклюзивных  сообщений.  Ha  таких  синтезаторах  мы  можем использовать одно событие 0xF0 для хранения первой части данного

Таблица 22.3. Формат системного эксклюзивного сообщения

Байты    Описание

1             Статусный байт 0xF0 (начало эксклюзивного системного сообщения SOX)

m            Длина последующих данных (только в файле MIDI), хранящаяся как целое переменной длины

1-3          Идентификатор производителя.

Если первый байт равен нулю, это 3-байтный идентификатор; в противном случае -

однобайтный. 0x7D зарезервировано для экспериментального

использования, 0x7E применяется для стандартных сообщений не в реальном времени, 0x7F для стандартных сообщений в реальном времени.

n             Байты данных.

Как и во всех событиях MIDI, все байты данных в системном эксклюзивном сообщении (включая описанный выше идентификатор производителя) имеют установленный в ноль старший бит.

1             Статусный байт 0xF7 (конец эксклюзивного системного сообщения EOX)

Хотя любой статусный байт, за исключением событий реального времени, отмечает конец системного эксклюзивного сообщения, рекомендуется включать значение

0xF7 после каждого такого сообщения.

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

B  канальном  протоколе  статусное  значение  0xF7 не  имеет  ассоциированных данных  и  не  производит  никакого  действия.  Оно,  по  традиции,  используется  для пометки конца системного эксклюзивного сообщения, хотя любое статусное значение  могло  бы  применяться  в  подобных  целях.  B  файлы  MIDI  принято  включать байт 0xF7 как последний эксклюзивного сообщения 0xF0. Если же сообщение разбито  на  несколько  событий  файла  MIDI  с  использованием  механизма  перехода

0xF7, этот завершающий байт 0xF7 включается только в последнее событие.

Листинг 22.9. Чтение эксклюзивного системного сообщения MIDI

// Читаем  эксклюзивное сообщение из  потока  _stream:

// * записываем событие в  структуру  pEvent.

// * соответствующим образом  уменьшаем значение  переменной

// bytesRemaining.

if (  (pEvent->status == 0xF0)

|| (pEvent->status == 0xF7) ) {

int sizeRead;

unsigned long msgLength = ReadVarInt(_stream,&sizeRead);

bytesRemaining-= sizeRead;

pEvent->metaData = new MidiExtendedEventData;

pEvent->metaData->length = msgLength;

pEvent->metaData->data = new AudioByte[msgLength];

_stream.read(reinterpret_cast<char *>(pEvent->metaData->data) , msgLength);

bytesRemaining-= msgLength;

}

Метасобытия

Музыкальная   партитура     это   не   просто   список   синхронизированных   нот. Файлы  MIDI  должны  также  содержать  информацию  о  ключах  (абсолютной  высоте  звукоряда),  тональностях  и  об  авторских  правах.  Для  записи  такой  информации  в  файлах  Standard  MIDI  используются  специальные  метасобытия  (meta events).  Множество  метасобытий  находится  в  начале  дорожки,  хотя  они  способны  появляться  в  любом  месте.  Например,  многие  композиции  меняют  тональность  в  середине  фрагмента,  поэтому  по  файлу  может  быть  распределено  множество метасобытий смены тональности.

По существующему соглашению, в файле первого типа первая дорожка зарезервирована   специально   для   определенных   метасобытий.   Например,   информация о тональности, размере и темпе хранится только на первой дорожке. Многие другие метасобытия, в том числе задающие имя дорожки и ее порядковый номер, могут размещаться  только  в  начале  дорожки.  Метасобытия  не  используются  в  канальном протоколе  MIDl  (хотя  эксклюзивные  сообщения  были  добавлены  в  связующий протокол для дублирования многих из этих возможностей).

Способ  хранения  метасобытий  внутри  файла  очень  похож  на  способ  хранения эксклюзивных  сообщений.  Метасобытие  начинается  не  с  одного  байта  0xF0 или

0xF7, а с 0xFF и байта типа, за которыми следуют длина и данные.

Листинг 22.10. Чтение метасобытия MIDI

// Читаем  метасобытие  из потока _stream:

// * записываем событие в  структуру  pEvent;

// * соответствующим образом  уменьшаем значение  переменной

// bytesRemaining.

if (pEvent->status == 0xFF) {

pEvent->data[0] = _stream.get();    // Тип  метасобытия.

bytesRemaining -= 1;

int sizeRead;

unsigned long msgLength = ReadVarInt(_stream,&sizeRead);

bytesRemaining -= sizeRead;

pEvent->metaData = new MidiExtendedEventData;

pEvent->metaData->length = msgLength;

pEvent->metaData->data = new AudioByte[msgLength+l];

_stream.read(reinterpret_cast<char *>(pEvent->metaData->data), msgLength) ,·

bytesRemaining -= msgLength;

pEvent->metaData->data[msgLength] = 0;     // Добавляем  нуль-

// терминатор.

}

Метасобытие порядкового номера дорожки (тип 0)

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

Текстовые метасобытия (типы от 1до 15)

Метасобытия  со  значением  типа  от  1  до  15  используются  для  хранения  текстовых  комментариев.  Как  и  любые  другие  они  хранятся  в  файле  в  виде  0xFF type len data (тип     длина     данные).   B   табл.   22.4   перечисляются   определенные в настоящее время текстовые метасобытия.

Листинг 22.11. Просмотр метасобытия

if(pEvent->data[0] == 3) {

// Дорожка  …

cerr << "Track " << static_cast<int>(pEvent->track) << ": ";

cerr << reinterpret_cast<char *>(pEvent->metaData->data) << "\n";

} else if (pEvent->data[0] < 16) {

const char *textPrefix[] = { "",

// Комментарий:  Авторские  права: Название дорожки:

"Comment: ", "Copyright: ", "Track Name: ",

Таблица 22.4. Текстовые метасобытия

Метасобытие                                 Описание

1                                                              Комментарий

2                                                              Уведомление об авторских правах

3                                                              Название дорожки

4                                                              Название инструмента

5                                                              Текст песни

6                                                              Маркер

7                                                              Место реплики

8-15                                                         Не определено

// Название инструмента  Слова: Маркер: "Instrument Name ", "Lyric: ", "Marker: ",

// Место   реплики:  Текст:…

"Cue Point: ", "Text: ", "Text: ", "Text: ",

"Text: ", "Text: ", "Text: ", "Text: ", "Text: "};

cerr << textPrefix[ pEvent->data[0] ];

cerr << reinterpret_cast<char *>(pEvent->metaData->data) << "\n";

}

Метасобытие конца дорожки (тип 47)

Метасобытие типа 47 помечает конец дорожки MIDI и не содержит данных.

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

Метасобытие задания темпа (тип 81)

Метасобытие   типа   81   задает   скорость   воспроизведения.   Заголовок   MIDI определяет  длительность  тика  в  единицах,  равных  длине  четверти  ноты  MIDl. Данные это три байта, которые определяют количество микросекунд на четверть ноты MIDI. (Смотри ниже информацию по синхронизации MIDI.)

Музыкальный размер (тип 88)

Метасобытие  задания  музыкального  размера  содержит  четыре  байта.  Первые два   определяют   музыкальный   размер,   например,   3/4   или   6/8.   Числитель   хранится  непосредственно,  знаменатель  как  степень  двойки.  Например,  размер  6/8 представляется двумя байтами: 6 и 3 (23  = 8). Третий байт определяет количество тактовых импульсов MIDI на один щелчок метронома. Тактовый импульс MIDI

составляет  1/24  четверти  ноты  MIDI,  а  длительность  четверти  ноты  MIDI  определяется  в  метасобытиях  задания  темпа.  Четвертый  байт  определяет  способ  записи  музыки.  Четверть  ноты  MIDI  может  не  соответствовать  четверти  ноты  в  традиционном   обозначении     она   обозначает   количество   записываемых   тридцать вторых долей ноты в одной четверти ноты MIDI.

Метасобытие задания тональности (тип 89)

Первый   байт   показывает   количество   диезов   (если   значение   положительное) или  бемолей  (если  значение  отрицательное),  второй  показывает,  имеем  ли  мы дело с мажорной (0) или с минорной (1) тональностью. До мажор, например, будет представлено как 0 0.

Секвенсорные метасобытия (тип 127)

Секвенсорные  метасобытия  позволяют  производителям  добавлять  нестандартные  данные  в  файл  MIDI.  Bo  избежание  путаницы  первый  байт  идентифицирует производителя   (если   первый   байт   равен   нулю,   то   производителя   определяют первые   три   байта).   Microsoft,   например,   использует   секвенсорное   метасобытие сидентификатором   производителя  0  0  65  для  пометки  МРС-совместимых  файлов MIDI.

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии