Source code for abjad.mutate

import collections
import copy as python_copy
import itertools

from . import _getlib
from . import bind as _bind
from . import duration as _duration
from . import enums as _enums
from . import exceptions as _exceptions
from . import get as _get
from . import indicators as _indicators
from . import iterate as _iterate
from . import makers as _makers
from . import parentage as _parentage
from . import pitch as _pitch
from . import score as _score
from . import select as _select
from . import sequence as _sequence
from . import spanners as _spanners
from . import tag as _tag


def _are_contiguous_logical_voice(
    selection, prototype=None, *, ignore_before_after_grace=None
) -> bool:
    r"""
    Is true when items in selection are contiguous components in the same logical voice.

    ..  container:: example

        >>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
        >>> abjad.mutate._are_contiguous_logical_voice(staff[:])
        True

        >>> staves = [staff[0], staff[-1]]
        >>> abjad.mutate._are_contiguous_logical_voice(staves)
        False

    ..  container:: example

        REGRESSION. Before-grace music may be ignored:

        >>> voice = abjad.Voice("c'4 d' e' f'")
        >>> container = abjad.BeforeGraceContainer("cs'16")
        >>> abjad.attach(container, voice[1])
        >>> abjad.show(voice) # doctest: +SKIP

        ..  docs::

            >>> string = abjad.lilypond(voice)
            >>> print(string)
            \new Voice
            {
                c'4
                \grace {
                    cs'16
                }
                d'4
                e'4
                f'4
            }

        >>> voice[:]
        [Note("c'4"), Note("d'4"), Note("e'4"), Note("f'4")]

        >>> abjad.mutate._are_contiguous_logical_voice(voice[:])
        False

        >>> abjad.mutate._are_contiguous_logical_voice(
        ...     voice[:],
        ...     ignore_before_after_grace=True
        ... )
        True

        After-grace music may be ignored, too:

        >>> voice = abjad.Voice("c'4 d' e' f'")
        >>> container = abjad.AfterGraceContainer("cs'16")
        >>> abjad.attach(container, voice[0])
        >>> abjad.show(voice) # doctest: +SKIP

        ..  docs::

            >>> string = abjad.lilypond(voice)
            >>> print(string)
            \new Voice
            {
                \afterGrace
                c'4
                {
                    cs'16
                }
                d'4
                e'4
                f'4
            }

        >>> voice[:]
        [Note("c'4"), Note("d'4"), Note("e'4"), Note("f'4")]

        >>> abjad.mutate._are_contiguous_logical_voice(voice[:])
        False

        >>> abjad.mutate._are_contiguous_logical_voice(
        ...     voice[:],
        ...     ignore_before_after_grace=True
        ... )
        True

    """
    if not isinstance(selection, collections.abc.Sequence):
        return False
    prototype = prototype or (_score.Component,)
    if not isinstance(prototype, tuple):
        prototype = (prototype,)
    assert isinstance(prototype, tuple)
    if len(selection) == 0:
        return True
    if all(isinstance(_, prototype) and _._parent is None for _ in selection):
        return True
    first = selection[0]
    if not isinstance(first, prototype):
        return False
    first_parentage = _parentage.Parentage(first)
    first_logical_voice = first_parentage.logical_voice()
    first_root = first_parentage.root
    previous = first
    for current in selection[1:]:
        current_parentage = _parentage.Parentage(current)
        current_logical_voice = current_parentage.logical_voice()
        # false if wrong type of component found
        if not isinstance(current, prototype):
            return False
        # false if in different logical voices
        if current_logical_voice != first_logical_voice:
            return False
        # false if components are in same score and are discontiguous
        if current_parentage.root == first_root:
            if not _immediately_precedes(
                previous,
                current,
                ignore_before_after_grace=ignore_before_after_grace,
            ):
                return False
        previous = current
    return True


def _are_contiguous_same_parent(
    self, prototype=None, *, ignore_before_after_grace=None
) -> bool:
    r"""
    Is true when items in selection are all contiguous components in the same parent.

    ..  container:: example

        >>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
        >>> abjad.mutate._are_contiguous_same_parent(staff[:])
        True

        >>> staves = [staff[0], staff[-1]]
        >>> abjad.mutate._are_contiguous_same_parent(staves)
        False

    ..  container:: example

        REGRESSION. Before-grace music music may be ignored:

        >>> voice = abjad.Voice("c'4 d' e' f'")
        >>> container = abjad.BeforeGraceContainer("cs'16")
        >>> abjad.attach(container, voice[1])
        >>> abjad.show(voice) # doctest: +SKIP

        ..  docs::

            >>> string = abjad.lilypond(voice)
            >>> print(string)
            \new Voice
            {
                c'4
                \grace {
                    cs'16
                }
                d'4
                e'4
                f'4
            }

        >>> voice[:]
        [Note("c'4"), Note("d'4"), Note("e'4"), Note("f'4")]

        >>> abjad.mutate._are_contiguous_same_parent(voice[:])
        False

        >>> abjad.mutate._are_contiguous_same_parent(
        ...     voice[:],
        ...     ignore_before_after_grace=True
        ... )
        True

        After-grace music may be ignored, too:

        >>> voice = abjad.Voice("c'4 d' e' f'")
        >>> container = abjad.AfterGraceContainer("cs'16")
        >>> abjad.attach(container, voice[0])
        >>> abjad.show(voice) # doctest: +SKIP

        ..  docs::

            >>> string = abjad.lilypond(voice)
            >>> print(string)
            \new Voice
            {
                \afterGrace
                c'4
                {
                    cs'16
                }
                d'4
                e'4
                f'4
            }

        >>> voice[:]
        [Note("c'4"), Note("d'4"), Note("e'4"), Note("f'4")]

        >>> abjad.mutate._are_contiguous_same_parent(voice[:])
        False

        >>> abjad.mutate._are_contiguous_same_parent(
        ...     voice[:],
        ...     ignore_before_after_grace=True
        ... )
        True

    """
    prototype = prototype or (_score.Component,)
    if not isinstance(prototype, tuple):
        prototype = (prototype,)
    assert isinstance(prototype, tuple)
    if len(self) == 0:
        return True
    if all(isinstance(_, prototype) and _._parent is None for _ in self):
        return True
    first = self[0]
    if not isinstance(first, prototype):
        return False
    first_parent = first._parent
    same_parent = True
    strictly_contiguous = True
    previous = first
    for current in self[1:]:
        if not isinstance(current, prototype):
            return False
        if current._parent is not first_parent:
            same_parent = False
        if not _immediately_precedes(
            previous,
            current,
            ignore_before_after_grace=ignore_before_after_grace,
        ):
            strictly_contiguous = False
        if current._parent is not None and (not same_parent or not strictly_contiguous):
            return False
        previous = current
    return True


def _attach_tie_to_leaves(selection):
    for leaf in selection[:-1]:
        _bind.detach(_indicators.Tie, leaf)
        _bind.attach(_indicators.Tie(), leaf)


def _copy_selection(selection):
    assert _are_contiguous_logical_voice(selection)
    new_components = []
    for component in selection:
        if isinstance(component, _score.Container):
            new_component = component._copy_with_children()
        else:
            new_component = component.__copy__()
        new_components.append(new_component)
    new_components = type(selection)(new_components)
    return new_components


