Added euclidean evaluation
This commit is contained in:
@ -5,6 +5,7 @@ import operator
|
|||||||
import random
|
import random
|
||||||
from .defaults import DEFAULT_OPTIONS
|
from .defaults import DEFAULT_OPTIONS
|
||||||
from .scale import note_from_pc, midi_to_pitch_class, midi_to_freq, get_scale_length
|
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)
|
@dataclass(kw_only=True)
|
||||||
@ -326,6 +327,8 @@ class Sequence(Meta):
|
|||||||
yield from item.evaluate_tree(options)
|
yield from item.evaluate_tree(options)
|
||||||
elif isinstance(item, Cyclic):
|
elif isinstance(item, Cyclic):
|
||||||
yield from _resolve_item(item.get_value(), options)
|
yield from _resolve_item(item.get_value(), options)
|
||||||
|
elif isinstance(item, Euclid):
|
||||||
|
yield from _euclidean_items(item, options)
|
||||||
elif isinstance(item, Modification):
|
elif isinstance(item, Modification):
|
||||||
options = _update_options(item, options)
|
options = _update_options(item, options)
|
||||||
elif isinstance(item, Meta): # Filters whitespace
|
elif isinstance(item, Meta): # Filters whitespace
|
||||||
@ -362,6 +365,13 @@ class Sequence(Meta):
|
|||||||
for item in tree:
|
for item in tree:
|
||||||
yield from _resolve_item(item, options)
|
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:
|
def _update_options(current: Item, options: dict) -> dict:
|
||||||
"""Update options based on current item"""
|
"""Update options based on current item"""
|
||||||
if isinstance(current, (OctaveChange, DurationChange)):
|
if isinstance(current, (OctaveChange, DurationChange)):
|
||||||
@ -727,7 +737,9 @@ class ListOperation(Sequence):
|
|||||||
(right.values if isinstance(right, Sequence) else [right]), left
|
(right.values if isinstance(right, Sequence) else [right]), left
|
||||||
)
|
)
|
||||||
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
|
for (x, y) in pairs
|
||||||
]
|
]
|
||||||
return left
|
return left
|
||||||
@ -767,9 +779,40 @@ class Euclid(Item):
|
|||||||
|
|
||||||
pulses: int
|
pulses: int
|
||||||
length: int
|
length: int
|
||||||
onset: list
|
onset: ListSequence
|
||||||
offset: list = field(default=None)
|
offset: ListSequence = field(default=None)
|
||||||
rotate: int = 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)
|
@dataclass(kw_only=True)
|
||||||
|
|||||||
@ -53,3 +53,23 @@ def string_rewrite(axiom: str, rules: dict):
|
|||||||
|
|
||||||
pattern = re.compile("|".join(rules.keys()))
|
pattern = re.compile("|".join(rules.keys()))
|
||||||
return pattern.sub(lambda m: next(_apply_rules(m)), axiom)
|
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):
|
def euclid(self, items):
|
||||||
"""Parse euclid notation"""
|
"""Parse euclid notation"""
|
||||||
params = items[1][1:-1].split(",")
|
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]
|
text = items[0].text + items[1]
|
||||||
if len(params) > 2:
|
if len(params) > 2:
|
||||||
init["rotate"] = params[2]
|
init["rotate"] = int(params[2])
|
||||||
if len(items) > 2:
|
if len(items) > 2:
|
||||||
init["offset"] = items[2]
|
init["offset"] = items[2]
|
||||||
text = text + items[2].text
|
text = text + items[2].text
|
||||||
|
|||||||
Reference in New Issue
Block a user