# ------------------------------------------------------------------------------
# Name: vexflow/toMusic21j.py
# Purpose: music21 classes for converting music21 objects to music21j
#
# Authors: Michael Scott Asato Cuthbert
# based on an earlier version by Christopher Reyes
#
# Copyright: Copyright © 2012-14 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Convert a music21 object into JSON and send it to the browser for music21j to use.
'''
from __future__ import annotations
import unittest
from music21.exceptions21 import Music21Exception
from music21 import freezeThaw
from music21 import stream
supportedDisplayModes = [
'html',
'jsbody',
'jsbodyScript',
'json'
]
[docs]
def fromObject(thisObject, *, mode='html', local=False):
'''
returns a string of data for a given Music21Object such as a Score, Note, etc. that
can be displayed in a browser using the music21j package. Called by .show('vexflow').
>>> n = note.Note('C#4')
>>> #_DOCS_SHOW print(vexflow.toMusic21j.fromObject(n))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- for MSIE 10 on Windows 8 -->
<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true"/>
<title>Music21 Fragment</title>
<script data-main='https://www.music21.org/music21j/src/music21'
src='https://www.music21.org/music21j/ext/require/require.js'></script>
<script>
require(['music21'], function() {
var pickleIn = '{"m21Version": {"py/tuple": [1, 9, 2]}, "stream":
{"_mutable": true, "_activeSite": null, "' +
'_priority": 0, "_elements": [], "_cache": {}, "definesExplicitPageBreaks":
false, "_unlinkedDuration": null, "' +
'id": ..., "_duration": null, "py/object": "music21.stream.Stream",
"streamStatus": {"py/object": "music' +
'21.stream.streamStatus.StreamStatus", "_enharmonics": null,
"_dirty": null, "_concertPitch": null, "_accidentals"' +
': null, "_ties": null, "_rests": null, "_ornaments": null,
"_client": null, "_beams": null, "_measures": nu' +
...
'd": null}, "definesExplicitSystemBreaks": false, ...}}';
var jpc = new music21.fromPython.Converter();
streamObj = jpc.run(pickleIn);
streamObj.renderOptions.events.resize = "reflow";
streamObj.appendNewCanvas();
});
</script>
<BLANKLINE>
</head>
<body>
</body>
</html>
* Changed in v8: mode and useLocal are keyword only.
'''
conv = VexflowPickler()
conv.mode = mode
conv.useLocal = local
return conv.fromObject(thisObject)
[docs]
class VexflowPickler:
templateHtml = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' + '''
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- for MSIE 10 on Windows 8 -->
<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true"/>
<title>{title}</title>
{loadM21Template}
{jsBodyScript}
</head>
<body>
</body>
</html>
''')
jsBodyScript = '''<script>\n{jsBody}\n</script>'''
jsBody = '''require(['music21'], function() {{
var pickleIn = {pickleOutput};
var jpc = new music21.fromPython.Converter();
streamObj = jpc.run(pickleIn);
{callback}
}});'''
loadM21Template = '''<script data-main='{m21URI}' src='{requireURI}'></script>'''
def __init__(self):
self.defaults = {
'pickleOutput': '{"py/object": "hello"}',
'm21URI': 'https://www.music21.org/music21j/src/music21',
'requireURI': 'https://www.music21.org/music21j/ext/require/require.js',
'callback': (
'streamObj.renderOptions.events.resize = "reflow";'
+ '\n\t\t'
+ 'streamObj.appendNewCanvas();'
),
'm21URIlocal': 'file:///Users/Cuthbert/git/music21j/src/music21',
'requireURIlocal': 'file:///Users/Cuthbert/git/music21j/ext/require/require.js',
}
self.mode = 'html'
self.useLocal = False
[docs]
def fromObject(self, thisObject, mode=None):
if mode is None:
mode = self.mode
if thisObject.isStream is False:
retStream = stream.Stream()
retStream.append(thisObject)
else:
retStream = thisObject
return self.fromStream(retStream, mode=mode)
[docs]
def splitLongJSON(self, jsonString, chunkSize=110):
allJSONList = []
for i in range(0, len(jsonString), chunkSize):
allJSONList.append("'" + jsonString[i:i + chunkSize] + "'")
return ' + \n '.join(allJSONList)
[docs]
def getLoadTemplate(self, urls=None):
'''
Gets the <script> tag for loading music21 from require.js
>>> vfp = vexflow.toMusic21j.VexflowPickler()
>>> vfp.getLoadTemplate()
"<script data-main='https://www.music21.org/music21j/src/music21'
src='https://www.music21.org/music21j/ext/require/require.js'></script>"
>>> d = {'m21URI': 'file:///tmp/music21', 'requireURI': 'http://requirejs.com/require.js'}
>>> vfp.getLoadTemplate(d)
"<script data-main='file:///tmp/music21' src='http://requirejs.com/require.js'></script>"
'''
if urls is None:
urls = self.defaults
if self.useLocal is False:
loadM21formatted = self.loadM21Template.format(m21URI=urls['m21URI'],
requireURI=urls['requireURI'],)
else:
loadM21formatted = self.loadM21Template.format(m21URI=urls['m21URIlocal'],
requireURI=urls['requireURIlocal'],)
return loadM21formatted
[docs]
def getJSBodyScript(self, dataSplit, defaults=None):
'''
Get the <script>...</script> tag to render the JSON
>>> vfp = vexflow.toMusic21j.VexflowPickler()
>>> print(vfp.getJSBodyScript('{"hi": "hello"}'))
<script>
require(['music21'], function() {
var pickleIn = {"hi": "hello"};
var jpc = new music21.fromPython.Converter();
streamObj = jpc.run(pickleIn);
streamObj.renderOptions.events.resize = "reflow";
streamObj.appendNewCanvas();
});
</script>
'''
if defaults is None:
defaults = self.defaults
jsBody = self.getJSBody(dataSplit, defaults)
jsBodyScript = self.jsBodyScript.format(jsBody=jsBody)
return jsBodyScript
[docs]
def getJSBody(self, dataSplit, defaults=None):
'''
Get the javascript code without the <script> tags to render the JSON
>>> vfp = vexflow.toMusic21j.VexflowPickler()
>>> print(vfp.getJSBody('{"hi": "hello"}'))
require(['music21'], function() {
var pickleIn = {"hi": "hello"};
var jpc = new music21.fromPython.Converter();
streamObj = jpc.run(pickleIn);
streamObj.renderOptions.events.resize = "reflow";
streamObj.appendNewCanvas();
});
'''
if defaults is None:
d = self.defaults
else:
d = defaults
jsBody = self.jsBody.format(pickleOutput=dataSplit,
callback=d['callback'])
return jsBody
[docs]
def getHTML(self, dataSplit, title=None, defaults=None):
'''
Get the complete HTML page to pass to the browser:
>>> vfp = vexflow.toMusic21j.VexflowPickler()
>>> print(vfp.getHTML('{"hi": "hello"}', 'myPiece'))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- for MSIE 10 on Windows 8 -->
<meta http-equiv="X-UA-Compatible" content="requiresActiveX=true"/>
<title>myPiece</title>
<script data-main='https://www.music21.org/music21j/src/music21'
src='https://www.music21.org/music21j/ext/require/require.js'></script>
<script>
require(['music21'], function() {
var pickleIn = {"hi": "hello"};
var jpc = new music21.fromPython.Converter();
streamObj = jpc.run(pickleIn);
streamObj.renderOptions.events.resize = "reflow";
streamObj.appendNewCanvas();
});
</script>
</head>
<body>
</body>
</html>
'''
if defaults is None:
d = self.defaults
else:
d = defaults
loadM21Formatted = self.getLoadTemplate(d)
jsBodyScript = self.getJSBodyScript(dataSplit, d)
formatted = self.templateHtml.format(title=title,
loadM21Template=loadM21Formatted,
jsBodyScript=jsBodyScript)
return formatted
[docs]
def fromStream(self, thisStream, mode=None):
if mode is None:
mode = self.mode
if thisStream.metadata is not None and thisStream.metadata.title != '':
title = thisStream.metadata.title
else:
title = 'Music21 Fragment'
sf = freezeThaw.StreamFreezer(thisStream)
# recursive data structures will be expanded up to a high depth
# -- make sure there are none
data = sf.writeStr(fmt='jsonpickle')
dataSplit = self.splitLongJSON(data)
if mode == 'json':
return data
elif mode == 'jsonSplit':
return dataSplit
elif mode == 'jsbody':
return self.getJSBody(dataSplit)
elif mode == 'jsbodyScript':
return self.getJSBodyScript(dataSplit)
elif mode == 'html':
return self.getHTML(dataSplit, title)
else:
raise VexflowToM21JException(f'Cannot deal with mode: {mode!r}')
class VexflowToM21JException(Music21Exception):
pass
class Test(unittest.TestCase):
pass
class TestCuthbert(unittest.TestCase): # pragma: no cover
def testCuthbertLocal(self):
'''
test a local version of this mess
'''
from music21 import corpus
from music21 import environment
environLocal = environment.Environment()
s = corpus.parse('luca/gloria').measures(1, 19)
# s = corpus.parse('beethoven/opus18no1', 2).parts[0].measures(4, 10)
vfp = VexflowPickler()
vfp.defaults['m21URI'] = 'file:///Users/Cuthbert/git/music21j/src/music21'
vfp.defaults['requireURI'] = 'file:///Users/Cuthbert/git/music21j/ext/require/require.js'
data = vfp.fromObject(s)
fp = environLocal.getTempFile('.html')
with open(fp, 'w', encoding='utf-8') as f:
f.write(data)
# environLocal.launch('vexflow', fp)
if __name__ == '__main__':
import music21
music21.mainTest(Test)
# from music21 import note, clef, meter
# s = stream.Measure()
# s.insert(0, clef.TrebleClef())
# s.insert(0, meter.TimeSignature('1/4'))
# n = note.Note()
# n.duration.quarterLength = 1/3
# s.repeatAppend(n, 3)
# p = stream.Part()
# p.repeatAppend(s, 2)
# p.show('vexflow', local=True)
#
# s.show('vexflow')