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

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

partition (not . isDrum . eventContent) . trackEvents

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