Source code for abjad.wf

import enum
import typing

from . import duration as _duration
from . import get as _get
from . import indicators as _indicators
from . import instruments as _instruments
from . import iterate as _iterate
from . import iterpitches as _iterpitches
from . import parentage as _parentage
from . import score as _score
from . import sequence as _sequence
from . import tag as _tag


def _aggregate_context_wrappers(argument):
    """
    Special_Voice may contain other instances of Special_Voice.
    This currently happens with OnBeatGraceContainer.
    This method aggregates all Special_Voice wrappers for checks.
    """
    context_name_to_wrappers = {}
    for context in _iterate.components(argument, _score.Context):
        if context.name not in context_name_to_wrappers:
            context_name_to_wrappers[context.name] = []
        wrappers = context._dependent_wrappers[:]
        context_name_to_wrappers[context.name].extend(wrappers)
    return context_name_to_wrappers


[docs]def check_beamed_lone_notes(argument) -> tuple[list, int]: r""" Checks beamed lone notes. Beamed single notes are not wellformed: .. container:: example >>> voice = abjad.Voice("c'8 d' e' f'") >>> abjad.attach(abjad.StartBeam(), voice[0]) >>> abjad.attach(abjad.StopBeam(), voice[0]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 [ ] d'8 e'8 f'8 } >>> abjad.wf.check_beamed_lone_notes(voice) ([Note("c'8")], 4) The examples above feature Abjad voice containers because beams are voice-persistent. """ violators, total = [], 0 for leaf in _iterate.leaves(argument): total += 1 if _get.has_indicator(leaf, _indicators.StartBeam): if _get.has_indicator(leaf, _indicators.StopBeam): violators.append(leaf) return violators, total
[docs]def check_beamed_long_notes(argument) -> tuple[list, int]: r""" Checks beamed long notes. Beamed quarter notes are not wellformed: .. container:: example >>> voice = abjad.Voice("c'4 d'4 e'4 f'4") >>> abjad.attach(abjad.StartBeam(), voice[0]) >>> abjad.attach(abjad.StopBeam(), voice[1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 [ d'4 ] e'4 f'4 } >>> abjad.wf.check_beamed_long_notes(voice) ([Note("c'4"), Note("d'4")], 4) Beamed eighth notes are wellformed: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8") >>> abjad.attach(abjad.StartBeam(), voice[0]) >>> abjad.attach(abjad.StopBeam(), voice[1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 [ d'8 ] e'8 f'8 } >>> abjad.wf.check_beamed_long_notes(voice) ([], 4) The examples above feature Abjad voice containers because beams are voice-persistent. """ violators, total = [], 0 for leaf in _iterate.leaves(argument): total += 1 if leaf.written_duration < _duration.Duration((1, 4)): continue start_wrapper = _get.effective(leaf, _indicators.StartBeam, unwrap=False) if start_wrapper is None: continue stop_wrapper = _get.effective(leaf, _indicators.StopBeam, unwrap=False) if stop_wrapper is None: violators.append(leaf) continue if stop_wrapper.leaked_start_offset < start_wrapper.leaked_start_offset: violators.append(leaf) continue leaf_start_offset = leaf._get_timespan().start_offset if stop_wrapper.leaked_start_offset == leaf_start_offset: violators.append(leaf) return violators, total
[docs]def check_duplicate_ids(argument) -> tuple[list, int]: """ Checks duplicate IDs. """ violators = [] components = _iterate.components(argument) total_ids = [id(_) for _ in components] unique_ids = _sequence.remove_repeats(total_ids) if len(unique_ids) < len(total_ids): for current_id in unique_ids: if 1 < total_ids.count(current_id): violators.extend([_ for _ in components if id(_) == current_id]) return violators, len(total_ids)
[docs]def check_empty_containers(argument) -> tuple[list, int]: r""" Checks empty containers. .. container:: example >>> staff = abjad.Staff("c'4 d' e' f'") >>> staff.append(abjad.Container()) >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'4 d'4 e'4 f'4 { } } >>> violators, total = abjad.wf.check_empty_containers(staff) >>> violators [Container()] """ violators, containers = [], set() for container in _iterate.components(argument, _score.Container): containers.add(container) if len(container) == 0: violators.append(container) return violators, len(containers)
[docs]def check_missing_parents(argument) -> tuple[list, int]: """ Checks missing parents. """ violators, total = [], set() components = _iterate.components(argument) for i, component in enumerate(components): total.add(component) if 0 < i: # BeforeGraceContainer._parent is always none; # so must use Parentage.parent, # which calls BeforeGraceContainer._get_parentage() if _parentage.Parentage(component).parent is None: violators.append(component) return violators, len(total)
[docs]def check_notes_on_wrong_clef(argument) -> tuple[list, int]: r""" Checks notes and chords on wrong clef. .. container:: example >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") >>> clef = abjad.Clef('alto') >>> abjad.attach(clef, staff[0]) >>> violin = abjad.Violin() >>> abjad.attach(violin, staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "alto" c'8 d'8 e'8 f'8 } >>> abjad.wf.check_notes_on_wrong_clef(staff) ([Note("c'8"), Note("d'8"), Note("e'8"), Note("f'8")], 4) .. container:: example All instruments allow percussion clef: >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") >>> clef = abjad.Clef('percussion') >>> abjad.attach(clef, staff[0]) >>> violin = abjad.Violin() >>> abjad.attach(violin, staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "percussion" c'8 d'8 e'8 f'8 } >>> abjad.wf.check_notes_on_wrong_clef(staff) ([], 4) """ violators, total = [], set() for leaf in _iterate.leaves(argument): total.add(leaf) instrument = _get.effective(leaf, _instruments.Instrument) if instrument is None: continue effective_clef = _get.effective(leaf, _indicators.Clef) if effective_clef is None: continue clefs = [] for clef in instrument.clefs: if isinstance(clef, str): clef = _indicators.Clef(clef) assert isinstance(clef, _indicators.Clef), repr(clef) clefs.append(clef) clefs.append(_indicators.Clef("percussion")) if effective_clef not in clefs: violators.append(leaf) return violators, len(total)
[docs]def check_out_of_range_pitches( argument, *, allow_indicators: typing.Sequence[str | enum.Enum] = () ) -> tuple[list, int]: r""" Checks out-of-range notes. Out of range: .. container:: example >>> staff = abjad.Staff("c'8 r8 <d fs>8 r8") >>> violin = abjad.Violin() >>> abjad.attach(violin, staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 r8 <d fs>8 r8 } >>> abjad.wf.check_out_of_range_pitches(staff) ([Chord('<d fs>8')], 2) .. container:: example Using ``allow_indicators``: >>> staff = abjad.Staff("c'8 r8 <d fs>8 r8") >>> violin = abjad.Violin() >>> abjad.attach(violin, staff[0]) >>> abjad.attach("ALLOW_OUT_OF_RANGE", staff[2]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 r8 <d fs>8 r8 } Does not check for indicator: >>> violators, total = abjad.wf.check_out_of_range_pitches(staff) >>> violators [Chord('<d fs>8')] Does check for indicator: >>> violators, total = abjad.wf.check_out_of_range_pitches( ... staff, allow_indicators=["ALLOW_OUT_OF_RANGE"] ... ) >>> violators [] """ violators, total = [], set() for leaf in _iterate.leaves(argument, pitched=True): total.add(leaf) ok = False for indicator in allow_indicators or (): if leaf._has_indicator(indicator): ok = True if ok is True: continue if "unpitched" in argument._get_indicators(str): continue instrument = _get.effective(leaf, _instruments.Instrument) if instrument is None: continue if not _iterpitches.sounding_pitches_are_in_range(leaf, instrument.pitch_range): violators.append(leaf) return violators, len(total)
[docs]def check_overlapping_beams(argument) -> tuple[list, int]: r""" Checks overlapping beams. .. container:: example >>> voice = abjad.Voice("c'8 [ d' [ e' f' ]") >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 [ d'8 [ e'8 f'8 ] } >>> abjad.wf.check_overlapping_beams(voice) ([Note("d'8")], 3) >>> voice = abjad.Voice("c'8 [ d' [ e' ] f' ]") >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 [ d'8 [ e'8 ] f'8 ] } >>> abjad.wf.check_overlapping_beams(voice) ([Note("d'8")], 4) >>> voice = abjad.Voice("c'8 [ d' e' f' ]") >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 [ d'8 e'8 f'8 ] } >>> abjad.wf.check_overlapping_beams(voice) ([], 2) """ violators, total = [], 0 context_name_to_wrappers = _aggregate_context_wrappers(argument) for _, wrappers in context_name_to_wrappers.items(): wrappers.sort(key=lambda _: _get.timespan(_.component).start_offset) open_beam_count = 0 for wrapper in wrappers: if _get.grace(wrapper.component) is True: continue total += 1 if isinstance(wrapper.unbundle_indicator(), _indicators.StartBeam): open_beam_count += 1 elif isinstance(wrapper.unbundle_indicator(), _indicators.StopBeam): open_beam_count -= 1 if open_beam_count < 0 or 1 < open_beam_count: violators.append(wrapper.component) return violators, total
[docs]def check_overlapping_text_spanners(argument) -> tuple[list, int]: r""" Checks overlapping text spanners. Overlapping text spanners are not wellformed: .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.text_spanner(voice) >>> abjad.text_spanner(voice[1:3]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan d'4 \startTextSpan e'4 \stopTextSpan f'4 \stopTextSpan } >>> abjad.wf.check_overlapping_text_spanners(voice) ([Note("d'4")], 2) .. container:: example Overlapping text spanners with different IDs are wellformed: >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.text_spanner(voice) >>> command = r'\startTextSpanOne' >>> start_text_span = abjad.StartTextSpan(command=command) >>> abjad.attach(start_text_span, voice[1]) >>> command = r'\stopTextSpanOne' >>> stop_text_span = abjad.StopTextSpan(command=command) >>> abjad.attach(stop_text_span, voice[2]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan d'4 \startTextSpanOne e'4 \stopTextSpanOne f'4 \stopTextSpan } >>> abjad.wf.check_overlapping_text_spanners(voice) ([], 2) .. container:: example Enchained text spanners do not overlap (and are wellformed): >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.text_spanner(voice[:3]) >>> abjad.text_spanner(voice[-2:]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan d'4 e'4 \stopTextSpan \startTextSpan f'4 \stopTextSpan } >>> abjad.wf.check_overlapping_text_spanners(voice) ([], 2) .. container:: example REGRESSION. Matching start- and stop-text-spans on a single leaf do not overlap (and are wellformed) iff stop-text-span leaks to the right: >>> voice = abjad.Voice("c'2 d'2 e'2 f'2") >>> abjad.attach(abjad.StartTextSpan(), voice[0]) >>> stop_text_span = abjad.StopTextSpan(leak=True) >>> abjad.attach(stop_text_span, voice[0]) >>> abjad.attach(abjad.StartTextSpan(), voice[2]) >>> stop_text_span = abjad.StopTextSpan() >>> abjad.attach(stop_text_span, voice[3]) >>> abjad.show(voice) # doctest: +SKIP >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'2 \startTextSpan <> \stopTextSpan d'2 e'2 \startTextSpan f'2 \stopTextSpan } >>> abjad.wf.check_overlapping_text_spanners(voice) ([], 2) """ violators, total = [], 0 def key(wrapper): if isinstance(wrapper.unbundle_indicator(), _indicators.StartTextSpan): priority = 1 else: priority = 0 return (wrapper.leaked_start_offset, priority) context_name_to_wrappers = _aggregate_context_wrappers(argument) for _, wrappers in context_name_to_wrappers.items(): wrappers.sort(key=key) open_spanners: dict = {} for wrapper in wrappers: if wrapper.deactivate is True: continue if isinstance(wrapper.unbundle_indicator(), _indicators.StartTextSpan): total += 1 command = wrapper.unbundle_indicator().command command = command.replace("start", "") command = command.replace("Start", "") if command not in open_spanners: open_spanners[command] = [] if open_spanners[command]: violators.append(wrapper.component) open_spanners[command].append(wrapper.component) elif isinstance(wrapper.unbundle_indicator(), _indicators.StopTextSpan): command = wrapper.unbundle_indicator().command command = command.replace("stop", "") command = command.replace("Stop", "") if command in open_spanners and open_spanners[command]: open_spanners[command].pop() return violators, total
[docs]def check_unmatched_stop_text_spans(argument) -> tuple[list, int]: r""" Checks unmatched stop text spans. Unmatched stop text span is not wellformed: .. container:: example >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> stop_text_span = abjad.StopTextSpan() >>> abjad.attach(stop_text_span, voice[-1]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 c'4 c'4 c'4 \stopTextSpan } >>> abjad.wf.check_unmatched_stop_text_spans(voice) ([Note("c'4")], 0) Matched stop text span is wellformed: >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_text_span = abjad.StartTextSpan() >>> abjad.attach(start_text_span, voice[0]) >>> stop_text_span = abjad.StopTextSpan() >>> abjad.attach(stop_text_span, voice[-1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan c'4 c'4 c'4 \stopTextSpan } >>> abjad.wf.wellformed(voice) True """ violators, total = [], 0 context_name_to_wrappers = _aggregate_context_wrappers(argument) for _, wrappers in context_name_to_wrappers.items(): wrappers.sort(key=lambda _: _.leaked_start_offset) open_spanners: dict = {} for wrapper in wrappers: if isinstance(wrapper.unbundle_indicator(), _indicators.StartTextSpan): total += 1 command = wrapper.unbundle_indicator().command command = command.replace("start", "") command = command.replace("Start", "") if command not in open_spanners: open_spanners[command] = [] open_spanners[command].append(wrapper.component) elif isinstance(wrapper.unbundle_indicator(), _indicators.StopTextSpan): command = wrapper.unbundle_indicator().command command = command.replace("stop", "") command = command.replace("Stop", "") if command not in open_spanners or not open_spanners[command]: violators.append(wrapper.component) else: open_spanners[command].pop() return violators, total
[docs]def check_unterminated_hairpins(argument) -> tuple[list, int]: r""" Checks unterminated hairpins. Unterminated crescendo is not wellformed: .. container:: example >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_hairpin = abjad.StartHairpin('<') >>> abjad.attach(start_hairpin, voice[0]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \< c'4 c'4 c'4 } >>> abjad.wf.check_unterminated_hairpins(voice) ([Note("c'4")], 1) Even with start dynamic: >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> dynamic = abjad.Dynamic('f') >>> abjad.attach(dynamic, voice[0]) >>> start_hairpin = abjad.StartHairpin('<') >>> abjad.attach(start_hairpin, voice[0]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \f \< c'4 c'4 c'4 } >>> abjad.wf.check_unterminated_hairpins(voice) ([Note("c'4")], 1) Terminated crescendo is wellformed: >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_hairpin = abjad.StartHairpin('<') >>> abjad.attach(start_hairpin, voice[0]) >>> dynamic = abjad.Dynamic('f') >>> abjad.attach(dynamic, voice[-1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \< c'4 c'4 c'4 \f } >>> abjad.wf.wellformed(voice) True .. container:: example Bang-terminated crescendo is wellformed: >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_hairpin = abjad.StartHairpin('<') >>> abjad.attach(start_hairpin, voice[0]) >>> stop_hairpin = abjad.StopHairpin() >>> abjad.attach(stop_hairpin, voice[-1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \< c'4 c'4 c'4 \! } >>> abjad.wf.wellformed(voice) True """ violators, total = [], 0 context_name_to_wrappers = _aggregate_context_wrappers(argument) for _, wrappers in context_name_to_wrappers.items(): last_dynamic = None last_tag = _tag.Tag() wrappers.sort(key=lambda _: _.leaked_start_offset) for wrapper in wrappers: parameter = getattr(wrapper.unbundle_indicator(), "parameter", None) if parameter == "DYNAMIC" or isinstance( wrapper.unbundle_indicator(), _indicators.StopHairpin ): last_dynamic = wrapper.unbundle_indicator() last_tag = wrapper.tag if isinstance(wrapper.unbundle_indicator(), _indicators.StartHairpin): total += 1 if ( isinstance(last_dynamic, _indicators.StartHairpin) and _tag.Tag("RIGHT_BROKEN").string not in last_tag.string ): violators.append(wrapper.component) return violators, total
[docs]def check_unterminated_text_spanners(argument) -> tuple[list, int]: r""" Checks unterminated text spanners. Unterminated text spanner is not wellformed: .. container:: example >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_text_span = abjad.StartTextSpan() >>> abjad.attach(start_text_span, voice[0]) >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan c'4 c'4 c'4 } >>> abjad.wf.check_unterminated_text_spanners(voice) ([Note("c'4")], 1) Terminated text span is wellformed: >>> voice = abjad.Voice("c'4 c'4 c'4 c'4") >>> start_text_span = abjad.StartTextSpan() >>> abjad.attach(start_text_span, voice[0]) >>> stop_text_span = abjad.StopTextSpan() >>> abjad.attach(stop_text_span, voice[-1]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTextSpan c'4 c'4 c'4 \stopTextSpan } >>> abjad.wf.wellformed(voice) True """ violators, total = [], 0 context_name_to_wrappers = _aggregate_context_wrappers(argument) for _, wrappers in context_name_to_wrappers.items(): wrappers.sort(key=lambda _: _.leaked_start_offset) open_spanners: dict = {} for wrapper in wrappers: if wrapper.deactivate is True: continue if isinstance(wrapper.unbundle_indicator(), _indicators.StartTextSpan): total += 1 command = wrapper.unbundle_indicator().command command = command.replace("start", "") command = command.replace("Start", "") if command not in open_spanners: open_spanners[command] = [] open_spanners[command].append(wrapper.component) elif isinstance(wrapper.unbundle_indicator(), _indicators.StopTextSpan): command = wrapper.unbundle_indicator().command command = command.replace("stop", "") command = command.replace("Stop", "") if command not in open_spanners or not open_spanners[command]: # unmatched stop text span pass else: open_spanners[command].pop() for command, list_ in open_spanners.items(): for component in list_: violators.append(component) return violators, total
_globals = globals() def _call_functions( component, check_beamed_lone_notes: bool = True, check_beamed_long_notes: bool = True, check_duplicate_ids: bool = True, check_empty_containers: bool = True, check_missing_parents: bool = True, check_notes_on_wrong_clef: bool = True, check_out_of_range_pitches: bool = True, check_overlapping_beams: bool = True, check_overlapping_text_spanners: bool = True, check_unmatched_stop_text_spans: bool = True, check_unterminated_hairpins: bool = True, check_unterminated_text_spanners: bool = True, ): triples = [] if check_beamed_lone_notes: name = "check_beamed_lone_notes" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_beamed_long_notes: name = "check_beamed_long_notes" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_duplicate_ids: name = "check_duplicate_ids" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_empty_containers: name = "check_empty_containers" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_missing_parents: name = "check_missing_parents" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_notes_on_wrong_clef: name = "check_notes_on_wrong_clef" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_out_of_range_pitches: name = "check_out_of_range_pitches" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_overlapping_beams: name = "check_overlapping_beams" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_overlapping_text_spanners: name = "check_overlapping_text_spanners" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_unmatched_stop_text_spans: name = "check_unmatched_stop_text_spans" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_unterminated_hairpins: name = "check_unterminated_hairpins" violators, count = _globals[name](component) triples.append((violators, count, name)) if check_unterminated_text_spanners: name = "check_unterminated_text_spanners" violators, count = _globals[name](component) triples.append((violators, count, name)) return triples
[docs]def tabulate_wellformedness( component, check_beamed_lone_notes: bool = True, check_beamed_long_notes: bool = True, check_duplicate_ids: bool = True, check_empty_containers: bool = True, check_missing_parents: bool = True, check_notes_on_wrong_clef: bool = True, check_out_of_range_pitches: bool = True, check_overlapping_beams: bool = True, check_overlapping_text_spanners: bool = True, check_unmatched_stop_text_spans: bool = True, check_unterminated_hairpins: bool = True, check_unterminated_text_spanners: bool = True, ) -> tuple[int, str]: """ Tabulates wellformedness. """ triples = _call_functions( component, check_beamed_lone_notes=check_beamed_lone_notes, check_beamed_long_notes=check_beamed_long_notes, check_duplicate_ids=check_duplicate_ids, check_empty_containers=check_empty_containers, check_missing_parents=check_missing_parents, check_notes_on_wrong_clef=check_notes_on_wrong_clef, check_out_of_range_pitches=check_out_of_range_pitches, check_overlapping_beams=check_overlapping_beams, check_overlapping_text_spanners=check_overlapping_text_spanners, check_unmatched_stop_text_spans=check_unmatched_stop_text_spans, check_unterminated_hairpins=check_unterminated_hairpins, check_unterminated_text_spanners=check_unterminated_text_spanners, ) strings = [] total_violators = 0 for violators, total, name in triples: violator_count = len(violators) name = name.replace("check_", "") name = name.replace("_", " ") string = f"{violator_count} / {total} {name}" strings.append(string) total_violators += violator_count return total_violators, "\n".join(strings)
[docs]def wellformed( component, check_beamed_lone_notes: bool = True, check_beamed_long_notes: bool = True, check_duplicate_ids: bool = True, check_empty_containers: bool = True, check_missing_parents: bool = True, check_notes_on_wrong_clef: bool = True, check_out_of_range_pitches: bool = True, check_overlapping_beams: bool = True, check_overlapping_text_spanners: bool = True, check_unmatched_stop_text_spans: bool = True, check_unterminated_hairpins: bool = True, check_unterminated_text_spanners: bool = True, ) -> bool: """ Is true when ``component`` is wellformed. """ triples = _call_functions( component, check_beamed_lone_notes=check_beamed_lone_notes, check_beamed_long_notes=check_beamed_long_notes, check_duplicate_ids=check_duplicate_ids, check_empty_containers=check_empty_containers, check_missing_parents=check_missing_parents, check_notes_on_wrong_clef=check_notes_on_wrong_clef, check_out_of_range_pitches=check_out_of_range_pitches, check_overlapping_beams=check_overlapping_beams, check_overlapping_text_spanners=check_overlapping_text_spanners, check_unmatched_stop_text_spans=check_unmatched_stop_text_spans, check_unterminated_hairpins=check_unterminated_hairpins, check_unterminated_text_spanners=check_unterminated_text_spanners, ) for violators, total, name in triples: if violators: return False return True