длительности, высоты и громкости. Высота в свою очередь состоит из обозначения октавы и ступени лада.
Теперь давайте посмотрим крупным планом на протокол midi.
Протокол midi
Протокол midi появился в ответ на бурное развитие синтезаторов. Каждый из синтезаторов предлагал
свои тембры, при этом люди задумались, а нужна ли синтезатору клавиатура? Вопрос кажется абсурдным,
если мы думаем об одном синтезаторе, но представьте, что у вас их десять, в каждом свой чем-то особенный
тембр. При этом нам нужно десять разных тембров, но мы вынужденны таскать за собой десять примерно
одинаковых клавиатур. Для того чтобы отделить тембр от управления (нажатия на клавиши игроком) был
придуман протокол midi. Протокол midi описывает специфическую для нажатия на клавиши информацию.
Производители тембров или генераторов тона, могут научить генератор тона понимать midi. При этом мы
можем сделать отдельную клавиатуру, которая не имеет собственного генератора тона, но умеет посылать
сообщения протокола midi, так мы сможем управлять десятью генераторами тона от разных производителей
с помощью одной клавиатуры. Такие клавиатуры называют midi-клавиатурами.
Познакомимся с терминологией midi. Протокол midi рассчитан на управление синтезаторами в режиме
реального времени. Можно сказать, что midi-файл – это история концерта или выступления, низкоуровневая
нотная запись. Каждое движение игрока кодируется событием. Например нажатие на клавишу, отпускание
клавиши, сила давления на клавишу в определённый момент времени, нажатие педали, поворот реле или
смена тэмбра.
Протокол midi изначально задумывался как расширяемый протокол. Каждый производитель тембров
имеет возможность добавить какие-то особенные настройки. При этом те сообщения, которые данный ге-
нератор тона не понимает просто игнорируются. Наш секвенсор будет понимать такие события как нажатие
на клавишу и отпускание клавиши. Также у нас будут разные инструменты.
Установим библиотеку HCodecs с Hackage:
cabal install HCodecs
Теперь заглянем на страницу документации этого пакета (на сайте Hackage), нас интересует модуль
Codec.Midi, ведь мы хотим создавать именно midi-файлы. Здесь мы видим описание протокола midi, за-
кодированное в типах. Посмотрим на тип Message, он описывает midi-сообщения. В первую очередь нас ин-
тересуют конструкторы:
NoteOn {
channel
:: !Channel,
key
:: !Key,
velocity :: !Velocity }
NoteOff
{
channel
:: !Channel,
key
:: !Key,
velocity :: !Velocity }
Восклицательные знаки перед типами означают взрывные шаблоны, о которых мы говорили в главах о
ленивых вычислениях. Конструктор NoteOn обозначает нажатие клавиши на канале Channel с высотой Key и
уровнем громкости Velocity. Конструктор NoteOff обозначает отпускание клавиши, параметры имеют тот
же смысл, что и в случае NoteOn.
Думаю что такое высота и громкость примерно понятно, но что такое канал? Считается, что один испол-
нитель может управлять сразу несколькими генераторами тона. Управление распределяется по каналам. На
каждом канале мы можем управлять отдельным инструментом. Немного о высоте и громкости. Они кодиру-
ются целыми числами из диапазона от 0 до 127. Ноте до первой октавы (
первой октавы (
Может показаться странным параметр Velocity в конструкторе NoteOff, он обозначает отпускание клави-
ши с определённой громкостью. Обычно этот параметр игнорируется и в него записывают среднее значение
64 или начальное значение 0.
Также мы будем играть разными инструментами. Инструменты в протоколе midi называются програм-
мами. Мы можем установить определённый инструмент на данном канале с помощью сообщения:
306 | Глава 21: Музыкальный пример
ProgramChange {
channel :: !Channel,
preset
:: !Preset }
Целое число Preset указывает на код инструмента. Теперь посмотрим, что же такое midi-файл:
data Midi = Midi {
fileType :: FileType,
timeDiv
:: TimeDiv,
tracks
:: [Track Ticks] }
midi-файл состоит из трёх значений. Это обозначение типа файла:
data FileType = SingleTrack | MultiTrack | MultiPattern
По типу midi-файлы могут различаться на файлы с одним треком, файлы с несколькими треками, и
файлы, которые содержат группы треков, которые называют узорами (pattern). По смыслу трек соответствует
партии инструмента.
Тип TimeDiv кодирует скорость записи сообщений. Различают два варианта:
data TimeDive = TicksPerBeat Int
| TicksPerSecond Int Int
Первый конструктор говорит о том, что разрешение времени закодировано в формате PPQN, он указы-
вает на число ударов в одной четвертной длительности. Второй конструктор говорит о том, что разрешение
кодируется в формате SMPTE, оно указывает на число кадров в секунде.
Теперь посмотрим, что такое трек: