ность события. Мы ослабим эти ограничения. Событие будет содержать лишь время начала, длительность и
некоторое содержание.
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’)
Теперь общая длительность трека равна длительности большего из треков, а события включают в себя
события каждого из треков. С помощью преобразований во времени мы можем определить последовательную
композицию, для этого мы сместим второй трек на длину первого и сыграем их одновременно:
308 | Глава 21: Музыкальный пример
(+:+) :: (Ord t, Num t) => Track t a -> Track t a -> Track t a
(+:+) a b = a =:= delay (dur a) b
При этом у нас как раз и получится, что мы сначала сыграем целиком трек a, а затем трек b. Теперь
определим аналоги операций =:= и +:+ для списков:
chord :: (Num t, Ord t) => [Track t a] -> Track t a
chord = foldr (=:=) (silence 0)
line :: (Num t, Ord t) => [Track t a] -> Track t a
line = foldr (+:+) (silence 0)
Мы можем определить в терминах этих операций цикличный повтор событий:
loop :: (Num t, Ord t) => Int -> Track t a -> Track t a
loop n t = line $ replicate n t
Экземпляры стандартных классов
Мы можем сделать тип трек экземпляром класса Functor:
instance Functor (Event t) where
fmap f e = e{ eventContent = f (eventContent e) }
instance Functor (Track t) where
fmap f t = t{ trackEvents = fmap (fmap f) (trackEvents t) }
Мы можем также определить экземпляр для класса Monoid. Параллельная композиция будет операцией
объединения, а нейтральным элементом будет тишина, которая длится ноль единиц времени:
instance (Ord t, Num t) => Monoid (Track t a) where
mappend = (=:=)
mempty
= silence 0
21.3 Ноты в midi
С помощью типа Track мы можем описывать всё, что имеет свойство случаться во времени и длиться,
мы можем описывать наборы событий. Операции из класса Temporal и операции последовательной и парал-