run black on codebase

This commit is contained in:
2023-02-05 16:22:01 +01:00
parent 4d74186df8
commit 3d7fe73569
10 changed files with 362 additions and 251 deletions

60
main.py
View File

@ -3,38 +3,38 @@ from rich import print
if __name__ == "__main__": if __name__ == "__main__":
expressions = { expressions = {
'Pitches': "-2 -1 0 1 2", "Pitches": "-2 -1 0 1 2",
'Chords': "0 024 2 246", "Chords": "0 024 2 246",
'Note lengths': "w 0 h 1 q 2 e 3 s 4", "Note lengths": "w 0 h 1 q 2 e 3 s 4",
'Subdivision': "[1 2 [3 4]]", "Subdivision": "[1 2 [3 4]]",
'Decimal durations': "0.25 0 1 <0.333>2 3", "Decimal durations": "0.25 0 1 <0.333>2 3",
'Octaves': "^ 0 ^ 1 _ 2 _ 3", "Octaves": "^ 0 ^ 1 _ 2 _ 3",
'Escaped octave': "<2> 1 <1>1<-2>3", "Escaped octave": "<2> 1 <1>1<-2>3",
'Roman chords': "i ii iii+4 iv+5 v+8 vi+10 vii+20", "Roman chords": "i ii iii+4 iv+5 v+8 vi+10 vii+20",
'Named chords': "i^7 i^min i^dim i^maj7", "Named chords": "i^7 i^min i^dim i^maj7",
'Modal interchange (a-g)': "iiia ig ivf^7", "Modal interchange (a-g)": "iiia ig ivf^7",
'Escape/eval': "{10 11} {1.2 2.43} {3+1*2}", "Escape/eval": "{10 11} {1.2 2.43} {3+1*2}",
'Randoms': "% ? % ? % ?", "Randoms": "% ? % ? % ?",
'Random between': "(-3,6)", "Random between": "(-3,6)",
'Random selections': "[q 1 2, q 3 e 4 6]", "Random selections": "[q 1 2, q 3 e 4 6]",
'Repeat': "[: 1 (2,6) 3 :4]", "Repeat": "[: 1 (2,6) 3 :4]",
'Repeat cycles': "[: <q e> (1,4) <(2 3) (3 (1,7))> :]", "Repeat cycles": "[: <q e> (1,4) <(2 3) (3 (1,7))> :]",
'Lists': "h 1 q(0 1 2 3) 2", "Lists": "h 1 q(0 1 2 3) 2",
'List cycles': "(: <q e> (1,4) <(2 3) (3 (1,7))> :)", "List cycles": "(: <q e> (1,4) <(2 3) (3 (1,7))> :)",
'Loop cycles (for zloop or z0-z9)': "<0 <1 <2 <3 <4 5>>>>>", "Loop cycles (for zloop or z0-z9)": "<0 <1 <2 <3 <4 5>>>>>",
'Basic operations': "(1 2 (3 4)+2)*2 ((1 2 3)+(0 9 13))-2 ((3 4 {10})*(2 9 3))%7", "Basic operations": "(1 2 (3 4)+2)*2 ((1 2 3)+(0 9 13))-2 ((3 4 {10})*(2 9 3))%7",
'Product operations': "(0 1 2 3)+(1 4 2 3) (0 1 2)-(0 2 1)+2", "Product operations": "(0 1 2 3)+(1 4 2 3) (0 1 2)-(0 2 1)+2",
'Euclid cycles': "(q1)<6,7>(q4 (e3 e4) q2) (q1)<6,7>(q4 q3 q2)", "Euclid cycles": "(q1)<6,7>(q4 (e3 e4) q2) (q1)<6,7>(q4 q3 q2)",
'Transformations': "(0 1 2)<r> (0 1 2)<i>(-2 1)", "Transformations": "(0 1 2)<r> (0 1 2)<i>(-2 1)",
'List assignation': "A=(0 (1,6) 3) B=(3 ? 2) B A B B A", "List assignation": "A=(0 (1,6) 3) B=(3 ? 2) B A B B A",
'Random repeat': "(: 1 (2,6) 3 :4)", "Random repeat": "(: 1 (2,6) 3 :4)",
'Conditionals': "1 {%<0.5?3} 3 4 (: 1 2 {%<0.2?3:2} :3)", "Conditionals": "1 {%<0.5?3} 3 4 (: 1 2 {%<0.2?3:2} :3)",
'Functions': "(0 1 2 3){x%3==0?x-2:x+2}", "Functions": "(0 1 2 3){x%3==0?x-2:x+2}",
'Polynomials': "(-10..10){(x**3)*(x+1)%12}", "Polynomials": "(-10..10){(x**3)*(x+1)%12}",
} }
for ex in expressions: for ex in expressions:
try: try:
print(f"Parsed: "+parse_expression(expressions[ex]).text) print(f"Parsed: " + parse_expression(expressions[ex]).text)
except Exception as e: except Exception as e:
print(f"[red]Failed on {ex}[/red]") print(f"[red]Failed on {ex}[/red]")
#print(f"[red]Failed on {ex}[/red]: {str(e)[0:40]}...") # print(f"[red]Failed on {ex}[/red]: {str(e)[0:40]}...")

View File

@ -1,12 +1,12 @@
from ziffers import * from ziffers import *
from rich import print from rich import print
EXIT_CONDITION = ('exit', 'quit', '') EXIT_CONDITION = ("exit", "quit", "")
if __name__ == "__main__": if __name__ == "__main__":
print(f"[purple]== ZIFFERS REPL ==[/purple]") print(f"[purple]== ZIFFERS REPL ==[/purple]")
while True: while True:
expr = input('> ') expr = input("> ")
if expr not in EXIT_CONDITION: if expr not in EXIT_CONDITION:
try: try:
result = parse_expression(expr) result = parse_expression(expr)

View File

@ -1,15 +1,16 @@
from ziffers import * from ziffers import *
import pytest import pytest
def test_can_parse(): def test_can_parse():
expressions = [ expressions = [
"[1 [2 3]]", "[1 [2 3]]",
"(1 (1,3) 1..3)", "(1 (1,3) 1..3)",
"_^ q _qe^3 qww_4 _123 <1 2>", "_^ q _qe^3 qww_4 _123 <1 2>",
"q _2 _ 3 ^ 343", "q _2 _ 3 ^ 343",
"2 qe2 e4", "2 qe2 e4",
"q 2 <3 343>", "q 2 <3 343>",
"q (2 <3 343 (3 4)>)", "q (2 <3 343 (3 4)>)",
] ]
results = [] results = []
for expression in expressions: for expression in expressions:
@ -21,10 +22,11 @@ def test_can_parse():
print(e) print(e)
results.append(False) results.append(False)
# Return true if all the results are true (succesfully parsed) # Return true if all the results are true (succesfully parsed)
print(results) print(results)
assert all(results) assert all(results)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pattern", "pattern",
[ [
@ -35,12 +37,13 @@ def test_can_parse():
def test_parsing_text(pattern: str): def test_parsing_text(pattern: str):
assert parse_expression(pattern).text == pattern assert parse_expression(pattern).text == pattern
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pattern,expected", "pattern,expected",
[ [
("1 2 3", [1,2,3]), ("1 2 3", [1, 2, 3]),
("q2 eq3", [2,3]), ("q2 eq3", [2, 3]),
], ],
) )
def test_pcs(pattern: str, expected: list): def test_pcs(pattern: str, expected: list):
assert parse_expression(pattern).pcs() == expected assert parse_expression(pattern).pcs() == expected

View File

@ -1,6 +1,7 @@
from ziffers import scale from ziffers import scale
import pytest import pytest
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name,expected", "name,expected",
[ [
@ -14,11 +15,37 @@ import pytest
def test_notenames(name: str, expected: int): def test_notenames(name: str, expected: int):
assert scale.note_to_midi(name) == expected assert scale.note_to_midi(name) == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pcs,expected", "pcs,expected",
[ [
(list(range(-9,10)), [45, 47, 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76]), (
list(range(-9, 10)),
[
45,
47,
48,
50,
52,
53,
55,
57,
59,
60,
62,
64,
65,
67,
69,
71,
72,
74,
76,
],
),
], ],
) )
def test_note_to_midi(pcs: str, expected: int): def test_note_to_midi(pcs: str, expected: int):
assert [scale.note_from_pc(root=60, pitch_class=val, intervals="Ionian") for val in pcs] == expected assert [
scale.note_from_pc(root=60, pitch_class=val, intervals="Ionian") for val in pcs
] == expected

View File

@ -2,81 +2,108 @@ from dataclasses import dataclass, field
import operator import operator
from typing import Any from typing import Any
@dataclass @dataclass
class Meta: class Meta:
''' Abstract class for all Ziffers items''' """Abstract class for all Ziffers items"""
def update(self, new_values): def update(self, new_values):
''' Update attributes from dict ''' """Update attributes from dict"""
for key, value in new_values.items(): for key, value in new_values.items():
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
@dataclass @dataclass
class Item(Meta): class Item(Meta):
''' Class for all Ziffers text based items ''' """Class for all Ziffers text based items"""
text: str text: str
@dataclass @dataclass
class Whitespace(Item): class Whitespace(Item):
''' Class for whitespace ''' """Class for whitespace"""
@dataclass @dataclass
class DurationChange(Item): class DurationChange(Item):
''' Class for changing duration ''' """Class for changing duration"""
dur: float dur: float
@dataclass @dataclass
class OctaveChange(Item): class OctaveChange(Item):
''' Class for changing octave ''' """Class for changing octave"""
oct: int oct: int
@dataclass @dataclass
class OctaveMod(Item): class OctaveMod(Item):
''' Class for modifying octave ''' """Class for modifying octave"""
oct: int oct: int
@dataclass @dataclass
class Event(Item): class Event(Item):
''' Abstract class for events with duration ''' """Abstract class for events with duration"""
dur: float = None dur: float = None
@dataclass @dataclass
class Pitch(Event): class Pitch(Event):
''' Class for pitch in time ''' """Class for pitch in time"""
pc: int = None pc: int = None
dur: float = None dur: float = None
oct: int = None oct: int = None
@dataclass @dataclass
class RandomPitch(Event): class RandomPitch(Event):
''' Class for random pitch ''' """Class for random pitch"""
pc: int = None pc: int = None
@dataclass @dataclass
class RandomPercent(Item): class RandomPercent(Item):
''' Class for random percent ''' """Class for random percent"""
percent: float = None percent: float = None
@dataclass @dataclass
class Chord(Event): class Chord(Event):
''' Class for chords ''' """Class for chords"""
pcs: list[Pitch] = None pcs: list[Pitch] = None
@dataclass @dataclass
class Function(Event): class Function(Event):
''' Class for functions ''' """Class for functions"""
run: str = None run: str = None
class dataclass_property(property): # pylint: disable=invalid-name
''' Hack for dataclass setters ''' class dataclass_property(property): # pylint: disable=invalid-name
"""Hack for dataclass setters"""
def __set__(self, __obj: Any, __value: Any) -> None: def __set__(self, __obj: Any, __value: Any) -> None:
if isinstance(__value, self.__class__): if isinstance(__value, self.__class__):
return None return None
return super().__set__(__obj, __value) return super().__set__(__obj, __value)
@dataclass @dataclass
class Sequence(Meta): class Sequence(Meta):
''' Class for sequences of items''' """Class for sequences of items"""
values: list[Item] values: list[Item]
text: str = field(init=False) text: str = field(init=False)
_text: str = field(default=None, init=False, repr=False) _text: str = field(default=None, init=False, repr=False)
@ -96,12 +123,12 @@ class Sequence(Meta):
self.text = self.collect_text() self.text = self.collect_text()
def update_values(self, new_values): def update_values(self, new_values):
''' Update value attributes from dict ''' """Update value attributes from dict"""
for key, value in new_values.items(): for key, value in new_values.items():
for obj in self.values: for obj in self.values:
if key!="text" and hasattr(obj, key): if key != "text" and hasattr(obj, key):
setattr(obj, key, value) setattr(obj, key, value)
def collect_text(self) -> str: def collect_text(self) -> str:
text = "".join([val.text for val in self.values]) text = "".join([val.text for val in self.values])
if self.wrap_start != None: if self.wrap_start != None:
@ -112,115 +139,145 @@ class Sequence(Meta):
def pcs(self) -> list[int]: def pcs(self) -> list[int]:
return [val.pc for val in self.values if type(val) is Pitch] return [val.pc for val in self.values if type(val) is Pitch]
def durations(self) -> list[float]: def durations(self) -> list[float]:
return [val.dur for val in self.values if type(val) is Pitch] return [val.dur for val in self.values if type(val) is Pitch]
def pairs(self) -> list[tuple]: def pairs(self) -> list[tuple]:
return [(val.pc,val.dur) for val in self.values if type(val) is Pitch] return [(val.pc, val.dur) for val in self.values if type(val) is Pitch]
@dataclass @dataclass
class ListSequence(Sequence): class ListSequence(Sequence):
''' Class for Ziffers list sequences ''' """Class for Ziffers list sequences"""
wrap_start: str = field(default="(", repr=False) wrap_start: str = field(default="(", repr=False)
wrap_end: str = field(default=")", repr=False) wrap_end: str = field(default=")", repr=False)
@dataclass @dataclass
class RepeatedListSequence(Sequence): class RepeatedListSequence(Sequence):
''' Class for Ziffers list sequences ''' """Class for Ziffers list sequences"""
repeats: Item = None repeats: Item = None
wrap_start: str = field(default="(:", repr=False) wrap_start: str = field(default="(:", repr=False)
wrap_end: str = field(default=":)", repr=False) wrap_end: str = field(default=":)", repr=False)
@dataclass @dataclass
class Subdivision(Item): class Subdivision(Item):
''' Class for subdivisions ''' """Class for subdivisions"""
values: list[Event] values: list[Event]
@dataclass @dataclass
class Cyclic(Sequence): class Cyclic(Sequence):
''' Class for cyclic sequences''' """Class for cyclic sequences"""
cycle: int = 0 cycle: int = 0
wrap_start: str = field(default="<", repr=False) wrap_start: str = field(default="<", repr=False)
wrap_end: str = field(default=">", repr=False) wrap_end: str = field(default=">", repr=False)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
# TODO: Do spaced need to be filtered out? # TODO: Do spaced need to be filtered out?
self.values = [val for val in self.values if type(val)!=Item] self.values = [val for val in self.values if type(val) != Item]
@dataclass @dataclass
class RandomInteger(Item): class RandomInteger(Item):
''' Class for random integer ''' """Class for random integer"""
min: int min: int
max: int max: int
@dataclass @dataclass
class Range(Item): class Range(Item):
''' Class for range ''' """Class for range"""
start: int start: int
end: int end: int
ops = { ops = {
'+' : operator.add, "+": operator.add,
'-' : operator.sub, "-": operator.sub,
'*' : operator.mul, "*": operator.mul,
'/' : operator.truediv, "/": operator.truediv,
'%' : operator.mod "%": operator.mod,
} }
@dataclass @dataclass
class Operator(Item): class Operator(Item):
''' Class for math operators ''' """Class for math operators"""
value: ... = field(init=False, repr=False)
value: ... = field(init=False, repr=False)
def __post_init__(self): def __post_init__(self):
self.value = ops[self.text] self.value = ops[self.text]
@dataclass @dataclass
class ListOperation(Sequence): class ListOperation(Sequence):
''' Class for list operations ''' """Class for list operations"""
def run(self): def run(self):
pass pass
@dataclass @dataclass
class Operation(Item): class Operation(Item):
''' Class for lisp-like operations: (+ 1 2 3) etc. ''' """Class for lisp-like operations: (+ 1 2 3) etc."""
values: list values: list
operator: operator operator: operator
@dataclass @dataclass
class Eval(Sequence): class Eval(Sequence):
''' Class for evaluation notation ''' """Class for evaluation notation"""
result: ... = None result: ... = None
wrap_start: str = field(default="{", repr=False) wrap_start: str = field(default="{", repr=False)
wrap_end: str = field(default="}", repr=False) wrap_end: str = field(default="}", repr=False)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
self.result = eval(self.text) self.result = eval(self.text)
@dataclass @dataclass
class Atom(Item): class Atom(Item):
''' Class for evaluable atoms''' """Class for evaluable atoms"""
value: ... value: ...
@dataclass @dataclass
class Integer(Item): class Integer(Item):
''' Class for integers ''' """Class for integers"""
value: int value: int
@dataclass @dataclass
class Euclid(Item): class Euclid(Item):
''' Class for euclidean cycles ''' """Class for euclidean cycles"""
pulses: int pulses: int
length: int length: int
onset: list onset: list
offset: list = None offset: list = None
rotate: int = None rotate: int = None
@dataclass @dataclass
class RepeatedSequence(Sequence): class RepeatedSequence(Sequence):
''' Class for repeats ''' """Class for repeats"""
repeats: Item = None repeats: Item = None
wrap_start: str = field(default="[:", repr=False) wrap_start: str = field(default="[:", repr=False)
wrap_end: str = field(default=":]", repr=False) wrap_end: str = field(default=":]", repr=False)

