From 188bbb97804465f6ce916ee9d38b5e7b1dfc6f5d Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Sat, 3 Dec 2005 11:43:07 +0000 Subject: [PATCH] *** empty log message *** --- Documentation/topdocs/NEWS.tely | 2 +- python/musicexp.py | 475 ++++++++++++++++++++++++++++++++ python/musicxml.py | 343 +++++++++++++++++++++++ python/rational.py | 267 ++++++++++++++++++ scripts/musicxml2ly.py | 141 ++++++++++ 5 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 python/musicexp.py create mode 100644 python/musicxml.py create mode 100644 python/rational.py create mode 100644 scripts/musicxml2ly.py diff --git a/Documentation/topdocs/NEWS.tely b/Documentation/topdocs/NEWS.tely index 007aa8be50..efc05d37ff 100644 --- a/Documentation/topdocs/NEWS.tely +++ b/Documentation/topdocs/NEWS.tely @@ -46,7 +46,7 @@ This document is also available in @uref{NEWS.pdf,PDF}. @itemize @bullet @item Texts set in a TrueType font are now kerned. This requires CVS -Pango or Pango 2.12. +Pango or Pango 1.12. @item Using the @TeX{} no longer requires linking or dynamically opening the kpathsea library, making the backend more easily usable on diff --git a/python/musicexp.py b/python/musicexp.py new file mode 100644 index 0000000000..76f9078a0b --- /dev/null +++ b/python/musicexp.py @@ -0,0 +1,475 @@ +import inspect +import sys +import string +from rational import Rational + +def flatten_list (fl): + if type(fl) == type((1,)): + return + + flattened = [] + for f in fl: + flattened += flatten_list (fl) + +def is_derived (deriv_class, maybe_base): + if deriv_class == maybe_base: + return True + + for c in deriv_class.__bases__: + if is_derived (c, maybe_base): + return True + + return False + +class Output_printer: + def __init__ (self): + self.line = '' + self.indent = 0 + self.file = sys.stdout + self.line_len = 72 + + def add_word (self, str): + if (len (str) + 1 + len (self.line) > self.line_len): + self.newline() + + self.indent += str.count ('<') + str.count ('{') + self.indent -= str.count ('>') + str.count ('}') + self.line += ' ' + str + + def newline (self): + self.file.write (self.line + '\n') + self.line = ' ' * self.indent + + def dump (self, str): + words = string.split (str) + for w in words: + self.add_word (w) + +class Duration: + def __init__ (self): + self.duration_log = 2 + self.dots = 0 + 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 ()) + + def ly_expression (self): + str = '%d%s' % (1 << self.duration_log, '.'*self.dots) + + if self.factor <> Rational (1,1): + str += '*%d/%d' % (self.factor.numerator (),self.factor.denominator ()) + + return str + + 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, + 1 << self.dots) + + log = abs (self.duration_log) + dur = 1 << log + if self.duration_log < 0: + base = Rational (dur) + else: + base = Rational (1, dur) + + return base * dot_fact * self.factor + +class Pitch: + def __init__ (self): + self.alteration = 0 + self.step = 0 + self.octave = 0 + + def lisp_expression (self): + return '(ly:make-pitch %d %d %d)' % (self.octave, + self.step, + self.alteration) + + def copy (self): + p = Pitch () + p.alteration = self.alteration + p.step = self.step + p.octave = self.octave + return p + + def steps (self): + return self.step + self.octave * 7 + + def ly_step_expression (self): + str = 'cdefgab'[self.step] + if self.alteration > 0: + str += 'is'* (self.alteration) + elif self.alteration < 0: + str += 'es'* (-self.alteration) + + return str.replace ('aes', 'as').replace ('ees', 'es') + + def ly_expression (self): + str = self.ly_step_expression () + if self.octave >= 0: + str += "'" * (self.octave + 1) + elif self.octave < -1: + str += "," * (-self.octave - 1) + + return str + +class Music: + def __init__ (self): + self.tag = None + self.parent = None + self.start = Rational (0) + pass + + def get_length(self): + return Rational (0) + + def set_tag (self, counter, tag_dict): + self.tag = counter + tag_dict [counter] = self + return counter + 1 + + def get_properties (self): + return '' + + def has_children (self): + return False + + def get_index (self): + if self.parent: + return self.parent.elements.index (self) + else: + return None + + def lisp_expression (self): + name = self.name() + tag = '' + if self.tag: + tag = "'input-tag %d" % self.tag + + props = self.get_properties () +# props += 'start %f ' % self.start + + return "(make-music '%s %s %s)" % (name, tag, props) + + def set_start (self, start): + self.start = start + + def find_first (self, predicate): + if predicate (self): + return self + return None + + def print_ly (self, printer): + printer (self.ly_expression ()) + +class Music_document: + def __init__ (self): + self.music = test_expr () + self.tag_dict = {} + self.touched = True + + def recompute (self): + self.tag_dict = {} + self.music.set_tag (0, self.tag_dict) + self.music.set_start (Rational (0)) + +class NestedMusic(Music): + def __init__ (self): + Music.__init__ (self) + self.elements = [] + def has_children (self): + return self.elements + def set_tag (self, counter, dict): + counter = Music.set_tag (self, counter, dict) + for e in self.elements : + counter = e.set_tag (counter, dict) + return counter + + def insert_around (self, succ, elt, dir): + assert elt.parent == None + assert succ == None or succ in self.elements + + + idx = 0 + if succ: + idx = self.elements.index (succ) + if dir > 0: + idx += 1 + else: + if dir < 0: + idx = 0 + elif dir > 0: + idx = len (self.elements) + + self.elements.insert (idx, elt) + elt.parent = self + + def get_properties (self): + return ("'elements (list %s)" + % string.join (map (lambda x: x.lisp_expression(), + self.elements))) + + def get_subset_properties (self, predicate): + return ("'elements (list %s)" + % string.join (map (lambda x: x.lisp_expression(), + 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 = 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: + e.set_start (start) + + def find_first (self, predicate): + 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 name(self): + return 'SequentialMusic' + + def print_ly (self, printer): + printer ('{') + for e in self.elements: + e.print_ly (printer) + printer ('}') + + def lisp_sub_expression (self, pred): + name = self.name() + tag = '' + if self.tag: + tag = "'input-tag %d" % self.tag + + + props = self.get_subset_properties (pred) + + return "(make-music '%s %s %s)" % (name, tag, props) + + def set_start (self, start): + for e in self.elements: + e.set_start (start) + start += e.get_length() + +class EventChord(NestedMusic): + def name(self): + return "EventChord" + + def get_length (self): + l = Rational (0) + for e in self.elements: + l = max(l, e.get_length()) + return l + + def print_ly (self, printer): + note_events = [e for e in self.elements if + is_derived (e.__class__, NoteEvent)] + rest_events = [e for e in self.elements if + is_derived (e.__class__, RhythmicEvent) + and not is_derived (e.__class__, NoteEvent)] + + other_events = [e for e in self.elements if + not is_derived (e.__class__, RhythmicEvent)] + + if rest_events: + printer (rest_events[0].ly_expression ()) + elif len (note_events) == 1: + printer (note_events[0].ly_expression ()) + elif note_events: + pitches = [x.pitch.ly_expression () for x in note_events] + printer ('<%s>' % string.join (pitches) + + note_events[0].duration.ly_expression ()) + else: + pass + # print 'huh', rest_events, note_events, other_events + + for e in other_events: + e.print_ly (printer) + + +class Event(Music): + def __init__ (self): + Music.__init__ (self) + + def name (self): + return "Event" + +class ArpeggioEvent(Music): + def name (self): + return 'ArpeggioEvent' + + def ly_expression (self): + return ('\\arpeggio') + +class RhythmicEvent(Event): + def __init__ (self): + Event.__init__ (self) + self.duration = Duration() + + def get_length (self): + return self.duration.get_length() + + def get_properties (self): + return ("'duration %s" + % self.duration.lisp_expression ()) + + def name (self): + return 'RhythmicEvent' + +class RestEvent (RhythmicEvent): + def name (self): + return 'RestEvent' + def ly_expression (self): + return 'r%s' % self.duration.ly_expression () + +class SkipEvent (RhythmicEvent): + def name (self): + return 'SkipEvent' + def ly_expression (self): + return 's%s' % self.duration.ly_expression () + +class NoteEvent(RhythmicEvent): + def __init__ (self): + RhythmicEvent.__init__ (self) + self.pitch = Pitch() + + def name (self): + return 'NoteEvent' + + def get_properties (self): + return ("'pitch %s\n 'duration %s" + % (self.pitch.lisp_expression (), + self.duration.lisp_expression ())) + + def ly_expression (self): + return '%s%s' % (self.pitch.ly_expression (), + self.duration.ly_expression ()) + + + +class KeySignatureEvent (Event): + def __init__ (self, tonic, scale): + Event.__init__ (self) + self.scale = scale + self.tonic = tonic + def name (self): + return 'KeySignatureEvent' + def ly_expression (self): + return '\\key %s \\major' % self.tonic.ly_step_expression () + + def lisp_expression (self): + pairs = ['(%d . %d)' % (i , self.scale[i]) for i in range (0,7)] + scale_str = ("'(%s)" % string.join (pairs)) + + return """ (make-music 'KeyChangeEvent + 'pitch-alist %s) """ % scale_str + +class ClefEvent (Event): + def __init__ (self, t): + Event.__init__ (self) + self.type = t + + def name (self): + return 'ClefEvent' + def ly_expression (self): + return '\\clef "%s"' % self.type + clef_dict = { + "G": ("clefs.G", -2, -6), + "C": ("clefs.C", 0, 0), + "F": ("clefs.F", 2, 6), + } + + def lisp_expression (self): + (glyph, pos, c0) = self.clef_dict [self.type] + clefsetting = """ + (make-music 'SequentialMusic + 'elements (list + (context-spec-music + (make-property-set 'clefGlyph "%s") 'Staff) + (context-spec-music + (make-property-set 'clefPosition %d) 'Staff) + (context-spec-music + (make-property-set 'middleCPosition %d) 'Staff))) +""" % (glyph, pos, c0) + return clefsetting + +def test_expr (): + m = SequentialMusic() + l = 2 + evc = EventChord() + n = NoteEvent() + n.duration.duration_log = l + n.pitch.step = 1 + evc.insert_around (None, n, 0) + m.insert_around (None, evc, 0) + + evc = EventChord() + n = NoteEvent() + n.duration.duration_log = l + n.pitch.step = 3 + evc.insert_around (None, n, 0) + m.insert_around (None, evc, 0) + + evc = EventChord() + n = NoteEvent() + n.duration.duration_log = l + n.pitch.step = 2 + evc.insert_around (None, n, 0) + m.insert_around (None, evc, 0) + + evc = ClefEvent("G") + 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] ) + evc.insert_around (None, n, 0) + m.insert_around (None, evc, 0) + + return m + + +if __name__ == '__main__': + expr = test_expr() + expr.set_start (Rational (0)) + print expr.ly_expression() + 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 + return ok + + print expr.lisp_sub_expression(sub) + diff --git a/python/musicxml.py b/python/musicxml.py new file mode 100644 index 0000000000..9537f9a753 --- /dev/null +++ b/python/musicxml.py @@ -0,0 +1,343 @@ +import sys +import new +import re +import string +from rational import Rational + +from xml.dom import minidom, Node + + +class Xml_node: + def __init__ (self): + self._children = [] + self._data = None + self._original = None + self._name = 'xml_node' + + def original (self): + return self._original + def get_name (self): + return self._name + + def get_text (self): + if self._data: + return self._data + + if not self._children: + return '' + + return ''.join ([c.get_text () for c in self._children]) + + def get_typed_children (self, klass): + return [c for c in self._children if c.__class__ == klass] + + def get_children (self, predicate): + return [c for c in self._children if predicate(c)] + + def get_all_children (self): + return self._children + + def get_maybe_exist_typed_child (self, klass): + cn = self.get_typed_children (klass) + if len (cn)==0: + return None + elif len (cn) == 1: + return cn[0] + else: + raise "More than 1 child", klass + + def get_unique_typed_child (self, klass): + cn = self.get_typed_children(klass) + if len (cn) <> 1: + print self.__dict__ + raise 'Child is not unique for', (klass, 'found', cn) + + return cn[0] + +class Music_xml_node (Xml_node): + def __init__ (self): + Xml_node.__init__ (self) + self.duration = Rational (0) + self.start = Rational (0) + +class Attributes (Music_xml_node): + def __init__ (self): + self._dict = {} + + def set_attributes_from_previous (self, dict): + self._dict.update (dict) + def read_self (self): + for c in self.get_all_children (): + self._dict[c.get_name()] = c + + def get_named_attribute (self, name): + return self._dict[name] + +class Duration (Music_xml_node): + def get_length (self): + dur = string.atoi (self.get_text ()) * Rational (1,4) + return dur + +class Hash_comment (Music_xml_node): + def to_ly (self, output_func): + output_func ('%% %s\n ' % self.get_text()) + +class Pitch (Music_xml_node): + def get_step (self): + ch = self.get_unique_typed_child (class_dict[u'step']) + step = ch.get_text ().strip () + return step + def get_octave (self): + ch = self.get_unique_typed_child (class_dict[u'octave']) + + step = ch.get_text ().strip () + return string.atoi (step) + + def get_alteration (self): + ch = self.get_maybe_exist_typed_child (class_dict[u'alter']) + alter = 0 + if ch: + alter = string.atoi (ch.get_text ().strip ()) + return alter + + def to_ly (self, output_func): + oct = (self.get_octave () - 4) + oct_str = '' + if oct > 0: + oct_str = "'" * oct + elif oct < 0: + oct_str = "," * -oct + + alt = self.get_alteration () + alt_str = '' + if alt > 0: + alt_str = 'is' * alt + elif alt < 0: + alt_str = 'es' * alt + + output_func ('%s%s%s' % (self.get_step ().lower(), alt_str, oct_str)) + +class Note (Music_xml_node): + def get_duration_log (self): + ch = self.get_maybe_exist_typed_child (class_dict[u'type']) + + if ch: + log = ch.get_text ().strip() + return {'eighth': 3, + 'quarter': 2, + 'half': 1, + '16th': 4, + '32nd': 5, + 'breve': -1, + 'long': -2, + 'whole': 0} [log] + else: + return 0 + + def get_factor (self): + return 1 + + def get_pitches (self): + return self.get_typed_children (class_dict[u'pitch']) + + def to_ly (self, func): + ps = self.get_pitches () + + if len (ps) == 0: + func ('r') + else: + func ('<') + for p in ps: + p.to_ly (func) + func ('>') + + func ('%d ' % (1 << self.get_duration_log ())) + + + + +class Measure(Music_xml_node): + def get_notes (self): + return self.get_typed_children (class_dict[u'note']) + def to_ly (self, func): + func (' { % measure \n ') + for c in self._children: + c.to_ly (func) + func (' } \n ') + +class Part (Music_xml_node): + def to_ly (self, func): + func (' { %% part %s \n ' % self.name) + for c in self._children: + c.to_ly (func) + func (' } \n ') + + def interpret (self): + """Set durations and starting points.""" + + now = Rational (0) + factor = Rational (1) + attr_dict = {} + measures = self.get_typed_children (Measure) + + for m in measures: + for n in m.get_all_children (): + dur = Rational (0) + + if n.__class__ == Attributes: + n.set_attributes_from_previous (attr_dict) + n.read_self () + attr_dict = n._dict.copy () + + factor = Rational (1, + string.atoi (attr_dict['divisions'] + .get_text ())) + elif (n.get_maybe_exist_typed_child (Duration) + and not n.get_maybe_exist_typed_child (Chord)): + mxl_dur = n.get_maybe_exist_typed_child (Duration) + dur = mxl_dur.get_length () * factor + if n.get_name() == 'backup': + dur = - dur + if n.get_maybe_exist_typed_child (Grace): + dur = Rational (0) + + n._when = now + n._duration = dur + now += dur + + def extract_voices (part): + voices = {} + measures = part.get_typed_children (Measure) + elements = [] + for m in measures: + elements.extend (m.get_typed_children (Note)) + + for n in elements: + voice_id = n.get_maybe_exist_typed_child (class_dict['voice']) + + if not voice_id: + continue + + id = voice_id.get_text () + if not voices.has_key (id): + voices[id] = [] + + voices[id].append (n) + + part._voices = voices + def get_voices (self): + return self._voices + +class Chord (Music_xml_node): + pass + +class Dot (Music_xml_node): + pass + +class Rest (Music_xml_node): + pass + +class Type (Music_xml_node): + pass +class Grace (Music_xml_node): + pass + +class_dict = { + 'grace': Grace, + 'rest':Rest, + 'dot': Dot, + 'chord': Chord, + 'duration': Duration, + 'attributes': Attributes, + 'note': Note, + 'pitch': Pitch, + 'part': Part, + 'measure': Measure, + 'type': Type, + '#comment': Hash_comment, +} + +def name2class_name (name): + name = name.replace ('-', '_') + name = name.replace ('#', 'hash_') + name = name[0].upper() + name[1:].lower() + + return str (name) + +def create_classes (names, dict): + for n in names: + if dict.has_key (n): + continue + + class_name = name2class_name (n) + klass = new.classobj (class_name, (Music_xml_node,) , {}) + dict[n] = klass + +def element_names (node, dict): + dict[node.nodeName] = 1 + for cn in node.childNodes: + element_names (cn, dict) + return dict + +def demarshal_node (node): + name = node.nodeName + klass = class_dict[name] + py_node = klass() + py_node._name = name + py_node._children = [demarshal_node (cn) for cn in node.childNodes] + if node.attributes: + for (name, value) in node.attributes.items(): + py_node.name = value + + py_node._data = None + if node.nodeType == node.TEXT_NODE and node.data: + py_node._data = node.data + + py_node._original = node + return py_node + +def strip_white_space (node): + node._children = \ + [c for c in node._children + if not (c._original.nodeType == Node.TEXT_NODE and + re.match (r'^\s*$', c._data))] + + for c in node._children: + strip_white_space (c) + +def create_tree (name): + doc = minidom.parse(name) + node = doc.documentElement + names = element_names (node, {}).keys() + create_classes (names, class_dict) + + return demarshal_node (node) + +def oldtest (): + n = tree._children[-2]._children[-1]._children[0] + print n + print n.get_duration_log() + print n.get_pitches() + print n.get_pitches()[0].get_alteration() + + +def test_musicxml (tree): + m = tree._children[-2] + print m + + m.to_ly (lambda str: sys.stdout.write (str)) + +def read_musicxml (name): + tree = create_tree (name) + strip_white_space (tree) + return tree + + + + + + +if __name__ == '__main__': + tree = read_musicxml ('BeetAnGeSample.xml') + test_musicxml (tree) + diff --git a/python/rational.py b/python/rational.py new file mode 100644 index 0000000000..fc6fd169b5 --- /dev/null +++ b/python/rational.py @@ -0,0 +1,267 @@ +"""Implementation of rational arithmetic.""" + +from __future__ import division + +import decimal as _decimal +import math as _math + +def _gcf(a, b): + """Returns the greatest common factor of a and b.""" + a = abs(a) + b = abs(b) + while b: + a, b = b, a % b + return a + +class Rational(object): + """ + This class provides an exact representation of rational numbers. + + All of the standard arithmetic operators are provided. In mixed-type + expressions, an int or a long can be converted to a Rational without + loss of precision, and will be done as such. + + Rationals can be implicity (using binary operators) or explicity + (using float(x) or x.decimal()) converted to floats or Decimals; + this may cause a loss of precision. The reverse conversions can be + done without loss of precision, and are performed with the + from_exact_float and from_exact_decimal static methods. However, + because of rounding error in the original values, this tends to + produce "ugly" fractions. "Nicer" conversions to Rational can be made + with approx_smallest_denominator or approx_smallest_error. + """ + + def __init__(self, numerator, denominator=1): + """Contructs the Rational object for numerator/denominator.""" + if not isinstance(numerator, (int, long)): + raise TypeError('numerator must have integer type') + if not isinstance(denominator, (int, long)): + raise TypeError('denominator must have integer type') + if not denominator: + raise ZeroDivisionError('rational construction') + # Store the fraction in reduced form as _n/_d + factor = _gcf(numerator, denominator) + self._n = numerator // factor + self._d = denominator // factor + if self._d < 0: + self._n = -self._n + self._d = -self._d + def numerator(self): + return self._n + + def denominator(self): + return self._d + + def __repr__(self): + if self._d == 1: + return "Rational(%d)" % self._n + else: + return "Rational(%d, %d)" % (self._n, self._d) + def __str__(self): + if self._d == 1: + return str(self._n) + else: + return "%d/%d" % (self._n, self._d) + def __hash__(self): + try: + return hash(float(self)) + except OverflowError: + return hash(long(self)) + def __float__(self): + return self._n / self._d + def __int__(self): + if self._n < 0: + return -int(-self._n // self._d) + else: + return int(self._n // self._d) + def __long__(self): + return long(int(self)) + def __nonzero__(self): + return bool(self._n) + def __pos__(self): + return self + def __neg__(self): + return Rational(-self._n, self._d) + def __abs__(self): + if self._n < 0: + return -self + else: + return self + def __add__(self, other): + if isinstance(other, Rational): + return Rational(self._n * other._d + self._d * other._n, + self._d * other._d) + elif isinstance(other, (int, long)): + return Rational(self._n + self._d * other, self._d) + elif isinstance(other, (float, complex)): + return float(self) + other + elif isinstance(other, _decimal.Decimal): + return self.decimal() + other + else: + return NotImplemented + __radd__ = __add__ + def __sub__(self, other): + if isinstance(other, Rational): + return Rational(self._n * other._d - self._d * other._n, + self._d * other._d) + elif isinstance(other, (int, long)): + return Rational(self._n - self._d * other, self._d) + elif isinstance(other, (float, complex)): + return float(self) - other + elif isinstance(other, _decimal.Decimal): + return self.decimal() - other + else: + return NotImplemented + def __rsub__(self, other): + if isinstance(other, (int, long)): + return Rational(other * self._d - self._n, self._d) + elif isinstance(other, (float, complex)): + return other - float(self) + elif isinstance(other, _decimal.Decimal): + return other - self.decimal() + else: + return NotImplemented + def __mul__(self, other): + if isinstance(other, Rational): + return Rational(self._n * other._n, self._d * other._d) + elif isinstance(other, (int, long)): + return Rational(self._n * other, self._d) + elif isinstance(other, (float, complex)): + return float(self) * other + elif isinstance(other, _decimal.Decimal): + return self.decimal() * other + else: + return NotImplemented + __rmul__ = __mul__ + def __truediv__(self, other): + if isinstance(other, Rational): + return Rational(self._n * other._d, self._d * other._n) + elif isinstance(other, (int, long)): + return Rational(self._n, self._d * other) + elif isinstance(other, (float, complex)): + return float(self) / other + elif isinstance(other, _decimal.Decimal): + return self.decimal() / other + else: + return NotImplemented + __div__ = __truediv__ + def __rtruediv__(self, other): + if isinstance(other, (int, long)): + return Rational(other * self._d, self._n) + elif isinstance(other, (float, complex)): + return other / float(self) + elif isinstance(other, _decimal.Decimal): + return other / self.decimal() + else: + return NotImplemented + __rdiv__ = __rtruediv__ + def __floordiv__(self, other): + truediv = self / other + if isinstance(truediv, Rational): + return truediv._n // truediv._d + else: + return truediv // 1 + def __rfloordiv__(self, other): + return (other / self) // 1 + def __mod__(self, other): + return self - self // other * other + def __rmod__(self, other): + return other - other // self * self + def __divmod__(self, other): + return self // other, self % other + def __cmp__(self, other): + if other == 0: + return cmp(self._n, 0) + else: + return cmp(self - other, 0) + def __pow__(self, other): + if isinstance(other, (int, long)): + if other < 0: + return Rational(self._d ** -other, self._n ** -other) + else: + return Rational(self._n ** other, self._d ** other) + else: + return float(self) ** other + def __rpow__(self, other): + return other ** float(self) + def decimal(self): + """Return a Decimal approximation of self in the current context.""" + return _decimal.Decimal(self._n) / _decimal.Decimal(self._d) + def round(self, denominator): + """Return self rounded to nearest multiple of 1/denominator.""" + int_part, frac_part = divmod(self * denominator, 1) + round_direction = cmp(frac_part * 2, 1) + if round_direction == 0: + numerator = int_part + (int_part & 1) # round to even + elif round_direction < 0: + numerator = int_part + else: + numerator = int_part + 1 + return Rational(numerator, denominator) + @staticmethod + def from_exact_float(x): + """Returns the exact Rational equivalent of x.""" + mantissa, exponent = _math.frexp(x) + mantissa = int(mantissa * 2 ** 53) + exponent -= 53 + if exponent < 0: + return Rational(mantissa, 2 ** (-exponent)) + else: + return Rational(mantissa * 2 ** exponent) + @staticmethod + def from_exact_decimal(x): + """Returns the exact Rational equivalent of x.""" + sign, mantissa, exponent = x.as_tuple() + sign = (1, -1)[sign] + mantissa = sign * reduce(lambda a, b: 10 * a + b, mantissa) + if exponent < 0: + return Rational(mantissa, 10 ** (-exponent)) + else: + return Rational(mantissa * 10 ** exponent) + @staticmethod + def approx_smallest_denominator(x, tolerance): + """ + Returns a Rational approximation of x. + Minimizes the denominator given a constraint on the error. + + x = the float or Decimal value to convert + tolerance = maximum absolute error allowed, + must be of the same type as x + """ + tolerance = abs(tolerance) + n = 1 + while True: + m = int(round(x * n)) + result = Rational(m, n) + if abs(result - x) < tolerance: + return result + n += 1 + @staticmethod + def approx_smallest_error(x, maxDenominator): + """ + Returns a Rational approximation of x. + Minimizes the error given a constraint on the denominator. + + x = the float or Decimal value to convert + maxDenominator = maximum denominator allowed + """ + result = None + minError = x + for n in xrange(1, maxDenominator + 1): + m = int(round(x * n)) + r = Rational(m, n) + error = abs(r - x) + if error == 0: + return r + elif error < minError: + result = r + minError = error + return result + +def divide(x, y): + """Same as x/y, but returns a Rational if both are ints.""" + if isinstance(x, (int, long)) and isinstance(y, (int, long)): + return Rational(x, y) + else: + return x / y + diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py new file mode 100644 index 0000000000..ee8f84d1b6 --- /dev/null +++ b/scripts/musicxml2ly.py @@ -0,0 +1,141 @@ +import sys +import re +import os + +datadir = '@local_lilypond_datadir@' +if not os.path.isdir (datadir): + datadir = '@lilypond_datadir@' +if os.environ.has_key ('LILYPONDPREFIX'): + datadir = os.environ['LILYPONDPREFIX'] + while datadir[-1] == os.sep: + datadir = datadir[:-1] + +if os.path.exists (os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/')): + datadir = os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/') + +sys.path.insert (0, os.path.join (datadir, 'python')) + +import musicxml +import musicexp +from rational import Rational + +def musicxml_duration_to_lily (mxl_note): + d = musicexp.Duration () + if mxl_note.get_maybe_exist_typed_child (musicxml.Type): + d.duration_log = mxl_note.get_duration_log () + else: + d.factor = mxl_note._duration + d.duration_log = 0 + + d.dots = len (mxl_note.get_typed_children (musicxml.Dot)) + d.factor = mxl_note._duration / d.get_length () + + return d + +def musicxml_voice_to_lily_voice (voice): + + ly_voice = [] + ly_now = Rational (0) + for n in voice: + if not n.__class__.__name__ == 'Note': + print 'not a note?' + continue + + 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 + elif n.get_maybe_exist_typed_child (musicxml.Rest): + event = musicexp.RestEvent() + + event.duration = musicxml_duration_to_lily (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 + 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_now = n._when + + ly_voice.append (musicexp.EventChord()) + else: + pass + #print 'append' + ev_chord = ly_voice[-1] + ev_chord.elements.append (event) + + + seq_music = musicexp.SequentialMusic() + + + seq_musicexp.elements = ly_voice + return seq_music + + +def musicxml_id_to_lily (id): + digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', + 'nine', 'ten'] + + for dig in digits: + d = digits.index (dig) + 1 + dig = dig[0].upper() + dig[1:] + id = re.sub ('%d' % d, dig, id) + + id = re.sub ('[^a-zA-Z]', 'X', id) + return id + + +def musicxml_pitch_to_lily (mxl_pitch): + p = musicexp.Pitch() + p.alteration = mxl_pitch.get_alteration () + p.step = (ord (mxl_pitch.get_step ()) - ord ('A') + 7 - 2) % 7 + p.octave = mxl_pitch.get_octave () -4 + return p + +def get_all_voices (parts): + all_voices = {} + for p in parts: + p.interpret () + p.extract_voices () + voice_dict = p.get_voices () + + for (id, voice) in voice_dict.items (): + m = musicxml_voice_to_lily_voice (voice) + m_name = 'Part' + p.name + 'Voice' + id + m_name = musicxml_id_to_lily (m_name) + all_voices[m_name] = m + + return all_voices + +printer = musicexp.Output_printer() + + +tree = musicxml.read_musicxml (sys.argv[1]) +parts = tree.get_typed_children (musicxml.Part) + +voices = get_all_voices (parts) +for (k,v) in voices.items(): + print '%s = \n' % k + v.print_ly (printer.dump) + printer.newline() + + -- 2.39.5