Interacting with editorial markup
MEI has a feature that lets us encode variant “readings” of a musical text. These readings may come from different sources of the same piece. A common type of alternate reading is a “contrafactum”, or alternate text. Typically this might occur between a Latin sacred text and a secular text in a vernacular, such as English, both set to the same music.
Verovio supports the selection of variant readings encoded with MEI <app>
, containing <lem>
and <rdg>
elements. Only one variant can be displayed at a time, and this is selected when the file is loaded. By default, Verovio selects the <lem>
(or the first <rdg>
if no <lem>
is provided).
In this example we are going to create a basic interface to switch between variants by applying XPath queries for selecting specific readings, and then highlight the editorial markup in different colours. The MEI file we will use for this purpose comes from the Marenzio edition:
https://raw.githubusercontent.com/marenzio/marenzio.github.io/master/mei/M-04-6/M_04_6_02_Di_nettare_amoroso_ebro_la_mente.mei
Selection with an xpath query
The first thing we need is a variable to store the XPath queries. This must be an array, but we can start with an empty one, which applies the default behaviour:
let appXPath = [];
Since Verovio selects the elements to be displayed when loading the file, we need to define a loadFile()
function that applies the options, loads the file into Verovio, and renders it:
// A function that loads a file
function loadFile() {
fetch("https://raw.githubusercontent.com/marenzio/marenzio.github.io/master/mei/M-04-6/M_04_6_02_Di_nettare_amoroso_ebro_la_mente.mei")
.then((response) => response.text())
.then((meiXML) => {
tk.setOptions({
pageWidth: document.body.clientWidth,
pageHeight: document.body.clientHeight,
scale: 50,
scaleToPageSize: true,
appXPathQuery: appXPath
});
tk.loadData(meiXML);
notationElement.innerHTML = tk.renderToSVG(currentPage);
});
}
To load, or reload, the file we can now call the function:
loadFile();
In the CSS file we also have defined two rules:
g.lem {
fill: darkcyan;
}
g.rdg {
fill: crimson;
}
At this stage, because we have a editorial-markup.css
with some rule highlighting for lem
and rdg
classes, the default <lem>
should appear highlighted.
Switching between readings
To switch between the original and a reading (an English contrafactum text, in this case), we add two buttons, with two handlers that bind to the button event listeners:
<button id="original">Original</button>
<button id="contrafactum">Contrafactum</button>
const originalHandler = function () {
// Do something to render the original
}
const contrafactumHandler = function () {
// Do something to render the contrafactum
}
document.getElementById("original").addEventListener("click", originalHandler);
document.getElementById("contrafactum").addEventListener("click", contrafactumHandler);
The file example we are using has some readings encoded as follows:
<app>
<lem source="Italian">
<verse>
<syl>Di</syl>
</verse>
</lem>
<rdg source="English">
<verse>
<syl>When</syl>
</verse>
</rdg>
</app>
To select the “English” reading (i.e., in the contrafactumHandler
), we can write an xPath query selecting <rdg>
elements with the corresponding @source
value, in this case “English”, and then reload the file:
appXPath = ["./rdg[@source='English']"];
loadFile();
For the original, we can reset Verovio by passing in an empty appXPath
array:
appXPath = [];
loadFile();
When switching between the views you will notice that the colour of the text differs between the original <lem>
and the English <rdg>
.
Full example
Open this example in a new window.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Interacting with editorial markup</title>
<!-- A stylesheet for modifying the appearance of the editorial markup -->
<link href="editorial-markup.css" rel="stylesheet" type="text/css" />
<!-- Verovio -->
<script src="https://www.verovio.org/javascript/develop/verovio-toolkit-wasm.js" defer></script>
</head>
<body>
<button id="original">Original</button>
<button id="contrafactum">Contrafactum</button>
<button id="prevPage">Previous page</button>
<button id="nextPage">Next page</button>
<div id="notation"></div>
<script>
/**
Load Verovio
**/
document.addEventListener("DOMContentLoaded", (event) => {
verovio.module.onRuntimeInitialized = function () {
// This line initializes the Verovio toolkit
const tk = new verovio.toolkit();
// An array to keep xpath queries for selecting editorial markup
let appXPath = [];
// Keep a variable to the notation div id
let notationElement = document.getElementById("notation");
// A function that loads the file
function loadFile() {
fetch("https://raw.githubusercontent.com/marenzio/marenzio.github.io/master/mei/M-04-6/M_04_6_02_Di_nettare_amoroso_ebro_la_mente.mei")
// ... then receives the response and "unpacks" the MEI from it
.then((response) => response.text())
.then((meiXML) => {
// First we set the options, including the appXPathQuery one
tk.setOptions({
pageWidth: document.body.clientWidth,
pageHeight: document.body.clientHeight,
scale: 50,
scaleToPageSize: true,
appXPathQuery: appXPath
});
// ... then we can load the data into Verovio ...
tk.loadData(meiXML);
// ... and generate and set the SVG for the current page ...
notationElement.innerHTML = tk.renderToSVG(currentPage);
});
}
// The current page, which will change when playing through the piece
let currentPage = 1;
/**
Wire up the button to actually work.
*/
const nextPageHandler = function () {
currentPage = Math.min(currentPage + 1, tk.getPageCount());
notationElement.innerHTML = tk.renderToSVG(currentPage);
}
const prevPageHandler = function () {
currentPage = Math.max(currentPage - 1, 1);
notationElement.innerHTML = tk.renderToSVG(currentPage);
}
const originalHandler = function () {
appXPath = [];
loadFile();
}
const contrafactumHandler = function () {
appXPath = ["./rdg[@source='English']"];
loadFile();
}
/**
Wire up the buttons to actually work.
*/
document.getElementById("nextPage").addEventListener("click", nextPageHandler);
document.getElementById("prevPage").addEventListener("click", prevPageHandler);
document.getElementById("original").addEventListener("click", originalHandler);
document.getElementById("contrafactum").addEventListener("click", contrafactumHandler);
// This line fetches the MEI file we want to render...
loadFile();
}
});
</script>
</body>
</html>