-
Notifications
You must be signed in to change notification settings - Fork 89
/
Copy pathsequential_switch.py
221 lines (180 loc) · 7.11 KB
/
sequential_switch.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
"""Sequential switch for the EuroPi
@author Chris Iverach-Brereton <[email protected]>
@date 2023-02-13
"""
from europi import *
from europi_script import EuroPiScript
from experimental.settings_menu import *
from experimental.screensaver import OledWithScreensaver
import time
import random
## Move in order 1>2>3>4>5>6>1>2>...
MODE_SEQUENTIAL=0
## Move in order 1>6>5>4>3>2>1>6>...
MODE_REVERSE=1
## Move in order 1>2>3>4>5>6>5>4>...
MODE_PINGPONG=2
## Pick a random output, which can be the same as the one we're currently using
MODE_RANDOM=3
## Instead of a traditional sequential switch, treat the outputs like a s&h shift register
#
# CV1 will contain the latest s&h reading, with CV2-6 outputting increasingly old readings
MODE_SHIFT=4
## Use the automatic screensaver display
ssoled = OledWithScreensaver()
class SequentialSwitchDisplay(MenuItem):
"""
A menu item that displays the current state of the sequential switch
This isn't an editable menu item; just a visualization used as the top-level menu item.
The actual settings are children of this item.
"""
def __init__(self, sequential_switch, parent=None, children=None):
super().__init__(parent=parent, children=children)
self.sequential_switch = sequential_switch
def draw(self, oled=ssoled):
if self.sequential_switch.mode.value == MODE_SHIFT:
BAR_WIDTH = OLED_WIDTH // 6
for i in range(self.sequential_switch.num_outputs.value):
h = max(1, int(self.sequential_switch.shift_register[i] / MAX_OUTPUT_VOLTAGE * OLED_HEIGHT))
oled.fill_rect(BAR_WIDTH * i + 1, OLED_HEIGHT - h, BAR_WIDTH-2, h, 1)
else:
# Show all 6 outputs as a string on 2 lines to mirror the panel
switches = ""
for i in range(2):
for j in range(3):
out_no = i*3 + j
if out_no < self.sequential_switch.num_outputs.value:
# output is enabled; is it hot?
if self.sequential_switch.current_output == out_no:
switches = switches + " [*] "
else:
switches = switches + " [ ] "
else:
# output is disabled; mark it with .
switches = switches + " . "
switches = switches + "\n"
oled.centre_text(switches, auto_show=False)
class SequentialSwitch(EuroPiScript):
"""The main workhorse of the whole module
Copies the analog input to one of the 6 outputs, cycling which output
whenever a trigger is received
"""
def __init__(self):
super().__init__()
# The index of the current outputs
self.current_output = 0
# For MODE_PINGPONG, this indicates the direction of travel
# it will always be +1 or -1
self.direction = 1
## The shift register we use as s&h in MODE_SHIFT
self.shift_register = [0] * len(cvs)
self.visualization_dirty = True
self.mode = SettingMenuItem(
config_point = ChoiceConfigPoint(
"mode",
[
MODE_SEQUENTIAL,
MODE_REVERSE,
MODE_PINGPONG,
MODE_RANDOM,
MODE_SHIFT,
],
MODE_SEQUENTIAL
),
title="Mode",
labels = {
MODE_SEQUENTIAL: "Seqential",
MODE_REVERSE: "Reverse",
MODE_PINGPONG: "Ping-Pong",
MODE_RANDOM: "Random",
MODE_SHIFT: "Shift Reg.",
},
)
self.num_outputs = SettingMenuItem(
config_point = IntegerConfigPoint(
"num_outs",
2,
6,
6
),
title="# Outputs",
)
self.menu = SettingsMenu(
short_press_cb = lambda: ssoled.notify_user_interaction(),
long_press_cb = lambda: ssoled.notify_user_interaction(),
menu_items = [
SequentialSwitchDisplay(
self,
children = [
self.mode,
self.num_outputs,
]
)
]
)
self.menu.load_defaults(self._state_filename)
din.handler(self.on_trigger)
b1.handler(self.on_trigger)
def on_trigger(self):
"""Handler for the rising edge of the input clock
Also used for manually advancing the output on a button press
"""
# to save on clock cycles, don't use modular arithmetic
# instead just to integer math and handle roll-over manually
next_out = self.current_output
if self.mode.value == MODE_SEQUENTIAL:
next_out = next_out + 1
elif self.mode.value == MODE_REVERSE:
next_out = next_out - 1
elif self.mode.value == MODE_PINGPONG:
next_out = next_out + self.direction
else:
next_out = random.randint(0, self.num_outputs.value-1)
if next_out < 0:
if self.mode.value == MODE_REVERSE:
next_out = self.num_outputs.value-1
else:
next_out = -next_out
self.direction = -self.direction
elif next_out >= self.num_outputs.value:
if self.mode.value == MODE_SEQUENTIAL:
next_out = 0
else:
next_out = self.num_outputs.value-2
self.direction = -self.direction
self.current_output = next_out
if self.mode.value == MODE_SHIFT:
self.shift_register.pop(-1)
self.shift_register.insert(0, ain.read_voltage())
self.visualization_dirty = True
def main(self):
"""The main loop
Connects event handlers for clock-in and button presses
and runs the main loop
"""
while True:
if self.visualization_dirty or self.menu.ui_dirty:
self.visualization_dirty = False
ssoled.fill(0)
self.menu.draw(ssoled)
ssoled.show()
if self.menu.settings_dirty:
self.menu.save(self._state_filename)
if self.mode.value == MODE_SHIFT:
# CV1 gets the most-recent s&h output, all the others get older values
for i in range(self.num_outputs.value):
cvs[i].voltage(self.shift_register[i])
# turn off any unused outputs
for i in range(self.num_outputs.value, len(cvs)):
cvs[i].off()
else:
# read the input and send it to the current output
# all other outputs should be zero
input_volts = ain.read_voltage()
for i in range(len(cvs)):
if i == self.current_output:
cvs[i].voltage(input_volts)
else:
cvs[i].off()
if __name__ == "__main__":
SequentialSwitch().main()