композицию, для этого мы сместим второй трек на длину первого и сыграем их одновременно:
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 achord =
foldr (=:=) (silence 0)line ::
(Num t, Ord t) => [Track t a] -> Track t aline =
foldr (+:+) (silence 0)Мы можем определить в терминах этих операций цикличный повтор событий:
loop ::
(Num t, Ord t) => Int -> Track t a -> Track t aloop n t =
line $ replicate n tЭкземпляры стандартных классов
Мы можем сделать тип трек экземпляром класса Functor
:instance Functor
(Event t) wherefmap f e =
e{ eventContent = f (eventContent e) }instance Functor
(Track t) wherefmap f t =
t{ trackEvents = fmap (fmap f) (trackEvents t) }Мы можем также определить экземпляр для класса Monoid
. Параллельная композиция будет операциейобъединения, а нейтральным элементом будет тишина, которая длится ноль единиц времени:
instance
(Ord t, Num t) => Monoid (Track t a) wheremappend =
(=:=)mempty
=
silence 021.3 Ноты в midi
С помощью типа Track
мы можем описывать всё, что имеет свойство случаться во времени и длиться,мы можем описывать наборы событий. Операции из класса Temporal
и операции последовательной и парал-лельной композиции дают нам возможность собирать сложные наборы событий из простейших. Но для того
чтобы это стало музыкой, нам не хватает нот.
Так построим их. Поскольку мы собираемся играть музыку в midi, наши ноты будут содержать только три
основных параметра, это номер инструмента, громкость и высота. Длительность ноты будет кодироваться в
событии, эта информация уже встроена в тип Track
.data Note = Note
{noteInstr
:: Instr
,noteVolume
:: Volume
,notePitch
:: Pitch
,isDrum
:: Bool
}
Итак нота содержит код инструмента, громкость и высоту и ещё один параметр. По последнему пара-
метру можно узнать сыграна нота на барабане или нет. В midi ноты для ударных обрабатываются особым
образом. Десятый канал выделен под ударные, при этом номер инструмента игнорируется, а вместо этого
высота звука кодирует номер ударного инструмента. Теперь определимся с типами параметров:
type Instr
= Int
type Volume = Int
type Pitch
= Int
Целые числа соответствуют целым числам в протоколе midi. Значения для типов Volume
и Pitch лежат вдиапазоне от 0 до 127.
Введём специальное обозначение для музыкального типа Track
:type Score = Track Double Note
Ноты в midi | 309
Синонимы для нот
Высота ноты
Музыкантам ближе буквенные обозначения для нот нежели коды midi. Определим удобные синонимы:
note :: Int -> Score
note n = Track
1 [Event 0 1 (Note 0 64 (60+n) False)]Эта функция строит трек, который содержит одну ноту. Нота длится одну целую длительность играется
на инструменте с кодом 0, на средней громкости. Параметр функции задаёт смещение от ноты до первой
октавы. Определим остальные ноты:
a, b, c, d, e, f, g,
as, bs, cs, ds, es, fs, gs,
af, bf, cf, df, ef, ff, gf :: Score
c =
note 0;cs =
note 1;d =
note 2;ds =
note 3;...
Первая буква содержит буквенное обозначение ноты, а вторая либо s (от англ. sharp диез) или f (от англ.
flat бемоль). Все эти ноты находятся в первой октаве, но смещением высоты на 12 единиц мы легко можем
смещать эти ноты в любую другую октаву:
higher :: Int -> Score -> Score
higher n =
fmap (\a -> a{ notePitch = 12*n + notePitch a })lower :: Int -> Score -> Score
lower n =
higher (-n)high :: Score -> Score
high =
higher 1low :: Score -> Score
low =
lower 1С помощью этих функций мы легко можем смещать группы нот в любую октаву. Функция higher прини-
мает число октав, на которые необходимо сместить вверх высоту во всех нотах трека. Смещение высоты на
12 определяет смещение на одну октаву. Остальные функции определены в через функцию higher.
Длительность ноты
Пока что наши ноты длятся 1 единицу времени. Но нам бы хотелось иметь в распоряжении и другие дли-
тельности. Ноты других длительностей мы можем легко получать с помощью функции stretch, мы просто
изменим масштаб времени и длительность всех нот изменится. Определим несколько синонимов:
bn, hn, qn, en, sn :: Score -> Score
-- (brewis note)
(half note)
(quater note)
bn =
stretch 2;hn =
stretch 0.5;qn =
stretch 0.25;-- (eighth note)
(sizth note)
en =
stretch 0.125;sn =
stretch 0.0625;Эти преобразования отвечают длительностям нот в европейской музыкальной традиции.
Громкость ноты