Если бинарная функция на соседних элементах исходного списка вернула True
, то они помещаются водин подсписок. Эта функция используется для того чтобы сгруппировать элементы списка по какому-нибудь
признаку. При этом для того чтобы сгруппировать элементы по идентификатору инструмента, мы сначала
отсортировали события по значению идентификатора. После этого значения с одинаковыми идентификато-
рами стали соседними и мы сгруппировали их с помощью groupBy.
Функция first применяет функцию к первому элементу пары. Вот мы и закончили, можно послушать ре-
зультаты. На самом деле остались два нюанса. В функции setChannel мы полагаем, что мелодия начинается
в момент времени t =
0, но на практике это может оказаться не так, мы можем сместить ноты функциейdelay в отрицательную сторону. Тогда первые ноты будут содержать отрицательное время начала события.
Но мы можем исправить эту ситуацию, сместив все ноты на время самой первой ноты, конечно смещать
необходимо только в том случае если время окажется отрицательным:
alignEvents ::
[MidiEvent] -> [MidiEvent]alignEvents es
|
d < 0=
map (delay (abs d)) es|
otherwise = eswhere
d = minimum $ map eventStart esВызовем эту функцию сразу после функции trackEvents в функции groupInstr. Второй нюанс заключа-
ется в том, что каждый трек в midi-файле должен заканчиваться специальным сообщением, в библиотеке
HCodecs
оно обозначается с помощью конструктора TrackEnd. В самом конце необходимо добавить сообще-ние (0, TrackEnd
):toTrack :: Score -> M.Track M.Ticks
toTrack =
addEndMsg . tfmTime . mergeInstr . groupInstraddEndMsg :: M.Track M.Ticks -> M.Track M.Ticks
addEndMsg =
(++ [(0, M.TrackEnd)])Теперь мы можем проверить, что у нас получилось. Создадим файл:
module Main where
import System
import Track
import Score
import Codec.Midi
out =
(>> system ”timidity tmp.mid”) .exportFile ”tmp.mid” .
renderВ функции out мы переводим нотную запись в значение типа Midi
, затем сохраняем это значение в файлеtmp.
mid и в самом конце запускаем файл с помощью проигрывателя timidity. Вместо timidity вы можетевоспользоваться вашим любимым проигрывателем midi-файлов. Теперь загрузим модуль Main
в интерпре-татор. Послушаем ноту до:
*Main>
out c314 | Глава 21: Музыкальный пример
Далее следуют сообщения из проигрывателя timidity и долгожданный звук. Мы слышим ноту до, сыг-
ранную на рояле. Наберём какую-нибудь мелодию:
*Main> let
x = line [c, hn e, hn e, low b, c]*Main>
out xСыграем в два раза быстрее, на другом инструменте:
*Main>
out $ instr 15 $ hn xСыграем канон. Канон это когда одна и та же мелодия ведётся в разных голосах с запаздыванием. Сыграем
двухголосный канон:
*Main>
out $ instr 80 (loop 3 x) =:= delay 2 (instr 65 $ low $ loop 3 x)Номера инструментов можно посмотреть по справке к протоколу General Midi. Это дополнение к прото-
колу midi определяет какие номера каким инструментам должны соответствовать. Звучит ужасно, но звучит!
21.5 Пример
Опираясь на примитивы композиции, которые мы определил в модуле Score
, мы можем написать мело-дию. Ниже приведён небольшой пример. Инструменты:
closedHiHat =
drum 42;rideCymbal =
drum 59;cabasa =
drum 69;maracas
=
drum 70;tom
=
drum 45;flute
=
instr 73;piano
=
instr 0;Ударная секция:
b1 =
bam 100b0 =
bam 84drums1 =
loop 80 $ chord [tom
$
line [qn b1, qn b0, hnr],maracas $
line [hnr, hn b0]]
drums2 =
quieter 20 $ cabasa $ loop 120 $ en $ line [b1, b0, b0, b0, b0]drums3 =
closedHiHat $ loop 50 $ en (line [b1, loop 12 wnr])drums =
drums1 =:= drums2 =:= drums3Уже сейчас мы можем загрузить эту партию в интерпретатор и послушать, вызвав out drums. Аккорды к
мелодии:
c7
=
chord [c, e, b]gs7 =
chord [low af, c, g]g7
=
chord [low g, low bf, f]harmony =
piano $ loop 12 $ lower 1 $ bn $ line [bn c7, gs7, g7]Мелодия:
ac =
louder 5mel1 =
bn $ line [bnr, subMel, ac $ stretch (1+1/8) e, c,subMel, enr]
where
subMel = line [g, stretch 1.5 $ qn g, qn f, qn g]mel2 =
loop 2 $ qn $ line [subMel, ac $ bn ds, c, d, ac $ bn c, c, c, wnr,subMel, ac $
bn g, f, ds, ac $ bn f, ds, ac $ bn c]where
subMel = line [ac ds, c, d, ac $ bn c, c, c]mel3 =
loop 2 $ line [pat1 (high c) as g, pat1 g f d]where
pat1 a b c = line [pat a, loop 3 qnr, wnr,pat b, qnr, hnr, pat c, qnr, hnr]
pat
x
=
en (x +:+ x)mel =
flute $ line [mel1, mel2, mel3]Пример | 315
Добавим в конце звук тарелки:
cha =
delay (dur mel1 + dur mel2) $ loop 10 $ rideCymbal $ delay 1 b1Соберём всё вместе и послушаем:
res =
chord [drums,
harmony,
high mel,
louder 40 cha,
rest 0]
main =
out resВ конце стоит фиктивный элемент rest 0 для того чтобы было удобно глушить инструменты комменти-
рованием.
21.6 Эффективное представление музыкальной нотации
Реализация, которую мы рассмотрели не эффективна, Мы могли бы определить тип Track
и по-другому.Мы очень часто пользуемся операцией delay через операцию line. Так в выражении: