]> git.donarmstrong.com Git - lilypond.git/commitdiff
*** empty log message ***
authorHan-Wen Nienhuys <hanwen@xs4all.nl>
Sat, 3 Dec 2005 11:43:07 +0000 (11:43 +0000)
committerHan-Wen Nienhuys <hanwen@xs4all.nl>
Sat, 3 Dec 2005 11:43:07 +0000 (11:43 +0000)
Documentation/topdocs/NEWS.tely
python/musicexp.py [new file with mode: 0644]
python/musicxml.py [new file with mode: 0644]
python/rational.py [new file with mode: 0644]
scripts/musicxml2ly.py [new file with mode: 0644]

index 007aa8be501344df8dcbc47b7b7afd8c7d90a9d3..efc05d37ffb009d093ee3869332c00eed51fd3e1 100644 (file)
@@ -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 (file)
index 0000000..76f9078
--- /dev/null
@@ -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 (file)
index 0000000..9537f9a
--- /dev/null
@@ -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 (file)
index 0000000..fc6fd16
--- /dev/null
@@ -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 (file)
index 0000000..ee8f84d
--- /dev/null
@@ -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()
+       
+