Программирование подсистемы MIDI

Евгений Музыченко

Краткие сведения об аппаратном устройстве MIDI-интерфейса

Основные черты и понятия интерфейса и подсистемы MIDI

     Партитура и звук

     Каналы и тембры

     Номера и звучание тембров

     Кэширование загружаемых тембров

     Реальные и виртуальные синтезаторы

     Внешние и внутренние синтезаторы

     Синхронные и асинхронные драйверы

     Управление громкостью
     Сходство подсистем MIDI и Audio/Wave

     Структура MIDI-сообщений

     Режим Running Status

     Отличие потока MIDI-данных от потока цифрового звука

     Временная шкала и темп потока вывода

     Непосредственный и буферизованный ввод/вывод сообщений

     Автоматизированный потоковый вывод в подсистеме MIDI

     Подготовка буферов данных

     Зависание нот

     Программные события и уведомление о них

     Дуплекс

     Поддержка нескольких клиентов

     Служба переназначения устройств, каналов, тембров и клавиш

     Номера MIDI-устройств

     Идентификаторы (ключи) открытых устройств

     Имена интерфейсных функций

Общая схема взаимодействия программы и подсистемы MIDI

Средства разработки, включаемые файлы и библиотеки

Структуры, используемые в интерфейсе

     Структуры MIDIINCAPS и MIDIOUTCAPS

     Структура MIDIHDR

     Структура упакованного короткого сообщения

     Структура MIDIEVENT

     Структура MMTIME

     Структуры MIDIPROPTEMPO и MIDIPROPTIMEDIV

     Структура MIDISTRMBUFFVER

Уведомления, передаваемые программе подсистемой MIDI

     Типы событий, о которых сообщается приложению

Набор интерфейсных функций подсистемы MIDI

     Перечень интерфейсных функций

     Значения, возвращаемые интерфейсными функциями

Описание интерфейсных функций

     Макрос MEVT_EVENTTYPE — выделение кода MIDI-события

     Макрос MEVT_EVENTPARM — выделение параметров MIDI-события

     GetNumDevs — запрос количества устройств

     GetDevCaps — запрос параметров и возможностей устройств

     Open — открывание устройства или потока вывода

     Close — закрытие устройства или потока

     PrepareHeader — подготовка буфера данных и его заголовка

     UnprepareHeader — отмена подготовительных действий для буфера

     ShortMsg — вывод короткого сообщения

     AddBuffer/LongMsg/Out — передача буфера данных устройству или потоку

     Stop/Pause — остановка ввода сообщений/воспроизведения потока

     Start/Restart — запуск ввода/воспроизведения потока

     Reset/Stop — сброс устройства или остановка потока

     Position — запрос текущей позиции потока

     SetVolume — установка громкости звука на выходе синтезатора

     GetVolume — запрос текущей громкости воспроизведения

     Property — запрос/установка параметров потока

     Connect/Disconnect — установка/разрыв виртуального соединения

     CachePatches/CacheDrumPatches — управление набором тембров (патчей)

     GetID — запрос номера устройства по ключу

     GetErrorText — запрос текстового сообщения об ошибке по коду

     Message — передача сообщения драйверу устройства

     CallbackProc — функция приложения, вызываемая при уведомлении

Недостатки подсистемы MME/MIDI

Пример программы, использующий интерфейс MIDI

 

Вместе с подсистемой собственно цифрового звука (Audio, Wave) в состав звукового интерфейса Windows входит также подсистема MIDI (Musical Instrument Digital Interface — цифровой интерфейс музыкальных инструментов). Подсистема MIDI имеет два основных назначения: управление работой музыкальных синтезаторов — как реальных, так и виртуальных, реализованных в виде имитирующей программы, и цифровое представление музыкальных партитур для редактирования и обработки.

Идея MIDI состоит в унификации связи между разными электронными музыкальными инструментами (ЭМИ), которые сейчас часто называют просто синтезаторами. С каждым действием исполнителя: нажатием/отпусканием клавиши, педали, кнопки, поворотом рукоятки и т.п. сопоставлен определенный тип события (event), однозначно характеризующего это действие. По каждому событию формируется сообщение (message), передаваемое по цифровому последовательному интерфейсу (порту). Каждый инструмент, получив сообщение, воспроизводит его, как будто это событие произошло при непосредственном воздействии на него.

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

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

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

MIDI-интерфейс содержит два уровня унификации — аппаратный, описывающий правила соединения устройств и передачи сигналов, и протокольный, описывающий протокол взаимодействия и виды передаваемых сообщений. Эта статья посвящена программированию передачи MIDI-сообщений в среде Windows и касается аппаратной и протокольной сторон интерфейса лишь по минимуму. Полное описание того и другого можно найти в справочной литературе и в различных статьях, например в моей статье «Описание интерфейса МIDI».

