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

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

которая превращает эту пару в набор midi-сообщений:

mergeInstr :: ([[MidiEvent]], [MidiEvent]) -> M.Track Double

Наши отсчёты времени записаны в виде значений типа Double, Нам необходимо перейти к целочислен-

ным Ticks. Представим, что такая функция у нас уже есть:

tfmTime :: M.Track Double -> M.Track M.Ticks

Тогда функция toTrack примет вид:

toTrack :: Score -> M.Track M.Ticks

toTrack = tfmTime . mergeInstr . groupInstr

Все три составляющие функции пока не определены. Начнём с функции tfmTime. Нам необходимо от-

сортировать события во времени для того, чтобы мы смогли перейти из абсолютных отсчётов во времени в

относительные. Специально для этого в библиотеке odecs определена функция:

fromAbsTime :: Num a -> Track a -> Track a

Также нам понадобится функция:

type Time = Double

fromRealTime :: TimeDiv -> Trrack Time -> Track Ticks

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

отсчёты. С помощью этих функций мы можем определить функцию timeDiv так:

import Data.List(sortBy)

import Data.Function (on)

...

tfmTime :: M.Track Double -> M.Track M.Ticks

tfmTime = M. fromAbsTime . M. fromRealTime timeDiv .

sortBy (compare ‘on‘ fst)

В этой функции мы сначала сортируем события во времени, затем переходим от абсолютных единиц к

относительным и в самом конце производим квантование по времени. Функция sortBy сортирует элементы

согласно некоторой функции упорядочивания:

sortBy :: (a -> a -> Ordering) -> [a] -> [a]

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

необходимо отсортировать элементы списка сообщений по значению временных отсчётов. Функцию упоря-

дочивания мы составляем с помощью специальной функции on, которая определена в модуле Data.Function.

С этой функцией мы уже сталкивались, когда говорили о функциях высшего порядка, она принимает функ-

цию двух аргументов и функцию одного аргумента и словно “подкладывает” вторую функцию под первую:

Prelude Data.Function> :t on

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

Теперь напишем функцию mergeInstr. Она устанавливает инструменты на каналы и преобразует события

в последовательность midi-сообщений. При этом мы различаем сообщения для ударных и сообщения для всех

остальных инструментов:

312 | Глава 21: Музыкальный пример

mergeInstr :: ([[MidiEvent]], [MidiEvent]) -> M.Track Double

mergeInstr (instrs, drums) = concat $ drums’ : instrs’

where instrs’ = zipWith setChannel ([0 .. 8] ++ [10 .. 15]) instrs

drums’

= setDrumChannel drums

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

setChannel = undefined

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

setDrumChannel =

undefined

Имя instrs’ указывает на последовательность списков сообщений для каждого неударного инструмента.

Функция setChannel принимает номер канала и список событий. По ним она строит список midi-сообщений.