def _extract(component):
    selection = [component]
    parent, start, stop = _get_parent_and_start_stop_indices(selection)
    if parent is not None:
        components = list(getattr(component, "components", ()))
        parent.__setitem__(slice(start, stop + 1), components)
    return component


def _fuse(components, *, tag=None):
    assert _are_contiguous_logical_voice(components)
    if all(isinstance(_, _score.Leaf) for _ in components):
        return _fuse_leaves(components, tag=tag)
    elif all(isinstance(_, _score.Tuplet) for _ in components):
        return _fuse_tuplets(components, tag=tag)
    else:
        raise Exception(f"can only fuse leaves and tuplets (not {components}).")


def _fuse_leaves(leaves, *, tag=None):
    assert all(isinstance(_, _score.Leaf) for _ in leaves)
    assert _are_contiguous_logical_voice(leaves)
    if len(leaves) <= 1:
        return leaves
    originally_tied = leaves[-1]._has_indicator(_indicators.Tie)
    total_preprolated = sum(_._get_preprolated_duration() for _ in leaves)
    for leaf in leaves[1:]:
        parent = leaf._parent
        if parent:
            index = parent.index(leaf)
            del parent[index]
    result = _set_leaf_duration(leaves[0], total_preprolated, tag=tag)
    if not originally_tied:
        last_leaf = _select.leaf(result, -1)
        _bind.detach(_indicators.Tie, last_leaf)
    return result


# TODO: remove because unused?
def _fuse_leaves_by_immediate_parent(leaves, *, tag=None):
    result = []
    parts = _get_leaves_grouped_by_immediate_parents(leaves)
    for part in parts:
        fused = _fuse(part, tag=tag)
        result.append(fused)
    return result


def _fuse_tuplets(tuplets, *, tag=None):
    assert _are_contiguous_same_parent(tuplets, prototype=_score.Tuplet)
    if len(tuplets) == 0:
        return None
    first = tuplets[0]
    first_multiplier = first.multiplier
    for tuplet in tuplets[1:]:
        if tuplet.multiplier != first_multiplier:
            raise ValueError("tuplets must carry same multiplier.")
    assert isinstance(first, _score.Tuplet)
    new_tuplet = _score.Tuplet(first_multiplier, [], tag=tag)
    wrapped = False
    if _get.parentage(tuplets[0]).root is not _get.parentage(tuplets[-1]).root:
        dummy_container = _score.Container(tuplets)
        wrapped = True
    swap(tuplets, new_tuplet)
    if wrapped:
        del dummy_container[:]
    return new_tuplet


def _get_leaves_grouped_by_immediate_parents(leaves):
    result = []
    pairs_generator = itertools.groupby(leaves, lambda _: id(_._parent))
    for key, values_generator in pairs_generator:
        group = list(values_generator)
        result.append(group)
    return result


def _give_components_to_empty_container(selection, container):
    assert _are_contiguous_same_parent(selection)
    assert isinstance(container, _score.Container)
    assert not container
    components = []
    for component in selection:
        components.extend(getattr(component, "components", ()))
    container._components.extend(components)
    _set_parents(container)


def _get_parent_and_start_stop_indices(selection, ignore_before_after_grace=None):
    assert _are_contiguous_same_parent(
        selection, ignore_before_after_grace=ignore_before_after_grace
    )
    if selection:
        first, last = selection[0], selection[-1]
        parent = first._parent
        if parent is not None:
            first_index = parent.index(first)
            last_index = parent.index(last)
            return parent, first_index, last_index
    return None, None, None


def _give_position_in_parent_to_container(selection, container):
    assert _are_contiguous_same_parent(selection)
    assert isinstance(container, _score.Container)
    parent, start, stop = _get_parent_and_start_stop_indices(selection)
    if parent is not None:
        parent._components.__setitem__(slice(start, start), [container])
        container._set_parent(parent)
        for component in selection:
            component._set_parent(None)


def _immediately_precedes(component_1, component_2, ignore_before_after_grace=None):
    successors = []
    current = component_1
    # do not include OnBeatGraceContainer here because
    # OnBeatGraceContainer is a proper container
    grace_prototype = (_score.AfterGraceContainer, _score.BeforeGraceContainer)
    while current is not None:
        sibling = _getlib._get_sibling_with_graces(current, 1)
        while (
            ignore_before_after_grace
            and sibling is not None
            and isinstance(sibling._parent, grace_prototype)
        ):
            sibling = _getlib._get_sibling_with_graces(sibling, 1)
        if sibling is None:
            current = current._parent
        else:
            descendants = sibling._get_descendants_starting_with()
            successors = descendants
            break
    return component_2 in successors


def _set_leaf_duration(leaf, new_duration, *, tag=None):
    new_duration = _duration.Duration(new_duration)
    if leaf.multiplier is not None:
        multiplier = new_duration.__div__(leaf.written_duration)
        leaf.multiplier = _duration.pair(multiplier)
        return [leaf]
    try:
        leaf.written_duration = new_duration
        return [leaf]
    except _exceptions.AssignabilityError:
        pass
    components = _makers.make_notes(0, new_duration, tag=tag)
    new_leaves = _select.leaves(components)
    following_leaf_count = len(new_leaves) - 1
    following_leaves = []
    for i in range(following_leaf_count):
        following_leaf = copy(leaf)
        for indicator in _get.indicators(following_leaf):
            if i != following_leaf_count - 1:
                if getattr(indicator, "time_orientation", _enums.LEFT) != _enums.MIDDLE:
                    _bind.detach(indicator, following_leaf)
            elif (
                getattr(indicator, "time_orientation", _enums.LEFT) != _enums.RIGHT
                and getattr(indicator, "time_orientation", _enums.LEFT) != _enums.MIDDLE
            ):
                _bind.detach(indicator, following_leaf)
        _bind.detach(_score.BeforeGraceContainer, following_leaf)
        following_leaves.append(following_leaf)
    if following_leaf_count > 0:
        for indicator in _get.indicators(leaf):
            if getattr(indicator, "time_orientation", _enums.LEFT) == _enums.RIGHT:
                _bind.detach(indicator, leaf)
    all_leaves = [leaf] + following_leaves
    assert len(all_leaves) == len(new_leaves)
    for all_leaf, new_leaf in zip(all_leaves, new_leaves):
        all_leaf.written_duration = new_leaf.written_duration
    logical_tie = _get.logical_tie(leaf)
    logical_tie_leaves = list(logical_tie)
    for leaf_ in logical_tie:
        _bind.detach(_indicators.Tie, leaf_)
        _bind.detach(_indicators.RepeatTie, leaf_)
    if leaf._parent is not None:
        index = leaf._parent.index(leaf)
        next_ = index + 1
        leaf._parent[next_:next_] = following_leaves
    index = logical_tie_leaves.index(leaf)
    next_ = index + 1
    logical_tie_leaves[next_:next_] = following_leaves
    if 1 < len(logical_tie_leaves) and isinstance(leaf, _score.Note | _score.Chord):
        _spanners.tie(logical_tie_leaves)
    if isinstance(components[0], _score.Leaf):
        assert isinstance(all_leaves, list)
        return all_leaves
    else:
        assert isinstance(components[0], _score.Tuplet)
        assert len(components) == 1
        tuplet = components[0]
        multiplier = tuplet.multiplier
        tuplet = _score.Tuplet(multiplier, [])
        assert isinstance(all_leaves, list)
        wrap(all_leaves, tuplet)
        return [tuplet]