Внешне подсистема MIDI очень похожа на подсистему цифрового звука Audio/Wave и содержит практически тот же набор функций и структур. Основная разница заключается в том, что подсистема Audio/Wave имеет дело с потоками отсчетов цифрового звука, а подсистема MIDI — с потоками сообщений.

Описание подсистемы MIDI есть во встроенной системе помощи любой современной среды программирования, а также в MSDN SDK. Microsoft поддерживает в Internet справочную систему MSDN Online, подсистема описана в разделе http://msdn.microsoft.com/library/psdk/multimed/midi_1wv3.htm.

Краткие сведения об аппаратном устройстве MIDI-интерфейса

Традиционный MIDI-интерфейс представляет собой универсальный асинхронный приемопередатчик (Universal Asynchonous Transmitter/Receiver, UART), похожий на обычный последовательный порт компьютера и также ориентированный на обмен байтами. Разница заключается в том, что в MIDI жестко фиксирована скорость обмена данными — 31 250 бит/сек и для передачи сигналов используется принцип «токовая петля» — в отличие от передачи напряжением, как в последовательных портах на IBM PC. Разъемы входа (In) и выхода (Out) разделены и представляют собой круглые 5-контактные розетки типа DIN-5, аналогичные разъемам отечественной звуковой аппаратуры 70-80-х годов.

Для соединения устройств используется симметричный кабель с одинаковыми разъемами-вилками на концах. Принцип токовой петли и низкая скорость обмена позволяют значительно увеличить длину соединительного кабеля — в стандарте MIDI она может достигать 15 метров, хотя на практике работают кабели со значительно большей длиной. В MIDI используется каскадный способ соединения устройств: один кабель может соединять только два устройства, но каждое устройство должно иметь специальный выход ретранслятора (Thru), на который копируются все данные, полученные устройством на входе. Таким образом, несколько устройств могут быть соединены в цепочку, либо более сложным способом, образуя MIDI-сеть.

Описанная разница со схемой последовательного порта IBM PC не позволяет подключать MIDI-устройства к компьютеру через этот порт. Даже если воспользоваться схемой преобразования сигналов, скорость передачи 31 250 бит/с в стандартном порту получить невозможно, кроме отдельных интерфейсных микросхем, специально разработанных для совместимости с MIDI. Большинство типовых звуковых карт содержит в себе MIDI-интерфейс, однако сигналы в нем передаются в стандарте ТТЛ, а не токовой петли, и вдобавок используются разные типы разъемов. Поэтому для подключения MIDI-устройств к компьютеру необходим специальный адаптер — либо в виде самостоятельной интерфейсной платы, либо в виде переходника от разъема Joystick/MIDI типа DB-15 к разъемам MIDI In/Out/Thru типа DIN-5.

Со стороны Windows-приложения любой MIDI-интерфейс видится, как пара системных MIDI-портов — ввода (In) и вывода (Out). Приход сообщений на вход интерфейса приводит к их появлению в системном порту In, откуда их считывает приложение, а передача приложением сообщений в системный порт Out приводит к их выдаче на выход интерфейса. Однако поведение драйвера в Windows таково, что в случае коротких (1-3 байта) сообщений обмен ведется только полными сообщениями, а не отдельными байтами. Иначе говоря, приложение не получит уведомления до тех пор, пока на вход интерфейса не поступят все составляющие сообщение байты.

В начало

В начало

Основные черты и понятия интерфейса и подсистемы MIDI

Партитура и звук

Очень важным свойством MIDI-протокола является то, что в его сообщениях ни в каком виде не содержится готовый звук. Содержится только информация о том, когда и какие ноты должны звучать, какими тембрами и с какими параметрами они должны быть исполнены и т.п. Непосредственно звук создается только на выходе синтезатора и очень сильно зависит от его типа, структуры, настройки и прочих факторов. Если поток цифрового звука полностью определяет звучание, то поток MIDI-сообщений определяет лишь основную схему исполнения музыкального произведения. Точное соответствие звучания возможно лишь на полностью совместимых синтезаторах, что в профессиональной области встречается лишь внутри линий одного производителя. Одну и ту же партитуру простейший FM-синтезатор звуковой карты может исполнить «брякающе», несложный таблично-волновой — «плоско», «невыразительно», а профессиональный или просто подходящий — «объемно», «живо», «выпукло» и т.п.

В начало

В начало

Каналы и тембры

Поскольку музыкальная партитура обычно состоит из нескольких независимых партий различных инструментов, в MIDI-протоколе предусмотрено разделение основных сообщений по виртуальным каналам (channels). Одно MIDI-устройство может поддерживать до 16 каналов (1..16), в каждом из которых может быть установлен собственный тембр звучания (часто называемый инструментом (instrument), или патчем (patch)).

