]> git.donarmstrong.com Git - lilypond.git/commitdiff
MusicXML: Add basic support for figured bass
authorReinhold Kainhofer <reinhold@kainhofer.com>
Wed, 2 Apr 2008 21:51:55 +0000 (23:51 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Wed, 2 Apr 2008 21:51:55 +0000 (23:51 +0200)
The figured bass definitionis correctly generated and written to the -defs.ly
file, but not yet integrated into the score itself.

For this, I'm keeping a second voice builder while going through the voices'
children and insert the figured bass there and at the end extract the
definition from it.

python/musicexp.py
python/musicxml.py
python/rational.py
scripts/musicxml2ly.py

index 2ae8a6784f3ea480f3343d6539323fd79ec706d0..3219c689d249e6a50520707945b4b1ddb83cf711 100644 (file)
@@ -1270,6 +1270,58 @@ class StaffChange (Music):
         else:
             return ''
 
+class FiguredBassNote (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.number = ''
+        self.prefix = ''
+        self.suffix = ''
+    def set_prefix (self, prefix):
+        self.prefix = prefix
+    def set_suffix (self, suffix):
+        self.prefix = suffix
+    def set_number (self, number):
+        self.number = number
+    def ly_expression (self):
+        res = ''
+        if self.number:
+            res += self.number
+        else:
+            res += '_'
+        if self.prefix:
+            res += self.prefix
+        if self.suffix:
+            res += self.suffix
+        return res
+
+
+class FiguredBassEvent (NestedMusic):
+    def __init__ (self):
+        NestedMusic.__init__ (self)
+        self.duration = None
+        self.real_duration = 0
+        self.parentheses = False
+        return
+    def set_duration (self, dur):
+        self.duration = dur
+    def set_parentheses (self, par):
+        self.parentheses = par
+    def set_real_duration (self, dur):
+        self.real_duration = dur
+
+    def print_ly (self, printer):
+        figured_bass_events = [e for e in self.elements if
+               isinstance (e, FiguredBassNote)]
+        if figured_bass_events:
+          notes = []
+          for x in figured_bass_events:
+              notes.append (x.ly_expression ())
+          contents = string.join (notes)
+          if self.parentheses:
+              contents = '[%]' % contents
+          printer ('<%s>' % contents)
+          self.duration.print_ly (printer)
+
 
 class MultiMeasureRest(Music):
 
index e0612802b79beb84532a7de77dc730893d972223..adfd1027cbc53bb396325947ab5bdae7cad3748b 100644 (file)
@@ -529,6 +529,12 @@ class Part (Music_xml_node):
                 measure_position = Rational (0)
 
             for n in m.get_all_children ():
+                # figured bass has a duration, but applies to the next note
+                # and should not change the current measure position!
+                if isinstance (n, FiguredBass):
+                    n._divisions = factor.denominator ()
+                    continue
+
                 if isinstance (n, Hash_text):
                     continue
                dur = Rational (0)
@@ -656,7 +662,8 @@ class Part (Music_xml_node):
 
            if not (voice_id or isinstance (n, Attributes) or
                     isinstance (n, Direction) or isinstance (n, Partial) or
-                    isinstance (n, Barline) or isinstance (n, Harmony) ):
+                    isinstance (n, Barline) or isinstance (n, Harmony) or
+                    isinstance (n, FiguredBass) ):
                continue
 
            if isinstance (n, Attributes) and not start_attr:
@@ -688,9 +695,9 @@ class Part (Music_xml_node):
                     voices[v].add_element (n)
                 continue
 
-            if isinstance (n, Harmony):
-                # store the harmony element until we encounter the next note
-                # and assign it only to that one voice.
+            if isinstance (n, Harmony) or isinstance (n, FiguredBass):
+                # store the harmony or figured bass element until we encounter 
+                # the next note and assign it only to that one voice.
                 assign_to_next_note.append (n)
                 continue
 
@@ -880,6 +887,10 @@ class Frame_Note (Music_xml_node):
         else:
             return ''
 
+class FiguredBass (Music_xml_node):
+    pass
+
+
 
 ## need this, not all classes are instantiated
 ## for every input file. Only add those classes, that are either directly
@@ -902,6 +913,7 @@ class_dict = {
        'duration': Duration,
         'frame': Frame,
         'frame-note': Frame_Note,
+        'figured-bass': FiguredBass,
         'glissando': Glissando,
        'grace': Grace,
         'harmony': Harmony,
index 705829e120835269894d3991ad28013208d1d598..5a9ee629b3bcdc56328f9ecc9b9a4951d4fabe56 100644 (file)
@@ -38,13 +38,18 @@ class Rational(object):
            raise TypeError('denominator must have integer type')
        if not denominator:
            raise ZeroDivisionError('rational construction')
-       # Store the fraction in reduced form as _n/_d
-       factor = _gcf(numerator, denominator)
-       self._n = numerator // factor
-       self._d = denominator // factor
+       self._d = denominator
+       self._n = numerator
+       self.normalize_self()
+    # Cancel the fraction to reduced form
+    def normalize_self(self):
+       factor = _gcf(self._n, self._d)
+       self._n = self._n // factor
+       self._d = self._d // factor
        if self._d < 0:
            self._n = -self._n
            self._d = -self._d
+
     def numerator(self):
        return self._n
 
index a429b752510dbcb104937f080be03548f4493572..bfb9ab2b0e310d7e5f0fbd4e186a4fbc885989be 100644 (file)
@@ -426,7 +426,6 @@ def extract_score_structure (part_list, staffinfo):
     return structure
 
 
-
 def musicxml_duration_to_lily (mxl_note):
     d = musicexp.Duration ()
     # if the note has no Type child, then that method spits out a warning and 
@@ -443,16 +442,27 @@ def musicxml_duration_to_lily (mxl_note):
 
 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 ())
-    if d.duration_log < 0:
+
+    rational_len.normalize_self ()
+    d_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)
+    print "d_log: %s, rational_len: %s\n" % (d_log, rational_len)
+
+    # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration
+    if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ):
+        # account for the dots!
+        d.dots = (rational_len.numerator()-1)/2
+        d.duration_log = d_log - d.dots
+    elif (d_log >= 0):
+        d.duration_log = d_log
+        d.factor = Rational (rational_len.numerator ())
+    else:
         error_message (_ ("Encountered rational duration with denominator %s, "
                        "unable to convert to lilypond duration") %
                        rational_len.denominator ())
         # TODO: Test the above error message
         return None
-    else:
-        return d
+
+    return d
 
 def musicxml_partial_to_lily (partial_len):
     if partial_len > 0:
@@ -1233,6 +1243,59 @@ def musicxml_harmony_to_lily (n):
 
     return res
 
+def musicxml_figured_bass_note_to_lily (n):
+    res = musicexp.FiguredBassNote ()
+    suffix_dict = { 'sharp' : "+", 
+                    'flat' : "-", 
+                    'natural' : "!", 
+                    'double-sharp' : "++", 
+                    'flat-flat' : "--", 
+                    'sharp-sharp' : "++", 
+                    'slash' : "/" }
+    prefix = n.get_maybe_exist_named_child ('prefix')
+    if prefix:
+        res.set_prefix (suffix_dict.get (prefix.get_text (), ""))
+    fnumber = n.get_maybe_exist_typed_child ('figure-number')
+    if fnumber:
+        print "figure-number: %s, text: %s" % (fnumber, fnumber.get_text ())
+        res.set_number (fnumber.get_text ())
+    suffix = n.get_maybe_exist_named_child ('suffix')
+    if suffix:
+        res.set_suffix (suffix_dict.get (suffix.get_text (), ""))
+    if n.get_maybe_exist_named_child ('extend'):
+        # TODO: Implement extender lines (unfortunately, in lilypond you have 
+        #       to use \set useBassFigureExtenders = ##t, which turns them on
+        #       globally, while MusicXML has a property for each note...
+        #       I'm not sure there is a proper way to implement this cleanly
+        #n.extend
+        pass
+    return res
+
+
+
+def musicxml_figured_bass_to_lily (n):
+    if not isinstance (n, musicxml.FiguredBass):
+        return
+    res = musicexp.FiguredBassEvent ()
+    for i in n.get_named_children ('figure'):
+        note = musicxml_figured_bass_note_to_lily (i)
+        if note:
+            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
+        print "duration: %s, divisions: %s"  % (dur.get_text(), n._divisions)
+        length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4)
+        res.set_real_duration (length)
+        duration = rational_to_lily_duration (length)
+        if duration:
+            res.set_duration (duration)
+            print "Converted to duration: %s" % duration.ly_expression ()
+    if hasattr (n, 'parentheses') and n.parentheses == "yes":
+        res.set_parentheses (True)
+    return res
+
 instrument_drumtype_dict = {
     'Acoustic Snare Drum': 'acousticsnare',
     'Side Stick': 'sidestick',
@@ -1427,6 +1490,7 @@ class VoiceData:
     def __init__ (self):
         self.voicedata = None
         self.ly_voice = None
+        self.figured_bass = None
         self.lyrics_dict = {}
         self.lyrics_order = []
 
@@ -1454,6 +1518,8 @@ def musicxml_voice_to_lily_voice (voice):
     ignore_lyrics = False
 
     current_staff = None
+    
+    pending_figured_bass = []
 
     # Make sure that the keys in the dict don't get reordered, since
     # we need the correct ordering of the lyrics stanzas! By default,
@@ -1462,7 +1528,8 @@ def musicxml_voice_to_lily_voice (voice):
     for k in return_value.lyrics_order:
         lyrics[k] = []
 
-    voice_builder = LilyPondVoiceBuilder()
+    voice_builder = LilyPondVoiceBuilder ()
+    figured_bass_builder = LilyPondVoiceBuilder ()
 
     for n in voice._elements:
         if n.get_name () == 'forward':
@@ -1495,6 +1562,13 @@ def musicxml_voice_to_lily_voice (voice):
                 else:
                     voice_builder.add_command (a)
             continue
+        
+        if isinstance (n, musicxml.FiguredBass):
+            a = musicxml_figured_bass_to_lily (n)
+            print "Figured bass element: %s\n" % a
+            if a:
+                pending_figured_bass.append (a)
+            continue
 
         is_chord = n.get_maybe_exist_named_child ('chord')
         if not is_chord:
@@ -1584,6 +1658,18 @@ def musicxml_voice_to_lily_voice (voice):
             if voice_builder.current_duration () == 0 and n._duration > 0:
                 voice_builder.set_duration (n._duration)
         
+        # if we have a figured bass, set its voice builder to the correct position
+        # and insert the pending figures
+        if pending_figured_bass:
+          try:
+              figured_bass_builder.jumpto (n._when)
+          except NegativeSkip, neg:
+              pass
+          for fb in pending_figured_bass:
+              figured_bass_builder.add_music (fb, fb.real_duration)
+          pending_figured_bass = []
+
+
         notations_children = n.get_typed_children (musicxml.Notations)
         tuplet_event = None
         span_events = []
@@ -1759,6 +1845,25 @@ def musicxml_voice_to_lily_voice (voice):
         v.mode = mode
         return_value.ly_voice = v
     
+    # create \figuremode { figured bass elements }
+    if figured_bass_builder.elements:
+        fbass_music = musicexp.SequentialMusic ()
+        fbass_music.elements = figured_bass_builder.elements
+        v = musicexp.ModeChangingMusicWrapper()
+        v.mode = 'figuremode'
+        v.element = fbass_music
+        
+        #printer = musicexp.Output_printer()
+        #debug_file = "debug.out"
+        #progress (_ ("FiguredBass debug output to `%s'") % debug_file)
+        #printer.set_file (codecs.open (debug_file, 'wb', encoding='utf-8'))
+        #v.print_ly( printer )
+        #printer.close ()
+
+        return_value.figured_bass = v
+    
+    
+    print figured_bass_builder.elements
     return return_value
 
 def musicxml_id_to_lily (id):
@@ -1925,6 +2030,10 @@ def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr):
     str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr)
     return musicxml_id_to_lily (str) 
 
+def music_xml_figuredbass_name_to_lily_name (part_id, name):
+    str = "Part%sVoice%sFiguredBass" % (part_id, name)
+    return musicxml_id_to_lily (str) 
+
 def print_voice_definitions (printer, part_list, voices):
     for part in part_list:
         part_id = part.id
@@ -1936,9 +2045,14 @@ def print_voice_definitions (printer, part_list, voices):
             printer.newline()
             for l in voice.lyrics_order:
                 lname = music_xml_lyrics_name_to_lily_name (part_id, name, l)
-                printer.dump ('%s = ' %lname )
+                printer.dump ('%s = ' % lname )
                 voice.lyrics_dict[l].print_ly (printer)
                 printer.newline()
+            if voice.figured_bass:
+                fbname = music_xml_figuredbass_name_to_lily_name (part_id, name)
+                printer.dump ('%s = ' % fbname )
+                voice.figured_bass.print_ly (printer)
+                printer.newline()
 
 
 def uniq_list (l):