Syntax for monzos supported in scala scales: [-1 1> etc.
Support for escaped pitches: {q12 e23 26}
117 lines
3.0 KiB
Python
117 lines
3.0 KiB
Python
""" Common methods used in parsing """
|
|
import re
|
|
from copy import deepcopy
|
|
|
|
|
|
def flatten(arr: list) -> list:
|
|
"""Flattens array"""
|
|
return (
|
|
flatten(arr[0]) + (flatten(arr[1:]) if len(arr) > 1 else [])
|
|
if isinstance(arr, list)
|
|
else [arr]
|
|
)
|
|
|
|
|
|
def rotate(arr, k):
|
|
"""Rotates array"""
|
|
# Calculate the effective rotation amount (mod the array length)
|
|
k = k % len(arr)
|
|
# Rotate the array to the right
|
|
if k > 0:
|
|
arr = arr[-k:] + arr[:-k]
|
|
# Rotate the array to the left
|
|
elif k < 0:
|
|
arr = arr[-k:] + arr[:-k]
|
|
return arr
|
|
|
|
|
|
def repeat_text(pos, neg, times):
|
|
"""Helper to repeat text"""
|
|
if times > 0:
|
|
return pos * times
|
|
if times < 0:
|
|
return neg * abs(times)
|
|
return ""
|
|
|
|
|
|
def sum_dict(arr: list[dict]) -> dict:
|
|
"""Sums a list of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}"""
|
|
result = arr[0]
|
|
for element in arr[1:]:
|
|
for key in element.keys():
|
|
if key in result:
|
|
result[key] = result[key] + element[key]
|
|
else:
|
|
result[key] = element[key]
|
|
return result
|
|
|
|
|
|
def string_rewrite(axiom: str, rules: dict):
|
|
"""String rewrite / Lindemeyer system for rule based text manipulation
|
|
|
|
Args:
|
|
axiom (str): Input string
|
|
rules (dict): String manipulation rules in dict:
|
|
|
|
Example:
|
|
rules = {
|
|
"1": "2",
|
|
"[2-9]": "45",
|
|
"4": lambda: str(randint(1, 7)),
|
|
"([1-9])(5)": lambda a, b: str(int(a)*int(b))
|
|
}
|
|
|
|
for i range(10):
|
|
print(string_rewrite("1", rules))
|
|
"""
|
|
|
|
def _apply_rules(match):
|
|
for key, value in rules.items():
|
|
if re.match(key, match.group(0)):
|
|
if callable(value):
|
|
yield value(
|
|
*match.groups()
|
|
) if value.__code__.co_argcount > 0 else value()
|
|
yield value
|
|
|
|
pattern = re.compile("|".join(rules.keys()))
|
|
return pattern.sub(lambda m: next(_apply_rules(m)), axiom)
|
|
|
|
|
|
def euclidian_rhythm(pulses: int, length: int, rot: int = 0):
|
|
"""Calculate Euclidean rhythms. Original algorithm by Thomas Morrill."""
|
|
|
|
def _starts_descent(arr, index):
|
|
length = len(arr)
|
|
next_index = (index + 1) % length
|
|
return arr[index] > arr[next_index]
|
|
|
|
def rotation(arr, idx):
|
|
return arr[-idx:] + arr[:-idx]
|
|
|
|
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, rot)
|
|
|
|
|
|
def cyclic_zip(first: list, second: list) -> list:
|
|
"""Cyclic zip method
|
|
|
|
Args:
|
|
first (list): First list is cycled
|
|
second (list): Second list
|
|
|
|
Returns:
|
|
list: Cyclicly zipped list
|
|
"""
|
|
result = []
|
|
s_length = len(second)
|
|
f_length = len(first)
|
|
for i in range(s_length):
|
|
result.append([first[i % f_length], second[i]])
|
|
return [deepcopy(item) for sublist in result for item in sublist]
|