]> git.donarmstrong.com Git - lilypond.git/commitdiff
MusicXML: Support for upbeats and partial/implicit measures
authorReinhold Kainhofer <reinhold@kainhofer.com>
Tue, 9 Oct 2007 22:23:55 +0000 (00:23 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Tue, 9 Oct 2007 22:51:34 +0000 (00:51 +0200)
-) In the Duration class, don't write out e.g. *3/1 for the factor.
   In this case, *3 is much better
-) Add support for implicit measures, i.e. measures in MusicXML that do
   not count. These are typically used for upbeats and for places where
   a repeat or clef/key change occurs inside a measure. In this case,
   the measure is split into to <measure> elements, the second one with
   the implicit attribute set to "yes". Now we don't start a measure
   in lilypond for those implicit measures.
-) Implicit measures at the very beginning are now converted as \partial X
-) Don't print out | %0 at the very beginning (i.e. for the measure with
   number 0)

Signed-off-by: Reinhold Kainhofer <reinhold@kainhofer.com>
input/regression/musicxml/07e-Upbeats-ImplicitMeasures-Finale.xml [new file with mode: 0644]
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

diff --git a/input/regression/musicxml/07e-Upbeats-ImplicitMeasures-Finale.xml b/input/regression/musicxml/07e-Upbeats-ImplicitMeasures-Finale.xml
new file mode 100644 (file)
index 0000000..c81bd38
--- /dev/null
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 1.0 Partwise//EN"\r
+                                "http://www.musicxml.org/dtds/partwise.dtd">\r
+<score-partwise>\r
+  <movement-title>Upbeats, implicit measure, incomplete final measure</movement-title>\r
+  <identification>\r
+    <encoding>\r
+      <software>Finale 2007 for Windows</software>\r
+      <software>Dolet Light for Finale 2007</software>\r
+      <encoding-date>2007-10-09</encoding-date>\r
+    </encoding>\r
+  </identification>\r
+  <part-list>\r
+    <score-part id="P1">\r
+      <part-name>MusicXML Part</part-name>\r
+      <score-instrument id="P1-I1">\r
+        <instrument-name>Grand Piano</instrument-name>\r
+      </score-instrument>\r
+      <midi-instrument id="P1-I1">\r
+        <midi-channel>1</midi-channel>\r
+        <midi-program>1</midi-program>\r
+      </midi-instrument>\r
+    </score-part>\r
+  </part-list>\r
+  <!--=========================================================-->\r
+  <part id="P1">\r
+    <measure implicit="yes" number="0">\r
+      <attributes>\r
+        <divisions>2</divisions>\r
+        <key>\r
+          <fifths>0</fifths>\r
+          <mode>major</mode>\r
+        </key>\r
+        <time symbol="common">\r
+          <beats>4</beats>\r
+          <beat-type>4</beat-type>\r
+        </time>\r
+        <clef>\r
+          <sign>G</sign>\r
+          <line>2</line>\r
+        </clef>\r
+      </attributes>\r
+      <sound tempo="120"/>\r
+      <note>\r
+          <pitch>\r
+              <step>E</step>\r
+              <octave>4</octave>\r
+          </pitch>\r
+          <duration>2</duration>\r
+          <voice>1</voice>\r
+          <type>quarter</type>\r
+          <stem>up</stem>\r
+      </note>\r
+      <note>\r
+          <pitch>\r
+              <step>E</step>\r
+              <octave>4</octave>\r
+          </pitch>\r
+          <duration>1</duration>\r
+          <voice>1</voice>\r
+          <type>eighth</type>\r
+          <stem>up</stem>\r
+      </note>\r
+    </measure>\r
+    <!--=======================================================-->\r
+    <measure number="1">\r
+      <note>\r
+        <pitch>\r
+          <step>F</step>\r
+          <octave>4</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>up</stem>\r
+      </note>\r
+      <note>\r
+        <pitch>\r
+          <step>G</step>\r
+          <octave>4</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>up</stem>\r
+      </note>\r
+      <barline location="right">\r
+        <bar-style>none</bar-style>\r
+      </barline>\r
+    </measure>\r
+    <!--=======================================================-->\r
+    <measure implicit="yes" number="X1">\r
+      <note>\r
+        <pitch>\r
+          <step>A</step>\r
+          <octave>4</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>up</stem>\r
+      </note>\r
+      <note>\r
+        <pitch>\r
+          <step>B</step>\r
+          <octave>4</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>down</stem>\r
+      </note>\r
+    </measure>\r
+    <!--=======================================================-->\r
+    <measure number="2">\r
+      <note>\r
+        <pitch>\r
+          <step>C</step>\r
+          <octave>5</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>down</stem>\r
+      </note>\r
+      <note>\r
+        <pitch>\r
+          <step>D</step>\r
+          <octave>5</octave>\r
+        </pitch>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+        <stem>down</stem>\r
+      </note>\r
+      <note>\r
+        <rest/>\r
+        <duration>2</duration>\r
+        <voice>1</voice>\r
+        <type>quarter</type>\r
+      </note>\r
+      <barline location="right">\r
+        <bar-style>light-heavy</bar-style>\r
+      </barline>\r
+    </measure>\r
+  </part>\r
+  <!--=========================================================-->\r
+</score-partwise>\r
index 8ae3105cada6d35d1efeba95378124861b1dc5cb..a78885dd3c6453eeeceb6e06fa7dafca273fb908 100644 (file)
@@ -148,7 +148,10 @@ class Duration:
         str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
 
         if factor <> Rational (1,1):