def _set_parents(container):
    for component in container:
        component._set_parent(container)


def _split_container_at_index(CONTAINER, i):
    """
    Splits container to the left of index ``i``.

    Preserves tuplet multiplier when container is a tuplet.

    Preserves time signature denominator when container is a measure.

    Resizes resizable containers.


    Returns split parts.
    """
    # partition my components
    left_components = CONTAINER[:i]
    right_components = CONTAINER[i:]
    # instantiate new left and right containers
    if isinstance(CONTAINER, _score.Tuplet):
        multiplier = CONTAINER.multiplier
        left = type(CONTAINER)(multiplier, [])
        wrap(left_components, left)
        right = type(CONTAINER)(multiplier, [])
        wrap(right_components, right)
    else:
        left = CONTAINER.__copy__()
        wrap(left_components, left)
        right = CONTAINER.__copy__()
        wrap(right_components, right)
    # save left and right containers together for iteration
    halves = (left, right)
    nonempty_halves = [half for half in halves if len(half)]
    # incorporate left and right parents in score if possible
    selection = [CONTAINER]
    parent, start, stop = _get_parent_and_start_stop_indices(selection)
    if parent is not None:
        parent._components.__setitem__(slice(start, stop + 1), nonempty_halves)
        for part in nonempty_halves:
            part._set_parent(parent)
    else:
        left._set_parent(None)
        right._set_parent(None)
    # return new left and right containers
    return halves


def _split_container_by_duration(CONTAINER, duration, *, tag=None):
    if CONTAINER.simultaneous:
        return _split_simultaneous_by_duration(CONTAINER, duration=duration, tag=tag)
    duration = _duration.Duration(duration)
    assert 0 <= duration, repr(duration)
    if duration == 0:
        # TODO: disallow and raise Exception
        return [], CONTAINER
    # get split point score offset
    timespan = _get.timespan(CONTAINER)
    global_split_point = timespan.start_offset + duration
    # get any duration-crossing descendents
    cross_offset = timespan.start_offset + duration
    duration_crossing_descendants = []
    for descendant in _get.descendants(CONTAINER):
        timespan = _get.timespan(descendant)
        start_offset = timespan.start_offset
        stop_offset = timespan.stop_offset
        if start_offset < cross_offset < stop_offset:
            duration_crossing_descendants.append(descendant)
    # any duration-crossing leaf will be at end of list
    bottom = duration_crossing_descendants[-1]
    did_split_leaf = False
    # if split point necessitates leaf split
    if isinstance(bottom, _score.Leaf):
        assert isinstance(bottom, _score.Leaf)
        original_bottom_parent = bottom._parent
        did_split_leaf = True
        timespan = _get.timespan(bottom)
        split_point_in_bottom = global_split_point - timespan.start_offset
        new_leaves = _split_leaf_by_durations(
            bottom,
            [split_point_in_bottom],
            tag=tag,
        )
        if new_leaves[0]._parent is not original_bottom_parent:
            new_leaves_tuplet_wrapper = new_leaves[0]._parent
            assert isinstance(new_leaves_tuplet_wrapper, _score.Tuplet)
            assert new_leaves_tuplet_wrapper._parent is original_bottom_parent
            _split_container_by_duration(
                new_leaves_tuplet_wrapper,
                split_point_in_bottom,
                tag=tag,
            )
        for leaf in new_leaves:
            timespan = _get.timespan(leaf)
            if timespan.stop_offset == global_split_point:
                leaf_left_of_split = leaf
            if timespan.start_offset == global_split_point:
                leaf_right_of_split = leaf
        duration_crossing_containers = duration_crossing_descendants[:-1]
        assert len(duration_crossing_containers)
    # if split point falls between leaves
    # then find leaf to immediate right of split point
    # in order to start upward crawl through duration-crossing containers
    else:
        duration_crossing_containers = duration_crossing_descendants[:]
        for leaf in _iterate.leaves(bottom):
            timespan = _get.timespan(leaf)
            if timespan.start_offset == global_split_point:
                leaf_right_of_split = leaf
                leaf_left_of_split = _get.leaf(leaf, -1)
                break
        else:
            raise Exception("can not split empty container {bottom!r}.")
    assert leaf_left_of_split is not None
    assert leaf_right_of_split is not None
    # find component to right of split
    # that is also immediate child of last duration-crossing container
    for component in leaf_right_of_split._get_parentage():
        if component._parent is duration_crossing_containers[-1]:
            highest_level_component_right_of_split = component
            break
    else:
        raise ValueError("should not be able to get here.")
    # crawl back up through duration-crossing containers and split each
    previous = highest_level_component_right_of_split
    for container in reversed(duration_crossing_containers):
        assert isinstance(container, _score.Container)
        index = container.index(previous)
        left, right = _split_container_at_index(container, index)
        previous = right
    # reapply tie here if crawl above killed tie applied to leaves
    if did_split_leaf:
        if isinstance(leaf_left_of_split, _score.Note):
            if (
                _get.parentage(leaf_left_of_split).root
                is _get.parentage(leaf_right_of_split).root
            ):
                leaves_around_split = (
                    leaf_left_of_split,
                    leaf_right_of_split,
                )
                _attach_tie_to_leaves(leaves_around_split)
    # return list-wrapped halves of container
    return [left], [right]


def _split_simultaneous_by_duration(CONTAINER, duration, *, tag=None):
    assert CONTAINER.simultaneous
    left_components, right_components = [], []
    for component in CONTAINER[:]:
        halves = _split_container_by_duration(component, duration=duration, tag=tag)
        left_components_, right_components_ = halves
        left_components.extend(left_components_)
        right_components.extend(right_components_)
    left_container = CONTAINER.__copy__()
    right_container = CONTAINER.__copy__()
    left_container.extend(left_components)
    right_container.extend(right_components)
    if _get.parentage(CONTAINER).parent is not None:
        containers = [left_container, right_container]
        replace(CONTAINER, containers)
    # return list-wrapped halves of container
    return [left_container], [right_container]


