"""Module to quantize free :class:`SequentialEvent` to notation based abjad :class:`Container`"""
import abc
import typing
import warnings
try:
import quicktions as fractions # type: ignore
except ImportError:
import fractions # type: ignore
import abjad # type: ignore
from abjadext import nauert # type: ignore
import ranges # type: ignore
from mutwo import core_converters
from mutwo import core_events
from mutwo import core_parameters
from mutwo import core_utilities
__all__ = (
"SequentialEventToQuantizedAbjadContainer",
"NauertSequentialEventToQuantizedAbjadContainer",
"NauertSequentialEventToDurationLineBasedQuantizedAbjadContainer",
"LeafMakerSequentialEventToQuantizedAbjadContainer",
"LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer",
)
class NoTimeSignatureError(Exception):
pass
[docs]class SequentialEventToQuantizedAbjadContainer(core_converters.abc.Converter):
"""Quantize :class:`~mutwo.core_events.SequentialEvent` objects.
:param time_signature_sequence: Set time signatures to divide the quantized abjad data
in desired bar sizes. If the converted :class:`~mutwo.core_events.SequentialEvent`
is longer than the sum of all passed time signatures, the last time signature
will be repeated for the remaining bars.
:param tempo_envelope: Defines the tempo of the converted music. This is an
:class:`core_events.TempoEnvelope` object which durations are beats and which
levels are either numbers (that will be interpreted as beats per minute ('BPM'))
or :class:`~mutwo.core_parameters.TempoPoint` objects. If no tempo envelope has
been defined, Mutwo will assume a constant tempo of 1/4 = 120 BPM.
"""
def __init__(
self,
time_signature_sequence: typing.Sequence[abjad.TimeSignature] = (
abjad.TimeSignature((4, 4)),
),
tempo_envelope: core_events.TempoEnvelope = None,
):
n_time_signature_sequence = len(time_signature_sequence)
if n_time_signature_sequence == 0:
raise NoTimeSignatureError(
"Found empty sequence for argument 'time_signature_sequence'. Specify at least"
" one time signature!"
)
time_signature_tuple = tuple(time_signature_sequence)
if tempo_envelope is None:
tempo_envelope = core_events.TempoEnvelope(
(
(0, core_parameters.TempoPoint(120)),
(0, core_parameters.TempoPoint(120)),
)
)
self._time_signature_tuple = time_signature_tuple
self._tempo_envelope = tempo_envelope
@property
def tempo_envelope(self) -> core_events.TempoEnvelope:
return self._tempo_envelope
# ###################################################################### #
# public methods for interaction with the user #
# ###################################################################### #
[docs] @abc.abstractmethod
def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...]]:
raise NotImplementedError
[docs]class NauertSequentialEventToQuantizedAbjadContainer(
SequentialEventToQuantizedAbjadContainer
):
"""Quantize :class:`~mutwo.core_events.SequentialEvent` objects via :mod:`abjadext.nauert`.
:param time_signature_sequence: Set time signatures to divide the quantized abjad data
in desired bar sizes. If the converted :class:`~mutwo.core_events.SequentialEvent`
is longer than the sum of all passed time signatures, the last time signature
will be repeated for the remaining bars.
:param duration_unit: This defines the `duration_unit` of the passed
:class:`~mutwo.core_events.SequentialEvent` (how the
:attr:`~mutwo.events.abc.Event.duration` attribute will be
interpreted). Can either be 'beats' (default) or 'miliseconds'.
WARNING: 'miliseconds' isn't working properly yet!
:param tempo_envelope: Defines the tempo of the converted music. This is an
:class:`core_events.TempoEnvelope` object which durations are beats and which
levels are either numbers (that will be interpreted as beats per minute ('BPM'))
or :class:`~mutwo.core_parameters.TempoPoint` objects. If no tempo envelope has
been defined, Mutwo will assume a constant tempo of 1/4 = 120 BPM.
:param attack_point_optimizer: Optionally the user can pass a
:class:`nauert.AttackPointOptimizer` object. Attack point optimizer help to
split events and tie them for better looking notation. The default attack point
optimizer is :class:`nauert.MeasurewiseAttackPointOptimizer` which splits events
to better represent metrical structures within bars. If no optimizer is desired
this argument can be set to ``None``.
Unlike :class:`LeafMakerSequentialEventToQuantizedAbjadContainer` this converter
supports nested tuplets and ties across tuplets. But this converter is much slower
than the :class:`LeafMakerSequentialEventToQuantizedAbjadContainer`. Because the
converter depends on the abjad extension `nauert` its quality is dependent on the
inner mechanism of the used package. Because the quantization made by the `nauert`
package can be somewhat indeterministic a lot of tweaking may be necessary for
complex musical structures.
"""
# TODO(add proper miliseconds conversion: you will have to add the tempo_envelope
# when building the QEventSequence. Furthermore you should auto write down the
# metronome marks when initialising from miliseconds?)
def __init__(
self,
time_signature_sequence: typing.Sequence[abjad.TimeSignature] = (
abjad.TimeSignature((4, 4)),
),
duration_unit: str = "beats", # for future: typing.Literal["beats", "miliseconds"]
tempo_envelope: core_events.TempoEnvelope = None,
attack_point_optimizer: typing.Optional[
nauert.AttackPointOptimizer
] = nauert.MeasurewiseAttackPointOptimizer(),
search_tree: typing.Optional[nauert.SearchTree] = None,
):
if duration_unit == "miliseconds":
# warning for not well implemented miliseconds conversion
message = (
"The current implementation can't apply tempo changes for duration unit"
" 'miliseconds' yet! Furthermore to quantize via duration_unit"
" 'miliseconds' isn't well tested yet and may return unexpected"
" results."
)
warnings.warn(message)
time_signature_tuple = tuple(time_signature_sequence)
# nauert will raise an error if there is only one time signature
if len(time_signature_tuple) == 1:
time_signature_tuple += time_signature_tuple
super().__init__(time_signature_tuple, tempo_envelope)
self._duration_unit = duration_unit
self._attack_point_optimizer = attack_point_optimizer
self._q_schema = NauertSequentialEventToQuantizedAbjadContainer._make_q_schema(
self._time_signature_tuple, search_tree
)
# ###################################################################### #
# static methods #
# ###################################################################### #
@staticmethod
def _get_respective_q_event_from_abjad_leaf(
abjad_leaf: typing.Union[abjad.Rest, abjad.Note]
) -> typing.Optional[nauert.QEvent]:
# TODO(improve ugly, heuristic, unreliable code)
try:
return abjad.get.indicators(abjad_leaf)[0]["q_events"][0]
except TypeError:
return None
except KeyError:
return None
except IndexError:
return None
@staticmethod
def _process_abjad_leaf(
indices: list[int],
abjad_leaf: abjad.Leaf,
related_abjad_leaves_per_simple_event: list[list[tuple[int, ...]]],
q_event_sequence: nauert.QEventSequence,
has_tie: bool,
index_of_previous_q_event: int,
) -> tuple[bool, int]:
q_event = NauertSequentialEventToQuantizedAbjadContainer._get_respective_q_event_from_abjad_leaf(
abjad_leaf
)
if q_event and type(q_event) != nauert.TerminalQEvent:
nth_q_event = q_event_sequence.sequence.index(q_event)
related_abjad_leaves_per_simple_event[nth_q_event].append(tuple(indices))
index_of_previous_q_event = nth_q_event
elif has_tie:
related_abjad_leaves_per_simple_event[index_of_previous_q_event].append(
tuple(indices)
)
# skip leaves without any links
# else:
# related_abjad_leaves_per_simple_event.append([tuple(indices)])
has_tie = abjad.get.has_indicator(abjad_leaf, abjad.Tie)
return has_tie, index_of_previous_q_event
@staticmethod
def _process_tuplet(
indices: list[int],
tuplet: abjad.Tuplet,
related_abjad_leaves_per_simple_event: list[list[tuple[int, ...]]],
q_event_sequence: nauert.QEventSequence,
has_tie: bool,
index_of_previous_q_event: int,
) -> tuple[bool, int]:
for (
nth_abjad_leaf_or_tuplet,
abjad_leaf_or_tuplet,
) in enumerate(tuplet):
(
has_tie,
index_of_previous_q_event,
) = NauertSequentialEventToQuantizedAbjadContainer._process_abjad_leaf_or_tuplet(
indices + [nth_abjad_leaf_or_tuplet],
abjad_leaf_or_tuplet,
related_abjad_leaves_per_simple_event,
q_event_sequence,
has_tie,
index_of_previous_q_event,
)
return has_tie, index_of_previous_q_event
@staticmethod
def _process_abjad_leaf_or_tuplet(
index_list: list[int],
abjad_leaf_or_tuplet: typing.Union[abjad.Tuplet, abjad.Leaf],
related_abjad_leaves_per_simple_event: list[list[tuple[int, ...]]],
q_event_sequence: nauert.QEventSequence,
has_tie: bool,
index_of_previous_q_event: int,
) -> tuple[bool, int]:
if isinstance(abjad_leaf_or_tuplet, abjad.Tuplet):
return NauertSequentialEventToQuantizedAbjadContainer._process_tuplet(
index_list,
abjad_leaf_or_tuplet,
related_abjad_leaves_per_simple_event,
q_event_sequence,
has_tie,
index_of_previous_q_event,
)
else:
return NauertSequentialEventToQuantizedAbjadContainer._process_abjad_leaf(
index_list,
abjad_leaf_or_tuplet,
related_abjad_leaves_per_simple_event,
q_event_sequence,
has_tie,
index_of_previous_q_event,
)
@staticmethod
def _make_related_abjad_leaves_per_simple_event(
sequential_event: core_events.SequentialEvent,
q_event_sequence: nauert.QEventSequence,
quanitisized_abjad_leaf_voice: abjad.Voice,
) -> tuple[tuple[tuple[int, ...], ...], ...,]:
has_tie = False
index_of_previous_q_event: int = 0
related_abjad_leaves_per_simple_event: list[list[tuple[int, ...]]] = [
[] for _ in sequential_event
]
for nth_bar, bar in enumerate(quanitisized_abjad_leaf_voice):
for nth_abjad_leaf_or_tuplet, abjad_leaf_or_tuplet in enumerate(bar):
(
has_tie,
index_of_previous_q_event,
) = NauertSequentialEventToQuantizedAbjadContainer._process_abjad_leaf_or_tuplet(
[nth_bar, nth_abjad_leaf_or_tuplet],
abjad_leaf_or_tuplet,
related_abjad_leaves_per_simple_event,
q_event_sequence,
has_tie,
index_of_previous_q_event,
)
return tuple(
tuple(tuple(item) for item in pair)
for pair in related_abjad_leaves_per_simple_event
)
@staticmethod
def _make_q_schema(
time_signature_tuple: tuple[abjad.TimeSignature, ...],
search_tree: typing.Optional[nauert.SearchTree],
) -> nauert.QSchema:
formated_time_signature_list = []
for time_signature in time_signature_tuple:
formated_time_signature_list.append({"time_signature": time_signature})
keyword_arguments = {
"use_full_measure": True,
"tempo": abjad.MetronomeMark((1, 4), 60),
}
if search_tree:
keyword_arguments.update({"search_tree": search_tree})
return nauert.MeasurewiseQSchema(
*formated_time_signature_list, **keyword_arguments
)
# ###################################################################### #
# private methods #
# ###################################################################### #
def _sequential_event_to_q_event_sequence(
self, sequential_event: core_events.SequentialEvent
) -> nauert.QEventSequence:
duration_list = list(sequential_event.get_parameter("duration"))
for nth_simple_event, simple_event in enumerate(sequential_event):
if simple_event.is_rest:
duration_list[nth_simple_event] = (
core_parameters.DirectDuration(0) - duration_list[nth_simple_event]
)
if self._duration_unit == "beats":
return nauert.QEventSequence.from_tempo_scaled_durations(
duration_list, tempo=abjad.MetronomeMark((1, 4), 60)
)
elif self._duration_unit == "miliseconds":
return nauert.QEventSequence.from_millisecond_durations(duration_list)
else:
message = (
"Unknown duration unit '{}'. Use duration unit 'beats' or"
" 'miliseconds'.".format(self._duration_unit)
)
raise NotImplementedError(message)
def _q_event_sequence_to_quanitisized_abjad_leaf_voice(
self, q_event_sequence: nauert.QEventSequence
) -> abjad.Voice:
quantizer = nauert.Quantizer()
return quantizer(
q_event_sequence,
q_schema=self._q_schema,
attach_tempos=True if self._duration_unit == "miliseconds" else False,
attack_point_optimizer=self._attack_point_optimizer,
)
# ###################################################################### #
# public methods for interaction with the user #
# ###################################################################### #
[docs] def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]:
q_event_sequence = self._sequential_event_to_q_event_sequence(
sequential_event_to_convert
)
quanitisized_abjad_leaf_voice = (
self._q_event_sequence_to_quanitisized_abjad_leaf_voice(q_event_sequence)
)
related_abjad_leaves_per_simple_event = NauertSequentialEventToQuantizedAbjadContainer._make_related_abjad_leaves_per_simple_event(
sequential_event_to_convert, q_event_sequence, quanitisized_abjad_leaf_voice
)
return (
quanitisized_abjad_leaf_voice,
related_abjad_leaves_per_simple_event,
)
[docs]class LeafMakerSequentialEventToQuantizedAbjadContainer(
SequentialEventToQuantizedAbjadContainer
):
"""Quantize :class:`~mutwo.core_events.SequentialEvent` object via :mod:`abjad.LeafMaker`.
:param time_signature_sequence: Set time signatures to divide the quantized abjad data
in desired bar sizes. If the converted
:class:`~mutwo.core_events.SequentialEvent` is longer than the sum of
all passed time signatures, the last time signature
will be repeated for the remaining bars.
:param tempo_envelope: Defines the tempo of the converted music. This is an
:class:`core_events.TempoEnvelope` object which durations are beats and which
levels are either numbers (that will be interpreted as beats per minute ('BPM'))
or :class:`~mutwo.core_parameters.TempoPoint` objects. If no tempo envelope
has been defined, Mutwo will assume a constant tempo of 1/4 = 120 BPM.
This method is significantly faster than the
:class:`NauertSequentialEventToQuantizedAbjadContainer`. But it also
has several known limitations:
1. :class:`LeafMakerSequentialEventToQuantizedAbjadContainer` doesn't
support nested tuplets.
2. :class:`LeafMakerSequentialEventToQuantizedAbjadContainer` doesn't
support ties across tuplets with different prolation (or across tuplets
and not-tuplet notation). If ties are desired the user has to build them
manually before passing the :class:`~mutwo.core_events.SequentialEvent`
to the converter.
"""
_maximum_dot_count = 1
def __init__(
self, *args, do_rewrite_meter: bool = True, add_beams: bool = True, **kwargs
):
self._leaf_maker = abjad.LeafMaker(
forbidden_note_duration=abjad.Duration(8, 1),
forbidden_rest_duration=abjad.Duration(8, 1),
)
super().__init__(*args, **kwargs)
self._do_rewrite_meter = do_rewrite_meter
self._add_beams = add_beams
# ###################################################################### #
# static private methods #
# ###################################################################### #
@staticmethod
def _find_offset_inventory(meter: abjad.Meter) -> tuple[abjad.Offset, ...]:
for nth_offset_inventory, offset_inventory in enumerate(
depthwise_offset_inventory := meter.depthwise_offset_inventory
):
difference = offset_inventory[1] - offset_inventory[0]
if difference == fractions.Fraction(1, 4):
return offset_inventory
elif difference <= fractions.Fraction(1, 4):
return depthwise_offset_inventory[nth_offset_inventory - 1]
return offset_inventory
@staticmethod
def _add_explicit_beams(
bar: abjad.Container, meter: abjad.Meter, global_offset: abjad.Offset
) -> None:
offset_inventory = (
LeafMakerSequentialEventToQuantizedAbjadContainer._find_offset_inventory(
meter
)
)
leaf_offset_list = []
# don't attach beams on tuplets
relevant_bar_items = filter(
lambda leaf_or_tuplet: isinstance(leaf_or_tuplet, abjad.Leaf)
and leaf_or_tuplet.written_duration < fractions.Fraction(1, 4),
bar,
)
leaf_selection = abjad.select(relevant_bar_items).leaves()
for leaf in leaf_selection:
offset = abjad.get.timespan(leaf).start_offset - global_offset
leaf_offset_list.append(offset)
beam_range_list = []
for start, end in zip(offset_inventory, offset_inventory[1:]):
area = ranges.Range(start, end)
offset_tuple = tuple(
filter(lambda offset: offset in area, leaf_offset_list)
)
n_elements = len(offset_tuple)
is_start_in_leaves = start in offset_tuple
# make new beam range
if is_start_in_leaves and n_elements > 1:
new_beam_range = [
leaf_offset_list.index(offset_tuple[0]),
leaf_offset_list.index(offset_tuple[-1]),
]
beam_range_list.append(new_beam_range)
for beam_range in beam_range_list:
start, stop = beam_range
abjad.attach(abjad.StartBeam(), leaf_selection[start])
abjad.attach(abjad.StopBeam(), leaf_selection[stop])
global_offset += offset_inventory[-1]
return global_offset
@staticmethod
def _find_tuplet_indices(bar: abjad.Container) -> tuple[int, ...]:
tuplet_index_list = []
for index, leaf_or_tuplet in enumerate(bar):
if isinstance(leaf_or_tuplet, abjad.Tuplet):
tuplet_index_list.append(index)
return tuple(tuplet_index_list)
@staticmethod
def _group_tuplet_indices(tuplet_index_tuple: tuple[int, ...]) -> list[list[int]]:
"""Put adjacent tuplet indices into groups."""
grouped_tuplet_index_list = [[]]
last_tuplet_index = None
for tuplet_index in tuplet_index_tuple:
if last_tuplet_index:
difference = tuplet_index - last_tuplet_index
if difference == 1:
grouped_tuplet_index_list[-1].append(tuplet_index)
else:
grouped_tuplet_index_list.append([tuplet_index])
else:
grouped_tuplet_index_list[-1].append(tuplet_index)
last_tuplet_index = tuplet_index
return grouped_tuplet_index_list
@staticmethod
def _concatenate_adjacent_tuplets_for_one_group(
bar: abjad.Container, group: list[int]
):
implied_prolation_list = [bar[index].implied_prolation for index in group]
common_prolation_group_list = [[implied_prolation_list[0], [group[0]]]]
for index, prolation in zip(group[1:], implied_prolation_list[1:]):
if prolation == common_prolation_group_list[-1][0]:
common_prolation_group_list[-1][1].append(index)
else:
common_prolation_group_list.append([prolation, [index]])
tuplet_list = []
for prolation, tuplet_index_list in common_prolation_group_list:
tuplet = abjad.Tuplet(prolation)
for tuplet_index in tuplet_index_list:
for component in bar[tuplet_index]:
tuplet.append(abjad.mutate.copy(component))
tuplet_list.append(tuplet)
bar[group[0] : group[-1] + 1] = tuplet_list
# ###################################################################### #
# private methods #
# ###################################################################### #
def _make_note_tuple(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Leaf, ...]:
pitch_list = [
None if event.is_rest else "c" for event in sequential_event_to_convert
]
# It has to be a list! Otherwise abjad will raise an exception.
duration_list = list(
map(
lambda duration: abjad.Duration(duration),
sequential_event_to_convert.get_parameter("duration"),
)
)
note_tuple = tuple(self._leaf_maker(pitch_list, duration_list))
return note_tuple
def _concatenate_adjacent_tuplets_for_one_bar(self, bar: abjad.Container):
tuplet_index_tuple = (
LeafMakerSequentialEventToQuantizedAbjadContainer._find_tuplet_indices(bar)
)
if tuplet_index_tuple:
grouped_tuplet_index_list_list = (
LeafMakerSequentialEventToQuantizedAbjadContainer._group_tuplet_indices(
tuplet_index_tuple
)
)
for tuplet_index_list in reversed(grouped_tuplet_index_list_list):
if len(tuplet_index_list) > 1:
LeafMakerSequentialEventToQuantizedAbjadContainer._concatenate_adjacent_tuplets_for_one_group(
bar, tuplet_index_list
)
def _concatenate_adjacent_tuplets(self, voice: abjad.Voice) -> abjad.Voice:
for bar in voice:
self._concatenate_adjacent_tuplets_for_one_bar(bar)
def _rewrite_meter(self, voice: abjad.Voice):
time_signature_iter = iter(self._time_signature_tuple)
last_time_signature = self._time_signature_tuple[-1]
# rewrite by meter
global_offset = abjad.Offset((0, 1))
previous_time_signature = None
for bar in voice:
try:
time_signature = next(time_signature_iter)
except StopIteration:
time_signature = last_time_signature
if time_signature != previous_time_signature:
abjad.attach(time_signature, abjad.get.leaf(bar, 0))
meter = abjad.Meter(time_signature)
abjad.Meter.rewrite_meter(
bar[:], time_signature, maximum_dot_count=self._maximum_dot_count
)
if self._add_beams:
global_offset = self._add_explicit_beams(bar, meter, global_offset)
previous_time_signature = time_signature
last_bar = bar
difference = time_signature.duration - abjad.get.duration(last_bar)
if difference:
last_bar.extend(self._leaf_maker([None], [difference]))
abjad.Meter.rewrite_meter(
last_bar[:], time_signature, maximum_dot_count=self._maximum_dot_count
)
def _make_voice(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> abjad.Voice:
# first build notes
note_tuple = self._make_note_tuple(sequential_event_to_convert)
# split notes by time signatures
notes_split_by_time_signature_sequence = abjad.mutate.split(
note_tuple,
[time_signature.duration for time_signature in self._time_signature_tuple],
cyclic=True,
)
bar_list = []
for selection in notes_split_by_time_signature_sequence:
try:
bar = abjad.Container(selection.items, simultaneous=False)
except Exception:
bar = abjad.Container(
abjad.mutate.copy(selection).items, simultaneous=False
)
bar_list.append(bar)
voice = abjad.Voice(bar_list)
if self._do_rewrite_meter:
self._rewrite_meter(voice)
self._concatenate_adjacent_tuplets(voice)
return voice
def _get_data_for_leaf(
self, index_tuple: tuple[int, ...], leaf: abjad.Leaf
) -> tuple[tuple[int, ...], bool, bool]:
has_tie = abjad.get.indicator(leaf, abjad.Tie())
is_rest = (
isinstance(leaf, abjad.Rest)
or isinstance(leaf, abjad.MultimeasureRest)
or isinstance(leaf, abjad.Skip)
)
return index_tuple, has_tie, is_rest
def _get_data_for_tuplet_or_leaf(
self,
index_tuple: tuple[int, ...],
leaf_or_tuplet: typing.Union[abjad.Leaf, abjad.Tuplet],
) -> tuple[tuple[tuple[int, ...], bool], ...]:
if isinstance(leaf_or_tuplet, abjad.Leaf):
return (self._get_data_for_leaf(index_tuple, leaf_or_tuplet),)
else:
data_per_leaf_or_tuplet_list = []
for nth_leaf_or_tuplet_of_tuplet, sub_leaf_or_tuplet in enumerate(
leaf_or_tuplet
):
data_per_leaf_or_tuplet_list.extend(
self._get_data_for_tuplet_or_leaf(
index_tuple + (nth_leaf_or_tuplet_of_tuplet,),
sub_leaf_or_tuplet,
)
)
return tuple(data_per_leaf_or_tuplet_list)
def _make_related_abjad_leaves_per_simple_event(
self, voice: abjad.Voice
) -> tuple[tuple[tuple[int, ...], ...], ...]:
data_per_tuplet_or_leaf_list = []
for nth_bar, bar in enumerate(voice):
for nth_leaf_or_tuplet, leaf_or_tuplet in enumerate(bar):
data_per_tuplet_or_leaf_list.extend(
self._get_data_for_tuplet_or_leaf(
(nth_bar, nth_leaf_or_tuplet), leaf_or_tuplet
)
)
related_abjad_leaves_per_simple_event = []
related_abjad_leaves = []
was_previous_note_rest = None
has_previous_tie = None
for index_tuple, has_tie, is_rest in data_per_tuplet_or_leaf_list:
if has_previous_tie or all((was_previous_note_rest, is_rest)):
related_abjad_leaves.append(index_tuple)
else:
if related_abjad_leaves:
related_abjad_leaves_per_simple_event.append(
tuple(related_abjad_leaves)
)
related_abjad_leaves = [index_tuple]
has_previous_tie = has_tie
was_previous_note_rest = is_rest
if related_abjad_leaves:
related_abjad_leaves_per_simple_event.append(tuple(related_abjad_leaves))
return tuple(related_abjad_leaves_per_simple_event)
[docs] def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]:
voice = self._make_voice(sequential_event_to_convert)
related_abjad_leaves_per_simple_event = (
self._make_related_abjad_leaves_per_simple_event(voice)
)
return voice, related_abjad_leaves_per_simple_event
class _DurationLineBasedQuantizedAbjadContainerMixin(object):
"""Mixin for duration-line based quantization.
:param duration_line_minimum_length: The minimum length of a duration line.
:type duration_line_minimum_length: int
:param duration_line_thickness: The thickness of a duration line.
:type duration_line_thickness: int
This converter differs from its parent class through
the usage of duration lines for indicating rhythm instead of using
flags, beams, dots and note head colors.
**Note**:
Don't forget to add the 'Duration_line_engraver' to the resulting
abjad Voice, otherwise Lilypond won't be able to render the desired output.
**Example:**
>>> import abjad
>>> from mutwo import abjad_converters
>>> from mutwo import core_events
>>> converter = abjad_converters.SequentialEventToAbjadVoiceConverter(
>>> abjad_converters.LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer(
>>> )
>>> )
>>> sequential_event_to_convert = core_events.SequentialEvent(
>>> [
>>> music_events.NoteLike("c", 0.125),
>>> music_events.NoteLike("d", 1),
>>> music_events.NoteLike([], 0.125),
>>> music_events.NoteLike("e", 0.16666),
>>> music_events.NoteLike("e", 0.08333333333333333)
>>> ]
>>> )
>>> converted_sequential_event = converter.convert(sequential_event_to_convert)
>>> converted_sequential_event.consists_commands.append("Duration_line_engraver")
"""
def __init__(
self,
*,
duration_line_minimum_length: int = 6,
duration_line_thickness: int = 3,
):
self._duration_line_minimum_length = duration_line_minimum_length
self._duration_line_thickness = duration_line_thickness
@classmethod
def _set_docs(cls, parent: type):
doc_base = parent.__doc__.split("\n\n")
doc_mixin = _DurationLineBasedQuantizedAbjadContainerMixin.__doc__.split("\n\n")
cls.__doc__ = "\n\n".join(
[doc_base[0], doc_base[1] + "\n" + doc_mixin[1]] + doc_mixin[2:]
)
def _prepare_first_element(self, first_element: abjad.Leaf):
# set duration line properties
abjad.attach(
abjad.LilyPondLiteral(
"\\override Staff.DurationLine.minimum-length = {}".format(
self._duration_line_minimum_length
)
),
first_element,
)
abjad.attach(
abjad.LilyPondLiteral(
"\\override Staff.DurationLine.thickness = {}".format(
self._duration_line_thickness
)
),
first_element,
)
def _adjust_quantisized_abjad_leaves(
self,
quanitisized_abjad_leaf_voice: abjad.Container,
related_abjad_leaves_per_simple_event: tuple[tuple[tuple[int, ...], ...], ...],
):
is_first = True
for abjad_leaves_indices in related_abjad_leaves_per_simple_event:
if abjad_leaves_indices:
first_element = core_utilities.get_nested_item_from_index_sequence(
abjad_leaves_indices[0], quanitisized_abjad_leaf_voice
)
if is_first:
self._prepare_first_element(first_element)
is_first = False
is_active = bool(abjad.get.pitches(first_element))
if is_active:
if len(abjad_leaves_indices) > 1:
abjad.detach(abjad.Tie(), first_element)
abjad.attach(
abjad.LilyPondLiteral("\\-", format_slot="after"), first_element
)
for indices in abjad_leaves_indices[1:]:
element = core_utilities.get_nested_item_from_index_sequence(
indices, quanitisized_abjad_leaf_voice
)
core_utilities.set_nested_item_from_index_sequence(
indices,
quanitisized_abjad_leaf_voice,
abjad.Skip(element.written_duration),
)
[docs]class NauertSequentialEventToDurationLineBasedQuantizedAbjadContainer(
NauertSequentialEventToQuantizedAbjadContainer,
_DurationLineBasedQuantizedAbjadContainerMixin,
):
def __init__(
self,
*args,
duration_line_minimum_length: int = 6,
duration_line_thickness: int = 3,
**kwargs,
):
super().__init__(*args, **kwargs)
_DurationLineBasedQuantizedAbjadContainerMixin.__init__(
self,
duration_line_minimum_length=duration_line_minimum_length,
duration_line_thickness=duration_line_thickness,
)
[docs] def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]:
(
quanitisized_abjad_leaf_voice,
related_abjad_leaves_per_simple_event,
) = super().convert(sequential_event_to_convert)
self._adjust_quantisized_abjad_leaves(
quanitisized_abjad_leaf_voice, related_abjad_leaves_per_simple_event
)
return quanitisized_abjad_leaf_voice, related_abjad_leaves_per_simple_event
[docs]class LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer(
LeafMakerSequentialEventToQuantizedAbjadContainer,
_DurationLineBasedQuantizedAbjadContainerMixin,
):
def __init__(
self,
*args,
duration_line_minimum_length: int = 6,
duration_line_thickness: int = 3,
**kwargs,
):
super().__init__(*args, **kwargs)
_DurationLineBasedQuantizedAbjadContainerMixin.__init__(
self,
duration_line_thickness=duration_line_thickness,
duration_line_minimum_length=duration_line_minimum_length,
)
[docs] def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]:
(
quanitisized_abjad_leaf_voice,
related_abjad_leaves_per_simple_event,
) = super().convert(sequential_event_to_convert)
self._adjust_quantisized_abjad_leaves(
quanitisized_abjad_leaf_voice, related_abjad_leaves_per_simple_event
)
# only assign first item to abjad leaves
post_processed_releated_abjad_leaves_per_simple_event = []
for related_abjad_leaves in related_abjad_leaves_per_simple_event:
post_processed_releated_abjad_leaves_per_simple_event.append(
(related_abjad_leaves[0],)
)
return (
quanitisized_abjad_leaf_voice,
post_processed_releated_abjad_leaves_per_simple_event,
)
NauertSequentialEventToDurationLineBasedQuantizedAbjadContainer._set_docs(
NauertSequentialEventToQuantizedAbjadContainer
)
LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer._set_docs(
LeafMakerSequentialEventToQuantizedAbjadContainer
)