1# This file is part of python-ly, https://pypi.python.org/pypi/python-ly 2# 3# Copyright (c) 2008 - 2015 by Wilbert Berendsen 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18# See http://www.gnu.org/licenses/ for more information. 19 20""" 21Using the tree structure from ly.music to initiate the conversion to MusicXML. 22 23Uses functions similar to ly.music.items.Document.iter_music() to iter through 24the node tree. But information about where a node branch ends 25is also added. During the iteration the information needed for the conversion 26is captured. 27""" 28 29from __future__ import unicode_literals 30from __future__ import print_function 31 32import ly.document 33import ly.music 34 35from . import create_musicxml 36from . import ly2xml_mediator 37from . import xml_objs 38 39#excluded from parsing 40excl_list = ['Version', 'Midi', 'Layout'] 41 42 43# Defining contexts in relation to musicXML 44group_contexts = ['StaffGroup', 'ChoirStaff'] 45 46pno_contexts = ['PianoStaff', 'GrandStaff'] 47 48staff_contexts = ['Staff', 'RhythmicStaff', 'TabStaff', 49 'DrumStaff', 'VaticanaStaff', 'MensuralStaff'] 50 51part_contexts = pno_contexts + staff_contexts 52 53 54class End(): 55 """ Extra class that gives information about the end of Container 56 elements in the node list. """ 57 def __init__(self, node): 58 self.node = node 59 60 def __repr__(self): 61 return '<{0} {1}>'.format(self.__class__.__name__, self.node) 62 63 64class ParseSource(): 65 """ creates the XML-file from the source code according to the Music XML standard """ 66 67 def __init__(self): 68 self.musxml = create_musicxml.CreateMusicXML() 69 self.mediator = ly2xml_mediator.Mediator() 70 self.relative = False 71 self.tuplet = [] 72 self.scale = '' 73 self.grace_seq = False 74 self.trem_rep = 0 75 self.piano_staff = 0 76 self.numericTime = False 77 self.voice_sep = False 78 self.sims_and_seqs = [] 79 self.override_dict = {} 80 self.ottava = False 81 self.with_contxt = None 82 self.schm_assignm = None 83 self.tempo = () 84 self.tremolo = False 85 self.tupl_span = False 86 self.unset_tuplspan = False 87 self.alt_mode = None 88 self.rel_pitch_isset = False 89 self.slurcount = 0 90 self.slurnr = 0 91 self.phrslurnr = 0 92 self.mark = False 93 self.pickup = False 94 95 def parse_text(self, ly_text, filename=None): 96 """Parse the LilyPond source specified as text. 97 98 If you specify a filename, it can be used to resolve \\include commands 99 correctly. 100 101 """ 102 doc = ly.document.Document(ly_text) 103 doc.filename = filename 104 self.parse_document(doc) 105 106 def parse_document(self, ly_doc, relative_first_pitch_absolute=False): 107 """Parse the LilyPond source specified as a ly.document document. 108 109 If relative_first_pitch_absolute is set to True, the first pitch in a 110 \relative expression without startpitch is considered to be absolute 111 (LilyPond 2.18+ behaviour). 112 113 """ 114 # The document is copied and the copy is converted to absolute mode to 115 # facilitate the export. The original document is unchanged. 116 doc = ly_doc.copy() 117 import ly.pitch.rel2abs 118 cursor = ly.document.Cursor(doc) 119 ly.pitch.rel2abs.rel2abs(cursor, first_pitch_absolute=relative_first_pitch_absolute) 120 mustree = ly.music.document(doc) 121 self.parse_tree(mustree) 122 123 def parse_tree(self, mustree): 124 """Parse the LilyPond source as a ly.music node tree.""" 125 # print(mustree.dump()) 126 header_nodes = self.iter_header(mustree) 127 if header_nodes: 128 self.parse_nodes(header_nodes) 129 score = self.get_score(mustree) 130 if score: 131 mus_nodes = self.iter_score(score, mustree) 132 else: 133 mus_nodes = self.find_score_sub(mustree) 134 self.mediator.new_section("fallback") #fallback/default section 135 self.parse_nodes(mus_nodes) 136 137 def parse_nodes(self, nodes): 138 """Work through all nodes by calling the function with the 139 same name as the nodes class.""" 140 if nodes: 141 for m in nodes: 142 # print(m) 143 func_name = m.__class__.__name__ #get instance name 144 if func_name not in excl_list: 145 try: 146 func_call = getattr(self, func_name) 147 func_call(m) 148 except AttributeError as ae: 149 print("Warning:", func_name, "not implemented!") 150 print(ae) 151 pass 152 else: 153 print("Warning! Couldn't parse source!") 154 155 def musicxml(self, prettyprint=True): 156 self.mediator.check_score() 157 xml_objs.IterateXmlObjs( 158 self.mediator.score, self.musxml, self.mediator.divisions) 159 xml = self.musxml.musicxml(prettyprint) 160 return xml 161 162 ## 163 # The different source types from ly.music are here sent to translation. 164 ## 165 166 def Assignment(self, a): 167 """ 168 Variables should already have been substituted 169 so this need only cover other types of assignments. 170 """ 171 if isinstance(a.value(), ly.music.items.Markup): 172 val = a.value().plaintext() 173 elif isinstance(a.value(), ly.music.items.String): 174 val = a.value().value() 175 elif isinstance(a.value(), ly.music.items.Scheme): 176 val = a.value().get_string() 177 if not val: 178 self.schm_assignm = a.name() 179 elif isinstance(a.value(), ly.music.items.UserCommand): 180 # Don't know what to do with this: 181 return 182 if self.look_behind(a, ly.music.items.With): 183 if self.with_contxt in group_contexts: 184 self.mediator.set_by_property(a.name(), val, True) 185 else: 186 self.mediator.set_by_property(a.name(), val) 187 else: 188 self.mediator.new_header_assignment(a.name(), val) 189 190 def MusicList(self, musicList): 191 if musicList.token == '<<': 192 if self.look_ahead(musicList, ly.music.items.VoiceSeparator): 193 self.mediator.new_snippet('sim-snip') 194 self.voice_sep = True 195 else: 196 self.mediator.new_section('simultan') 197 self.sims_and_seqs.append('sim') 198 elif musicList.token == '{': 199 self.sims_and_seqs.append('seq') 200 201 def Chord(self, chord): 202 self.mediator.clear_chord() 203 204 def Q(self, q): 205 self.mediator.copy_prev_chord(q.duration) 206 207 def Context(self, context): 208 r""" \context """ 209 self.in_context = True 210 self.check_context(context.context(), context.context_id(), context.token) 211 212 def check_context(self, context, context_id=None, token=""): 213 """Check context and do appropriate action (e.g. create new part).""" 214 # Check first if part already exists 215 if context_id: 216 match = self.mediator.get_part_by_id(context_id) 217 if match: 218 self.mediator.new_part(to_part=match) 219 return 220 if context in pno_contexts: 221 self.mediator.new_part(context_id, piano=True) 222 self.piano_staff = 1 223 elif context in group_contexts: 224 self.mediator.new_group() 225 elif context in staff_contexts: 226 if self.piano_staff: 227 if self.piano_staff > 1: 228 self.mediator.set_voicenr(nr=self.piano_staff+3) 229 self.mediator.new_section('piano-staff'+str(self.piano_staff)) 230 self.mediator.set_staffnr(self.piano_staff) 231 self.piano_staff += 1 232 else: 233 if token != '\\context' or self.mediator.part_not_empty(): 234 self.mediator.new_part(context_id) 235 self.mediator.add_staff_id(context_id) 236 elif context == 'Voice': 237 self.sims_and_seqs.append('voice') 238 if context_id: 239 self.mediator.new_section(context_id) 240 else: 241 self.mediator.new_section('voice') 242 elif context == 'Devnull': 243 self.mediator.new_section('devnull', True) 244 else: 245 print("Context not implemented:", context) 246 247 def VoiceSeparator(self, voice_sep): 248 self.mediator.new_snippet('sim') 249 self.mediator.set_voicenr(add=True) 250 251 def Change(self, change): 252 r""" A \change music expression. Changes the staff number. """ 253 if change.context() == 'Staff': 254 self.mediator.set_staffnr(0, staff_id=change.context_id()) 255 256 def PipeSymbol(self, barcheck): 257 """ PipeSymbol = | """ 258 pass 259 260 def Clef(self, clef): 261 r""" Clef \clef""" 262 self.mediator.new_clef(clef.specifier()) 263 264 def KeySignature(self, key): 265 self.mediator.new_key(key.pitch().output(), key.mode()) 266 267 def Relative(self, relative): 268 r"""A \relative music expression.""" 269 self.relative = True 270 271 def Partial(self, partial): 272 self.pickup = True 273 274 def Note(self, note): 275 """ notename, e.g. c, cis, a bes ... """ 276 #print(note.token) 277 if note.length(): 278 if self.relative and not self.rel_pitch_isset: 279 self.mediator.new_note(note, False) 280 self.mediator.set_relative(note) 281 self.rel_pitch_isset = True 282 else: 283 self.mediator.new_note(note, self.relative) 284 self.check_note(note) 285 else: 286 if isinstance(note.parent(), ly.music.items.Relative): 287 self.mediator.set_relative(note) 288 self.rel_pitch_isset = True 289 elif isinstance(note.parent(), ly.music.items.Chord): 290 if self.mediator.current_chord: 291 self.mediator.new_chord(note, chord_base=False) 292 else: 293 self.mediator.new_chord(note, note.parent().duration, self.relative) 294 self.check_tuplet() 295 # chord as grace note 296 if self.grace_seq: 297 self.mediator.new_chord_grace() 298 299 def Unpitched(self, unpitched): 300 """A note without pitch, just a standalone duration.""" 301 if unpitched.length(): 302 if self.alt_mode == 'drum': 303 self.mediator.new_iso_dura(unpitched, self.relative, True) 304 else: 305 self.mediator.new_iso_dura(unpitched, self.relative) 306 self.check_note(unpitched) 307 308 def DrumNote(self, drumnote): 309 """A note in DrumMode.""" 310 if drumnote.length(): 311 self.mediator.new_note(drumnote, is_unpitched=True) 312 self.check_note(drumnote) 313 314 def check_note(self, note): 315 """Generic check for all notes, both pitched and unpitched.""" 316 self.check_tuplet() 317 if self.grace_seq: 318 self.mediator.new_grace() 319 if self.trem_rep and not self.look_ahead(note, ly.music.items.Duration): 320 self.mediator.set_tremolo(trem_type='start', repeats=self.trem_rep) 321 322 def check_tuplet(self): 323 """Generic tuplet check.""" 324 if self.tuplet: 325 tlevels = len(self.tuplet) 326 nested = True if tlevels > 1 else False 327 for td in self.tuplet: 328 if nested: 329 self.mediator.change_to_tuplet(td['fraction'], td['ttype'], 330 td['nr'], td['length']) 331 else: 332 self.mediator.change_to_tuplet(td['fraction'], td['ttype'], 333 td['nr']) 334 td['ttype'] = "" 335 self.mediator.check_divs() 336 337 def Duration(self, duration): 338 """A written duration""" 339 if self.tempo: 340 self.mediator.new_tempo(duration.token, duration.tokens, *self.tempo) 341 self.tempo = () 342 elif self.tremolo: 343 self.mediator.set_tremolo(duration=int(duration.token)) 344 self.tremolo = False 345 elif self.tupl_span: 346 self.mediator.set_tuplspan_dur(duration.token, duration.tokens) 347 self.tupl_span = False 348 elif self.pickup: 349 self.mediator.set_pickup() 350 self.pickup = False 351 else: 352 self.mediator.new_duration_token(duration.token, duration.tokens) 353 if self.trem_rep: 354 self.mediator.set_tremolo(trem_type='start', repeats=self.trem_rep) 355 356 def Tempo(self, tempo): 357 """ Tempo direction, e g '4 = 80' """ 358 if self.look_ahead(tempo, ly.music.items.Duration): 359 self.tempo = (tempo.tempo(), tempo.text()) 360 else: 361 self.mediator.new_tempo(0, (), tempo.tempo(), tempo.text()) 362 363 def Tie(self, tie): 364 """ tie ~ """ 365 self.mediator.tie_to_next() 366 367 def Rest(self, rest): 368 r""" rest, r or R. Note: NOT by command, i.e. \rest """ 369 if rest.token == 'R': 370 self.scale = 'R' 371 self.mediator.new_rest(rest) 372 373 def Skip(self, skip): 374 r""" invisible rest/spacer rest (s or command \skip)""" 375 if 'lyrics' in self.sims_and_seqs: 376 self.mediator.new_lyrics_item(skip.token) 377 else: 378 self.mediator.new_rest(skip) 379 380 def Scaler(self, scaler): 381 r""" 382 \times \tuplet \scaleDurations 383 384 """ 385 if scaler.token == '\\scaleDurations': 386 ttype = "" 387 fraction = (scaler.denominator, scaler.numerator) 388 elif scaler.token == '\\times': 389 ttype = "start" 390 fraction = (scaler.denominator, scaler.numerator) 391 elif scaler.token == '\\tuplet': 392 ttype = "start" 393 fraction = (scaler.numerator, scaler.denominator) 394 nr = len(self.tuplet) + 1 395 self.tuplet.append({'set': False, 396 'fraction': fraction, 397 'ttype': ttype, 398 'length': scaler.length(), 399 'nr': nr}) 400 if self.look_ahead(scaler, ly.music.items.Duration): 401 self.tupl_span = True 402 self.unset_tuplspan = True 403 404 def Number(self, number): 405 pass 406 407 def Articulation(self, art): 408 """An articulation, fingering, string number, or other symbol.""" 409 self.mediator.new_articulation(art.token) 410 411 def Postfix(self, postfix): 412 pass 413 414 def Beam(self, beam): 415 pass 416 417 def Slur(self, slur): 418 """ Slur, '(' = start, ')' = stop. """ 419 if slur.token == '(': 420 self.slurcount += 1 421 self.slurnr = self.slurcount 422 self.mediator.set_slur(self.slurnr, "start") 423 elif slur.token == ')': 424 self.mediator.set_slur(self.slurnr, "stop") 425 self.slurcount -= 1 426 427 def PhrasingSlur(self, phrslur): 428 r"""A \( or \).""" 429 if phrslur.token == '\(': 430 self.slurcount += 1 431 self.phrslurnr = self.slurcount 432 self.mediator.set_slur(self.phrslurnr, "start", phrasing=True) 433 elif phrslur.token == '\)': 434 self.mediator.set_slur(self.phrslurnr, "stop", phrasing=True) 435 self.slurcount -= 1 436 437 def Dynamic(self, dynamic): 438 """Any dynamic symbol.""" 439 self.mediator.new_dynamics(dynamic.token[1:]) 440 441 def Grace(self, grace): 442 self.grace_seq = True 443 444 def TimeSignature(self, timeSign): 445 self.mediator.new_time(timeSign.numerator(), timeSign.fraction(), self.numericTime) 446 447 def Repeat(self, repeat): 448 if repeat.specifier() == 'volta': 449 self.mediator.new_repeat('forward') 450 elif repeat.specifier() == 'tremolo': 451 self.trem_rep = repeat.repeat_count() 452 453 def Tremolo(self, tremolo): 454 """A tremolo item ":".""" 455 if self.look_ahead(tremolo, ly.music.items.Duration): 456 self.tremolo = True 457 else: 458 self.mediator.set_tremolo() 459 460 def With(self, cont_with): 461 r"""A \with ... construct.""" 462 self.with_contxt = cont_with.parent().context() 463 464 def Set(self, cont_set): 465 r"""A \set command.""" 466 if isinstance(cont_set.value(), ly.music.items.Scheme): 467 if cont_set.property() == 'tupletSpannerDuration': 468 moment = cont_set.value().get_ly_make_moment() 469 if moment: 470 self.mediator.set_tuplspan_dur(fraction=moment) 471 else: 472 self.mediator.unset_tuplspan_dur() 473 return 474 val = cont_set.value().get_string() 475 else: 476 val = cont_set.value().value() 477 if cont_set.context() in part_contexts: 478 self.mediator.set_by_property(cont_set.property(), val) 479 elif cont_set.context() in group_contexts: 480 self.mediator.set_by_property(cont_set.property(), val, group=True) 481 482 def Command(self, command): 483 r""" \bar, \rest etc """ 484 excls = ['\\major', '\\minor', '\\dorian', '\\bar'] 485 if command.token == '\\rest': 486 self.mediator.note2rest() 487 elif command.token == '\\numericTimeSignature': 488 self.numericTime = True 489 elif command.token == '\\defaultTimeSignature': 490 self.numericTime = False 491 elif command.token.find('voice') == 1: 492 self.mediator.set_voicenr(command.token[1:], piano=self.piano_staff) 493 elif command.token == '\\glissando': 494 try: 495 self.mediator.new_gliss(self.override_dict["Glissando.style"]) 496 except KeyError: 497 self.mediator.new_gliss() 498 elif command.token == '\\startTrillSpan': 499 self.mediator.new_trill_spanner() 500 elif command.token == '\\stopTrillSpan': 501 self.mediator.new_trill_spanner("stop") 502 elif command.token == '\\ottava': 503 self.ottava = True 504 elif command.token == '\\mark': 505 self.mark = True 506 self.mediator.new_mark() 507 elif command.token == '\\breathe': 508 art = type('',(object,),{"token": "\\breathe"})() 509 self.Articulation(art) 510 elif command.token == '\\stemUp' or command.token == '\\stemDown' or command.token == '\\stemNeutral': 511 self.mediator.stem_direction(command.token) 512 elif command.token == '\\default': 513 if self.tupl_span: 514 self.mediator.unset_tuplspan_dur() 515 self.tupl_span = False 516 elif self.mark: 517 self.mark = False 518 elif command.token == '\\compressFullBarRests': 519 self.mediator.set_mult_rest() 520 elif command.token == '\\break': 521 self.mediator.add_break() 522 else: 523 if command.token not in excls: 524 print("Unknown command:", command.token) 525 526 def UserCommand(self, usercommand): 527 """Music variables are substituted so this must be something else.""" 528 if usercommand.name() == 'tupletSpan': 529 self.tupl_span = True 530 531 def Markup(self, markup): 532 pass 533 534 def MarkupWord(self, markupWord): 535 self.mediator.new_word(markupWord.token) 536 537 def MarkupList(self, markuplist): 538 pass 539 540 def String(self, string): 541 prev = self.get_previous_node(string) 542 if prev and prev.token == '\\bar': 543 self.mediator.create_barline(string.value()) 544 545 def LyricsTo(self, lyrics_to): 546 r"""A \lyricsto expression. """ 547 self.mediator.new_lyric_section('lyricsto'+lyrics_to.context_id(), lyrics_to.context_id()) 548 self.sims_and_seqs.append('lyrics') 549 550 def LyricText(self, lyrics_text): 551 """A lyric text (word, markup or string), with a Duration.""" 552 self.mediator.new_lyrics_text(lyrics_text.token) 553 554 def LyricItem(self, lyrics_item): 555 """Another lyric item (skip, extender, hyphen or tie).""" 556 self.mediator.new_lyrics_item(lyrics_item.token) 557 558 def NoteMode(self, notemode): 559 r"""A \notemode or \notes expression.""" 560 self.alt_mode = 'note' 561 562 def ChordMode(self, chordmode): 563 r"""A \chordmode or \chords expression.""" 564 self.alt_mode = 'chord' 565 566 def DrumMode(self, drummode): 567 r"""A \drummode or \drums expression. 568 569 If the shorthand form \drums is found, DrumStaff is implicit. 570 571 """ 572 if drummode.token == '\\drums': 573 self.check_context('DrumStaff') 574 self.alt_mode = 'drum' 575 576 def FigureMode(self, figmode): 577 r"""A \figuremode or \figures expression.""" 578 self.alt_mode = 'figure' 579 580 def LyricMode(self, lyricmode): 581 r"""A \lyricmode, \lyrics or \addlyrics expression.""" 582 self.alt_mode = 'lyric' 583 584 def Override(self, override): 585 r"""An \override command.""" 586 self.override_key = '' 587 588 def PathItem(self, item): 589 r"""An item in the path of an \override or \revert command.""" 590 self.override_key += item.token 591 592 def Scheme(self, scheme): 593 """A Scheme expression inside LilyPond.""" 594 pass 595 596 def SchemeItem(self, item): 597 """Any scheme token.""" 598 if self.ottava: 599 self.mediator.new_ottava(item.token) 600 self.ottava = False 601 elif self.look_behind(item, ly.music.items.Override): 602 self.override_dict[self.override_key] = item.token 603 elif self.schm_assignm: 604 self.mediator.set_by_property(self.schm_assignm, item.token) 605 elif self.mark: 606 self.mediator.new_mark(int(item.token)) 607 else: 608 print("SchemeItem not implemented:", item.token) 609 610 def SchemeQuote(self, quote): 611 """A ' in scheme.""" 612 pass 613 614 def End(self, end): 615 if isinstance(end.node, ly.music.items.Scaler): 616 if self.unset_tuplspan: 617 self.mediator.unset_tuplspan_dur() 618 self.unset_tuplspan = False 619 if end.node.token != '\\scaleDurations': 620 self.mediator.change_tuplet_type(len(self.tuplet) - 1, "stop") 621 self.tuplet.pop() 622 self.fraction = None 623 elif isinstance(end.node, ly.music.items.Grace): #Grace 624 self.grace_seq = False 625 elif end.node.token == '\\repeat': 626 if end.node.specifier() == 'volta': 627 self.mediator.new_repeat('backward') 628 elif end.node.specifier() == 'tremolo': 629 if self.look_ahead(end.node, ly.music.items.MusicList): 630 self.mediator.set_tremolo(trem_type="stop") 631 else: 632 self.mediator.set_tremolo(trem_type="single") 633 self.trem_rep = 0 634 elif isinstance(end.node, ly.music.items.Context): 635 self.in_context = False 636 if end.node.context() == 'Voice': 637 self.mediator.check_voices() 638 self.sims_and_seqs.pop() 639 elif end.node.context() in group_contexts: 640 self.mediator.close_group() 641 elif end.node.context() in staff_contexts: 642 if not self.piano_staff: 643 self.mediator.check_part() 644 elif end.node.context() in pno_contexts: 645 self.mediator.check_voices() 646 self.mediator.check_part() 647 self.piano_staff = 0 648 self.mediator.set_voicenr(nr=1) 649 elif end.node.context() == 'Devnull': 650 self.mediator.check_voices() 651 elif end.node.token == '<<': 652 if self.voice_sep: 653 self.mediator.check_voices_by_nr() 654 self.mediator.revert_voicenr() 655 self.voice_sep = False 656 elif not self.piano_staff: 657 self.mediator.check_simultan() 658 if self.sims_and_seqs: 659 self.sims_and_seqs.pop() 660 elif end.node.token == '{': 661 if self.sims_and_seqs: 662 self.sims_and_seqs.pop() 663 elif end.node.token == '<': #chord 664 self.mediator.chord_end() 665 elif end.node.token == '\\lyricsto': 666 self.mediator.check_lyrics(end.node.context_id()) 667 self.sims_and_seqs.pop() 668 elif end.node.token == '\\with': 669 self.with_contxt = None 670 elif end.node.token == '\\drums': 671 self.mediator.check_part() 672 elif isinstance(end.node, ly.music.items.Relative): 673 self.relative = False 674 self.rel_pitch_isset = False 675 else: 676 # print("end:", end.node.token) 677 pass 678 679 ## 680 # Additional node manipulation 681 ## 682 683 def get_previous_node(self, node): 684 """ Returns the nodes previous node 685 or false if the node is first in its branch. """ 686 parent = node.parent() 687 i = parent.index(node) 688 if i > 0: 689 return parent[i-1] 690 else: 691 return False 692 693 def simple_node_gen(self, node): 694 """Unlike iter_score are the subnodes yielded without substitution.""" 695 for n in node: 696 yield n 697 for s in self.simple_node_gen(n): 698 yield s 699 700 def iter_header(self, tree): 701 """Iter only over header nodes.""" 702 for t in tree: 703 if isinstance(t, ly.music.items.Header): 704 return self.simple_node_gen(t) 705 706 def get_score(self, node): 707 """ Returns (first) Score node or false if no Score is found. """ 708 for n in node: 709 if isinstance(n, ly.music.items.Score) or isinstance(n, ly.music.items.Book): 710 return n 711 return False 712 713 def iter_score(self, scorenode, doc): 714 r""" 715 Iter over score. 716 717 Similarly to items.Document.iter_music user commands are substituted. 718 719 Furthermore \repeat unfold expressions are unfolded. 720 """ 721 for s in scorenode: 722 if isinstance(s, ly.music.items.Repeat) and s.specifier() == 'unfold': 723 for u in self.unfold_repeat(s, s.repeat_count(), doc): 724 yield u 725 else: 726 n = doc.substitute_for_node(s) or s 727 yield n 728 for c in self.iter_score(n, doc): 729 yield c 730 if isinstance(s, ly.music.items.Container): 731 yield End(s) 732 733 def unfold_repeat(self, repeat_node, repeat_count, doc): 734 r""" 735 Iter over node which represent a \repeat unfold expression 736 and do the unfolding directly. 737 """ 738 for r in range(repeat_count): 739 for n in repeat_node: 740 for c in self.iter_score(n, doc): 741 yield c 742 743 def find_score_sub(self, doc): 744 """Find substitute for scorenode. Takes first music node that isn't 745 an assignment.""" 746 for n in doc: 747 if not isinstance(n, ly.music.items.Assignment): 748 if isinstance(n, ly.music.items.Music): 749 return self.iter_score(n, doc) 750 751 def look_ahead(self, node, find_node): 752 """Looks ahead in a container node and returns True 753 if the search is successful.""" 754 for n in node: 755 if isinstance(n, find_node): 756 return True 757 return False 758 759 def look_behind(self, node, find_node): 760 """Looks behind on the parent node(s) and returns True 761 if the search is successful.""" 762 parent = node.parent() 763 if parent: 764 if isinstance(parent, find_node): 765 ret = True 766 else: 767 ret = self.look_behind(parent, find_node) 768 return ret 769 else: 770 return False 771 772 ## 773 # Other functions 774 ## 775 def gen_med_caller(self, func_name, *args): 776 """Call any function in the mediator object.""" 777 func_call = getattr(self.mediator, func_name) 778 func_call(*args) 779