1#!@TARGET_PYTHON@
2# -*- coding: utf-8 -*-
3#
4# This file is part of LilyPond, the GNU music typesetter.
5#
6# Copyright (C) 2005--2021  Han-Wen Nienhuys <hanwen@xs4all.nl>,
7#                           Jan Nieuwenhuizen <janneke@gnu.org>,
8#                           Reinhold Kainhofer <reinhold@kainhofer.com>,
9#                           Patrick L. Schmidt <pls@philomelos.net>
10#
11# LilyPond is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# LilyPond is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
23
24
25from collections import OrderedDict
26from fractions import Fraction
27from functools import reduce
28import gettext
29import io
30import optparse
31import os
32import re
33import sys
34import tempfile
35import warnings
36import zipfile
37
38"""
39@relocate-preamble@
40"""
41
42import musicexp
43import musicxml
44import musicxml2ly_conversion
45import utilities
46
47# Load translation and install _() into Python's builtins namespace.
48gettext.install('lilypond', '@localedir@')
49
50import lilylib as ly
51
52lilypond_version = "@TOPLEVEL_VERSION@"
53
54# Store command-line options in a global variable, so we can access them everywhere
55options = None
56
57
58class Conversion_Settings:
59    def __init__(self):
60        self.ignore_beaming = False
61        self.convert_stem_directions = False
62        self.convert_rest_positions = True
63
64
65conversion_settings = Conversion_Settings()
66# Use a global variable to store the setting needed inside a \layout block.
67# whenever we need to change a setting or add/remove an engraver, we can access
68# this layout and add the corresponding settings
69layout_information = musicexp.Layout()
70# Use a global variable to store the setting needed inside a \paper block.
71paper = musicexp.Paper()
72
73needed_additional_definitions = []
74additional_definitions = {
75    "tuplet-note-wrapper": """      % a formatter function, which is simply a wrapper around an existing
76      % tuplet formatter function. It takes the value returned by the given
77      % function and appends a note of given length.
78  #(define-public ((tuplet-number::append-note-wrapper function note) grob)
79    (let* ((txt (if function (function grob) #f)))
80      (if txt
81        (markup txt #:fontsize -5 #:note note UP)
82        (markup #:fontsize -5 #:note note UP)
83      )
84    )
85  )""",
86
87    "tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob)
88  (number->string (if denominator
89                      denominator
90                      (ly:event-property (event-cause grob) 'denominator))))
91""",
92
93    "tuplet-non-default-fraction": """#(define ((tuplet-number::non-default-tuplet-fraction-text denominator numerator) grob)
94    (let* ((ev (event-cause grob))
95           (den (if denominator denominator (ly:event-property ev 'denominator)))
96           (num (if numerator numerator (ly:event-property ev 'numerator))))
97       (format #f "~a:~a" den num)))
98""",
99}
100
101
102def round_to_two_digits(val):
103    return round(val * 100) / 100
104
105
106def extract_paper_information(score_partwise):
107    defaults = score_partwise.get_maybe_exist_named_child('defaults')
108    if not defaults:
109        return None
110    tenths = -1
111    scaling = defaults.get_maybe_exist_named_child('scaling')
112    default_tenths_to_millimeters_ratio = 0.175
113    default_staff_size = 20
114    if scaling:
115        mm = scaling.get_named_child('millimeters')
116        mm = float(mm.get_text())
117        tn = scaling.get_maybe_exist_named_child('tenths')
118        tn = float(tn.get_text())
119        # The variable 'tenths' is actually a ratio, NOT the value of <tenths>.
120        # TODO: rename and replace.
121        tenths = mm / tn
122        ratio = tenths / default_tenths_to_millimeters_ratio
123        staff_size = default_staff_size * ratio
124
125        if 1 < staff_size < 100:
126            paper.global_staff_size = staff_size
127        else:
128            msg = "paper.global_staff_size %s is too large, using defaults=20" % staff_size
129            warnings.warn(msg)
130            paper.global_staff_size = 20
131
132    # We need the scaling(i.e. the size of staff tenths for everything!
133    if tenths < 0:
134        return None
135
136    def from_tenths(txt):
137        return round_to_two_digits(float(txt) * tenths / 10)
138
139    def set_paper_variable(varname, parent, element_name):
140        el = parent.get_maybe_exist_named_child(element_name)
141        if el:  # Convert to cm from tenths
142            setattr(paper, varname, from_tenths(el.get_text()))
143
144    pagelayout = defaults.get_maybe_exist_named_child('page-layout')
145    if pagelayout:
146        # TODO: How can one have different margins for even and odd pages???
147        set_paper_variable("page_height", pagelayout, 'page-height')
148        set_paper_variable("page_width", pagelayout, 'page-width')
149
150        if conversion_settings.convert_page_margins:
151            pmargins = pagelayout.get_named_children('page-margins')
152            for pm in pmargins:
153                set_paper_variable("left_margin", pm, 'left-margin')
154                set_paper_variable("right_margin", pm, 'right-margin')
155                set_paper_variable("bottom_margin", pm, 'bottom-margin')
156                set_paper_variable("top_margin", pm, 'top-margin')
157
158    systemlayout = defaults.get_maybe_exist_named_child('system-layout')
159    if systemlayout:
160        sl = systemlayout.get_maybe_exist_named_child('system-margins')
161        if sl:
162            set_paper_variable("system_left_margin", sl, 'left-margin')
163            set_paper_variable("system_right_margin", sl, 'right-margin')
164        set_paper_variable("system_distance", systemlayout, 'system-distance')
165        set_paper_variable("top_system_distance",
166                           systemlayout, 'top-system-distance')
167
168    stafflayout = defaults.get_named_children('staff-layout')
169    for sl in stafflayout:
170        nr = getattr(sl, 'number', 1)
171        dist = sl.get_named_child('staff-distance')
172        # TODO: the staff distance needs to be set in the Staff context!!!
173
174    # TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
175    appearance = defaults.get_named_child('appearance')
176    if appearance:
177        lws = appearance.get_named_children('line-width')
178        for lw in lws:
179            # Possible types are: beam, bracket, dashes,
180            #    enclosure, ending, extend, heavy barline, leger,
181            #    light barline, octave shift, pedal, slur middle, slur tip,
182            #    staff, stem, tie middle, tie tip, tuplet bracket, and wedge
183            tp = lw.type
184            w = from_tenths(lw.get_text())
185            # TODO: Do something with these values!
186        nss = appearance.get_named_children('note-size')
187        for ns in nss:
188            # Possible types are: cue, grace and large
189            tp = ns.type
190            sz = from_tenths(ns.get_text())
191            # TODO: Do something with these values!
192        # <other-appearance> elements have no specified meaning
193
194    rawmusicfont = defaults.get_named_child('music-font')
195    if rawmusicfont:
196        # TODO: Convert the font
197        pass
198    rawwordfont = defaults.get_named_child('word-font')
199    if rawwordfont:
200        # TODO: Convert the font
201        pass
202    rawlyricsfonts = defaults.get_named_children('lyric-font')
203    for lyricsfont in rawlyricsfonts:
204        # TODO: Convert the font
205        pass
206
207    return paper
208
209
210credit_dict = {
211    None: None,
212    '': None,
213    'page number': None,  # TODO: what is it used for ?
214    'title': 'title',
215    'subtitle': 'subtitle',
216    'composer': 'composer',
217    'arranger': 'arranger',
218    'lyricist': 'poet',
219    'rights': 'copyright'
220}
221# score information is contained in the <work>, <identification> or <movement-title> tags
222# extract those into a hash, indexed by proper lilypond header attributes
223
224
225def extract_score_information(tree):
226    header = musicexp.Header()
227
228    def set_if_exists(field, value):
229        if value:
230            header.set_field(field, utilities.escape_ly_output_string(value))
231
232    movement_title = tree.get_maybe_exist_named_child('movement-title')
233    movement_number = tree.get_maybe_exist_named_child('movement-number')
234    if movement_title:
235        set_if_exists('title', movement_title.get_text())
236    if movement_number:
237        set_if_exists('movementnumber', movement_number.get_text())
238        # set_if_exists('piece', movement_number.get_text()) # the movement number should be visible in the score.
239
240    work = tree.get_maybe_exist_named_child('work')
241    if work:
242        work_number = work.get_work_number()
243        work_title = work.get_work_title()
244        # Overwrite the title from movement-title with work->title
245        set_if_exists('title', work.get_work_title())
246        set_if_exists('opus', work.get_work_number())
247        # Use movement-title as subtitle
248        if movement_title:
249            set_if_exists('subtitle', movement_title.get_text())
250
251# TODO: Translation of opus element. Not to be confused with opus in LilyPond. MusicXML opus is a document element for opus DTD
252    identifications = tree.get_named_children('identification')
253    for ids in identifications:
254        set_if_exists('copyright', ids.get_rights())
255        set_if_exists('composer', ids.get_composer())
256        set_if_exists('arranger', ids.get_arranger())
257        set_if_exists('editor', ids.get_editor())
258        set_if_exists('poet', ids.get_poet())
259
260        set_if_exists('encodingsoftware', ids.get_encoding_software())
261        set_if_exists('encodingdate', ids.get_encoding_date())
262        set_if_exists('encoder', ids.get_encoding_person())
263        set_if_exists('encodingdescription', ids.get_encoding_description())
264        set_if_exists('source', ids.get_source())
265
266        # <miscellaneous><miscellaneous-field name="description"> ... becomes
267        # \header { texidoc = ...
268        set_if_exists('texidoc', ids.get_file_description())
269
270        # Finally, apply the required compatibility modes
271        # Some applications created wrong MusicXML files, so we need to
272        # apply some compatibility mode, e.g. ignoring some features/tags
273        # in those files
274        software = ids.get_encoding_software_list()
275
276        # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
277        #         is missing all beam ends => ignore all beaming information
278        ignore_beaming_software = {
279            "Dolet 4 for Sibelius, Beta 2": "Dolet 4 for Sibelius, Beta 2",
280            "Dolet 3.5 for Sibelius": "Dolet 3.5 for Sibelius",
281            "Dolet 3.4 for Sibelius": "Dolet 3.4 for Sibelius",
282            "Dolet 3.3 for Sibelius": "Dolet 3.3 for Sibelius",
283            "Dolet 3.2 for Sibelius": "Dolet 3.2 for Sibelius",
284            "Dolet 3.1 for Sibelius": "Dolet 3.1 for Sibelius",
285            "Dolet for Sibelius 1.3": "Dolet for Sibelius 1.3",
286            "Noteworthy Composer": "Noteworthy Composer's nwc2xm[",
287        }
288        for s in software:
289            app_description = ignore_beaming_software.get(s, False)
290            if app_description:
291                conversion_settings.ignore_beaming = True
292                ly.warning(_("Encountered file created by %s, containing "
293                             "wrong beaming information. All beaming "
294                             "information in the MusicXML file will be "
295                             "ignored") % app_description)
296
297    credits = tree.get_named_children('credit')
298    has_composer = False
299    for cred in credits:
300        type = credit_dict.get(cred.get_type())
301        if type is None:
302            type = credit_dict.get(cred.find_type(credits))
303        if type == 'composer':
304            if has_composer:
305                type = 'poet'
306            else:
307                has_composer = True
308            set_if_exists(type, cred.get_text())
309        elif type == 'title':
310            if not work and not movement_title:
311                set_if_exists('title', cred.get_text())
312            # elif(not(movement_title)): #bullshit!
313            #    set_if_exists('subtitle', cred.get_text()) #bullshit! otherwise both title and subtitle show the work-title.
314        elif type is None:
315            pass
316        else:
317            set_if_exists(type, cred.get_text())
318
319    # TODO: Check for other unsupported features
320    return header
321
322
323class PartGroupInfo:
324    def __init__(self):
325        self.start = {}
326        self.end = {}
327
328    def is_empty(self):
329        return len(self.start) + len(self.end) == 0
330
331    def add_start(self, g):
332        self.start[getattr(g, 'number', "1")] = g
333
334    def add_end(self, g):
335        self.end[getattr(g, 'number', "1")] = g
336
337    def print_ly(self, printer):
338        ly.warning(_("Unprocessed PartGroupInfo %s encountered") % self)
339
340    def ly_expression(self):
341        ly.warning(_("Unprocessed PartGroupInfo %s encountered") % self)
342        return ''
343
344
345def staff_attributes_to_string_tunings(mxl_attr):
346    details = mxl_attr.get_maybe_exist_named_child('staff-details')
347    if not details:
348        return []
349    lines = 6
350    staff_lines = details.get_maybe_exist_named_child('staff-lines')
351    if staff_lines:
352        lines = int(staff_lines.get_text())
353    tunings = [musicexp.Pitch()] * lines
354    staff_tunings = details.get_named_children('staff-tuning')
355    for i in staff_tunings:
356        p = musicexp.Pitch()
357        line = 0
358        try:
359            line = int(i.line) - 1
360        except ValueError:
361            pass
362        tunings[line] = p
363
364        step = i.get_named_child('tuning-step')
365        step = step.get_text().strip()
366        p.step = musicxml2ly_conversion.musicxml_step_to_lily(step)
367
368        octave = i.get_named_child('tuning-octave')
369        octave = octave.get_text().strip()
370        p.octave = int(octave) - 4
371
372        alter = i.get_named_child('tuning-alter')
373        if alter:
374            p.alteration = int(alter.get_text().strip())
375    # lilypond seems to use the opposite ordering than MusicXML...
376    tunings.reverse()
377    return tunings
378
379
380def staff_attributes_to_lily_staff(mxl_attr):
381    if not mxl_attr:
382        return musicexp.Staff()
383
384    (staff_id, attributes) = list(mxl_attr.items())[0]
385
386    # distinguish by clef:
387    # percussion(percussion and rhythmic), tab, and everything else
388    clef_sign = None
389    clef = attributes.get_maybe_exist_named_child('clef')
390    if clef:
391        sign = clef.get_maybe_exist_named_child('sign')
392        if sign:
393            clef_sign = {"percussion": "percussion",
394                         "TAB": "tab"}.get(sign.get_text(), None)
395
396    lines = 5
397    details = attributes.get_named_children('staff-details')
398    for d in details:
399        staff_lines = d.get_maybe_exist_named_child('staff-lines')
400        if staff_lines:
401            lines = int(staff_lines.get_text())
402
403    # TODO: Handle other staff attributes like staff-space, etc.
404
405    staff = None
406    if clef_sign == "percussion" and lines == 1:
407        staff = musicexp.RhythmicStaff()
408    elif clef_sign == "percussion":
409        staff = musicexp.DrumStaff()
410        # staff.drum_style_table = ???
411    elif clef_sign == "tab":
412        staff = musicexp.TabStaff()
413        staff.string_tunings = staff_attributes_to_string_tunings(attributes)
414        # staff.tablature_format = ???
415    else:
416        staff = musicexp.Staff()
417        # TODO: Handle case with lines != 5!
418        if lines != 5:
419            staff.add_context_modification(
420                "\\override StaffSymbol #'line-count = #%s" % lines)
421
422    return staff
423
424
425def extract_instrument_sound(score_part):
426    score_instrument = score_part.get_maybe_exist_named_child(
427        'score-instrument')
428    if not score_instrument:
429        return None
430    sound = score_instrument.get_maybe_exist_named_child('instrument-sound')
431    if sound:
432        return utilities.musicxml_sound_to_lilypond_midi_instrument(sound.get_text())
433
434
435def extract_score_structure(part_list, staffinfo):
436    score = musicexp.Score()
437    structure = musicexp.StaffGroup(None)
438    score.set_contents(structure)
439
440    if not part_list:
441        return structure
442
443    def read_score_part(el):
444        if not isinstance(el, musicxml.Score_part):
445            return
446        # Depending on the attributes of the first measure, we create different
447        # types of staves(Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
448        staff = staff_attributes_to_lily_staff(staffinfo.get(el.id, None))
449        if not staff:
450            return None
451        staff.id = el.id
452        partname = el.get_maybe_exist_named_child('part-name')
453        # Finale gives unnamed parts the name "MusicXML Part" automatically!
454        if partname and partname.get_text() != "MusicXML Part":
455            staff.instrument_name = partname.get_text()
456        # part-name-display overrides part-name!
457        partname = el.get_maybe_exist_named_child("part-name-display")
458        if partname:
459            staff.instrument_name = extract_display_text(partname)
460        if hasattr(options, 'midi') and options.midi:
461            staff.sound = extract_instrument_sound(el)
462        if staff.instrument_name:
463            paper.indent = max(paper.indent, len(staff.instrument_name))
464            paper.instrument_names.append(staff.instrument_name)
465        partdisplay = el.get_maybe_exist_named_child('part-abbreviation')
466        if partdisplay:
467            staff.short_instrument_name = partdisplay.get_text()
468        # part-abbreviation-display overrides part-abbreviation!
469        partdisplay = el.get_maybe_exist_named_child(
470            "part-abbreviation-display")
471        if partdisplay:
472            staff.short_instrument_name = extract_display_text(partdisplay)
473        # TODO: Read in the MIDI device / instrument
474        if staff.short_instrument_name:
475            paper.short_indent = max(
476                paper.short_indent, len(staff.short_instrument_name))
477
478        return staff
479
480    def read_score_group(el):
481        if not isinstance(el, musicxml.Part_group):
482            return
483        group = musicexp.StaffGroup()
484        if hasattr(el, 'number'):
485            id = el.number
486            group.id = id
487            #currentgroups_dict[id] = group
488            # currentgroups.append(id)
489        if el.get_maybe_exist_named_child('group-name'):
490            group.instrument_name = el.get_maybe_exist_named_child(
491                'group-name').get_text()
492        if el.get_maybe_exist_named_child('group-abbreviation'):
493            group.short_instrument_name = el.get_maybe_exist_named_child(
494                'group-abbreviation').get_text()
495        if el.get_maybe_exist_named_child('group-symbol'):
496            group.symbol = el.get_maybe_exist_named_child(
497                'group-symbol').get_text()
498        if el.get_maybe_exist_named_child('group-barline'):
499            group.spanbar = el.get_maybe_exist_named_child(
500                'group-barline').get_text()
501        return group
502
503    parts_groups = part_list.get_all_children()
504
505    # the start/end group tags are not necessarily ordered correctly and groups
506    # might even overlap, so we can't go through the children sequentially!
507
508    # 1) Replace all Score_part objects by their corresponding Staff objects,
509    #    also collect all group start/stop points into one PartGroupInfo object
510    staves = []
511    group_info = PartGroupInfo()
512    for el in parts_groups:
513        if isinstance(el, musicxml.Score_part):
514            if not group_info.is_empty():
515                staves.append(group_info)
516                group_info = PartGroupInfo()
517            staff = read_score_part(el)
518            if staff:
519                staves.append(staff)
520        elif isinstance(el, musicxml.Part_group):
521            if el.type == "start":
522                group_info.add_start(el)
523            elif el.type == "stop":
524                group_info.add_end(el)
525    if not group_info.is_empty():
526        staves.append(group_info)
527
528    # 2) Now, detect the groups:
529    group_starts = []
530    pos = 0
531    while pos < len(staves):
532        el = staves[pos]
533        if isinstance(el, PartGroupInfo):
534            prev_start = 0
535            if len(group_starts) > 0:
536                prev_start = group_starts[-1]
537            elif len(el.end) > 0:  # no group to end here
538                el.end = {}
539            if len(el.end) > 0:  # closes an existing group
540                ends = list(el.end.keys())
541                prev_started = list(staves[prev_start].start.keys())
542                grpid = None
543                intersection = [x for x in prev_started if x in ends]
544                if len(intersection) > 0:
545                    grpid = intersection[0]
546                else:
547                    # Close the last started group
548                    grpid = list(staves[prev_start].start.keys())[0]
549                    # Find the corresponding closing tag and remove it!
550                    j = pos + 1
551                    foundclosing = False
552                    while j < len(staves) and not foundclosing:
553                        if isinstance(staves[j], PartGroupInfo) and grpid in staves[j].end:
554                            foundclosing = True
555                            del staves[j].end[grpid]
556                            if staves[j].is_empty():
557                                del staves[j]
558                        j += 1
559                grpobj = staves[prev_start].start[grpid]
560                group = read_score_group(grpobj)
561                # remove the id from both the start and end
562                if grpid in el.end:
563                    del el.end[grpid]
564                del staves[prev_start].start[grpid]
565                if el.is_empty():
566                    del staves[pos]
567                # replace the staves with the whole group
568                for j in staves[(prev_start + 1):pos]:
569                    group.append_staff(j)
570                del staves[(prev_start + 1):pos]
571                staves.insert(prev_start + 1, group)
572                # reset pos so that we continue at the correct position
573                pos = prev_start
574                # remove an empty start group
575                if staves[prev_start].is_empty():
576                    del staves[prev_start]
577                    group_starts.remove(prev_start)
578                    pos -= 1
579            elif len(el.start) > 0:  # starts new part groups
580                group_starts.append(pos)
581        pos += 1
582
583    for i in staves:
584        structure.append_staff(i)
585    return score
586
587
588def musicxml_partial_to_lily(partial_len):
589    if partial_len > 0:
590        p = musicexp.Partial()
591        p.partial = musicxml2ly_conversion.rational_to_lily_duration(
592            partial_len)
593        return p
594    else:
595        return None
596
597# Detect repeats and alternative endings in the chord event list(music_list)
598# and convert them to the corresponding musicexp objects, containing nested
599# music
600
601
602def group_repeats(music_list):
603    repeat_replaced = True
604    music_start = 0
605    i = 0
606    # Walk through the list of expressions, looking for repeat structure
607    # (repeat start/end, corresponding endings). If we find one, try to find the
608    # last event of the repeat, replace the whole structure and start over again.
609    # For nested repeats, as soon as we encounter another starting repeat bar,
610    # treat that one first, and start over for the outer repeat.
611    while repeat_replaced and i < 100:
612        i += 1
613        repeat_start = -1  # position of repeat start / end
614        repeat_end = -1  # position of repeat start / end
615        repeat_times = 0
616        ending_start = -1  # position of current ending start
617        endings = []  # list of already finished endings
618        pos = 0
619        last = len(music_list) - 1
620        repeat_replaced = False
621        final_marker = 0
622        while pos < len(music_list) and not repeat_replaced:
623            e = music_list[pos]
624            repeat_finished = False
625            if isinstance(e, musicxml2ly_conversion.RepeatMarker):
626                if not repeat_times and e.times:
627                    repeat_times = e.times
628                if e.direction == -1:
629                    if repeat_end >= 0:
630                        repeat_finished = True
631                    else:
632                        repeat_start = pos
633                        repeat_end = -1
634                        ending_start = -1
635                        endings = []
636                elif e.direction == 1:
637                    if repeat_start < 0:
638                        repeat_start = 0
639                    if repeat_end < 0:
640                        repeat_end = pos
641                    final_marker = pos
642            elif isinstance(e, musicxml2ly_conversion.EndingMarker):
643                if e.direction == -1:
644                    if repeat_start < 0:
645                        repeat_start = 0
646                    if repeat_end < 0:
647                        repeat_end = pos
648                    ending_start = pos
649                elif e.direction == 1:
650                    if ending_start < 0:
651                        ending_start = 0
652                    endings.append([ending_start, pos])
653                    ending_start = -1
654                    final_marker = pos
655            elif not isinstance(e, musicexp.BarLine):
656                # As soon as we encounter an element when repeat start and end
657                # is set and we are not inside an alternative ending,
658                # this whole repeat structure is finished => replace it
659                if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
660                    repeat_finished = True
661
662            # Finish off all repeats without explicit ending bar(e.g. when
663            # we convert only one page of a multi-page score with repeats)
664            if pos == last and repeat_start >= 0:
665                repeat_finished = True
666                final_marker = pos
667                if repeat_end < 0:
668                    repeat_end = pos
669                if ending_start >= 0:
670                    endings.append([ending_start, pos])
671                    ending_start = -1
672
673            if repeat_finished:
674                # We found the whole structure replace it!
675                r = musicexp.RepeatedMusic()
676                if repeat_times <= 0:
677                    repeat_times = 2
678                r.repeat_count = repeat_times
679                # don't erase the first element for "implicit" repeats(i.e. no
680                # starting repeat bars at the very beginning)
681                start = repeat_start + 1
682                if repeat_start == music_start:
683                    start = music_start
684                r.set_music(music_list[start:repeat_end])
685                for(start, end) in endings:
686                    s = musicexp.SequentialMusic()
687                    s.elements = music_list[start + 1:end]
688                    r.add_ending(s)
689                del music_list[repeat_start:final_marker + 1]
690                music_list.insert(repeat_start, r)
691                repeat_replaced = True
692            pos += 1
693        # TODO: Implement repeats until the end without explicit ending bar
694    return music_list
695
696
697# Extract the settings for tuplets from the <notations><tuplet> and the
698# <time-modification> elements of the note:
699def musicxml_tuplet_to_lily(tuplet_elt, time_modification):
700    tsm = musicexp.TimeScaledMusic()
701    fraction = (1, 1)
702    if time_modification:
703        fraction = time_modification.get_fraction()
704    tsm.numerator = fraction[0]
705    tsm.denominator = fraction[1]
706
707    normal_type = tuplet_elt.get_normal_type()
708    if not normal_type and time_modification:
709        normal_type = time_modification.get_normal_type()
710    if not normal_type and time_modification:
711        note = time_modification.get_parent()
712        if note:
713            normal_type = note.get_duration_info()
714    if normal_type:
715        normal_note = musicexp.Duration()
716        (normal_note.duration_log, normal_note.dots) = normal_type
717        tsm.normal_type = normal_note
718
719    actual_type = tuplet_elt.get_actual_type()
720    if actual_type:
721        actual_note = musicexp.Duration()
722        (actual_note.duration_log, actual_note.dots) = actual_type
723        tsm.actual_type = actual_note
724
725    # Obtain non-default nrs of notes from the tuplet object!
726    tsm.display_numerator = tuplet_elt.get_normal_nr()
727    tsm.display_denominator = tuplet_elt.get_actual_nr()
728
729    if hasattr(tuplet_elt, 'bracket') and tuplet_elt.bracket == "no":
730        tsm.display_bracket = None
731    elif hasattr(tuplet_elt, 'line-shape') and getattr(tuplet_elt, 'line-shape') == "curved":
732        tsm.display_bracket = "curved"
733    else:
734        tsm.display_bracket = "bracket"
735
736    display_values = {"none": None, "actual": "actual", "both": "both"}
737    if hasattr(tuplet_elt, "show-number"):
738        tsm.display_number = display_values.get(
739            getattr(tuplet_elt, "show-number"), "actual")
740
741    if hasattr(tuplet_elt, "show-type"):
742        tsm.display_type = display_values.get(
743            getattr(tuplet_elt, "show-type"), None)
744
745    return tsm
746
747
748def group_tuplets(music_list, events):
749    """Collect Musics from
750    MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
751    """
752
753    indices = []
754    brackets = {}
755
756    j = 0
757    for(ev_chord, tuplet_elt, time_modification) in events:
758        while j < len(music_list):
759            if music_list[j] == ev_chord:
760                break
761            j += 1
762        nr = 0
763        if hasattr(tuplet_elt, 'number'):
764            nr = getattr(tuplet_elt, 'number')
765        if tuplet_elt.type == 'start':
766            tuplet_object = musicxml_tuplet_to_lily(
767                tuplet_elt, time_modification)
768            tuplet_info = [j, None, tuplet_object]
769            indices.append(tuplet_info)
770            brackets[nr] = tuplet_info
771        elif tuplet_elt.type == 'stop':
772            bracket_info = brackets.get(nr, None)
773            if bracket_info:
774                bracket_info[1] = j  # Set the ending position to j
775                del brackets[nr]
776
777    new_list = []
778    last = 0
779    for(i1, i2, tsm) in indices:
780        if i1 > i2:
781            continue
782
783        new_list.extend(music_list[last:i1])
784        seq = musicexp.SequentialMusic()
785        last = i2 + 1
786
787        # At this point music_list[i1:last] encompasses all the notes of the
788        # tuplet. There might be dynamics following this range, however, which
789        # apply to the last note of the tuplet. Advance last to include them
790        # in the range.
791        while last < len(music_list) and isinstance(music_list[last], musicexp.DynamicsEvent):
792            last += 1
793
794        seq.elements = music_list[i1:last]
795
796        tsm.element = seq
797
798        new_list.append(tsm)
799        # TODO: Handle nested tuplets!!!!
800
801    new_list.extend(music_list[last:])
802    return new_list
803
804
805def musicxml_clef_to_lily(attributes):
806    change = musicexp.ClefChange()
807    (change.type, change.position, change.octave) = attributes.get_clef_information()
808    return change
809
810
811def musicxml_time_to_lily(attributes):
812    change = musicexp.TimeSignatureChange()
813    # time signature function
814    if hasattr(options, 'shift_meter') and options.shift_meter:
815        tmp_meter = options.shift_meter.split("/", 1)
816        sig = [int(tmp_meter[0]), int(tmp_meter[1])]
817        change.originalFractions = attributes.get_time_signature()
818    else:
819        sig = attributes.get_time_signature()
820    if not sig:
821        return None
822    change.fractions = sig
823
824    time_elm = attributes.get_maybe_exist_named_child('time')
825    if time_elm and hasattr(time_elm, 'symbol'):
826        change.style = {'single-number': "'single-digit",
827                        'cut': None,
828                        'common': None,
829                        'normal': "'()"}.get(time_elm.symbol, "'()")
830    else:
831        change.style = "'()"
832
833    if getattr(time_elm, 'print-object', 'yes') == 'no':
834        change.visible = False
835
836    # TODO: Handle senza-misura measures
837    # TODO: What shall we do if the symbol clashes with the sig? e.g. "cut"
838    #       with 3/8 or "single-number" with(2+3)/8 or 3/8+2/4?
839    return change
840
841
842def musicxml_key_to_lily(attributes):
843    key_sig = attributes.get_key_signature()
844    if not key_sig or not(isinstance(key_sig, list) or isinstance(key_sig, tuple)):
845        ly.warning(_("Unable to extract key signature!"))
846        return None
847
848    change = musicexp.KeySignatureChange()
849
850    if len(key_sig) == 2 and not isinstance(key_sig[0], list):
851        # standard key signature,(fifths, mode)
852        (fifths, mode) = key_sig
853        change.mode = mode
854
855        start_pitch = musicexp.Pitch()
856        start_pitch.octave = 0
857        try:
858            (n, a) = {
859                'major': (0, 0),
860                'minor': (5, 0),
861                'ionian': (0, 0),
862                'dorian': (1, 0),
863                'phrygian': (2, 0),
864                'lydian': (3, 0),
865                'mixolydian': (4, 0),
866                'aeolian': (5, 0),
867                'locrian': (6, 0),
868            }[mode]
869            start_pitch.step = n
870            start_pitch.alteration = a
871        except KeyError:
872            ly.warning(_("unknown mode %s, expecting 'major' or 'minor' "
873                         "or a church mode!") % mode)
874
875        fifth = musicexp.Pitch()
876        fifth.step = 4
877        if fifths < 0:
878            fifths *= -1
879            fifth.step *= -1
880            fifth.normalize()
881        for x in range(fifths):
882            start_pitch = start_pitch.transposed(fifth)
883        change.tonic = start_pitch
884
885    else:
886        # Non-standard key signature of the form [[step,alter<,octave>],...]
887        # MusicXML contains C,D,E,F,G,A,B as steps, lily uses 0-7, so convert
888        alterations = []
889        for k in key_sig:
890            k[0] = musicxml2ly_conversion.musicxml_step_to_lily(k[0])
891            alterations.append(k)
892        change.non_standard_alterations = alterations
893    return change
894
895
896def musicxml_transpose_to_lily(attributes):
897    transpose = attributes.get_transposition()
898    if not transpose:
899        return None
900
901    shift = musicexp.Pitch()
902    octave_change = transpose.get_maybe_exist_named_child('octave-change')
903    if octave_change:
904        shift.octave = int(octave_change.get_text())
905    chromatic_shift = int(transpose.get_named_child('chromatic').get_text())
906    chromatic_shift_normalized = chromatic_shift % 12
907    (shift.step, shift.alteration) = [
908        (0, 0), (0, 1), (1, 0), (2, -1), (2, 0),
909        (3, 0), (3, 1), (4, 0), (5, -1), (5, 0),
910        (6, -1), (6, 0)][chromatic_shift_normalized]
911
912    shift.octave += (chromatic_shift - chromatic_shift_normalized) // 12
913
914    diatonic = transpose.get_maybe_exist_named_child('diatonic')
915    if diatonic:
916        diatonic_step = int(diatonic.get_text()) % 7
917        if diatonic_step != shift.step:
918            # We got the alter incorrect!
919            old_semitones = shift.semitones()
920            shift.step = diatonic_step
921            new_semitones = shift.semitones()
922            shift.alteration += old_semitones - new_semitones
923
924    transposition = musicexp.Transposition()
925    transposition.pitch = musicexp.Pitch().transposed(shift)
926    return transposition
927
928
929def musicxml_staff_details_to_lily(attributes):
930    details = attributes.get_maybe_exist_named_child('staff-details')
931    if not details:
932        return None
933
934    # TODO: Handle staff-type, staff-lines, staff-tuning, capo, staff-size
935    ret = []
936
937    stafflines = details.get_maybe_exist_named_child('staff-lines')
938    if stafflines:
939        lines = int(stafflines.get_text())
940        lines_event = musicexp.StaffLinesEvent(lines)
941        ret.append(lines_event)
942
943    return ret
944
945
946def musicxml_attributes_to_lily(attrs):
947    elts = []
948    attr_dispatch = [
949        ('clef', musicxml_clef_to_lily),
950        ('time', musicxml_time_to_lily),
951        ('key', musicxml_key_to_lily),
952        ('transpose', musicxml_transpose_to_lily),
953        ('staff-details', musicxml_staff_details_to_lily),
954    ]
955    for (k, func) in attr_dispatch:
956        children = attrs.get_named_children(k)
957        if children:
958            ev = func(attrs)
959            if isinstance(ev, list):
960                for e in ev:
961                    elts.append(e)
962            elif ev:
963                elts.append(ev)
964
965    return elts
966
967
968def extract_display_text(el):
969    children = el.get_typed_children(musicxml.get_class("display-text"))
970    if children:
971        return " ".join([child.get_text() for child in children])
972    else:
973        return False
974
975
976def musicxml_print_to_lily(el):
977    # TODO: Implement other print attributes
978    #  <!ELEMENT print (page-layout?, system-layout?, staff-layout*,
979    #          measure-layout?, measure-numbering?, part-name-display?,
980    #          part-abbreviation-display?)>
981    #  <!ATTLIST print
982    #      staff-spacing %tenths; #IMPLIED
983    #      new-system %yes-no; #IMPLIED
984    #      new-page %yes-no-number; #IMPLIED
985    #      blank-page NMTOKEN #IMPLIED
986    #      page-number CDATA #IMPLIED
987    #  >
988    elts = []
989    if (hasattr(el, "new-system") and conversion_settings.convert_system_breaks):
990        val = getattr(el, "new-system")
991        if val == "yes":
992            elts.append(musicexp.Break("break"))
993    if hasattr(el, "new-page") and conversion_settings.convert_page_breaks:
994        val = getattr(el, "new-page")
995        if val == "yes":
996            elts.append(musicexp.Break("pageBreak"))
997    child = el.get_maybe_exist_named_child("part-name-display")
998    if child:
999        elts.append(musicexp.SetEvent("Staff.instrumentName",
1000                                      "\"%s\"" % extract_display_text(child)))
1001    child = el.get_maybe_exist_named_child("part-abbreviation-display")
1002    if child:
1003        elts.append(musicexp.SetEvent("Staff.shortInstrumentName",
1004                                      "\"%s\"" % extract_display_text(child)))
1005    return elts
1006
1007
1008spanner_event_dict = {
1009    'beam': musicexp.BeamEvent,
1010    'dashes': musicexp.TextSpannerEvent,
1011    'bracket': musicexp.BracketSpannerEvent,
1012    'glissando': musicexp.GlissandoEvent,
1013    'octave-shift': musicexp.OctaveShiftEvent,
1014    'pedal': musicexp.PedalEvent,
1015    'slide': musicexp.GlissandoEvent,
1016    'slur': musicexp.SlurEvent,
1017    'wavy-line': musicexp.TextSpannerEvent,
1018    'wedge': musicexp.HairpinEvent
1019}
1020spanner_type_dict = {
1021    'start': -1,
1022    'begin': -1,
1023    'crescendo': -1,
1024    'decreschendo': -1,
1025    'diminuendo': -1,
1026    'continue': 0,
1027    'change': 0,
1028    'up': -1,
1029    'down': -1,
1030    'stop': 1,
1031    'end': 1
1032}
1033
1034
1035def musicxml_spanner_to_lily_event(mxl_event):
1036    ev = None
1037
1038    name = mxl_event.get_name()
1039    func = spanner_event_dict.get(name)
1040    if func:
1041        ev = func()
1042    else:
1043        ly.warning(_('unknown span event %s') % mxl_event)
1044
1045    if name == "wavy-line":
1046        ev.style = OrnamenthasWhat(mxl_event)
1047
1048    type = mxl_event.get_type()
1049    span_direction = spanner_type_dict.get(type)
1050    # really check for None, because some types will be translated to 0, which
1051    # would otherwise also lead to the unknown span warning
1052    if span_direction is not None:
1053        ev.span_direction = span_direction
1054    else:
1055        ly.warning(_('unknown span type %s for %s') % (type, name))
1056
1057    ev.set_span_type(type)
1058    ev.line_type = getattr(mxl_event, 'line-type', 'solid')
1059
1060    # assign the size, which is used for octave-shift, etc.
1061    ev.size = mxl_event.get_size()
1062
1063    return ev
1064
1065
1066def musicxml_direction_to_indicator(direction):
1067    return {"above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1}.get(direction, 0)
1068
1069
1070def musicxml_fermata_to_lily_event(mxl_event):
1071
1072    ev = musicexp.ArticulationEvent()
1073    txt = mxl_event.get_text()
1074
1075    # The contents of the element defined the shape, possible are normal, angled and square
1076    ev.type = {"angled": "shortfermata",
1077               "square": "longfermata"}.get(txt, "fermata")
1078    fermata_types = {"angled": "shortfermata",
1079                     "square": "longfermata"}
1080
1081    # MusicXML fermata types can be specified in two different ways:
1082    # 1. <fermata>angled</fermata> and
1083    # 2. <fermata type="angled"/> -- both need to be handled.
1084    if hasattr(mxl_event, 'type'):
1085        fermata_type = fermata_types.get(mxl_event.type, 'fermata')
1086    else:
1087        fermata_type = fermata_types.get(mxl_event.get_text(), 'fermata')
1088
1089    ev.type = fermata_type
1090
1091    if hasattr(mxl_event, 'type'):
1092        dir = musicxml_direction_to_indicator(mxl_event.type)
1093        if dir and options.convert_directions:
1094            ev.force_direction = dir
1095    return ev
1096
1097
1098def musicxml_arpeggiate_to_lily_event(mxl_event):
1099    ev = musicexp.ArpeggioEvent()
1100    ev.direction = musicxml_direction_to_indicator(
1101        getattr(mxl_event, 'direction', None))
1102    return ev
1103
1104
1105def musicxml_nonarpeggiate_to_lily_event(mxl_event):
1106    ev = musicexp.ArpeggioEvent()
1107    ev.non_arpeggiate = True
1108    ev.direction = musicxml_direction_to_indicator(
1109        getattr(mxl_event, 'direction', None))
1110    return ev
1111
1112
1113def musicxml_tremolo_to_lily_event(mxl_event):
1114    ev = musicexp.TremoloEvent()
1115    txt = mxl_event.get_text()
1116    if txt:
1117        ev.strokes = txt
1118    else:
1119        # This is supposed to be a default for empty tremolo elements
1120        # TODO: Add empty tremolo element to test cases in tremolo.xml
1121        # TODO: Test empty tremolo element
1122        # TODO: Consideration: Is 3 really a reasonable default?
1123        ev.strokes = "3"
1124    return ev
1125
1126
1127def musicxml_falloff_to_lily_event(mxl_event):
1128    ev = musicexp.BendEvent()
1129    ev.alter = -4
1130    return ev
1131
1132
1133def musicxml_doit_to_lily_event(mxl_event):
1134    ev = musicexp.BendEvent()
1135    ev.alter = 4
1136    return ev
1137
1138
1139def musicxml_bend_to_lily_event(mxl_event):
1140    ev = musicexp.BendEvent()
1141    ev.alter = mxl_event.bend_alter()
1142    return ev
1143
1144
1145def musicxml_caesura_to_lily_event(mxl_event):
1146    ev = musicexp.MarkupEvent()
1147    # FIXME: default to straight or curved caesura?
1148    ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
1149    ev.force_direction = 1
1150    return ev
1151
1152
1153def musicxml_fingering_event(mxl_event):
1154    ev = musicexp.ShortArticulationEvent()
1155    ev.type = mxl_event.get_text()
1156    return ev
1157
1158
1159def musicxml_string_event(mxl_event):
1160    ev = musicexp.NoDirectionArticulationEvent()
1161    ev.type = mxl_event.get_text()
1162    return ev
1163
1164
1165def musicxml_accidental_mark(mxl_event):
1166    ev = musicexp.MarkupEvent()
1167    contents = {"sharp": "\\sharp",
1168                "natural": "\\natural",
1169                "flat": "\\flat",
1170                "double-sharp": "\\doublesharp",
1171                "sharp-sharp": "\\sharp\\sharp",
1172                "flat-flat": "\\flat\\flat",
1173                "flat-flat": "\\doubleflat",
1174                "natural-sharp": "\\natural\\sharp",
1175                "natural-flat": "\\natural\\flat",
1176                "quarter-flat": "\\semiflat",
1177                "quarter-sharp": "\\semisharp",
1178                "three-quarters-flat": "\\sesquiflat",
1179                "three-quarters-sharp": "\\sesquisharp",
1180                }.get(mxl_event.get_text())
1181    if contents:
1182        ev.contents = contents
1183        return ev
1184    else:
1185        return None
1186
1187
1188# translate articulations, ornaments and other notations into ArticulationEvents
1189# possible values:
1190#   -) string  (ArticulationEvent with that name)
1191#   -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
1192#   -) (class, name)  (like string, only that a different class than ArticulationEvent is used)
1193# TODO: Some translations are missing!
1194articulations_dict = {
1195    "accent": (musicexp.ShortArticulationEvent, ">"),  # or "accent"
1196    "accidental-mark": musicxml_accidental_mark,
1197    "bend": musicxml_bend_to_lily_event,
1198    "breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
1199    "caesura": musicxml_caesura_to_lily_event,
1200    # "delayed-turn": "?",
1201    "detached-legato": (musicexp.ShortArticulationEvent, "_"),  # or "portato"
1202    "doit": musicxml_doit_to_lily_event,
1203    # "double-tongue": "?",
1204    "down-bow": "downbow",
1205    "falloff": musicxml_falloff_to_lily_event,
1206    "fingering": musicxml_fingering_event,
1207    # "fingernails": "?",
1208    # "fret": "?",
1209    # "hammer-on": "?",
1210    "harmonic": "flageolet",
1211    # "heel": "?",
1212    "inverted-mordent": "prall",
1213    "inverted-turn": "reverseturn",
1214    "mordent": "mordent",
1215    "open-string": "open",
1216    # "plop": "?",
1217    # "pluck": "?",
1218    # "pull-off": "?",
1219    # "schleifer": "?",
1220    # "scoop": "?",
1221    # "shake": "?",
1222    "snap-pizzicato": "snappizzicato",
1223    # "spiccato": "?",
1224    # or "staccatissimo"
1225    "staccatissimo": (musicexp.ShortArticulationEvent, "!"),
1226    "staccato": (musicexp.ShortArticulationEvent, "."),  # or "staccato"
1227    "stopped": (musicexp.ShortArticulationEvent, "+"),  # or "stopped"
1228    # "stress": "?",
1229    "string": musicxml_string_event,
1230    "strong-accent": (musicexp.ShortArticulationEvent, "^"),  # or "marcato"
1231    # "tap": "?",
1232    "tenuto": (musicexp.ShortArticulationEvent, "-"),  # or "tenuto"
1233    "thumb-position": "thumb",
1234    # "toe": "?",
1235    "turn": "turn",
1236    "tremolo": musicxml_tremolo_to_lily_event,
1237    "trill-mark": "trill",
1238    # "triple-tongue": "?",
1239    # "unstress": "?"
1240    "up-bow": "upbow",
1241    # "wavy-line": "?",
1242}
1243articulation_spanners = ["wavy-line"]
1244
1245
1246def OrnamenthasWhat(mxl_event):
1247    wavy = trilly = ignore = start = stop = False
1248    for i in mxl_event._parent._children:
1249        if i._name == "wavy-line":
1250            wavy = True
1251        elif i._name == "trill-mark":
1252            trilly = True
1253        try:
1254            if i.type == "continue":
1255                ignore = True
1256            elif i.type == "start":
1257                start = True
1258            elif i.type == "stop":
1259                stop = True
1260        except Exception: ## TODO: find out what to except.
1261            pass
1262    if start == True:
1263        if wavy == True and trilly == False:
1264            musicexp.whatOrnament = "wave"
1265        else:
1266            musicexp.whatOrnament = "trill"
1267    if ignore == True:
1268        return "ignore"
1269    elif stop == True:
1270        return "stop"
1271    elif wavy == True and trilly == True:
1272        return "trill and wave"
1273    elif wavy == True:
1274        return "wave"
1275    elif trilly == True:
1276        return "trill"
1277
1278
1279def OrnamenthasWavyline(mxl_event):
1280    for i in mxl_event._parent._children:
1281        if i._name == "wavy-line":
1282            return True
1283    return False
1284
1285
1286def musicxml_articulation_to_lily_event(mxl_event):
1287    # wavy-line elements are treated as trill spanners, not as articulation ornaments
1288    if mxl_event.get_name() in articulation_spanners:
1289        return musicxml_spanner_to_lily_event(mxl_event)
1290
1291    tmp_tp = articulations_dict.get(mxl_event.get_name())
1292    if OrnamenthasWavyline(mxl_event):
1293        return
1294    if not tmp_tp:
1295        return
1296
1297    if isinstance(tmp_tp, str):
1298        ev = musicexp.ArticulationEvent()
1299        ev.type = tmp_tp
1300    elif isinstance(tmp_tp, tuple):
1301        ev = tmp_tp[0]()
1302        ev.type = tmp_tp[1]
1303    else:
1304        ev = tmp_tp(mxl_event)
1305
1306    # Some articulations use the type attribute, other the placement...
1307    dir = None
1308    if hasattr(mxl_event, 'type') and hasattr(options, 'convert_directions') and options.convert_directions:
1309        dir = musicxml_direction_to_indicator(mxl_event.type)
1310    if hasattr(mxl_event, 'placement') and hasattr(options, 'convert_directions') and options.convert_directions:
1311        dir = musicxml_direction_to_indicator(mxl_event.placement)
1312    if dir:
1313        ev.force_direction = dir
1314    return ev
1315
1316
1317def musicxml_dynamics_to_lily_event(dynentry):
1318    dynamics_available = (
1319        "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
1320        "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz")
1321    dynamicsname = dynentry.get_name()
1322    if dynamicsname == "other-dynamics":
1323        dynamicsname = dynentry.get_text()
1324    if not dynamicsname or dynamicsname == "#text":
1325        return None
1326
1327    if not dynamicsname in dynamics_available:
1328        # Get rid of - in tag names (illegal in ly tags!)
1329        dynamicstext = dynamicsname
1330        dynamicsname = dynamicsname.replace("-", "")
1331        additional_definitions[dynamicsname] = dynamicsname + \
1332            " = #(make-dynamic-script \"" + dynamicstext + "\")"
1333        needed_additional_definitions.append(dynamicsname)
1334    event = musicexp.DynamicsEvent()
1335    event.type = dynamicsname
1336    return event
1337
1338# Convert single-color two-byte strings to numbers 0.0 - 1.0
1339
1340
1341def hexcolorval_to_nr(hex_val):
1342    try:
1343        v = int(hex_val, 16)
1344        if v == 255:
1345            v = 256
1346        return v / 256.
1347    except ValueError:
1348        return 0.
1349
1350
1351def hex_to_color(hex_val):
1352    res = re.match(
1353        r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE)
1354    if res:
1355        return [hexcolorval_to_nr(x) for x in res.group(2, 3, 4)]
1356    else:
1357        return None
1358
1359
1360def font_size_number_to_lily_command(size):
1361    d = {
1362        (0, 8): r'\teeny',
1363        (8, 10): r'\tiny',
1364        (10, 12): r'\small',
1365        (12, 16): r'',
1366        (16, 24): r'\large',
1367        (24, float('inf')): r'\huge',
1368    }
1369    result = None
1370    for r in list(d.keys()):
1371        if r[0] <= size < r[1]:
1372            result = d[r]
1373            break
1374    return result
1375
1376
1377def font_size_word_to_lily_command(size):
1378    font_size_dict = {
1379        "xx-small": '\\teeny',
1380        "x-small": '\\tiny',
1381        "small": '\\small',
1382        "medium": '',
1383        "large": '\\large',
1384        "x-large": '\\huge',
1385        "xx-large": '\\larger\\huge'
1386    }
1387    return font_size_dict.get(size, '')
1388
1389
1390def get_font_size(size):
1391    try:
1392        size = float(size)
1393        return font_size_number_to_lily_command(size)
1394    except ValueError:
1395        return font_size_word_to_lily_command(size)
1396
1397
1398def musicxml_words_to_lily_event(words):
1399    event = musicexp.TextEvent()
1400    text = words.get_text()
1401    # remove white spaces and line breaks before text
1402    text = re.sub('^ *\n? *', '', text)
1403    # remove white spaces and line breaks before text
1404    text = re.sub(' *\n? *$', '', text)
1405    event.text = text
1406
1407    if hasattr(words, 'default-y') and hasattr(options, 'convert_directions') and options.convert_directions:
1408        offset = getattr(words, 'default-y')
1409        try:
1410            off = int(offset)
1411            if off > 0:
1412                event.force_direction = 1
1413            else:
1414                event.force_direction = -1
1415        except ValueError:
1416            event.force_direction = 0
1417
1418    if hasattr(words, 'font-weight'):
1419        font_weight = {"normal": '', "bold": '\\bold'}.get(
1420            getattr(words, 'font-weight'), '')
1421        if font_weight:
1422            event.markup += font_weight
1423
1424    if hasattr(words, 'font-size'):
1425        size = getattr(words, 'font-size')
1426        # font_size = font_size_dict.get(size, '')
1427        font_size = get_font_size(size)
1428        if font_size:
1429            event.markup += font_size
1430
1431    if hasattr(words, 'color'):
1432        color = getattr(words, 'color')
1433        rgb = hex_to_color(color)
1434        if rgb:
1435            event.markup += "\\with-color #(rgb-color %s %s %s)" % (
1436                rgb[0], rgb[1], rgb[2])
1437
1438    if hasattr(words, 'font-style'):
1439        font_style = {"italic": '\\italic'}.get(
1440            getattr(words, 'font-style'), '')
1441        if font_style:
1442            event.markup += font_style
1443
1444    # TODO: How should I best convert the font-family attribute?
1445
1446    # TODO: How can I represent the underline, overline and line-through
1447    #       attributes in LilyPond? Values of these attributes indicate
1448    #       the number of lines
1449
1450    return event
1451
1452
1453# convert accordion-registration to lilypond.
1454# Since lilypond does not have any built-in commands, we need to create
1455# the markup commands manually and define our own variables.
1456# Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
1457def musicxml_accordion_to_markup(mxl_event):
1458    commandname = "accReg"
1459    command = ""
1460
1461    high = mxl_event.get_maybe_exist_named_child('accordion-high')
1462    if high:
1463        commandname += "H"
1464        command += """\\combine
1465          \\raise #2.5 \\musicglyph #\"accordion.dot\"
1466          """
1467    middle = mxl_event.get_maybe_exist_named_child('accordion-middle')
1468    if middle:
1469        # By default, use one dot (when no or invalid content is given). The
1470        # MusicXML spec is quiet about this case...
1471        txt = 1
1472        try:
1473            txt = int(middle.get_text())
1474        except ValueError:
1475            pass
1476        if txt == 3:
1477            commandname += "MMM"
1478            command += r"""\combine
1479          \raise #1.5 \musicglyph #"accordion.dot"
1480          \combine
1481          \raise #1.5 \translate #(cons 1 0) \musicglyph #"accordion.dot"
1482          \combine
1483          \raise #1.5 \translate #(cons -1 0) \musicglyph #"accordion.dot"
1484          """
1485        elif txt == 2:
1486            commandname += "MM"
1487            command += r"""\combine
1488          \raise #1.5 \translate #(cons 0.5 0) \musicglyph #"accordion.dot"
1489          \combine
1490          \raise #1.5 \translate #(cons -0.5 0) \musicglyph #"accordion.dot"
1491          """
1492        elif not txt <= 0:
1493            commandname += "M"
1494            command += r"""\combine
1495          \raise #1.5 \musicglyph #"accordion.dot"
1496          """
1497    low = mxl_event.get_maybe_exist_named_child('accordion-low')
1498    if low:
1499        commandname += "L"
1500        command += r"""\combine
1501          \raise #0.5 \musicglyph #"accordion.dot"
1502          """
1503
1504    command += r'\musicglyph #"accordion.discant"'
1505    command = r"\markup { \normalsize %s }" % command
1506    # Define the newly built command \accReg[H][MMM][L]
1507    additional_definitions[commandname] = "%s = %s" % (commandname, command)
1508    needed_additional_definitions.append(commandname)
1509    return "\\%s" % commandname
1510
1511
1512def musicxml_accordion_to_ly(mxl_event):
1513    txt = musicxml_accordion_to_markup(mxl_event)
1514    if txt:
1515        ev = musicexp.MarkEvent(txt)
1516        return ev
1517    return
1518
1519
1520def musicxml_rehearsal_to_ly_mark(mxl_event):
1521    text = mxl_event.get_text()
1522    if not text:
1523        return
1524    # default is boxed rehearsal marks!
1525    encl = "box"
1526    if hasattr(mxl_event, 'enclosure'):
1527        encl = {"none": None, "square": "box", "circle": "circle"}.get(
1528            mxl_event.enclosure, None)
1529    if encl:
1530        text = "\\%s { %s }" % (encl, text)
1531    ev = musicexp.MarkEvent("\\markup { %s }" % text)
1532    return ev
1533
1534
1535def musicxml_harp_pedals_to_ly(mxl_event):
1536    count = 0
1537    result = "\\harp-pedal #\""
1538    for t in mxl_event.get_named_children('pedal-tuning'):
1539        alter = t.get_named_child('pedal-alter')
1540        if alter:
1541            val = int(alter.get_text().strip())
1542            result += {1: "v", 0: "-", -1: "^"}.get(val, "")
1543        count += 1
1544        if count == 3:
1545            result += "|"
1546    ev = musicexp.MarkupEvent()
1547    ev.contents = result + "\""
1548    return ev
1549
1550
1551def musicxml_eyeglasses_to_ly(mxl_event):
1552    needed_additional_definitions.append("eyeglasses")
1553    return musicexp.MarkEvent("\\markup { \\eyeglasses }")
1554
1555
1556def next_non_hash_index(lst, pos):
1557    pos += 1
1558    while pos < len(lst) and isinstance(lst[pos], musicxml.Hash_text):
1559        pos += 1
1560    return pos
1561
1562
1563def musicxml_metronome_to_ly(mxl_event, text_event=None):
1564    children = mxl_event.get_all_children()
1565    if not children:
1566        return
1567
1568    index = -1
1569    index = next_non_hash_index(children, index)
1570    if isinstance(children[index], musicxml.BeatUnit):
1571        # first form of metronome-mark, using unit and beats/min or other unit
1572        ev = musicexp.TempoMark()
1573        if text_event:
1574            ev.set_text(text_event.get_text().strip())
1575
1576        if hasattr(mxl_event, 'parentheses'):
1577            ev.set_parentheses(mxl_event.parentheses == "yes")
1578
1579        d = musicexp.Duration()
1580        d.duration_log = utilities.musicxml_duration_to_log(
1581            children[index].get_text())
1582        index = next_non_hash_index(children, index)
1583        if isinstance(children[index], musicxml.BeatUnitDot):
1584            d.dots = 1
1585            index = next_non_hash_index(children, index)
1586        ev.set_base_duration(d)
1587        if isinstance(children[index], musicxml.BeatUnit):
1588            # Form "note = newnote"
1589            newd = musicexp.Duration()
1590            newd.duration_log = utilities.musicxml_duration_to_log(
1591                children[index].get_text())
1592            index = next_non_hash_index(children, index)
1593            if isinstance(children[index], musicxml.BeatUnitDot):
1594                newd.dots = 1
1595                index = next_non_hash_index(children, index)
1596            ev.set_new_duration(newd)
1597        elif isinstance(children[index], musicxml.PerMinute):
1598            # Form "note = bpm"
1599            try:
1600                beats = int(children[index].get_text())
1601                ev.set_beats_per_minute(beats)
1602            except ValueError:
1603                pass
1604        else:
1605            ly.warning(_("Unknown metronome mark, ignoring"))
1606            return
1607        return ev
1608    else:
1609        # TODO: Implement the other (more complex) way for tempo marks!
1610        ly.warning(
1611            _("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
1612        return
1613
1614
1615# translate directions into Events, possible values:
1616#   -) string  (MarkEvent with that command)
1617#   -) function (function(mxl_event) needs to return a full Event-derived object
1618#   -) (class, name)  (like string, only that a different class than MarkEvent is used)
1619directions_dict = {
1620    'accordion-registration': musicxml_accordion_to_ly,
1621    'coda': (musicexp.MusicGlyphMarkEvent, "coda"),
1622    #     'damp' : ???
1623    #     'damp-all' : ???
1624    'eyeglasses': musicxml_eyeglasses_to_ly,
1625    'harp-pedals': musicxml_harp_pedals_to_ly,
1626    #     'image' : ???
1627    'metronome': musicxml_metronome_to_ly,
1628    'rehearsal': musicxml_rehearsal_to_ly_mark,
1629    #     'scordatura' : ???
1630    'segno': (musicexp.MusicGlyphMarkEvent, "segno"),
1631    'words': musicxml_words_to_lily_event,
1632}
1633directions_spanners = ['octave-shift', 'pedal', 'wedge', 'dashes', 'bracket']
1634
1635
1636def musicxml_direction_to_lily(n):
1637    # TODO: Handle the <staff> element!
1638    res = []
1639    # placement applies to all children!
1640    dir = None
1641    if hasattr(n, 'placement') and hasattr(options, 'convert_directions') and options.convert_directions:
1642        dir = musicxml_direction_to_indicator(n.placement)
1643    dirtype_children = []
1644    # TODO: The direction-type is used for grouping (e.g. dynamics with text),
1645    #       so we can't simply flatten them out!
1646    for dt in n.get_typed_children(musicxml.DirType):
1647        dirtype_children += dt.get_all_children()
1648
1649    dirtype_children = [d for d in dirtype_children if d.get_name() != "#text"]
1650
1651    for i, entry in enumerate(dirtype_children):
1652        if not entry:
1653            continue
1654
1655        # brackets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
1656        if entry.get_name() in directions_spanners:
1657            event = musicxml_spanner_to_lily_event(entry)
1658            if event:
1659                event.force_direction = dir
1660                res.append(event)
1661            continue
1662
1663        # handle text+bpm marks like "Allegro moderato (♩ = 144)"
1664        if entry.get_name() == 'words' and i < len(dirtype_children) - 1:
1665            next_entry = dirtype_children[i+1]
1666            if next_entry.get_name() == 'metronome':
1667                event = musicxml_metronome_to_ly(next_entry, entry)
1668                if event:
1669                    res.append(event)
1670                    dirtype_children[i+1] = None
1671                    continue
1672
1673        # now treat all the "simple" ones, that can be translated using the dict
1674        ev = None
1675        tmp_tp = directions_dict.get(entry.get_name(), None)
1676        if isinstance(tmp_tp, str):  # string means MarkEvent
1677            ev = musicexp.MarkEvent(tmp_tp)
1678        elif isinstance(tmp_tp, tuple):  # tuple means (EventClass, "text")
1679            ev = tmp_tp[0](tmp_tp[1])
1680        elif tmp_tp:
1681            ev = tmp_tp(entry)
1682        if ev:
1683            # TODO: set the correct direction! Unfortunately, \mark in ly does
1684            #       not seem to support directions!
1685            ev.force_direction = dir
1686            res.append(ev)
1687            continue
1688
1689        if entry.get_name() == "dynamics":
1690            for dynentry in entry.get_all_children():
1691                ev = musicxml_dynamics_to_lily_event(dynentry)
1692                if ev:
1693                    ev.force_direction = dir
1694                    res.append(ev)
1695
1696    return res
1697
1698
1699notehead_styles_dict = {
1700    'slash': '\'slash',
1701    'triangle': '\'triangle',
1702    'diamond': '\'diamond',
1703    'square': '\'la',  # TODO: Proper squared note head
1704    'cross': None,  # TODO: + shaped note head
1705    'x': '\'cross',
1706    'circle-x': '\'xcircle',
1707    'inverted triangle': None,  # TODO: Implement
1708    'arrow down': None,  # TODO: Implement
1709    'arrow up': None,  # TODO: Implement
1710    'slashed': None,  # TODO: Implement
1711    'back slashed': None,  # TODO: Implement
1712    'normal': None,
1713    'cluster': None,  # TODO: Implement
1714    'none': '#f',
1715    'do': '\'do',
1716    're': '\'re',
1717    'mi': '\'mi',
1718    'fa': '\'fa',
1719    'so': None,
1720    'la': '\'la',
1721    'ti': '\'ti',
1722}
1723
1724
1725def musicxml_chordpitch_to_lily(mxl_cpitch):
1726    r = musicexp.ChordPitch()
1727    r.alteration = mxl_cpitch.get_alteration()
1728    r.step = musicxml2ly_conversion.musicxml_step_to_lily(
1729        mxl_cpitch.get_step())
1730    return r
1731
1732
1733chordkind_dict = {
1734    'major': ':5',
1735    'minor': ':m5',
1736    'augmented': ':aug5',
1737    'diminished': ':dim5',
1738    # Sevenths:
1739    'dominant': ':7',
1740    'dominant-seventh': ':7',
1741    'major-seventh': ':maj7',
1742    'minor-seventh': ':m7',
1743    'diminished-seventh': ':dim7',
1744    'augmented-seventh': ':aug7',
1745    'half-diminished': ':dim5m7',
1746    'major-minor': ':maj7m5',
1747    # Sixths:
1748    'major-sixth': ':6',
1749    'minor-sixth': ':m6',
1750    # Ninths:
1751    'dominant-ninth': ':9',
1752    'major-ninth': ':maj9',
1753    'minor-ninth': ':m9',
1754    # 11ths (usually as the basis for alteration):
1755    'dominant-11th': ':11',
1756    'major-11th': ':maj11',
1757    'minor-11th': ':m11',
1758    # 13ths (usually as the basis for alteration):
1759    'dominant-13th': ':13.11',
1760    'major-13th': ':maj13.11',
1761    'minor-13th': ':m13',
1762    # Suspended:
1763    'suspended-second': ':sus2',
1764    'suspended-fourth': ':sus4',
1765    # Functional sixths:
1766    # TODO
1767    # 'Neapolitan': '???',
1768    # 'Italian': '???',
1769    # 'French': '???',
1770    # 'German': '???',
1771    # Other:
1772    # 'pedal': '???',(pedal-point bass)
1773    'power': ':1.5',
1774    # 'Tristan': '???',
1775    'other': ':1',
1776    'none': None,
1777}
1778
1779
1780def musicxml_chordkind_to_lily(kind):
1781    res = chordkind_dict.get(kind, None)
1782    # Check for None, since a major chord is converted to ''
1783    if res is None:
1784        ly.warning(_("Unable to convert chord type %s to lilypond.") % kind)
1785    return res
1786
1787
1788# Global variable for guitar string tunings
1789string_tunings = None
1790
1791
1792def musicxml_get_string_tunings(lines):
1793    global string_tunings
1794    if string_tunings is None:
1795        if not lines:
1796            lines = 6
1797        string_tunings = [musicexp.Pitch()] * lines
1798        for i in range(0, lines):
1799            p = musicexp.Pitch()
1800            p.step = musicxml2ly_conversion.musicxml_step_to_lily(
1801                ((("E", "A", "D", "G", "B")*(lines/5+1))[0:lines])[i])
1802            p.octave = (([-2+int(x % 5 > 1)+2*(x/5)
1803                          for x in range(0, lines)][0:lines])[i])
1804            p.alteration = 0
1805            p._force_absolute_pitch = True
1806            string_tunings[i] = p
1807        string_tunings = string_tunings[::-1]
1808    return string_tunings[0:lines]
1809
1810
1811def musicxml_frame_to_lily_event(frame):
1812    ev = musicexp.FretEvent()
1813    ev.strings = frame.get_strings()
1814    ev.frets = frame.get_frets()
1815    #offset = frame.get_first_fret() - 1
1816    #offset = frame.get_first_fret()
1817    barre = []
1818    open_strings = list(range(1, ev.strings+1))
1819    for fn in frame.get_named_children('frame-note'):
1820        fret = fn.get_fret()
1821        if fret <= 0:
1822            fret = "o"
1823        el = [fn.get_string(), fret]
1824        fingering = fn.get_fingering()
1825        if fingering >= 0:
1826            el.append(fingering)
1827        ev.elements.append(el)
1828        open_strings.remove(fn.get_string())
1829        b = fn.get_barre()
1830        if b == 'start':
1831            barre.append(el[0])  # start string
1832            barre.append(el[1])  # fret
1833        elif b == 'stop':
1834            barre.insert(1, el[0])  # end string
1835    for string in open_strings:
1836        ev.elements.append([string, 'x'])
1837    ev.elements.sort()
1838    ev.elements.reverse()
1839    if barre:
1840        ev.barre = barre
1841    return ev
1842
1843
1844def musicxml_harmony_to_lily(n):
1845    res = []
1846    for f in n.get_named_children('frame'):
1847        ev = musicxml_frame_to_lily_event(f)
1848        if ev:
1849            res.append(ev)
1850    return res
1851
1852
1853def musicxml_harmony_to_lily_fretboards(n):
1854    res = []
1855    frame = n.get_maybe_exist_named_child('frame')
1856    if frame:
1857        strings = frame.get_strings()
1858        if not strings:
1859            strings = 6
1860        tunings = musicxml_get_string_tunings(strings)
1861        ev = musicexp.FretBoardEvent()
1862        #barre = []
1863        for fn in frame.get_named_children('frame-note'):
1864            fbn = musicexp.FretBoardNote()
1865            string = fn.get_string()
1866            fbn.string = string
1867            fingering = fn.get_fingering()
1868            if fingering >= 0:
1869                fbn.fingering = fingering
1870            p = tunings[string-1].copy()
1871            p.add_semitones(fn.get_fret())
1872            fbn.pitch = p
1873            ev.append(fbn)
1874        res.append(ev)
1875    return res
1876
1877
1878def musicxml_harmony_to_lily_chordname(n):
1879    res = []
1880    root = n.get_maybe_exist_named_child('root')
1881    if root:
1882        ev = musicexp.ChordNameEvent()
1883        ev.root = musicxml_chordpitch_to_lily(root)
1884        kind = n.get_maybe_exist_named_child('kind')
1885        if kind:
1886            ev.kind = musicxml_chordkind_to_lily(kind.get_text())
1887            if not ev.kind:
1888                return res
1889        bass = n.get_maybe_exist_named_child('bass')
1890        if bass:
1891            ev.bass = musicxml_chordpitch_to_lily(bass)
1892        inversion = n.get_maybe_exist_named_child('inversion')
1893        if inversion:
1894            # TODO: LilyPond does not support inversions, does it?
1895
1896            # Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
1897            # 4. LilyPond supports the first inversion in the form of added
1898            # bass notes.  So the first inversion of C major would be c:/g.
1899            # To get the second inversion of C major, you would need to do
1900            # e:6-3-^5 or e:m6-^5.  However, both of these techniques
1901            # require you to know the chord and calculate either the fifth
1902            # pitch (for the first inversion) or the third pitch (for the
1903            # second inversion) so they may not be helpful for musicxml2ly.
1904            inversion_count = int(inversion.get_text())
1905            if inversion_count == 1:
1906                # TODO: Calculate the bass note for the inversion...
1907                pass
1908            pass
1909        for deg in n.get_named_children('degree'):
1910            d = musicexp.ChordModification()
1911            d.type = deg.get_type()
1912            d.step = deg.get_value()
1913            d.alteration = deg.get_alter()
1914            ev.add_modification(d)
1915        # TODO: convert the user-symbols attribute:
1916            # major: a triangle, like Unicode 25B3
1917            # minor: -, like Unicode 002D
1918            # augmented: +, like Unicode 002B
1919            # diminished: (degree), like Unicode 00B0
1920            # half-diminished: (o with slash), like Unicode 00F8
1921        if ev and ev.root:
1922            res.append(ev)
1923    return res
1924
1925
1926def musicxml_figured_bass_note_to_lily(n):
1927    res = musicexp.FiguredBassNote()
1928    suffix_dict = {'sharp': "+",
1929                   'flat': "-",
1930                   'natural': "!",
1931                   'double-sharp': "++",
1932                   'flat-flat': "--",
1933                   'sharp-sharp': "++",
1934                   'slash': "/"}
1935    prefix = n.get_maybe_exist_named_child('prefix')
1936    if prefix:
1937        res.set_prefix(suffix_dict.get(prefix.get_text(), ""))
1938    fnumber = n.get_maybe_exist_named_child('figure-number')
1939    if fnumber:
1940        res.set_number(fnumber.get_text())
1941    suffix = n.get_maybe_exist_named_child('suffix')
1942    if suffix:
1943        res.set_suffix(suffix_dict.get(suffix.get_text(), ""))
1944    if n.get_maybe_exist_named_child('extend'):
1945        # TODO: Implement extender lines (unfortunately, in lilypond you have
1946        #       to use \set useBassFigureExtenders = ##t, which turns them on
1947        #       globally, while MusicXML has a property for each note...
1948        #       I'm not sure there is a proper way to implement this cleanly
1949        # n.extend
1950        pass
1951    return res
1952
1953
1954def musicxml_figured_bass_to_lily(n):
1955    if not isinstance(n, musicxml.FiguredBass):
1956        return
1957    res = musicexp.FiguredBassEvent()
1958    for i in n.get_named_children('figure'):
1959        note = musicxml_figured_bass_note_to_lily(i)
1960        if note:
1961            res.append(note)
1962    dur = n.get_maybe_exist_named_child('duration')
1963    if dur:
1964        # apply the duration to res
1965        length = Fraction(int(dur.get_text()), n._divisions) * Fraction(1, 4)
1966        res.set_real_duration(length)
1967        duration = musicxml2ly_conversion.rational_to_lily_duration(length)
1968        if duration:
1969            res.set_duration(duration)
1970    if hasattr(n, 'parentheses') and n.parentheses == "yes":
1971        res.set_parentheses(True)
1972    return res
1973
1974
1975def musicxml_lyrics_to_text(lyrics, ignoremelismata):
1976    # TODO: Implement text styles for lyrics syllables
1977    continued = False
1978    extended = False
1979    text = ''
1980    for e in lyrics.get_all_children():
1981        if isinstance(e, musicxml.Syllabic):
1982            continued = e.continued()
1983        elif isinstance(e, musicxml.Text):
1984            # We need to convert soft hyphens to -, otherwise the ascii codec as well
1985            # as lilypond will barf on that character
1986            text += e.get_text().replace('\xad', '-')
1987        elif isinstance(e, musicxml.Elision):
1988            if text:
1989                text += " "
1990            continued = False
1991            extended = False
1992        elif isinstance(e, musicxml.Extend):
1993            if text:
1994                text += " "
1995            extended = True
1996
1997    if text == "-" and continued:
1998        return "--"
1999    elif text == "_" and extended:
2000        return "__"
2001    elif continued and text:
2002        if hasattr(options, 'convert_beaming') and options.convert_beaming:
2003            if ignoremelismata == "on":
2004                return r" \set ignoreMelismata = ##t " + utilities.escape_ly_output_string(text)
2005            elif ignoremelismata == "off":
2006                return " " + utilities.escape_ly_output_string(text) + " -- \\unset ignoreMelismata"
2007            else:
2008                return " " + utilities.escape_ly_output_string(text) + " --"
2009        else:
2010            return " " + utilities.escape_ly_output_string(text) + " -- "
2011    elif continued:
2012        return "--"
2013    elif extended and text:
2014        return " " + utilities.escape_ly_output_string(text) + " __"
2015    elif extended:
2016        return "__"
2017    elif text:
2018        return " " + utilities.escape_ly_output_string(text)
2019    else:
2020        return ""
2021
2022# TODO
2023
2024
2025class NegativeSkip:
2026    def __init__(self, here, dest):
2027        self.here = here
2028        self.dest = dest
2029
2030
2031class LilyPondVoiceBuilder:
2032    def __init__(self):
2033        self.elements = []
2034        self.pending_dynamics = []
2035        self.end_moment = Fraction(0)
2036        self.begin_moment = Fraction(0)
2037        self.pending_multibar = Fraction(0)
2038        self.ignore_skips = False
2039        self.has_relevant_elements = False
2040        self.measure_length = Fraction(4, 4)
2041        self.stay_here = False
2042
2043    def _insert_multibar(self):
2044        layout_information.set_context_item('Score', 'skipBars = ##t')
2045        r = musicexp.MultiMeasureRest()
2046        lenfrac = self.measure_length
2047        r.duration = musicxml2ly_conversion.rational_to_lily_duration(lenfrac)
2048        r.duration.factor *= self.pending_multibar / lenfrac
2049        self.elements.append(r)
2050        self.begin_moment = self.end_moment
2051        self.end_moment = self.begin_moment + self.pending_multibar
2052        self.pending_multibar = Fraction(0)
2053
2054    def set_measure_length(self, mlen):
2055        if (mlen != self.measure_length) and self.pending_multibar:
2056            self._insert_multibar()
2057        self.measure_length = mlen
2058
2059    def add_multibar_rest(self, duration):
2060        self.pending_multibar += duration
2061
2062    def set_duration(self, duration):
2063        self.end_moment = self.begin_moment + duration
2064
2065    def current_duration(self):
2066        return self.end_moment - self.begin_moment
2067
2068    def add_pending_dynamics(self):
2069        for d in self.pending_dynamics:
2070            self.elements.append(d)
2071        self.pending_dynamics = []
2072
2073    def add_music(self, music, duration, relevant=True):
2074        assert isinstance(music, musicexp.Music)
2075        if self.pending_multibar > Fraction(0):
2076            self._insert_multibar()
2077
2078        self.has_relevant_elements = self.has_relevant_elements or relevant
2079
2080        if isinstance(music, musicexp.BarLine):
2081            if self.pending_dynamics:
2082                for d in self.pending_dynamics:
2083                    if not isinstance(d, (musicexp.SpanEvent, musicexp.DynamicsEvent)):
2084                        index = self.pending_dynamics.index(d)
2085                        dyn = self.pending_dynamics.pop(index)
2086                        self.elements.append(dyn)
2087
2088        self.elements.append(music)
2089        self.begin_moment = self.end_moment
2090        self.set_duration(duration)
2091
2092        # Insert all pending dynamics right after the note/rest:
2093        if isinstance(music, musicexp.ChordEvent) and self.pending_dynamics:
2094            self.add_pending_dynamics()
2095
2096    # Insert some music command that does not affect the position in the measure
2097    def add_command(self, command, relevant=True):
2098        assert isinstance(command, musicexp.Music)
2099        if self.pending_multibar > Fraction(0):
2100            self._insert_multibar()
2101        self.has_relevant_elements = self.has_relevant_elements or relevant
2102        self.elements.append(command)
2103
2104    def add_barline(self, barline, relevant=False):
2105        # Insert only if we don't have a barline already
2106        # TODO: Implement proper merging of default barline and custom bar line
2107        has_relevant = self.has_relevant_elements
2108        if (not (self.elements) or
2109            not (isinstance(self.elements[-1], musicexp.BarLine)) or
2110                (self.pending_multibar > Fraction(0))):
2111
2112            self.add_music(barline, Fraction(0))
2113
2114        self.has_relevant_elements = has_relevant or relevant
2115
2116    def add_partial(self, command):
2117        self.ignore_skips = True
2118        # insert the partial, but restore relevant_elements (partial is not relevant)
2119        relevant = self.has_relevant_elements
2120        self.add_command(command)
2121        self.has_relevant_elements = relevant
2122
2123    def add_dynamics(self, dynamic):
2124        # store the dynamic item(s) until we encounter the next note/rest:
2125        self.pending_dynamics.append(dynamic)
2126
2127    def add_bar_check(self, number):
2128        # re/store has_relevant_elements, so that a barline alone does not
2129        # trigger output for figured bass, chord names
2130        b = musicexp.BarLine()
2131        b.bar_number = number
2132        self.add_barline(b)
2133
2134    def jumpto(self, moment):
2135        if not self.stay_here:
2136            current_end = self.end_moment + self.pending_multibar
2137            diff = moment - current_end
2138
2139            if diff < Fraction(0):
2140                ly.warning(_('Negative skip %s (from position %s to %s)') %
2141                            (diff, current_end, moment))
2142                diff = Fraction(0)
2143
2144            if diff > Fraction(0) and not(self.ignore_skips and moment == 0):
2145                skip = musicexp.SkipEvent()
2146                duration_factor = 1
2147                duration_log = {1: 0, 2: 1, 4: 2, 8: 3, 16: 4, 32: 5,
2148                                64: 6, 128: 7, 256: 8, 512: 9}.get(diff.denominator, -1)
2149                duration_dots = 0
2150                # TODO: Use the time signature for skips, too. Problem: The skip
2151                #       might not start at a measure boundary!
2152                if duration_log > 0:  # denominator is a power of 2...
2153                    if diff.numerator == 3:
2154                        duration_log -= 1
2155                        duration_dots = 1
2156                    else:
2157                        duration_factor = Fraction(diff.numerator)
2158                else:
2159                    # for skips of a whole or more, simply use s1*factor
2160                    duration_log = 0
2161                    duration_factor = diff
2162                skip.duration.duration_log = duration_log
2163                skip.duration.factor = duration_factor
2164                skip.duration.dots = duration_dots
2165
2166                evc = musicexp.ChordEvent()
2167                evc.elements.append(skip)
2168                self.add_music(evc, diff, False)
2169
2170            if diff > Fraction(0) and moment == 0:
2171                self.ignore_skips = False
2172
2173    def last_event_chord(self, starting_at):
2174        value = None
2175
2176        # if the position matches, find the last ChordEvent, do not cross a bar line!
2177        at = len(self.elements) - 1
2178        while (at >= 0 and
2179               not isinstance(self.elements[at], musicexp.ChordEvent) and
2180               not isinstance(self.elements[at], musicexp.BarLine)):
2181            at -= 1
2182
2183        if (self.elements
2184            and at >= 0
2185            and isinstance(self.elements[at], musicexp.ChordEvent)
2186                and self.begin_moment == starting_at):
2187            value = self.elements[at]
2188        else:
2189            self.jumpto(starting_at)
2190            value = None
2191        return value
2192
2193    def correct_negative_skip(self, goto):
2194        self.end_moment = goto
2195        self.begin_moment = goto
2196        evc = musicexp.ChordEvent()
2197        self.elements.append(evc)
2198
2199
2200class VoiceData:
2201    def __init__(self):
2202        self.voicename = None
2203        self.voicedata = None
2204        self.ly_voice = None
2205        self.figured_bass = None
2206        self.chordnames = None
2207        self.fretboards = None
2208        self.lyrics_dict = {}
2209        self.lyrics_order = []
2210
2211
2212def measure_length_from_attributes(attr, current_measure_length):
2213    len = attr.get_measure_length()
2214    if not len:
2215        len = current_measure_length
2216    return len
2217
2218
2219def music_xml_voice_name_to_lily_name(part_id, name):
2220    s = "Part%sVoice%s" % (part_id, name)
2221    return musicxml_id_to_lily(s)
2222
2223
2224def music_xml_lyrics_name_to_lily_name(part_id, name, lyricsnr):
2225    s = music_xml_voice_name_to_lily_name(
2226        part_id, name)+("Lyrics%s" % lyricsnr)
2227    return musicxml_id_to_lily(s)
2228
2229
2230def music_xml_figuredbass_name_to_lily_name(part_id, voicename):
2231    s = music_xml_voice_name_to_lily_name(part_id, voicename)+"FiguredBass"
2232    return musicxml_id_to_lily(s)
2233
2234
2235def music_xml_chordnames_name_to_lily_name(part_id, voicename):
2236    s = music_xml_voice_name_to_lily_name(part_id, voicename)+"Chords"
2237    return musicxml_id_to_lily(s)
2238
2239
2240def music_xml_fretboards_name_to_lily_name(part_id, voicename):
2241    s = music_xml_voice_name_to_lily_name(part_id, voicename)+"FretBoards"
2242    return musicxml_id_to_lily(s)
2243
2244
2245def get_all_lyric_parts_in_voice(voice):
2246    r'''
2247    Collect the indexes of all lyric parts in this voice.
2248    In case not all of the current lyric parts are active (a typical case would be
2249    a refrain/chorus), the current implementation inserts \skip-commands in the
2250    inactive parts to keep them in sync.
2251    '''
2252    all_lyric_parts = []
2253    for elem in voice._elements:
2254        lyrics = elem.get_typed_children(musicxml.Lyric)
2255        if lyrics:
2256            for lyric in lyrics:
2257                index = lyric.get_number()
2258                if not index in all_lyric_parts:
2259                    all_lyric_parts.append(index)
2260    return all_lyric_parts
2261
2262
2263def extract_lyrics(voice, lyric_key, lyrics_dict):
2264    curr_number = None
2265    result = []
2266
2267    def is_note(elem):
2268        return isinstance(elem, musicxml.Note)
2269
2270    def is_rest(elem):
2271        return elem.get_typed_children(musicxml.Rest)
2272
2273    def is_chord(elem):
2274        return elem.get_typed_children(musicxml.Chord)
2275
2276    def is_note_and_not_rest(elem):
2277        return is_note(elem) and not is_rest(elem)
2278
2279    def get_lyric_elements(note):
2280        return note.get_typed_children(musicxml.Lyric)
2281
2282    def has_lyric_belonging_to_lyric_part(note, lyric_part_id):
2283        lyric_elements = get_lyric_elements(note)
2284        lyric_numbers = [lyric.get_number() for lyric in lyric_elements]
2285        return any([lyric_number == lyric_part_id for lyric_number in lyric_numbers])
2286
2287    for idx, elem in enumerate(voice._elements):
2288        lyrics = get_lyric_elements(elem)
2289        lyric_keys = [lyric.get_number() for lyric in lyrics]
2290        note_has_lyric_belonging_to_lyric_part = lyric_key in lyric_keys
2291        # Current note has lyric with 'number' matching 'lyric_key'.
2292        if note_has_lyric_belonging_to_lyric_part:
2293            for lyric in lyrics:
2294                if lyric.get_number() == lyric_key:
2295                    text = musicxml_lyrics_to_text(lyric, None)
2296                    result.append(text)
2297        # Note has any lyric.
2298        elif get_lyric_elements(elem) and \
2299                not note_has_lyric_belonging_to_lyric_part:
2300            result.append(r'\skip1 ')
2301        # Note does not have any lyric attached to it.
2302        elif is_chord(elem):
2303            # note without lyrics part of a chord. MusicXML format is
2304            # unclear if a chord element could contain a lyric, lets
2305            # asume that we do not want to put a skip here.
2306            continue
2307        elif is_note_and_not_rest(elem):
2308            result.append(r'\skip1 ')
2309
2310    lyrics_dict[lyric_key].extend(result)
2311
2312
2313def musicxml_voice_to_lily_voice(voice):
2314    tuplet_events = []
2315    lyrics = {}
2316    return_value = VoiceData()
2317    return_value.voicedata = voice
2318
2319    # First pitch needed for relative mode (if selected in command-line options)
2320    first_pitch = None
2321
2322    # Needed for melismata detection (ignore lyrics on those notes!):
2323    inside_slur = False
2324    is_tied = False
2325    is_chord = False
2326    is_beamed = False
2327    ignore_lyrics = False
2328
2329    current_staff = None
2330
2331    pending_figured_bass = []
2332    pending_chordnames = []
2333    pending_fretboards = []
2334
2335    # Make sure that the keys in the dict don't get reordered, since
2336    # we need the correct ordering of the lyrics stanzas! By default,
2337    # a dict will reorder its keys
2338    return_value.lyrics_order = voice.get_lyrics_numbers()
2339    for k in return_value.lyrics_order:
2340        lyrics[k] = []
2341
2342    voice_builder = LilyPondVoiceBuilder()
2343    figured_bass_builder = LilyPondVoiceBuilder()
2344    chordnames_builder = LilyPondVoiceBuilder()
2345    fretboards_builder = LilyPondVoiceBuilder()
2346    current_measure_length = Fraction(4, 4)
2347    voice_builder.set_measure_length(current_measure_length)
2348    in_slur = False
2349
2350    all_lyric_parts = set(get_all_lyric_parts_in_voice(voice))
2351    if list(lyrics.keys()):
2352        for number in list(lyrics.keys()):
2353            extracted_lyrics = extract_lyrics(voice, number, lyrics)
2354
2355    last_bar_check = -1
2356    for idx, n in enumerate(voice._elements):
2357        tie_started = False
2358        if n.get_name() == 'forward':
2359            continue
2360        staff = n.get_maybe_exist_named_child('staff')
2361        if staff:
2362            staff = staff.get_text()
2363            if current_staff and staff != current_staff and not n.get_maybe_exist_named_child('chord'):
2364                voice_builder.add_command(musicexp.StaffChange(staff))
2365            current_staff = staff
2366
2367        if isinstance(n, musicxml.Partial) and n.partial > 0:
2368            a = musicxml_partial_to_lily(n.partial)
2369            if a:
2370                voice_builder.add_partial(a)
2371                figured_bass_builder.add_partial(a)
2372                chordnames_builder.add_partial(a)
2373                fretboards_builder.add_partial(a)
2374            continue
2375
2376        is_chord = n.get_maybe_exist_named_child('chord')
2377        is_after_grace = (isinstance(n, musicxml.Note) and n.is_after_grace())
2378        if not is_chord and not is_after_grace:
2379            try:
2380                voice_builder.jumpto(n._when)
2381                figured_bass_builder.jumpto(n._when)
2382                chordnames_builder.jumpto(n._when)
2383                fretboards_builder.jumpto(n._when)
2384            except NegativeSkip as neg:
2385                voice_builder.correct_negative_skip(n._when)
2386                figured_bass_builder.correct_negative_skip(n._when)
2387                chordnames_builder.correct_negative_skip(n._when)
2388                fretboards_builder.correct_negative_skip(n._when)
2389                n.message(_("Negative skip found: from %s to %s, difference is %s") % (
2390                    neg.here, neg.dest, neg.dest - neg.here))
2391
2392        if isinstance(n, musicxml.Barline):
2393            barlines = n.to_lily_object()
2394            for a in barlines:
2395                if isinstance(a, musicexp.BarLine):
2396                    voice_builder.add_barline(a)
2397                    figured_bass_builder.add_barline(a, False)
2398                    chordnames_builder.add_barline(a, False)
2399                    fretboards_builder.add_barline(a, False)
2400                elif isinstance(a, musicxml2ly_conversion.RepeatMarker) or isinstance(a, musicxml2ly_conversion.EndingMarker):
2401                    voice_builder.add_command(a)
2402                    figured_bass_builder.add_barline(a, False)
2403                    chordnames_builder.add_barline(a, False)
2404                    fretboards_builder.add_barline(a, False)
2405            continue
2406
2407        if isinstance(n, musicxml.Print):
2408            for a in musicxml_print_to_lily(n):
2409                voice_builder.add_command(a, False)
2410            continue
2411
2412        # Continue any multimeasure-rests before trying to add bar checks!
2413        # Don't handle new MM rests yet, because for them we want bar checks!
2414        rest = n.get_maybe_exist_typed_child(musicxml.Rest)
2415        if (rest and rest.is_whole_measure()
2416                and voice_builder.pending_multibar > Fraction(0)):
2417            voice_builder.add_multibar_rest(n._duration)
2418            continue
2419
2420        # Print bar checks between measures.
2421        if n._measure_position == Fraction(0) and n != voice._elements[0]:
2422            try:
2423                num = int(n.get_parent().number)
2424            except ValueError:
2425                num = 0
2426            if num > 0 and num > last_bar_check:
2427                voice_builder.add_bar_check(num)
2428                figured_bass_builder.add_bar_check(num)
2429                chordnames_builder.add_bar_check(num)
2430                fretboards_builder.add_bar_check(num)
2431                last_bar_check = num
2432
2433        if isinstance(n, musicxml.Direction):
2434            # check if Direction already has been converted in another voice.
2435            if n.converted:
2436                continue
2437            else:
2438                n.converted = True
2439                for direction in musicxml_direction_to_lily(n):
2440                    if direction.wait_for_note():
2441                        voice_builder.add_dynamics(direction)
2442                    else:
2443                        voice_builder.add_command(direction)
2444                continue
2445
2446        # Start any new multimeasure rests
2447        if (rest and rest.is_whole_measure()):
2448            if pending_chordnames:
2449                chordnames_builder.jumpto(n._when)
2450                chordnames_builder.stay_here = True
2451            if pending_figured_bass:
2452                figured_bass_builder.jumpto(n._when)
2453                figured_bass_builder.stay_here = True
2454            if pending_fretboards:
2455                fretboards_builder.jumpto(n._when)
2456                fretboards_builder.stay_here = True
2457            voice_builder.add_multibar_rest(n._duration)
2458            continue
2459
2460        if isinstance(n, musicxml.Harmony):
2461            if options.fretboards:
2462                # Makes fretboard diagrams in a separate FretBoards voice
2463                for a in musicxml_harmony_to_lily_fretboards(n):
2464                    pending_fretboards.append(a)
2465            else:
2466                # Makes markup fretboard-diagrams inside the voice
2467                for a in musicxml_harmony_to_lily(n):
2468                    if a.wait_for_note():
2469                        voice_builder.add_dynamics(a)
2470                    else:
2471                        voice_builder.add_command(a)
2472            for a in musicxml_harmony_to_lily_chordname(n):
2473                pending_chordnames.append(a)
2474            continue
2475
2476        if isinstance(n, musicxml.FiguredBass):
2477            a = musicxml_figured_bass_to_lily(n)
2478            if a:
2479                pending_figured_bass.append(a)
2480            continue
2481
2482        if isinstance(n, musicxml.Attributes):
2483            for a in musicxml_attributes_to_lily(n):
2484                voice_builder.add_command(a)
2485            measure_length = measure_length_from_attributes(
2486                n, current_measure_length)
2487            if current_measure_length != measure_length:
2488                current_measure_length = measure_length
2489                voice_builder.set_measure_length(current_measure_length)
2490            continue
2491
2492        if not n.__class__.__name__ == 'Note':
2493            n.message(_('unexpected %s; expected %s or %s or %s') %
2494                      (n, 'Note', 'Attributes', 'Barline'))
2495            continue
2496
2497#        if not hasattr(conversion_settings, 'convert_rest_positions'):
2498#            conversion_settings.convert_rest_positions = True
2499
2500        main_event = n.to_lily_object(
2501            convert_stem_directions=conversion_settings.convert_stem_directions,
2502            convert_rest_positions=conversion_settings.convert_rest_positions)
2503
2504        if main_event and not first_pitch:
2505            first_pitch = main_event.pitch
2506        # ignore lyrics for notes inside a slur, tie, chord or beam
2507        ignore_lyrics = is_tied or is_chord  # or is_beamed or inside_slur
2508
2509        ev_chord = voice_builder.last_event_chord(n._when)
2510        if not ev_chord:
2511            ev_chord = musicexp.ChordEvent()
2512            voice_builder.add_music(ev_chord, n._duration)
2513
2514        # For grace notes:
2515        grace = n.get_maybe_exist_typed_child(musicxml.Grace)
2516        if n.is_grace():
2517            is_after_grace = ev_chord.has_elements() or n.is_after_grace()
2518            is_chord = n.get_maybe_exist_typed_child(musicxml.Chord)
2519
2520            grace_chord = None
2521
2522            # after-graces and other graces use different lists; Depending on
2523            # whether we have a chord or not, obtain either a new ChordEvent or
2524            # the previous one to create a chord
2525            if is_after_grace:
2526                if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord):
2527                    grace_chord = ev_chord.after_grace_elements.get_last_event_chord()
2528                if not grace_chord:
2529                    grace_chord = musicexp.ChordEvent()
2530                    ev_chord.append_after_grace(grace_chord)
2531            elif n.is_grace():
2532                if ev_chord.grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord):
2533                    grace_chord = ev_chord.grace_elements.get_last_event_chord()
2534                if not grace_chord:
2535                    grace_chord = musicexp.ChordEvent()
2536                    ev_chord.append_grace(grace_chord)
2537
2538            if hasattr(grace, 'slash') and not is_after_grace:
2539                # TODO: use grace_type = "appoggiatura" for slurred grace notes
2540                if grace.slash == "yes":
2541                    ev_chord.grace_type = "acciaccatura"
2542            # now that we have inserted the chord into the grace music, insert
2543            # everything into that chord instead of the ev_chord
2544            ev_chord = grace_chord
2545            ev_chord.append(main_event)
2546            ignore_lyrics = True
2547        else:
2548            ev_chord.append(main_event)
2549            # When a note/chord has grace notes (duration==0), the duration of the
2550            # event chord is not yet known, but the event chord was already added
2551            # with duration 0. The following correct this when we hit the real note!
2552            if voice_builder.current_duration() == 0 and n._duration > 0:
2553                voice_builder.set_duration(n._duration)
2554
2555        # if we have a figured bass, set its voice builder to the correct position
2556        # and insert the pending figures
2557        if pending_figured_bass:
2558            try:
2559                figured_bass_builder.jumpto(n._when)
2560                if figured_bass_builder.stay_here:
2561                    figured_bass_builder.stay_here = False
2562            except NegativeSkip as neg:
2563                pass
2564            for fb in pending_figured_bass:
2565                # if a duration is given, use that, otherwise the one of the note
2566                dur = fb.real_duration
2567                if not dur:
2568                    dur = ev_chord.get_length()
2569                if not fb.duration:
2570                    fb.duration = ev_chord.get_duration()
2571                figured_bass_builder.add_music(fb, dur)
2572            pending_figured_bass = []
2573
2574        if pending_chordnames:
2575            try:
2576                chordnames_builder.jumpto(n._when)
2577                if chordnames_builder.stay_here:
2578                    chordnames_builder.stay_here = False
2579            except NegativeSkip as neg:
2580                pass
2581            for cn in pending_chordnames:
2582                # Assign the duration of the EventChord
2583                cn.duration = ev_chord.get_duration()
2584                chordnames_builder.add_music(cn, ev_chord.get_length())
2585            pending_chordnames = []
2586
2587        if pending_fretboards:
2588            try:
2589                fretboards_builder.jumpto(n._when)
2590                if fretboards_builder.stay_here:
2591                    fretboards_builder.stay_here = False
2592            except NegativeSkip as neg:
2593                pass
2594            for fb in pending_fretboards:
2595                # Assign the duration of the EventChord
2596                fb.duration = ev_chord.get_duration()
2597                fretboards_builder.add_music(fb, ev_chord.get_length())
2598            pending_fretboards = []
2599
2600        notations_children = n.get_typed_children(musicxml.Notations)
2601        tuplet_event = None
2602        span_events = []
2603
2604        # The <notation> element can have the following children (+ means implemented, ~ partially, - not):
2605        # +tied | +slur | +tuplet | glissando | slide |
2606        #    ornaments | technical | articulations | dynamics |
2607        #    +fermata | arpeggiate | non-arpeggiate |
2608        #    accidental-mark | other-notation
2609        for notations in notations_children:
2610            for tuplet_event in notations.get_tuplets():
2611                time_mod = n.get_maybe_exist_typed_child(
2612                    musicxml.Time_modification)
2613                tuplet_events.append((ev_chord, tuplet_event, time_mod))
2614
2615            # First, close all open slurs, only then start any new slur
2616            # TODO: Record the number of the open slur to dtermine the correct
2617            #       closing slur!
2618            endslurs = [s for s in notations.get_named_children('slur')
2619                        if s.get_type() in ('stop')]
2620            if endslurs and not inside_slur:
2621                endslurs[0].message(
2622                    _('Encountered closing slur, but no slur is open'))
2623            elif endslurs:
2624                if len(endslurs) > 1:
2625                    endslurs[0].message(
2626                        _('Cannot have two simultaneous (closing) slurs'))
2627                # record the slur status for the next note in the loop
2628                inside_slur = False
2629                lily_ev = musicxml_spanner_to_lily_event(endslurs[0])
2630                ev_chord.append(lily_ev)
2631
2632            startslurs = [s for s in notations.get_named_children('slur')
2633                          if s.get_type() in ('start')]
2634            if startslurs and inside_slur:
2635                startslurs[0].message(
2636                    _('Cannot have a slur inside another slur'))
2637            elif startslurs:
2638                if len(startslurs) > 1:
2639                    startslurs[0].message(
2640                        _('Cannot have two simultaneous slurs'))
2641                # record the slur status for the next note in the loop
2642                inside_slur = True
2643                lily_ev = musicxml_spanner_to_lily_event(startslurs[0])
2644                ev_chord.append(lily_ev)
2645
2646            if not grace:
2647                mxl_tie = notations.get_tie()
2648                if mxl_tie and mxl_tie.type == 'start':
2649                    ev_chord.append(musicexp.TieEvent())
2650                    is_tied = True
2651                    tie_started = True
2652                else:
2653                    is_tied = False
2654
2655            fermatas = notations.get_named_children('fermata')
2656            for a in fermatas:
2657                ev = musicxml_fermata_to_lily_event(a)
2658                if ev:
2659                    ev_chord.append(ev)
2660
2661            arpeggiate = notations.get_named_children('arpeggiate')
2662            for a in arpeggiate:
2663                ev = musicxml_arpeggiate_to_lily_event(a)
2664                if ev:
2665                    ev_chord.append(ev)
2666
2667            arpeggiate = notations.get_named_children('non-arpeggiate')
2668            for a in arpeggiate:
2669                ev = musicxml_nonarpeggiate_to_lily_event(a)
2670                if ev:
2671                    ev_chord.append(ev)
2672
2673            glissandos = notations.get_named_children('glissando')
2674            glissandos += notations.get_named_children('slide')
2675            for a in glissandos:
2676                ev = musicxml_spanner_to_lily_event(a)
2677                if ev:
2678                    ev_chord.append(ev)
2679
2680            # accidental-marks are direct children of <notation>!
2681            for a in notations.get_named_children('accidental-mark'):
2682                ev = musicxml_articulation_to_lily_event(a)
2683                if ev:
2684                    ev_chord.append(ev)
2685
2686            # Articulations can contain the following child elements:
2687            #         accent | strong-accent | staccato | tenuto |
2688            #         detached-legato | staccatissimo | spiccato |
2689            #         scoop | plop | doit | falloff | breath-mark |
2690            #         caesura | stress | unstress
2691            # Technical can contain the following child elements:
2692            #         up-bow | down-bow | harmonic | open-string |
2693            #         thumb-position | fingering | pluck | double-tongue |
2694            #         triple-tongue | stopped | snap-pizzicato | fret |
2695            #         string | hammer-on | pull-off | bend | tap | heel |
2696            #         toe | fingernails | other-technical
2697            # Ornaments can contain the following child elements:
2698            #         trill-mark | turn | delayed-turn | inverted-turn |
2699            #         shake | wavy-line | mordent | inverted-mordent |
2700            #         schleifer | tremolo | other-ornament, accidental-mark
2701            ornaments = notations.get_named_children('ornaments')
2702            ornaments += notations.get_named_children('articulations')
2703            ornaments += notations.get_named_children('technical')
2704
2705            for a in ornaments:
2706                for ch in a.get_all_children():
2707                    ev = musicxml_articulation_to_lily_event(ch)
2708                    if ev:
2709                        ev_chord.append(ev)
2710
2711            dynamics = notations.get_named_children('dynamics')
2712            for a in dynamics:
2713                for ch in a.get_all_children():
2714                    ev = musicxml_dynamics_to_lily_event(ch)
2715                    if ev:
2716                        ev_chord.append(ev)
2717
2718        mxl_beams = [b for b in n.get_named_children('beam')
2719                     if (b.get_type() in ('begin', 'end')
2720                         and b.is_primary())]
2721        if mxl_beams and not conversion_settings.ignore_beaming:
2722            beam_ev = musicxml_spanner_to_lily_event(mxl_beams[0])
2723            if beam_ev:
2724                ev_chord.append(beam_ev)
2725                if beam_ev.span_direction == -1:  # beam and thus melisma starts here
2726                    is_beamed = True
2727                elif beam_ev.span_direction == 1:  # beam and thus melisma ends here
2728                    is_beamed = False
2729
2730        # Assume that a <tie> element only lasts for one note.
2731        # This might not be correct MusicXML interpretation, but works for
2732        # most cases and fixes broken files, which have the end tag missing
2733        if is_tied and not tie_started:
2734            is_tied = False
2735
2736    # force trailing mm rests to be written out.
2737    voice_builder.add_music (musicexp.ChordEvent(), Fraction(0))
2738
2739    if hasattr(options, 'shift_meter') and options.shift_meter:
2740        for event in voice_builder.elements:
2741            if isinstance(event, musicexp.TimeSignatureChange):
2742                sd = []
2743                for i in range(0, 5):
2744                    sd.append(musicexp.ShiftDurations())
2745                    sd[i].set_shift_durations_parameters(event)
2746                break
2747
2748    ly_voice = group_tuplets(voice_builder.elements, tuplet_events)
2749    ly_voice = group_repeats(ly_voice)
2750
2751    seq_music = musicexp.SequentialMusic()
2752
2753    seq_music.elements = ly_voice
2754    for k in list(lyrics.keys()):
2755        return_value.lyrics_dict[k] = musicexp.Lyrics()
2756        return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
2757
2758    if hasattr(options, 'shift_meter') and options.shift_meter:
2759        sd[-1].element = seq_music
2760        seq_music = sd[-1]
2761        sd.pop()
2762
2763    if hasattr(options, 'relative') and options.relative:
2764        v = musicexp.RelativeMusic()
2765        v.element = seq_music
2766        v.basepitch = first_pitch
2767        seq_music = v
2768
2769    return_value.ly_voice = seq_music
2770
2771    # create \figuremode { figured bass elements }
2772    if figured_bass_builder.has_relevant_elements:
2773        fbass_music = musicexp.SequentialMusic()
2774        fbass_music.elements = group_repeats(figured_bass_builder.elements)
2775        v = musicexp.ModeChangingMusicWrapper()
2776        v.mode = 'figuremode'
2777        v.element = fbass_music
2778        if hasattr(options, 'shift_meter') and options.shift_meter:
2779            sd[-1].element = v
2780            v = sd[-1]
2781            sd.pop()
2782        return_value.figured_bass = v
2783
2784    # create \chordmode { chords }
2785    if chordnames_builder.has_relevant_elements:
2786        cname_music = musicexp.SequentialMusic()
2787        cname_music.elements = group_repeats(chordnames_builder.elements)
2788        v = musicexp.ModeChangingMusicWrapper()
2789        v.mode = 'chordmode'
2790        v.element = cname_music
2791        if hasattr(options, 'shift_meter') and options.shift_meter:
2792            sd[-1].element = v
2793            v = sd[-1]
2794            sd.pop()
2795        return_value.chordnames = v
2796
2797    # create diagrams for FretBoards engraver
2798    if fretboards_builder.has_relevant_elements:
2799        fboard_music = musicexp.SequentialMusic()
2800        fboard_music.elements = group_repeats(fretboards_builder.elements)
2801        v = musicexp.MusicWrapper()
2802        v.element = fboard_music
2803        if hasattr(options, 'shift_meter') and options.shift_meter:
2804            sd[-1].element = v
2805            v = sd[-1]
2806            sd.pop()
2807        return_value.fretboards = v
2808
2809    # coll = []
2810    # pending = []
2811
2812    # for elt in return_value.ly_voice.element.elements:
2813    #     if isinstance(elt, musicexp.TimeScaledMusic):
2814    #         print elt.element.elements
2815    #         pending.append(elt)
2816    #     else:
2817    #         coll.append(elt)
2818
2819    # if pending:
2820    #     coll.extend(pending)
2821
2822    # return_value.ly_voice.element.elements = coll
2823
2824    return return_value
2825
2826
2827def musicxml_id_to_lily(id):
2828    digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
2829              'Six', 'Seven', 'Eight', 'Nine', 'Ten']
2830
2831    for digit in digits:
2832        d = digits.index(digit)
2833        id = re.sub('%d' % d, digit, id)
2834
2835    id = re.sub('[^a-zA-Z]', 'X', id)
2836    return id
2837
2838
2839def voices_in_part(part):
2840    """Return a Name -> Voice dictionary for PART"""
2841    part.interpret()
2842    part.extract_voices()
2843    voices = part.get_voices()
2844    part_info = part.get_staff_attributes()
2845
2846    return (voices, part_info)
2847
2848
2849def voices_in_part_in_parts(parts):
2850    """return a Part -> Name -> Voice dictionary"""
2851    # don't crash if Part doesn't have an id (that's invalid MusicXML,
2852    # but such files are out in the wild!)
2853    dictionary = {}
2854    for p in parts:
2855        voices = voices_in_part(p)
2856        if hasattr(p, "id"):
2857            dictionary[p.id] = voices
2858        else:
2859            # TODO: extract correct part id from other sources
2860            dictionary[None] = voices
2861    return dictionary
2862
2863
2864def get_all_voices(parts):
2865    all_voices = voices_in_part_in_parts(parts)
2866
2867    all_ly_voices = {}
2868    all_ly_staffinfo = {}
2869    for p, (name_voice, staff_info) in list(all_voices.items()):
2870
2871        part_ly_voices = OrderedDict()
2872        for n, v in list(name_voice.items()):
2873            ly.progress(_("Converting to LilyPond expressions..."), True)
2874            # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
2875            voice = musicxml_voice_to_lily_voice(v)
2876            part_ly_voices[n] = voice
2877
2878        all_ly_voices[p] = part_ly_voices
2879        all_ly_staffinfo[p] = staff_info
2880
2881    return (all_ly_voices, all_ly_staffinfo)
2882
2883
2884def option_parser():
2885    p = ly.get_option_parser(usage=_("musicxml2ly [OPTION]... FILE.xml"),
2886                             description=_("""Convert MusicXML from FILE.xml to LilyPond input.
2887If the given filename is -, musicxml2ly reads from the command line.
2888"""), add_help_option=False)
2889
2890    p.add_option("-h", "--help",
2891                 action="help",
2892                 help=_("show this help and exit"))
2893
2894    p.version = ('%prog (LilyPond) ' + lilypond_version + '\n\n'
2895                 +
2896                 _("""Copyright (c) 2005--2021 by
2897    Han-Wen Nienhuys <hanwen@xs4all.nl>,
2898    Jan Nieuwenhuizen <janneke@gnu.org> and
2899    Reinhold Kainhofer <reinhold@kainhofer.com>
2900    Patrick L. Schmidt <pls@philomelos.net>
2901"""
2902                   +
2903                   """
2904This program is free software.  It is covered by the GNU General Public
2905License and you are welcome to change it and/or distribute copies of it
2906under certain conditions.  Invoke as `%s --warranty' for more
2907information.""") % 'lilypond')
2908
2909    p.add_option("--version",
2910                 action="version",
2911                 help=_("show version number and exit"))
2912
2913    p.add_option('-v', '--verbose',
2914                 action="callback",
2915                 callback=ly.handle_loglevel_option,
2916                 callback_args=("DEBUG",),
2917                 help=_("be verbose"))
2918
2919    p.add_option('', '--lxml',
2920                 action="store_true",
2921                 default=False,
2922                 dest="use_lxml",
2923                 help=_("use lxml.etree; uses less memory and cpu time"))
2924
2925    p.add_option('-z', '--compressed',
2926                 action="store_true",
2927                 dest='compressed',
2928                 default=False,
2929                 help=_("input file is a compressed MusicXML file "
2930                        "(by default, activate if file extension is .mxl)"))
2931
2932    p.add_option('-r', '--relative',
2933                 action="store_true",
2934                 default=True,
2935                 dest="relative",
2936                 help=_("convert pitches in relative mode (default)"))
2937
2938    p.add_option('-a', '--absolute',
2939                 action="store_false",
2940                 dest="relative",
2941                 help=_("convert pitches in absolute mode"))
2942
2943    p.add_option('-l', '--language',
2944                 metavar=_("LANG"),
2945                 action="store",
2946                 help=_("use LANG for pitch names, e.g. 'deutsch' for note names in German"))
2947
2948    p.add_option("--loglevel",
2949                 help=_("Print log messages according to LOGLEVEL "
2950                        "(NONE, ERROR, WARNING, PROGRESS (default), DEBUG)"),
2951                 metavar=_("LOGLEVEL"),
2952                 action='callback',
2953                 callback=ly.handle_loglevel_option,
2954                 type='string')
2955
2956    p.add_option('--nd', '--no-articulation-directions',
2957                 action="store_false",
2958                 default=True,
2959                 dest="convert_directions",
2960                 help=_("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
2961
2962    p.add_option('--nrp', '--no-rest-positions',
2963                 action="store_false",
2964                 default=True,
2965                 dest="convert_rest_positions",
2966                 help=_("do not convert exact vertical positions of rests"))
2967
2968    p.add_option('--nsb', '--no-system-breaks',
2969                 action="store_false",
2970                 default=True,
2971                 dest="convert_system_breaks",
2972                 help=_("ignore system breaks"))
2973
2974    p.add_option('--npb', '--no-page-breaks',
2975                 action="store_false",
2976                 default=True,
2977                 dest="convert_page_breaks",
2978                 help=_("ignore page breaks"))
2979
2980    p.add_option('--npm', '--no-page-margins',
2981                 action="store_false",
2982                 default=True,
2983                 dest="convert_page_margins",
2984                 help=_("ignore page margins"))
2985
2986    p.add_option('--npl', '--no-page-layout',
2987                 action="store_false",
2988                 default=True,
2989                 dest="convert_page_layout",
2990                 help=_("do not convert the exact page layout and breaks (shortcut for \"--nsb --npb --npm\" options)"))
2991
2992    p.add_option('--nsd', '--no-stem-directions',
2993                 action="store_false",
2994                 default=True,
2995                 dest="convert_stem_directions",
2996                 help=_("ignore stem directions from MusicXML, use lilypond's automatic stemming instead"))
2997
2998    p.add_option('--nb', '--no-beaming',
2999                 action="store_false",
3000                 default=True,
3001                 dest="convert_beaming",
3002                 help=_("do not convert beaming information, use lilypond's automatic beaming instead"))
3003
3004    p.add_option('-o', '--output',
3005                 metavar=_("FILE"),
3006                 action="store",
3007                 default=None,
3008                 type='string',
3009                 dest='output_name',
3010                 help=_("set output filename to FILE, stdout if -"))
3011
3012    p.add_option('-m', '--midi',
3013                 action="store_true",
3014                 default=False,
3015                 dest="midi",
3016                 help=_("activate midi-block in .ly file"))
3017
3018    # transpose function
3019    p.add_option('--transpose',
3020                 metavar=_("TOPITCH"),
3021                 action="store",
3022                 dest="transpose",
3023                 help=_("set pitch to transpose by the interval between pitch 'c' and TOPITCH"))
3024
3025    # time signature changing function
3026    p.add_option('--sm', '--shift-meter',
3027                 metavar=_("BEATS/BEATTYPE"),
3028                 action="store",
3029                 dest="shift_meter",
3030                 help=_("change the length|duration of notes as a function of a given time signature to make the score look faster or slower, (eg. '4/4' or '2/2')"))
3031
3032    # switch tabstaff clef
3033    p.add_option('--tc', '--tab-clef',
3034                 metavar=_("TABCLEFNAME"),
3035                 action="store",
3036                 dest="tab_clef",
3037                 help=_("switch between two versions of tab clefs (\"tab\" and \"moderntab\")"))
3038
3039    # StringNumber stencil on/off
3040    p.add_option('--sn', '--string-numbers',
3041                 metavar=_("t[rue]/f[alse]"),
3042                 action="store",
3043                 dest="string_numbers",
3044                 help=_("deactivate string number stencil with --string-numbers f[alse]. Default is t[rue]"))
3045
3046    # StringNumber stencil on/off
3047    p.add_option('--fb', '--fretboards',
3048                 action="store_true",
3049                 default=False,
3050                 dest="fretboards",
3051                 help=_("converts '<frame>' events to a separate FretBoards voice instead of markups"))
3052
3053    p.add_option_group('',
3054                       description=(
3055                           _("Report bugs via %s")
3056                           % 'bug-lilypond@gnu.org') + '\n')
3057    return p
3058
3059
3060def print_voice_definitions(printer, part_list, voices):
3061    for part in part_list:
3062        part_id = part.id
3063        nv_dict = voices.get(part_id, {})
3064        for (name, voice) in list(nv_dict.items()):
3065            k = music_xml_voice_name_to_lily_name(part_id, name)
3066            printer.dump('%s = ' % k)
3067            voice.ly_voice.print_ly(printer)
3068            printer.newline()
3069            if voice.chordnames:
3070                cnname = music_xml_chordnames_name_to_lily_name(part_id, name)
3071                printer.dump('%s = ' % cnname)
3072                voice.chordnames.print_ly(printer)
3073                printer.newline()
3074            for l in voice.lyrics_order:
3075                lname = music_xml_lyrics_name_to_lily_name(part_id, name, l)
3076                printer.dump('%s = ' % lname)
3077                voice.lyrics_dict[l].print_ly(printer)
3078                printer.newline()
3079            if voice.figured_bass:
3080                fbname = music_xml_figuredbass_name_to_lily_name(part_id, name)
3081                printer.dump('%s = ' % fbname)
3082                voice.figured_bass.print_ly(printer)
3083                printer.newline()
3084            if voice.fretboards:
3085                fbdname = music_xml_fretboards_name_to_lily_name(part_id, name)
3086                printer.dump('%s = ' % fbdname)
3087                voice.fretboards.print_ly(printer)
3088                printer.newline()
3089
3090
3091# format the information about the staff in the form
3092#     [staffid,
3093#         [
3094#            [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
3095#            [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
3096#            ...
3097#         ]
3098#     ]
3099# raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
3100
3101
3102def format_staff_info(part_id, staff_id, raw_voices):
3103    voices = []
3104    for (v, lyricsids, figured_bass, chordnames, fretboards) in raw_voices:
3105        voice_name = music_xml_voice_name_to_lily_name(part_id, v)
3106        voice_lyrics = [music_xml_lyrics_name_to_lily_name(part_id, v, l)
3107                        for l in lyricsids]
3108        figured_bass_name = ''
3109        if figured_bass:
3110            figured_bass_name = music_xml_figuredbass_name_to_lily_name(
3111                part_id, v)
3112        chordnames_name = ''
3113        if chordnames:
3114            chordnames_name = music_xml_chordnames_name_to_lily_name(
3115                part_id, v)
3116        fretboards_name = ''
3117        if fretboards:
3118            fretboards_name = music_xml_fretboards_name_to_lily_name(
3119                part_id, v)
3120        voices.append([voice_name, voice_lyrics, figured_bass_name,
3121                       chordnames_name, fretboards_name])
3122    return [staff_id, voices]
3123
3124
3125def update_score_setup(score_structure, part_list, voices, parts):
3126    for part_definition in part_list:
3127        part_id = part_definition.id
3128        nv_dict = voices.get(part_id)
3129        if not nv_dict:
3130            if len(part_list) == len(voices) == 1:
3131                # If there is only one part, infer the ID.
3132                # See input/regression/musicxml/41g-PartNoId.xml.
3133                nv_dict = list(voices.values())[0]
3134                voices[part_id] = nv_dict
3135            else:
3136                ly.warning(_('unknown part in part-list: %s') % part_id)
3137                continue
3138
3139        staves = reduce(lambda x, y: x + y,
3140                        [list(voice.voicedata._staves.keys())
3141                         for voice in list(nv_dict.values())],
3142                        [])
3143        staves_info = []
3144        if len(staves) > 1:
3145            staves_info = []
3146            staves = sorted(set(staves))
3147            for s in staves:
3148                thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards)
3149                                        for (voice_name, voice) in list(nv_dict.items())
3150                                        if voice.voicedata._start_staff == s]
3151                staves_info.append(format_staff_info(
3152                    part_id, s, thisstaff_raw_voices))
3153        else:
3154            thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards)
3155                                    for (voice_name, voice) in list(nv_dict.items())]
3156            staves_info.append(format_staff_info(
3157                part_id, None, thisstaff_raw_voices))
3158        score_structure.set_part_information(part_id, staves_info)
3159
3160    sounds = []
3161    for part in parts:
3162        for measure in part.get_typed_children(musicxml.Measure):
3163            for sound in measure.get_typed_children(musicxml.Sound):
3164                sounds.append(sound)
3165            for direction in measure.get_typed_children(musicxml.Direction):
3166                for sound in direction.get_typed_children(musicxml.Sound):
3167                    sounds.append(sound)
3168
3169    score_structure.set_tempo('100')
3170    if len(sounds) != 0:
3171        for sound in sounds:
3172            if (sound.get_tempo() is not None and sound.get_tempo() != ""):
3173                score_structure.set_tempo(sound.get_tempo())
3174                break
3175
3176
3177# Set global values in the \layout block, like auto-beaming etc.
3178def update_layout_information():
3179    if not conversion_settings.ignore_beaming and layout_information:
3180        layout_information.set_context_item('Score', 'autoBeaming = ##f')
3181    if musicexp.get_string_numbers() == "f":
3182        layout_information.set_context_item(
3183            'Score', '\\override StringNumber #\'stencil = ##f')
3184
3185#  \n\t\t\t\t\\override StringNumber #\'stencil = ##f
3186
3187
3188def print_ly_preamble(printer, filename):
3189    printer.dump_version(lilypond_version)
3190    printer.print_verbatim(
3191        '% automatically converted by musicxml2ly from ' + filename)
3192    printer.newline()
3193    printer.dump(r'\pointAndClickOff')
3194    printer.newline()
3195    if options.midi:
3196        printer.newline()
3197        printer.dump(r'\include "articulate.ly"')
3198        printer.newline()
3199
3200
3201def print_ly_additional_definitions(printer, filename=None):
3202    if needed_additional_definitions:
3203        printer.newline()
3204        printer.print_verbatim(
3205            '%% additional definitions required by the score:')
3206        printer.newline()
3207    for a in sorted(set(needed_additional_definitions)):
3208        printer.print_verbatim(additional_definitions.get(a, ''))
3209        printer.newline()
3210    printer.newline()
3211
3212# Read in the tree from the given I/O object (either file or string) and
3213# demarshall it using the classes from the musicxml.py file
3214
3215
3216def read_xml(io_object, use_lxml):
3217    if use_lxml:
3218        import lxml.etree
3219        tree = lxml.etree.parse(io_object)
3220        mxl_tree = musicxml.lxml_demarshal_node(tree.getroot())
3221        return mxl_tree
3222    else:
3223        from xml.dom import minidom, Node
3224        doc = minidom.parse(io_object)
3225        node = doc.documentElement
3226        return musicxml.minidom_demarshal_node(node)
3227    return None
3228
3229
3230def read_musicxml(filename, compressed, use_lxml):
3231    raw_string = None
3232    if compressed:
3233        if filename == "-":
3234            ly.progress(
3235                _("Input is compressed, extracting raw MusicXML data from stdin"), True)
3236            # unfortunately, zipfile.ZipFile can't read directly from
3237            # stdin, so copy everything from stdin to a temp file and read
3238            # that. TemporaryFile() will remove the file when it is closed.
3239            tmp = tempfile.TemporaryFile()
3240            # Make sys.stdin binary
3241            sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
3242            bytes_read = sys.stdin.read(8192)
3243            while bytes_read:
3244                tmp.write(bytes_read)
3245                bytes_read = sys.stdin.read(8192)
3246            z = zipfile.ZipFile(tmp, "r")
3247        else:
3248            ly.progress(
3249                _("Input file %s is compressed, extracting raw MusicXML data") % filename, True)
3250            z = zipfile.ZipFile(filename, "r")
3251        container_xml = z.read("META-INF/container.xml").decode("utf-8")
3252        if not container_xml:
3253            return None
3254        container = read_xml(io.StringIO(container_xml), use_lxml)
3255        if not container:
3256            return None
3257        rootfiles = container.get_maybe_exist_named_child('rootfiles')
3258        if not rootfiles:
3259            return None
3260        rootfile_list = rootfiles.get_named_children('rootfile')
3261        mxml_file = None
3262        if len(rootfile_list) > 0:
3263            mxml_file = getattr(rootfile_list[0], 'full-path', None)
3264        if mxml_file:
3265            raw_string = z.read(mxml_file).decode('utf-8')
3266
3267    if raw_string:
3268        io_object = io.StringIO(raw_string)
3269    elif filename == "-":
3270        io_object = sys.stdin
3271    else:
3272        io_object = filename
3273
3274    return read_xml(io_object, use_lxml)
3275
3276
3277def convert(filename, options):
3278    if filename == "-":
3279        ly.progress(_("Reading MusicXML from Standard input ..."), True)
3280    else:
3281        ly.progress(_("Reading MusicXML from %s ...") % filename, True)
3282
3283    tree = read_musicxml(filename, options.compressed, options.use_lxml)
3284    score_information = extract_score_information(tree)
3285    paper_information = extract_paper_information(tree)
3286
3287    parts = tree.get_typed_children(musicxml.Part)
3288    (voices, staff_info) = get_all_voices(parts)
3289
3290    score = None
3291    mxl_pl = tree.get_maybe_exist_typed_child(musicxml.Part_list)
3292    if mxl_pl:
3293        score = extract_score_structure(mxl_pl, staff_info)
3294        part_list = mxl_pl.get_named_children("score-part")
3295
3296    # score information is contained in the <work>, <identification> or <movement-title> tags
3297    update_score_setup(score, part_list, voices, parts)
3298    # After the conversion, update the list of settings for the \layout block
3299    update_layout_information()
3300
3301    if not options.output_name:
3302        options.output_name = os.path.basename(filename)
3303        options.output_name = os.path.splitext(options.output_name)[0]
3304    elif re.match(r".*\.ly", options.output_name):
3305        options.output_name = os.path.splitext(options.output_name)[0]
3306
3307    #defs_ly_name = options.output_name + '-defs.ly'
3308    if options.output_name == "-":
3309        output_ly_name = 'Standard output'
3310    else:
3311        output_ly_name = options.output_name + '.ly'
3312    ly.progress(_("Output to `%s'") % output_ly_name, True)
3313    printer = musicexp.Output_printer()
3314    #ly.progress(_("Output to `%s'") % defs_ly_name, True)
3315    if options.output_name == "-":
3316        printer.set_file(sys.stdout)
3317    else:
3318        printer.set_file(open(output_ly_name, 'w', encoding='utf-8'))
3319    print_ly_preamble(printer, filename)
3320    print_ly_additional_definitions(printer, filename)
3321    if score_information:
3322        score_information.print_ly(printer)
3323    if paper_information and conversion_settings.convert_page_layout:
3324        paper_information.print_ly(printer)
3325    if layout_information:
3326        layout_information.print_ly(printer)
3327    print_voice_definitions(printer, part_list, voices)
3328
3329    printer.newline()
3330    printer.dump("% The score definition")
3331    printer.newline()
3332    score.print_ly(printer)
3333    printer.newline()
3334
3335    # Syntax update to current version
3336    if options.output_name != "-":
3337        version = os.popen(
3338            "lilypond --version | head -1 | cut -d' ' -f3").read().strip()
3339        ly.progress(
3340            _("Converting to current version (%s) notations ..." % version), True)
3341        os.system("convert-ly -e %s 2> /dev/null" %
3342                  utilities.escape_ly_output_string(output_ly_name))
3343
3344    return voices
3345
3346
3347def get_existing_filename_with_extension(filename, ext):
3348    if os.path.exists(filename):
3349        return filename
3350    newfilename = filename + "." + ext
3351    if os.path.exists(newfilename):
3352        return newfilename
3353    newfilename = filename + ext
3354    if os.path.exists(newfilename):
3355        return newfilename
3356    return ''
3357
3358
3359def main():
3360    opt_parser = option_parser()
3361
3362    global options
3363    (options, args) = opt_parser.parse_args()
3364
3365#   in case of shell entry w/o special characters
3366    if options.language == 'catalan' or options.language == 'catala':
3367        options.language = 'català'
3368    if options.language == 'espanol':
3369        options.language = 'español'
3370    if options.language == 'francais':
3371        options.language = 'français'
3372    if options.language == 'portugues':
3373        options.language = 'português'
3374
3375    if not args:
3376        opt_parser.print_usage()
3377        sys.exit(2)
3378
3379    # midi-block option
3380    if options.midi:
3381        musicexp.set_create_midi(options.midi)
3382
3383    # transpose function
3384    if options.transpose:
3385        musicexp.set_transpose(options.transpose)
3386
3387    # tab clef option
3388    if options.tab_clef:
3389        musicexp.set_tab_clef(options.tab_clef)
3390
3391    # string numbers option
3392    if options.string_numbers:
3393        musicexp.set_string_numbers(options.string_numbers)
3394
3395    if options.language:
3396        musicexp.set_pitch_language(options.language)
3397        needed_additional_definitions.append(options.language)
3398        additional_definitions[options.language] = "\\language \"%s\"\n" % options.language
3399
3400    conversion_settings.ignore_beaming = not options.convert_beaming
3401    conversion_settings.convert_page_layout = options.convert_page_layout
3402    if conversion_settings.convert_page_layout:
3403        conversion_settings.convert_system_breaks = options.convert_system_breaks
3404        conversion_settings.convert_page_breaks = options.convert_page_breaks
3405        conversion_settings.convert_page_margins = options.convert_page_margins
3406    else:
3407        conversion_settings.convert_system_breaks = False
3408        conversion_settings.convert_page_breaks = False
3409        conversion_settings.convert_page_margins = False
3410    conversion_settings.convert_stem_directions = options.convert_stem_directions
3411    conversion_settings.convert_rest_positions = options.convert_rest_positions
3412
3413    # Allow the user to leave out the .xml or xml on the filename
3414    basefilename = args[0]
3415    if basefilename == "-":  # Read from stdin
3416        filename = "-"
3417    else:
3418        filename = get_existing_filename_with_extension(basefilename, "xml")
3419        if not filename:
3420            filename = get_existing_filename_with_extension(
3421                basefilename, "mxl")
3422            options.compressed = True
3423    if filename and filename.endswith("mxl"):
3424        options.compressed = True
3425
3426    if filename and (filename == "-" or os.path.exists(filename)):
3427        voices = convert(filename, options)
3428    else:
3429        ly.error(_("Unable to find input file %s") % basefilename)
3430        sys.exit(1)
3431
3432
3433if __name__ == '__main__':
3434    main()
3435