View File

@ -1,14 +1,19 @@
def flatten(arr) -> list: def flatten(arr) -> list:
''' Flattens array''' """Flattens array"""
return flatten(arr[0]) + (flatten(arr[1:]) if len(arr) > 1 else []) if type(arr) is list else [arr] return (
flatten(arr[0]) + (flatten(arr[1:]) if len(arr) > 1 else [])
if type(arr) is list
else [arr]
)
def sum_dict(arr) -> dict: def sum_dict(arr) -> dict:
''' Sums array of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4} ''' """Sums array of dicts: [{a:3,b:3},{b:1}] -> {a:3,b:4}"""
result = arr[0] result = arr[0]
for hash in arr[1:]: for hash in arr[1:]:
for key in hash.keys(): for key in hash.keys():
if key in result: if key in result:
result[key] = result[key] + hash[key] result[key] = result[key] + hash[key]
else: else:
result[key] = hash[key] result[key] = hash[key]
return result return result

View File

@ -1,37 +1,37 @@
default_durs = { default_durs = {
'm': 8.0, # 15360/1920 "m": 8.0, # 15360/1920
'k': 10240/1920, # ~5.333 "k": 10240 / 1920, # ~5.333
'l': 4.0, # 7680/1920 "l": 4.0, # 7680/1920
'd.': 3.0, # "d.": 3.0, #
'p': 5120/1920, # ~2.666 "p": 5120 / 1920, # ~2.666
'd': 2.0, # 3840/1920 "d": 2.0, # 3840/1920
'w.': 1.5, # 2280/1920 "w.": 1.5, # 2280/1920
'c': 2560/1920, # ~1.333 "c": 2560 / 1920, # ~1.333
'w': 1.0, # 1920/1920 "w": 1.0, # 1920/1920
'h..': 0.875, # 1680/1920 "h..": 0.875, # 1680/1920
'h.': 0.75, # 1440/1920 "h.": 0.75, # 1440/1920
'y': 1280/1920, # ~0.666 "y": 1280 / 1920, # ~0.666
'h': 0.5, # 960/1920 - 1/2 "h": 0.5, # 960/1920 - 1/2
'q..': 840/1920, # ~0.4375 "q..": 840 / 1920, # ~0.4375
'q.': 0.375, # 720/1920 "q.": 0.375, # 720/1920
'n': 640/1920, # ~0.333 "n": 640 / 1920, # ~0.333
'q': 0.25, # 480/1920 - 1/4 "q": 0.25, # 480/1920 - 1/4
'e..': 420/1920, # = 0.218 "e..": 420 / 1920, # = 0.218
'e.': 0.1875, # 360/1920 "e.": 0.1875, # 360/1920
'a': 320/1920, # 0.167 - 1/8 "a": 320 / 1920, # 0.167 - 1/8
'e': 0.125, # 240/1920 "e": 0.125, # 240/1920
's..': 210/1920, # ~0.10937 "s..": 210 / 1920, # ~0.10937
's.': 180/1920, # ~0.0937 "s.": 180 / 1920, # ~0.0937
'f': 160/1920, # ~0.083 - 1/16 "f": 160 / 1920, # ~0.083 - 1/16
's': 0.0625, # 120/1920 "s": 0.0625, # 120/1920
't..': 105/1920, # ~0.0546 "t..": 105 / 1920, # ~0.0546
't.': 90/1920, # ~0.0468 "t.": 90 / 1920, # ~0.0468
'x': 80/1920, # ~0.042 - 1/32 "x": 80 / 1920, # ~0.042 - 1/32
't': 60/1920, # ~0.031 "t": 60 / 1920, # ~0.031
'u.': 45/1920, # ~0.023 "u.": 45 / 1920, # ~0.023
'g': 40/1920, # ~0.021 - 1/64 "g": 40 / 1920, # ~0.021 - 1/64
'u': 30/1920, # ~0.016 "u": 30 / 1920, # ~0.016
'j': 15/1920, # ~0.0078 - 1/128 "j": 15 / 1920, # ~0.0078 - 1/128
'o': 8/1920, # ~0.00416 "o": 8 / 1920, # ~0.00416
'z': 0.0 # 0 "z": 0.0, # 0
} }