-            str += '*%d/%d' % (factor.numerator (), factor.denominator ())
+            if factor.denominator () <> 1:
+                str += '*%d/%d' % (factor.numerator (), factor.denominator ())
+            else:
+                str += '*%d' % factor.numerator ()
 
         return str
     
@@ -537,6 +540,13 @@ class EventChord (NestedMusic):
 
         self.print_comment (printer)
             
+class Partial (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.partial = None
+    def print_ly (self, printer):
+        if self.partial:
+            printer.dump ("\\partial %s" % self.partial.ly_expression ())
 
 class BarCheck (Music):
     def __init__ (self):
index 80b332bf6487cda846aada5e7e986e483ff89839..9a5d43eeec74e4520f36de7b44e92c41a479d5f2 100644 (file)
@@ -271,6 +271,10 @@ class Attributes (Measure_element):
         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
         return (fifths, mode)
                 
+class Partial (Measure_element):
+    def __init__ (self, partial):
+        Measure_element.__init__ (self)
+        self.partial = partial
 
 class Note (Measure_element):
     def __init__ (self):
@@ -337,6 +341,11 @@ class Score_part (Music_xml_node):
     pass
         
 class Measure (Music_xml_node):
+    def __init__ (self):
+        Music_xml_node.__init__ (self)
+        self.partial = 0
+    def is_implicit (self):
+        return hasattr (self, 'implicit') and self.implicit == 'yes'
     def get_notes (self):
        return self.get_typed_children (get_class (u'note'))
 
@@ -443,10 +452,35 @@ class Part (Music_xml_node):
        measures = self.get_typed_children (Measure)
         last_moment = Rational (-1)
         last_measure_position = Rational (-1)
+        measure_position = Rational (0)
+        measure_start_moment = now
+        is_first_measure = True
+        prvious_measure = None
        for m in measures:
-            measure_start_moment = now
-            measure_position = Rational (0)
-           for n in m.get_all_children ():
+            # implicit measures are used for artificial measures, e.g. when
+            # a repeat bar line splits a bar into two halves. In this case,
+            # don't reset the measure position to 0. They are also used for
+            # upbeats (initial value of 0 fits these, too).
+            # Also, don't reset the measure position at the end of the loop,
+            # but rather when starting the next measure (since only then do we
+            # know if the next measure is implicit and continues that measure)
+            if not m.is_implicit ():
+                # Warn about possibly overfull measures and reset the position
+                if attributes_object:
+                    length = attributes_object.get_measure_length ()
+                    new_now = measure_start_moment + length
+                    if now <> new_now:
+                        problem = 'incomplete'
+                        if now > new_now:
+                            problem = 'overfull'
+                        ## only for verbose operation.
+                        if problem <> 'incomplete' and previous_measure:
+                            previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
+                    now = new_now
+                measure_start_moment = now
+                measure_position = Rational (0)
+
+            for n in m.get_all_children ():
                 if isinstance (n, Hash_text):
                     continue
                dur = Rational (0)
@@ -505,20 +539,16 @@ class Part (Music_xml_node):
                     if instrument:
                         n.instrument_name = part_list.get_instrument (instrument.id)
 
-            if attributes_object:
-                length = attributes_object.get_measure_length ()
-                new_now = measure_start_moment + length
-                
-                if now <> new_now:
-                    problem = 'incomplete'
-                    if now > new_now:
-                        problem = 'overfull'
-
-                    ## only for verbose operation.
-                    if problem <> 'incomplete':
-                        m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
-
-                now = new_now
+            # Incomplete first measures are not padded, but registered as partial
+            if is_first_measure:
+                is_first_measure = False
+                # upbeats are marked as implicit measures
+                if attributes_object and m.is_implicit ():
+                    length = attributes_object.get_measure_length ()
+                    measure_end = measure_start_moment + length
+                    if measure_end <> now:
+                        m.partial = now
+            previous_measure = m
 
     # modify attributes so that only those applying to the given staff remain
     def extract_attributes_for_staff (part, attr, staff):
@@ -535,6 +565,8 @@ class Part (Music_xml_node):
        measures = part.get_typed_children (Measure)
        elements = []
        for m in measures:
+            if m.partial > 0:
+                elements.append (Partial (m.partial))
            elements.extend (m.get_all_children ())
         # make sure we know all voices already so that dynamics, clefs, etc.
         # can be assigned to the correct voices
@@ -572,7 +604,7 @@ class Part (Music_xml_node):
        for n in elements:
            voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
 
-           if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) ):
+           if not (voice_id or isinstance (n, Attributes) or isinstance (n, Direction) or isinstance (n, Partial) ):
                continue
 
            if isinstance (n, Attributes) and not start_attr:
