]> git.donarmstrong.com Git - lilypond.git/commitdiff
MusicXML: Implement general key signatures
authorReinhold Kainhofer <reinhold@kainhofer.com>
Sun, 30 Nov 2008 18:11:34 +0000 (19:11 +0100)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Sun, 30 Nov 2008 20:25:21 +0000 (21:25 +0100)
-) convert key signatures with <key-step>, <key-alter> and <key-octave>
   to the keySignature alist of the form ((( octave . step)|step . alter)
-) Semitone-key signatures are not yet working

input/regression/musicxml/21b-KeySignatures-NonTraditional.xml [new file with mode: 0644]
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

diff --git a/input/regression/musicxml/21b-KeySignatures-NonTraditional.xml b/input/regression/musicxml/21b-KeySignatures-NonTraditional.xml
new file mode 100644 (file)
index 0000000..bb3cc6e
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 1.0 Partwise//EN"
+                                "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise>
+  <identification>
+    <miscellaneous>
+      <miscellaneous-field name="description">Non-traditional key signatures,
+          where each alteration is separately given. Here we have (f sharp, 
+          a flat, b flat) and (c flatflat, g sharp sharp, d flat, b sharp, f 
+          natural), where in the second case an explicit octave is given for 
+          each alteration.</miscellaneous-field>
+    </miscellaneous>
+  </identification>
+  <part-list>
+    <score-part id="P1">
+      <part-name>MusicXML Part</part-name>
+    </score-part>
+  </part-list>
+  <!--=========================================================-->
+  <part id="P1">
+    <measure number="1">
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <key-step>3</key-step>
+          <key-alter>1</key-alter>
+          <key-step>5</key-step>
+          <key-alter>-1</key-alter>
+          <key-step>6</key-step>
+          <key-alter>-1</key-alter>
+        </key>
+        <time>
+          <beats>2</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+      </note>
+    </measure>
+    <measure number="2">
+      <attributes>
+        <key>
+          <key-step>0</key-step>
+          <key-alter>-2</key-alter>
+          <key-step>4</key-step>
+          <key-alter>2</key-alter>
+          <key-step>1</key-step>
+          <key-alter>-1</key-alter>
+          <key-step>6</key-step>
+          <key-alter>1</key-alter>
+          <key-step>5</key-step>
+          <key-alter>0</key-alter>
+          <key-octave number="1">2</key-octave>
+          <key-octave number="2">3</key-octave>
+          <key-octave number="3">4</key-octave>
+          <key-octave number="4">5</key-octave>
+          <key-octave number="5">6</key-octave>
+        </key>
+      </attributes>
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+      </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>
+
index c5635a2c020c9cbf82673d2d583fa38a69eabb25..a2d405973bcea05008e0da791c638021b1ae9f45 100644 (file)
@@ -205,6 +205,7 @@ class Duration:
 # Implement the different note names for the various languages
 def pitch_generic (pitch, notenames, accidentals):
     str = notenames[pitch.step]
+    # TODO: Handle microtones!
     if pitch.alteration < 0:
         str += accidentals[0] * (-pitch.alteration)
     elif pitch.alteration > 0:
@@ -1422,20 +1423,39 @@ class NoteEvent(RhythmicEvent):
 class KeySignatureChange (Music):
     def __init__ (self):
         Music.__init__ (self)
-        self.scale = []
-        self.tonic = Pitch()
+        self.tonic = None
         self.mode = 'major'
-        
+        self.non_standard_alterations = None
+
+    def format_non_standard_alteration (self, a):
+        alter_dict = { -2: ",DOUBLE-FLAT",
+                       -1: ",FLAT",
+                        0: ",NATURAL",
+                        1: ",SHARP",
+                        2: ",DOUBLE-SHARP"}
+        if len (a) == 2:
+            return "( %s . %s )" % (a[0], alter_dict.get (a[1], a[1]))
+        elif len (a) == 3:
+            return "(( %s . %s ) . %s )" % (a[2], a[0], alter_dict.get (a[1], a[1]))
+        else:
+            return ''
+
     def ly_expression (self):
-        return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
+        if self.tonic:
+            return '\\key %s \\%s' % (self.tonic.ly_step_expression (),
                      self.mode)
-    
-    def lisp_expression (self):
-        pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)]
-        scale_str = ("'(%s)" % string.join (pairs))
-
-        return """ (make-music 'KeyChangeEvent
-     'pitch-alist %s) """ % scale_str
+        elif self.non_standard_alterations:
+            alterations = [self.format_non_standard_alteration (a) for
+                                        a in self.non_standard_alterations]
+            # TODO: Check if the alterations should really be given in reverse
+            #       order of if that's just a bug in Lilypond. If it's a bug,
+            #       fix it and remove the following call, otherwise add a
+            #       proper comment here!
+            alterations.reverse ()
+            print "Non-Standard alterations printed out: %s" % alterations
+            return "\\set Staff.keySignature = #`(%s)" % string.join (alterations, " ")
+        else:
+            return ''
 
 class TimeSignatureChange (Music):
     def __init__ (self):
index 115bfaa4455354099fdaaf7b02b21d3f5d63ece0..8413480aef40c668679ce9818ce35cfb213677ae 100644 (file)
@@ -389,22 +389,56 @@ class Attributes (Measure_element):
         return clefinfo
 
     def get_key_signature (self):
-        "return (fifths, mode) tuple"
+        "return (fifths, mode) tuple if the key signatures is given as "
+        "major/minor in the Circle of fifths. Otherwise return an alterations"
+        "list of the form [[step,alter<,octave>], [step,alter<,octave>], ...], "
+        "where the octave values are optional."
 
         key = self.get_named_attribute ('key')