Сообщения о начале/конце звучания ноты, смене тембра и параметров звучания имеют в своем составе номер канала, к которому они относятся; такие сообщения называются канальными. Остальные сообщения, предназначенные инструменту в целом, называются системными.

Каждый канал может работать в двух режимах: мелодическом (melodic) и ударном (percussion). В первом случае номер клавиши, передаваемый в сообщениях, определяет номер хроматического полутона («До» первой октавы имеет номер 60). Во втором случае номер клавиши определяет произвольный (обычно ударный) звук фиксированной высоты; можно сказать, что по клавиатуре «раскладывается» множество различных звуков, каждый из которых извлекается определенной клавишей.

Канал 10 по умолчанию обычно находится в ударном режиме, остальные каналы — в мелодическом.

В начало

В начало

Номера и звучание тембров

Существуют различные стандарты на соответствие условных номеров тембров их звучанию. Минимальный стандарт — General MIDI (GM) — определяет лишь базовые свойства 128 мелодических и 37 ударных тембров, однако реализации стандарта очень сильно различаются. Стандарт GS задает более жесткие требования на соответствие номеров и звучаний, а стандарт XG обеспечивает практически полное соответствие. На данный момент XG является единственным мировым стандартом, обеспечивающим практически абсолютно точное воспроизведение партитур на различных моделях синтезаторов.

В начало

В начало

Кэширование загружаемых тембров

На заре развития звуковых адаптеров с таблично-волновыми синтезаторами остро стояла проблема минимизации объема оперативной памяти, используемой для загрузки тембров (patches). Тогда был предложен способ кэширования — в память синтезатора загружался лишь тот набор тембров, который использовался в очередной композиции, а при переходе к следующей композиции содержимое кэша обновлялось — частично или целиком. Этот способ не получил распространения, и единственным известным адаптером, который его использует, остался Gravis Ultrasound (GUS). Тем не менее в подсистеме MIDI была реализована поддержка кэширования, и приложения должны использовать этот механизм, если планируется работа с синтезаторами GUS.

В начало

В начало

Реальные и виртуальные синтезаторы

Музыкальный инструмент, управляемый по MIDI-протоколу, может быть как аппаратным (реальным), сделанным в виде физического устройства, так и программным (виртуальным), выполненным в виде специального драйвера. Реальный синтезатор воспринимает MIDI-сообщения, переданные по физическим линиям связи, отрабатывает их и преобразует в звук на физическом выходе. Виртуальный синтезатор воспринимает сообщения, переданные его виртуальному системному MIDI-порту, создает на их основе поток цифрового звука, который выводится через обычный звуковой адаптер (Audio/Wave) либо записывается непосредственно на диск (файл типа Wav).

Виртуальные синтезаторы могут либо имитировать работу реальных (Roland VSC, Yamaha S-YG, S-YXG), либо воплощать новую, физически не существующую модель синтезатора (Reality, GigaSampler, Generator и т.п.). Большинство виртуальных синтезаторов имеет только входной порт (видимый со стороны приложения как выходной, Out), поскольку они не имеют клавиатуры и не могут сами генерировать сообщения.

В начало

В начало

Внешние и внутренние синтезаторы

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

В начало

В начало

Синхронные и асинхронные драйверы

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

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

В начало

В начало

Управление громкостью

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

В начало

В начало

Сходство подсистем MIDI и Audio/Wave

Подсистемы MIDI и Audio/Wave очень схожи. Обе являются программными интерфейсами с устройствами ввода/вывода (In/Out) и содержат примерно одинаковый набор функций. Многие функции двух подсистем различаются только префиксами (midi/wave), в остальном же логика их работы полностью совпадает. Подсистема MIDI точно так же поддерживает асинхронный обмен данными с различными видами уведомлений; схемы взаимодействия приложения с драйвером при этом почти в точности совпадают.

Как и подсистема Audio, подсистема MIDI в Windows 95/98 является 16-разрядной и наследуется непосредственно из Windows 3.x. В Windows NT/2000 все подсистемы являются 32-разрядными.

Описание программного интерфейса с базовой звуковой подсистемой читайте в статье «Низкоуровневое программирование звука в Windows», опубликованной в CD-приложении к Компьютер Пресс № 6’2000 г.

В начало

В начало

Структура MIDI-сообщений

MIDI-сообщения делятся на короткие (1-3 байта) и длинные (более трех байтов). Длинные представляют собой особый вид сообщений — системные исключительные (System Exclusive, или SysEx), которые имеют переменную длину.

