forked from Allen-Synthesis/EuroPi
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathitty_bitty.py
205 lines (162 loc) · 6.05 KB
/
itty_bitty.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
"""
Two 8-step trigger, gate & CV sequencers based on the binary representation of an 8-bit number
"""
from europi import *
from europi_script import EuroPiScript
import configuration
import time
class BittySequence:
"""
A container class for one sequencer
"""
def __init__(self, button_in, trigger_out, gate_out, cv_out, use_gray_encoding=False):
"""
Create a new sequencer
@param button_in The button the user can press to manually advance the sequence
@param trigger_out The CV output for the trigger signal
@param gate_out The CV output for the gate signal
@param cv_out The CV output for the CV signal
@param use_gray_encoding If true, we use gray encoding instead of traditional binary
"""
button_in.handler(self.advance)
button_in.handler_falling(self.trigger_off)
self.trigger_out = trigger_out
self.gate_out = gate_out
self.cv_out = cv_out
self.use_gray_encoding = use_gray_encoding
# the integer representation of our sequence
# if we're not using gray encoding this should be the same as our binary sequence
self.sequence_n = 0
# the raw binary pattern that represents our sequence
self.binary_sequence = 0x00
# the current step
self.step = 0
# is the current output state in need of refreshing?
self.output_dirty = False
# turn everything off initially
self.trigger_out.off()
self.gate_out.off()
self.cv_out.off()
def advance(self):
self.step = (self.step + 1) & 0x07 # restrict this to 0-7
self.output_dirty = True
def trigger_off(self):
self.trigger_out.off()
def apply_output(self):
now = time.ticks_ms()
if self.current_bit:
self.trigger_out.on()
self.gate_out.on()
else:
self.trigger_out.off()
self.gate_out.off()
self.cv_out.voltage(europi_config.MAX_OUTPUT_VOLTAGE * self.cv_sequence / 255)
self.output_dirty = False
def change_sequence(self, n):
self.sequence_n = n
if self.use_gray_encoding:
# convert the number from traditional binary to its gray encoding equivalent
n = (n & 0xff) ^ ((n & 0xff) >> 1)
else:
n = n & 0xff
self.binary_sequence = n
@property
def shifted_sequence(self):
return ((self.binary_sequence << self.step) & 0xff) | ((self.binary_sequence & 0xff) >> (8 - self.step))
@property
def cv_sequence(self):
# reverse the bits of the shifted sequence
s = self.shifted_sequence
cv = 0x00
while s:
cv = cv << 1
cv = cv | (s & 0x01)
s = s >> 1
return cv & 0xff
@property
def current_bit(self):
return (self.shifted_sequence >> 7) & 0x01
class IttyBitty(EuroPiScript):
def __init__(self):
super().__init__()
self.sequencers = [
BittySequence(b1, cv1, cv2, cv3, use_gray_encoding=self.config.USE_GRAY_ENCODING),
BittySequence(b2, cv4, cv5, cv6, use_gray_encoding=self.config.USE_GRAY_ENCODING),
]
@din.handler
def on_clock_rise():
for s in self.sequencers:
s.advance()
@din.handler_falling
def on_clock_fall():
for s in self.sequencers:
s.trigger_off()
@classmethod
def config_points(cls):
return [
# If true, use gray encoding instead of standard binary
# Gray encding flips a single bit at each step, meaning any two adjacent
# sequences differ by only 1 bit
configuration.boolean(
"USE_GRAY_ENCODING",
False
),
# Flags to enable AIN to control channel A and/or channel B
# when enabled, the knob acts as an attenuator instead of a selector
configuration.boolean(
"USE_AIN_A",
False
),
configuration.boolean(
"USE_AIN_B",
False
),
]
def main(self):
TEXT_TOP = CHAR_HEIGHT
BITS_LEFT = CHAR_WIDTH * 6
N_STEPS = 256
N_SAMPLES = 200
while True:
cv = ain.percent(samples=N_SAMPLES)
if self.config.USE_AIN_A:
atten = k1.percent(samples=N_SAMPLES)
n1 = round(cv * atten * N_STEPS)
if n1 == N_STEPS:
# prevent bounds problems since percent() returns [0, 1], not [0, 1)
n1 = N_STEPS - 1
else:
n1 = k1.read_position(steps=N_STEPS, samples=N_SAMPLES)
if self.config.USE_AIN_B:
atten = k2.percent(samples=N_SAMPLES)
n2 = round(cv * atten * N_STEPS)
if n2 == N_STEPS:
n2 = N_STEPS - 1
else:
n2 = k2.read_position(steps=N_STEPS, samples=N_SAMPLES)
self.sequencers[0].change_sequence(n1)
self.sequencers[1].change_sequence(n2)
oled.fill(0)
for i in range(len(self.sequencers)):
s = self.sequencers[i]
# Set the output voltages if needed
if s.output_dirty:
s.apply_output()
# Show the sequence number, sequence, and draw a box around the active bit
oled.text(f"{s.sequence_n:5} {s.binary_sequence:08b}", 0, CHAR_HEIGHT*i + TEXT_TOP, 1)
oled.fill_rect(
BITS_LEFT + s.step * CHAR_WIDTH,
CHAR_HEIGHT*i + TEXT_TOP,
CHAR_WIDTH,
CHAR_HEIGHT,
1
)
oled.text(
f"{s.current_bit}",
BITS_LEFT + s.step * CHAR_WIDTH,
CHAR_HEIGHT*i + TEXT_TOP,
0
)
oled.show()
if __name__ == "__main__":
IttyBitty().main()