]> git.donarmstrong.com Git - lilypond.git/commitdiff
Lilypond-book: Implement MusicXML support in lilypond-book
authorReinhold Kainhofer <reinhold@kainhofer.com>
Fri, 11 Jun 2010 19:19:19 +0000 (21:19 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Thu, 18 Aug 2011 20:29:18 +0000 (22:29 +0200)
This patch adds support for including MusicXML files into
documents processed by lilypond-book. In particular:
-) HTML: <musicxmlfile options>filename.xml</musicxmlfile>
-) TeX: \musicxmlfile[options]{filename.xml}
-) Texinfo: @musicxmlfile[options]{filename.xml}

Since MusicXML is so verbose, it doesn't make much sense to
support inline MusicXML.

The snippets are basically processed like a lilypond file,
except that musicxml2ly is run on them (with the options given
for the snippet) and the returned lilypond code is then processed
as if it were the original contents of the file.

For the output links, however, the html and texinfo pages will
link to the .xml/.mxl file rather than the intermediate .ly file.

If a file has the extension .mxl, it is assumed to be a compressed
MusicXML file (alternatively, the 'compressed' snippet option can
be given).

What's missing is proper documentation in "Usage". I'm unsure
how to document such new snippet types...

15 files changed:
input/regression/lilypond-book/html-musicxml-file-compressed.htmly [new file with mode: 0644]
input/regression/lilypond-book/html-musicxml-file-options.htmly [new file with mode: 0644]
input/regression/lilypond-book/html-musicxml-file.htmly [new file with mode: 0644]
input/regression/lilypond-book/include.mxl [new file with mode: 0644]
input/regression/lilypond-book/include.xml [new file with mode: 0644]
input/regression/lilypond-book/tex-musicxml-file-options.lytex [new file with mode: 0644]
input/regression/lilypond-book/tex-musicxml-file.lytex [new file with mode: 0644]
input/regression/lilypond-book/texinfo-musicxml-file-options.tely [new file with mode: 0644]
input/regression/lilypond-book/texinfo-musicxml-file.tely [new file with mode: 0644]
python/book_base.py
python/book_docbook.py
python/book_html.py
python/book_latex.py
python/book_snippets.py
python/book_texinfo.py

