X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=python%2Fmusicexp.py;h=8997b1674ccae7c14f7d4989f7469d2c354e286a;hb=589f549df3d23e7f4b91e42fead64c3e91b28905;hp=df54f70106c113f3e30681093e043511d3e2e59f;hpb=3c7ed0acd8e3cd7005ae3341012fef483b7b6cb7;p=lilypond.git diff --git a/python/musicexp.py b/python/musicexp.py index df54f70106..8997b1674c 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -1,8 +1,12 @@ +# -*- coding: utf-8 -*- import inspect import sys import string import re +import math import lilylib as ly +import warnings +import utilities _ = ly._ @@ -11,17 +15,15 @@ from rational import Rational # Store previously converted pitch for \relative conversion as a global state variable previous_pitch = None relative_pitches = False - -def warning (str): - ly.stderr_write ((_ ("warning: %s") % str) + "\n") - +whatOrnament = "" +ly_dur = None # stores lilypond durations def escape_instrument_string (input_string): retstring = string.replace (input_string, "\"", "\\\"") if re.match ('.*[\r\n]+.*', retstring): rx = re.compile (r'[\n\r]+') strings = rx.split (retstring) - retstring = "\\markup { \\column { " + retstring = "\\markup { \\center-column { " for s in strings: retstring += "\\line {\"" + s + "\"} " retstring += "} }" @@ -37,14 +39,11 @@ class Output_stack_element: o.factor = self.factor return o -class Output_printer: - - """A class that takes care of formatting (eg.: indenting) a +class Output_printer(object): + """ + A class that takes care of formatting (eg.: indenting) a Music expression as a .ly file. - """ - ## TODO: support for \relative. - def __init__ (self): self._line = '' self._indent = 4 @@ -57,22 +56,21 @@ class Output_printer: def set_file (self, file): self._file = file - - def dump_version (self): - self.newline () - self.print_verbatim ('\\version "@TOPLEVEL_VERSION@"') + + def dump_version (self, version): + self.print_verbatim ('\\version "' + version + '"') self.newline () - + def get_indent (self): return self._nesting * self._indent - + def override (self): last = self._output_state_stack[-1] self._output_state_stack.append (last.copy()) - + def add_factor (self, factor): self.override() - self._output_state_stack[-1].factor *= factor + self._output_state_stack[-1].factor *= factor def revert (self): del self._output_state_stack[-1] @@ -87,21 +85,30 @@ class Output_printer: def unformatted_output (self, str): # don't indent on \< and indent only once on << - self._nesting += ( str.count ('<') - - str.count ('\<') - str.count ('<<') - + str.count ('{') ) - self._nesting -= ( str.count ('>') - str.count ('\>') - str.count ('>>') + self._nesting += (str.count ('<') + - str.count ('\<') - str.count ('<<') + + str.count ('{')) + self._nesting -= (str.count ('>') - str.count ('\>') - str.count ('>>') - str.count ('->') - str.count ('_>') - str.count ('^>') - + str.count ('}') ) + + str.count ('}')) self.print_verbatim (str) - + def print_duration_string (self, str): if self._last_duration == str: return - + self.unformatted_output (str) - + +# def print_note_color (self, object, rgb=None): +# if rgb: +# str = ("\\once\\override %s #'color = #(rgb-color %s # %s %s)" % (object, rgb[0], rgb[1], rgb[2])) +# else: +# str = "\\revert %s #'color" % object +# self.newline() +# self.add_word(str) +# self.newline() + def add_word (self, str): if (len (str) + 1 + len (self._line) > self._line_len): self.newline() @@ -111,7 +118,7 @@ class Output_printer: self._line += ' ' self.unformatted_output (str) self._skipspace = False - + def newline (self): self._file.write (self._line + '\n') self._line = ' ' * self._indent * self._nesting @@ -119,20 +126,20 @@ class Output_printer: def skipspace (self): self._skipspace = True - + def __call__(self, arg): self.dump (arg) - + def dump (self, str): if self._skipspace: self._skipspace = False self.unformatted_output (str) else: - words = string.split (str) + # Avoid splitting quoted strings (e.g. "1. Wie") when indenting. + words = utilities.split_string_and_preserve_doublequoted_substrings(str) for w in words: self.add_word (w) - def close (self): self.newline () self._file.close () @@ -140,19 +147,19 @@ class Output_printer: class Duration: - def __init__ (self): + def __init__(self): self.duration_log = 0 self.dots = 0 - self.factor = Rational (1) - - def lisp_expression (self): + self.factor = Rational(1) + + def lisp_expression(self): return '(ly:make-duration %d %d %d %d)' % (self.duration_log, self.dots, - self.factor.numerator (), - self.factor.denominator ()) - + self.factor.numerator(), + self.factor.denominator()) - def ly_expression (self, factor = None, scheme_mode = False): + def ly_expression(self, factor=None, scheme_mode=False): + global ly_dur # stores lilypond durations if not factor: factor = self.factor @@ -161,54 +168,148 @@ class Duration: longer_dict = {-1: "breve", -2: "longa"} else: longer_dict = {-1: "\\breve", -2: "\\longa"} - str = longer_dict.get (self.duration_log, "1") + dur_str = longer_dict.get(self.duration_log, "1") else: - str = '%d' % (1 << self.duration_log) - str += '.'*self.dots + dur_str = '%d' % (1 << self.duration_log) + dur_str += '.' * self.dots - if factor <> Rational (1,1): - if factor.denominator () <> 1: - str += '*%d/%d' % (factor.numerator (), factor.denominator ()) + if factor <> Rational(1, 1): + if factor.denominator() <> 1: + dur_str += '*%d/%d' % (factor.numerator(), factor.denominator()) else: - str += '*%d' % factor.numerator () + dur_str += '*%d' % factor.numerator() + + if dur_str.isdigit(): + ly_dur = int(dur_str) + # TODO: We need to deal with dotted notes and scaled durations + # otherwise ly_dur won't work in combination with tremolos. + return dur_str + + def print_ly(self, outputter): + dur_str = self.ly_expression(self.factor / outputter.duration_factor()) + outputter.print_duration_string(dur_str) - return str - - def print_ly (self, outputter): - str = self.ly_expression (self.factor / outputter.duration_factor ()) - outputter.print_duration_string (str) - def __repr__(self): return self.ly_expression() - - def copy (self): - d = Duration () + + def copy(self): + d = Duration() d.dots = self.dots d.duration_log = self.duration_log d.factor = self.factor return d - def get_length (self): - dot_fact = Rational( (1 << (1 + self.dots))-1, + def get_length(self): + dot_fact = Rational((1 << (1 + self.dots)) - 1, 1 << self.dots) - log = abs (self.duration_log) + log = abs(self.duration_log) dur = 1 << log if self.duration_log < 0: - base = Rational (dur) + base = Rational(dur) else: - base = Rational (1, dur) + base = Rational(1, dur) return base * dot_fact * self.factor +def set_create_midi(option): + """ + Implement the midi command line option '-m' and '--midi'. + If True, add midi-block to .ly file (see L{musicexp.Score.print_ly}). + + @param option: Indicates whether the midi-block has to be added or not. + @type option: boolean + """ + global midi_option + midi_option = option + +def get_create_midi (): + """ + Return, if exists the state of the midi-option. + + @return: The state of the midi-option. + @rtype: boolean + """ + try: + return midi_option + except: + return False + +# implement the command line option '--transpose' +def set_transpose(option): + global transpose_option + transpose_option = option + +def get_transpose(optType): + try: + if(optType == "string"): + return '\\transpose c %s' % transpose_option + elif(optType == "integer"): + p = generic_tone_to_pitch(transpose_option) + return p.semitones() + except: + if(optType == "string"): + return "" + elif(optType == "integer"): + return 0 + +# implement the command line option '--tab-clef' +def set_tab_clef(option): + global tab_clef_option + tab_clef_option = option + +def get_tab_clef(): + try: + return ("tab", tab_clef_option)[tab_clef_option == "tab" or tab_clef_option == "moderntab"] + except: + return "tab" + +# definitions of the command line option '--string-numbers' +def set_string_numbers(option): + global string_numbers_option + string_numbers_option = option + +def get_string_numbers(): + try: + return ("t", string_numbers_option)[string_numbers_option == "t" or string_numbers_option == "f"] + except: + return "t" + +def generic_tone_to_pitch (tone): + accidentals_dict = { + "" : 0, + "es" : -1, + "s" : -1, + "eses" : -2, + "ses" : -2, + "is" : 1, + "isis" : 2 + } + p = Pitch () + tone_ = tone.strip().lower() + p.octave = tone_.count("'") - tone_.count(",") + tone_ = tone_.replace(",","").replace("'","") + p.step = ((ord (tone_[0]) - ord ('a') + 5) % 7) + p.alteration = accidentals_dict.get(tone_[1:], 0) + return p # Implement the different note names for the various languages def pitch_generic (pitch, notenames, accidentals): str = notenames[pitch.step] - if pitch.alteration < 0: - str += accidentals[0] * (-pitch.alteration) + halftones = int (pitch.alteration) + if halftones < 0: + str += accidentals[0] * (-halftones) elif pitch.alteration > 0: - str += accidentals[3] * (pitch.alteration) + str += accidentals[3] * (halftones) + # Handle remaining fraction to pitch.alteration (for microtones) + if (halftones != pitch.alteration): + if None in accidentals[1:3]: + ly.warning (_ ("Language does not support microtones contained in the piece")) + else: + try: + str += {-0.5: accidentals[1], 0.5: accidentals[2]}[pitch.alteration - halftones] + except KeyError: + ly.warning (_ ("Language does not support microtones contained in the piece")) return str def pitch_general (pitch): @@ -230,7 +331,7 @@ def pitch_norsk (pitch): return pitch_deutsch (pitch) def pitch_svenska (pitch): - str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['ess', '', '', 'iss']) + str = pitch_generic (pitch, ['c', 'd', 'e', 'f', 'g', 'a', 'h'], ['ess', None, None, 'iss']) return str.replace ('hess', 'b').replace ('aes', 'as').replace ('ees', 'es') def pitch_italiano (pitch): @@ -241,11 +342,11 @@ def pitch_catalan (pitch): return pitch_italiano (pitch) def pitch_espanol (pitch): - str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 's']) + str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', None, None, 's']) return str def pitch_vlaams (pitch): - str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', '', '', 'k']) + str = pitch_generic (pitch, ['do', 're', 'mi', 'fa', 'sol', 'la', 'si'], ['b', None, None, 'k']) return str def set_pitch_language (language): @@ -265,24 +366,24 @@ def set_pitch_language (language): # global variable to hold the formatting function. pitch_generating_function = pitch_general - class Pitch: def __init__ (self): self.alteration = 0 self.step = 0 self.octave = 0 - + self._force_absolute_pitch = False + def __repr__(self): return self.ly_expression() def transposed (self, interval): c = self.copy () - c.alteration += interval.alteration + c.alteration += interval.alteration c.step += interval.step c.octave += interval.octave c.normalize () - - target_st = self.semitones() + interval.semitones() + + target_st = self.semitones() + interval.semitones() c.alteration += target_st - c.semitones() return c @@ -291,7 +392,7 @@ class Pitch: c.step += 7 c.octave -= 1 c.octave += c.step / 7 - c.step = c.step % 7 + c.step = c.step % 7 def lisp_expression (self): return '(ly:make-pitch %d %d %d)' % (self.octave, @@ -302,16 +403,39 @@ class Pitch: p = Pitch () p.alteration = self.alteration p.step = self.step - p.octave = self.octave + p.octave = self.octave + p._force_absolute_pitch = self._force_absolute_pitch return p def steps (self): - return self.step + self.octave *7 + return self.step + self.octave * 7 def semitones (self): - return self.octave * 12 + [0,2,4,5,7,9,11][self.step] + self.alteration - - def ly_step_expression (self): + return self.octave * 12 + [0, 2, 4, 5, 7, 9, 11][self.step] + self.alteration + + def normalize_alteration (c): + if(c.alteration < 0 and [True, False, False, True, False, False, False][c.step]): + c.alteration += 1 + c.step -= 1 + elif(c.alteration > 0 and [False, False, True, False, False, False, True][c.step]): + c.alteration -= 1 + c.step += 1 + c.normalize () + + def add_semitones (self, number): + semi = number + self.alteration + self.alteration = 0 + if(semi == 0): + return + sign = (1,-1)[semi < 0] + prev = self.semitones() + while abs((prev + semi) - self.semitones ()) > 1: + self.step += sign + self.normalize() + self.alteration += (prev + semi) - self.semitones () + self.normalize_alteration () + + def ly_step_expression (self): return pitch_generating_function (self) def absolute_pitch (self): @@ -340,32 +464,31 @@ class Pitch: def ly_expression (self): str = self.ly_step_expression () - if relative_pitches: + if relative_pitches and not self._force_absolute_pitch: str += self.relative_pitch () else: str += self.absolute_pitch () - return str - + def print_ly (self, outputter): outputter (self.ly_expression()) - + class Music: def __init__ (self): self.parent = None self.start = Rational (0) self.comment = '' self.identifier = None - + def get_length(self): return Rational (0) - + def get_properties (self): return '' - + def has_children (self): return False - + def get_index (self): if self.parent: return self.parent.elements.index (self) @@ -373,13 +496,13 @@ class Music: return None def name (self): return self.__class__.__name__ - + def lisp_expression (self): name = self.name() props = self.get_properties () - - return "(make-music '%s %s)" % (name, props) + + return "(make-music '%s %s)" % (name, props) def set_start (self, start): self.start = start @@ -389,26 +512,26 @@ class Music: return self return None - def print_comment (self, printer, text = None): + def print_comment (self, printer, text=None): if not text: text = self.comment if not text: return - + if text == '\n': printer.newline () return - + lines = string.split (text, '\n') for l in lines: if l: printer.unformatted_output ('% ' + l) printer.newline () - + def print_with_identifier (self, printer): - if self.identifier: + if self.identifier: printer ("\\%s" % self.identifier) else: self.print_ly (printer) @@ -445,13 +568,86 @@ class RelativeMusic (MusicWrapper): previous_pitch = self.basepitch if not previous_pitch: previous_pitch = Pitch () - func ('\\relative %s%s' % (pitch_generating_function (previous_pitch), + func ('\\relative %s%s' % (pitch_generating_function (previous_pitch), previous_pitch.absolute_pitch ())) MusicWrapper.print_ly (self, func) relative_pitches = prev_relative_pitches class TimeScaledMusic (MusicWrapper): + def __init__ (self): + MusicWrapper.__init__ (self) + self.numerator = 1 + self.denominator = 1 + self.display_number = "actual" # valid values "actual" | "both" | None + # Display the basic note length for the tuplet: + self.display_type = None # value values "actual" | "both" | None + self.display_bracket = "bracket" # valid values "bracket" | "curved" | None + self.actual_type = None # The actually played unit of the scaling + self.normal_type = None # The basic unit of the scaling + self.display_numerator = None + self.display_denominator = None + def print_ly (self, func): + if self.display_bracket == None: + func ("\\once \\override TupletBracket #'stencil = ##f") + func.newline () + elif self.display_bracket == "curved": + ly.warning (_ ("Tuplet brackets of curved shape are not correctly implemented")) + func ("\\once \\override TupletBracket #'stencil = #ly:slur::print") + func.newline () + + base_number_function = {None: "#f", + "actual": "tuplet-number::calc-denominator-text", + "both": "tuplet-number::calc-fraction-text"}.get (self.display_number, None) + # If we have non-standard numerator/denominator, use our custom function + if self.display_number == "actual" and self.display_denominator: + base_number_function = "(tuplet-number::non-default-tuplet-denominator-text %s)" % self.display_denominator + elif self.display_number == "both" and (self.display_denominator or self.display_numerator): + if self.display_numerator: + num = self.display_numerator + else: + num = "#f" + if self.display_denominator: + den = self.display_denominator + else: + den = "#f" + base_number_function = "(tuplet-number::non-default-tuplet-fraction-text %s %s)" % (den, num) + + + if self.display_type == "actual" and self.normal_type: + # Obtain the note duration in scheme-mode, i.e. \longa as \\longa + base_duration = self.normal_type.ly_expression (None, True) + func ("\\once \\override TupletNumber #'text = #(tuplet-number::append-note-wrapper %s \"%s\")" % + (base_number_function, base_duration)) + func.newline () + elif self.display_type == "both": # TODO: Implement this using actual_type and normal_type! + if self.display_number == None: + func ("\\once \\override TupletNumber #'stencil = ##f") + func.newline () + elif self.display_number == "both": + den_duration = self.normal_type.ly_expression (None, True) + # If we don't have an actual type set, use the normal duration! + if self.actual_type: + num_duration = self.actual_type.ly_expression (None, True) + else: + num_duration = den_duration + if (self.display_denominator or self.display_numerator): + func ("\\once \\override TupletNumber #'text = #(tuplet-number::non-default-fraction-with-notes %s \"%s\" %s \"%s\")" % + (self.display_denominator, den_duration, + self.display_numerator, num_duration)) + func.newline () + else: + func ("\\once \\override TupletNumber #'text = #(tuplet-number::fraction-with-notes \"%s\" \"%s\")" % + (den_duration, num_duration)) + func.newline () + else: + if self.display_number == None: + func ("\\once \\override TupletNumber #'stencil = ##f") + func.newline () + elif self.display_number == "both": + func ("\\once \\override TupletNumber #'text = #%s" % base_number_function) + func.newline () + func ('\\times %d/%d ' % (self.numerator, self.denominator)) func.add_factor (Rational (self.numerator, self.denominator)) @@ -466,7 +662,7 @@ class NestedMusic(Music): def append (self, what): if what: self.elements.append (what) - + def has_children (self): return self.elements @@ -474,7 +670,7 @@ class NestedMusic(Music): assert elt.parent == None assert succ == None or succ in self.elements - + idx = 0 if succ: idx = self.elements.index (succ) @@ -488,7 +684,7 @@ class NestedMusic(Music): self.elements.insert (idx, elt) elt.parent = self - + def get_properties (self): return ("'elements (list %s)" % string.join (map (lambda x: x.lisp_expression(), @@ -497,22 +693,22 @@ class NestedMusic(Music): def get_subset_properties (self, predicate): return ("'elements (list %s)" % string.join (map (lambda x: x.lisp_expression(), - filter ( predicate, self.elements)))) + filter (predicate, self.elements)))) def get_neighbor (self, music, dir): assert music.parent == self idx = self.elements.index (music) idx += dir - idx = min (idx, len (self.elements) -1) + idx = min (idx, len (self.elements) - 1) idx = max (idx, 0) return self.elements[idx] def delete_element (self, element): assert element in self.elements - + self.elements.remove (element) element.parent = None - + def set_start (self, start): self.start = start for e in self.elements: @@ -522,17 +718,17 @@ class NestedMusic(Music): r = Music.find_first (self, predicate) if r: return r - + for e in self.elements: r = e.find_first (predicate) if r: return r return None - + class SequentialMusic (NestedMusic): def get_last_event_chord (self): value = None - at = len( self.elements ) - 1 + at = len(self.elements) - 1 while (at >= 0 and not isinstance (self.elements[at], ChordEvent) and not isinstance (self.elements[at], BarLine)): @@ -542,7 +738,7 @@ class SequentialMusic (NestedMusic): value = self.elements[at] return value - def print_ly (self, printer, newline = True): + def print_ly (self, printer, newline=True): printer ('{') if self.comment: self.print_comment (printer) @@ -555,15 +751,15 @@ class SequentialMusic (NestedMusic): printer ('}') if newline: printer.newline() - + def lisp_sub_expression (self, pred): name = self.name() props = self.get_subset_properties (pred) - - return "(make-music '%s %s)" % (name, props) - + + return "(make-music '%s %s)" % (name, props) + def set_start (self, start): for e in self.elements: e.set_start (start) @@ -582,8 +778,8 @@ class RepeatedMusic: self.music = SequentialMusic () self.music.elements = music else: - warning (_ ("unable to set the music %(music)s for the repeat %(repeat)s" % \ - {'music':music, 'repeat':self})) + ly.warning (_ ("unable to set the music %(music)s for the repeat %(repeat)s") % \ + {'music':music, 'repeat':self}) def add_ending (self, music): self.endings.append (music) def print_ly (self, printer): @@ -591,7 +787,7 @@ class RepeatedMusic: if self.music: self.music.print_ly (printer) else: - warning (_ ("encountered repeat without body")) + ly.warning (_ ("encountered repeat without body")) printer.dump ('{}') if self.endings: printer.dump ('\\alternative {') @@ -605,39 +801,59 @@ class Lyrics: self.lyrics_syllables = [] def print_ly (self, printer): - printer.dump ("\lyricmode {") - for l in self.lyrics_syllables: - printer.dump ( "%s " % l ) - printer.dump ("}") + printer.dump (self.ly_expression ()) + printer.newline() + printer.dump ('}') + printer.newline() def ly_expression (self): - lstr = "\lyricmode {\n " + lstr = "\lyricmode {\set ignoreMelismata = ##t" for l in self.lyrics_syllables: - lstr += l + " " - lstr += "\n}" + lstr += l + #lstr += "\n}" return lstr - class Header: + def __init__ (self): self.header_fields = {} + def set_field (self, field, value): self.header_fields[field] = value - def print_ly (self, printer): - printer.dump ("\header {") - printer.newline () - for (k,v) in self.header_fields.items (): + def format_header_strings(self, key, value, printer): + printer.dump(key + ' = ') + + # If a header item contains a line break, it is segmented. The + # substrings are formatted with the help of \markup, using + # \column and \line. + if '\n' in value: + value = value.replace('"', '') + printer.dump(r'\markup \column {') + substrings = value.split('\n') + for s in substrings: + printer.newline() + printer.dump(r'\line { "' + s + '"}') + printer.dump('}') + printer.newline() + else: + printer.dump(value) + printer.newline() + + def print_ly(self, printer): + printer.dump("\header {") + printer.newline() + for (k, v) in self.header_fields.items(): if v: - printer.dump ('%s = %s' % (k,v)) - printer.newline () - printer.dump ("}") - printer.newline () - printer.newline () + self.format_header_strings(k, v, printer) + #printer.newline() + printer.dump("}") + printer.newline() + printer.newline() class Paper: - def __init__ (self): + def __init__(self): self.global_staff_size = -1 # page size self.page_width = -1 @@ -651,28 +867,51 @@ class Paper: self.system_right_margin = -1 self.system_distance = -1 self.top_system_distance = -1 + self.indent = 0 + self.short_indent = 0 + self.instrument_names = [] def print_length_field (self, printer, field, value): if value >= 0: printer.dump ("%s = %s\\cm" % (field, value)) printer.newline () + + def get_longest_instrument_name(self): + result = '' + for name in self.instrument_names: + lines = name.split('\n') + for line in lines: + if len(line) > len(result): + result = line + return result + def print_ly (self, printer): if self.global_staff_size > 0: printer.dump ('#(set-global-staff-size %s)' % self.global_staff_size) printer.newline () printer.dump ('\\paper {') printer.newline () + printer.dump ("markup-system-spacing #'padding = #2") + printer.newline () self.print_length_field (printer, "paper-width", self.page_width) self.print_length_field (printer, "paper-height", self.page_height) self.print_length_field (printer, "top-margin", self.top_margin) - self.print_length_field (printer, "botton-margin", self.bottom_margin) + self.print_length_field (printer, "bottom-margin", self.bottom_margin) self.print_length_field (printer, "left-margin", self.left_margin) # TODO: maybe set line-width instead of right-margin? self.print_length_field (printer, "right-margin", self.right_margin) # TODO: What's the corresponding setting for system_left_margin and - # system_right_margin in Lilypond? + # system_right_margin in LilyPond? self.print_length_field (printer, "between-system-space", self.system_distance) self.print_length_field (printer, "page-top-space", self.top_system_distance) + # TODO: Compute the indentation with the instrument name lengths + + # TODO: font width ? + char_per_cm = (len(self.get_longest_instrument_name()) * 13) / self.page_width + if (self.indent != 0): + self.print_length_field (printer, "indent", self.indent/char_per_cm) + if (self.short_indent != 0): + self.print_length_field (printer, "short-indent", self.short_indent/char_per_cm) printer.dump ('}') printer.newline () @@ -706,6 +945,7 @@ class Layout: class ChordEvent (NestedMusic): def __init__ (self): NestedMusic.__init__ (self) + self.after_grace_elements = None self.grace_elements = None self.grace_type = None def append_grace (self, element): @@ -713,6 +953,16 @@ class ChordEvent (NestedMusic): if not self.grace_elements: self.grace_elements = SequentialMusic () self.grace_elements.append (element) + def append_after_grace (self, element): + if element: + if not self.after_grace_elements: + self.after_grace_elements = SequentialMusic () + self.after_grace_elements.append (element) + + def has_elements (self): + return [e for e in self.elements if + isinstance (e, NoteEvent) or isinstance (e, RestEvent)] != [] + def get_length (self): l = Rational (0) @@ -735,10 +985,13 @@ class ChordEvent (NestedMusic): rest_events = [e for e in self.elements if isinstance (e, RhythmicEvent) and not isinstance (e, NoteEvent)] - + other_events = [e for e in self.elements if not isinstance (e, RhythmicEvent)] + if self.after_grace_elements: + printer ('\\afterGrace {') + if self.grace_elements and self.elements: if self.grace_type: printer ('\\%s' % self.grace_type) @@ -746,8 +999,18 @@ class ChordEvent (NestedMusic): printer ('\\grace') # don't print newlines after the { and } braces self.grace_elements.print_ly (printer, False) - # Print all overrides and other settings needed by the + elif self.grace_elements: # no self.elements! + ly.warning (_ ("Grace note with no following music: %s") % self.grace_elements) + if self.grace_type: + printer ('\\%s' % self.grace_type) + else: + printer ('\\grace') + self.grace_elements.print_ly (printer, False) + printer ('{}') + + # Print all overrides and other settings needed by the # articulations/ornaments before the note + for e in other_events: e.print_before_note (printer) @@ -759,10 +1022,17 @@ class ChordEvent (NestedMusic): global previous_pitch pitches = [] basepitch = None + stem = None for x in note_events: - pitches.append (x.pitch.ly_expression ()) + if(x.associated_events): + for aev in x.associated_events: + if (isinstance(aev, StemEvent) and aev.value): + stem = aev + pitches.append (x.chord_element_ly ()) if not basepitch: basepitch = previous_pitch + if stem: + printer (stem.ly_expression ()) printer ('<%s>' % string.join (pitches)) previous_pitch = basepitch duration = self.get_duration () @@ -770,15 +1040,19 @@ class ChordEvent (NestedMusic): duration.print_ly (printer) else: pass - + for e in other_events: e.print_ly (printer) for e in other_events: e.print_after_note (printer) + if self.after_grace_elements: + printer ('}') + self.after_grace_elements.print_ly (printer, False) + self.print_comment (printer) - + class Partial (Music): def __init__ (self): Music.__init__ (self) @@ -792,9 +1066,9 @@ class BarLine (Music): Music.__init__ (self) self.bar_number = 0 self.type = None - + def print_ly (self, printer): - bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': ":", + bar_symbol = { 'regular': "|", 'dotted': ":", 'dashed': "dashed", 'heavy': "|", 'light-light': "||", 'light-heavy': "|.", 'heavy-light': ".|", 'heavy-heavy': ".|.", 'tick': "'", 'short': "'", 'none': "" }.get (self.type, None) @@ -805,7 +1079,7 @@ class BarLine (Music): if self.bar_number > 0 and (self.bar_number % 10) == 0: printer.dump ("\\barNumberCheck #%d " % self.bar_number) - else: + elif self.bar_number > 0: printer.print_verbatim (' %% %d' % self.bar_number) printer.newline () @@ -834,7 +1108,7 @@ class SpanEvent (Event): self.span_direction = 0 # start/stop self.line_type = 'solid' self.span_type = 0 # e.g. cres/decrescendo, ottava up/down - self.size = 0 # size of e.g. ocrave shift + self.size = 0 # size of e.g. octave shift def wait_for_note (self): return True def get_properties(self): @@ -844,13 +1118,13 @@ class SpanEvent (Event): class SlurEvent (SpanEvent): def print_before_note (self, printer): - command = {'dotted': '\\slurDotted', + command = {'dotted': '\\slurDotted', 'dashed' : '\\slurDashed'}.get (self.line_type, '') if command and self.span_direction == -1: printer.dump (command) def print_after_note (self, printer): # reset non-solid slur types! - command = {'dotted': '\\slurSolid', + command = {'dotted': '\\slurSolid', 'dashed' : '\\slurSolid'}.get (self.line_type, '') if command and self.span_direction == -1: printer.dump (command) @@ -868,16 +1142,41 @@ class PedalEvent (SpanEvent): 1:'\\sustainOff'}.get (self.span_direction, '') class TextSpannerEvent (SpanEvent): + def print_before_note (self, printer): + if hasattr(self, 'style') and self.style=="wave": + printer.dump("\once \override TextSpanner #'style = #'trill") + try: + x = {-1:'\\textSpannerDown', 0:'\\textSpannerNeutral', 1: '\\textSpannerUp'}.get(self.force_direction, '') + printer.dump (x) + except: + pass + + def print_after_note (self, printer): + pass + def ly_expression (self): - return {-1: '\\startTextSpan', - 1:'\\stopTextSpan'}.get (self.span_direction, '') + global whatOrnament + if hasattr(self, 'style') and self.style=="ignore": + return "" + # if self.style=="wave": + if whatOrnament == "wave": + return {-1: '\\startTextSpan', + 1:'\\stopTextSpan'}.get (self.span_direction, '') + else: + if hasattr(self, 'style') and self.style=="stop" and whatOrnament != "trill": return "" + return {-1: '\\startTrillSpan', + 1:'\\stopTrillSpan'}.get (self.span_direction, '') class BracketSpannerEvent (SpanEvent): # Ligature brackets use prefix-notation!!! def print_before_note (self, printer): if self.span_direction == -1: + if self.force_direction == 1: + printer.dump("\once \override LigatureBracket #' direction = #UP") + elif self.force_direction == -1: + printer.dump("\once \override LigatureBracket #' direction = #DOWN") printer.dump ('\[') - # the the bracket after the last note + # the bracket after the last note def print_after_note (self, printer): if self.span_direction == 1: printer.dump ('\]') @@ -890,20 +1189,24 @@ class OctaveShiftEvent (SpanEvent): def wait_for_note (self): return False def set_span_type (self, type): - self.span_type = {'up': 1, 'down': -1}.get (type, 0) + self.span_type = {'up': 1, 'down':-1}.get (type, 0) def ly_octave_shift_indicator (self): # convert 8/15 to lilypond indicators (+-1/+-2) - value = {8: 1, 15: 2}.get (self.size, 0) + try: + value = {8: 1, 15: 2}[self.size] + except KeyError: + ly.warning (_ ("Invalid octave shift size found: %s. Using no shift.") % self.size) + value = 0 # negative values go up! - value *= -1*self.span_type + value *= -1 * self.span_type return value def ly_expression (self): dir = self.ly_octave_shift_indicator () value = '' if dir: value = '\ottava #%s' % dir - return { - -1: value, + return { + - 1: value, 1: '\ottava #0'}.get (self.span_direction, '') class TrillSpanEvent (SpanEvent): @@ -915,13 +1218,13 @@ class TrillSpanEvent (SpanEvent): class GlissandoEvent (SpanEvent): def print_before_note (self, printer): if self.span_direction == -1: - style= { + style = { "dashed" : "dashed-line", "dotted" : "dotted-line", - "wavy" : "zigzag" + "wavy" : "zigzag" }. get (self.line_type, None) if style: - printer.dump ("\once \override Glissando #'style = #'%s" % style) + printer.dump ("\\once \\override Glissando #'style = #'%s" % style) def ly_expression (self): return {-1: '\\glissando', 1:''}.get (self.span_direction, '') @@ -954,20 +1257,24 @@ class TieEvent(Event): class HairpinEvent (SpanEvent): def set_span_type (self, type): - self.span_type = {'crescendo' : 1, 'decrescendo' : -1, 'diminuendo' : -1 }.get (type, 0) + self.span_type = {'crescendo' : 1, 'decrescendo' :-1, 'diminuendo' :-1 }.get (type, 0) def hairpin_to_ly (self): if self.span_direction == 1: return '\!' else: return {1: '\<', -1: '\>'}.get (self.span_type, '') - + + def direction_mod (self): + return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') + def ly_expression (self): return self.hairpin_to_ly () - + def print_ly (self, printer): val = self.hairpin_to_ly () if val: - printer.dump (val) + # printer.dump (val) + printer.dump ('%s%s' % (self.direction_mod (), val)) @@ -975,6 +1282,7 @@ class DynamicsEvent (Event): def __init__ (self): Event.__init__ (self) self.type = None + self.force_direction = 0 def wait_for_note (self): return True def ly_expression (self): @@ -983,9 +1291,12 @@ class DynamicsEvent (Event): else: return + def direction_mod (self): + return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') + def print_ly (self, printer): if self.type: - printer.dump ("\\%s" % self.type) + printer.dump ('%s\\%s' % (self.direction_mod (), self.type)) class MarkEvent (Event): def __init__ (self, text="\\default"): @@ -1016,9 +1327,30 @@ class TextEvent (Event): self.force_direction = None self.markup = '' def wait_for_note (self): + """ This is problematic: the lilypond-markup ^"text" + requires wait_for_note to be true. Otherwise the + compilation will fail. So we are forced to set return to True. + But in some cases this might lead to a wrong placement of the text. + In case of words like Allegro the text should be put in a '\tempo'-command. + In this case we don't want to wait for the next note. + In some other cases the text is supposed to be used in a '\mark\markup' construct. + We would not want to wait for the next note either. + There might be other problematic situations. + In the long run we should differentiate between various contexts in MusicXML, e.g. + the following markup should be interpreted as '\tempo "Allegretto"': + + + Allegretto + + + + In the mean time arising problems have to be corrected manually after the conversion. + """ return True def direction_mod (self): + """ 1: placement="above"; -1: placement="below"; 0: no placement attribute. + see musicxml_direction_to_indicator in musicxml2ly_conversion.py """ return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') def ly_expression (self): @@ -1052,9 +1384,21 @@ class ShortArticulationEvent (ArticulationEvent): return '' class NoDirectionArticulationEvent (ArticulationEvent): + + def is_breathing_sign(self): + return self.type == 'breathe' + + def print_after_note(self, printer): + # The breathing sign should, according to current LilyPond + # praxis, be treated as an independent musical + # event. Consequently, it should be printed _after_ the note + # to which it is attached. + if self.is_breathing_sign(): + printer.dump(r'\breathe') + def ly_expression (self): - if self.type: - return '\\%s' % self.type + if self.type and not self.is_breathing_sign(): + return '\\%s' % self.type else: return '' @@ -1083,11 +1427,11 @@ class FretEvent (MarkupEvent): if self.frets <> 4: val += "h:%s;" % self.frets if self.barre and len (self.barre) >= 3: - val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2]) + val += "c:%s-%s-%s;" % (self.barre[0], self.barre[1], self.barre[2]+get_transpose("integer")) have_fingering = False for i in self.elements: if len (i) > 1: - val += "%s-%s" % (i[0], i[1]) + val += "%s-%s" % (i[0], i[1]+(get_transpose("integer"),'')[isinstance(i[1],str)]) if len (i) > 2: have_fingering = True val += "-%s" % i[2] @@ -1099,13 +1443,114 @@ class FretEvent (MarkupEvent): else: return '' +class FretBoardNote (Music): + def __init__ (self): + Music.__init__ (self) + self.pitch = None + self.string = None + self.fingering = None + def ly_expression (self): + str = self.pitch.ly_expression() + if self.fingering: + str += "-%s" % self.fingering + if self.string: + str += "\%s" % self.string + return str + +class FretBoardEvent (NestedMusic): + def __init__ (self): + NestedMusic.__init__ (self) + self.duration = None + def print_ly (self, printer): + fretboard_notes = [n for n in self.elements if isinstance (n, FretBoardNote)] + if fretboard_notes: + notes = [] + for n in fretboard_notes: + notes.append (n.ly_expression ()) + contents = string.join (notes) + printer ('<%s>%s' % (contents,self.duration)) + +class FunctionWrapperEvent (Event): + def __init__ (self, function_name=None): + Event.__init__ (self) + self.function_name = function_name + def pre_note_ly (self, is_chord_element): + if self.function_name: + return "\\%s" % self.function_name + else: + return '' + def pre_chord_ly (self): + return '' + def ly_expression (self): + if self.function_name: + return "\\%s" % self.function_name + else: + return '' + +class ParenthesizeEvent (FunctionWrapperEvent): + def __init__ (self): + FunctionWrapperEvent.__init__ (self, "parenthesize") + +class StemEvent (Event): + """" + A class to take care of stem values (up, down, double, none) + """ + def __init__ (self): + Event.__init__ (self) + self.value = None + def pre_chord_ly (self): + if self.value: + return "\\%s" % self.value + else: + return '' + def pre_note_ly (self, is_chord_element): + return '' + def ly_expression (self): + return self.pre_chord_ly () + +class NotestyleEvent (Event): #class changed by DaLa: additional attribute color + def __init__ (self): + Event.__init__ (self) + self.style = None + self.filled = None + self.color = None + def pre_chord_ly (self): + return_string = '' + if self.style: + return_string += " \\once \\override NoteHead #'style = #%s" % self.style + if self.color: + return_string += " \\once \\override NoteHead #'color = #(rgb-color %s %s %s)" % (self.color[0], self.color[1], self.color[2]) + return return_string + def pre_note_ly (self, is_chord_element): + if self.style and is_chord_element: + return "\\tweak #'style #%s" % self.style + else: + return '' + def ly_expression (self): + return self.pre_chord_ly () + +class StemstyleEvent (Event): #class added by DaLa + def __init__ (self): + Event.__init__ (self) + self.color = None + def pre_chord_ly (self): + if self.color: + return "\\once \\override Stem #'color = #(rgb-color %s %s %s)" % (self.color[0], self.color[1], self.color[2]) + else: + return '' + def pre_note_ly (self, is_chord_element): + return '' + def ly_expression (self): + return self.pre_chord_ly () + + class ChordPitch: def __init__ (self): self.alteration = 0 self.step = 0 def __repr__(self): return self.ly_expression() - def ly_expression (self): + def ly_expression (self): return pitch_generating_function (self) class ChordModification: @@ -1133,14 +1578,14 @@ class ChordNameEvent (Event): def add_modification (self, mod): self.modifications.append (mod) def ly_expression (self): + if not self.root: return '' value = self.root.ly_expression () if self.duration: value += self.duration.ly_expression () if self.kind: - value += ":" - value += self.kind + value = self.kind.format(value) # First print all additions/changes, and only afterwards all subtractions for m in self.modifications: if m.type == 1: @@ -1153,23 +1598,44 @@ class ChordNameEvent (Event): return value -class TremoloEvent (ArticulationEvent): - def __init__ (self): - Event.__init__ (self) - self.bars = 0 - - def ly_expression (self): - str='' - if self.bars and self.bars > 0: - str += ':%s' % (2 ** (2 + string.atoi (self.bars))) - return str +class TremoloEvent(ArticulationEvent): + def __init__(self): + Event.__init__(self) + self.strokes = 0 + + def ly_expression(self): + ly_str = '' + if self.strokes and int(self.strokes) > 0: + # ly_dur is a global variable defined in class Duration + # ly_dur stores the value of the reciprocal values of notes + # ly_dur is used here to check the current note duration + # if the duration is smaller than 8, e.g. + # quarter, half and whole notes, + # `:(2 ** (2 + number of tremolo strokes))' + # should be appended to the pitch and duration, e.g. + # 1 stroke: `c4:8' or `c2:8' or `c1:8' + # 2 strokes: `c4:16' or `c2:16' or `c1:16' + # ... + # else (if ly_dur is equal to or greater than 8): + # we need to make sure that the tremolo value that is to + # be appended to the pitch and duration is twice the + # duration (if there is only one tremolo stroke. + # Each additional stroke doubles the tremolo value, e.g.: + # 1 stroke: `c8:16', `c16:32', `c32:64', ... + # 2 strokes: `c8:32', `c16:64', `c32:128', ... + # ... + if ly_dur < 8: + ly_str += ':%s' % (2 ** (2 + int(self.strokes))) + else: + ly_str += ':%s' % (2 ** int((math.log(ly_dur, 2)) + int(self.strokes))) + return ly_str class BendEvent (ArticulationEvent): def __init__ (self): Event.__init__ (self) - self.alter = 0 + self.alter = None def ly_expression (self): - if self.alter: + if self.alter != None: return "-\\bendAfter #%s" % self.alter else: return '' @@ -1178,25 +1644,50 @@ class RhythmicEvent(Event): def __init__ (self): Event.__init__ (self) self.duration = Duration() - + self.associated_events = [] + + def add_associated_event (self, ev): + if ev: + self.associated_events.append (ev) + + def pre_chord_ly (self): + return [ev.pre_chord_ly () for ev in self.associated_events] + + def pre_note_ly (self, is_chord_element): + return [ev.pre_note_ly (is_chord_element) for ev in self.associated_events] + + def ly_expression_pre_note (self, is_chord_element): + res = string.join (self.pre_note_ly (is_chord_element), ' ') + if res != '': + res = res + ' ' + return res + def get_length (self): return self.duration.get_length() - + def get_properties (self): return ("'duration %s" % self.duration.lisp_expression ()) - + class RestEvent (RhythmicEvent): def __init__ (self): RhythmicEvent.__init__ (self) self.pitch = None + def ly_expression (self): + res = self.ly_expression_pre_note (False) if self.pitch: - return "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ()) + return res + "%s%s\\rest" % (self.pitch.ly_expression (), self.duration.ly_expression ()) else: return 'r%s' % self.duration.ly_expression () - + def print_ly (self, printer): + for ev in self.associated_events: + ev.print_ly (printer) +# if hasattr(self, 'color'): +# printer.print_note_color("NoteHead", self.color) +# printer.print_note_color("Stem", self.color) +# printer.print_note_color("Beam", self.color) if self.pitch: self.pitch.print_ly (printer) self.duration.print_ly (printer) @@ -1207,26 +1698,26 @@ class RestEvent (RhythmicEvent): class SkipEvent (RhythmicEvent): def ly_expression (self): - return 's%s' % self.duration.ly_expression () + return 's%s' % self.duration.ly_expression () class NoteEvent(RhythmicEvent): def __init__ (self): RhythmicEvent.__init__ (self) - self.pitch = None + #self.pitch = None self.drum_type = None self.cautionary = False self.forced_accidental = False - + def get_properties (self): str = RhythmicEvent.get_properties (self) - + if self.pitch: str += self.pitch.lisp_expression () elif self.drum_type: str += "'drum-type '%s" % self.drum_type return str - + def pitch_mods (self): excl_question = '' if self.cautionary: @@ -1235,17 +1726,36 @@ class NoteEvent(RhythmicEvent): excl_question += '!' return excl_question - + def ly_expression (self): + # obtain all stuff that needs to be printed before the note: + res = self.ly_expression_pre_note (True) if self.pitch: - return '%s%s%s' % (self.pitch.ly_expression (), + return res + '%s%s%s' % (self.pitch.ly_expression (), self.pitch_mods(), self.duration.ly_expression ()) elif self.drum_type: - return '%s%s' (self.drum_type, + return res + '%s%s' (self.drum_type, self.duration.ly_expression ()) + def chord_element_ly (self): + # obtain all stuff that needs to be printed before the note: + res = self.ly_expression_pre_note (True) + if self.pitch: + return res + '%s%s' % (self.pitch.ly_expression (), + self.pitch_mods()) + elif self.drum_type: + return res + '%s%s' (self.drum_type) + + def print_ly (self, printer): + for ev in self.associated_events: + ev.print_ly (printer) + if hasattr(self, 'color'): + printer.print_note_color("NoteHead", self.color) + printer.print_note_color("Stem", self.color) + printer.print_note_color("Beam", self.color) + if self.pitch: self.pitch.print_ly (printer) printer (self.pitch_mods ()) @@ -1254,31 +1764,116 @@ class NoteEvent(RhythmicEvent): self.duration.print_ly (printer) +# if hasattr(self, 'color'): +# printer.print_note_color("NoteHead") +# printer.print_note_color("Stem") +# printer.print_note_color("Beam") + 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.5: ",THREE-Q-FLAT", + - 1: ",FLAT", + - 0.5: ",SEMI-FLAT", + 0: ",NATURAL", + 0.5: ",SEMI-SHARP", + 1: ",SHARP", + 1.5: ",THREE-Q-SHARP", + 2: ",DOUBLE-SHARP"} + try: + accidental = alter_dict[a[1]] + except KeyError: + ly.warning (_ ("Unable to convert alteration %s to a lilypond expression") % a[1]) + return '' + if len (a) == 2: + return "( %s . %s )" % (a[0], accidental) + elif len (a) == 3: + return "(( %s . %s ) . %s )" % (a[2], a[0], accidental) + 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)) + elif self.non_standard_alterations: + alterations = [self.format_non_standard_alteration (a) for + a in self.non_standard_alterations] + return "\\set Staff.keyAlterations = #`(%s)" % string.join (alterations, " ") + else: + return '' - return """ (make-music 'KeyChangeEvent - 'pitch-alist %s) """ % scale_str +class ShiftDurations (MusicWrapper): + def __init__ (self): + MusicWrapper.__init__ (self) + self.params = [0,0] + + def set_shift_durations_parameters(self, timeSigChange): + self.params = timeSigChange.get_shift_durations_parameters() + + def print_ly (self, func): + func (' \\shiftDurations #%d #%d ' % tuple(self.params)) + MusicWrapper.print_ly (self, func) class TimeSignatureChange (Music): def __init__ (self): Music.__init__ (self) - self.fraction = (4,4) + self.fractions = [4, 4] + self.style = None + # Used for the --time-signature option of musicxml2ly + self.originalFractions = [4, 4] + + def get_fractions_ratio (self): + """ + Calculate the ratio between the original time fraction and the new one. + Used for the "--time-signature" option. + + @return: The ratio between the two time fractions. + @rtype: float + """ + return (float(self.originalFractions[0])/self.originalFractions[1])*(float(self.fractions[1])/self.fractions[0]) + + def get_shift_durations_parameters (self): + dur = math.ceil(math.log(self.get_fractions_ratio(),2)) + dots = (1/self.get_fractions_ratio())/(math.pow(2,-dur)) + dots = int(math.log(2-dots,0.5)) + return [dur, dots] + + def format_fraction (self, frac): + if isinstance (frac, list): + l = [self.format_fraction (f) for f in frac] + return "(" + string.join (l, " ") + ")" + else: + return "%s" % frac + def ly_expression (self): - return '\\time %d/%d ' % self.fraction - + st = '' + # Print out the style if we have ome, but the '() should only be + # forced for 2/2 or 4/4, since in all other cases we'll get numeric + # signatures anyway despite the default 'C signature style! + is_common_signature = self.fractions in ([2, 2], [4, 4], [4, 2]) + if self.style: + if self.style == "common": + st = "\\defaultTimeSignature" + elif (self.style != "'()"): + st = "\\once \\override Staff.TimeSignature #'style = #%s " % self.style + elif (self.style != "'()") or is_common_signature: + st = "\\numericTimeSignature" + + # Easy case: self.fractions = [n,d] => normal \time n/d call: + if len (self.fractions) == 2 and isinstance (self.fractions[0], int): + return st + '\\time %d/%d ' % tuple (self.fractions) + elif self.fractions: + return st + "\\compoundMeter #'%s" % self.format_fraction (self.fractions) + else: + return st + '' + class ClefChange (Music): def __init__ (self): Music.__init__ (self) @@ -1288,6 +1883,7 @@ class ClefChange (Music): def octave_modifier (self): return {1: "^8", 2: "^15", -1: "_8", -2: "_15"}.get (self.octave, '') + def clef_name (self): return {('G', 2): "treble", ('G', 1): "french", @@ -1300,7 +1896,10 @@ class ClefChange (Music): ('F', 4): "bass", ('F', 5): "subbass", ("percussion", 2): "percussion", - ("TAB", 5): "tab"}.get ((self.type, self.position), None) + # Workaround: MuseScore uses PERC instead of percussion + ("PERC", 2): "percussion", + ("TAB", 5): get_tab_clef ()}.get ((self.type, self.position), None) + def ly_expression (self): return '\\clef "%s%s"' % (self.clef_name (), self.octave_modifier ()) @@ -1309,7 +1908,7 @@ class ClefChange (Music): "C": ("clefs.C", 0, 0), "F": ("clefs.F", 2, 6), } - + def lisp_expression (self): try: (glyph, pos, c0) = self.clef_dict[self.type] @@ -1327,6 +1926,13 @@ class ClefChange (Music): """ % (glyph, pos, c0) return clefsetting +class Transposition (Music): + def __init__ (self): + Music.__init__ (self) + self.pitch = None + def ly_expression (self): + self.pitch._force_absolute_pitch = True + return '\\transposition %s' % self.pitch.ly_expression () class StaffChange (Music): def __init__ (self, staff): @@ -1338,6 +1944,26 @@ class StaffChange (Music): else: return '' +class SetEvent (Music): + def __init__ (self, contextprop, value): + Music.__init__ (self) + self.context_prop = contextprop + self.value = value + def ly_expression (self): + if self.value: + return "\\set %s = %s" % (self.context_prop, self.value) + else: + return '' + +class StaffLinesEvent (Music): + def __init__ (self, lines): + Music.__init__ (self) + self.lines = lines + def ly_expression (self): + if (self.lines > 0): + return "\\stopStaff \\override Staff.StaffSymbol #'line-count = #%s \\startStaff" % self.lines + else: + return "\\stopStaff \\revert Staff.StaffSymbol #'line-count \\startStaff" class TempoMark (Music): def __init__ (self): @@ -1358,7 +1984,7 @@ class TempoMark (Music): return False def duration_to_markup (self, dur): if dur: - # Generate the markup to print the note, use scheme mode for + # 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: @@ -1461,16 +2087,26 @@ class MultiMeasureRest(Music): return 'R%s' % self.duration.ly_expression () +class Break (Music): + def __init__ (self, tp="break"): + Music.__init__ (self) + self.type = tp + def print_ly (self, printer): + if self.type: + printer.dump ("\\%s" % self.type) + class StaffGroup: - def __init__ (self, command = "StaffGroup"): + def __init__ (self, command="StaffGroup"): self.stafftype = command self.id = None self.instrument_name = None + self.sound = None self.short_instrument_name = None self.symbol = None self.spanbar = None self.children = [] self.is_group = True + self.context_modifications = [] # part_information is a list with entries of the form # [staffid, voicelist] # where voicelist is a list with entries of the form @@ -1487,58 +2123,159 @@ class StaffGroup: for c in self.children: c.set_part_information (part_name, staves_info) + def add_context_modification (self, modification): + self.context_modifications.append (modification) + def print_ly_contents (self, printer): for c in self.children: if c: c.print_ly (printer) - def print_ly_overrides (self, printer): + #Intention: I want to put the content of new StaffGroup in angled brackets (<< >>) + #printer.dump ("test")# test is printed twice at the end of a staffgroup with two staves. + #printer ("test") # test is printed twice at the end of a staffgroup with two staves. + + def needs_with (self): needs_with = False needs_with |= self.spanbar == "no" needs_with |= self.instrument_name != None needs_with |= self.short_instrument_name != None needs_with |= (self.symbol != None) and (self.symbol != "bracket") + return needs_with + + def print_ly_context_mods (self, printer): + if self.instrument_name or self.short_instrument_name: + printer.dump ("\\consists \"Instrument_name_engraver\"") + if self.spanbar == "no": + printer.dump ("\\override SpanBar #'transparent = ##t") + brack = {"brace": "SystemStartBrace", + "none": "f", + "line": "SystemStartSquare"}.get (self.symbol, None) + if brack: + printer.dump ("systemStartDelimiter = #'%s" % brack) + + def print_ly_overrides (self, printer): + needs_with = self.needs_with () | (len (self.context_modifications) > 0); if needs_with: printer.dump ("\\with {") - if self.instrument_name or self.short_instrument_name: - printer.dump ("\\consists \"Instrument_name_engraver\"") - if self.spanbar == "no": - printer.dump ("\\override SpanBar #'transparent = ##t") - brack = {"brace": "SystemStartBrace", - "none": "f", - "line": "SystemStartSquare"}.get (self.symbol, None) - if brack: - printer.dump ("systemStartDelimiter = #'%s" % brack) - printer.dump ("}") + self.print_ly_context_mods (printer) + for m in self.context_modifications: + printer.dump (m) + printer.dump ("} <<") + printer.newline () + #print a single << after StaffGroup only when the with-block is not needed. + #This doesn't work. << is printed before and after StaffGroup! + #else: + # printer.dump (" <<") + #prints loads off << before and after StaffGroup and before \set Staff.instrumentName + #elif not needs_with: + # printer.dump (" <<") + + def print_chords(self, printer): + try: + for [staff_id, voices] in self.part_information: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: + if chordnames: + printer ('\context ChordNames = "%s" {%s \\%s}' % (chordnames, get_transpose ("string"), chordnames)) + printer.newline() + except TypeError: + return + + def print_fretboards(self, printer): + try: + for [staff_id, voices] in self.part_information: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: + if fretboards: + printer ('\context FretBoards = "%s" {%s \\%s}' % (fretboards, get_transpose ("string"), fretboards)) + printer.newline() + except TypeError: + return def print_ly (self, printer): + #prints two << before a StaffGroup, one after a StaffGroup and one before each \new Staff + #printer.dump("<<") + #printer.newline () + self.print_chords(printer) + self.print_fretboards(printer) if self.stafftype: printer.dump ("\\new %s" % self.stafftype) + printer.dump ("<<") + printer.newline () + # if there is a width-block << should not be printed after StaffGroup but after the width-block + # this seems to work. It prints \new StaffGroup \with{} <<: +# if self.stafftype == "StaffGroup" and self.print_ly_overrides: + #prints a new line before each new staff type, e.g. before \new StaffGroup and \new Staff... + #printer.newline () +# printer.dump("\\new %s" % self.stafftype) + #self.print_ly_overrides (printer) + #prints a << and a new line after each new staff type. + # printer.dump ("<<") + # printer.newline() + # print a << after all other staff types: + # can't use "else:" because then we get a '\new None' staff type in LilyPond... +# elif self.stafftype == "StaffGroup": +# printer.dump ("\\new %s" % self.stafftype) +# printer.dump ("<<") +# printer.newline () + # << should be printed directly after StaffGroups without a with-block: + # this doesn't work: +# elif self.stafftype == "StaffGroup" and not self.print_ly_overrides: +# printer.dump ("<<") +# printer.newline () + # this is bullshit: +# elif self.stafftype == "StaffGroup" and self.stafftype == "Staff": +# printer.dump ("<<") +# printer.newline () + # this prints \new Staff << for every staff in the score: +# elif self.stafftype: +# printer.dump ("\\new %s" % self.stafftype) +# printer.dump ("<<") +# printer.newline () self.print_ly_overrides (printer) - printer.dump ("<<") - printer.newline () + #printer.dump ("<<") + #printer.newline () if self.stafftype and self.instrument_name: - printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, + printer.dump ("\\set %s.instrumentName = %s" % (self.stafftype, escape_instrument_string (self.instrument_name))) - printer.newline () + #printer.newline () if self.stafftype and self.short_instrument_name: printer.dump ("\\set %s.shortInstrumentName = %s" % (self.stafftype, escape_instrument_string (self.short_instrument_name))) printer.newline () + if self.sound: + printer.dump( + r'\set {stafftype}.midiInstrument = #"{sound}"'.format( + stafftype=self.stafftype, sound=self.sound)) self.print_ly_contents (printer) printer.newline () - printer.dump (">>") - printer.newline () +# This is a crude hack: In scores with staff groups the closing angled brackets are not printed. +# That's why I added the following two lines. I couldn't find a proper solution. This won't work with scores several staff groups!!! + if self.stafftype == "StaffGroup": + printer.dump (">>") + #printer.dump (">>") + #printer.dump (">>") + #printer.newline () + #printer.dump ("test") #test is printed 4 times in a staffgroup with two staves: once at the end of each staff and twice at the end of the staffgroup. That's not what we want! + #printer.dump ("test") NameError: name 'printer' is not defined + +#test +# def print_staffgroup_closing_brackets (self, printer): #test see class Staff / Score. +# printer.dump ("test") class Staff (StaffGroup): - def __init__ (self, command = "Staff"): + def __init__ (self, command="Staff"): StaffGroup.__init__ (self, command) self.is_group = False self.part = None self.voice_command = "Voice" self.substafftype = None + self.sound = None - def print_ly_overrides (self, printer): + def needs_with (self): + return False + + def print_ly_context_mods (self, printer): + #printer.dump ("test") #does nothing. pass def print_ly_contents (self, printer): @@ -1547,45 +2284,59 @@ class Staff (StaffGroup): sub_staff_type = self.substafftype if not sub_staff_type: sub_staff_type = self.stafftype + #printer.dump ("test") #prints test in each staff after the definitions of the instrument name and before the definition of the contexts. + printer.newline() for [staff_id, voices] in self.part_information: - # Chord names need to come before the staff itself! - for [v, lyrics, figuredbass, chordnames] in voices: - if chordnames: - printer ('\context ChordNames = "%s" \\%s' % (chordnames, chordnames)) - # now comes the real staff definition: if staff_id: printer ('\\context %s = "%s" << ' % (sub_staff_type, staff_id)) else: printer ('\\context %s << ' % sub_staff_type) printer.newline () + printer.dump("\mergeDifferentlyDottedOn\mergeDifferentlyHeadedOn") + printer.newline() n = 0 nr_voices = len (voices) - for [v, lyrics, figuredbass, chordnames] in voices: + for [v, lyrics, figuredbass, chordnames, fretboards] in voices: n += 1 voice_count_text = '' if nr_voices > 1: - voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo', - 3: ' \\voiceThree'}.get (n, ' \\voiceFour') - printer ('\\context %s = "%s" {%s \\%s }' % (self.voice_command, v, voice_count_text, v)) + """ +The next line contains a bug: The voices might not appear in numerical order! Some voices might be missing e.g. if the xml file contains only voice one, three and four, this would result in: \voiceOne, \voiceTwo and \voiceThree. This causes wrong stem directions and collisions. + """ + voice_count_text = {1: ' \\voiceOne', 2: ' \\voiceTwo', 3: ' \\voiceThree'}.get (n, ' \\voiceFour') + printer ('\\context %s = "%s" {%s %s \\%s }' % (self.voice_command, v, get_transpose ("string"), voice_count_text, v)) printer.newline () - + lyrics_id = 1 for l in lyrics: - printer ('\\new Lyrics \\lyricsto "%s" \\%s' % (v,l)) + printer ('\\new Lyrics \\lyricsto "%s" { \\set stanza = "%s." \\%s }' % (v, lyrics_id, l)) + lyrics_id += 1 printer.newline() if figuredbass: printer ('\context FiguredBass = "%s" \\%s' % (figuredbass, figuredbass)) printer ('>>') + #printer.dump ("test") #prints test after each definition of a context. + #printer.newline () + #printer.dump ("test") #prints test after each definition of a context. def print_ly (self, printer): if self.part_information and len (self.part_information) > 1: self.stafftype = "PianoStaff" self.substafftype = "Staff" + #printer.dump ('test') StaffGroup.print_ly (self, printer) + #StaffGroup.print_staffgroup_closing_brackets (self, printer) prints test after each definition of a staff + printer.dump ('>>') + #printer.dump ("test") #prints test after each definition of a context. + printer.newline () + #StaffGroup.print_staffgroup_closing_brackets(self, printer) #prints test after each definition of a staff. + #printer.dump ("test")# NameError: name 'printer' is not defined + #StaffGroup.print_staffgroup_closing_brackets() #TypeError: unbound method print_staffgroup_closing_brackets() must be called with StaffGroup instance as first argument (got nothing instead) + class TabStaff (Staff): - def __init__ (self, command = "TabStaff"): + def __init__ (self, command="TabStaff"): Staff.__init__ (self, command) self.string_tunings = [] self.tablature_format = None @@ -1594,9 +2345,9 @@ class TabStaff (Staff): if self.string_tunings or self.tablature_format: printer.dump ("\\with {") if self.string_tunings: - printer.dump ("stringTunings = #'(") + printer.dump ("stringTunings = #`(") for i in self.string_tunings: - printer.dump ("%s" % i.semitones ()) + printer.dump (",%s" % i.lisp_expression ()) printer.dump (")") if self.tablature_format: printer.dump ("tablatureFormat = #%s" % self.tablature_format) @@ -1604,7 +2355,7 @@ class TabStaff (Staff): class DrumStaff (Staff): - def __init__ (self, command = "DrumStaff"): + def __init__ (self, command="DrumStaff"): Staff.__init__ (self, command) self.drum_style_table = None self.voice_command = "DrumVoice" @@ -1615,52 +2366,99 @@ class DrumStaff (Staff): printer.dump ("}") class RhythmicStaff (Staff): - def __init__ (self, command = "RhythmicStaff"): + def __init__ (self, command="RhythmicStaff"): Staff.__init__ (self, command) - + +#Test +#def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff +# printer.dump ("test") + class Score: def __init__ (self): + """ + Constructs a new Score object. + """ self.contents = None self.create_midi = False def set_contents (self, contents): self.contents = contents - + def set_part_information (self, part_id, staves_info): if self.contents: self.contents.set_part_information (part_id, staves_info) + def set_tempo (self, tempo): + """ + Set the tempo attribute of the Score. + This attribute can be used in L{print_ly} for the midi output (see L{musicxml.Sound}). + + @param tempo: The value of the tempo, in beats per minute. + @type tempo: String + """ + self.tempo = tempo + #Test +# def print_staffgroup_closing_brackets (self, printer): #test see class Score / class Staff +# printer.dump ("test") + def print_ly (self, printer): - printer.dump ("\\score {"); + """ + Print the content of the score to the printer, in lilypond format. + + @param printer: A printer given to display correctly the output. + @type printer: L{Output_printer} + """ + self.create_midi = get_create_midi() + printer.dump("\\score {") + printer.newline () + #prints opening <<: + printer.dump ('<<') printer.newline () if self.contents: - self.contents.print_ly (printer); - printer.dump ("\\layout {}"); + self.contents.print_ly(printer) + #printer.dump ("test") prints test once before the >> of the score block, independent of the existence of a staffgroup. + #if StaffGroup == False: # True or False: nothing happens. + # printer.dump ('>>') + printer.dump ('>>') printer.newline () - if not self.create_midi: - printer.dump ("% To create MIDI output, uncomment the following line:"); - printer.newline (); - printer.dump ("% "); - printer.dump ("\\midi {}"); + #StaffGroup.print_staffgroup_closing_brackets(self, printer) #TypeError: unbound method print_staffgroup_closing_brackets() must be called with StaffGroup instance as first argument (got Score instance instead) + #print_staffgroup_closing_brackets(self, printer) #NameError: global name 'print_staffgroup_closing_brackets' is not defined. prints test once before the >> of the score block, independent of the existence of a staffgroup. + printer.dump ("\\layout {}") printer.newline () - printer.dump ("}"); + # If the --midi option was not passed to musicxml2ly, that comments the "midi" line + if self.create_midi: + printer.dump ("}") + printer.newline() + printer.dump("\\score {") + printer.newline () + printer.dump("\\unfoldRepeats \\articulate {") + printer.newline () + self.contents.print_ly(printer) + printer.dump("}") + printer.newline () + else: + printer.dump ("% To create MIDI output, uncomment the following line:") + printer.newline () + printer.dump ("% ") + printer.dump ("\\midi {\\tempo 4 = "+self.tempo+" }") + printer.newline () + printer.dump ("}") printer.newline () - def test_pitch (): bflat = Pitch() bflat.alteration = -1 - bflat.step = 6 + bflat.step = 6 bflat.octave = -1 fifth = Pitch() fifth.step = 4 down = Pitch () down.step = -4 down.normalize () - - + + print bflat.semitones() - print bflat.transposed (fifth), bflat.transposed (fifth).transposed (fifth) + print bflat.transposed (fifth), bflat.transposed (fifth).transposed (fifth) print bflat.transposed (fifth).transposed (fifth).transposed (fifth) print bflat.semitones(), 'down' @@ -1683,7 +2481,7 @@ def test_printer (): m.append (make_note ()) m.append (make_note ()) - + t = TimeScaledMusic () t.numerator = 2 t.denominator = 3 @@ -1694,14 +2492,14 @@ def test_printer (): m.append (make_tup ()) m.append (make_tup ()) m.append (make_tup ()) - + printer = Output_printer() m.print_ly (printer) printer.newline () - + def test_expr (): m = SequentialMusic() - l = 2 + l = 2 evc = ChordEvent() n = NoteEvent() n.duration.duration_log = l @@ -1719,7 +2517,7 @@ def test_expr (): evc = ChordEvent() n = NoteEvent() n.duration.duration_log = l - n.pitch.step = 2 + n.pitch.step = 2 evc.insert_around (None, n, 0) m.insert_around (None, evc, 0) @@ -1732,9 +2530,9 @@ def test_expr (): tonic.step = 2 tonic.alteration = -2 n = KeySignatureChange() - n.tonic=tonic.copy() - n.scale = [0, 0, -2, 0, 0,-2,-2] - + n.tonic = tonic.copy() + n.scale = [0, 0, -2, 0, 0, -2, -2] + evc.insert_around (None, n, 0) m.insert_around (None, evc, 0) @@ -1745,15 +2543,14 @@ if __name__ == '__main__': test_printer () raise 'bla' test_pitch() - + expr = test_expr() expr.set_start (Rational (0)) print expr.ly_expression() - start = Rational (0,4) - stop = Rational (4,2) + start = Rational (0, 4) + stop = Rational (4, 2) def sub(x, start=start, stop=stop): - ok = x.start >= start and x.start +x.get_length() <= stop + ok = x.start >= start and x.start + x.get_length() <= stop return ok - - print expr.lisp_sub_expression(sub) + print expr.lisp_sub_expression(sub)