Added euclidean evaluation

This commit is contained in:
2023-02-19 22:56:11 +02:00
parent cca08b250f
commit 9a4c970b95
3 changed files with 69 additions and 6 deletions

View File

@ -5,6 +5,7 @@ import operator
import random
from .defaults import DEFAULT_OPTIONS
from .scale import note_from_pc, midi_to_pitch_class, midi_to_freq, get_scale_length
from .common import euclidian_rhythm
@dataclass(kw_only=True)
@ -326,6 +327,8 @@ class Sequence(Meta):
yield from item.evaluate_tree(options)
elif isinstance(item, Cyclic):
yield from _resolve_item(item.get_value(), options)
elif isinstance(item, Euclid):
yield from _euclidean_items(item, options)
elif isinstance(item, Modification):
options = _update_options(item, options)
elif isinstance(item, Meta): # Filters whitespace
@ -362,6 +365,13 @@ class Sequence(Meta):
for item in tree:
yield from _resolve_item(item, options)
def _euclidean_items(euclid: Item, options: dict):
"""Repeats items with the same random values"""
euclid.evaluate(options)
for item in euclid.evaluated_values:
yield from _resolve_item(item, options)
def _update_options(current: Item, options: dict) -> dict:
"""Update options based on current item"""
if isinstance(current, (OctaveChange, DurationChange)):
@ -727,7 +737,9 @@ class ListOperation(Sequence):
(right.values if isinstance(right, Sequence) else [right]), left
)
left = [
Pitch(pitch_class=operation(x.get_value(), y.get_value()), kwargs=options)
Pitch(
pitch_class=operation(x.get_value(), y.get_value()), kwargs=options
)
for (x, y) in pairs
]
return left
@ -767,9 +779,40 @@ class Euclid(Item):
pulses: int
length: int
onset: list
offset: list = field(default=None)
rotate: int = field(default=None)
onset: ListSequence
offset: ListSequence = field(default=None)
rotate: int = field(default=0)
evaluated_values: list = field(default=None)
def evaluate(self, options):
onset_values = [
val for val in self.onset.values if not isinstance(val, Whitespace)
]
onset_length = len(onset_values)
booleans = euclidian_rhythm(self.pulses, self.length, self.rotate)
self.evaluated_values = []
if self.offset is not None:
offset_values = [
val for val in self.offset.values if not isinstance(val, Whitespace)
]
offset_length = len(offset_values)
on_i = 0
off_i = 0
for i in range(self.length):
if booleans[i]:
value = onset_values[on_i % onset_length]
on_i+=1
else:
if self.offset is None:
value = Rest(duration=options["duration"])
else:
value = offset_values[off_i % offset_length]
off_i+=1
self.evaluated_values.append(value)
@dataclass(kw_only=True)

View File

@ -53,3 +53,23 @@ def string_rewrite(axiom: str, rules: dict):
pattern = re.compile("|".join(rules.keys()))
return pattern.sub(lambda m: next(_apply_rules(m)), axiom)
def euclidian_rhythm(pulses: int, length: int, rotate: int = 0):
"""Calculate Euclidean rhythms. Original algorithm by Thomas Morrill."""
def _starts_descent(list, index):
length = len(list)
next_index = (index + 1) % length
return list[index] > list[next_index]
def rotation(l, n):
return l[-n:] + l[:-n]
if pulses >= length:
return [True]
res_list = [pulses * t % length for t in range(-1, length - 1)]
bool_list = [_starts_descent(res_list, index) for index in range(length)]
return rotation(bool_list, rotate)

View File

@ -340,10 +340,10 @@ class ZiffersTransformer(Transformer):
def euclid(self, items):
"""Parse euclid notation"""
params = items[1][1:-1].split(",")
init = {"onset": items[0], "pulses": params[0], "length": params[1]}
init = {"onset": items[0], "pulses": int(params[0]), "length": int(params[1])}
text = items[0].text + items[1]
if len(params) > 2:
init["rotate"] = params[2]
init["rotate"] = int(params[2])
if len(items) > 2:
init["offset"] = items[2]
text = text + items[2].text