def _split_leaf_by_durations(leaf, durations, *, cyclic=False, tag=None):
    durations = [_duration.Duration(_) for _ in durations]
    leaf_duration = _get.duration(leaf)
    if cyclic:
        durations = _sequence.repeat_to_weight(durations, leaf_duration)
    if sum(durations) < leaf_duration:
        last_duration = leaf_duration - sum(durations)
        durations = list(durations)
        durations.append(last_duration)
    durations = _sequence.truncate(durations, weight=leaf_duration)
    originally_tied = leaf._has_indicator(_indicators.Tie)
    originally_repeat_tied = leaf._has_indicator(_indicators.RepeatTie)
    result_selections = []
    # detach grace containers
    before_grace_container = leaf._before_grace_container
    if before_grace_container is not None:
        _bind.detach(before_grace_container, leaf)
    after_grace_container = leaf._after_grace_container
    if after_grace_container is not None:
        _bind.detach(after_grace_container, leaf)
    # do other things
    leaf_prolation = _get.parentage(leaf).prolation
    for duration in durations:
        new_leaf = python_copy.copy(leaf)
        preprolated_duration = duration / leaf_prolation
        selection = _set_leaf_duration(new_leaf, preprolated_duration, tag=tag)
        result_selections.append(selection)
    result_components = _sequence.flatten(result_selections, depth=-1)
    result_leaves = _select.leaves(result_components, grace=False)
    assert all(isinstance(_, _score.Component) for _ in result_components)
    assert all(isinstance(_, _score.Leaf) for _ in result_leaves)
    # strip result leaves of all indicators
    for leaf_ in result_leaves:
        _bind.detach(object, leaf_)
    # replace leaf with flattened result
    if _get.parentage(leaf).parent is not None:
        replace(leaf, result_components)
    # move indicators
    first_result_leaf = result_leaves[0]
    last_result_leaf = result_leaves[-1]
    for indicator in _get.indicators(leaf):
        _bind.detach(indicator, leaf)
        direction = getattr(indicator, "time_orientation", _enums.LEFT)
        if direction is _enums.LEFT:
            _bind.attach(indicator, first_result_leaf)
        elif direction == _enums.RIGHT:
            _bind.attach(indicator, last_result_leaf)
        elif direction == _enums.MIDDLE:
            _bind.attach(indicator, first_result_leaf)
            indicator_copy = python_copy.copy(indicator)
            _bind.attach(indicator_copy, last_result_leaf)
        else:
            raise ValueError(direction)
    # reattach grace containers
    if before_grace_container is not None:
        _bind.attach(before_grace_container, first_result_leaf)
    if after_grace_container is not None:
        _bind.attach(after_grace_container, last_result_leaf)
    # fuse tuplets
    if isinstance(result_components[0], _score.Tuplet):
        fuse(result_components)
    # tie split notes
    if isinstance(leaf, _score.Note | _score.Chord) and 1 < len(result_leaves):
        _attach_tie_to_leaves(result_leaves)
    if originally_repeat_tied and not result_leaves[0]._has_indicator(
        _indicators.RepeatTie
    ):
        _bind.attach(_indicators.RepeatTie(), result_leaves[0])
    if originally_tied and not result_leaves[-1]._has_indicator(_indicators.Tie):
        _bind.attach(_indicators.Tie(), result_leaves[-1])
    assert isinstance(result_leaves, list)
    assert all(isinstance(_, _score.Leaf) for _ in result_leaves)
    return result_leaves


