Читаем Учебник по Haskell полностью

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-сообщений.

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

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 .

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]]

Перейти на страницу:

Похожие книги

1С: Бухгалтерия 8 с нуля
1С: Бухгалтерия 8 с нуля

Книга содержит полное описание приемов и методов работы с программой 1С:Бухгалтерия 8. Рассматривается автоматизация всех основных участков бухгалтерии: учет наличных и безналичных денежных средств, основных средств и НМА, прихода и расхода товарно-материальных ценностей, зарплаты, производства. Описано, как вводить исходные данные, заполнять справочники и каталоги, работать с первичными документами, проводить их по учету, формировать разнообразные отчеты, выводить данные на печать, настраивать программу и использовать ее сервисные функции. Каждый урок содержит подробное описание рассматриваемой темы с детальным разбором и иллюстрированием всех этапов.Для широкого круга пользователей.

Алексей Анатольевич Гладкий

Программирование, программы, базы данных / Программное обеспечение / Бухучет и аудит / Финансы и бизнес / Книги по IT / Словари и Энциклопедии
1С: Управление торговлей 8.2
1С: Управление торговлей 8.2

Современные торговые предприятия предлагают своим клиентам широчайший ассортимент товаров, который исчисляется тысячами и десятками тысяч наименований. Причем многие позиции могут реализовываться на разных условиях: предоплата, отсрочка платежи, скидка, наценка, объем партии, и т.д. Клиенты зачастую делятся на категории – VIP-клиент, обычный клиент, постоянный клиент, мелкооптовый клиент, и т.д. Товарные позиции могут комплектоваться и разукомплектовываться, многие товары подлежат обязательной сертификации и гигиеническим исследованиям, некондиционные позиции необходимо списывать, на складах периодически должна проводиться инвентаризация, каждая компания должна иметь свою маркетинговую политику и т.д., вообщем – современное торговое предприятие представляет живой организм, находящийся в постоянном движении.Очевидно, что вся эта кипучая деятельность требует автоматизации. Для решения этой задачи существуют специальные программные средства, и в этой книге мы познакомим вам с самым популярным продуктом, предназначенным для автоматизации деятельности торгового предприятия – «1С Управление торговлей», которое реализовано на новейшей технологической платформе версии 1С 8.2.

Алексей Анатольевич Гладкий

Финансы / Программирование, программы, базы данных