Поскольку MIDI-протокол создавался для связи между устройствами, которые могут быть соединены и разъединены в любой момент, структура сообщений сделана самосинхронизирующейся, чтобы приемное устройство всегда могло выделить начало сообщения. Первый байт каждого сообщения, называемый также байтом состояния (status byte) имеет установленный (единица) старший бит, во всех остальных байтах (байты данных, data bytes) старший бит сброшен (нуль).

Байты состояния канальных сообщений содержат в младшей тетраде номер канала (нумерация с нуля).

Сообщения типа SysEx имеют байт состояния F0. Для индикации конца SysEx используется байт F7 (End Of Exclusive — EOX). Все байты данных, расположенные между ними, считаются принадлежащими этому сообщению и имеют нулевой старший бит.

В начало

В начало

Режим Running Status

Когда в потоке одно за другим следуют канальные сообщения одного типа, относящиеся к одному каналу, их байты состояния совпадают. В этом случае для сокращения плотности потока может использоваться режим, называемый Running Status, при котором повторяющиеся байты состояния не передаются. Приемное устройство, получив байт состояния канального сообщения, должно запомнить его. Если после приема всех байтов данных сообщения очередной байт не является байтом состояния (старший бит не установлен) — это означает, что подразумевается точно такой же байт состояния, а принятый байт является первым байтом данных сообщения.

Например, сообщения о нажатии/отпускании клавиш в канале 2 имеют байт состояния 92 и содержат два байта данных. Пара таких сообщений кодируется, как 92 3C FF, 92 3C 00, а при использовании Running Status второй байт 92 не передается. При смене типа сообщения или канала вновь передается один байт состояния, после чего идет серия байтов данных.

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

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

В начало

В начало

Отличие потока MIDI-данных от потока цифрового звука

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

Скорость MIDI-потока на много (полтора порядка) ниже, чем потока цифрового звука, а большинство сообщений имеет длину 1-3 байта. Это позволяет передавать и принимать сообщения по одному, а не упаковывать их в блоки, как это делается в подсистеме цифрового звука. Однако сообщения SysEx, длина которых может достигать нескольких десятков килобайт, всегда передаются и принимаются в виде блоков.

В начало

В начало

Временная шкала и темп потока вывода

Поскольку каждое MIDI-сообщение привязано к определенному моменту времени, а поток сообщений неоднороден, требуется опорная временная сетка, относительно которой ведется отсчет времени. В MIDI для этого используется понятие тика (tick) — минимального кванта времени, на который могут отстоять друг от друга MIDI-события. Длительность тика определяется путем деления либо длительности четвертной доли (quarter note), либо независимого временного интервала. В первом случае получается относительное деление, обычно используемое в музыке, во втором — абсолютное, используемое при синхронизации с изображением. На практике используются значения от 48 до 480 тиков на четвертную долю, наиболее часто — 96 и 120.

В соответствии со спецификацией формата стандартного MIDI-файла (SMF) 1.0, параметры длительности тика задаются 16-разрядным словом (тип short int). Положительные значения задают количество тиков в четвертной доле, отрицательные — непосредственно частоту тиков в формате SMPTE (Society of Motion Picture and Television Engineers — сообщество инженеров кино и телевидения). Отрицательное значение формируется из двух байтов по формуле MAKEWORD (TicksPerFrame, -FramesPerSec), где TicksPerFrame — количество тиков в кадре, FramesPerSec — количество кадров в секунде (обычно 24, 25, 29 или 30). Отрицательное значение старшего байта дает отрицательное значение всего слова.

Если задано относительное деление, то частота тиков в секунду рассчитывается, исходя из темпа и количества тиков в четвертной доле. Темп потока вывода задается длительностью четвертной доли в микросекундах. Например, для размера 4/4 темп 120 представляется значением 500 000.

В начало

В начало

Непосредственный и буферизованный ввод/вывод сообщений

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

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

Длинные сообщения принимаются и выводятся при помощи очереди буферов, передаваемых драйверу, как и в подсистеме Audio. Если к моменту прихода длинного сообщения в очереди драйвера нет ни одного буфера — сообщение отвергается.

В начало

В начало

Автоматизированный потоковый вывод в подсистеме MIDI

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

Каждое событие в потоке вывода представляется специальным описателем, содержащим вместе с кодом события и другими параметрами значение интервала времени (Delta Time), чтобы можно было определить, в какой момент его нужно обработать.

В отношении потока вывода применяются те же операции, что и для потока цифрового звука — запуск, остановка, возобновление, запрос позиции, сброс.

Надо заметить, что поток вывода в подсистеме MIDI — структура более высокого уровня, нежели собственно поток MIDI-сообщений. Поток вывода состоит из мета–событий, из которых обычно большинство, но не все, представляют собой MIDI-сообщения.

