From 35cc20ad3b54a73a183f4453ab2df7dd85320f55 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Sun, 30 Nov 2008 19:11:34 +0100 Subject: [PATCH] MusicXML: Implement general key signatures -) convert key signatures with , and to the keySignature alist of the form ((( octave . step)|step . alter) -) Semitone-key signatures are not yet working --- .../21b-KeySignatures-NonTraditional.xml | 86 +++++++++++++++++++ python/musicexp.py | 42 ++++++--- python/musicxml.py | 61 ++++++++++--- scripts/musicxml2ly.py | 73 +++++++++------- 4 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 input/regression/musicxml/21b-KeySignatures-NonTraditional.xml diff --git a/input/regression/musicxml/21b-KeySignatures-NonTraditional.xml b/input/regression/musicxml/21b-KeySignatures-NonTraditional.xml new file mode 100644 index 0000000000..bb3cc6e29a --- /dev/null +++ b/input/regression/musicxml/21b-KeySignatures-NonTraditional.xml @@ -0,0 +1,86 @@ + + + + + + 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. + + + + + MusicXML Part + + + + + + + 1 + + 3 + 1 + 5 + -1 + 6 + -1 + + + + G + 2 + + + + + C + 4 + + 2 + 1 + half + + + + + + 0 + -2 + 4 + 2 + 1 + -1 + 6 + 1 + 5 + 0 + 2 + 3 + 4 + 5 + 6 + + + + + C + 4 + + 2 + 1 + half + + + light-heavy + + + + + diff --git a/python/musicexp.py b/python/musicexp.py index c5635a2c02..a2d405973b 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -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): diff --git a/python/musicxml.py b/python/musicxml.py index 115bfaa445..8413480aef 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -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, diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index e06795d69b..dca950e8f7 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -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): -- 2.39.2