Using emscripten to port libmscore to Javascript

Updated 1 year ago

Setting up Libmscore emscripten development[a]

Getting Started

First, follow all of the steps outlined here for how to set up LLVM/clang, emscripten, and emscriptenqt

Next, you can either clone a version of libmscore already set up for emscripten here or you can try to clone a pre-qt5 version of libmscore and follow the notes in appendix A of the necessary changes to use emscripten/qt.

As of the time of writing, emscriptenqt will not work with the latest version of LLVM/clang, however, using the compatible version will generate build errors because of unsupported atomic calls. Since everything done in emscripten is basically synchronous, this is not really an issue, and these atomic methods can be stubbed out. I have already created a file called “atomic_fixed ” Move and rename this file (remove the “_fixed” suffix) to “emscripten/system/include/libcxx/atomic” where “emscripten” is the folder which contains your emscripten directory.

Once you have the libmscore code and emscripten and emscriptenqt set up, open up the “build.sh” file (or obtain one here ), and edit the “EMSCRIPTEN_FOLDER” to point to your folder which contains the emscripten and emscripten-qt folders.

When the build.sh file is set up correctly, you can simply run “./build.sh” and that will generate a “libmscore.js” file which contains libmscore code converted to javascript. Make sure that you have the valid EXPORTs from the emscripten setup process:

export PATH=/path/to/emscriptenqt/build-emscripten-qt/install/bin/:$PATH
export QMAKESPEC=/path/to/emscriptenqt/build-emscripten-qt/install/mkspecs/qws/emscripten-clang
export PATH=/path/to/emscriptenqt/emscripten/:$PATH

You will need these exports everytime before you build. Replace “path/to/emscriptenqt” with the path to your emscriptenqt folder.I recommend creating a simple shell script containing those lines, which you can simply run before the build.

Exposing Libmscore’s C++ methods to Javascript

Once libmscore.js has been generated successfully, there are still more steps to expose libmscore methods to Javascript. Any desired methods must be exported during the compile step (part of build.sh), and they must be added to Module in Javascript. Also, it’s usually useful to wrap C++ methods in an extern “C” {} section and stick to C-style calls, as these are easier to export.

For my purposes, I found it easiest to put all my wrapped functions into “embinds/classScore.cpp” : classScore.cpp . There you can see how I wrap C++ class methods and such, and the basic naming convention that I followed. This was mostly to prevent conflicts with existing libmscore code and for ease of use of exporting to javascript.

Next, add this method to the “-s EXPORTED_FUNCTIONS” array in the build.sh file: this line . Note, you do have to add a “_” before the function name when listing it in this array, even though that method doesn’t have a “_” in the source.

Finally, you call “Module.cwrap” from Javascript, before calling your method. This is explained in better detail here . Following my examples in test.html should be fairly straightforward: test.html. I put all my cwraps into one large “Libmscore” object.

It’s important to note that “Module.cwrap”, the emscripten functionality that allows calling these methods, only understands basic types: number, boolean, and String (for char*). Any pointers to objects are just treated as a number as an index to the emscripten’s heap object. So it’s perfectly fine to pass that number in and out of wrapped methods, but you can’t expect to do anything with that pointer in regular javascript, because to JS it will just be a number. With Strings, emscripten works some magic that copies a passed in string to its internal memory structure, and then passes that created pointer to the C++ method.

After you have successfully exported the method and cwrap’d it, you can call from normal javascript, like in test.html.

Appendix A: Modifying libmscore for emscripten

Foreward: this is a collection of my notes detailing the steps I took to convert a normal clone of MuseScore into the codebase I used for compiling with emscripten/qt. I don’t imagine these exact steps will work with any other version of MuseScore except for the specific one I used, but the process should be similar. It is therefore my hope that these notes may be useful for any interested party to replicate the process for a different version of MuseScore.

First I cloned MuseScore from this sha . I then copied the entirety of the “libmscore” folder out of that directory and into a different one. I then created a new repository in that directory. This allowed me to keep most of what I was doing separate from the rest of MuseScore, while pulling in only the pieces I needed.

I created a libmscore.pro qmake project file more or less following a template similar to mscoreserver's project file . I removed things which were not necessary for the emscripten port. The current .pro file I’m using (at the time of writing) looks like this . Of particular interest will be the cxxflags, sources and includes, the “QT += svg declarative”, and the precompiled header. I added “-std=gnu++11” and “-stdlib=libc++” lines, and removed “-fno-rtti.”

I also copied over the “all.h” precompiled header from the MuseScore repo into the new repo. This file could probably be generated during the build process, but for my purposes I did not pursue this route. There were some files I needed to remove from “all.h”, these included:
QWebView, QWebFrame, QtXml, QAbstractMessageHandler, QXmlSchema, QXmlSchemaValidator, QXmlStreamReader, QNetworkAccessManager, QNetworkReply, QNetworkCookieJar, QHostAddress, QUdpSocket, QHttpPart, QHttpMultiPart, QDeclarativeEngine, QDeclarativeComponent, QDeclarativeItem, and QDeclarativeView. “all.h” should end up looking something like this .

I copied over “config.h.in” from the MuseScore repo into the new repo, and renamed it “config.h” I had to mess around with some of the defines, such as USE_ALSA and other audio flags, resulting in this .

I had to add some missing includes to all.h ( and ).

Lots of methods which had to deal with midi events and synthesizer stuff had to be stubbed and commented out. Mostly inside of “instrument.h”, “undo.h”, “undo.cpp”, “instrtemplate.h”, “instrtemplate.cpp”, “instrument_p.h”, “instrument.cpp”, “keyfinder.cpp”, “layout.cpp”, “pitchspelling.cpp”, and “rendermidi.cpp”

Edited the synthesizer source files to remove and reference to “effects/effect.h” stuff, then removed that include from those files.

I had to rename “QZipReader”, “QZipWriter”, and “CentralFileHeader” because of conflicts with emscriptenqt classes. I simply added an “M” onto the end of all these classes.

I commented out the chord list loading stuff in “style.cpp”

I commented out a lot of the fonts in mscore.cpp so that I could test loading one font at a time to see which were giving issues.

After these steps I was able to get a very basic score file to load (this score ) and was able to verify some of the meta tags associated with it.