В начало

В начало

Подготовка буферов данных

Подготовка (фиксация в памяти) буферов данных перед их передачей подсистеме требуется только для устройств ввода и вывода, так как только они обслуживаются низкоуровневым драйвером, который обычно работает с аппаратными прерываниями и DMA. Потоки вывода обслуживаются подсистемой на более высоком уровне секвенсора MCI, который обменивается с буферами данных только в режиме приложения (user mode). Поэтому для буферов данных потока вывода не требуется какой-либо подготовки перед их передачей функции вывода.

В начало

В начало

Зависание нот

В структуре MIDI-протокола сообщения о начале (нажатии) и окончании (отпускании) звучания нот являются полностью независимыми, и каждая нота оформляется парой сообщений о нажатии/отпускании. При переполнении буфера приемного устройства, ошибках передачи, временном разрыве связи некоторые сообщения могут искажаться и пропадать; в их числе могут оказаться и сообщения об отпускании нот. В этом случае синтезатор, получивший ранее сообщения о нажатии этих нот продолжает генерировать соответствующие звучания до тех пор, пока не будет выключен, не получит сообщения об отпускании каждой из нажатых нот, либо не получит в соответствующем канале сообщения «All Notes Off» (отключить все ноты). Это трехбайтовое канальное сообщение, которое кодируется, как Bn 7B 00, где n — номер канала.

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

Для предотвращения зависания нот рекомендуется вызывать функцию сброса и посылать во все каналы сообщения «All Notes Off» каждый раз перед закрыванием устройства или потока вывода.

В начало

В начало

Программные события и уведомление о них

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

В начало

В начало

Дуплекс

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

В начало

В начало

Поддержка нескольких клиентов

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

Такое поведение драйвера очень неудобно при использовании программных панелей управления синтезатором совместно с музыкальным редактором (секвенсором). Существуют дополнительные MIDI-драйверы, реализующие поддержку нескольких клиентов для любых портов — MultiMid и Hubi's Loopback. После установки любого из них для всех установленных MIDI-портов создаются многоклиентные копии.

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

Короткие сообщения, посылаемые параллельно с потоком вывода, адресуются непосредственно потоку (в функции ShortMsg задается ключ потока). Сообщения должны быть полностью сформированными — режим Running Status в этом случае запрещен.

В начало

В начало

Служба переназначения устройств, каналов, тембров и клавиш

Для MIDI-портов, как и для звуковых, Windows содержит службу переназначения — MIDI Mapper. Существует понятие стандартного системного устройства ввода и стандартного системного устройства вывода, которые задаются в закладке MIDI-формы свойств мультимедиа. Приложение может запросить работу либо с конкретным устройством, либо со стандартным системным — в последнем случае нужное устройство определяет служба переназначения.

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

И, наконец, MIDI Mapper может масштабировать параметры сообщений громкости в канале (Main Volume), проходящих через него. Значения параметров умножаются на заданный коэффициент, и в результате общая громкость произведения повышается или понижается. Однако такой способ нельзя назвать удачным — он введен лишь в качестве крайней меры.

В начало

В начало

Номера MIDI-устройств

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

Вместо номера устройства может использоваться ключ (handle) ранее открытого устройства; система автоматически определяет, какое именно значение передано интерфейсной функции.

В начало

В начало

Идентификаторы (ключи) открытых устройств

При открывании объекта — устройства или потока — подсистема возвращает его идентификатор, или ключ (handle), по которому затем происходит вся остальная работа с объектом. Формально идентификаторы устройств ввода и вывода имеют различные типы: HMIDIIN и HMIDIOUT, однако оба они эквивалентны (если не определена макропеременная STRICT) типу HMIDI, который может использоваться для создания универсальных функций, не зависящих от типа устройства. Ключ потока вывода имеет тип HMIDISTRM и в некоторых функциях может использоваться вместо ключа устройства вывода; в таких случаях требуется явное приведение к типу HMIDIOUT.

В начало

В начало

Имена интерфейсных функций

Подсистема MIDI, в отличие от звуковой подсистемы, предоставляет приложению три, а не два класса объектов: устройство ввода (In), устройство вывода (Out) и буферизованный поток (Stream). Соответственно, имеется три класса интерфейсных функций для обслуживания этих объектов. Имена функций имеют соответствующие префиксы — midiInStart, midiStreamOut и т.п. Чаще всего одноименные функции с разными префиксами различаются только видами объектов, к которым они относятся; в таком случае я буду описывать все функции в одной главе и ссылаться на них по общей части имени, вместо типа объекта в имени будет писаться «xxx». Если же смысл и поведение функций различается — они будут описываться и упоминаться по отдельности.

В начало

В начало

Общая схема взаимодействия программы и подсистемы MIDI

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

При необходимости программа может запросить параметры и имена MIDI-устройств при помощи функций GetDevCaps.

Работа программы с устройством ввода начинается с его открывания функцией Open. При этом программа указывает требуемый способ уведомления о наступлении различных событий.

Для приема коротких сообщений программе достаточно предусмотреть обработчик асинхронных событий типа DATA, который будет получать уведомление от подсистемы MIDI каждый раз, когда на вход интерфейса поступит очередное MIDI-сообщение. Поскольку длина коротких сообщений не превышает 3 байтов, они передаются обработчику в числе параметров функции или сообщения упакованными в переменную типа DWORD и для их приема не требуется выделения каких-либо буферов в памяти. Прием сообщений начинается сразу же после обращения к функции Start.

Для вывода коротких сообщений применяется функция ShortMsg, в которой сообщение передается целиком, упакованным в переменную типа DWORD.

Для приема и вывода длинных сообщений необходимо использовать механизм буферизации — по той же схеме, что и для записи/воспроизведения цифрового звука. Для этой цели служат функции Prepare/Unprepare, AddBuffer, LongMsg, Stop, Reset.

Для устройств вывода, поддерживающих расширенные функции управления, программа может регулировать громкость звука функцией SetVolume.

Для автоматизированного потокового вывода функцией midiStreamOpen создается MIDI–поток, привязанный к заданному устройству вывода. Затем приложение формирует последовательность мета-событий, оформляя их в виде цепочки описателей событий, последовательно расположенных в памяти. Последовательность может занимать один непрерывный блок памяти, либо быть разбита на несколько блоков. Сформированные блоки последовательно передаются подсистеме функцией midiStreamOut.

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

Воспроизведение потока может быть приостановлено функцией midiStreamPause, после чего возобновлено функцией midiStreamRestart. Для аварийного прерывания обработки потока используется функция midiStreamReset, немедленно останавливающая процесс ввода или вывода и возвращающая все буферы из очереди приложению.

При полном завершении работы с устройством или потоком они закрываются функцией Close.

В начало

В начало

Средства разработки, включаемые файлы и библиотеки

Как всегда, описывается программирование на языке C/C++ в среде Microsoft Visual C++.

Все необходимые константы, типы, структуры и прототипы функций подсистемы определяется в файле MMSYSTEM.H, который по умолчанию включается в компиляцию из общего файла WINDOWS.H. Дополнительные, редко используемые константы определены в файле MMREG.H.

Интерфейсные функции импортируются из библиотеки WINMM.LIB.

В начало

В начало

Структуры, используемые в интерфейсе

При передаче указателей на некоторые структуры в параметрах интерфейсных функций передаются также размеры этих структур. Это делается для того, чтобы подсистема могла отслеживать версию интерфейса, используемую программой — 16- или 32-разрядный, ASCII или UNICODE.

В начало

В начало

Структуры MIDIINCAPS и MIDIOUTCAPS

Описывают свойства и характеристики MIDI–устройства. Все поля структур заполняются только подсистемой и драйвером MIDI. Структуры MIDIINCAPS и MIDIOUTCAPS весьма похожи и имеют четыре общих поля:

WORD  wMid;
 WORD  wPid;
 MMVERSION  vDriverVersion;
 CHAR  szPname [MAXPNAMELEN];

Индивидуальная часть структуры MIDIINCAPS имеет одно дополнительное поле:

DWORD  dwSupport;

Индивидуальная часть структуры MIDIOUTCAPS имеет пять дополнительных полей:

WORD  wTechnology;
 WORD  wVoices;
 WORD  wNotes;
 WORD  wChannelMask;
 DWORD  dwSupport;
  • wMid, wPid — идентификаторы разработчика (Manufacturer) и самого драйвера (Product). Полный список известных на момент выпуска SDK идентификаторов определен в файле MMREG.H
  • vDriverVersion — версия драйвера, представленная младшим словом (тип MMVERSION эквивалентен типу UINT, который в Win32 является 32-разрядным). Старший байт слова представляет основной номер версии, а младший байт — номер подверсии, отражающей непринципиальные изменения драйвера. Для выделения каждого номера можно использовать стандартные макросы HIBYTE и LOBYTE.
  • szPname — имя устройства в виде строки ASCIIZ.
  • dwSupport — описатель поддерживаемых драйвером синтезатора или порта возможностей и режимов. Для устройств ввода (структура MIDIINCAPS) это слово не имеет смысла и всегда равно нулю; для устройств вывода (структура MIDIOUTCAPS) оно может содержать ряд флагов, имена которых имеют префикс MIDICAPS_:

VOLUME

Поддержка управления громкостью.

LRVOLUME

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

STREAM

