Added euclidean evaluation
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user