Refactored list operation to use product

Evaluated pitches are now stored to evaluated_valued variable
This commit is contained in:
2023-02-12 22:05:48 +02:00
parent 70b56dbc52
commit 4f019bfda4
4 changed files with 69 additions and 59 deletions

View File

@ -1,6 +1,6 @@
""" Test cases for the parser """ """ Test cases for the parser """
import pytest import pytest
from ziffers import parse_expression from ziffers import zparse
def test_can_parse(): def test_can_parse():
@ -17,7 +17,7 @@ def test_can_parse():
for expression in expressions: for expression in expressions:
try: try:
print(f"Parsing expression: {expression}") print(f"Parsing expression: {expression}")
result = parse_expression(expression) result = zparse(expression)
results.append(True) results.append(True)
except Exception as e: except Exception as e:
print(e) print(e)
@ -36,7 +36,7 @@ def test_can_parse():
], ],
) )
def test_parsing_text(pattern: str): def test_parsing_text(pattern: str):
assert parse_expression(pattern).text == pattern assert zparse(pattern).text == pattern
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -47,7 +47,7 @@ def test_parsing_text(pattern: str):
], ],
) )
def test_pitch_classes(pattern: str, expected: list): def test_pitch_classes(pattern: str, expected: list):
assert parse_expression(pattern).pitch_classes() == expected assert zparse(pattern).pitch_classes() == expected
# TODO: Add tests for octaves # 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]), # ("__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]),

View File

