Changed default options to immutable MappingProxyType

Options as immutable dicts are less prone to bugs
This commit is contained in:
2023-02-22 22:14:10 +02:00
parent 4dd0f12aca
commit 805d7af216
2 changed files with 52 additions and 36 deletions

View File

@ -319,18 +319,19 @@ class Sequence(Meta):
if isinstance(item, ListOperation):
yield from item.evaluate(options)
elif isinstance(item, RepeatedSequence):
item.evaluate_values(options)
repeats = item.repeats.get_value(options)
yield from _normal_repeat(item.evaluated_values, repeats, options)
elif isinstance(item, RepeatedListSequence):
repeats = item.repeats.get_value(options)
yield from _generative_repeat(item, repeats, options)
elif isinstance(item, Subdivision):
item.evaluated_values = list(item.evaluate_tree(options))
item.evaluate_values(options)
yield item
else:
yield from item.evaluate_tree(options)
elif isinstance(item, Range):
yield from item.evaluate(options)
yield from item.evaluate(options)
elif isinstance(item, Cyclic):
yield from _resolve_item(item.get_value(), options)
elif isinstance(item, Euclid):
@ -486,7 +487,7 @@ class Sequence(Meta):
class Ziffers(Sequence):
"""Main class for holding options and the current state"""
options: dict = field(default_factory=DEFAULT_OPTIONS)
options: dict = field(default_factory=DEFAULT_OPTIONS.copy())
start_options: dict = None
loop_i: int = field(default=0, init=False)
cycle_i: int = field(default=0, init=False)
@ -514,11 +515,11 @@ class Ziffers(Sequence):
# pylint: disable=locally-disabled, dangerous-default-value
def init_opts(self, options=None):
"""Evaluate the Ziffers tree using the options"""
self.options.update(DEFAULT_OPTIONS)
self.options.update(DEFAULT_OPTIONS.copy())
if options:
self.options.update(options)
else:
self.options = DEFAULT_OPTIONS
self.options = DEFAULT_OPTIONS.copy()
self.start_options = self.options.copy()
self.init_tree(self.options)
@ -531,12 +532,14 @@ class Ziffers(Sequence):
self.init_tree(self.options)
def init_tree(self, options):
"""Initialize evaluated values and perform post-evaluation"""
self.evaluated_values = list(self.evaluate_tree(options))
self.evaluated_values = list(self.post_check())
self.evaluated_values = list(self.post_evaluation())
self.iterator = iter(self.evaluated_values)
self.cycle_length = len(self.evaluated_values)
def post_check(self):
def post_evaluation(self):
"""Post-evaluation performs evaluation that can only be done after initial evaluation"""
for item in self.evaluated_values:
if isinstance(item, Subdivision):
yield from item.evaluate_durations()
@ -568,7 +571,7 @@ class Ziffers(Sequence):
Args:
options (dict): Options as a dict
"""
self.options = DEFAULT_OPTIONS | options
self.options = DEFAULT_OPTIONS.copy() | options
def pitch_classes(self) -> list[int]:
"""Return list of pitch classes as ints"""
@ -650,6 +653,10 @@ class RepeatedListSequence(Sequence):
class Subdivision(Sequence):
"""Class for subdivisions"""
def evaluate_values(self, options):
"""Evaluate values and store to evaluated_values"""
self.evaluated_values = list(self.evaluate_tree(options))
def evaluate_durations(self, duration=None):
"""Calculate new durations by dividing with the number of items in the sequence"""
if duration is None:
@ -702,17 +709,17 @@ class Range(Item):
end: int = field(default=None)
def evaluate(self, options):
if self.start<self.end:
for i in range(self.start,self.end+1):
"""Evaluates range and generates a generator of Pitches"""
if self.start < self.end:
for i in range(self.start, self.end + 1):
yield Pitch(pitch_class=i, kwargs=options)
elif self.start>self.end:
for i in reversed(range(self.end,self.start+1)):
elif self.start > self.end:
for i in reversed(range(self.end, self.start + 1)):
yield Pitch(pitch_class=i, kwargs=options)
else:
yield Pitch(pitch_class=self.start, kwargs=options)
@dataclass(kw_only=True)
class Operator(Item):
"""Class for math operators"""
@ -730,8 +737,7 @@ class ListOperation(Sequence):
super().__post_init__()
self.evaluated_values = self.evaluate()
# pylint: disable=locally-disabled, dangerous-default-value
def evaluate(self, options=DEFAULT_OPTIONS):
def evaluate(self, options=DEFAULT_OPTIONS.copy()):
"""Evaluates the operation"""
def filter_operation(input_list):
@ -766,7 +772,7 @@ class ListOperation(Sequence):
(right.values if isinstance(right, Sequence) else [right]), left
)
left = [
#TODO: Get options from x value?
# TODO: Get options from x value?
Pitch(
pitch_class=operation(x.get_value(options), y.get_value(options)),
kwargs=options,
@ -858,12 +864,13 @@ class RepeatedSequence(Sequence):
evaluated_values: list = None
def __post_init__(self):
super().__post_init__()
self.evaluated_values = list(self.evaluate())
def evaluate_values(self, options):
"""Evaluate values and store to evaluated_values"""
self.evaluated_values = list(self.evaluate(options))
def evaluate(self):
def evaluate(self, options: dict):
"""Evaluate repeated sequence partially. Leaves Cycles intact."""
self.local_options = options.copy()
for item in self.values:
if isinstance(item, Sequence):
if isinstance(item, ListOperation):
@ -879,6 +886,9 @@ class RepeatedSequence(Sequence):
elif isinstance(item, Rest):
yield item.get_updated_item(self.local_options)
elif isinstance(item, Range):
yield from item.evaluate(options)
yield from item.evaluate(self.local_options)
elif isinstance(item, (Event, RandomInteger)):
yield Pitch(pitch_class=item.get_value(self.local_options), kwargs=self.local_options)
yield Pitch(
pitch_class=item.get_value(self.local_options),
kwargs=self.local_options,
)

View File

@ -1,7 +1,8 @@
""" Default options for Ziffers """
import operator
from types import MappingProxyType
DEFAULT_DURS = {
DEFAULT_DURS = MappingProxyType({
"m": 8.0, # 15360/1920
"k": 10240 / 1920, # ~5.333
"l": 4.0, # 7680/1920
@ -37,13 +38,18 @@ DEFAULT_DURS = {
"j": 15 / 1920, # ~0.0078 - 1/128
"o": 8 / 1920, # ~0.00416
"z": 0.0, # 0
}
})
DEFAULT_OCTAVE = 4
DEFAULT_OPTIONS = {"octave": 0, "duration": 0.25, "key": "C4", "scale": "IONIAN"}
DEFAULT_OPTIONS = MappingProxyType({
"octave": 0,
"duration": 0.25,
"key": "C4",
"scale": "IONIAN"
})
OPERATORS = {
OPERATORS = MappingProxyType({
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
@ -53,10 +59,10 @@ OPERATORS = {
"&": operator.and_,
"<<": operator.ilshift,
">>": operator.irshift
}
})
NOTES_TO_INTERVALS = {
NOTES_TO_INTERVALS = MappingProxyType({
"C": 0,
"Cs": 1,
"D": 2,
@ -69,9 +75,9 @@ NOTES_TO_INTERVALS = {
"A": 9,
"Bb": 10,
"B": 11,
}
})
INTERVALS_TO_NOTES = {
INTERVALS_TO_NOTES = MappingProxyType({
0: "C",
1: "Cs",
2: "D",
@ -84,9 +90,9 @@ INTERVALS_TO_NOTES = {
9: "A",
10: "Bb",
11: "B",
}
})
CIRCLE_OF_FIFTHS = [
CIRCLE_OF_FIFTHS = (
"Gb",
"Cs",
"Ab",
@ -100,15 +106,15 @@ CIRCLE_OF_FIFTHS = [
"E",
"B",
"Fs",
]
)
MODIFIERS = {
MODIFIERS = MappingProxyType({
"#": 1,
"b": -1,
"s": 1,
}
})
ROMANS = {"i": 1, "v": 5, "x": 10, "l": 50, "c": 100, "d": 500, "m": 1000}
ROMANS = MappingProxyType({"i": 1, "v": 5, "x": 10, "l": 50, "c": 100, "d": 500, "m": 1000})
# pylint: disable=locally-disabled, too-many-lines