haskell-notes

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

чтобы это стало музыкой, нам не хватает нот.

Так построим их. Поскольку мы собираемся играть музыку в 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 1

low :: 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;

Эти преобразования отвечают длительностям нот в европейской музыкальной традиции.

Громкость ноты

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

тех, что изменяли высоту звука октавами:

louder :: Int -> Score -> Score

louder n = fmap $ a -> a{ noteVolume = n + noteVolume a }

quieter :: Int -> Score -> Score

quieter n = louder (n)

310 | Глава 21: Музыкальный пример

Смена инструмента

Изначально мы создаём ноты, которые играются на инструменте с кодом 0, в протоколе General Midi этот

номер соответствует роялю. Но с помощью класса Functor мы легко можем изменить инструмент:

instr :: Int -> Score -> Score

instr n = fmap $ a -> a{ noteInstr = n, isDrum = False }

drum :: Int -> Score -> Score

drum n = fmap $ a -> a{ notePitch = n, isDrum = True }

Согласно протоколу midi в случае ударных инструментов высота звука кодирует инструмент. Поэтому

в функции drum мы изменяем именно поле notePitch. Создадим также несколько синонимов для создания

нот, которые играются на барабанах. В этом случае нам не важна высота звука но важна громкость:

bam :: Int -> Score

bam n = Track 1 [Event 0 1 (Note 0 n 35 True)]

Номер 35 кодирует “бочку”.

Паузы

Слово silence верно отражает смысл, но оно слишком длинное. Давайте определим несколько синони-

мов:

rest :: Double -> Score

rest = silence

wnr = rest 1;

bnr = bn wnr;

hnr = hn wnr;

qnr = qn wnr;

enr = en wnr;

snr = sn wnr;

21.4 Перевод в midi

Теперь мы можем составить какую нибудь мелодию:

q = line [c, c, hn e, hn d, bn e, chord [c, e]]

Мы можем составлять мелодии, но пока мы не умеем их интерпретировать. Для этого нам нужно написать

функцию:

render :: Score -> Midi

Мы реализуем простейший случай. Будем считать, что у нас только 15 инструментов, а все остальные

инструменты – ударные. Мы запишем нашу музыку на один трек midi-файла, распределив 15 неударных

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

разрешение по времени для всех возможных мелодий. Будем считать, что 96 ударов для одной четверти нам

достаточно. Принимая во внимания эти посылки мы можем написать такую функцию:

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162