52700.fb2
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. Нам необходимо от-
сортировать события во времени для того, чтобы мы смогли перейти из абсолютных отсчётов во времени в
относительные. Специально для этого в библиотеке HС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-сообщений.