Refactored evaluation for subdivisions
This commit is contained in:
@ -2,9 +2,12 @@
|
||||
import pytest
|
||||
from ziffers import zparse
|
||||
|
||||
# pylint: disable=missing-function-docstring, line-too-long, invalid-name
|
||||
|
||||
def test_can_parse():
|
||||
expressions = [
|
||||
"0 1 2 3 4 5 6 7 8 9 T E",
|
||||
"023 i iv iv^min",
|
||||
"[1 [2 3]]",
|
||||
"(1 (1,3) 1..3)",
|
||||
"_^ q _qe^3 qww_4 _123 <1 2>",
|
||||
@ -12,6 +15,8 @@ def test_can_parse():
|
||||
"2 qe2 e4",
|
||||
"q 2 <3 343>",
|
||||
"q (2 <3 343 (3 4)>)",
|
||||
"? 1 2",
|
||||
"(? 2 ? 4)+(1,4)"
|
||||
]
|
||||
results = []
|
||||
for expression in expressions:
|
||||
@ -49,6 +54,36 @@ def test_parsing_text(pattern: str):
|
||||
def test_pitch_classes(pattern: str, expected: list):
|
||||
assert zparse(pattern).pitch_classes() == expected
|
||||
|
||||
# TODO: Add tests for octaves
|
||||
# ("__6 _0 _1 _2 _3 _4 _5 _6 0 1 2 3 4 5 6 ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^^0", [-2, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2]),
|
||||
# ("_ 1 _2 <3>3 ^^4", [-1,-2,3,-1]),
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pattern,expected",
|
||||
[
|
||||
("__6 _0 _1 _2 _3 _4 _5 _6 0 1 2 3 4 5 6 ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^^0", [-2, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2]),
|
||||
("_ 1 _2 <3>3 ^^4", [-1, -1, 3, 2]),
|
||||
]
|
||||
)
|
||||
def test_pitch_octaves(pattern: str, expected: list):
|
||||
assert zparse(pattern).octaves() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pattern,expected",
|
||||
[
|
||||
("w [1 [2 3]]", [0.5, 0.25, 0.25]),
|
||||
("1.0 [1 [2 3]] 4 [3 [4 5]]", [0.5, 0.25, 0.25, 1.0, 0.5, 0.25, 0.25]),
|
||||
("0.5 (0 0.25 3)+1", [0.5, 0.25])
|
||||
]
|
||||
)
|
||||
def test_subdivisions(pattern: str, expected: list):
|
||||
assert zparse(pattern).durations() == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pattern,expected",
|
||||
[
|
||||
("[: 1 [: 2 :] 3 :]", [62, 64, 64, 65, 62, 64, 64, 65]),
|
||||
("(: 1 (: 2 :) 3 :)", [62, 64, 64, 65, 62, 64, 64, 65])
|
||||
]
|
||||
)
|
||||
def test_repeats(pattern: str, expected: list):
|
||||
assert zparse(pattern).notes() == expected
|
||||
|
||||
|
||||
@ -174,7 +174,7 @@ class Pitch(Event):
|
||||
self.freq = freq
|
||||
|
||||
# pylint: disable=locally-disabled, unused-argument
|
||||
def get_value(self) -> int:
|
||||
def get_value(self, options) -> int:
|
||||
"""Returns the pitch class
|
||||
|
||||
Returns:
|
||||
@ -318,17 +318,14 @@ class Sequence(Meta):
|
||||
if isinstance(item, ListOperation):
|
||||
yield from item.evaluate(options)
|
||||
elif isinstance(item, RepeatedSequence):
|
||||
repeats = item.repeats.get_value()
|
||||
repeats = item.repeats.get_value(options)
|
||||
yield from _normal_repeat(item.evaluated_values, repeats, options)
|
||||
elif isinstance(item, RepeatedListSequence):
|
||||
repeats = item.repeats.get_value()
|
||||
repeats = item.repeats.get_value(options)
|
||||
yield from _generative_repeat(item, repeats, options)
|
||||
elif isinstance(item, Subdivision):
|
||||
items = item.evaluate(options)
|
||||
if item.has_children:
|
||||
yield from items.evaluated_values
|
||||
else:
|
||||
yield from _loop_items(item, options)
|
||||
item.evaluated_values = list(item.evaluate_tree(options))
|
||||
yield item
|
||||
else:
|
||||
yield from item.evaluate_tree(options)
|
||||
elif isinstance(item, Cyclic):
|
||||
@ -513,17 +510,27 @@ class Ziffers(Sequence):
|
||||
self.options = DEFAULT_OPTIONS
|
||||
|
||||
self.start_options = self.options.copy()
|
||||
self.evaluated_values = list(self.evaluate_tree(self.options))
|
||||
self.iterator = iter(self.evaluated_values)
|
||||
self.init_tree(self.options)
|
||||
|
||||
def re_eval(self, options=None):
|
||||
"""Re-evaluate the iterator"""
|
||||
self.options = self.start_options.copy()
|
||||
if options:
|
||||
self.options.update(options)
|
||||
self.evaluated_values = list(self.evaluate_tree(self.options))
|
||||
self.init_tree(self.options)
|
||||
|
||||
def init_tree(self, options):
|
||||
self.evaluated_values = list(self.evaluate_tree(options))
|
||||
self.evaluated_values = list(self.post_check())
|
||||
self.iterator = iter(self.evaluated_values)
|
||||
|
||||
def post_check(self):
|
||||
for item in self.evaluated_values:
|
||||
if isinstance(item, Subdivision):
|
||||
yield from item.evaluate_durations()
|
||||
else:
|
||||
yield item
|
||||
|
||||
def get_list(self):
|
||||
"""Return list"""
|
||||
return list(self)
|
||||
@ -593,7 +600,7 @@ class Integer(Item):
|
||||
value: int
|
||||
|
||||
# pylint: disable=locally-disabled, unused-argument
|
||||
def get_value(self):
|
||||
def get_value(self, options):
|
||||
"""Return value of the integer"""
|
||||
return self.value
|
||||
|
||||
@ -631,31 +638,17 @@ class RepeatedListSequence(Sequence):
|
||||
class Subdivision(Sequence):
|
||||
"""Class for subdivisions"""
|
||||
|
||||
subdiv_length: float = field(default=None, init=False)
|
||||
local_options: dict = None
|
||||
has_children: bool = field(default=False, init=False)
|
||||
|
||||
def evaluate(self, options):
|
||||
"""Evaluate tree and then calculate lengths using subdivision"""
|
||||
self.evaluated_values = list(self.evaluate_tree(options.copy()))
|
||||
self.evaluated_values = list(self.evaluate_subdivisions(options))
|
||||
return self
|
||||
|
||||
def evaluate_subdivisions(self, options):
|
||||
def evaluate_durations(self, duration=None):
|
||||
"""Calculate new durations by dividing with the number of items in the sequence"""
|
||||
self.subdiv_length = len(self.evaluated_values)
|
||||
self.local_options = options.copy()
|
||||
self.local_options["duration"] = options["duration"] / self.subdiv_length
|
||||
if duration is None:
|
||||
duration = self.evaluated_values[0].duration
|
||||
new_d = duration / len(self.evaluated_values)
|
||||
for item in self.evaluated_values:
|
||||
if isinstance(item, Subdivision):
|
||||
self.has_children = True
|
||||
yield from item.evaluate_subdivisions(self.local_options)
|
||||
elif isinstance(item, Cyclic):
|
||||
yield item # Return the cycle
|
||||
elif isinstance(item, Rest):
|
||||
yield item.get_updated_item(self.local_options)
|
||||
elif isinstance(item, Pitch):
|
||||
item.duration = self.local_options["duration"]
|
||||
yield from item.evaluate_durations(new_d)
|
||||
if isinstance(item, Event):
|
||||
if duration is not None:
|
||||
item.duration = new_d
|
||||
yield item
|
||||
|
||||
|
||||
@ -748,7 +741,8 @@ class ListOperation(Sequence):
|
||||
)
|
||||
left = [
|
||||
Pitch(
|
||||
pitch_class=operation(x.get_value(), y.get_value()), kwargs=options
|
||||
pitch_class=operation(x.get_value(options), y.get_value(options)),
|
||||
kwargs=options,
|
||||
)
|
||||
for (x, y) in pairs
|
||||
]
|
||||
@ -856,4 +850,4 @@ class RepeatedSequence(Sequence):
|
||||
elif isinstance(item, Rest):
|
||||
yield item.get_updated_item(self.local_options)
|
||||
elif isinstance(item, (Event, RandomInteger)):
|
||||
yield Pitch(pitch_class=item.get_value(), kwargs=self.local_options)
|
||||
yield Pitch(pitch_class=item.get_value(self.local_options), kwargs=self.local_options)
|
||||
|
||||
@ -92,7 +92,8 @@ class ZiffersTransformer(Transformer):
|
||||
|
||||
def pitch(self, items):
|
||||
"""Return pitch class info"""
|
||||
return {"pitch_class": int(items[0].value), "text": items[0].value}
|
||||
text_value = items[0].value.replace("T","10").replace("E","11")
|
||||
return {"pitch_class": int(text_value), "text": items[0].value}
|
||||
|
||||
def prefix(self, items):
|
||||
"""Return prefix"""
|
||||
|
||||
Reference in New Issue
Block a user