Прямая поддержка драйвером потоков вывода.

CACHE

Поддержка кэширования тембров.

  • wTechnology — тип устройства. Константы типов имеют префикс MOD_:

MIDIPORT

Аппаратный MIDI–порт.

MAPPER

Порт службы переназначения.

SYNTH

Внутренний синтезатор с технологией неизвестного типа.

FMSYNTH

Внутренний синтезатор с частотно-модуляционной технологией.

SQSYNTH

Внутренний синтезатор выходными сигналами в форме меандра (square wave).

Внутренние таблично-волновые синтезаторы имеют тип MOD_SYNTH.

  • wVoices — максимальное количество одновременно звучащих голосов (одна нота может одновременно звучать несколькими голосами). Имеет смысл только для внутренних синтезаторов, иначе равно нулю.
  • wNotes — максимальное количество одновременно активных нот. Имеет смысл только для внутренних синтезаторов, иначе равно нулю.
  • wChannelMask — маска поддерживаемых внутренним синтезатором MIDI–каналов (каналу 1 соответствует бит 0). Для внешних портов всегда равно 0xFFFF.
В начало

В начало

Структура MIDIHDR

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

LPSTR  lpData;
 DWORD  dwBufferLength;
 DWORD  dwBytesRecorded;
 DWORD  dwUser;
 DWORD  dwFlags;
 LPMIDIHDR  lpNext;
 DWORD  reserved;
 DWORD  dwOffset;
 DWORD  dwReserved [4];
  • lpData — указатель буфера данных (тип char *). Устанавливается приложением, драйвером не изменяется.
  • dwBufferLength — размер буфера в байтах. Устанавливается приложением, драйвером не изменяется.
  • dwBytesRecorded — для буферов ввода длинных сообщений обозначает количество байтов, записанных устройством в буфер, устанавливается драйвером и имеет значение только после завершения обработки данного буфера. Для буферов потоков вывода устанавливается приложением, и обозначает количество байтов, реально занятое описателями сообщений.
  • dwUser — поле пользователя, в которое программа может занести любое угодное ей значение — например, ссылку на внутренний описатель устройства. Устанавливается приложением, драйвером не используется и не изменяется.
  • dwFlags — слово состояния буфера. Устанавливается приложением, модифицируется драйвером. Имена констант флагов состояния имеют префикс MHDR_:

PREPARED

Буфер подготовлен (зафиксирован в памяти)

INQUEUE

Буфер находится в очереди драйвера

DONE

Обработка буфера драйвером завершена

ISSTRM

Буфер принадлежит потоку вывода

  • lpNext — указатель заголовка следующего в очереди буфера. Управляется только драйвером.
  • reserved — служебное поле. Управляется только драйвером.
  • dwOffset — смещение в буфере описателя мета-события уведомления о достижении позиции (в оригинале это событие называется Callback Event). Устанавливается драйвером непосредственно перед выполнением уведомления. Если уведомление обрабатывается недостаточно быстро, и в потоке встречается следующее мета-событие — драйвер перезапишет смещение в этом поле, выполняя уведомление для следующего мета-события.
  • dwReserved — служебное поле. Управляется только драйвером.
В начало

В начало

Структура упакованного короткого сообщения

Короткие сообщения передаются упакованными в переменные типа DWORD. По сути, сообщение просто записывается в область памяти, отведенную под эту переменную, начиная с первого (младшего) байта. Таким образом, первый байт сообщения (это байт состояния, если не используется Running Status) попадает в младший байт переменной, а следующие байты сообщения — в следующие по старшинству байты переменной. Поскольку длина коротких сообщений не превышает трех байтов, старший байт переменной сообщением никогда не используется.

По отдельности байты сообщения из упакованной переменной можно извлекать, поочередно сдвигая ее значение вправо на 8 разрядов, либо приведя к типу указателя на байт (char * или BYTE *) , и последовательно выбирая байты в порядке возрастания адресов.

В начало

В начало

Структура MIDIEVENT

Описывает мета-событие в потоке вывода. Заполняется приложением.

DWORD  dwDeltaTime;
 DWORD  dwStreamID;
 DWORD  dwEvent;
 DWORD  dwParms [];
  • dwDeltaTime — интервал времени от предыдущего события, в тиках.
  • dwStreamID — зарезервированное поле, должно иметь нулевое значение.
  • dwEvent — код типа события и дополнительные флаги (старший байт), параметры события (остальные байты). Для выделения кода и параметров служат макросы MEVT_EVENTTYPE и MEVT_EVENTPARM. Имена констант кодов событий и дополнительных флагов имеют префикс MEVT_.

Флаги, указываемые вместе с кодом события:

F_SHORT

