From 899be51b6aec294310ca4c868f67ec23dd26042c Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Fri, 30 Dec 2005 19:08:31 +0000 Subject: [PATCH] * scripts/musicxml2ly.py (print_voice_definitions): new function (print_score_setup): new function (convert): read part definition to output staves properly. * python/musicxml.py (Measure.get_notes): new class. Wrap lists of Music_xml notes. * python/musicexp.py (Output_printer.__init__): use _ in data members. (Output_printer.unformatted_output): new function: no formatting, but count braces. (Duration.get_length): remove isinstance() checks for Output_printer everywhere. * input/regression/lyrics-bar.ly (texidoc): add Separating_line_group_engraver. --- ChangeLog | 20 +++- input/regression/lyrics-bar.ly | 3 +- lily/relocate.cc | 8 +- python/musicexp.py | 167 ++++++++++++++++++++------------- python/musicxml.py | 57 +++++++++-- scripts/musicxml2ly.py | 147 +++++++++++++++++++++++------ 6 files changed, 294 insertions(+), 108 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4f05fcde60..8d1afa7e48 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2005-12-30 Han-Wen Nienhuys + + + * scripts/musicxml2ly.py (print_voice_definitions): new function + (print_score_setup): new function + (convert): read part definition to output staves properly. + + * python/musicxml.py (Measure.get_notes): new class. Wrap lists of + Music_xml notes. + + * python/musicexp.py (Output_printer.__init__): use _ in data members. + (Output_printer.unformatted_output): new function: no formatting, + but count braces. + (Duration.get_length): remove isinstance() checks for Output_printer everywhere. + + * input/regression/lyrics-bar.ly (texidoc): add Separating_line_group_engraver. + 2005-12-29 Jan Nieuwenhuizen * lily/relocate.cc (framework_relocation): New function, @@ -7,7 +24,8 @@ 2005-12-30 Han-Wen Nienhuys - * scm/*.scm: use LEFT/RIGHT/UP/DOWN etc. iso. magical numbers. + * scm/*.scm: use LEFT/RIGHT/UP/DOWN etc. iso. magical + numbers. Patch by Erlend Aasland. 2005-12-29 Han-Wen Nienhuys diff --git a/input/regression/lyrics-bar.ly b/input/regression/lyrics-bar.ly index c14ed991c2..0c34ee3d23 100644 --- a/input/regression/lyrics-bar.ly +++ b/input/regression/lyrics-bar.ly @@ -21,7 +21,8 @@ lyrics do not collide with barlines. } \context Lyrics \with { \consists "Bar_engraver" - \override BarLine #'bar-size = #4 + \consists "Separating_line_group_engraver" + \override BarLine #'bar-size = #4 } \lyricmode { looooooooooooooooooooooooooooooooooong1 syllable } diff --git a/lily/relocate.cc b/lily/relocate.cc index 753557dc0b..ecc8a787ea 100644 --- a/lily/relocate.cc +++ b/lily/relocate.cc @@ -41,10 +41,10 @@ sane_putenv (char const *key, String value, bool overwrite) } static int -set_env_file (char const *key, String value) +set_env_file (char const *key, String value, bool overwrite = false) { if (is_file (value)) - return sane_putenv (key, value, false); + return sane_putenv (key, value, overwrite); else if (be_verbose_global) warning (_f ("no such file: %s for %s", value, key)); return -1; @@ -132,7 +132,7 @@ framework_relocation (String prefix) /* need otherwise dynamic .so's aren't found. */ prepend_env_path ("DYLD_LIBRARY_PATH", libdir); - set_env_file ("FONTCONFIG_FILE", sysconfdir + "/fonts/fonts.conf"); + set_env_file ("FONTCONFIG_FILE", sysconfdir + "/fonts/fonts.conf", true); set_env_dir ("FONTCONFIG_PATH", sysconfdir + "/fonts"); #ifdef __MINGW32__ @@ -156,7 +156,7 @@ framework_relocation (String prefix) set_env_file ("PANGO_RC_FILE", sysconfdir + "/pango/pangorc"); set_env_dir ("PANGO_PREFIX", prefix); - + prepend_env_path ("PATH", bindir); } diff --git a/python/musicexp.py b/python/musicexp.py index 43c75c870f..5698c9f699 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -14,67 +14,77 @@ class Output_stack_element: return o class Output_printer: + + """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 - self.nesting = 0 - self.file = sys.stdout - self.line_len = 72 - self.output_state_stack = [Output_stack_element()] + self._line = '' + self._indent = 4 + self._nesting = 0 + self._file = sys.stdout + self._line_len = 72 + self._output_state_stack = [Output_stack_element()] self._skipspace = False - self.last_duration = None + self._last_duration = None + def set_file (self, file): + self._file = file + def dump_version (self): self.newline () self.print_verbatim ('\\version "@TOPLEVEL_VERSION@"') self.newline () def get_indent (self): - return self.nesting * self.indent + return self._nesting * self._indent def override (self): - last = self.output_state_stack[-1] - self.output_state_stack.append (last.copy()) + 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] - if not self.output_state_stack: + del self._output_state_stack[-1] + if not self._output_state_stack: raise 'empty' def duration_factor (self): - return self.output_state_stack[-1].factor + return self._output_state_stack[-1].factor def print_verbatim (self, str): - self.line += str + self._line += str + + def unformatted_output (self, str): + self._nesting += str.count ('<') + str.count ('{') + self._nesting -= str.count ('>') + str.count ('}') + self.print_verbatim (str) def print_duration_string (self, str): - if self.last_duration == str: + if self._last_duration == str: return - self.print_verbatim (str) + self.unformatted_output (str) def add_word (self, str): - if (len (str) + 1 + len (self.line) > self.line_len): + if (len (str) + 1 + len (self._line) > self._line_len): self.newline() self._skipspace = True - self.nesting += str.count ('<') + str.count ('{') - self.nesting -= str.count ('>') + str.count ('}') - if not self._skipspace: - self.line += ' ' - self.line += str + self._line += ' ' + self.unformatted_output (str) self._skipspace = False def newline (self): - self.file.write (self.line + '\n') - self.line = ' ' * self.indent * self.nesting + self._file.write (self._line + '\n') + self._line = ' ' * self._indent * self._nesting self._skipspace = True def skipspace (self): @@ -84,9 +94,10 @@ class Output_printer: self.dump (arg) def dump (self, str): + if self._skipspace: self._skipspace = False - self.print_verbatim (str) + self.unformatted_output (str) else: words = string.split (str) for w in words: @@ -117,11 +128,8 @@ class Duration: return str def print_ly (self, outputter): - if isinstance (outputter, Output_printer): - str = self.ly_expression (self.factor / outputter.duration_factor ()) - outputter.print_duration_string (str) - else: - outputter (self.ly_expression ()) + str = self.ly_expression (self.factor / outputter.duration_factor ()) + outputter.print_duration_string (str) def __repr__(self): return self.ly_expression() @@ -218,7 +226,8 @@ class Music: self.parent = None self.start = Rational (0) self.comment = '' - + self.identifier = None + def get_length(self): return Rational (0) @@ -260,21 +269,22 @@ class Music: return - if isinstance (printer, Output_printer): - if text == '\n': - printer.newline () - return - - lines = string.split (text, '\n') - for l in lines: - if l: - printer.print_verbatim ('% ' + l) - printer.newline () - else: - printer ('% ' + re.sub ('\n', '\n% ', text)) - printer ('\n') + if text == '\n': + printer.newline () + return + lines = string.split (text, '\n') + for l in lines: + if l: + printer.dump ('% ' + l) + printer.newline () + def print_with_identifier (self, printer): + if self.identifier: + printer ("\\%s" % self.identifier) + else: + self.print_ly (printer) + def print_ly (self, printer): printer (self.ly_expression ()) @@ -287,15 +297,11 @@ class MusicWrapper (Music): class TimeScaledMusic (MusicWrapper): def print_ly (self, func): - if isinstance(func, Output_printer): - func ('\\times %d/%d ' % - (self.numerator, self.denominator)) - func.add_factor (Rational (self.numerator, self.denominator)) - MusicWrapper.print_ly (self, func) - func.revert () - else: - func (r'\times 1/1 ') - MusicWrapper.print_ly (self, func) + func ('\\times %d/%d ' % + (self.numerator, self.denominator)) + func.add_factor (Rational (self.numerator, self.denominator)) + MusicWrapper.print_ly (self, func) + func.revert () class NestedMusic(Music): def __init__ (self): @@ -373,16 +379,14 @@ class SequentialMusic (NestedMusic): printer ('{') if self.comment: self.print_comment (printer) - elif isinstance (printer, Output_printer): - printer.newline() - else: - printer ('\n') - + + printer.newline() for e in self.elements: e.print_ly (printer) printer ('}') - + printer.newline() + def lisp_sub_expression (self, pred): name = self.name() @@ -593,6 +597,37 @@ def test_pitch (): print bflat.transposed (down).transposed (down) print bflat.transposed (down).transposed (down).transposed (down) + + +def test_printer (): + def make_note (): + evc = EventChord() + n = NoteEvent() + evc.append (n) + return n + + def make_tup (): + m = SequentialMusic() + m.append (make_note ()) + m.append (make_note ()) + m.append (make_note ()) + + + t = TimeScaledMusic () + t.numerator = 2 + t.denominator = 3 + t.element = m + return t + + m = SequentialMusic () + 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 @@ -617,14 +652,18 @@ def test_expr (): evc.insert_around (None, n, 0) m.insert_around (None, evc, 0) - evc = ClefChange("G") + evc = ClefChange() + evc.type = 'treble' m.insert_around (None, evc, 0) evc = EventChord() tonic = Pitch () tonic.step = 2 tonic.alteration = -2 - n = KeySignatureEvent(tonic, [0, 0, -2, 0, 0,-2,-2] ) + n = KeySignatureChange() + 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) @@ -632,8 +671,10 @@ def test_expr (): if __name__ == '__main__': + test_printer () + raise 'bla' test_pitch() - raise 1 + expr = test_expr() expr.set_start (Rational (0)) print expr.ly_expression() diff --git a/python/musicxml.py b/python/musicxml.py index 8886ecead0..78d68dcd0b 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -16,7 +16,7 @@ class Xml_node: self._parent = None def is_first (self): - return self._parent.get_typed_children (self.__class__)[0] == self + return self._parent.get_typed_children (self.__class__)[0] == self def original (self): return self._original @@ -38,6 +38,9 @@ class Xml_node: def get_named_children (self, nm): return self.get_typed_children (class_dict[nm]) + def get_named_child (self, nm): + return self.get_maybe_exist_named_child (nm) + def get_children (self, predicate): return [c for c in self._children if predicate(c)] @@ -147,13 +150,39 @@ class Note (Measure_element): def get_pitches (self): return self.get_typed_children (class_dict[u'pitch']) - +class Part_list (Music_xml_node): + pass class Measure(Music_xml_node): def get_notes (self): return self.get_typed_children (class_dict[u'note']) + +class Musicxml_voice: + def __init__ (self): + self._elements = [] + self._staves = {} + self._start_staff = None + + def add_element (self, e): + self._elements.append (e) + if (isinstance (e, Note) + and e.get_maybe_exist_typed_child (Staff)): + name = e.get_maybe_exist_typed_child (Staff).get_text () + + if not self._start_staff: + self._start_staff = name + self._staves[name] = True + + def insert (self, idx, e): + self._elements.insert (idx, e) + + + class Part (Music_xml_node): + def __init__ (self): + self._voices = [] + def interpret (self): """Set durations and starting points.""" @@ -207,31 +236,36 @@ class Part (Music_xml_node): if isinstance (n, Attributes): for v in voices.values (): - v.append (n) + v.add_element (n) continue id = voice_id.get_text () if not voices.has_key (id): - voices[id] = [] + voices[id] = Musicxml_voice() - voices[id].append (n) + voices[id].add_element (n) if start_attr: for (k,v) in voices.items (): v.insert (0, start_attr) part._voices = voices + def get_voices (self): return self._voices class Notations (Music_xml_node): def get_tie (self): - return self.get_maybe_exist_named_child ('tied') + ts = self.get_named_children ('tied') + starts = [t for t in ts if t.type == 'start'] + if starts: + return starts[0] + else: + return None def get_tuplet (self): return self.get_maybe_exist_typed_child (Tuplet) - class Time_modification(Music_xml_node): def get_fraction (self): b = self.get_maybe_exist_typed_child (class_dict['actual-notes']) @@ -269,11 +303,15 @@ class Rest (Music_xml_node): pass class Mode (Music_xml_node): pass - +class Tied (Music_xml_node): + pass + class Type (Music_xml_node): pass class Grace (Music_xml_node): pass +class Staff (Music_xml_node): + pass class_dict = { '#comment': Hash_comment, @@ -293,9 +331,12 @@ class_dict = { 'pitch': Pitch, 'rest':Rest, 'slur': Slur, + 'tied': Tied, 'time-modification': Time_modification, 'tuplet': Tuplet, 'type': Type, + 'part-list': Part_list, + 'staff': Staff, } def name2class_name (name): diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index dddafd08e3..b5fd27f154 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -220,14 +220,13 @@ def musicxml_note_to_lily_main_event (n): return event def musicxml_voice_to_lily_voice (voice): - ly_voice = [] ly_now = Rational (0) pending_skip = Rational (0) tuplet_events = [] - for n in voice: + for n in voice._elements: if n.get_name () == 'forward': continue @@ -275,6 +274,7 @@ def musicxml_voice_to_lily_voice (voice): span_events = [] if notations: if notations.get_tuplet(): + tuplet_event = notations.get_tuplet() mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) frac = (1,1) if mod: @@ -338,30 +338,34 @@ def musicxml_pitch_to_lily (mxl_pitch): p.octave = mxl_pitch.get_octave () - 4 return p -def synchronize_musicxml (parts): - progress ("Synchronizing MusicXML...") - - all_voices = {} - for p in parts: - p.interpret () - p.extract_voices () - voice_dict = p.get_voices () - - for (id, voice) in voice_dict.items (): - m_name = 'Part' + p.id + 'Voice' + id - m_name = musicxml_id_to_lily (m_name) - all_voices[m_name] = voice - return all_voices + + +def voices_in_part (part): + """Return a Name -> Voice dictionary for PART""" + part.interpret () + part.extract_voices () + voice_dict = part.get_voices () + + return voice_dict + +def voices_in_part_in_parts (parts): + """return a Part -> Name -> Voice dictionary""" + return dict([(p, voices_in_part (p)) for p in parts]) + def get_all_voices (parts): + all_voices = voices_in_part_in_parts (parts) - all_voices = synchronize_musicxml (parts) - - progress ("Converting to LilyPond expressions...") all_ly_voices = {} - for (k, v) in all_voices.items(): - all_ly_voices[k] = musicxml_voice_to_lily_voice (v) + for p, name_voice in all_voices.items (): + part_ly_voices = {} + for n, v in name_voice.items (): + progress ("Converting to LilyPond expressions...") + part_ly_voices[n] = (musicxml_voice_to_lily_voice (v), v) + + all_ly_voices[p] = part_ly_voices + return all_ly_voices class NonDentedHeadingFormatter (optparse.IndentedHelpFormatter): @@ -425,6 +429,80 @@ Copyright (c) 2005 by p.formatter = NonDentedHeadingFormatter () return p +def music_xml_voice_name_to_lily_name (part, name): + str = "Part%sVoice%s" % (part.id, name) + return musicxml_id_to_lily (str) + +def print_voice_definitions (printer, voices): + for (part, nv_dict) in voices.items(): + for (name, (voice, mxlvoice)) in nv_dict.items (): + k = music_xml_voice_name_to_lily_name (part, name) + printer.dump ('%s = ' % k) + voice.print_ly (printer) + printer.newline() +def uniq_list (l): + return dict ([(elt,1) for elt in l]).keys () + +def print_score_setup (printer, part_list, voices): + part_dict = dict ([(p.id, p) for p in voices.keys ()]) + + printer ('<<') + printer.newline () + for part_definition in part_list: + part_name = part_definition.id + try: + part = part_dict[part_name] + except KeyError: + print 'unknown part in part-list:', part_name + continue + + nv_dict = voices[part] + staves = reduce (lambda x,y: x+ y, + [mxlvoice._staves.keys () + for (v, mxlvoice) in nv_dict.values ()], + []) + + if len (staves) > 1: + staves = uniq_list (staves) + staves.sort () + printer ('\\context PianoStaff << ') + printer.newline () + + for s in staves: + staff_voices = [music_xml_voice_name_to_lily_name (part, voice_name) + for (voice_name, (v, mxlvoice)) in nv_dict.items () + if mxlvoice._start_staff == s] + + printer ('\\context Staff = "%s" << ' % s) + printer.newline () + for v in staff_voices: + printer ('\\context Voice = "%s" \\%s' % (v,v)) + printer.newline () + printer ('>>') + printer.newline () + + printer ('>>') + printer.newline () + + else: + printer ('\\new Staff <<') + printer.newline () + for (n,v) in nv_dict.items (): + + n = music_xml_voice_name_to_lily_name (part, n) + printer ('\\context Voice = "%s" \\%s' % (n,n)) + printer ('>>') + printer.newline () + + + printer ('>>') + printer.newline () + + + +def print_ly_preamble (printer, filename): + printer.dump_version () + printer.print_verbatim ('%% converted from %s\n' % filename) def convert (filename, output_name): printer = musicexp.Output_printer() @@ -432,21 +510,28 @@ def convert (filename, output_name): tree = musicxml.read_musicxml (filename) parts = tree.get_typed_children (musicxml.Part) - voices = get_all_voices (parts) - if output_name: - printer.file = open (output_name,'w') + part_list = [] + if tree.get_maybe_exist_typed_child (musicxml.Part_list): + pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) + part_list = pl.get_named_children ("score-part") - progress ("Printing as .ly...") + if not output_name: + output_name = os.path.basename (filename) + output_name = os.path.splitext (output_name)[0] + '.ly' - printer.dump_version () - for (k,v) in voices.items(): - printer.print_verbatim ('%% converted from %s\n' % filename) - printer.dump ('%s = ' % k) - v.print_ly (printer) - printer.newline() + + if output_name: + progress ("Output to `%s'" % output_name) + printer.set_file (open (output_name, 'w')) + + progress ("Printing as .ly...") + print_ly_preamble (printer, filename) + print_voice_definitions (printer, voices) + print_score_setup (printer, part_list, voices) + printer.newline () return voices -- 2.39.5