diff --git a/input/regression/lilypond-book/html-musicxml-file-compressed.htmly b/input/regression/lilypond-book/html-musicxml-file-compressed.htmly
new file mode 100644 (file)
index 0000000..a839edd
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+<body>
+Including a compressed MusicXML file:
+<musicxmlfile language="deutsch" absolute no-beaming printfilename>include.mxl</musicxmlfile>
+</body>
+</html>
diff --git a/input/regression/lilypond-book/html-musicxml-file-options.htmly b/input/regression/lilypond-book/html-musicxml-file-options.htmly
new file mode 100644 (file)
index 0000000..6e05bbe
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+<body>
+Including a MusicXML file:
+<musicxmlfile language="deutsch" absolute no-beaming printfilename>include.xml</musicxmlfile>
+</body>
+</html>
diff --git a/input/regression/lilypond-book/html-musicxml-file.htmly b/input/regression/lilypond-book/html-musicxml-file.htmly
new file mode 100644 (file)
index 0000000..6f8d7e3
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+<body>
+Including a MusicXML file:
+<musicxmlfile>include.xml</musicxmlfile>
+</body>
+</html>
diff --git a/input/regression/lilypond-book/include.mxl b/input/regression/lilypond-book/include.mxl
new file mode 100644 (file)
index 0000000..d709094
Binary files /dev/null and b/input/regression/lilypond-book/include.mxl differ
diff --git a/input/regression/lilypond-book/include.xml b/input/regression/lilypond-book/include.xml
new file mode 100644 (file)
index 0000000..839e145
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 0.6 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise>
+       <identification>
+               <miscellaneous>
+                       <miscellaneous-field name="description">One simple chord
+                            consisting of two notes.</miscellaneous-field>
+               </miscellaneous>
+       </identification>
+       <part-list>
+               <score-part id="P0">
+                       <part-name>MusicXML Part</part-name>
+               </score-part>
+       </part-list>
+       <part id="P0">
+               <measure number="1">
+                       <attributes>
+                               <divisions>960</divisions>
+                               <time>
+                               <beats>4</beats>
+                               <beat-type>4</beat-type>
+                               </time>
+                               <clef>
+                               <sign>G</sign>
+                               <line>2</line>
+                               </clef>
+                       </attributes>
+                       <note>
+                               <pitch>
+                                       <step>B</step>
+                                       <octave>4</octave>
+                               </pitch>
+                               <duration>960</duration>
+                               <voice>1</voice>
+                               <type>quarter</type>
+                       </note>
+                       <note>
+                               <chord/>
+                               <pitch>
+                                       <step>G</step>
+                                       <octave>4</octave>
+                               </pitch>
+                               <duration>960</duration>
+                               <voice>1</voice>
+                               <type>quarter</type>
+                       </note>
+                       <note>
+                               <rest/>
+                               <duration>960</duration>
+                               <voice>1</voice>
+                               <type>quarter</type>
+                       </note>
+               </measure>
+       </part>
+</score-partwise>
diff --git a/input/regression/lilypond-book/tex-musicxml-file-options.lytex b/input/regression/lilypond-book/tex-musicxml-file-options.lytex
new file mode 100644 (file)
index 0000000..154ed66
--- /dev/null
@@ -0,0 +1,5 @@
+\documentclass{article}
+\begin{document}
+Including MusicMXL file:
+\musicxmlfile[language=deutsch,absolute,no-beaming]{include.xml}
+\end{document}
diff --git a/input/regression/lilypond-book/tex-musicxml-file.lytex b/input/regression/lilypond-book/tex-musicxml-file.lytex
new file mode 100644 (file)
index 0000000..2995b35
--- /dev/null
@@ -0,0 +1,5 @@
+\documentclass{article}
+\begin{document}
+Including MusicMXL file:
+\musicxmlfile{include.xml}
+\end{document}
diff --git a/input/regression/lilypond-book/texinfo-musicxml-file-options.tely b/input/regression/lilypond-book/texinfo-musicxml-file-options.tely
new file mode 100644 (file)
index 0000000..8545453
--- /dev/null
@@ -0,0 +1,12 @@
+\input texinfo @c -*- coding: utf-8; mode: texinfo; -*-
+@setfilename texinfo-musicxml-file.info
+@settitle MusicXML inside texinfo
+
+@node Top
+@top MusicMXL in texinfo
+
+MusicXML included in texinfo
+@musicxmlfile[language=deutsch,absolute,no-beaming]{include.xml}
+
+
+@bye
diff --git a/input/regression/lilypond-book/texinfo-musicxml-file.tely b/input/regression/lilypond-book/texinfo-musicxml-file.tely
new file mode 100644 (file)
index 0000000..8ff2384
--- /dev/null
@@ -0,0 +1,12 @@
+\input texinfo @c -*- coding: utf-8; mode: texinfo; -*-
+@setfilename texinfo-musicxml-file.info
+@settitle MusicXML inside texinfo
+
+@node Top
+@top MusicMXL in texinfo
+
+MusicXML included in texinfo
+@musicxmlfile{include.xml}
+
+
+@bye
index b73f37336e4f3975dce9ffd7c7a47de6bcd33b8f..b43b9e00d4b4477b5ea2fe0f05eea0be9e3ae8f5 100644 (file)
@@ -172,7 +172,8 @@ class BookOutputFormat:
         rep = snippet.get_replacements ()
         if PRINTFILENAME in snippet.option_dict:
             rep['base'] = basename
-            rep['filename'] = os.path.basename (snippet.substring ('filename'))
+            rep['filename'] = os.path.basename (snippet.filename)
+            rep['ext'] = snippet.ext
             str = self.output[PRINTFILENAME] % rep
 
         return str
