A Vue 3 component to display music scores with Verovio.
This component requires the use of an async wrapper around VerovioToolkit
to
ensure compatibility when using Verovio as a web worker. To set everything up
quickly this package exports helper functions: createAsyncVerovioToolkit
,
createWorkerVerovioToolkit
and createVerovioWorker
.
npm i vue-verovio-canvas verovio
How to use a web worker requires a somewhat advanced setup. And a bundler like Vite to get access to the web worker.
If you want to use the VerovioCanvas in several places in your project, it is best to save the verovio worker in a pinia store. Otherwiese, you can import it directly where you need it.
import { defineStore } from 'pinia';
import createVerovioWorker from '../workers/verovio.js?worker';
export const useVerovioWorker = defineStore('verovio_worker', {
state: () => {
return {
verovioWorker: createVerovioWorker(),
};
},
});
import createVerovioModule from 'verovio/wasm'; // use verovio/wasm-hum for humdrum support
import { VerovioToolkit, enableLog, LOG_OFF as logLevel } from 'verovio/esm';
import { createVerovioWorker } from 'vue-verovio-canvas';
createVerovioWorker(createVerovioModule, VerovioToolkit, enableLog, logLevel);
<script setup>
import 'vue-verovio-canvas/style.css';
import { VerovioCanvas, createWorkerVerovioToolkit } from 'vue-verovio-canvas';
const { verovioWorker } = useVerovioWorker();
const toolkit = createWorkerVerovioToolkit(verovioWorker);
</script>
<template>
<VerovioCanvas :toolkit="toolkit" />
</template>
To set up the web worker as an ES module, the following Vite configuration is required:
import { defineConfig } from 'vite';
export default defineConfig({
//...
worker: {
format: 'es',
},
optimizeDeps: {
// for Vite >= 4.3 && Vite < v5.1.1
exclude: ['verovio'],
},
});
Note that worker modules are currently (March 2023) not support in Firefox (https://caniuse.com/mdn-api_worker_worker_ecmascript_modules). But it looks like Firefox version 111 will implement this: https://bugzilla.mozilla.org/show_bug.cgi?id=1247687, mdn/content#24402, mdn/content#25030.
If you need to support Firefox it's recommended to setup your own worker file
and not import import { createVerovioWorker } from 'vue-verovio-canvas';
as
Firefox will throw an error import declarations may only appear at top level of a module. Use
importScripts()` instead.
For Vite >= 4.3 and < 5.1.1 it is necessary to exclude Verovio from
optimizeDeps
in your Vite config. For further information, have a look at
vitejs/vite#13530 and
vitejs/vite#14169
<script setup>
import 'vue-verovio-canvas/style.css';
import { VerovioCanvas, createAsyncVerovioToolkit } from 'vue-verovio-canvas';
import { VerovioToolkit } from 'verovio/esm';
import createVerovioModule from 'verovio/wasm-hum';
const toolkit = ref(null);
createVerovioModule().then(VerovioModule => {
toolkit.value = createAsyncVerovioToolkit(new VerovioToolkit(VerovioModule));
});
</script>
<template>
<VerovioCanvas :toolkit="toolkit" />
</template>
Prop | Default | Description |
---|---|---|
toolkit | – | A createWorkerVerovioToolkit or createAsyncVerovioToolkit instance |
url | – | URL of the score file to display |
data | – | String of the score to display when url prop is not set |
scale | 40 |
Scale of the output in percent (min: 1; max: 1000) and fallback value for options.scale |
viewMode | vertical |
page , horizontal , vertical |
pageMargin | 0 |
Change all page margins at once and fallback value when top, right, left or bottom page margins are not set |
options | – | Pass options to verovio; Use verovio -h in the CLI to check all options and values |
select | {} |
Select a portion of a score; e.g. {measureRange: '8-end'} , {start: 'measure-L337', end: 'measure-L355'} |
lazy | false |
Lazy load verovio score when visible on screen |
unload | false |
Emits unload event when VerovioCanvas component is not intersecting with the viewport; loading and unloading the toolkit needs to be handled manually (see load and unload events) |
lazyDelay | 0 |
Delay before lazy loading the score to prevent always loading the toolkit with fast scrolling |
Event | Description |
---|---|
moduleIsReady |
Emitted when the emscripten module is ready |
scoreIsReady |
Emitted when verovio toolkit method loadData did load the score |
scoreLoaded |
Emitted directly after the score is loaded with Verovios loadData method |
load |
Emitted when lazy prop is true and component is intersecting with viewport |
unload |
Emitted when lazy prop is true and component stops intersecting with viewport |
If you want to display the whole score at once with the available width of the
parent container element and the hight of the score adjusted to the required
height use the vertical
view mode:
<VerovioCanvas url="/file.mei" view-mode="vertical" />
Or use the horizontal
view mode to make the score scrollable on the x-axis:
<VerovioCanvas url="/file.mei" view-mode="horizontal" />
If you want to use pagination wrap the <VerovioCanvas />
component in a
container element with specific dimensions:
<div style="width: 640px; height: 360px;">
<VerovioCanvas url="/file.mei" view-mode="page" />
</div>
You can also use the wrapper element with specific dimensions if you want to use
vertical
view mode but with scrollable on the y-axis.
The <VerovioCanvas>
component exposes the following options
defineExpose({
isLoading,
dimensions,
page,
callVerovioMethod,
nextPage,
prevPage,
setPage,
});
These exposed variables and methods can be used to interact with the
VerovioCanvas
component:
<script setup>
import { ref, computed } from 'vue';
const scale = ref(30);
const score = ref(null);
const viewMode = ref('page');
function next() {
score.value.nextPage();
}
function prev() {
score.value.prevPage();
}
const page = computed({
get() {
return score.value && score.value.page;
},
set(value) {
score.value.setPage(value);
},
});
</script>
<template>
<input type="range" min="1" max="100" v-model.number="scale" />
<select v-model="viewMode">
<option value="page">page</option>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
</select>
<button @click="prev">prev page</button>
<input type="text" v-model.lazy="page" />
<button @click="next">next page</button>
<div style="width: 640px; height: 360px">
<VerovioCanvas ref="score" url="https://raw.githubusercontent.com/WolfgangDrescher/lassus-geistliche-psalmen/master/kern/01-beatus-vir.krn" :scale="scale" :view-mode="viewMode" />
</div>
</template>
Use callVerovioMethod
to call other methods on the Verovio toolkit instance.
callVerovioMethod
is async to ensure that the Verovio runtime is initialized
and the score if already loaded.
<script setup>
import { ref, onMounted } from 'vue';
const score = ref(null);
onMounted(() => {
score.value.callVerovioMethod('renderToMIDI', {
midiTempoAdjustment: 1.25,
}).then(midiBase64 => {
console.log(midiBase64);
});
});
</script>
<template>
<VerovioCanvas ref="score" url="https://raw.githubusercontent.com/WolfgangDrescher/lassus-geistliche-psalmen/master/kern/01-beatus-vir.krn" />
</template>
When you have a lot of scores per page that need to be rendered with verovio it
might be useful to not only lazy load the component, but also only create the
toolkit when the component is actually needed and within the viewport and detroy
the toolkit when it is outside of the viewport. Use in combination with lazy
and lazyDelay
props.
<script setup>
import { ref } from 'vue';
import 'vue-verovio-canvas/style.css';
import { VerovioCanvas, createWorkerVerovioToolkit } from 'vue-verovio-canvas';
const toolkit = ref();
const { verovioWorker } = useVerovioWorker();
function loadToolkit() {
toolkit.value = createWorkerVerovioToolkit(verovioWorker);
}
function unloadToolkit() {
if (toolkit.value) {
toolkit.value.removeToolkit();
toolkit.value = null;
}
}
</script>
<template>
<VerovioCanvas :toolkit="toolkit" lazy unload :load-delay="1000" @load="loadToolkit" @unload="unloadToolkit" />
</template>
Errors may occur when the component is loaded with server side rendering. Try to
load the VerovioCanvas
component in wrapped in ClientOnly
. If not available
you can try something like this:
<script setup>
import { defineAsyncComponent } from 'vue';
import 'vue-verovio-canvas/dist/style.css';
const AsyncVerovioCanvas = defineAsyncComponent(() => import('vue-verovio-canvas'));
</script>
<template>
<component :is="AsyncVerovioCanvas"></component>
</template>