-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlookup_table_gen.py
154 lines (124 loc) · 6.05 KB
/
lookup_table_gen.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
################################################################################
#
# File Description:
# This python script generates Rust language code files containing the
# sine, attack, and decay lookup-tables used by the system.
#
# The attack LUT is a truncated rising rc curve and the decay LUT is a decaying
# rc curve. The decay LUT is used for both the ADSR decay and release phases.
#
# To write the rust language source code file:
# $ python lookup_table_gen.py write [--attack_target=xxx] [--num_time_constants=xxx]
#
# To see a graphical plot of the attack and decay curves
# $ python lookup_table_gen.py plot [--attack_target=xxx] [--num_time_constants=xxx]
#
# Default values are provided for the attack target and number of time constants,
# so these are not mandatory to enter. However, if the user wants to experiment
# with different ADSR curve shapes, a quick way to do it is to experimentally
# plot some different shapes, and then when you have shapes that seem interesting
# write the LUTs with the same values.
#
################################################################################
import argparse
import numpy as np
from matplotlib import pyplot as plt
# The output file path for the lookup table source files to generate.
OUTPUT_FILE_PATH = "../src/lookup_tables.rs"
# the lookup table size, must be a power of 2
ADSR_LUT_SIZE = 2**10
SINE_LUT_SIZE = 2**10
# arguments decide whether to plot the curves or write the c files, and allow the
# user to modify the shape of the curves
parser = argparse.ArgumentParser()
parser.add_argument(
'action',
help='the action to do, either graphically plot the attack and decay LUTs, or generate and write the c files',
choices=['plot', 'write']
)
parser.add_argument(
'--attack_target',
help='the target value for the attack curve, useful range: [1..5]',
nargs='?',
default=3.0,
type=float
)
parser.add_argument(
'--num_time_constants',
help='the number of time constants to extend the attack and decay curves, useful range: [3..10]',
nargs='?',
default=4.0,
type=float
)
args = parser.parse_args()
# the size of the optional plotted figure
plt.rcParams['figure.figsize'] = (8.0, 6.0)
# the target for the attack curve, flattens out the attack curve like an analog ADSR
# which sets a target for the attack curve higher than the trip point
# Example: the attack target is often 15 volts, but the comparator trips at 10 volts
# bigger number means flatter attack curve, adjust to taste
ATTACK_TARGET = args.attack_target
# how far out to take the curves in the LUT, too short and the curves will not
# have time to decay gracefully, too long and all the action will be bunched up
# in the front of the LUT
NUM_TIME_CONSTANTS = args.num_time_constants
# the linear input to transform into the attack and decay curves
X = np.linspace(0, NUM_TIME_CONSTANTS, ADSR_LUT_SIZE)
# generate the attack curve, range [0, 1], increasing truncated rc curve
adsr_attack_lut = 1 - np.exp(-X / ATTACK_TARGET)
adsr_attack_lut = adsr_attack_lut / adsr_attack_lut.max() # range [0, 1]
adsr_attack_lut = np.float32(adsr_attack_lut) # avoid excessive precision
# generate the decay/release curve, range [0, 1], decreasing rc curve
adsr_decay_lut = np.exp(-X)
adsr_decay_lut = adsr_decay_lut - adsr_decay_lut.min() # end at zero
adsr_decay_lut = adsr_decay_lut / adsr_decay_lut.max() # range [0, 1]
adsr_decay_lut = np.float32(adsr_decay_lut)
# sine lut is pretty straightforward
sine_lut = np.float32(np.sin(np.linspace(0, 2*np.pi, SINE_LUT_SIZE)))
if (args.action == 'plot'): # graphically plot the curves
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(np.linspace(0, ADSR_LUT_SIZE-1, ADSR_LUT_SIZE),
adsr_attack_lut, label="attack")
ax.plot(np.linspace(0, ADSR_LUT_SIZE-1, ADSR_LUT_SIZE),
adsr_decay_lut, label="decay")
plt.suptitle(f"Attack and decay lookup tables with {ADSR_LUT_SIZE} points")
plt.title(
f'attack target: {ATTACK_TARGET}\nnum time constants: {NUM_TIME_CONSTANTS}')
plt.xlabel("LUT index")
plt.ylabel("value")
plt.legend()
plt.show()
elif (args.action == 'write'):
SINE_LUT_SIZE_CONSTANT = f'pub const SINE_LUT_SIZE: usize = {SINE_LUT_SIZE};\n\n'
ADSR_LUT_SIZE_CONSTANT = f'pub const ADSR_CURVE_LUT_SIZE: usize = {ADSR_LUT_SIZE};\n\n'
# allow approx constant so clippy doesn't complain about the ln(2) that shows up
SINE_TABLE_TYPE = f'#[allow(clippy::approx_constant)]\npub const SINE_TABLE: [f32; SINE_LUT_SIZE]'
ATTACK_TABLE_TYPE = f'#[allow(clippy::approx_constant)]\npub const ADSR_ATTACK_TABLE: [f32; ADSR_CURVE_LUT_SIZE]'
DECAY_TABLE_TYPE = f'#[allow(clippy::approx_constant)]\npub const ADSR_DECAY_TABLE: [f32; ADSR_CURVE_LUT_SIZE]'
# write the rust source file
print(f"generating {OUTPUT_FILE_PATH} file...")
top_of_file_comment = f'// FILE AUTOMATICALLY GENERATED BY: /non_rust_utils/lookup_table_gen.py\n\n'
end_of_lut = '];\n'
def strip_extra_zeros_in_small_exp(f32: np.float32) -> str:
'''
Rust complains about f32 number that end in a small exponent with an extra zero, like 1.2e-05
This function gets converts the above example to 1.2e-5 so we don't need to adjust clippy settings
'''
return str(f32).replace("e-0", "e-")
with open(OUTPUT_FILE_PATH, 'w') as writer:
writer.write(top_of_file_comment)
writer.write(SINE_LUT_SIZE_CONSTANT)
writer.write(SINE_TABLE_TYPE + ' = [\n')
writer.writelines(" %s,\n" % strip_extra_zeros_in_small_exp(y)
for y in sine_lut)
writer.write(end_of_lut + '\n')
writer.write(ADSR_LUT_SIZE_CONSTANT)
writer.write(ATTACK_TABLE_TYPE + ' = [\n')
writer.writelines(" %s,\n" % strip_extra_zeros_in_small_exp(y)
for y in adsr_attack_lut)
writer.write(end_of_lut + '\n')
writer.write(DECAY_TABLE_TYPE + ' = [\n')
writer.writelines(" %s,\n" % strip_extra_zeros_in_small_exp(y)
for y in adsr_decay_lut)
writer.write(end_of_lut)