Событие с коротким представлением. Все параметры события находятся в младших 24 разрядах слова, выделяемых макросом MEVT_EVENTPARM.

F_LONG

Событие с длинным представлением. Блок параметров события располагается, начиная с поля dwParms, длина блока определяется младшими 24 разрядами слова.

F_CALLBACK

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

 

Один из флагов длины представления события должен быть задан обязательно.

Коды типов событий:

SHORTMSG

Короткое MIDI–сообщение (короткое представление). Байты сообщения упакованы в поле параметров события.

LONGMSG

Длинное MIDI–сообщение (длинное представление), предназначено исключительно для передачи сообщений SysEx. Полностью сформированное сообщение SysEx находится в блоке параметров события. Передача с помощью этого события серии коротких сообщений не рекомендуется, так как при этом не отрабатывается режим Running Status.

TEMPO

Изменение темпа (короткое представление). Значение темпа представлено в поле параметров события, в виде длительности четвертной доли в микросекундах. Событие смены темпа не отрабатывается, если шкала времени для потока задана в формате SMPTE.

COMMENT

Комментарий (длинное представление). Первый байт блока параметров кодирует тип комментария, остальные байты — сам комментарий. Событие не влечет никаких специальных действий, блок параметров игнорируется.

VERSION

Информация о версии (длинное представление). Блок данных содержит структуру типа MIDISTRMBUFFVER.

NOP

Пустое событие (короткое представление). Поле параметров игнорируется.

  • dwParms — Первое двойное слово блока параметров, который должен содержать также целое количество двойных слов, и при необходимости дополняться нулевыми байтами до границы двойного слова. В младшей части поля dwEvent всегда указывается реальная длина блока параметров в байтах.
В начало

В начало

Структура MMTIME

Описывает время в терминах различных мультимедийных потоков. Используется для получения текущей позиции потока вывода. Приложением устанавливается только поле wType, остальные поля заполняются драйвером.

UINT wType;
 union {
   DWORD ms;
   DWORD sample;
   DWORD cb;
   DWORD ticks;
   struct {
     BYTE hour;
     BYTE min;
     BYTE sec;
     BYTE frame;
     BYTE fps;
     BYTE dummy;
     BYTE pad[2]
   } smpte;
   struct {
     DWORD songptrpos;
   } midi;
 } u;
  • wType — формат времени, описываемого структурой. Константы формата имеют префикс TIME_:

BYTES

Количество байтов от начала потока

MIDI

Время в стандарте MIDI (Sond Position Pointer)

MS

Время в миллисекундах

SAMPLES

Количество звуковых блоков с начала звукового (Audio) потока

SMPTE

Время в стандарте SMPTE (в кадрах)

TICKS

Время в тиках от начала MIDI–потока

  • ms — счетчик миллисекунд, для случая TIME_MS.
  • sample — счетчик звуковых блоков, для случая TIME_SAMPLES.
  • cb — счетчик байтов, для случая TIME_BYTES.
  • ticks — счетчик тиков, для случая TIME_TICKS.
  • smpte — набор данных в формате SMPTE, для случая TIME_SMPTE.
  • midi — данные в формате MIDI–времени.
  • hour, min, sec — счетчик полных часов, минут и секунд.
  • frame — счетчик кадров внутри последней секунды.
  • fps — кадров в секунду (24, 25, 29, 30).
  • dummy, pad — выравнивание на границу двойного слова.
  • songptrpos — позиция указателя композиции в MIDI (Song Position Pointer).
В начало

В начало

Структуры MIDIPROPTEMPO и MIDIPROPTIMEDIV

Описывают свойства потока вывода — темп и временную шкалу. Формат структуры MIDIPROPTEMPO:

DWORD cbStruct;
 DWORD dwTempo;
  • cbStruct — размер структуры в байтах, отражает версию интерфейса. В будущем возможно добавление новых полей.
  • dwTempo — темп воспроизведения потока, в микросекундах на четвертную долю.

Формат структуры MIDIPROPTIMEDIV:

DWORD cbStruct;
 DWORD dwTimeDiv;
  • cbStruct — размер структуры в байтах.
  • dwTimeDiv — длительность деления временной шкалы (тика) потока (младшее слово).
В начало

В начало

Структура MIDISTRMBUFFVER

Описывает версию потока вывода.

DWORD dwVersion;
 DWORD dwMid;
 DWORD dwOEMVersion;
  • dwVersion — версия потока (старшее/младшее слово содержат старшую/младшую часть номера версии). В данное время поддерживается только версия 1.0.
  • dwMid — идентификатор производителя. Константы для кодов идентификаторов определены в файле MMREG.H.
  • dwOEMVersion — номер версии производителя, создающего поток.
В начало

В начало

Предыдущая страница


Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует