Учебник по VexFlow.js
В этой статье я хочу рассказать про JavaScript библиотеку VexFlow и показать несколько примеров того что она умеет. Библиотека VexFlow предназначена для создания скриптов, при помощи которых будет происходить запись музыкального текста, с целью дальнейшей отрисовки этого текста в браузере. Она может быть полезной тем, кто собирается разрабатывать такие сервисы как: jellynote.com, musicnotes.com, музыкальный редактор (аналог Сибелиуса) или даже систему распознавания отсканированных музыкальных нот (что-то типа SharpEye)
Чтобы полноценно использовать данную библиотеку требуется некоторый опыт программирования на JavaScript и знание музыкальной теории, на уровне того как записывается музыка для различных музыкальных инструментов: что такое нотоносец, скрипичный и басовый ключи, как записываются ноты, длительности и т.д. Кстати, следует сказать, что VexFlow поддерживает работу как с Canvas, так и с SVG, благодаря чему у нас появляются безграничные оформительские возможности.
Подключаем библиотеку:
С помощью NPM
$ npm install vexflow
Через CDN
- Debug version: https://unpkg.com/vexflow/releases/vexflow-debug.js
- Minified version: https://unpkg.com/vexflow/releases/vexflow-min.js
Либо по старинке – скачиваем архив (https://github.com/0xfe/vexflow/archive/master.zip) в папку с проектом, ну а дальше сами знаете.
Нотный стан:
Запись музыкального произведения начинается со строки состоящей из пяти параллельных линий, которая называется нотным станом или нотоносцем. В самом начале этой строки нужно указать ключ и обозначить размер. Этим мы сейчас и займемся.
В HTML-файле нам потребуется всего лишь один элемент:
<div id="music-notebook"></div>
Имя идентификатора указываем на свой вкус.
Начинаем скриптовать.
Работа с библиотекой начинается с выбора базовых параметров. Нам нужно указать элемент, внутри которого будет происходить запись нотного текста, выбрать формат рендеринга (я предпочитаю SVG) и установить необходимые размеры для нашего «полотна»
VF = Vex.Flow;
// Создаём SVG рендер и закрепляем его за элементом с ID "music-notebook"
var elem = document.getElementById("music-notebook");
var renderer = new VF.Renderer(elem, VF.Renderer.Backends.SVG);
// Указываем размер:(<ширина>, <высота>)
renderer.resize(500, 200);
// Получаем контекст рисования
var context = renderer.getContext();
По сути, мы создали цифровую версию нотной тетради, внутри которой можно нарисовать любой музыкальный символ. Самое время заняться творчеством.
Для начала нарисуем нотный стан, разместим на нём скрипичный ключ и укажем музыкальный размер в четыре четверти:
// Создаем нотную строку со следующими параметрами:(<x>, <y>, <ширина>)
var fiveLineStave = new VF.Stave(10, 25, 400);
// Добавляем скрипичный ключ и указываем музыкальный размер
fiveLineStave.addClef("treble").addTimeSignature("4/4");
// Указываем контекст и даём команду рисовать
fiveLineStave.setContext(context).draw();
Вот как это выглядит: [запустить]
А теперь немного разъяснений:
При создании «нотной тетради» я решил использовать формат SVG:
var renderer = new VF.Renderer(elem, VF.Renderer.Backends.SVG);
Разумеется, у вас есть выбор, и вместо VF.Renderer.Backends.SVG
вы можете указать VF.Renderer.Backends.CANVAS
После создания «нотной тетради» весь дальнейший процесс будет сводиться к созданию VexFlow-элементов, размещению их в контексте нашей тетради и отрисовки с помощью метода .draw().
Каждый VexFlow-элемент (VF) представляет собой тот или иной музыкальный символ. Например, VF.Stave
– нотная строка, VF.StaveNote
– какая-то нота, VF.Beam
– горизонтальное ребро для группировки нот и т.д. Разумеется, что у каждого VF-элемента есть соответствующие параметры.
Создавая нотную строку, я указал начало координат относительно нашего полотна:
var fiveLineStave = new VF.Stave(10, 25, 400);
Таким образом я зарезервировал немного места сверху, для того чтобы в дальнейшем оставить там какую-нибудь заметку, например, forte. Если вы ничего такого не предполагаете, то укажите координаты: 0, 0.
Ещё может возникнуть вопрос, почему мы делаем не так – fiveLineStave.draw()
, а так – fiveLineStave.setContext(context).draw();
Отвечаю – учитесь думать головой! Шутка! Дело в том, что у fiveLineStave
нет метода draw()
. Зато у нашего контекста (нашей «нотной тетради») есть такой метод! Этот и множество других замечательных методов ;) Кстати, контекст вообще очень гибко настраивается. Вы можете управлять такими вещами как цвет, шрифт, масштабирование: context.setStrokeStyle('red');
context.setLineWidth(50);
и т.д.
Кстати, а попробуйте переделать наш пример таким образом, чтобы вместо скрипичного ключа стоял басовый, а музыкальный размер был в шесть восьмых. Получилось? Не сомневаюсь! ;-)
Ноты, длительности, простая мелодия:
Для начала рассмотрим общие принципы создания партитур в данной библиотеке, ну а сразу после примера пробежим по нюансам.
Размещая в определенной последовательности ноты, мы создаем мелодию, так называемый голос. Делается это с помощью конструктора new VF.Voice()
Если мелодию нужно обогатить, мы создаем ещё один голос, и так далее. В момент, когда у нас появляется второй голос, наша партитура представляет собой полифоническую музыку, т.е. многоголосье. Голоса можно группировать в группы – VoiceGroup
.
После того как ввод закончен, мы вызываем метод, который выполнит отрисовку, так называемый рендеринг. Но, перед этим, к введенной партитуре рекомендуется применить методы класса Formatter()
, для того чтобы все ноты выровнялись равномерно, и получилась красивая партитурная композиция – типографский партитурный ритм. Автор библиотеки выявил правила красивого партитурного оформления и упаковал их в соответствующий класс, за что ему отдельные респект и уважуха.
var notes = [
// Нота «До»
new VF.StaveNote({clef: "treble", keys: ["c/4"], duration: "q" }),
// Нота «Ре»
new VF.StaveNote({clef: "treble", keys: ["d/4"], duration: "q" }),
// Четвертная пауза. С помощью поля keys: ["b/4"] мы указали её позицию
// по вертикали. Поэкспериментируйте ради прикола.
new VF.StaveNote({clef: "treble", keys: ["b/4"], duration: "qr" }),
// Большой мажорный септаккорд – Cmaj7
new VF.StaveNote({clef: "treble", keys: ["c/4", "e/4", "g/4", "b/4"], duration: "q" })
];
// Создаем объект «Голос» с размером состоящим из четырёх долей,
// каждая из которых по длительности равна четвертной ноте,
// и помещаем в него нашу мелодию
var voice = new VF.Voice({num_beats: 4, beat_value: 4});
voice.addTickables(notes);
// Равномерно выровняем все ноты и знаки
var formatter = new VF.Formatter().joinVoices([voice]).format([voice], 400);
// Отрисовываем (рендерим)
voice.draw(context, stave);
Давайте посмотрим, как это выглядит: [запустить]
Итак, с помощью конструктора StaveNote
мы можем создавать ноту, трезвучие или аккорд. Объединив ноты в массив и применив соответствующий метод (см. в пример) мы получаем голос.
Пара слов о параметрах:
Поле clef
не является обязательным параметром. Т.е. clef: "treble"
указывать необязательно, если вам, конечно, не потребовалось в каком-нибудь месте партитуры сменить ключ. Более того, скрипичный ключ в данной библиотеке устанавливается в качестве дефолтного значения. Как-то так:
Flow.keyProperties = (key, clef, params) => { if (clef === undefined) { clef = 'treble'; }
Рассмотрим оставшиеся параметры:
keys: ["c/4"]
– так в нашей библиотеке обозначается «до» первой октавы. Если вам это кажется удивительным, то уверяю вас – ничего удивительного в этом нет. Это вполне обычная запись так называемой «научной нотацией», предложенной в 1939 году Американским акустическим обществом. В этой нотации номер октавы записывается сразу после обозначения ступени, при этом октавная система индексируется (нумеруются) с субконтроктавы, которой присваивается номер 0. Соответственно, контроктаве присваивается номер 1, большой октаве номер 2 и т.д.
В поле duration
обозначается длительность ноты. "q"
– обозначает четвертную ноту (сокращение от слова «quarter-note») Также можно использовать цифры.
Двигаем дальше.
Добавим второй голос в нашу мелодию: [запустить]
var notes = [
new VF.StaveNote({clef: "treble", keys: ["c/4"], duration: "q" }),
new VF.StaveNote({clef: "treble", keys: ["d/4"], duration: "q" }),
new VF.StaveNote({clef: "treble", keys: ["b/4"], duration: "qr" }),
new VF.StaveNote({clef: "treble", keys: ["c/4", "e/4", "g/4", "b/4"], duration: "q" })
];
var notes2 = [
new VF.StaveNote({clef: "treble", keys: ["f/3"], duration: "w" })
];
var voices = [
new VF.Voice({num_beats: 4, beat_value: 4}).addTickables(notes),
new VF.Voice({num_beats: 4, beat_value: 4}).addTickables(notes2)
]
var formatter = new VF.Formatter().joinVoices(voices).format(voices, 400);
voices.forEach(function(v) { v.draw(context, stave); })
Второй голос представляет собой целую ноту – duration: "w"
, сокращение от whole note.
И конечно, из-за того, что у нас образовался массив голосов (var voices = […]), их отрисовка происходит в цикле.
Модификаторы:
Модификаторами в изучаемой нами библиотеке называются такие элементы музыкальной нотации, которые относятся к знакам альтерации, артикуляции, мелизмам. Т.е. диезы, бемоли, знаки стаккато, вибрато и т.д. Модификаторы наследуются от базового класса VF.Modifier
, например, VF.Accidental
– знаки альтерации, VF.Vibrato
– для вибрато и других мелизмов, VF.Annotation
– для аннотаций и т. д.
Посмотрим, как работать с модификаторами на примере двух септаккордов:
var notes = [
new VF.StaveNote({ keys: ["d/4", "f/4", "a/4", "c/5"], duration: "h" }).
addAccidental(1, new VF.Accidental("#")).
addAccidental(2, new VF.Accidental("#")).
addAccidental(3, new VF.Accidental("#")),
new VF.StaveNote({ keys: ["d/4", "f/4", "a/4", "c/5"], duration: "h" }).
addAccidental(1, new VF.Accidental("n")).
addAccidental(2, new VF.Accidental("b")).
addAccidental(3, new VF.Accidental("b")),
];
VF.Formatter.FormatAndDraw(context, stave, notes);
Собственно, вот что у нас получилось: [запустить]
Полагаю, что данный пример настолько нагляден, что в дополнительных комментариях не нуждается.
И ещё один фрагмент, для разнообразия:
var notes = [
new VF.StaveNote({keys: ["e##/5"], duration: "8d" }).
addAccidental(0, new VF.Accidental("##")).addDotToAll(),
new VF.StaveNote({keys: ["eb/5"], duration: "16" }).
addAccidental(0, new VF.Accidental("b")),
new VF.StaveNote({keys: ["d/5", "eb/4"], duration: "h" }).addDot(0),
new VF.StaveNote({keys: ["c/5", "eb/5", "g#/5"], duration: "q" }).
addAccidental(1, new VF.Accidental("b")).
addAccidental(2, new VF.Accidental("#")).addDotToAll()
];
VF.Formatter.FormatAndDraw(context, stave, notes);
В результате мы имеем вот что: [запустить]
Попробуйте самостоятельно заменить дубль-диез дубль-бемолем, и сделать двойное увеличение продолжительности для ноты с точкой (для любой) В общем, попробуйте оба метода: addDot(<параметр>)
и addDotToAll()
. Я в вас верю ;)
Вместо заключения:
Вот и подошла к концу первая часть туториала по VexFlow.js. Это была ознакомительная часть, что называется, небольшая демонстрация возможностей. Впереди ожидается ещё две части, в которых я планирую показать, как научиться создавать профессиональные музыкальные нотации для различных инструментов: от фортепиано, где требуется объединение двух нотоносцев со скрипичным и басовым ключом, до гитарных табулатур. Также следует ожидать исчерпывающие сведения обо всех параметрах и методах доступных в данной библиотеке. Что-то типа путеводителя по API.
Спасибо за внимание.