52700.fb2
where groupByInstrId = groupBy ((==) ‘on‘ instrId) .
sortBy
(compare ‘on‘ instrId)
В этом определении мы воспользовались двумя новыми стандартными функциями из модуля Data.List.
Функция partition разделяет список на пару списков. В первом списке находятся все те элементы, для
которых заданный предикат вернул True, а во втором списке – все остальные элементы исходного списка:
Prelude Data.List> :t partition
partition :: (a -> Bool) -> [a] -> ([a], [a])
Функция groupBy превращает список в список списков:
Prelude Data.List> :t groupBy
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
Если бинарная функция на соседних элементах исходного списка вернула True, то они помещаются в
один подсписок. Эта функция используется для того чтобы сгруппировать элементы списка по какому-нибудь
признаку. При этом для того чтобы сгруппировать элементы по идентификатору инструмента, мы сначала
отсортировали события по значению идентификатора. После этого значения с одинаковыми идентификато-
рами стали соседними и мы сгруппировали их с помощью groupBy.
Функция first применяет функцию к первому элементу пары. Вот мы и закончили, можно послушать ре-
зультаты. На самом деле остались два нюанса. В функции setChannel мы полагаем, что мелодия начинается
в момент времени t = 0, но на практике это может оказаться не так, мы можем сместить ноты функцией
delay в отрицательную сторону. Тогда первые ноты будут содержать отрицательное время начала события.
Но мы можем исправить эту ситуацию, сместив все ноты на время самой первой ноты, конечно смещать
необходимо только в том случае если время окажется отрицательным:
alignEvents :: [MidiEvent] -> [MidiEvent]
alignEvents es
| d < 0
= map (delay (abs d)) es
| otherwise = es
where d = minimum $ map eventStart es
Вызовем эту функцию сразу после функции trackEvents в функции groupInstr. Второй нюанс заключа-
ется в том, что каждый трек в midi-файле должен заканчиваться специальным сообщением, в библиотеке
HCodecs оно обозначается с помощью конструктора TrackEnd. В самом конце необходимо добавить сообще-
ние (0, TrackEnd):
toTrack :: Score -> M.Track M.Ticks
toTrack = addEndMsg . tfmTime . mergeInstr . groupInstr
addEndMsg :: M.Track M.Ticks -> M.Track M.Ticks
addEndMsg = (++ [(0, M.TrackEnd)])
Теперь мы можем проверить, что у нас получилось. Создадим файл:
module Main where
import System
import Track
import Score
import Codec.Midi
out = (>> system ”timidity tmp.mid”) .
exportFile ”tmp.mid” . render
В функции out мы переводим нотную запись в значение типа Midi, затем сохраняем это значение в файле
tmp. mid и в самом конце запускаем файл с помощью проигрывателя timidity. Вместо timidity вы можете
воспользоваться вашим любимым проигрывателем midi-файлов. Теперь загрузим модуль Main в интерпре-
татор. Послушаем ноту до:
*Main> out c