View File

@ -4,211 +4,231 @@ from .common import flatten, sum_dict
from .defaults import default_durs from .defaults import default_durs
import operator import operator
class ZiffersTransformer(Transformer): class ZiffersTransformer(Transformer):
def start(self, items):
def start(self,items):
return Sequence(values=items[0]) return Sequence(values=items[0])
def sequence(self,items): def sequence(self, items):
return flatten(items) return flatten(items)
def random_integer(self,s): def random_integer(self, s):
val = s[0][1:-1].split(",") val = s[0][1:-1].split(",")
return RandomInteger(min=val[0],max=val[1],text=s[0].value) return RandomInteger(min=val[0], max=val[1], text=s[0].value)
def range(self,s): def range(self, s):
val = s[0].split("..") val = s[0].split("..")
return Range(start=val[0],end=val[1],text=s[0]) return Range(start=val[0], end=val[1], text=s[0])
def cycle(self, items): def cycle(self, items):
values = items[0] values = items[0]
return Cyclic(values=values) return Cyclic(values=values)
def pc(self, s): def pc(self, s):
if(len(s)>1): if len(s) > 1:
# Collect&sum prefixes from any order: _qee^s4 etc. # Collect&sum prefixes from any order: _qee^s4 etc.
result = sum_dict(s) result = sum_dict(s)
return Pitch(**result) return Pitch(**result)
else: else:
val = s[0] val = s[0]
return Pitch(**val) return Pitch(**val)
def pitch(self,s): def pitch(self, s):
return {"pc":int(s[0].value),"text":s[0].value} return {"pc": int(s[0].value), "text": s[0].value}
def prefix(self,s): def prefix(self, s):
return s[0] return s[0]
def oct_change(self,s): def oct_change(self, s):
octave = s[0] octave = s[0]
return [OctaveChange(oct=octave["oct"],text=octave["text"]),s[1]] return [OctaveChange(oct=octave["oct"], text=octave["text"]), s[1]]
def oct_mod(self,s): def oct_mod(self, s):
octave = s[0] octave = s[0]
return [OctaveMod(oct=octave["oct"],text=octave["text"]),s[1]] return [OctaveMod(oct=octave["oct"], text=octave["text"]), s[1]]
def escaped_octave(self,s): def escaped_octave(self, s):
value = s[0][1:-1] value = s[0][1:-1]
return {"oct": int(value), "text":s[0].value} return {"oct": int(value), "text": s[0].value}
def octave(self,s): def octave(self, s):
value = sum([1 if char=='^' else -1 for char in s[0].value]) value = sum([1 if char == "^" else -1 for char in s[0].value])
return {"oct": value, "text":s[0].value} return {"oct": value, "text": s[0].value}
def chord(self,s): def chord(self, s):
return Chord(pcs=s,text="".join([val.text for val in s])) return Chord(pcs=s, text="".join([val.text for val in s]))
def dur_change(self,s): def dur_change(self, s):
durs = s[0] durs = s[0]
return DurationChange(dur=durs[1], text=durs[0]) return DurationChange(dur=durs[1], text=durs[0])
def char_change(self,s): def char_change(self, s):
chars = "" chars = ""
durs = 0.0 durs = 0.0
for (dchar,dots) in s: for (dchar, dots) in s:
val = default_durs[dchar] val = default_durs[dchar]
if(dots>0): if dots > 0:
val = val * (2.0-(1.0/(2*dots))) val = val * (2.0 - (1.0 / (2 * dots)))
chars = chars + (dchar+"."*dots) chars = chars + (dchar + "." * dots)
durs = durs + val durs = durs + val
return [chars,durs] return [chars, durs]
def dchar_not_prefix(self,s): def dchar_not_prefix(self, s):
dur = s[0].split(".",1) dur = s[0].split(".", 1)
dots = 0 dots = 0
if len(dur)>1: if len(dur) > 1:
dots = len(dur[1])+1 dots = len(dur[1]) + 1
return [dur[0],dots] return [dur[0], dots]
def escaped_decimal(self,s): def escaped_decimal(self, s):
val = s[0] val = s[0]
val["text"] = "<"+val["text"]+">" val["text"] = "<" + val["text"] + ">"
return val return val
def random_pitch(self,s): def random_pitch(self, s):
return RandomPitch(text="?") return RandomPitch(text="?")
def random_percent(self,s): def random_percent(self, s):
return RandomPercent(text="%") return RandomPercent(text="%")
def duration_chars(self,s): def duration_chars(self, s):
durations = [val[1] for val in s] durations = [val[1] for val in s]
characters = "".join([val[0] for val in s]) characters = "".join([val[0] for val in s])
return {"dur": sum(durations), "text":characters} return {"dur": sum(durations), "text": characters}
def dotted_dur(self,s): def dotted_dur(self, s):
key = s[0] key = s[0]
val = default_durs[key] val = default_durs[key]
dots = len(s)-1 dots = len(s) - 1
if(dots>0): if dots > 0:
val = val * (2.0-(1.0/(2*dots))) val = val * (2.0 - (1.0 / (2 * dots)))
return [key+"."*dots,val] return [key + "." * dots, val]
def decimal(self,s): def decimal(self, s):
val = s[0] val = s[0]
return {"dur": float(val),"text": val.value} return {"dur": float(val), "text": val.value}
def dot(self,s): def dot(self, s):
return "." return "."
def dchar(self,s): def dchar(self, s):
chardur = s[0].value chardur = s[0].value
return chardur return chardur
def WS(self,s): def WS(self, s):
return Whitespace(text=s[0]) return Whitespace(text=s[0])
def subdivision(self,items): def subdivision(self, items):
values = flatten(items[0]) values = flatten(items[0])
return Subdivision(values=values,text="["+"".join([val.text for val in values])+"]") return Subdivision(
values=values, text="[" + "".join([val.text for val in values]) + "]"
)
def subitems(self,s): def subitems(self, s):
return s return s
# Eval rules # Eval rules
def eval(self,s): def eval(self, s):
val = s[0] val = s[0]
return Eval(values=val) return Eval(values=val)
def operation(self,s): def operation(self, s):
return s return s
def atom(self,s): def atom(self, s):
val = s[0].value val = s[0].value
return Atom(value=val,text=val) return Atom(value=val, text=val)
# List rules # List rules
def list(self,items): def list(self, items):
if len(items)>1: if len(items) > 1:
prefixes = sum_dict(items[0:-1]) prefixes = sum_dict(items[0:-1])
values = items[-1] values = items[-1]
seq = ListSequence(values=values,wrap_start=prefixes["text"]+"(") seq = ListSequence(values=values, wrap_start=prefixes["text"] + "(")
seq.update_values(prefixes) seq.update_values(prefixes)
return seq return seq
else: else:
seq = ListSequence(values=items[0]) seq = ListSequence(values=items[0])
return seq return seq
def repeated_list(self,items): def repeated_list(self, items):
if len(items)>2: if len(items) > 2:
prefixes = sum_dict(items[0:-2]) # If there are prefixes prefixes = sum_dict(items[0:-2]) # If there are prefixes
if items[-1]!=None: if items[-1] != None:
seq = RepeatedListSequence(values=items[-2],repeats=items[-1],wrap_end=":"+items[-1].text+")") seq = RepeatedListSequence(
values=items[-2],
repeats=items[-1],
wrap_end=":" + items[-1].text + ")",
)
else: else:
seq = RepeatedListSequence(values=items[-2],repeats=Integer(text='1', value=1)) seq = RepeatedListSequence(
values=items[-2], repeats=Integer(text="1", value=1)
)
seq.update_values(prefixes) seq.update_values(prefixes)
return seq return seq
else: else:
if items[-1]!=None: if items[-1] != None:
seq = RepeatedListSequence(values=items[-2],repeats=items[-1],wrap_end=":"+items[-1].text+")") seq = RepeatedListSequence(
values=items[-2],
repeats=items[-1],
wrap_end=":" + items[-1].text + ")",
)
else: else:
seq = RepeatedListSequence(values=items[-2],repeats=Integer(text='1', value=1)) seq = RepeatedListSequence(
values=items[-2], repeats=Integer(text="1", value=1)
)
return seq return seq
def SIGNED_NUMBER(self, s): def SIGNED_NUMBER(self, s):
val = s.value val = s.value
return Integer(text=val,value=int(val)) return Integer(text=val, value=int(val))
def number(self,s): def number(self, s):
return s return s
def cyclic_number(self,s): def cyclic_number(self, s):
return Cyclic(values=s) return Cyclic(values=s)
def lisp_operation(self,s): def lisp_operation(self, s):
op = s[0] op = s[0]
values = s[1:] values = s[1:]
return Operation(operator=op,values=values,text="(+"+"".join([v.text for v in values])+")") return Operation(
operator=op,
values=values,
text="(+" + "".join([v.text for v in values]) + ")",
)
def operator(self,s): def operator(self, s):
val = s[0].value val = s[0].value
return Operator(text=val) return Operator(text=val)
def list_items(self,s): def list_items(self, s):
return Sequence(values=s) return Sequence(values=s)
def list_op(self,s): def list_op(self, s):
return ListOperation(values=s) return ListOperation(values=s)
def euclid(self,s): def euclid(self, s):
params = s[1][1:-1].split(",") params = s[1][1:-1].split(",")
init = {"onset":s[0],"pulses":params[0],"length":params[1]} init = {"onset": s[0], "pulses": params[0], "length": params[1]}
text = s[0].text+s[1] text = s[0].text + s[1]
if len(params)>2: if len(params) > 2:
init["rotate"] = params[2] init["rotate"] = params[2]
if len(s)>2: if len(s) > 2:
init["offset"] = s[2] init["offset"] = s[2]
text = text+s[2].text text = text + s[2].text
init["text"] = text init["text"] = text
return Euclid(**init) return Euclid(**init)
def euclid_operator(self,s): def euclid_operator(self, s):
return s.value return s.value
def repeat(self,s): def repeat(self, s):
if s[-1]!=None: if s[-1] != None:
return RepeatedSequence(values=s[0],repeats=s[-1],wrap_end=":"+s[-1].text+"]") return RepeatedSequence(
values=s[0], repeats=s[-1], wrap_end=":" + s[-1].text + "]"
)
else: else:
return RepeatedSequence(values=s[0],repeats=Integer(value=1,text='1')) return RepeatedSequence(values=s[0], repeats=Integer(value=1, text="1"))

