haskell-notes

одинаковых клавиатур. Для того чтобы отделить тембр от управления (нажатия на клавиши игроком) был

придуман протокол 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. Ноте до первой октавы ( C) соответствует цифра 60, ноте ля

первой октавы ( A) соответствует номер 69. Одно число кодирует сразу и октаву и ступень лада.

Может показаться странным параметр 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, оно указывает на число кадров в секунде.

Теперь посмотрим, что такое трек:

type Track a = [(a, Message)]

Трек это список событий с временными отсчётами. Время в midi отсчитывается относительно предыдуще-

го события. Например в следующей записи три события произошли одновременно и затем спустя 10 тактов

произошли ещё два события:

[(0, e1), (0, e2), (0, e3), (10, e4), (0, e5)]

21.2 Музыкальная запись в виде событий

Писать музыку в виде событий midi очень неудобно, пусть даже и через HCodecs, необходимо придумать

надстройку над протоколом midi. Я долго думал об этом и в итоге пришёл к выводу, что наиболее простой

и податливый способ представления музыки на нотном уровне реализован в языке Csound. Там ноты пред-

ставлены в виде последовательности событий. Каждое событие начинается в определённый момент и длится

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

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

параметрами события являются лишь номер инструмента, который играет ноту, начало события и длитель-

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162