Refactored evaluation for subdivisions
This commit is contained in:
@ -2,9 +2,12 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from ziffers import zparse
|
from ziffers import zparse
|
||||||
|
|
||||||
|
# pylint: disable=missing-function-docstring, line-too-long, invalid-name
|
||||||
|
|
||||||
def test_can_parse():
|
def test_can_parse():
|
||||||
expressions = [
|
expressions = [
|
||||||
|
"0 1 2 3 4 5 6 7 8 9 T E",
|
||||||
|
"023 i iv iv^min",
|
||||||
"[1 [2 3]]",
|
"[1 [2 3]]",
|
||||||
"(1 (1,3) 1..3)",
|
"(1 (1,3) 1..3)",
|
||||||
"_^ q _qe^3 qww_4 _123 <1 2>",
|
"_^ q _qe^3 qww_4 _123 <1 2>",
|
||||||
@ -12,6 +15,8 @@ def test_can_parse():
|
|||||||
"2 qe2 e4",
|
"2 qe2 e4",
|
||||||
"q 2 <3 343>",
|
"q 2 <3 343>",
|
||||||
"q (2 <3 343 (3 4)>)",
|
"q (2 <3 343 (3 4)>)",
|
||||||
|
"? 1 2",
|
||||||
|
"(? 2 ? 4)+(1,4)"
|
||||||
]
|
]
|
||||||
results = []
|
results = []
|
||||||
for expression in expressions:
|
for expression in expressions:
|
||||||
@ -49,6 +54,36 @@ def test_parsing_text(pattern: str):
|
|||||||
def test_pitch_classes(pattern: str, expected: list):
|
def test_pitch_classes(pattern: str, expected: list):
|
||||||
assert zparse(pattern).pitch_classes() == expected
|
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]),
|
@pytest.mark.parametrize(
|
||||||
# ("_ 1 _2 <3>3 ^^4", [-1,-2,3,-1]),
|
"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
|
self.freq = freq
|
||||||
|
|
||||||
# pylint: disable=locally-disabled, unused-argument
|
# pylint: disable=locally-disabled, unused-argument
|
||||||
def get_value(self) -> int:
|
def get_value(self, options) -> int:
|
||||||
"""Returns the pitch class
|
"""Returns the pitch class
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -318,17 +318,14 @@ class Sequence(Meta):
|
|||||||
if isinstance(item, ListOperation):
|
if isinstance(item, ListOperation):
|
||||||
yield from item.evaluate(options)
|
yield from item.evaluate(options)
|
||||||
elif isinstance(item, RepeatedSequence):
|
elif isinstance(item, RepeatedSequence):
|
||||||
repeats = item.repeats.get_value()
|
repeats = item.repeats.get_value(options)
|
||||||
yield from _normal_repeat(item.evaluated_values, repeats, options)
|
yield from _normal_repeat(item.evaluated_values, repeats, options)
|
||||||
elif isinstance(item, RepeatedListSequence):
|
elif isinstance(item, RepeatedListSequence):
|
||||||
repeats = item.repeats.get_value()
|
repeats = item.repeats.get_value(options)
|
||||||
yield from _generative_repeat(item, repeats, options)
|
yield from _generative_repeat(item, repeats, options)
|
||||||
elif isinstance(item, Subdivision):
|
elif isinstance(item, Subdivision):
|
||||||
items = item.evaluate(options)
|
item.evaluated_values = list(item.evaluate_tree(options))
|
||||||
if item.has_children:
|
yield item
|
||||||
yield from items.evaluated_values
|
|
||||||
else:
|
|
||||||
yield from _loop_items(item, options)
|
|
||||||
else:
|
else:
|
||||||
yield from item.evaluate_tree(options)
|
yield from item.evaluate_tree(options)
|
||||||
elif isinstance(item, Cyclic):
|
elif isinstance(item, Cyclic):
|
||||||
@ -513,17 +510,27 @@ class Ziffers(Sequence):
|
|||||||
self.options = DEFAULT_OPTIONS
|
self.options = DEFAULT_OPTIONS
|
||||||
|
|
||||||
self.start_options = self.options.copy()
|
self.start_options = self.options.copy()
|
||||||
self.evaluated_values = list(self.evaluate_tree(self.options))
|
self.init_tree(self.options)
|
||||||
self.iterator = iter(self.evaluated_values)
|
|
||||||
|
|
||||||
def re_eval(self, options=None):
|
def re_eval(self, options=None):
|
||||||
"""Re-evaluate the iterator"""
|
"""Re-evaluate the iterator"""
|
||||||
self.options = self.start_options.copy()
|
self.options = self.start_options.copy()
|
||||||
if options:
|
if options:
|
||||||
self.options.update(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)
|
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):
|
def get_list(self):
|
||||||
"""Return list"""
|
"""Return list"""
|
||||||
return list(self)
|
return list(self)
|
||||||
@ -593,7 +600,7 @@ class Integer(Item):
|
|||||||
value: int
|
value: int
|
||||||
|
|
||||||
# pylint: disable=locally-disabled, unused-argument
|
# pylint: disable=locally-disabled, unused-argument
|
||||||
def get_value(self):
|
def get_value(self, options):
|
||||||
"""Return value of the integer"""
|
"""Return value of the integer"""
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
@ -631,31 +638,17 @@ class RepeatedListSequence(Sequence):
|
|||||||
class Subdivision(Sequence):
|
class Subdivision(Sequence):
|
||||||
"""Class for subdivisions"""
|
"""Class for subdivisions"""
|
||||||
|
|
||||||
subdiv_length: float = field(default=None, init=False)
|
def evaluate_durations(self, duration=None):
|
||||||
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):
|
|
||||||
"""Calculate new durations by dividing with the number of items in the sequence"""
|
"""Calculate new durations by dividing with the number of items in the sequence"""
|
||||||
self.subdiv_length = len(self.evaluated_values)
|
if duration is None:
|
||||||
self.local_options = options.copy()
|
duration = self.evaluated_values[0].duration
|
||||||
self.local_options["duration"] = options["duration"] / self.subdiv_length
|
new_d = duration / len(self.evaluated_values)
|
||||||
for item in self.evaluated_values:
|
for item in self.evaluated_values:
|
||||||
if isinstance(item, Subdivision):
|
if isinstance(item, Subdivision):
|
||||||
self.has_children = True
|
yield from item.evaluate_durations(new_d)
|
||||||
yield from item.evaluate_subdivisions(self.local_options)
|
if isinstance(item, Event):
|
||||||
elif isinstance(item, Cyclic):
|
if duration is not None:
|
||||||
yield item # Return the cycle
|
item.duration = new_d
|
||||||
elif isinstance(item, Rest):
|
|
||||||
yield item.get_updated_item(self.local_options)
|
|
||||||
elif isinstance(item, Pitch):
|
|
||||||
item.duration = self.local_options["duration"]
|
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
|
|
||||||
@ -748,7 +741,8 @@ class ListOperation(Sequence):
|
|||||||
)
|
)
|
||||||
left = [
|
left = [
|
||||||
Pitch(
|
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
|
for (x, y) in pairs
|
||||||
]
|
]
|
||||||
@ -856,4 +850,4 @@ class RepeatedSequence(Sequence):
|
|||||||
elif isinstance(item, Rest):
|
elif isinstance(item, Rest):
|
||||||
yield item.get_updated_item(self.local_options)
|
yield item.get_updated_item(self.local_options)
|
||||||
elif isinstance(item, (Event, RandomInteger)):
|
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):
|
def pitch(self, items):
|
||||||
"""Return pitch class info"""
|
"""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):
|
def prefix(self, items):
|
||||||
"""Return prefix"""
|
"""Return prefix"""
|
||||||
|
|||||||
Reference in New Issue
Block a user