From 175dabd615768d2d232c5f975ab2ed2a59dc6171 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Wed, 7 Dec 2005 12:38:18 +0000 Subject: [PATCH] * scripts/musicxml2ly.py (progress): new function (musicxml_key_to_lily): don't barf on modeless keys. (create_skip_music): new function. (musicxml_spanner_to_lily_event): new function. Handle beams too. (musicxml_note_to_lily_main_event): new function. * python/musicexp.py (Music.__init__): add comment field. (NestedMusic.append): new routine. (SequentialMusic.print_ly): print comment. (ArpeggioEvent.ly_expression): new class (BeamEvent.ly_expression): new class (NoteEvent.__init__): support for cautionary/forced accs. * lily/lookup.cc (slur): normal order for array loop. --- ChangeLog | 18 ++++ THANKS | 3 + lily/lookup.cc | 6 +- python/musicexp.py | 88 +++++++++++----- python/musicxml.py | 48 +++++---- scripts/musicxml2ly.py | 225 ++++++++++++++++++++++++++++------------- 6 files changed, 269 insertions(+), 119 deletions(-) diff --git a/ChangeLog b/ChangeLog index 984add0693..82d1a7ecb7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,23 @@ 2005-12-07 Han-Wen Nienhuys + * scripts/musicxml2ly.py (progress): new function + (musicxml_key_to_lily): don't barf on modeless keys. + (create_skip_music): new function. + (musicxml_spanner_to_lily_event): new function. Handle beams too. + (musicxml_note_to_lily_main_event): new function. + + * python/musicexp.py (Music.__init__): add comment field. + (NestedMusic.append): new routine. + (SequentialMusic.print_ly): print comment. + (ArpeggioEvent.ly_expression): new class + (BeamEvent.ly_expression): new class + (NoteEvent.__init__): support for cautionary/forced accs. + + * lily/lookup.cc (slur): normal order for array loop. + + * scm/framework-ps.scm (dump-stencil-as-EPS): set left X of bbox + to 0.0. + * ly/engraver-init.ly: set bar-size, so bar-lines aren't collapsed. diff --git a/THANKS b/THANKS index d99c42c56e..6bbc21da9f 100644 --- a/THANKS +++ b/THANKS @@ -24,6 +24,7 @@ Nicolas Sceaux SPONSORS Aaron Mehl +Christian Ebert Henrik Frisk Jay Hamilton Jamie Bullock @@ -45,6 +46,7 @@ BUG HUNTERS/SUGGESTIONS Bob Broadus Chris Sawer +Christian Ebert Darius Blasband Donald Axel Edward Neeman @@ -52,6 +54,7 @@ Eduardo Vieira Erlend Aasland Hans Forbrich Jukka Akkanen +Lambros Lambrou Matevž Jekovec Michael Welsh Duggan Milan Zamazal diff --git a/lily/lookup.cc b/lily/lookup.cc index 42c6ad9d57..9465463a0c 100644 --- a/lily/lookup.cc +++ b/lily/lookup.cc @@ -365,9 +365,9 @@ Lookup::slur (Bezier curve, Real curvethick, Real linethick) SCM scontrols[8]; - for (int i = 4; i--;) - scontrols[ i ] = ly_offset2scm (back.control_[i]); - for (int i = 4; i--;) + for (int i = 0; i < 4; i++) + scontrols[i] = ly_offset2scm (back.control_[i]); + for (int i = 0; i < 4; i++) scontrols[i + 4] = ly_offset2scm (curve.control_[i]); /* diff --git a/python/musicexp.py b/python/musicexp.py index 717a9a8a41..b2b5846a6f 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -12,8 +12,8 @@ class Output_stack_element: o = Output_stack_element() o.factor = self.factor return o -class Output_printer: +class Output_printer: ## TODO: support for \relative. def __init__ (self): @@ -92,7 +92,7 @@ class Duration: self.duration_log = 0 self.dots = 0 self.factor = Rational (1) - + def lisp_expression (self): return '(ly:make-duration %d %d %d %d)' % (self.duration_log, self.dots, @@ -147,7 +147,7 @@ class Pitch: self.alteration = 0 self.step = 0 self.octave = 0 - + def __repr__(self): return self.ly_expression() @@ -212,12 +212,11 @@ class Music: def __init__ (self): self.parent = None self.start = Rational (0) - pass + self.comment = '' def get_length(self): return Rational (0) - def get_properties (self): return '' @@ -248,25 +247,31 @@ class Music: return self return None - def print_ly (self, printer): - printer (self.ly_expression ()) + def print_comment (self, printer, text = None): + if not text: + text = self.comment + if not text: + return -class Comment (Music): - def __name__ (self): - self.text = '' - def print_ly (self, printer): + if isinstance (printer, Output_printer): - lines = string.split (self.text, '\n') + 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% ', self.text)) + printer ('% ' + re.sub ('\n', '\n% ', text)) printer ('\n') - + + def print_ly (self, printer): + printer (self.ly_expression ()) class MusicWrapper (Music): def __init__ (self): @@ -290,11 +295,15 @@ class TimeScaledMusic (MusicWrapper): class NestedMusic(Music): def __init__ (self): Music.__init__ (self) - self.elements = [] + self.elements = [] + + def append (self, what): + if what: + self.elements.append (what) + def has_children (self): return self.elements - def insert_around (self, succ, elt, dir): assert elt.parent == None assert succ == None or succ in self.elements @@ -357,8 +366,16 @@ class NestedMusic(Music): class SequentialMusic (NestedMusic): def print_ly (self, printer): printer ('{') + if self.comment: + self.print_comment (printer) + elif isinstance (printer, Output_printer): + printer.newline() + else: + printer ('\n') + for e in self.elements: e.print_ly (printer) + printer ('}') def lisp_sub_expression (self, pred): @@ -406,7 +423,8 @@ class EventChord(NestedMusic): # print 'huh', rest_events, note_events, other_events for e in other_events: e.print_ly (printer) - + + self.print_comment (printer) class Event(Music): pass @@ -417,15 +435,28 @@ class SpanEvent (Event): self.span_direction = 0 def get_properties(self): return "'span-direction %d" % self.span_direction + class SlurEvent (SpanEvent): def ly_expression (self): return {-1: '(', 0:'', 1:')'}[self.span_direction] -class ArpeggioEvent(Music): +class BeamEvent (SpanEvent): + def ly_expression (self): + return {-1: '[', + 0:'', + 1:']'}[self.span_direction] + +class ArpeggioEvent(Event): def ly_expression (self): return ('\\arpeggio') + + +class TieEvent(Event): + def ly_expression (self): + return '~' + class RhythmicEvent(Event): def __init__ (self): @@ -445,8 +476,6 @@ class RestEvent (RhythmicEvent): def print_ly (self, printer): printer('r') - if isinstance(printer, Output_printer): - printer.skipspace() self.duration.print_ly (printer) class SkipEvent (RhythmicEvent): @@ -457,18 +486,31 @@ class NoteEvent(RhythmicEvent): def __init__ (self): RhythmicEvent.__init__ (self) self.pitch = Pitch() - + self.cautionary = False + self.forced_accidental = False + def get_properties (self): return ("'pitch %s\n 'duration %s" % (self.pitch.lisp_expression (), self.duration.lisp_expression ())) + def pitch_mods (self): + excl_question = '' + if self.cautionary: + excl_question += '?' + if self.forced_accidental: + excl_question += '!' + + return excl_question + def ly_expression (self): - return '%s%s' % (self.pitch.ly_expression (), - self.duration.ly_expression ()) + return '%s%s%s' % (self.pitch.ly_expression (), + self.pitch_mods(), + self.duration.ly_expression ()) def print_ly (self, printer): self.pitch.print_ly (printer) + printer (self.pitch_mods ()) self.duration.print_ly (printer) class KeySignatureChange (Music): diff --git a/python/musicxml.py b/python/musicxml.py index 34de577a7c..a1107f6bf0 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -225,18 +225,12 @@ class Part (Music_xml_node): return self._voices class Notations (Music_xml_node): + def get_tie (self): + return self.get_maybe_exist_named_child ('tied') + def get_tuplet (self): return self.get_maybe_exist_typed_child (Tuplet) - def get_slur (self): - slurs = self.get_typed_children (Slur) - - if not slurs: - return None - - if len (slurs) > 1: - print "More than one slur?!" - - return slurs[0] + class Time_modification(Music_xml_node): def get_fraction (self): @@ -248,18 +242,28 @@ class Time_modification(Music_xml_node): class Tuplet(Music_xml_node): pass + class Slur (Music_xml_node): - pass + def get_type (self): + return self.type + +class Beam (Music_xml_node): + def get_type (self): + return self.get_text () class Chord (Music_xml_node): pass + class Dot (Music_xml_node): pass + class Alter (Music_xml_node): pass class Rest (Music_xml_node): pass +class Mode (Music_xml_node): + pass class Type (Music_xml_node): pass @@ -267,23 +271,25 @@ class Grace (Music_xml_node): pass class_dict = { - 'notations': Notations, - 'time-modification': Time_modification, + '#comment': Hash_comment, 'alter': Alter, - 'grace': Grace, - 'rest':Rest, - 'dot': Dot, + 'attributes': Attributes, + 'beam' : Beam, 'chord': Chord, + 'dot': Dot, 'duration': Duration, - 'attributes': Attributes, + 'grace': Grace, + 'mode' : Mode, + 'measure': Measure, + 'notations': Notations, 'note': Note, + 'part': Part, 'pitch': Pitch, - 'part': Part, - 'measure': Measure, - 'type': Type, + 'rest':Rest, 'slur': Slur, + 'time-modification': Time_modification, 'tuplet': Tuplet, - '#comment': Hash_comment, + 'type': Type, } def name2class_name (name): diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 31bab22dee..4e5a39b2d8 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -27,6 +27,12 @@ import musicxml import musicexp from rational import Rational + +def progress (str): + sys.stderr.write (str + '\n') + sys.stderr.flush () + + def musicxml_duration_to_lily (mxl_note): d = musicexp.Duration () if mxl_note.get_maybe_exist_typed_child (musicxml.Type): @@ -39,12 +45,14 @@ def musicxml_duration_to_lily (mxl_note): return d -span_event_dict = { - 'start': -1, - 'stop': 1 -} - def group_tuplets (music_list, events): + + + """Collect Musics from + MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects. + """ + + indices = [] j = 0 @@ -80,6 +88,7 @@ def group_tuplets (music_list, events): new_list.extend (music_list[last:]) return new_list + def musicxml_clef_to_lily (mxl): sign = mxl.get_maybe_exist_named_child ('sign') change = musicexp.ClefChange () @@ -98,7 +107,22 @@ def musicxml_time_to_lily (mxl): return change def musicxml_key_to_lily (mxl): - mode = mxl.get_maybe_exist_named_child ('mode').get_text () + start_pitch = musicexp.Pitch () + try: + mode = mxl.get_maybe_exist_named_child ('mode') + if mode: + mode = mode.get_text () + else: + mode = 'major' + + (n,a) = { 'major' : (0,0), + 'minor' : (6,0), + }[mode] + start_pitch.step = n + start_pitch.alteration = a + except KeyError: + print 'unknown mode', mode + fifths = string.atoi (mxl.get_maybe_exist_named_child ('fifths').get_text ()) fifth = musicexp.Pitch() @@ -108,15 +132,15 @@ def musicxml_key_to_lily (mxl): fifth.step *= -1 fifth.normalize () - c = musicexp.Pitch() + start_pitch = musicexp.Pitch() for x in range (fifths): - c = c.transposed (fifth) + start_pitch = start_pitch.transposed (fifth) - c.octave = 0 + start_pitch.octave = 0 change = musicexp.KeySignatureChange() change.mode = mode - change.tonic = c + change.tonic = start_pitch return change def musicxml_attributes_to_lily (attrs): @@ -135,16 +159,67 @@ def musicxml_attributes_to_lily (attrs): return elts -def insert_measure_start_comments (ly_voice, indices): - idxs = indices[:] - idxs.reverse () - for i in idxs: - c = musicexp.Comment() - c.text = '' - ly_voice.insert (i, c) +def create_skip_music (duration): + skip = musicexp.SkipEvent() + skip.duration.duration_log = 0 + skip.duration.factor = duration + + evc = musicexp.EventChord () + evc.append (skip) + return evc + +spanner_event_dict = { + 'slur' : musicexp.SlurEvent, + 'beam' : musicexp.BeamEvent, +} +spanner_type_dict = { + 'start': -1, + 'begin': -1, + 'stop': 1, + 'end' : 1 +} - return ly_voice +def musicxml_spanner_to_lily_event (mxl_event): + ev = None + name = mxl_event.get_name() + try: + func = spanner_event_dict[name] + ev = func() + except KeyError: + print 'unknown span event ', mxl_event + + try: + key = mxl_event.get_type () + ev.span_direction = spanner_type_dict[key] + except KeyError: + print 'unknown span type', key, 'for', name + + return ev + +def musicxml_note_to_lily_main_event (n): + pitch = None + duration = None + + mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch) + event = None + if mxl_pitch: + pitch = musicxml_pitch_to_lily (mxl_pitch) + event = musicexp.NoteEvent() + event.pitch = pitch + + acc = n.get_maybe_exist_named_child ('accidental') + if acc: + # let's not force accs everywhere. + event.cautionary = acc.editorial + print event, event.cautionary, event.ly_expression() + + elif n.get_maybe_exist_typed_child (musicxml.Rest): + event = musicexp.RestEvent() + + event.duration = musicxml_duration_to_lily (n) + return event + def musicxml_voice_to_lily_voice (voice): ly_voice = [] @@ -152,68 +227,76 @@ def musicxml_voice_to_lily_voice (voice): tuplet_events = [] - measure_start_indices = [] for n in voice: - if n.is_first (): - measure_start_indices.append (len (ly_voice)) - + if n.get_name () == 'forward': + continue + if isinstance (n, musicxml.Attributes): ly_voice.extend (musicxml_attributes_to_lily (n)) continue if not n.__class__.__name__ == 'Note': - print 'not a Note or Attributes?' + print 'not a Note or Attributes?', n continue - - - pitch = None - duration = None - - mxl_pitch = n.get_maybe_exist_typed_child (musicxml.Pitch) - event = None - notations = n.get_maybe_exist_typed_child (musicxml.Notations) - tuplet_event = None - slur_event = None - if notations: - tuplet_event = notations.get_tuplet () - slur_event = notations.get_slur () - - if mxl_pitch: - pitch = musicxml_pitch_to_lily (mxl_pitch) - event = musicexp.NoteEvent() - event.pitch = pitch - elif n.get_maybe_exist_typed_child (musicxml.Rest): - event = musicexp.RestEvent() - - event.duration = musicxml_duration_to_lily (n) + if n.is_first () and ly_voice: + ly_voice[-1].comment += '\n' + ev_chord = None if None == n.get_maybe_exist_typed_child (musicxml.Chord): if ly_voice: ly_now += ly_voice[-1].get_length () if ly_now <> n._when: - diff = n._when - ly_now + diff = n._when - ly_now if diff < Rational (0): print 'huh: negative skip', n._when, ly_now, n._duration diff = Rational (1,314159265) - - skip = musicexp.SkipEvent() - skip.duration.duration_log = 0 - skip.duration.factor = diff - evc = musicexp.EventChord () - evc.elements.append (skip) - ly_voice.append (evc) + ly_voice.append (create_skip_music (diff)) ly_now = n._when ly_voice.append (musicexp.EventChord()) else: pass - + ev_chord = ly_voice[-1] - ev_chord.elements.append (event) + main_event = musicxml_note_to_lily_main_event (n) + ev_chord.append (main_event) + + notations = n.get_maybe_exist_typed_child (musicxml.Notations) + tuplet_event = None + span_events = [] + if notations: + if notations.get_tuplet(): + mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) + frac = (1,1) + if mod: + frac = mod.get_fraction () + + tuplet_events.append ((ev_chord, tuplet_event, frac)) + + slurs = [s for s in notations.get_named_children ('slur') + if s.get_type () in ('start','stop')] + if slurs: + if len (slurs) > 1: + print 'more than 1 slur?' + + lily_ev = musicxml_spanner_to_lily_event (slurs[0]) + ev_chord.append (lily_ev) + + mxl_tie = notations.get_tie () + if mxl_tie and mxl_tie.type == 'start': + ev_chord.append (musicexp.TieEvent ()) + + mxl_beams = [b for b in n.get_named_children ('beam') + if b.get_type () in ('begin', 'end')] + if mxl_beams: + beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0]) + if beam_ev: + ev_chord.append (beam_ev) + if tuplet_event: mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) frac = (1,1) @@ -221,16 +304,7 @@ def musicxml_voice_to_lily_voice (voice): frac = mod.get_fraction () tuplet_events.append ((ev_chord, tuplet_event, frac)) - - if slur_event: - sp = musicexp.SlurEvent() - try: - sp.span_direction = span_event_dict[slur_event.type] - ev_chord.elements.append (sp) - except KeyError: - pass - - ly_voice = insert_measure_start_comments (ly_voice, measure_start_indices) + ly_voice = group_tuplets (ly_voice, tuplet_events) seq_music = musicexp.SequentialMusic() @@ -260,6 +334,8 @@ def musicxml_pitch_to_lily (mxl_pitch): return p def get_all_voices (parts): + progress ("Synchronizing MusicXML...") + all_voices = {} for p in parts: p.interpret () @@ -267,12 +343,17 @@ def get_all_voices (parts): voice_dict = p.get_voices () for (id, voice) in voice_dict.items (): - m = musicxml_voice_to_lily_voice (voice) m_name = 'Part' + p.id + 'Voice' + id m_name = musicxml_id_to_lily (m_name) - all_voices[m_name] = m + all_voices[m_name] = voice + + + 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) - return all_voices + return all_ly_voices class NonDentedHeadingFormatter (optparse.IndentedHelpFormatter): def format_heading(self, heading): @@ -339,15 +420,18 @@ Copyright (c) 2005 by def convert (filename, output_name): printer = musicexp.Output_printer() + + progress ("Reading MusicXML...") + 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') + progress ("Printing as .ly...") for (k,v) in voices.items(): printer.dump ('%s = ' % k) v.print_ly (printer) @@ -359,9 +443,6 @@ def convert (filename, output_name): opt_parser = option_parser() (options, args) = opt_parser.parse_args () -if options.version: - opt_parser.print_version() - sys.exit (0) if not args: opt_parser.print_usage() sys.exit (2) -- 2.39.5