-        mode_node = key.get_maybe_exist_named_child ('mode')
-        mode = None
-        if mode_node:
-            mode = mode_node.get_text ()
-        if not mode or mode == '':
-            mode = 'major'
-
-        fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
-        return (fifths, mode)
-        
+        if not key:
+            return None
+        fifths_elm = key.get_maybe_exist_named_child ('fifths')
+        if fifths_elm:
+            mode_node = key.get_maybe_exist_named_child ('mode')
+            mode = None
+            if mode_node:
+                mode = mode_node.get_text ()
+            if not mode or mode == '':
+                mode = 'major'
+            fifths = int (fifths_elm.get_text ())
+            # TODO: Shall we try to convert the key-octave and the cancel, too?
+            return (fifths, mode)
+        else:
+            alterations = []
+            current_step = 0
+            for i in key.get_all_children ():
+                if isinstance (i, KeyStep):
+                    current_step = int (i.get_text ())
+                elif isinstance (i, KeyAlter):
+                    alterations.append ([current_step, int (i.get_text ())])
+                elif isinstance (i, KeyOctave):
+                    nr = -1
+                    if hasattr (i, 'number'):
+                        nr = int (i.number)
+                    if (nr > 0) and (nr <= len (alterations)):
+                        # MusicXML Octave 4 is middle C -> shift to 0
+                        alterations[nr-1].append (int (i.get_text ())-4)
+                    else:
+                        i.message (_ ("Key alteration octave given for a "
+                            "non-existing alteration nr. %s, available numbers: %s!") % (nr, len(alterations)))
+                    i.message ( "Non-standard key signature (after octave %s for alter nr %s): %s" % (i.get_text (), nr, alterations))
+            i.message ( "Non-standard key signature with alterations %s found!" % alterations)
+            return alterations
+
     def get_transposition (self):
         return self.get_named_attribute ('transpose')
-        
+
+class KeyAlter (Music_xml_node):
+    pass
+class KeyStep (Music_xml_node):
+    pass
+class KeyOctave (Music_xml_node):
+    pass
 
 
 class Barline (Measure_element):
@@ -1153,6 +1187,9 @@ class_dict = {
        'grace': Grace,
         'harmony': Harmony,
         'identification': Identification,
+        'key-alter': KeyAlter,
+        'key-octave': KeyOctave,
+        'key-step': KeyStep,
         'lyric': Lyric,
        'measure': Measure,
        'notations': Notations,
index e06795d69b257a1a8928b9574b7b9867478912e4..dca950e8f7ae2a062eeb7ae05394807f18f4bfb5 100644 (file)
@@ -760,40 +760,51 @@ def musicxml_time_to_lily (attributes):
     return change
 
 def musicxml_key_to_lily (attributes):
-    start_pitch  = musicexp.Pitch ()
-    (fifths, mode) = attributes.get_key_signature () 
-    try:
-        (n,a) = {
-            'major'     : (0,0),
-            'minor'     : (5,0),
-            'ionian'    : (0,0),
-            'dorian'    : (1,0),
-            'phrygian'  : (2,0),
-            'lydian'    : (3,0),
-            'mixolydian': (4,0),
-            'aeolian'   : (5,0),
-            'locrian'   : (6,0),
-            }[mode]
-        start_pitch.step = n
-        start_pitch.alteration = a
-    except  KeyError:
-        error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % mode)
-
-    fifth = musicexp.Pitch()
-    fifth.step = 4
-    if fifths < 0:
-        fifths *= -1
-        fifth.step *= -1
-        fifth.normalize ()
+    key_sig = attributes.get_key_signature () 
+    if not key_sig or not (isinstance (key_sig, list) or isinstance (key_sig, tuple)):
+        error_message (_ ("Unable to extract key signature!"))
+        return None
+    
+    change = musicexp.KeySignatureChange()
     
-    for x in range (fifths):
-        start_pitch = start_pitch.transposed (fifth)
+    if len (key_sig) == 2 and not isinstance (key_sig[0], list):
+        # standard key signature, (fifths, mode)
+        (fifths, mode) = key_sig
+        change.mode = mode
 
-    start_pitch.octave = 0
+        start_pitch  = musicexp.Pitch ()
+        start_pitch.octave = 0
+        try:
+            (n,a) = {
+                'major'     : (0,0),
+                'minor'     : (5,0),
+                'ionian'    : (0,0),
+                'dorian'    : (1,0),
+                'phrygian'  : (2,0),
+                'lydian'    : (3,0),
+                'mixolydian': (4,0),
+                'aeolian'   : (5,0),
+                'locrian'   : (6,0),
+                }[mode]
+            start_pitch.step = n
+            start_pitch.alteration = a
+        except  KeyError:
+            error_message (_ ("unknown mode %s, expecting 'major' or 'minor' "
+                "or a church mode!") % mode)
+
+        fifth = musicexp.Pitch()
+        fifth.step = 4
+        if fifths < 0:
+            fifths *= -1
+            fifth.step *= -1
+            fifth.normalize ()
+        for x in range (fifths):
+            start_pitch = start_pitch.transposed (fifth)
+        change.tonic = start_pitch
 
-    change = musicexp.KeySignatureChange()
-    change.mode = mode
-    change.tonic = start_pitch
+    else:
+        # Non-standard key signature of the form [[step,alter<,octave>],...]
+        change.non_standard_alterations = key_sig
     return change
 
 def musicxml_transpose_to_lily (attributes):