-
Notifications
You must be signed in to change notification settings - Fork 32
/
TreeCalc.py
409 lines (329 loc) · 13.2 KB
/
TreeCalc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
from lark import Transformer, v_args
from typing import Union
from .Utilities import zip_cycle, map_unary_function, map_binary_function
from . import FuncLibrary
from lark.lexer import Token
from typing import Any
from itertools import cycle, takewhile, count
from time import time
import datetime
import random
from rich import print
from rich.panel import Panel
@v_args(inline=True)
class CalculateTree(Transformer):
def __init__(self, clock, iterators, variables):
super().__init__()
self.clock = clock
self.iterators = iterators
self.variables = variables
self.memory = {}
def number(self, number):
try:
return [int(number)]
except ValueError:
return [float(number)]
def return_pattern(self, *args):
return list(args)
# ---------------------------------------------------------------------- #
# Silence: handling silence
# ---------------------------------------------------------------------- #
def silence(self, *args):
"""
Pure silence. The absence of an event. Silence is represented using
a dot or multiple dots for multiple silences.
"""
return [None] * len(args)
# ---------------------------------------------------------------------- #
# Variables: methods concerning bi-valent variables
# ---------------------------------------------------------------------- #
def get_variable(self, letter):
letter = str(letter)
return [getattr(self.variables, letter)]
def reset_variable(self, letter):
letter = str(letter)
self.variables.reset(letter)
return [getattr(self.variables, letter)]
def set_variable(self, letter, number):
letter, number = str(letter), int(number)
setattr(self.variables, letter, number)
return [getattr(self.variables, letter)]
# ---------------------------------------------------------------------- #
# Iterators: methods concerning iterators
# ---------------------------------------------------------------------- #
def get_iterator(self, letter):
letter = str(letter)
return [getattr(self.iterators, letter)]
def reset_iterator(self, letter):
letter = str(letter)
self.iterators.reset(letter)
return [getattr(self.iterators, letter)]
def set_iterator(self, letter, number):
letter, number = str(letter), int(number)
setattr(self.iterators, letter, number)
return [getattr(self.iterators, letter)]
def set_iterator_step(self, letter, number, step):
letter, number, step = str(letter), int(number), int(step)
setattr(self.iterators, letter, [number, step])
return [getattr(self.iterators, letter)]
# ---------------------------------------------------------------------- #
# Notes: methods used by the note-specific parser
# ---------------------------------------------------------------------- #
def specify_address(self, name0, name1):
"""Convert underscore into slash for name based addresses"""
return "".join([name0, "/", name1])
def random_note_in_range(self, number0, number1):
"""Generates a random MIDI Note in the range number0 - number1.
Args:
number0 (int): low boundary
number1 (int): high boundary
Returns:
int: a random MIDI Note.
"""
return random.randint(int(number0), int(number1))
def make_note(self, note):
"""Return a valid MIDI Note (fourth octave)
from a valid anglo-saxon, french or canadian note name.
Args:
note (str): a string representing a valid note ("A" to "G", or "Do" to "Si").
Returns:
int: A MIDI Note (fourth octave)
"""
if note in ["C", "Do"]:
return 0 + 12 + 12 * 4
elif note in ["D", "Re", "Ré"]:
return 2 + 12 + 12 * 4
elif note in ["E", "Mi"]:
return 4 + 12 + 12 * 4
elif note in ["F", "Fa"]:
return 5 + 12 + 12 * 4
elif note in ["G", "Sol"]:
return 7 + 12 + 12 * 4
elif note in ["A", "La"]:
return 9 + 12 + 12 * 4
elif note in ["B", "Si", "Ti"]:
return 11 + 12 + 12 * 4
def note_flat(self, note):
"""Flatten a note"""
return note - 1
def note_sharp(self, note):
"""Sharpen a note"""
return note + 1
def note_set_octave(self, note, value):
"""Move a note to a given octave"""
return ((note - 12) % 12) + 12 + 12 * int(value)
def note_octave_up(self, note):
"""Move a note one octave up"""
return note + 12
def note_octave_down(self, note):
"""Move a note one octave down"""
return note - 12
def finish_note(self, note):
"""Finish the note construction"""
return [note]
def add_qualifier(self, note, *quali):
"""Adding a qualifier to a note taken from a qualifier list.
Adding a qualifier is the main method to generate scales and
chords in a monophonic fashion (each note layed out as a list).
Qualifiers are applied by taking the first note as a reference,
and building the collection of notes against it. Eg: c5:major
the reference note -> [60, added notes from coll -> 67, 72]
Args:
note (int): A MIDI Note
Returns:
list: A collection of notes built by applying a collection to
the note given as input.
"""
quali = list(quali)
quali = "".join([str(x) for x in quali])
try:
return map_binary_function(lambda x, y: x + y, note,
FuncLibrary.qualifiers[str(quali)])
except KeyError:
return note
def make_number(self, *token):
"""Turn a number from string to integer. Used by the parser,
transform a token matched as a string to a number before
applying other rules.
Returns:
int: an integer
"""
return int("".join(token))
def id(self, a):
"""Identity function. Returns first argument."""
return a
def make_list(self, *args):
"""Make a list from gathered arguments (alias used by parser)
Returns:
list: Gathered arguments in a list
"""
return sum(args, start=[])
def get_time(self):
"""Return current clock time (tick) as integer"""
return [int(self.clock.tick)]
def get_year(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().year)]
def get_month(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().month)]
def get_day(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().day)]
def get_hour(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().hour)]
def get_minute(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().minute)]
def get_second(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().second)]
def get_microsecond(self):
"""Return current clock time (tick) as integer"""
return [int(datetime.datetime.now().microsecond)]
def get_measure(self):
"""Return current measure (bar) as integer"""
return [int(self.clock.bar)]
def get_phase(self):
"""Return current phase (phase) as integer"""
return [int(self.clock.phase)]
def get_unix_time(self):
"""Return current unix time as integer"""
return [int(time())]
def get_random_number(self):
"""Return a random number (alias used by parser)
Returns:
float: a random floating point number
"""
return [random.random()]
def generate_ramp(self, left, right):
"""Generates a ramp of integers between x included and y
included (used by parser). Note that this is an extension
of Python's default range function: descending ranges are
possible.
Args:
left (int): First boundary
right (int): Second boundary
Returns:
list: a ramp of ascending or descending integers
"""
ramp_from = left[-1]
ramp_to = right[0]
between = range(min(ramp_from, ramp_to) + 1, max(ramp_from, ramp_to))
between_flipped = between if ramp_from <= ramp_to else reversed(between)
return left + list(between_flipped) + right
def generate_ramp_with_range(self, left, right, step):
"""Generates a ramp of integers between x included and y
included (used by parser). Variant using a step param.
Args:
left (int): First boundary
right (int): Second boundary
step (int): Range every x steps
Returns:
list: a ramp of ascending or descending integers with step
"""
start, stop, step, epsilon = (
min(left, right)[0],
max(left, right)[0],
step[0],
0.0000001
)
ramp = list(
takewhile(
lambda x: x < stop + epsilon, (start + i * abs(step) for i in count())
)
)
if left > right:
return list(reversed(ramp))
else:
return ramp
def extend(self, left, right):
"""Extend a token: repeat the token x times. Note that this function
will work for any possible type on its left and right side. A list
can extend a list, etc.. etc.. See examples for a better understanding
of the mechanism.
Examples:
[1,2,3]![1,2,3] -> [1,2,2,3,3,3]
1!3 -> [1,1,1]
[2,1]!4 -> [2,1,2,1,2,1,2,1]
etc..
Args:
left (Union[list, float, int]): A token that will be extended
right (Union[list, float, int]): A token used as an extension rule
Returns:
list: A list of integers after applying the expansion rule.
"""
return left * sum(int(x) for x in right)
def extend_repeat(self, left, right):
"""Variation of the preceding rule.
TODO: document this behavior."""
return sum(([x] * int(y) for (x, y) in zip_cycle(left, right)), start=[])
def choice(self, left, right):
"""Choose 50%-50% between the 'left' or 'right' token
Args:
left (any): First possible choice
right (any): Second possible choice
Returns:
any: A token chosen between 'left' or 'right'.
"""
return random.choice([left, right])
def random_in_range(self, left, right):
def my_random(low, high):
if isinstance(low, int) and isinstance(high, int):
return random.randint(low, high)
else:
return random.uniform(low, high)
return map_binary_function(my_random, left, right)
def negation(self, value):
return map_unary_function(lambda x: -x, value)
def addition(self, left, right):
return map_binary_function(lambda x, y: x + y, left, right)
def modulo(self, left, right):
return map_binary_function(lambda x, y: x % y, left, right)
def power(self, left, right):
return map_binary_function(lambda x, y: pow(x, y), left, right)
def substraction(self, left, right):
return map_binary_function(lambda x, y: x - y, left, right)
def multiplication(self, left, right):
return map_binary_function(lambda x, y: x * y, left, right)
def division(self, left, right):
return map_binary_function(lambda x, y: x / y, left, right)
def floor_division(self, left, right):
return map_binary_function(lambda x, y: x // y, left, right)
def name(self, name):
"""Generating a name"""
return str(name)
def assoc_sp_number(self, name, value):
def _simple_association(name, value):
return name + ":" + str(int(value))
return map_binary_function(_simple_association, name, value)
def finish_patname(self, patname):
"""Finish the patname construction"""
return [patname]
def function_call(self, func_name, *args):
modifiers_list = {
"expand": FuncLibrary.expand,
"disco": FuncLibrary.disco,
"palindrome": FuncLibrary.palindrome,
"reverse": FuncLibrary.reverse,
"braid": FuncLibrary.braid,
"shuffle": FuncLibrary.shuffle,
"drop2": FuncLibrary.drop2,
"drop3": FuncLibrary.drop3,
"drop2and4": FuncLibrary.drop2and4,
"sin": FuncLibrary.sinus,
"cos": FuncLibrary.cosinus,
"tan": FuncLibrary.tangent,
}
try:
return modifiers_list[func_name](*args)
except Exception as e:
# Fail safe
print(
Panel.fit(
f"[red]/!\\\\[/red] Unknown function: [bold yellow]{func_name}[/bold yellow]\n"
+ "".join(f"\n- {name}" for name in modifiers_list.keys())
)
)
return args[0]