forked from Allen-Synthesis/EuroPi
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtraffic.py
152 lines (122 loc) · 5.86 KB
/
traffic.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
#!/usr/bin/env python3
"""A EuroPi re-imagining of Traffic by Jasmine & Olive Trees
Two gate inputs are sent to AIN and DIN, their values multiplied by gains controlled by K1 and K2,
and the summed & differenced outputs sent to the outputs
"""
from europi import *
from europi_script import EuroPiScript
from experimental.a_to_d import AnalogReaderDigitalWrapper
from experimental.knobs import KnobBank
from experimental.screensaver import OledWithScreensaver
ssoled = OledWithScreensaver()
class Traffic(EuroPiScript):
def __init__(self):
super().__init__()
state = self.load_state_json()
# Button handlers to change the active channel for setting gains
self.channel_markers = ['>', ' ', ' '] # used to indicate the editable channel on the screen
@b1.handler
def b1_rising():
"""Activate channel b controls while b1 is held
"""
ssoled.notify_user_interaction()
self.k1_bank.set_current("channel_b")
self.k2_bank.set_current("channel_b")
self.channel_markers[0] = ' '
self.channel_markers[1] = '>'
self.channel_markers[2] = ' '
@b2.handler
def b2_rising():
"""Activate channel c controls while b1 is held
"""
ssoled.notify_user_interaction()
self.k1_bank.set_current("channel_c")
self.k2_bank.set_current("channel_c")
self.channel_markers[0] = ' '
self.channel_markers[1] = ' '
self.channel_markers[2] = '>'
def either_button_falling():
"""Revert to channel a when the button is released
"""
self.k1_bank.set_current("channel_a")
self.k2_bank.set_current("channel_a")
self.channel_markers[0] = '>'
self.channel_markers[1] = ' '
self.channel_markers[2] = ' '
self.save_state()
b1.handler_falling(either_button_falling)
b2.handler_falling(either_button_falling)
# Input trigger handlers
self.input1_trigger_at = time.ticks_ms()
self.input2_trigger_at = self.input1_trigger_at
@din.handler
def din1_rising():
self.input1_trigger_at = time.ticks_ms()
# Set the all-triggers output high
self.last_output_trigger_at = self.input1_trigger_at
cv6.on()
def din2_rising():
self.input2_trigger_at = time.ticks_ms()
# Set the all-triggers output high
self.last_output_trigger_at = self.input2_trigger_at
cv6.on()
self.k1_bank = (
KnobBank.builder(k1) \
.with_unlocked_knob("channel_a") \
.with_locked_knob("channel_b", initial_percentage_value=state.get("gain_b1", 0.5)) \
.with_locked_knob("channel_c", initial_percentage_value=state.get("gain_c1", 0.5)) \
.build()
)
self.k2_bank = (
KnobBank.builder(k2) \
.with_unlocked_knob("channel_a") \
.with_locked_knob("channel_b", initial_percentage_value=state.get("gain_b2", 0.5)) \
.with_locked_knob("channel_c", initial_percentage_value=state.get("gain_c2", 0.5)) \
.build()
)
self.din1 = din
self.din2 = AnalogReaderDigitalWrapper(ain, cb_rising=din2_rising)
# we fire a trigger on CV6, so keep track of the previous outputs so we know when something's changed
self.last_output_trigger_at = time.ticks_ms()
def save_state(self):
state = {
"gain_b1": self.k1_bank["channel_b"].percent(samples=1024),
"gain_b2": self.k2_bank["channel_b"].percent(samples=1024),
"gain_c1": self.k1_bank["channel_c"].percent(samples=1024),
"gain_c2": self.k2_bank["channel_c"].percent(samples=1024)
}
self.save_state_json(state)
def main(self):
TRIGGER_DURATION = 10 # 10ms triggers every time we get a rising edge on either input channel
while True:
self.din2.update()
gain_a1 = round(self.k1_bank["channel_a"].percent(samples=1024), 3)
gain_a2 = round(self.k2_bank["channel_a"].percent(samples=1024), 3)
gain_b1 = round(self.k1_bank["channel_b"].percent(samples=1024), 3)
gain_b2 = round(self.k2_bank["channel_b"].percent(samples=1024), 3)
gain_c1 = round(self.k1_bank["channel_c"].percent(samples=1024), 3)
gain_c2 = round(self.k2_bank["channel_c"].percent(samples=1024), 3)
# calculate the outputs
delta_t = time.ticks_diff(self.input1_trigger_at, self.input2_trigger_at)
if delta_t > 0 or abs(delta_t) <= 50: # assume triggers within 50ms are simultaneous
# din received a trigger more recently than ain
out_a = MAX_OUTPUT_VOLTAGE * gain_a1
out_b = MAX_OUTPUT_VOLTAGE * gain_b1
out_c = MAX_OUTPUT_VOLTAGE * gain_c1
else:
# ain received a trigger more recently than din
out_a = MAX_OUTPUT_VOLTAGE * gain_a2
out_b = MAX_OUTPUT_VOLTAGE * gain_b2
out_c = MAX_OUTPUT_VOLTAGE * gain_c2
cv1.voltage(out_a)
cv2.voltage(out_b)
cv3.voltage(out_c)
cv4.voltage(abs(out_a - out_b))
cv5.voltage(abs(out_a - out_c))
now = time.ticks_ms()
if time.ticks_diff(now, self.last_output_trigger_at) > TRIGGER_DURATION:
cv6.off()
# show the current gains * outputs, marking the channel we're controlling via the knobs & buttons
ssoled.centre_text(f"{self.channel_markers[0]} A {gain_a1:0.3f} {gain_a2:0.3f}\n{self.channel_markers[1]} B {gain_b1:0.3f} {gain_b2:0.3f}\n{self.channel_markers[2]} C {gain_c1:0.3f} {gain_c2:0.3f}")
if __name__ == "__main__":
Traffic().main()