index 5ae766bbaf8a457b9bb83e561935dc5e018abc25..ef009935d6990e06832f100ddfbf328a7993afc5 100644 (file)
@@ -79,7 +79,7 @@ Docbook_output = {
 
     PRINTFILENAME: r'''<textobject>
   <simpara>
-    <ulink url="%(base)s.ly">
+    <ulink url="%(base)s%(ext)s">
       <filename>
         %(filename)s
       </filename>
index cf56dd5f49ae92b1bc69058a59f49a9f27117638..ac70b743a0e3a91727f3ee099b5026056b028bbb 100644 (file)
@@ -40,6 +40,13 @@ HTML_snippet_res = {
     'multiline_comment':
          r'''(?smx)(?P<match>\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s)''',
 
+    'musicxml_file':
+         r'''(?mx)
+          (?P<match>
+          <musicxmlfile\s*(?P<options>.*?)\s*>
+          \s*(?P<filename>.*?)\s*
+          </musicxmlfile\s*>)''',
+
     'verb':
          r'''(?x)(?P<match>(?P<code><pre>.*?</pre>))''',
 
@@ -62,7 +69,7 @@ HTML_output = {
 </p>''',
 
     BEFORE: r'''<p>
- <a href="%(base)s.ly">''',
+ <a href="%(base)s%(ext)s">''',
 
     OUTPUT: r'''
   <img align="middle"
@@ -70,7 +77,7 @@ HTML_output = {
        src="%(image)s"
        alt="%(alt)s">''',
 
-    PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
+    PRINTFILENAME: '<p><tt><a href="%(base)s%(ext)s">%(filename)s</a></tt></p>',
 
     QUOTE: r'''<blockquote>
 %(str)s
@@ -118,6 +125,8 @@ class BookHTMLOutputFormat (BookBase.BookOutputFormat):
         str = ''
         rep = snippet.get_replacements ();
         rep['base'] = basename
+        rep['filename'] = os.path.basename (snippet.filename)
+        rep['ext'] = snippet.ext
         str += self.output_print_filename (basename, snippet)
         if VERBATIM in snippet.option_dict:
             rep['verb'] = BookBase.verbatim_html (snippet.verb_ly ())
index 677e88a1f70fe42a78df7419b8f4f512689c3693..8097443f7f320f01c858e2cac7af597eb6f1de66 100644 (file)
@@ -68,6 +68,17 @@ Latex_snippet_res = {
            (?P<filename>\S+?)
           })''',
 
+    'musicxml_file':
+         r'''(?smx)
+          ^[^%\n]*?
+          (?P<match>
+          \\musicxmlfile\s*(
+          \[
+           \s*(?P<options>.*?)\s*
+          \])?\s*\{
+           (?P<filename>\S+?)
+          })''',
+
     'singleline_comment':
          r'''(?mx)
           ^.*?
index b7c3ddf0d67d369acfec84b3db605a54860b4db9..aedee3ceefc417dcec85902b6ccc60381d5444d5 100644 (file)
@@ -659,6 +659,11 @@ printing diff against existing file." % filename)
                 os.makedirs (dst_path)
             os.link (src, dst)
 
+    def additional_files_to_consider (self, base, full):
+        return []
+    def additional_files_required (self, base, full):
+        return []
+
 
     def all_output_files (self, output_dir, output_dir_files):
         """Return all files generated in lily_output_dir, a set.
@@ -681,8 +686,7 @@ printing diff against existing file." % filename)
 
         # UGH - junk self.global_options
         skip_lily = self.global_options.skip_lilypond_run
-        for required in [base + '.ly',
-                         base + '.txt']:
+        for required in [base + '.ly']:
             require_file (required)
         if not skip_lily:
             require_file (base + '-systems.count')
@@ -722,6 +726,8 @@ printing diff against existing file." % filename)
             if 'ddump-signature' in self.global_options.process_cmd:
                 consider_file (systemfile + '.signature')
 
+        map (consider_file, self.additional_files_to_consider (base, full))
+        map (require_file, self.additional_files_required (base, full))
 
         return (result, missing)
 
@@ -808,7 +814,9 @@ re_end_verbatim = re.compile (r'\s+%.*?end verbatim.*$', re.M)
 class LilypondFileSnippet (LilypondSnippet):
     def __init__ (self, type, match, formatter, line_number, global_options):
         LilypondSnippet.__init__ (self, type, match, formatter, line_number, global_options)
-        self.contents = file (BookBase.find_file (self.substring ('filename'), global_options.include_path)).read ()
+        self.filename = self.substring ('filename')
+        self.ext = os.path.splitext (os.path.basename (self.filename))[1]
+        self.contents = file (BookBase.find_file (self.filename, global_options.include_path)).read ()
 
     def get_snippet_code (self):
         return self.contents;
@@ -824,18 +832,110 @@ class LilypondFileSnippet (LilypondSnippet):
         return s
 
     def ly (self):
-        name = self.substring ('filename')
+        name = self.filename
         return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
                 % (name, self.contents))
 
     def final_basename (self):
         if self.global_options.use_source_file_names:
-            base = os.path.splitext (os.path.basename (self.substring ('filename')))[0]
+            base = os.path.splitext (os.path.basename (self.filename))[0]
             return base
         else:
             return self.basename ()
 
 
+class MusicXMLFileSnippet (LilypondFileSnippet):
+    def __init__ (self, type, match, formatter, line_number, global_options):
+        LilypondFileSnippet.__init__ (self, type, match, formatter, line_number, global_options)
+        self.compressed = False
+        self.converted_ly = None
+        self.musicxml_options_dict = {
+           'verbose': '--verbose',
+           'lxml': '--lxml',
+           'compressed': '--compressed',
+           'relative': '--relative',
+           'absolute': '--absolute',
+           'no-articulation-directions': '--no-articulation-directions',
+           'no-rest-positions': '--no-rest-positions',
+           'no-page-layout': '--no-page-layout',
+           'no-beaming': '--no-beaming',
+           'language': '--language',
+        }
+
+    def snippet_options (self):
+        return self.musicxml_options_dict.keys ()
+
+    def convert_from_musicxml (self):
+        name = self.filename
+        option_list = []
+        for (key, value) in self.option_dict.items ():
+            cmd_key = self.musicxml_options_dict.get (key, None)
+            if cmd_key == None:
+                continue
+            if value == None:
+                option_list.append (cmd_key)
+            else:
+                option_list.append (cmd_key + '=' + value)
+        if ('.mxl' in name) and ('--compressed' not in option_list):
+            option_list.append ('--compressed')
+            self.compressed = True
+        opts = " ".join (option_list)
+
+        ly_code = self.filter_pipe (self.contents, 'musicxml2ly %s --out=- - ' % opts)
+        return ly_code
+
+    def ly (self):
+        if self.converted_ly == None:
+            self.converted_ly = self.convert_from_musicxml ()
+        name = self.filename
+        return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
+                % (name, self.converted_ly))
+
+    def additional_files_required (self, base, full):
+        result = [];
+        if self.compressed:
+            result.append (base + '.mxl')
+        else:
+            result.append (base + '.xml')
+        return result
+
+    def write_ly (self):
+        base = self.basename ()
+        path = os.path.join (self.global_options.lily_output_dir, base)
+        directory = os.path.split(path)[0]
+        if not os.path.isdir (directory):
+            os.makedirs (directory)
+
+        # First write the XML to a file (so we can link it!)
+        if self.compressed:
+            xmlfilename = path + '.mxl'
+        else:
+            xmlfilename = path + '.xml'
+        if os.path.exists (xmlfilename):
+            diff_against_existing = self.filter_pipe (self.contents, 'diff -u %s - ' % xmlfilename)
+            if diff_against_existing:
+                warning ("%s: duplicate filename but different contents of orginal file,\n\
+printing diff against existing file." % xmlfilename)
+                ly.stderr_write (diff_against_existing)
+        else:
+            out = file (xmlfilename, 'w')
+            out.write (self.contents)
+            out.close ()
+
+        # also write the converted lilypond
+        filename = path + '.ly'
+        if os.path.exists (filename):
+            diff_against_existing = self.filter_pipe (self.full_ly (), 'diff -u %s -' % filename)
+            if diff_against_existing:
+                warning ("%s: duplicate filename but different contents of converted lilypond file,\n\
+printing diff against existing file." % filename)
+                ly.stderr_write (diff_against_existing)
+        else:
+            out = file (filename, 'w')
+            out.write (self.full_ly ())
+            out.close ()
+
+
 class LilyPondVersionString (Snippet):
     """A string that does not require extra memory."""
     def __init__ (self, type, match, formatter, line_number, global_options):
@@ -851,4 +951,5 @@ snippet_type_to_class = {
     'lilypond': LilypondSnippet,
     'include': IncludeSnippet,
     'lilypondversion': LilyPondVersionString,
+    'musicxml_file': MusicXMLFileSnippet,
 }
index 03f723754a43619cf055d82d9f89601ffeda3356..939945164743be1c9c21ac2ce0639654f04a6b8a 100644 (file)
@@ -58,6 +58,15 @@ TexInfo_snippet_res = {
             .*?
            @end\s+ignore))\s''',
 
+    'musicxml_file': r'''(?mx)
+          ^(?P<match>
+          @musicxmlfile\s*(
+          \[
+           \s*(?P<options>.*?)\s*
+          \])?\s*{
+           (?P<filename>\S+)
+          })''',
+
     'singleline_comment': r'''(?mx)
           ^.*
           (?P<match>
@@ -103,7 +112,7 @@ TexInfo_output = {
 @end ifinfo
 @html
 <p>
- <a href="%(base)s.ly">
+ <a href="%(base)s%(ext)s">
   <img align="middle"
        border="0"
        src="%(image)s"
@@ -115,7 +124,7 @@ TexInfo_output = {
 
     PRINTFILENAME: '''
 @html
-<a href="%(base)s.ly">
+<a href="%(base)s%(ext)s">
 @end html
 @file{%(filename)s}
 @html