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

type Track a = [(a, Message)]

Трек это список событий с временными отсчётами. Время в midi отсчитывается относительно предыдуще-

го события. Например в следующей записи три события произошли одновременно и затем спустя 10 тактов

произошли ещё два события:

[(0, e1), (0, e2), (0, e3), (10, e4), (0, e5)]

21.2 Музыкальная запись в виде событий

Писать музыку в виде событий midi очень неудобно, пусть даже и через HCodecs, необходимо придумать

надстройку над протоколом midi. Я долго думал об этом и в итоге пришёл к выводу, что наиболее простой

и податливый способ представления музыки на нотном уровне реализован в языке Csound. Там ноты пред-

ставлены в виде последовательности событий. Каждое событие начинается в определённый момент и длится

некоторое время. Событие содержит код инструмента и набор параметров, которые могут включать в себя

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

параметрами события являются лишь номер инструмента, который играет ноту, начало события и длитель-

ность события. Мы ослабим эти ограничения. Событие будет содержать лишь время начала, длительность и

некоторое содержание.

data Event t a = Event {

eventStart

:: t,

eventDur

:: t,

eventContent

:: a

} deriving (Show, Eq)

Параметр t символизирует время, а параметр a – некоторое содержание события. Мы будем говорить,

что в некоторый момент времени произошло значение типа a и оно длилось некоторое время. Треком мы

будем называть набор событий, которые длятся определённой время:

data Track t a = Track {

trackDur

:: t,

trackEvents

:: [Event t a]

}

Первый параметр указывает на общую длительность трека, а второй содержит события, которые про-

изошли. Мы явно указываем длительность трека для того, чтобы иметь возможность представить тишину.

Значение тишины будет выглядеть так:

silence t = Track t []

Этим мы говорим, что ничего не произошло в течение t единиц времени.

Музыкальная запись в виде событий | 307

Преобразование событий во времени

Наши события привязаны ко времени. Мы можем ввести линейные операции, которые будут изменять

расположение событий во времени. Самый простой способ изменения положения это задержка. Мы можем

задержать появление события, прибавив какое-нибудь число ко времени начала события:

delayEvent :: Num t => t -> Event t a -> Event t a

delayEvent d e = e{ eventStart = d + eventStart e }

Ещё одно простое преобразование заключается в изменении масштаба времени, в музыке или анимации

этой операции соответствует перемотка. Событие начинает происходить быстрее или медленнее:

stretchEvent :: Num t => t -> Event t a -> Event t a

stretchEvent s e = e{

eventStart

= s * eventStart e,

eventDur

= s * eventDur

e }

Для изменения масштаба времени мы умножили временные параметры на число s. Эти операции мы

можем перенести и на значения типа Track.

delayTrack :: Num t => t -> Track t a -> Track t a

delayTrack d (Track t es) = Track (t + d) (map (delayEvent d) es)

stretchTrack :: Num t => t -> Track t a -> Track t a

stretchTrack s (Track t es) = Track (t * s) (map (stretchEvent s) es)

Класс преобразований во времени

У нас есть аналогичные операции преобразования во времени для событий и треков, это говорит о том,

что мы можем ввести специальный класс, который объединит в себе эти операции. Назовём его классом

Temporal (временн ой):

class Temporal a where

type Dur a :: *

dur

:: a -> Dur a

delay

:: Dur a -> a -> a

stretch :: Dur a -> a -> a

В этом классе определён один тип, который обозначает размерность времени, и три метода в дополнении

к методам delay и stretch мы добавим метод dur, мы будем считать, что всё что происходит во времени

конечно и с помощью метода dur мы всегда можем узнать протяжённость значения их класса Temporal во

времени. Для определения этого класса нам придётся подключить расширение TypeFamilies. Теперь мы

легко можем определить экземпляры класса Temporal для Event и Track:

instance Num t => Temporal (Event t a) where

type Dur (Event t a) = t

dur

= eventDur

delay

= delayEvent

stretch = stretchEvent

instance Num t => Temporal (Track t a) where

type Dur (Track t a) = t

dur

= trackDur

delay

= delayTrack

stretch = stretchTrack

Композиция треков

Определим две полезные в музыке операции: параллельную и последовательную композицию треков. В

параллельной композиции мы играем два трека одновременно:

(=:=) :: Ord t => Track t a -> Track t a -> Track t a

Track t es =:= Track t’ es’ = Track (max t t’) (es ++ es’)

Теперь общая длительность трека равна длительности большего из треков, а события включают в себя

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

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

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

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

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