"""
Rhythm tree functions.
"""
import abjad
from abjadext import rmakers
from .sequence import Sequence
[docs]def flatten_tree_level(rtm, recurse=False):
r"""
.. container:: example
>>> string = "(1 (1 (1 ((2 (1 1 1)) 2 2 1))))"
>>> rhythm_tree_container_ = evans.flatten_tree_level(
... string,
... recurse=False,
... )
>>> strings = [string, rhythm_tree_container_]
>>> durations = [abjad.Duration(1, 1), abjad.Duration(1, 1)]
>>> h = evans.RhythmHandler(evans.RTMMaker(strings), forget=False)
>>> selections = h(durations)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'2
\times 4/7 {
\times 2/3 {
c'8
c'8
c'8
}
c'4
c'4
c'8
}
c'8
\times 2/3 {
c'8
c'8
c'8
}
c'4
c'4
c'8
}
.. container:: example
>>> string = "(1 (1 (1 ((2 (1 1 1)) 2 2 1))))"
>>> rhythm_tree_container_ = evans.flatten_tree_level(
... string,
... recurse=True,
... )
>>> strings = [string, rhythm_tree_container_]
>>> durations = [abjad.Duration(1, 1), abjad.Duration(1, 1)]
>>> h = evans.RhythmHandler(evans.RTMMaker(strings), forget=False)
>>> selections = h(durations)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'2
\times 4/7 {
\times 2/3 {
c'8
c'8
c'8
}
c'4
c'4
c'8
}
\times 8/9 {
c'8
c'8
c'8
c'8
c'4
c'4
c'8
}
}
"""
parser = abjad.rhythmtrees.RhythmTreeParser()
rhythm_tree_list = parser(rtm)
rhythm_tree = rhythm_tree_list[0]
out = []
for item in rhythm_tree:
if isinstance(item, abjad.rhythmtrees.RhythmTreeContainer):
if recurse:
temp = flatten_tree_level(item.rtm_format)
temp_tree_list = parser(temp)
temp_tree = temp_tree_list[0]
for _ in temp_tree:
out.append(_)
else:
for item_ in item:
out.append(item_)
else:
out.append(item)
out = abjad.rhythmtrees.RhythmTreeContainer(out)
return out.rtm_format
[docs]def nested_list_to_rtm(nested_list):
r"""
.. container:: example
>>> nested_list = [1, [1, 1, [1, [1, 1]], 1]]
>>> rtm_string = evans.nested_list_to_rtm(nested_list)
>>> print(rtm_string)
(1 (1 1 (1 (1 1)) 1))
>>> rtm_list = [rtm_string]
>>> maker = evans.RTMMaker(rtm=rtm_list)
>>> divisions = [abjad.Duration(1, 1)]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'4
c'4
c'8
c'8
c'4
}
"""
out_string = ""
for item in str(nested_list):
if item == "[":
out_string += "("
if item == "]":
out_string += ")"
if str.isdigit(item):
out_string += item
if item == "-":
out_string += item
if item == " ":
out_string += item
return out_string
[docs]def rotate_tree(rtm_string, n=1):
r"""
.. container:: example
>>> rtm = "(1 (2 (1 (1 2)) 1))"
>>> for x in range(6):
... new_rtm = evans.rotate_tree(rtm, x)
... print(new_rtm)
...
(1 (2 (1 (1 2)) 1))
(1 (1 (1 (2 1)) 2))
(1 (1 (2 (1 2)) 1))
(1 (2 (1 (2 1)) 1))
(1 (1 (2 (1 1)) 2))
(1 (2 (1 (1 2)) 1))
>>> rtm_list = [evans.rotate_tree(rtm, x) for x in range(6)]
>>> maker = evans.RTMMaker(rtm=rtm_list)
>>> divisions = [abjad.Duration(1, 1) for _ in rtm_list]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'2
\times 2/3 {
c'8
c'4
}
c'4
c'4
\times 2/3 {
c'4
c'8
}
c'2
c'4
\times 2/3 {
c'4
c'2
}
c'4
c'2
\times 2/3 {
c'4
c'8
}
c'4
\times 4/5 {
c'4
c'4
c'4
c'2
}
c'2
\times 2/3 {
c'8
c'4
}
c'4
}
>>> divisions = [
... abjad.Duration(1, 2),
... abjad.Duration(1, 4),
... abjad.Duration(1, 4),
... abjad.Duration(1, 1),
... abjad.Duration(1, 4),
... abjad.Duration(3, 4),
... abjad.Duration(2, 1),
... abjad.Duration(5, 8),
... abjad.Duration(3, 8),
... ]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'4
\times 2/3 {
c'16
c'8
}
c'8
c'16
\times 2/3 {
c'16
c'32
}
c'8
c'16
\times 2/3 {
c'16
c'8
}
c'16
c'2
\times 2/3 {
c'4
c'8
}
c'4
\times 4/5 {
c'16
c'16
c'16
c'8
}
\tweak text #tuplet-number::calc-fraction-text
\times 3/4 {
c'2
\times 2/3 {
c'8
c'4
}
c'4
}
c'1
\times 2/3 {
c'4
c'2
}
c'2
\tweak text #tuplet-number::calc-fraction-text
\times 5/8 {
c'4
\times 2/3 {
c'4
c'8
}
c'2
}
\tweak text #tuplet-number::calc-fraction-text
\times 3/4 {
c'8
\times 2/3 {
c'8
c'4
}
c'8
}
}
"""
opening = rtm_string[:3]
middle = rtm_string[3:-1]
closing = rtm_string[-1]
digits = [_ for _ in middle if str.isdigit(_)]
digits = (_ for _ in digits[n:] + digits[:n])
new_middle = ""
for item in middle:
if str.isdigit(item):
new_middle += next(digits)
continue
new_middle += item
return opening + new_middle + closing
[docs]def funnel_tree_to_x(rtm, x):
list_out = [rtm]
out = rtm
digits = [int(_) for _ in out if _.isdigit()]
if x < max(digits):
for _ in range(abs(x - max(digits))):
if not all(digit == x for digit in digits):
new_out = ""
for i, _ in enumerate(out):
if _.isdigit():
if int(_) == x:
new_out = new_out + _
elif int(_) > x:
new_out = new_out + f"{int(_) - 1}"
else:
new_out = new_out + f"{int(_) + 1}"
else:
new_out = new_out + _
out = new_out
digits = [int(_) for _ in out if _.isdigit()]
list_out.append(out)
else:
for _ in range(abs(x - min(digits))):
if not all(digit == x for digit in digits):
new_out = ""
for i, _ in enumerate(out):
if _.isdigit():
if int(_) == x:
new_out = new_out + _
elif int(_) > x:
new_out = new_out + f"{int(_) - 1}"
else:
new_out = new_out + f"{int(_) + 1}"
else:
new_out = new_out + _
out = new_out
digits = [int(_) for _ in out if _.isdigit()]
list_out.append(out)
return list_out
[docs]def funnel_inner_tree_to_x(rtm_string, x=1):
r"""
.. container:: example
>>> rtm = '(1 (3 (2 (1 2 1 1)) 3))'
>>> for x in evans.funnel_inner_tree_to_x(rtm_string=rtm, x=5):
... print(x)
...
(1 (3 (2 (1 2 1 1)) 3))
(1 (4 (3 (2 3 2 2)) 4))
(1 (5 (4 (3 4 3 3)) 5))
(1 (5 (5 (4 5 4 4)) 5))
(1 (5 (5 (5 5 5 5)) 5))
>>> rtm_list = evans.funnel_inner_tree_to_x(rtm_string=rtm, x=5)
>>> maker = evans.RTMMaker(rtm=rtm_list)
>>> divisions = [abjad.Duration(1, 1) for _ in rtm_list]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'4.
\times 4/5 {
c'16
c'8
c'16
c'16
}
c'4.
\times 8/11 {
c'2
\times 2/3 {
c'8
c'8.
c'8
c'8
}
c'2
}
\times 4/7 {
c'2
~
c'8
\times 8/13 {
c'8.
c'4
c'8.
c'8.
}
c'2
~
c'8
}
\times 8/15 {
c'2
~
c'8
\tweak text #tuplet-number::calc-fraction-text
\times 10/17 {
c'4
c'4
~
c'16
c'4
c'4
}
c'2
~
c'8
}
\times 8/15 {
c'2
~
c'8
c'8
~
c'32
c'8
~
c'32
c'8
~
c'32
c'8
~
c'32
c'2
~
c'8
}
}
.. container:: example
>>> rtm_list = evans.funnel_inner_tree_to_x(rtm_string="(1 ((4 (1 1 (2 (3 1)))) 1))", x=1)
>>> maker = evans.RTMMaker(rtm=rtm_list)
>>> divisions = [abjad.Duration(1, 1) for _ in rtm_list]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
\times 4/5 {
c'4
c'4
c'4.
c'8
c'4
}
c'4
c'4
\times 2/3 {
c'4
c'8
}
c'4
\times 2/3 {
\times 2/3 {
c'2
c'2
c'4
c'4
}
c'2
}
\times 2/3 {
c'4
c'4
c'8
c'8
}
c'2
}
"""
opening = rtm_string[:3]
middle = rtm_string[3:-1]
closing = rtm_string[-1]
funnel_list = []
for _ in funnel_tree_to_x(rtm=middle, x=x):
funnel_list.append(opening + _ + closing)
return funnel_list
[docs]class RTMMaker(rmakers.RhythmMaker):
r"""
.. container:: example
>>> rtm = ['(1 (1 1))', '(1 (1 1 1))', '(1 (1 1 1 1))']
>>> maker = evans.RTMMaker(rtm=rtm)
>>> divisions = [abjad.Duration(1, 1), abjad.Duration(1, 1), abjad.Duration(1, 1)]
>>> selections = maker(divisions)
>>> staff = abjad.Staff()
>>> staff.extend(selections)
>>> score = abjad.Score([staff])
>>> moment = "#(ly:make-moment 1 25)"
>>> abjad.setting(score).proportional_notation_duration = moment
>>> file = abjad.LilyPondFile(
... items=[score],
... includes=["abjad.ily"],
... global_staff_size=16,
... )
...
>>> abjad.show(file) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
c'2
c'2
\times 2/3 {
c'2
c'2
c'2
}
c'4
c'4
c'4
c'4
}
"""
def __init__(self, rtm, tie_across_divisions=False):
self.rtm = abjad.CyclicTuple(rtm)
self.tie_across_divisions = tie_across_divisions
self._state = -1
[docs] def __str__(self):
return abjad.storage(self)
[docs] def __repr__(self):
return abjad.storage(self)
[docs] def __call__(self, divisions, previous_state=-1):
starting_index = -1
if previous_state is not None:
starting_index = previous_state + 1
selections = self._rtm_maker(divisions, starting_index=starting_index)
if previous_state is not None:
self._state += len(selections)
self._state %= len(self.rtm)
return selections
@staticmethod
def _rhythm_cell(duration, rtm):
rtm_parser = abjad.rhythmtrees.RhythmTreeParser()
selection = abjad.select(rtm_parser(rtm)[0](duration))
return selection
def _rtm_maker(self, divisions, starting_index=0):
rtm = self.rtm[starting_index : starting_index + len(divisions)]
selections = []
for rtm_string, division in zip(rtm, divisions):
selection = self._rhythm_cell(division, rtm_string)
selections.append(selection)
for selection_ in selections[:-1]:
if self.tie_across_divisions is True:
last_leaf = abjad.select(selection_).leaves()[-1]
abjad.attach(abjad.Tie(), last_leaf)
return selections
[docs]class RhythmTreeQuantizer:
def __init__(self):
pass
[docs] def __call__(self, string):
parsed_string = self._parse_tree(string)
self._quantize_rhythm_tree(parsed_string)
return parsed_string.rtm_format
def _operation(self, nodes):
numerators = []
for node in nodes:
numerators.append(node.preprolated_duration.numerator)
numerators_sum = sum(numerators)
if 4 < numerators_sum:
normalized = Sequence(numerators).normalize_to_sum(4)
new_numerators = [round(_) for _ in normalized if round(_) != 0]
for numerator, node in zip(new_numerators, nodes):
node.preprolated_duration = abjad.Duration(numerator, 1)
def _parse_tree(self, string):
parser = abjad.rhythmtrees.RhythmTreeParser()
rhythm_tree_list = parser(string)
rhythm_tree_container = rhythm_tree_list[0]
return rhythm_tree_container
def _recursive_operation(self, layers):
temp = []
for layer in layers:
if isinstance(layer, list):
self._recursive_operation(layer)
else:
temp.append(layer)
self._operation(temp)
def _return_layers(self, parsed_tree):
layers = []
for node in parsed_tree:
if isinstance(node, abjad.rhythmtrees.RhythmTreeContainer):
layers.append(node)
layers.append(self._return_layers(node))
else:
layers.append(node)
return layers
def _quantize_rhythm_tree(self, rtm_container):
layers = self._return_layers(rtm_container)
self._recursive_operation(layers)