@ -1,6 +1,6 @@
""" Ziffers classes for the parsed notation """ """ Ziffers classes for the parsed notation """
from dataclasses import dataclass, field, replace from dataclasses import dataclass, field, replace
import itertools from itertools import product, islice, cycle
import operator import operator
import random import random
from .defaults import DEFAULT_OPTIONS from .defaults import DEFAULT_OPTIONS
@ -225,7 +225,7 @@ class Sequence(Meta):
wrap_start: str = field(default=None, repr=False) wrap_start: str = field(default=None, repr=False)
wrap_end: str = field(default=None, repr=False) wrap_end: str = field(default=None, repr=False)
local_index: int = field(default=0, init=False) local_index: int = field(default=0, init=False)
evaluation: bool = field(default=False, init=False) evaluated_values: list = field(default=None)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@ -250,12 +250,13 @@ class Sequence(Meta):
text = text + self.wrap_end text = text + self.wrap_end
return text return text
def evaluate_tree(self, options=None): def evaluate_tree(self, options=None, eval_tree=False):
"""Evaluates and flattens the Ziffers object tree""" """Evaluates and flattens the Ziffers object tree"""
for item in self.values: values = self.evaluated_values if eval_tree else self.values
for item in values:
if isinstance(item, Sequence): if isinstance(item, Sequence):
if item.evaluation: if isinstance(item, ListOperation):
yield from item.evaluate(options) yield from item.evaluate_tree(options, True)
else: else:
yield from item.evaluate_tree(options) yield from item.evaluate_tree(options)
else: else:
@ -425,14 +426,16 @@ class Ziffers(Sequence):
else: else:
self.options = DEFAULT_OPTIONS self.options = DEFAULT_OPTIONS
self.iterator = iter(self.evaluate_tree(self.options)) self.evaluated_values = list(self.evaluate_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.update(DEFAULT_OPTIONS) self.options.update(DEFAULT_OPTIONS)
if options: if options:
self.options.update(options) self.options.update(options)
self.iterator = iter(self.evaluate_tree(self.options)) self.evaluated_values = list(self.evaluate_tree(self.options))
self.iterator = iter(self.evaluated_values)
def get_list(self): def get_list(self):
"""Return list""" """Return list"""
@ -447,11 +450,11 @@ class Ziffers(Sequence):
Returns: Returns:
list: List of pitch class items list: List of pitch class items
""" """
return list(itertools.islice(itertools.cycle(self), num)) return list(islice(cycle(self), num))
def loop(self) -> iter: def loop(self) -> iter:
"""Return cyclic loop""" """Return cyclic loop"""
return itertools.cycle(iter(self)) return cycle(iter(self))
def set_defaults(self, options: dict): def set_defaults(self, options: dict):
"""Sets options for the parser """Sets options for the parser
@ -461,24 +464,27 @@ class Ziffers(Sequence):
""" """
self.options = DEFAULT_OPTIONS | options self.options = DEFAULT_OPTIONS | options
# TODO: Refactor these
def pitch_classes(self) -> list[int]: def pitch_classes(self) -> list[int]:
"""Return list of pitch classes as ints""" """Return list of pitch classes as ints"""
return [val.pitch_class for val in self.values if isinstance(val, Pitch)] return [
val.pitch_class for val in self.evaluated_values if isinstance(val, Pitch)
]
def durations(self) -> list[float]: def durations(self) -> list[float]:
"""Return list of pitch durations as floats""" """Return list of pitch durations as floats"""
return [val.dur for val in self.values if isinstance(val, Pitch)] return [val.duration for val in self.evaluated_values if isinstance(val, Pitch)]
def pairs(self) -> list[tuple]: def pairs(self) -> list[tuple]:
"""Return list of pitches and durations""" """Return list of pitches and durations"""
return [ return [
(val.pitch_class, val.dur) for val in self.values if isinstance(val, Pitch) (val.pitch_class, val.duration)
for val in self.evaluated_values
if isinstance(val, Pitch)
] ]
def octaves(self) -> list[int]: def octaves(self) -> list[int]:
"""Return list of octaves""" """Return list of octaves"""
return [val.octave for val in self.values if isinstance(val, Pitch)] return [val.octave for val in self.evaluated_values if isinstance(val, Pitch)]
@dataclass(kw_only=True) @dataclass(kw_only=True)
@ -586,46 +592,48 @@ class Operator(Item):
class ListOperation(Sequence): class ListOperation(Sequence):
"""Class for list operations""" """Class for list operations"""
evaluated_values: list = None
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
self.evaluation = True self.evaluated_values = self.evaluate()
def filter_operation(self, values): def evaluate(self):
"""Filtering for the operation elements"""
keep = (Sequence, Event, RandomInteger, Integer, Cyclic)
for item in values:
if isinstance(item, Sequence):
yield item.filter(keep)
elif isinstance(item, keep):
yield item
def evaluate(self, options: dict):
"""Evaluates the operation""" """Evaluates the operation"""
def filter_operation(input_list):
flattened_list = []
for item in input_list:
if isinstance(item, (list, Sequence)):
if isinstance(item, ListOperation):
flattened_list.extend(item.evaluated_values)
else:
flattened_list.append(filter_operation(item))
elif isinstance(item, (Event, RandomInteger, Integer, Cyclic)):
flattened_list.append(item)
if isinstance(input_list, Sequence):
return replace(input_list, values=flattened_list)
return flattened_list
operators = self.values[1::2] # Fetch every second operator element operators = self.values[1::2] # Fetch every second operator element
values = self.values[::2] # Fetch every second list element values = self.values[::2] # Fetch every second list element
values = list(self.filter_operation(values)) # Filter out crap values = filter_operation(values) # Filter out crap
result = values[0] # Start results with the first array left = values[0] # Start results with the first array
for i, operand in enumerate(operators): for i, operand in enumerate(operators):
operation = operand.value operation = operand.value
right_value = values[i + 1] right = values[i + 1]
if isinstance(right_value, Sequence): pairs = product(
result = [ (right.values if isinstance(right, Sequence) else [right]), left
Pitch( )
pitch_class=operation(x.get_value(), y.get_value()), left = [
kwargs=options, Pitch(pitch_class=operation(x.get_value(), y.get_value()))
) for (x, y) in pairs
for x in result ]
for y in right_value return left
]
else:
result = [
Pitch(
pitch_class=operation(x.get_value(), right_value.get_value()),
kwargs=options,
)
for x in result
]
return Sequence(values=result)
@dataclass(kw_only=True) @dataclass(kw_only=True)

View File

@ -17,3 +17,4 @@ def sum_dict(arr: list[dict]) -> dict:
else: else:
result[key] = element[key] result[key] = element[key]
return result return result

View File

@ -43,6 +43,15 @@ DEFAULT_OCTAVE = 4
DEFAULT_OPTIONS = {"octave": 0, "duration": 0.25} DEFAULT_OPTIONS = {"octave": 0, "duration": 0.25}
OPERATORS = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
"%": operator.mod,
}
NOTES_TO_INTERVALS = { NOTES_TO_INTERVALS = {
'C': 0, 'C': 0,
'Cs': 1, 'Cs': 1,
@ -83,14 +92,6 @@ MODIFIERS = {
ROMANS = {"i": 1, "v": 5, "x": 10, "l": 50, "c": 100, "d": 500, "m": 1000} ROMANS = {"i": 1, "v": 5, "x": 10, "l": 50, "c": 100, "d": 500, "m": 1000}
OPERATORS = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
"%": operator.mod,
}
# pylint: disable=locally-disabled, too-many-lines # pylint: disable=locally-disabled, too-many-lines
SCALES = { SCALES = {