View File

@ -6,7 +6,14 @@ from lark import Lark
grammar_path = Path(__file__).parent grammar_path = Path(__file__).parent
grammar = grammar_path / "ziffers.lark" grammar = grammar_path / "ziffers.lark"
ziffers_parser = Lark.open(grammar, rel_to=__file__, start='root', parser='lalr', transformer=ZiffersTransformer()) ziffers_parser = Lark.open(
grammar,
rel_to=__file__,
start="root",
parser="lalr",
transformer=ZiffersTransformer(),
)
def parse_expression(expr): def parse_expression(expr):
return ziffers_parser.parse(expr) return ziffers_parser.parse(expr)

View File

@ -1495,15 +1495,7 @@ SCALES = {
"Chromatic": 111111111111, "Chromatic": 111111111111,
} }
note_to_interval = { note_to_interval = {"C": 0, "D": 2, "E": 4, "F": 5, "G": 7, "A": 9, "B": 11}
"C": 0,
"D": 2,
"E": 4,
"F": 5,
"G": 7,
"A": 9,
"B": 11
}
modifiers = { modifiers = {
"#": 1, "#": 1,
@ -1511,10 +1503,11 @@ modifiers = {
"s": 1, "s": 1,
} }
def note_to_midi(name: str) -> int: def note_to_midi(name: str) -> int:
''' Parse note name to midi ''' """Parse note name to midi"""
items = re.match(r"^([a-gA-G])([#bs])?([1-9])?$",name) items = re.match(r"^([a-gA-G])([#bs])?([1-9])?$", name)
if items==None: if items == None:
return 60 return 60
values = items.groups() values = items.groups()
octave = int(values[2]) if values[2] else 4 octave = int(values[2]) if values[2] else 4
@ -1522,31 +1515,30 @@ def note_to_midi(name: str) -> int:
interval = note_to_interval[values[0].capitalize()] interval = note_to_interval[values[0].capitalize()]
return 12 + octave * 12 + interval + modifier return 12 + octave * 12 + interval + modifier
def get_scale(name: str) -> list: def get_scale(name: str) -> list:
"""Get a scale from the global scale list""" """Get a scale from the global scale list"""
scale = SCALES.get( scale = SCALES.get(name.lower().capitalize(), SCALES["Chromatic"])
name.lower().capitalize(),
SCALES["Chromatic"])
return list(map(int, str(scale))) return list(map(int, str(scale)))
def note_from_pc( def note_from_pc(
root: int | str, root: int | str,
pitch_class: int, pitch_class: int,
intervals: str | list[int | float], intervals: str | list[int | float],
cents: bool = False, cents: bool = False,
octave: int = 0, octave: int = 0,
modifier: int = 0) -> int: modifier: int = 0,
) -> int:
"""Resolve a pitch class into a note from a scale""" """Resolve a pitch class into a note from a scale"""
# Initialization # Initialization
root = note_to_midi(root) if isinstance(root, str) else root root = note_to_midi(root) if isinstance(root, str) else root
intervals = get_scale(intervals) if isinstance( intervals = get_scale(intervals) if isinstance(intervals, str) else intervals
intervals, str) else intervals
intervals = list(map(lambda x: x / 100), intervals) if cents else intervals intervals = list(map(lambda x: x / 100), intervals) if cents else intervals
# Computing the result # Computing the result
interval_sum = sum(intervals[0:pitch_class % len(intervals)]) interval_sum = sum(intervals[0 : pitch_class % len(intervals)])
note = (root + interval_sum if pitch_class >= 0 else root - interval_sum) note = root + interval_sum if pitch_class >= 0 else root - interval_sum
return note + octave*sum(intervals) + modifier return note + octave * sum(intervals) + modifier