Учебник по VexFlow.js

В этой статье я хочу рассказать про JavaScript библиотеку VexFlow и показать несколько примеров того что она умеет. Библиотека VexFlow предназначена для создания скриптов, при помощи которых будет происходить запись музыкального текста, с целью дальнейшей отрисовки этого текста в браузере. Она может быть полезной тем, кто собирается разрабатывать такие сервисы как: jellynote.com, musicnotes.com, музыкальный редактор (аналог Сибелиуса) или даже систему распознавания отсканированных музыкальных нот (что-то типа SharpEye)

Чтобы полноценно использовать данную библиотеку требуется некоторый опыт программирования на JavaScript и знание музыкальной теории, на уровне того как записывается музыка для различных музыкальных инструментов: что такое нотоносец, скрипичный и басовый ключи, как записываются ноты, длительности и т.д. Кстати, следует сказать, что VexFlow поддерживает работу как с Canvas, так и с SVG, благодаря чему у нас появляются безграничные оформительские возможности.

Подключаем библиотеку:

С помощью NPM
$ npm install vexflow

Через CDN

Либо по старинке – скачиваем архив (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.

Спасибо за внимание.