Skip to content

Instantly share code, notes, and snippets.

@WolfgangDrescher
Last active October 22, 2020 23:54
Show Gist options
  • Save WolfgangDrescher/63d24cd755e648afa998270dd3d7c283 to your computer and use it in GitHub Desktop.
Save WolfgangDrescher/63d24cd755e648afa998270dd3d7c283 to your computer and use it in GitHub Desktop.
Verovio JavaScript Worker
<div id="score"></div>
<script src="/verovio-toolkit-proxy.js"></script>
<script>
// Create a new instance of VerovioToolkitProxy
const proxy = new VerovioToolkitProxy();
// Listen and wait for Module to emit onRuntimeInitialized
proxy.onRuntimeInitialized().then(async () => {
// Fetch a MEI file
const response = await fetch('file.mei');
const data = await response.text();
// Set inner HTML of #score to verovio response
// Note that method calls on VerovioToolkitProxy instances are asynchronous
// and will return Promises. So the must be awaited with the `await`
// keyword or `.then(() => {...})`
document.getElementById('score').innerHTML = await proxy.loadData(data);
});
</script>
// Use factory pattern to export VerovioToolkitProxy
(function (root, factory) {
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = factory();
} else {
root.VerovioToolkitProxy = factory();
}
})(this, function () {
// Wrapper class to allow to resolve a Promise outside of its scope
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.reject = reject
this.resolve = resolve
});
}
}
// HashMap to store all method calls on the verovio toolkit worker
const workerTasks = {};
// Helper function to create a UUID
const uuidv4 = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
// Helper function to identify method calls in worker onMessage listener
const createTaksId = function () {
let id;
do {
id = uuidv4();
} while (workerTasks[id]);
return id;
};
class VerovioToolkitProxy {
constructor() {
this.worker = new Worker('verovio-worker.js');
// Listen to response of the service worker
this.worker.addEventListener('message', function (event) {
const { id, result } = event.data
// Check if there is a Deferred instance in workerTasks HashMap with
// passed id of the Worker
const deferred = workerTasks[id];
if(deferred) {
// If so resolve deferred promise and pass verovio toolkit return value
deferred.resolve(result);
}
}, false);
// Retrun a Proxy so it is possible to catch all property and method calls
// of a VerovioToolkitProxy instance
return new Proxy(this, {
get: (target, method) => {
return function () {
const args = Array.prototype.slice.call(arguments);
const id = createTaksId();
// Post a message to service worker with UUID, method name of the
// verovio toolkit function and passed arguments
target.worker.postMessage({
id,
method,
arguments: args,
});
// Create a new Deferred instance and store it in workerTasks HashMap
const deferred = new Deferred();
workerTasks[id] = deferred;
// Return the (currently still unresolved) Promise of the Deferred instance
return deferred.promise;
};
},
});
}
}
// Pass VerovioToolkitProxy to factory
return VerovioToolkitProxy;
});
importScripts("verovio-toolkit-2.5.0.js");
// Check if we are in a web worker
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
// Initializing an empty object to prevent if in onMessage listener the toolkit
// is beeing accessed before Module.onRuntimeInitialized
let verovioToolkit = {};
// Wrapper class to allow to resolve a Promise outside of its scope
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.reject = reject
this.resolve = resolve
});
}
}
// Global deferred Promise that can be resolved when Module is initialized
const moduleIsReady = new Deferred();
// Create a new toolkit instance when Module is ready
Module.onRuntimeInitialized = function () {
verovioToolkit = new verovio.toolkit();
moduleIsReady.resolve();
};
// Listen to messages send to this worker
addEventListener('message', function (event) {
// Destruct properties passed to this message event
const { id, method, arguments } = event.data;
// postMessage on a `onRuntimeInitialized` method as soon as
// Module is initialized
if(method === 'onRuntimeInitialized') {
moduleIsReady.promise.then(() => {
postMessage({
id,
method,
arguments,
result: null,
}, event);
});
return;
}
// Check if verovio toolkit has passed method
const fn = verovioToolkit[method || null];
let result;
if(fn) {
// Call verovio toolkit method and pass arguments
result = fn.apply(null, arguments || []);
}
// Always respond to worker calls with postMessage
postMessage({
id,
method,
arguments,
result,
}, event);
}, false);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment