Open supersational opened 6 years ago
I've been trying a similar route in Jupyter notebooks, but for some reason can't seem to see the required opensheetmusicdisplay
function?
Here's the recipe I was trying:
from music21 import *
c = chord.Chord("C4 E4 G4")
xml = open(c.write('musicxml')).read()
html='''
<div id='main-div'></div>
<script>
var openSheetMusicDisplay = new opensheetmusicdisplay.OpenSheetMusicDisplay("main-div");
openSheetMusicDisplay
.load('{data}')
.then(
function() {{ openSheetMusicDisplay.render(); }} );
</script>'''.format(data=xml.replace('\n','').replace('\t','')).replace('\n','')
from IPython.display import HTML, Javascript
Javascript('https://cdn.jsdelivr.net/npm/opensheetmusicdisplay@0.3.1/build/opensheetmusicdisplay.min.js')
HTML(html)
Error is:
Javascript error adding output!
ReferenceError: opensheetmusicdisplay is not defined
See your browser Javascript console for more details.
It strikes me that opensheetmusicdisplay
is a great way to go for Jupyter notebooks, perhaps implemented via a notebook extension, or even IPython magic.
I've also just come across https://github.com/akaihola/jupyter_abc, a notebook extension "for rendering ABC markup as graphical music notation in a Jupyter notebook", although it doesn't seem to work on Azure notebooks, which is where I'm trying to demo things. I'm not sure if there's a straightforward root to using this with music21
too?
Yep, unfortunately Jupyter notebooks don't have the best JavaScript debugging support.
I've now got a working script that displays most scores in notebooks too:
from IPython.core.display import display, HTML, Javascript
import json, random
def showScore(score):
xml = open(score.write('musicxml')).read()
showMusicXML(xml)
def showMusicXML(xml):
DIV_ID = "OSMD-div-"+str(random.randint(0,1000000))
print("DIV_ID", DIV_ID)
display(HTML('<div id="'+DIV_ID+'">loading OpenSheetMusicDisplay</div>'))
print('xml length:', len(xml))
script = """
console.log("loadOSMD()");
function loadOSMD() {
return new Promise(function(resolve, reject){
if (window.opensheetmusicdisplay) {
console.log("already loaded")
return resolve(window.opensheetmusicdisplay)
}
console.log("loading osmd for the first time")
// OSMD script has a 'define' call which conflicts with requirejs
var _define = window.define // save the define object
window.define = undefined // now the loaded script will ignore requirejs
var s = document.createElement( 'script' );
s.setAttribute( 'src', "https://cdn.jsdelivr.net/npm/opensheetmusicdisplay@0.3.1/build/opensheetmusicdisplay.min.js" );
//s.setAttribute( 'src', "/custom/opensheetmusicdisplay.js" );
s.onload=function(){
window.define = _define
console.log("loaded OSMD for the first time",opensheetmusicdisplay)
resolve(opensheetmusicdisplay);
};
document.body.appendChild( s ); // browser will try to load the new script tag
})
}
loadOSMD().then((OSMD)=>{
console.log("loaded OSMD",OSMD)
var div_id = "{{DIV_ID}}";
console.log(div_id)
window.openSheetMusicDisplay = new OSMD.OpenSheetMusicDisplay(div_id);
openSheetMusicDisplay
.load({{data}})
.then(
function() {
console.log("rendering data")
openSheetMusicDisplay.render();
}
);
})
""".replace('{{DIV_ID}}',DIV_ID).replace('{{data}}',json.dumps(xml))
display(Javascript(script))
return DIV_ID
It also returns the ID of the div element, in case we want to use it for something later.
I can't link to a notebook here, but this PDF shows what it's capable of. showScore demo.pdf
Apart from a few hiccups with lyric character-encoding it looks pretty good!
(It can also fail silently to render scores without any notes)
Very cool -- do you want to try to get it into a converter - output format? if so, my comments would be: try to be deterministic on the random id so it can't possibly fail (random concat w/ time.time() is generally good). Make sure that importing still works w/o IPython (of course this won't work). Then make it invokable with c.show('ipython.osmd') -- and I'll handle making config settings for it.
Yep, was hoping to! Would you recommend using ConverterIPython as a base class?
@supersational Lovely... works for me w/ demo score:
import random
selected_piece = random.choice(corpus.getPaths())
score = corpus.parse(selected_piece)
I notice that the score-part
ID value in the MusicXML is being displayed - and wondered if there's an easy way to hide that via an OpenSheetMusicDisplay parameter or otherwise set it via music21
?
Probably something that the part.partId can change.
I have a demo notebook on Azure notebooks here, although it's quite slow to install music21
(a prebuilt binderhub demo would be quicker) : https://notebooks.azure.com/OUsefulInfo/libraries/gettingstarted/html/4.1.0%20Music%20Notation.ipynb
@mscuthbert any tips on how to make it invokable via s.show()? I can't find where the other classes register themselves for that to work.
Ah, I was going to point to http://web.mit.edu/music21/doc/usersGuide/usersGuide_54_extendingConverter.html but apparently, we haven't demonstrated an output format. I'll look into it -- but probably won't have time till the weekend at earliest (first week of university teaching starting now).
The weekend came very fast. Added to converter.subConverters.py -- a way of getting to the Chant-notation parser in music21.volpiano. It was already done, but I hadn't integrated it to converter.
class ConverterVolpiano(SubConverter):
'''
Reads or writes volpiano (Chant encoding).
Normally, just use 'converter' and .show()/.write()
>>> p = converter.parse('volpiano: 1---c-d-ef----4')
>>> p.show('text')
{0.0} <music21.stream.Measure 0 offset=0.0>
{0.0} <music21.clef.TrebleClef>
{0.0} <music21.note.Note C>
{1.0} <music21.note.Note D>
{2.0} <music21.note.Note E>
{3.0} <music21.note.Note F>
{4.0} <music21.volpiano.Neume <music21.note.Note E><music21.note.Note F>>
{4.0} <music21.bar.Barline style=double>
>>> p.show('volpiano')
1---c-d-ef----4
'''
registerFormats = ('volpiano',)
registerInputExtensions = ('volpiano', 'vp')
registerOutputExtensions = ('txt', 'vp')
def parseData(self, dataString, **keywords):
from music21 import volpiano
breaksToLayout = keywords.get('breaksToLayout', False)
self.stream = volpiano.toPart(dataString, breaksToLayout=breaksToLayout)
def getDataStr(self, obj, *args, **keywords):
'''
Get the raw data, for storing as a variable.
'''
from music21 import volpiano
if (obj.isStream):
s = obj
else:
s = stream.Stream()
s.append(obj)
return volpiano.fromStream(s)
def write(self, obj, fmt, fp=None, subformats=None, **keywords): # pragma: no cover
dataStr = self.getDataStr(obj, **keywords)
self.writeDataStream(fp, dataStr)
return fp
def show(self, obj, *args, **keywords):
print(self.getDataStr(obj, *args, **keywords))
once it's set, there are a few tests in converter/__init__.py
that will need to pass.
@mscuthbert @psychemedia @supersational I saw some of the discussion on the mailing list re: Azure notebooks, cloud. I decided to give this a go using Binder (https://mybinder.org).
I've got all running but the rendering of the sheetmusic. Though I am able to install musescore and lilypond into the container, I'm getting an error
SubConverterException: To create PNG files directly from MusicXML you need to download MuseScore and put a link to it in your .music21rc via Environment.
Here's the branch from my fork: https://github.com/willingc/music21/tree/binderize Press the Launch Binder button in the README or click here https://mybinder.org/v2/gh/willingc/music21/binderize
hey @willingc, this is fantastic! I've got it up and running in a binder of my repo.
Still finishing off the PR for this, but it's working. You can create a new notebook in the root directory and execute the following:
from music21 import *
s = converter.parse("tinyNotation: 3/4 E4 r f# g=lastG trip{b-8 a g} c4~ c")
s.show('osmd')
Hope this is the right URL for sharing: https://mybinder.org/v2/gh/supersational/music21/opensheetmusicdisplay
P.S. the nice thing about this is you shouldn't have to install either musescore or lilypond for this to work, it's pure musicXML -> JS rendering!
@supersational - that suggested demo not working for me? I see an output message of the form:
OSMD-div-RANDOM-ID
but no notation display?
@psychemedia my bad, should work now with the latest commits (use the same link)
Just wondering if this is still actively being worked on? Thanks!
@mscuthbert I'll have another go at finishing this over the next few days.. greatly appreciate the detailed comments you've made on the pull request, but have simply been busy.
Wondering if this stalled again?
@willingc if I use that Binder, and try the following (taken from https://github.com/cuthbertLab/music21/pull/326#issuecomment-429867351) in a notebook:
!pip install matplotlib
import matplotlib
%matplotlib inline
import music21
s = music21.converter.parse("tinyNotation: 3/4 E4 r f# g=lastG trip{b-8 a g} c4~ c")
s.show('osmd')
I get Music21ObjectException: cannot support showing in this format yet: osmd
(the PR is yet to be accepted...).
The PR is currently working fine but I don't have the time to finish the testing/refactoring unfortunately.
Link to it directly and it does work using the above code (matplotlib no longer needed): https://mybinder.org/v2/gh/supersational/music21.git/opensheetmusicdisplay
@supersational Okay - thanks, will do... My own music related demo notebooks are in various stages of broken and I was hoping to try to tidy them up this weekend with this renderer at the core.
@supersational If you give us push access to your branch for the PR, we could likely clean up the PR with tests/refactor.
@willingc that would be fantastic! Feel free to make any changes as you see fit, you should both have push access now.
I ran into this excellent library for rendering sheet music in-browser using MusicXML, and wondered if it could be used with music21: Open Sheet Music Display
Turns out it is possible with a bit of js-hackery! I've posted the script below. This makes it easy to display scores without installing any other programs (apart from a web browser).
The script requires the following file in the same directory (I can't upload .js files here, but it works fine as .txt). It's simply a compiled version of the OSMD project. opensheetmusicdisplay.min.js.txt
This should display a random score from the corpus, enjoy!