]> git.donarmstrong.com Git - lilypond.git/commitdiff
MusicXML: Implement metronome mark (tempo marks)
authorReinhold Kainhofer <reinhold@kainhofer.com>
Sat, 5 Apr 2008 19:26:13 +0000 (21:26 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Sat, 5 Apr 2008 19:26:13 +0000 (21:26 +0200)
MusicXML supports multiple types of metronome marks:
-) A simple one: note = beats (=> Lilypond \tempo note = beats)
-) note = other note (=> Lilypond has to use markup, no effect on
   midi :-((
-) A very advanced and possibly complex one, which uses <metronome-note>.
   This is not yet implemented.

Lilypond does not support parenthesized tempo marks, either, so in
this case, I also generate markup, which unfortunately does not have any
effect on midi..

input/regression/musicxml/03c-MetronomeMarks.xml [new file with mode: 0644]
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

diff --git a/input/regression/musicxml/03c-MetronomeMarks.xml b/input/regression/musicxml/03c-MetronomeMarks.xml
new file mode 100644 (file)
index 0000000..5f4ef49
--- /dev/null
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 1.1 Partwise//EN"
+                                "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="1.1">
+  <movement-title>Tempo markings</movement-title>
+  <identification/>
+  <defaults/>
+  <part-list>
+    <score-part id="P1">
+      <part-name></part-name>
+      <score-instrument id="P1-I1">
+        <instrument-name>Grand Piano</instrument-name>
+      </score-instrument>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+      </midi-instrument>
+    </score-part>
+  </part-list>
+  <!--=========================================================-->
+  <part id="P1">
+    <measure number="1" width="527">
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+        <time symbol="common">
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <direction>
+        <direction-type>
+          <metronome>
+            <beat-unit>quarter</beat-unit>
+            <beat-unit-dot/>
+            <per-minute>100</per-minute>
+          </metronome>
+        </direction-type>
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <direction>
+        <direction-type>
+          <words>Adagio</words>
+        </direction-type>\r
+        <direction-type>\r
+          <metronome>\r
+            <beat-unit>long</beat-unit>\r
+            <per-minute>100</per-minute>\r
+          </metronome>\r
+        </direction-type>\r
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+    </measure>
+    <!--=======================================================-->
+    <measure number="2">
+      <direction>
+        <direction-type>
+          <metronome>
+            <beat-unit>quarter</beat-unit>
+            <beat-unit-dot/>
+            <beat-unit>half</beat-unit>
+            <beat-unit-dot/>
+          </metronome>
+        </direction-type>
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <direction>\r
+        <direction-type>\r
+          <metronome>\r
+            <beat-unit>long</beat-unit>\r
+            <beat-unit>32nd</beat-unit>\r
+            <beat-unit-dot/>\r
+          </metronome>\r
+        </direction-type>
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+    </measure>
+    <!--=======================================================-->
+    <measure number="3">
+      <direction>
+        <direction-type>
+          <metronome parentheses="yes">
+            <beat-unit>quarter</beat-unit>
+            <beat-unit-dot/>
+            <beat-unit>half</beat-unit>
+            <beat-unit-dot/>
+          </metronome>
+        </direction-type>
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <direction>
+        <direction-type>
+          <metronome parentheses="yes">
+            <beat-unit>quarter</beat-unit>
+            <beat-unit-dot/>
+            <per-minute>77</per-minute>
+          </metronome>
+        </direction-type>
+      </direction>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <note>
+        <pitch><step>C</step><octave>5</octave></pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+      </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+  <!--=========================================================-->
+</score-partwise>
index cdd0271486c2944f6242030b9fbf1f104d963f0e..0346e91f4c96df400292d751f6a3641d96af63f4 100644 (file)
@@ -152,12 +152,16 @@ class Duration:
                              self.factor.denominator ())
 
 
-    def ly_expression (self, factor = None):
+    def ly_expression (self, factor = None, scheme_mode = False):
         if not factor:
             factor = self.factor
 
         if self.duration_log < 0:
-            str = {-1: "\\breve", -2: "\\longa"}.get (self.duration_log, "1")
+            if scheme_mode:
+                longer_dict = {-1: "breve", -2: "longa"}
+            else:
+                longer_dict = {-1: "\\breve", -2: "\\longa"}
+            str = longer_dict.get (self.duration_log, "1")
         else:
             str = '%d' % (1 << self.duration_log)
         str += '.'*self.dots
@@ -1270,6 +1274,56 @@ class StaffChange (Music):
         else:
             return ''
 
+
+class TempoMark (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.baseduration = None
+        self.newduration = None
+        self.beats = None
+        self.parentheses = False
+    def set_base_duration (self, dur):
+        self.baseduration = dur
+    def set_new_duration (self, dur):
+        self.newduration = dur
+    def set_beats_per_minute (self, beats):
+        self.beats = beats
+    def set_parentheses (self, parentheses):
+        self.parentheses = parentheses
+    def wait_for_note (self):
+        return False
+    def duration_to_markup (self, dur):
+        if dur:
+            # Generate the markup to print the note, use scheme mode for 
+            # ly_expression to get longa and not \longa (which causes an error)
+            return "\\general-align #Y #DOWN \\smaller \\note #\"%s\" #UP" % dur.ly_expression(None, True)
+        else:
+            return ''
+    def tempo_markup_template (self):
+        return "\\mark\\markup { \\fontsize #-2 \\line { %s } }"
+    def ly_expression (self):
+        res = ''
+        if not self.baseduration:
+            return res
+        if self.beats:
+            if self.parentheses:
+                dm = self.duration_to_markup (self.baseduration)
+                contents = "\"(\" %s = %s \")\"" % (dm, self.beats)
+                res += self.tempo_markup_template() % contents
+            else:
+                res += "\\tempo %s=%s" % (self.baseduration.ly_expression(), self.beats)
+        elif self.newduration:
+            dm = self.duration_to_markup (self.baseduration)
+            ndm = self.duration_to_markup (self.newduration)
+            if self.parentheses:
+                contents = "\"(\" %s = %s \")\"" % (dm, ndm)
+            else:
+                contents = " %s = %s " % (dm, ndm)
+            res += self.tempo_markup_template() % contents
+        else:
+            return ''
+        return res
+
 class FiguredBassNote (Music):
     def __init__ (self):
         Music.__init__ (self)
index adfd1027cbc53bb396325947ab5bdae7cad3748b..194fffbd9c709dbf418114b5de59a3be266ae3f7 100644 (file)
@@ -21,6 +21,22 @@ def escape_ly_output_string (input_string):
     return return_string
 
 
+def musicxml_duration_to_log (dur):
+    return  {'256th': 8,
+             '128th': 7,
+             '64th': 6,
+             '32nd': 5,
+             '16th': 4,
+             'eighth': 3,
+             'quarter': 2,
+             'half': 1,
+             'whole': 0,
+             'breve': -1,
+             'longa': -2,
+             'long': -2}.get (dur, 0)
+
+
+
 class Xml_node:
     def __init__ (self):
        self._children = []
@@ -330,17 +346,7 @@ class Note (Measure_element):
 
         if ch:
             log = ch.get_text ().strip()
-            return {'256th': 8,
-                    '128th': 7,
-                    '64th': 6,
-                    '32nd': 5,
-                    '16th': 4,
-                    'eighth': 3,
-                    'quarter': 2,
-                    'half': 1,
-                    'whole': 0,
-                    'breve': -1,
-                    'longa': -2}.get (log, 0)
+            return musicxml_duration_to_log (log)
        elif self.get_maybe_exist_named_child (u'grace'):
            # FIXME: is it ok to default to eight note for grace notes?
            return 3
@@ -890,6 +896,15 @@ class Frame_Note (Music_xml_node):
 class FiguredBass (Music_xml_node):
     pass
 
+class BeatUnit (Music_xml_node):
+    pass
+
+class BeatUnitDot (Music_xml_node):
+    pass
+
+class PerMinute (Music_xml_node):
+    pass
+
 
 
 ## need this, not all classes are instantiated
@@ -903,6 +918,8 @@ class_dict = {
         'barline': Barline,
         'bar-style': BarStyle,
        'beam' : Beam,
+        'beat-unit': BeatUnit,
+        'beat-unit-dot': BeatUnitDot,
         'bend' : Bend,
         'bracket' : Bracket,
        'chord': Chord,
@@ -927,6 +944,7 @@ class_dict = {
     'part-group': Part_group,
        'part-list': Part_list,
         'pedal': Pedal,
+        'per-minute': PerMinute,
        'pitch': Pitch,
        'rest': Rest,
     'score-part': Score_part,
index 1f7f076b45d06ddff866e43a40baa81e90f696a6..f9effaa4322b85907d6474df0b8803a633d272d5 100644 (file)
@@ -1162,6 +1162,57 @@ def musicxml_eyeglasses_to_ly (mxl_event):
     needed_additional_definitions.append ("eyeglasses")
     return musicexp.MarkEvent ("\\eyeglasses")
 
+def next_non_hash_index (lst, pos):
+    pos += 1
+    while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text):
+        pos += 1
+    return pos
+
+def musicxml_metronome_to_ly (mxl_event):
+    children = mxl_event.get_all_children ()
+    if not children:
+        return
+
+    index = -1
+    index = next_non_hash_index (children, index)
+    if isinstance (children[index], musicxml.BeatUnit): 
+        # first form of metronome-mark, using unit and beats/min or other unit
+        ev = musicexp.TempoMark ()
+        if hasattr (mxl_event, 'parentheses'):
+            ev.set_parentheses (mxl_event.parentheses == "yes")
+
+        d = musicexp.Duration ()
+        d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
+        index = next_non_hash_index (children, index)
+        if isinstance (children[index], musicxml.BeatUnitDot):
+            d.dots = 1
+            index = next_non_hash_index (children, index)
+        ev.set_base_duration (d)
+        if isinstance (children[index], musicxml.BeatUnit):
+            # Form "note = newnote"
+            newd = musicexp.Duration ()
+            newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ())
+            index = next_non_hash_index (children, index)
+            if isinstance (children[index], musicxml.BeatUnitDot):
+                newd.dots = 1
+                index = next_non_hash_index (children, index)
+            ev.set_new_duration (newd)
+        elif isinstance (children[index], musicxml.PerMinute):
+            # Form "note = bpm"
+            try:
+                beats = int (children[index].get_text ())
+                ev.set_beats_per_minute (beats)
+            except ValueError:
+                pass
+        else:
+            error_message (_ ("Unknown metronome mark, ignoring"))
+            return
+        return ev
+    else:
+        #TODO: Implement the other (more complex) way for tempo marks!
+        error_message (_ ("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
+        return
+
 # translate directions into Events, possible values:
 #   -) string  (MarkEvent with that command)
 #   -) function (function(mxl_event) needs to return a full Event-derived object
@@ -1172,11 +1223,11 @@ directions_dict = {
 #     'damp' : ???
 #     'damp-all' : ???
     'eyeglasses': musicxml_eyeglasses_to_ly,
-#     'harp-pedals' : 
-#     'image' : 
-#     'metronome' : 
+#     'harp-pedals' : ???
+#     'image' : ???
+    'metronome' : musicxml_metronome_to_ly,
     'rehearsal' : musicxml_rehearsal_to_ly_mark,
-#     'scordatura' : 
+#     'scordatura' : ???
     'segno' : (musicexp.MusicGlyphMarkEvent, "segno"),
     'words' : musicxml_words_to_lily_event,
 }
@@ -1299,7 +1350,6 @@ def musicxml_figured_bass_to_lily (n):
             res.append (note)
     dur = n.get_maybe_exist_named_child ('duration')
     if dur:
-        # TODO: implement duration (given in base steps!)
         # apply the duration to res
         length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
         res.set_real_duration (length)