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