Главная » Программирование звука » Дорожки MIDI

0

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

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

Целые значения переменной длины

Из экономии места файлы MIDI используют целые значения переменной длины для хранения значений дельта-времени и других важных параметров. Это позволяет  наиболее  распространенные  значения  (такие  как  ноль)  хранить  в  одном  байте и одновременно обрабатывать значения размером вплоть до 32 бит.

B данном формате в каждом байте хранится 7 бит; самый старший показывает, является  ли  этот  байт  последним  (старший  бит  равен  нулю)  или  за  ним  следуют другие (старший бит равен единице).

Следующий  короткий  фрагмент  кода  считывает  целое  значение  переменной длины  из  потока  istream.  Он  сохраняет  количество  прочитанных  байтов  в  переменной *size.

Листинг 22.3. Чтение целых чисел переменной длины

static unsigned long ReadVarInt(istream &in, int *size) {

*size = 0;

unsigned long 1, retVal = 0;

do {

l = static_cast<unsigned long>(in.get()) & 255; (*size)++;

if (*size > 6) {

cerr << "Unterminated variable integer\n";

exit(1);

}

retVal = (retVal << 7) + (l & 0x7F);

} while (l & 0x80);

return retVal;

}

Относительное время

События   MIDI   происходят   в   определенные   моменты   времени.   Существуют два  способа  учета  подобной  информации.  Можно  хранить  абсолютное  время  возникновения   события   или   интервалы   времени   между   событиями.   Файлы   MIDI используют  последний  подход.  Каждое  событие  предваряется  числом,  показывающим  количество  тиков,  отделяющих  его  от  предыдущего  события.  (Более  подробно  вопросы  синхронизации  MIDI,  а  также  способы  определения  длительности тика описаны в разделе «Синхронизация MIDI» в данной главе.)

Точная  длительность  тика  зависит  от  формата  времени,  указанного  в  заголов-

ке, и может быть изменена специальным событием в пределах файла.

Чтение дорожек MIDI

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

Листинг 22.4. Чтение блока дорожки MIDI

void MidiRead::ReadTracks() {

int tracksRead = 0;

// Читаем  оставшиеся  блоки.

while (!_stream.eof() && (tracksRead < _numberTracks)) {

unsigned long chunkType = ReadIntMsb(_stream,4);

long bytesRemaining = ReadIntMsb(_stream,4);

if (_stream.eof()) continue; // Пропускаем  оставшуюся

// часть цикла.

// Если   блок не   относится

// к  блокам MTrk, пропускаем его.

if (chunkType != ChunkName("M",’T’,’r’,’k’)) {

char name[5];

name[0] = chunkType >> 24;     name[1] = chunkType >> 16; name[2] = chunkType >> 8;           name[3] = chunkType; name[4] = 0;

// Блок  не   распознан.

cerr << "Unrecognized chunk `" << name << ""\n"; SkipBytes(_stream,bytesRemaining);

continue;                 // Обратно на  начало цикла while.

}

tracksRead++;

// Читаем только  первую дорожку

// в  файле типа  2.

if ((_fileType == 2) & (tracksRead > 1)) { SkipBytes(_stream,bytesRemaining); continue;

}

MidiEvent *pLastEvent = 0;

while((bytesRemaining > 0)&&(!_stream.eof())) { MidiEvent *pEvent = new MidiEvent;

pEvent->track = tracksRead;

if(bytesRemaining < 0) {

cerr << "Contents of track chunk were too long.\n";

}

}

}

}

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

обновить заголовок списка), и второй когда событие приходит в середину спис-

ка (в этом случае обновляется другой указатель next).

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

Листинг 22.5. Вставка события MIDI в список

if ( !_events

|| (!pLastEvent && (_events->delay > pEvent->delay))) {

// Это   событие идет  в  титул  списка.

pEvent->next = _events;

_events = pLastEvent = pEvent;

} else {                 // He в начало списка.

if (!pLastEvent) {    // Переходим за  первый элемент  списка.

pLastEvent = _events;

pEvent->delay = _events->delay;

}

while(pLastEvent->next

// Пропускаем  события,

// которые  произошли раньше.

&& pLastEvent->next->delay <= pEvent->delay ) { pEvent->delay = pLastEvent->next->delay; pLastEvent = pLastEvent->next;

}

// "Вклеиваемся" в  список.

pEvent->next = pLastEvent->next;

pLastEvent->next = pEvent;

}

if (pEvent->next)        // Если   есть  последующие элементы,

// то  уменьшаем задержку.

pEvent->next->delay = pEvent->delay;

pLastEvent = pEvent;     // Последнее событие в  этой  дорожке.

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

По теме:

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