[docs]def copy(argument, n=1) -> list[_score.Component]: r""" Copies argument. Copies explicit clefs: .. container:: example >>> staff = abjad.Staff("c'8 cs'8 d'8 ef'8 e'8 f'8 fs'8 g'8") >>> clef = abjad.Clef('treble') >>> abjad.attach(clef, staff[0]) >>> clef = abjad.Clef('bass') >>> abjad.attach(clef, staff[4]) >>> copied_notes = abjad.mutate.copy(staff[:2]) >>> staff.extend(copied_notes) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "treble" c'8 cs'8 d'8 ef'8 \clef "bass" e'8 f'8 fs'8 g'8 \clef "treble" c'8 cs'8 } .. container:: example Does not copy implicit clefs: >>> staff = abjad.Staff("c'8 cs'8 d'8 ef'8 e'8 f'8 fs'8 g'8") >>> clef = abjad.Clef('treble') >>> abjad.attach(clef, staff[0]) >>> clef = abjad.Clef('bass') >>> abjad.attach(clef, staff[4]) >>> copied_notes = abjad.mutate.copy(staff[2:4]) >>> staff.extend(copied_notes) .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "treble" c'8 cs'8 d'8 ef'8 \clef "bass" e'8 f'8 fs'8 g'8 d'8 ef'8 } .. container:: example Copy components one time: >>> staff = abjad.Staff(r"c'8 d'8 e'8 f'8") >>> staff.extend(r"g'8 a'8 b'8 c''8") >>> score = abjad.Score([staff], name="Score") >>> time_signature = abjad.TimeSignature((2, 4)) >>> abjad.attach(time_signature, staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \time 2/4 c'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8 } >>> selection = staff[2:4] >>> result = abjad.mutate.copy(selection) >>> new_staff = abjad.Staff(result) >>> abjad.show(new_staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(new_staff) >>> print(string) \new Staff { e'8 f'8 } >>> staff[2] is new_staff[0] False """ if isinstance(argument, _score.Component): selection = [argument] else: selection = argument if n == 1: result = _copy_selection(selection) if isinstance(argument, _score.Component): if len(result) == 1: result = result[0] return result else: result = [] for _ in range(n): result_ = copy(argument) result.append(result_) return result
[docs]def eject_contents(container: _score.Container) -> list[_score.Component]: r""" Ejects ``container`` contents. Ejects leaves from container: .. container:: example >>> container = abjad.Container("c'4 ~ c'4 d'4 ~ d'4") >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'4 ~ c'4 d'4 ~ d'4 } >>> leaves = abjad.mutate.eject_contents(container) >>> leaves [Note("c'4"), Note("c'4"), Note("d'4"), Note("d'4")] Leaves can be added to a new container: >>> staff = abjad.Staff(leaves, lilypond_type="RhythmicStaff") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new RhythmicStaff { c'4 ~ c'4 d'4 ~ d'4 } Old container is empty: >>> container Container() """ assert isinstance(container, _score.Container), repr(container) components = container[:] container[:] = [] return components
[docs]def extract(argument): r""" Extracts ``argument`` from score. Leaves children of ``argument`` in score. .. container:: example Extract tuplets: >>> voice = abjad.Voice() >>> voice.append(abjad.Tuplet((3, 2), "c'4 e'4")) >>> voice.append(abjad.Tuplet((3, 2), "d'4 f'4")) >>> leaves = abjad.select.leaves(voice) >>> staff = abjad.Staff([voice]) >>> score = abjad.Score([staff], name="Score") >>> time_signature = abjad.TimeSignature((3, 4)) >>> abjad.attach(time_signature, leaves[0]) >>> abjad.hairpin('p < f', leaves) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \tweak text #tuplet-number::calc-fraction-text \times 3/2 { \time 3/4 c'4 \p \< e'4 } \tweak text #tuplet-number::calc-fraction-text \times 3/2 { d'4 f'4 \f } } } >>> empty_tuplet = abjad.mutate.extract(voice[-1]) >>> empty_tuplet = abjad.mutate.extract(voice[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \time 3/4 c'4 \p \< e'4 d'4 f'4 \f } } .. container:: example Scales tuplet contents and then extracts tuplet: >>> voice = abjad.Voice() >>> staff = abjad.Staff([voice]) >>> voice.append(abjad.Tuplet((3, 2), "c'4 e'4")) >>> voice.append(abjad.Tuplet((3, 2), "d'4 f'4")) >>> score = abjad.Score([staff], name="Score") >>> leaves = abjad.select.leaves(staff) >>> abjad.hairpin('p < f', leaves) >>> time_signature = abjad.TimeSignature((3, 4)) >>> abjad.attach(time_signature, leaves[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \tweak text #tuplet-number::calc-fraction-text \times 3/2 { \time 3/4 c'4 \p \< e'4 } \tweak text #tuplet-number::calc-fraction-text \times 3/2 { d'4 f'4 \f } } } >>> abjad.mutate.scale(voice[-1], abjad.Fraction(3, 2)) >>> empty_tuplet = abjad.mutate.extract(voice[-1]) >>> abjad.mutate.scale(voice[0], abjad.Fraction(3, 2)) >>> empty_tuplet = abjad.mutate.extract(voice[0]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \time 3/4 c'4. \p \< e'4. d'4. f'4. \f } } .. container:: example Extracting out-of-score component does nothing and returns component: >>> tuplet = abjad.Tuplet((3, 2), "c'4 e'4") >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(tuplet) >>> print(string) \tweak text #tuplet-number::calc-fraction-text \times 3/2 { c'4 e'4 } >>> abjad.mutate.extract(tuplet) Tuplet('2:3', "c'4 e'4") >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(tuplet) >>> print(string) \tweak text #tuplet-number::calc-fraction-text \times 3/2 { c'4 e'4 } Returns ``argument``. """ return _extract(argument)
[docs]def fuse(argument) -> _score.Tuplet | list[_score.Leaf]: r""" Fuses ``argument``. Fuses in-score leaves: .. container:: example >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") >>> abjad.show(staff) # doctest: +SKIP >>> abjad.mutate.fuse(staff[1:]) [Note("d'4.")] >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 d'4. } .. container:: example Fuses parent-contiguous tuplets in selection: >>> tuplet_1 = abjad.Tuplet((2, 3), "c'8 d' e'") >>> tuplet_2 = abjad.Tuplet((2, 3), "c'16 d' e'") >>> voice = abjad.Voice([tuplet_1, tuplet_2]) >>> staff = abjad.Staff([voice]) >>> abjad.beam(tuplet_1[:]) >>> abjad.slur(tuplet_2[:]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \times 2/3 { c'8 [ d'8 e'8 ] } \times 2/3 { c'16 ( d'16 e'16 ) } } } >>> tuplets = voice[:] >>> abjad.mutate.fuse(tuplets) Tuplet('3:2', "c'8 d'8 e'8 c'16 d'16 e'16") >>> abjad.show(staff) #doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { \times 2/3 { c'8 [ d'8 e'8 ] c'16 ( d'16 e'16 ) } } } Returns new tuplet in selection. Fuses zero or more parent-contiguous ``tuplets``. Allows in-score ``tuplets``. Allows outside-of-score ``tuplets``. All ``tuplets`` must carry the same multiplier. All ``tuplets`` must be of the same type. .. container:: example REGRESSION. Trims tie from fused note: >>> staff = abjad.Staff("d'8 ~ d'32 ~ d'16 d'32") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { d'8 ~ d'32 ~ d'16 d'32 } >>> logical_tie = abjad.select.logical_tie(staff[0]) >>> abjad.mutate.fuse(logical_tie) [Note("d'8..")] >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { d'8.. d'32 } >>> abjad.get.has_indicator(staff[0], abjad.Tie) False """ _are_contiguous_logical_voice(argument) if isinstance(argument, _score.Component): result = _fuse([argument]) else: result = _fuse(list(argument)) assert isinstance(result, list | _score.Tuplet), repr(result) return result
[docs]def logical_tie_to_tuplet( argument, proportions, *, tag: _tag.Tag | None = None ) -> _score.Tuplet: r""" Changes logical tie to tuplet. .. container:: example >>> voice = abjad.Voice(r"df'8 c'8 ~ c'16 cqs''4") >>> staff = abjad.Staff([voice]) >>> score = abjad.Score([staff], name="Score") >>> abjad.attach(abjad.Dynamic('p'), voice[0]) >>> abjad.attach(abjad.StartHairpin('<'), voice[0]) >>> abjad.attach(abjad.Dynamic('f'), voice[-1]) >>> abjad.override(staff).DynamicLineSpanner.staff_padding = 3 >>> time_signature = abjad.TimeSignature((9, 16)) >>> abjad.attach(time_signature, voice[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override DynamicLineSpanner.staff-padding = 3 } { \new Voice { \time 9/16 df'8 \p \< c'8 ~ c'16 cqs''4 \f } } >>> logical_tie = abjad.select.logical_tie(voice[1]) >>> abjad.mutate.logical_tie_to_tuplet(logical_tie, [2, 1, 1, 1]) Tuplet('5:3', "c'8 c'16 c'16 c'16") .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override DynamicLineSpanner.staff-padding = 3 } { \new Voice { \time 9/16 df'8 \p \< \tweak text #tuplet-number::calc-fraction-text \times 3/5 { c'8 c'16 c'16 c'16 } cqs''4 \f } } >>> abjad.show(staff) # doctest: +SKIP .. container:: example >>> voice = abjad.Voice(r"c'8 ~ c'16 cqs''4") >>> staff = abjad.Staff([voice]) >>> score = abjad.Score([staff], name="Score") >>> abjad.hairpin('p < f', voice[:]) >>> abjad.override(staff).DynamicLineSpanner.staff_padding = 3 >>> time_signature = abjad.TimeSignature((7, 16)) >>> abjad.attach(time_signature, voice[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override DynamicLineSpanner.staff-padding = 3 } { \new Voice { \time 7/16 c'8 \p \< ~ c'16 cqs''4 \f } } """ assert all(isinstance(_, int) for _ in proportions), repr(proportions) target_duration = sum(_._get_preprolated_duration() for _ in argument) assert isinstance(target_duration, _duration.Duration) prolated_duration = target_duration / sum(proportions) basic_written_duration = prolated_duration.equal_or_greater_power_of_two written_durations = [_ * basic_written_duration for _ in proportions] notes: list[_score.Note | _score.Tuplet] try: notes = [_score.Note(0, _) for _ in written_durations] except _exceptions.AssignabilityError: denominator = target_duration._denominator note_durations = [_duration.Duration(_, denominator) for _ in proportions] notes = _makers.make_notes(0, note_durations, tag=tag) tuplet = _score.Tuplet.from_duration(target_duration, notes, tag=tag) for leaf in argument: _bind.detach(_indicators.Tie, leaf) _bind.detach(_indicators.RepeatTie, leaf) replace(argument, tuplet) return tuplet
[docs]def replace(argument, recipients, wrappers=False): r""" Replaces ``argument`` (and contents of ``argument``) with ``recipients``. .. container:: example Replaces in-score tuplet (and children of tuplet) with notes. Functions exactly the same as container setitem: >>> tuplet_1 = abjad.Tuplet((2, 3), "c'4 d'4 e'4") >>> tuplet_2 = abjad.Tuplet((2, 3), "d'4 e'4 f'4") >>> voice = abjad.Voice([tuplet_1, tuplet_2]) >>> leaves = abjad.select.leaves(voice) >>> abjad.hairpin('p < f', leaves) >>> abjad.slur(leaves) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \times 2/3 { c'4 \p ( \< d'4 e'4 } \times 2/3 { d'4 e'4 f'4 \f ) } } >>> notes = abjad.makers.make_notes("c' d' e' f' c' d' e' f'", (1, 16)) >>> abjad.mutate.replace([tuplet_1], notes) >>> abjad.attach(abjad.Dynamic('p'), voice[0]) >>> abjad.attach(abjad.StartHairpin('<'), voice[0]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'16 \p \< d'16 e'16 f'16 c'16 d'16 e'16 f'16 \times 2/3 { d'4 e'4 f'4 \f ) } } Preserves both hairpin and slur. .. container:: example Copies no wrappers when ``wrappers`` is false: >>> staff = abjad.Staff("c'2 f'4 g'") >>> abjad.attach(abjad.Clef('alto'), staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "alto" c'2 f'4 g'4 } >>> for leaf in staff: ... leaf, abjad.get.effective(leaf, abjad.Clef) ... (Note("c'2"), Clef(name='alto', hide=False)) (Note("f'4"), Clef(name='alto', hide=False)) (Note("g'4"), Clef(name='alto', hide=False)) >>> chord = abjad.Chord("<d' e'>2") >>> abjad.mutate.replace(staff[0], chord) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { <d' e'>2 f'4 g'4 } >>> for leaf in staff: ... leaf, abjad.get.effective(leaf, abjad.Clef) ... (Chord("<d' e'>2"), None) (Note("f'4"), None) (Note("g'4"), None) >>> abjad.wf.wellformed(staff) True .. container:: example Set ``wrappers`` to true to copy all wrappers from one leaf to another leaf (and avoid full-score update). Only works from one leaf to another leaf: >>> staff = abjad.Staff("c'2 f'4 g'") >>> abjad.attach(abjad.Clef('alto'), staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "alto" c'2 f'4 g'4 } >>> for leaf in staff: ... leaf, abjad.get.effective(leaf, abjad.Clef) ... (Note("c'2"), Clef(name='alto', hide=False)) (Note("f'4"), Clef(name='alto', hide=False)) (Note("g'4"), Clef(name='alto', hide=False)) >>> chord = abjad.Chord("<d' e'>2") >>> abjad.mutate.replace(staff[0], chord, wrappers=True) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \clef "alto" <d' e'>2 f'4 g'4 } >>> for leaf in staff: ... leaf, abjad.get.effective(leaf, abjad.Clef) ... (Chord("<d' e'>2"), Clef(name='alto', hide=False)) (Note("f'4"), Clef(name='alto', hide=False)) (Note("g'4"), Clef(name='alto', hide=False)) >>> abjad.wf.wellformed(staff) True .. container:: example .. todo:: Fix. Introduces duplicate ties: >>> staff = abjad.Staff("c'2 ~ c'2") >>> tied_notes = abjad.makers.make_notes(0, abjad.Duration(5, 8)) >>> abjad.mutate.replace(staff[:1], tied_notes) >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'2 ~ c'8 c'2 } Returns none. """ if isinstance(argument, _score.Component): donors = [argument] else: donors = argument assert _are_contiguous_same_parent(donors) if not isinstance(recipients, list): if isinstance(recipients, _score.Component): recipients = [recipients] else: recipients = list(recipients) assert _are_contiguous_same_parent(recipients) if not donors: return if wrappers is True: if 1 < len(donors) or not isinstance(donors[0], _score.Leaf): raise Exception(f"set wrappers only with single leaf: {donors!r}.") if 1 < len(recipients) or not isinstance(recipients[0], _score.Leaf): raise Exception(f"set wrappers only with single leaf: {recipients!r}.") donor = donors[0] wrappers = _get.wrappers(donor) recipient = recipients[0] parent, start, stop = _get_parent_and_start_stop_indices(donors) assert parent is not None, repr(donors) parent.__setitem__(slice(start, stop + 1), recipients) if not wrappers: return for wrapper in wrappers: # bypass Wrapper._bind_component() # to avoid full-score update / traversal; # this works because one-to-one leaf replacement # including all (persistent) indicators # doesn't change score structure: donor._wrappers.remove(wrapper) wrapper._component = recipient recipient._wrappers.append(wrapper) context = wrapper._find_correct_effective_context( wrapper.component, wrapper.context ) if context is not None: context._dependent_wrappers.append(wrapper)
[docs]def scale(argument, multiplier) -> None: r""" Scales ``argument`` by ``multiplier``. Scales note duration by dot-generating multiplier: .. container:: example >>> staff = abjad.Staff("c'8 ( d'8 e'8 f'8 )") >>> abjad.show(staff) # doctest: +SKIP >>> abjad.mutate.scale(staff[1], abjad.Fraction(3, 2)) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 ( d'8. e'8 f'8 ) } .. container:: example Scales tied leaves by dot-generating mutliplier: >>> staff = abjad.Staff(r"c'8 \accent ~ c'8 d'8") >>> score = abjad.Score([staff], name="Score") >>> time_signature = abjad.TimeSignature((3, 8)) >>> abjad.attach(time_signature, staff[0]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \time 3/8 c'8 - \accent ~ c'8 d'8 } >>> logical_tie = abjad.select.logical_tie(staff[0]) >>> logical_tie = abjad.mutate.scale(logical_tie, abjad.Fraction(3, 2)) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \time 3/8 c'8. - \accent ~ c'8. d'8 } .. container:: example Scales leaves in container by dot-generating multiplier: >>> container = abjad.Container(r"c'8 ( d'8 e'8 f'8 )") >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'8 ( d'8 e'8 f'8 ) } >>> abjad.mutate.scale(container, abjad.Fraction(3, 2)) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'8. ( d'8. e'8. f'8. ) } .. container:: example Scales leaves in tuplet: >>> staff = abjad.Staff() >>> score = abjad.Score([staff], name="Score") >>> tuplet = abjad.Tuplet((4, 5), "c'8 d'8 e'8 f'8 g'8") >>> staff.append(tuplet) >>> time_signature = abjad.TimeSignature((4, 8)) >>> leaf = abjad.get.leaf(staff, 0) >>> abjad.attach(time_signature, leaf) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \times 4/5 { \time 4/8 c'8 d'8 e'8 f'8 g'8 } } >>> abjad.mutate.scale(tuplet, abjad.Fraction(2)) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \times 4/5 { \time 4/8 c'4 d'4 e'4 f'4 g'4 } } .. container:: example Scales leaves carrying LilyPond multiplier: >>> note = abjad.Note("c'8", multiplier=(1, 2)) >>> abjad.show(note) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(note) >>> print(string) c'8 * 1/2 >>> abjad.mutate.scale(note, abjad.Fraction(1, 2)) >>> abjad.show(note) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(note) >>> print(string) c'16 * 1/2 """ if hasattr(argument, "_scale"): argument._scale(multiplier) else: assert isinstance(argument, list) for component in argument: component._scale(multiplier)
# TODO: add tests of tupletted notes and rests. # TODO: add examples that show indicator handling. # TODO: add example showing grace and after grace handling.
[docs]def split(argument, durations, *, cyclic=False, tag=None): r""" Splits ``argument`` by ``durations``. .. container:: example Splits leaves cyclically and ties split notes: >>> voice = abjad.Voice("c'1 d'1") >>> abjad.hairpin('p < f', voice[:]) >>> abjad.override(voice).DynamicLineSpanner.staff_padding = 3 >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice \with { \override DynamicLineSpanner.staff-padding = 3 } { c'1 \p \< d'1 \f } >>> durations = [(3, 4)] >>> result = abjad.mutate.split( ... voice[:], ... durations, ... cyclic=True, ... ) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice \with { \override DynamicLineSpanner.staff-padding = 3 } { c'2. \p \< ~ c'4 d'2 \f ~ d'2 } .. container:: example Splits custom voice and preserves context name: >>> voice = abjad.Voice( ... "c'4 d' e' f'", ... lilypond_type='CustomVoice', ... name='1', ... ) >>> staff = abjad.Staff([voice]) >>> abjad.hairpin('p < f', voice[:]) >>> abjad.override(staff).DynamicLineSpanner.staff_padding = 3 >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override DynamicLineSpanner.staff-padding = 3 } { \context CustomVoice = "1" { c'4 \p \< d'4 e'4 f'4 \f } } >>> durations = [(1, 8)] >>> result = abjad.mutate.split( ... staff[:], ... durations, ... cyclic=True, ... ) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override DynamicLineSpanner.staff-padding = 3 } { \context CustomVoice = "1" { c'8 \p \< ~ } \context CustomVoice = "1" { c'8 } \context CustomVoice = "1" { d'8 ~ } \context CustomVoice = "1" { d'8 } \context CustomVoice = "1" { e'8 ~ } \context CustomVoice = "1" { e'8 } \context CustomVoice = "1" { f'8 \f ~ } \context CustomVoice = "1" { f'8 } } >>> for voice in staff: ... voice ... Voice("c'8", lilypond_type='CustomVoice', name='1') Voice("c'8", lilypond_type='CustomVoice', name='1') Voice("d'8", lilypond_type='CustomVoice', name='1') Voice("d'8", lilypond_type='CustomVoice', name='1') Voice("e'8", lilypond_type='CustomVoice', name='1') Voice("e'8", lilypond_type='CustomVoice', name='1') Voice("f'8", lilypond_type='CustomVoice', name='1') Voice("f'8", lilypond_type='CustomVoice', name='1') .. container:: example Splits parallel container: >>> voice_1 = abjad.Voice( ... "e''4 ( es'' f'' fs'' )", ... name='Voice_1', ... ) >>> voice_2 = abjad.Voice( ... r"c'4 \p \< cs' d' ds' \f", ... name='Voice_2', ... ) >>> abjad.override(voice_1).Stem.direction = abjad.UP >>> abjad.override(voice_1).Slur.direction = abjad.UP >>> container = abjad.Container( ... [voice_1, voice_2], ... simultaneous=True, ... ) >>> abjad.override(voice_2).Stem.direction = abjad.DOWN >>> staff = abjad.Staff([container]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { << \context Voice = "Voice_1" \with { \override Slur.direction = #up \override Stem.direction = #up } { e''4 ( es''4 f''4 fs''4 ) } \context Voice = "Voice_2" \with { \override Stem.direction = #down } { c'4 \p \< cs'4 d'4 ds'4 \f } >> } >>> durations = [(3, 8)] >>> result = abjad.mutate.split( ... container, ... durations, ... cyclic=False, ... ) >>> abjad.show(staff) # doctest: +SKIP >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { << \context Voice = "Voice_1" \with { \override Slur.direction = #up \override Stem.direction = #up } { e''4 ( es''8 ~ } \context Voice = "Voice_2" \with { \override Stem.direction = #down } { c'4 \p \< cs'8 ~ } >> << \context Voice = "Voice_1" \with { \override Slur.direction = #up \override Stem.direction = #up } { es''8 f''4 fs''4 ) } \context Voice = "Voice_2" \with { \override Stem.direction = #down } { cs'8 d'4 ds'4 \f } >> } .. container:: example Splits leaves with articulations: >>> staff = abjad.Staff("c'4 d' e' f'") >>> abjad.attach(abjad.Articulation('^'), staff[0]) >>> abjad.attach(abjad.LaissezVibrer(), staff[1]) >>> abjad.attach(abjad.Articulation('^'), staff[2]) >>> abjad.attach(abjad.LaissezVibrer(), staff[3]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'4 - \marcato d'4 \laissezVibrer e'4 - \marcato f'4 \laissezVibrer } >>> durations = [(1, 8)] >>> result = abjad.mutate.split( ... staff[:], ... durations, ... cyclic=True, ... ) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 - \marcato ~ c'8 d'8 ~ d'8 \laissezVibrer e'8 - \marcato ~ e'8 f'8 ~ f'8 \laissezVibrer } .. container:: example REGRESSION. Preserves tie: >>> staff = abjad.Staff("d'2 ~ d'") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { d'2 ~ d'2 } >>> result = abjad.mutate.split(staff[0], [(1, 32)]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { d'32 ~ d'4... ~ d'2 } Returns list of selections. """ components = argument if isinstance(components, _score.Component): components = [components] assert all(isinstance(_, _score.Component) for _ in components) durations = [_duration.Duration(_) for _ in durations] assert len(durations), repr(durations) total_component_duration = _get.duration(components) total_split_duration = sum(durations) if cyclic: durations = _sequence.repeat_to_weight(durations, total_component_duration) durations = list(durations) elif total_split_duration < total_component_duration: final_offset = total_component_duration - sum(durations) durations.append(final_offset) elif total_component_duration < total_split_duration: weight = total_component_duration durations = _sequence.truncate(durations, weight=weight) durations = list(durations) # keep copy of durations to partition result components durations_copy = durations[:] total_split_duration = sum(durations) assert total_split_duration == total_component_duration result, shard = [], [] offset_index = 0 current_shard_duration = _duration.Duration(0) remaining_components = list(components[:]) advance_to_next_offset = True # build shards: # grab next component and next duration each time through loop while True: # grab next split point if advance_to_next_offset: if not durations: break next_split_point = durations.pop(0) advance_to_next_offset = True # grab next component from input stack of components if not remaining_components: break current_component = remaining_components.pop(0) # find where current component endpoint will position us duration_ = _get.duration(current_component) candidate_shard_duration = current_shard_duration + duration_ # if current component would fill current shard exactly if candidate_shard_duration == next_split_point: shard.append(current_component) result.append(shard) shard = [] current_shard_duration = _duration.Duration(0) offset_index += 1 # if current component would exceed current shard elif next_split_point < candidate_shard_duration: local_split_duration = next_split_point local_split_duration -= current_shard_duration if isinstance(current_component, _score.Leaf): leaf_split_durations = [local_split_duration] duration_ = _get.duration(current_component) current_duration = duration_ additional_required_duration = current_duration additional_required_duration -= local_split_duration split_durations = _sequence.split( durations, [additional_required_duration], cyclic=False, overhang=True, ) split_durations = [list(_) for _ in split_durations] additional_durations = split_durations[0] leaf_split_durations.extend(additional_durations) durations = split_durations[-1] leaf_shards = _split_leaf_by_durations( current_component, leaf_split_durations, cyclic=False, tag=tag, ) shard.extend(leaf_shards) result.append(shard) offset_index += len(additional_durations) else: assert isinstance(current_component, _score.Container) pair = _split_container_by_duration( current_component, local_split_duration, tag=tag, ) left_list, right_list = pair shard.extend(left_list) result.append(shard) remaining_components.__setitem__(slice(0, 0), right_list) shard = [] offset_index += 1 current_shard_duration = _duration.Duration(0) # if current component would not fill current shard elif candidate_shard_duration < next_split_point: shard.append(current_component) duration_ = _get.duration(current_component) current_shard_duration += duration_ advance_to_next_offset = False else: message = "can not process candidate duration: {!r}." message = message.format(candidate_shard_duration) raise ValueError(message) # append any stub shard if len(shard): result.append(shard) # append any unexamined components if len(remaining_components): result.append(remaining_components) # partition split components according to input durations result = _sequence.flatten(result, depth=-1) result = _select.partition_by_durations(result, durations_copy, fill=_enums.EXACT) # return list of shards assert all(isinstance(_, list) for _ in result) return result
[docs]def swap(argument, container): r""" Swaps ``argument`` for empty ``container``. .. container:: example Swaps containers for tuplet: >>> voice = abjad.Voice() >>> staff = abjad.Staff([voice]) >>> score = abjad.Score([staff], name="Score") >>> voice.append(abjad.Container("c'4 d'4 e'4")) >>> voice.append(abjad.Container("d'4 e'4 f'4")) >>> abjad.attach(abjad.TimeSignature((3, 4)), voice[0][0]) >>> leaves = abjad.select.leaves(voice) >>> abjad.hairpin('p < f', leaves) >>> measures = voice[:] >>> abjad.slur(leaves) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { { \time 3/4 c'4 \p ( \< d'4 e'4 } { d'4 e'4 f'4 \f ) } } >>> containers = voice[:] >>> tuplet = abjad.Tuplet((2, 3), []) >>> tuplet.denominator = 4 >>> abjad.mutate.swap(containers, tuplet) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \times 4/6 { \time 3/4 c'4 \p ( \< d'4 e'4 d'4 e'4 f'4 \f ) } } REGRESSION: context indicators survive swap: >>> prototype = abjad.TimeSignature >>> for component in abjad.iterate.components(voice): ... time_signature = abjad.get.effective(component, prototype) ... print(component, time_signature) ... Voice("{ 2/3 c'4 d'4 e'4 d'4 e'4 f'4 }") TimeSignature(pair=(3, 4), hide=False, partial=None) Tuplet('3:2', "c'4 d'4 e'4 d'4 e'4 f'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("c'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("d'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("e'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("d'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("e'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Note("f'4") TimeSignature(pair=(3, 4), hide=False, partial=None) Returns none. """ if isinstance(argument, list): donors = argument else: assert isinstance(argument, _score.Component) donors = [argument] assert _are_contiguous_same_parent(donors) assert isinstance(container, _score.Container) assert not container, repr(container) _give_components_to_empty_container(donors, container) _give_position_in_parent_to_container(donors, container)
[docs]def transpose(argument, interval): r""" Transposes notes and chords in ``argument`` by ``interval``. .. todo:: Move to abjad.pitch package. .. container:: example Transposes notes and chords in staff: >>> staff = abjad.Staff() >>> score = abjad.Score([staff], name="Score") >>> staff.extend("c'4 d'4 e'4 r4") >>> abjad.attach(abjad.TimeSignature((4, 4)), staff[0]) >>> staff.extend("d'4 e'4 <f' a' c''>4") >>> abjad.attach(abjad.TimeSignature((3, 4)), staff[4]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \time 4/4 c'4 d'4 e'4 r4 \time 3/4 d'4 e'4 <f' a' c''>4 } >>> abjad.mutate.transpose(staff, "+m3") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \time 4/4 ef'4 f'4 g'4 r4 \time 3/4 f'4 g'4 <af' c'' ef''>4 } Returns none. """ named_interval = _pitch.NamedInterval(interval) for item in _iterate.components(argument, (_score.Note, _score.Chord)): if isinstance(item, _score.Note): old_written_pitch = item.note_head.written_pitch new_written_pitch = old_written_pitch.transpose(named_interval) item.note_head.written_pitch = new_written_pitch else: for note_head in item.note_heads: old_written_pitch = note_head.written_pitch new_written_pitch = old_written_pitch.transpose(named_interval) note_head.written_pitch = new_written_pitch
[docs]def wrap(argument, container): r""" Wraps ``argument`` in empty ``container``. .. container:: example Wraps in-score notes in tuplet: >>> staff = abjad.Staff("c'8 [ ( d' e' ] ) c' [ ( d' e' ] )") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 [ ( d'8 e'8 ) ] c'8 [ ( d'8 e'8 ) ] } >>> tuplet = abjad.Tuplet((2, 3), []) >>> abjad.mutate.wrap(staff[-3:], tuplet) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'8 [ ( d'8 e'8 ) ] \times 2/3 { c'8 [ ( d'8 e'8 ) ] } } .. container:: example Wraps outside-score notes in tuplet: >>> notes = abjad.makers.make_notes([0, 2, 4], [(1, 8)]) >>> tuplet = abjad.Tuplet((2, 3), []) >>> abjad.mutate.wrap(notes, tuplet) >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(tuplet) >>> print(string) \times 2/3 { c'8 d'8 e'8 } (This usage merely substitutes for the tuplet initializer.) .. container:: example Wraps leaves in container: >>> notes = [abjad.Note(n, (1, 8)) for n in range(8)] >>> staff = abjad.Staff(notes) >>> score = abjad.Score([staff], name="Score") >>> abjad.attach(abjad.TimeSignature((4, 8)), staff[0]) >>> container = abjad.Container() >>> abjad.mutate.wrap(staff[:4], container) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { { \time 4/8 c'8 cs'8 d'8 ef'8 } e'8 f'8 fs'8 g'8 } .. container:: example Wraps each leaf in tuplet: >>> notes = [abjad.Note(n, (1, 1)) for n in range(4)] >>> staff = abjad.Staff(notes) >>> for note in staff: ... tuplet = abjad.Tuplet((2, 3)) ... abjad.mutate.wrap(note, tuplet) ... .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \tweak edge-height #'(0.7 . 0) \times 2/3 { c'1 } \tweak edge-height #'(0.7 . 0) \times 2/3 { cs'1 } \tweak edge-height #'(0.7 . 0) \times 2/3 { d'1 } \tweak edge-height #'(0.7 . 0) \times 2/3 { ef'1 } } .. container:: example Raises exception on nonempty ``container``: >>> import pytest >>> staff = abjad.Staff("c'8 [ ( d' e' ] ) c' [ ( d' e' ] )") >>> tuplet = abjad.Tuplet((2, 3), "g'8 a' fs'") >>> abjad.mutate.wrap(staff[-3:], tuplet) Traceback (most recent call last): ... Exception: must be empty container: Tuplet('3:2', "g'8 a'8 fs'8"). .. container:: example REGRESSION. Contexted indicators (like time signature) survive wrap: >>> staff = abjad.Staff("c'4 d' e' f'") >>> score = abjad.Score([staff], name="Score") >>> leaves = abjad.select.leaves(staff) >>> abjad.attach(abjad.TimeSignature((3, 8)), leaves[0]) >>> container = abjad.Container() >>> abjad.mutate.wrap(leaves, container) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { { \time 3/8 c'4 d'4 e'4 f'4 } } >>> prototype = abjad.TimeSignature >>> for component in abjad.iterate.components(staff): ... time_signature = abjad.get.effective(component, prototype) ... print(component, time_signature) ... Staff("{ c'4 d'4 e'4 f'4 }") TimeSignature(pair=(3, 8), hide=False, partial=None) Container("c'4 d'4 e'4 f'4") TimeSignature(pair=(3, 8), hide=False, partial=None) Note("c'4") TimeSignature(pair=(3, 8), hide=False, partial=None) Note("d'4") TimeSignature(pair=(3, 8), hide=False, partial=None) Note("e'4") TimeSignature(pair=(3, 8), hide=False, partial=None) Note("f'4") TimeSignature(pair=(3, 8), hide=False, partial=None) Returns none. """ if not isinstance(container, _score.Container) or 0 < len(container): raise Exception(f"must be empty container: {container!r}.") if isinstance(argument, _score.Component): selection = [argument] else: selection = argument parent, start, stop = _get_parent_and_start_stop_indices( selection, ignore_before_after_grace=True ) if not _are_contiguous_logical_voice(selection, ignore_before_after_grace=True): message = "must be contiguous components in same logical voice:\n" message += f" {selection!r}." raise Exception(message) container._components = list(selection) _set_parents(container) if parent is not None: parent._components.insert(start, container) container._set_parent(parent)