Выбрать главу

alignEvents :: [MidiEvent] -> [MidiEvent]

alignEvents es

| d < 0

= map (delay (abs d)) es

| otherwise = es

where d = minimum $ map eventStart es

Вызовем эту функцию сразу после функции trackEvents в функции groupInstr. Второй нюанс заключа-

ется в том, что каждый трек в midi-файле должен заканчиваться специальным сообщением, в библиотеке

HCodecs оно обозначается с помощью конструктора TrackEnd. В самом конце необходимо добавить сообще-

ние (0, TrackEnd):

toTrack :: Score -> M.Track M.Ticks

toTrack = addEndMsg . tfmTime . mergeInstr . groupInstr

addEndMsg :: 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 c

314 | Глава 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 100

b0 = bam 84

drums1 = 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 5

mel1 = 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. Так в выражении:

q = line [s1, s2, line [loop 2 s3, s4], s5]

Мы будем несколько раз обходить элемент s3 для каждого применения line. К примеру сначала мы

смести все элементы на 3, потом сместим на 5, потом на 10, но вместо этого мы могли бы сразу сместить