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

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

Определим эту функцию:

setChannel :: M.Channel -> [MidiEvent] -> M.Track Double

setChannel ch ms = case ms of

[]

-> []

x:xs

-> (0, M.ProgramChange ch (instrId x)) : (fromEvent ch =<< ms)

instrId = noteInstr . eventContent

fromEvent :: M.Channel -> MidiEvent -> M.Track Double

fromEvent = undefined

Первым событием мы присоединяем событие, которое устанавливает на данном канале определённый

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

менте, поэтому мы узнаём идентификатор инструмента из первого элемента списка. У нас появилась новая

неопределённая функция fromEvent она переводит сообщение в список midi-сообщений:

fromEvent :: M.Channel -> MidiEvent -> M.Track Double

fromEvent ch e = [

(eventStart e, noteOn n),

(eventStart e + eventDur e, noteOff n)]

where n = clipToMidi $ eventContent e

noteOn

n = M.NoteOn

ch (notePitch n) (noteVolume n)

noteOff n = M.NoteOff ch (notePitch n) 0

clipToMidi :: Note -> Note

clipToMidi n = n {

notePitch

= clip $ notePitch n,

noteVolume

= clip $ noteVolume n }

where clip = max 0 . min 127

Определив эти функции, мы легко можем написать и функцию setDrumChannel она переводит сообщения

для ударных инструментов в midi-сообщения:

setDrumChannel :: [MidiEvent] -> M.Track Double

setDrumChannel ms = fromEvent drumChannel =<< ms

where drumChannel = 9

Для ударных инструментов выделен отдельный канал. Считается, что все они происходят на 10 канале.

Поскольку в библиотеке HCodecs первый канал называется нулевым, мы будем записывать все сообщения на

девятый канал.

Мы переводим событие в два midi-сообщения, первое говорит о том, что мы начали играть ноту, а второе

говорит о том, что мы закончили её играть. Функция clipToMidi приводит значения для высоты и громкости

в диапазон midi.

Нам осталось определить только одну функцию. Эта функция распределяет события по инструментам.

Сначала мы разделим события на те, что играются на ударных и неударных инструментах, а затем разделим

“неударные” ноты по инструментам:

import Control.Arrow(first, second)

import Data.List(sortBy, groupBy, partition)

...

groupInstr :: Score -> ([[MidiEvent]], [MidiEvent])

Перевод в midi | 313

groupInstr = first groupByInstrId .