@@ -587,6 +619,11 @@ class Part (Music_xml_node):
                         voices[v].add_element (staff_attributes)
                 continue
 
+            if isinstance (n, Partial):
+                for v in voices.keys ():
+                    voices[v].add_element (n)
+                continue
+
             if isinstance (n, Direction):
                 staff_id = n.get_maybe_exist_named_child (u'staff')
                 if staff_id:
index fd13721d9655121362f7dc2361b1c6d9dd84be3c..b31511a2919f7cc3856cac768ce3fbaa4afd4ba1 100644 (file)
@@ -150,7 +150,21 @@ def musicxml_duration_to_lily (mxl_note):
     if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
         d.factor = mxl_note._duration / d.get_length ()
 
-    return d         
+    return d
+
+def rational_to_lily_duration (rational_len):
+    d = musicexp.Duration ()
+    d.duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1)
+    d.factor = Rational (rational_len.numerator ())
+    return d
+
+def musicxml_partial_to_lily (partial_len):
+    if partial_len > 0:
+        p = musicexp.Partial ()
+        p.partial = rational_to_lily_duration (partial_len)
+        return p
+    else:
+        return Null
 
 def group_tuplets (music_list, events):
 
@@ -535,6 +549,7 @@ class LilyPondVoiceBuilder:
         self.end_moment = Rational (0)
         self.begin_moment = Rational (0)
         self.pending_multibar = Rational (0)
+        self.ignore_skips = False
 
     def _insert_multibar (self):
         r = musicexp.MultiMeasureRest ()
@@ -575,6 +590,9 @@ class LilyPondVoiceBuilder:
         if self.pending_multibar > Rational (0):
             self._insert_multibar ()
         self.elements.append (command)
+    def add_partial (self, command):
+        self.ignore_skips = True
+        self.add_command (command)
 
     def add_dynamics (self, dynamic):
         # store the dynamic item(s) until we encounter the next note/rest:
@@ -593,7 +611,7 @@ class LilyPondVoiceBuilder:
             error_message ('Negative skip %s' % diff)
             diff = Rational (0)
 
-        if diff > Rational (0):
+        if diff > Rational (0) and not self.ignore_skips:
             skip = musicexp.SkipEvent()
             skip.duration.duration_log = 0
             skip.duration.factor = diff
@@ -601,7 +619,10 @@ class LilyPondVoiceBuilder:
             evc = musicexp.EventChord ()
             evc.elements.append (skip)
             self.add_music (evc, diff)
-                
+
+        if diff > Rational (0) and moment == 0:
+            self.ignore_skips = False
+
     def last_event_chord (self, starting_at):
 
         value = None
@@ -646,6 +667,12 @@ def musicxml_voice_to_lily_voice (voice):
         if n.get_name () == 'forward':
             continue
 
+        if isinstance (n, musicxml.Partial) and n.partial > 0:
+            a = musicxml_partial_to_lily (n.partial)
+            if a:
+                voice_builder.add_partial (a)
+            continue
+
         if isinstance (n, musicxml.Direction):
             for a in musicxml_direction_to_lily (n):
                 if a.wait_for_note ():
@@ -667,8 +694,9 @@ def musicxml_voice_to_lily_voice (voice):
                     number = int (n.get_parent ().number)
                 except ValueError:
                     number = 0
+                if number > 0:
+                    voice_builder.add_bar_check (number)
                 
-                voice_builder.add_bar_check (number)
             for a in musicxml_attributes_to_lily (n):
                 voice_builder.add_music (a, Rational (0))
             continue
@@ -689,7 +717,8 @@ def musicxml_voice_to_lily_voice (voice):
                 num = int (n.get_parent ().number)
             except ValueError:
                 num = 0
-            voice_builder.add_bar_check (num)
+            if num > 0:
+                voice_builder.add_bar_check (num)
         
         main_event = musicxml_note_to_lily_main_event (n)