52700.fb2 Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 340

Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 340

ленивых вычислениях. Конструктор 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. Там ноты пред-

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

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

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

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