From: Reinhold Kainhofer Date: Sat, 5 Apr 2008 19:26:13 +0000 (+0200) Subject: MusicXML: Implement metronome mark (tempo marks) X-Git-Tag: release/2.11.44-1~28 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=8adcaecb5ec48cad518d6097b458ba26baaa34fe;p=lilypond.git MusicXML: Implement metronome mark (tempo marks) 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 . 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.. --- diff --git a/input/regression/musicxml/03c-MetronomeMarks.xml b/input/regression/musicxml/03c-MetronomeMarks.xml new file mode 100644 index 0000000000..5f4ef49890 --- /dev/null +++ b/input/regression/musicxml/03c-MetronomeMarks.xml @@ -0,0 +1,180 @@ + + + + Tempo markings + + + + + + + Grand Piano + + + 1 + 1 + + + + + + + + 1 + + 0 + major + + + + G + 2 + + + + + + quarter + + 100 + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + + Adagio + + + + long + 100 + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + + + + + + quarter + + half + + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + + + long + 32nd + + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + + + + + + quarter + + half + + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + + + quarter + + 77 + + + + + C5 + 1 + 1 + quarter + + + C5 + 1 + 1 + quarter + + + light-heavy + + + + + diff --git a/python/musicexp.py b/python/musicexp.py index cdd0271486..0346e91f4c 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -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) diff --git a/python/musicxml.py b/python/musicxml.py index adfd1027cb..194fffbd9c 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -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, diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 1f7f076b45..f9effaa432 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -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 ( 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)