-
Notifications
You must be signed in to change notification settings - Fork 86
/
probapoly.py
236 lines (194 loc) · 8.61 KB
/
probapoly.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
from europi import *
from time import ticks_diff, ticks_ms, sleep
from random import randint, uniform
from europi_script import EuroPiScript
import machine
'''
Probapoly
author: Nik Ansell (github.com/gamecat69)
A polyrhythmic sequencer with probability
digital_in: Clock input
analog_in: Different mode, adjusted by setting self.ainMode as follows:
- Mode 1: Analogue input toggles double time feature
- Mode 2: Analogue input voltage adjusts the upper poly value
- [default] Mode 3: Analogue input voltage adjusts the probabilities of outputs 2,3,5,6 sending gates
button_1: Short press (<500ms): Reduce pattern length (using manual pattern length is ON). Long Press (>500ms): Toggle doubletime feature
button_2: Short press (<500ms): Reduce pattern length (using manual pattern length is ON). Long Press (>500ms): Toggle Manual pattern length feature
knob_1: Select upper polyrhythm value
knob_2: Select lower polyrhythm value
output_1: Gate upper polyrhythm
output_2: Gate upper polyrhythm (50% probability)
output_3: Gate upper polyrhythm (25% probability)
output_4: Gate lower polyrhythm
output_5: Gate lower polyrhythm (50% probability)
output_6: Gate lower polyrhythm (50% probability)
'''
class Probapoly(EuroPiScript):
def __init__(self):
# Needed if using europi_script
super().__init__()
# Variables
self.step = 1
self.clockStep = 0
self.resetTimeout = 2000
self.maxPolyVal = 23
self.upper = 1
self.lower = 3
self.ainValue = 0
self.upperBernoulliProb = 50
self.lowerBernoulliProb = 50
self.upperProb1 = 50
self.upperProb2 = 25
self.lowerProb1 = 50
self.lowerProb2 = 25
self.doubleTime = False
self.doubleTimeManualOverride = False
self.manualPatternLengthFeature = False
self.patternLength = self.lcm(self.upper, self.lower)
self.manualPatternLength = 32 # Default manual pattern length when self.manualPatternLengthFeature is first True
self.UPPER_BUTTON_PRESS_TIME_LIMIT = 3000 # Used as a workaround to stop phantom button presses (Issue 132)
self.SHORT_BUTTON_PRESS_TIME_THRESHOLD = 500
# Todo: Make this mode accessible from the UI
# Mode 1: Analogue input toggles double time feature
# Mode 2: Analogue input voltage adjusts the upper poly value
# Mode 3: Analogue input voltage adjusts the probabilities of outputs 2,3,5,6 sending gates
self.ainMode = 3
@din.handler
def clockRising():
for cv in cvs:
cv.off()
self.handleClock()
self.clockStep +=1
self.step += 1
# Reached end of pattern, or a shorter pattern is now needed (based on upper and lower values), reset step to 0
if self.step > self.patternLength:
self.step = 1
@din.handler_falling
def clockFalling():
for cv in cvs:
cv.off()
if self.doubleTimeManualOverride or self.doubleTime:
self.handleClock()
self.clockStep +=1
self.step += 1
# Reached end of pattern, or a shorter pattern is now needed, reset step to 1
if self.step > self.patternLength:
self.step = 1
@b1.handler_falling
def b1Pressed():
if ticks_diff(ticks_ms(), b1.last_pressed()) > self.SHORT_BUTTON_PRESS_TIME_THRESHOLD and ticks_diff(ticks_ms(), b1.last_pressed()) < self.UPPER_BUTTON_PRESS_TIME_LIMIT:
# toggle double-time feature
self.doubleTimeManualOverride = not self.doubleTimeManualOverride
else:
# Short press, decrease manual pattern length if self.manualPatternLengthFeature is True
if self.manualPatternLengthFeature:
self.manualPatternLength -= 1
self.patternLength = self.manualPatternLength
@b2.handler_falling
def b2Pressed():
if ticks_diff(ticks_ms(), b2.last_pressed()) > self.SHORT_BUTTON_PRESS_TIME_THRESHOLD and ticks_diff(ticks_ms(), b2.last_pressed()) < self.UPPER_BUTTON_PRESS_TIME_LIMIT:
# Toggle manualPatternLengthFeature
self.manualPatternLengthFeature = not self.manualPatternLengthFeature
if self.manualPatternLengthFeature:
self.patternLength = self.manualPatternLength
else:
# Short press, increase manual pattern length if self.manualPatternLengthFeature is True
if self.manualPatternLengthFeature:
self.manualPatternLength += 1
self.patternLength = self.manualPatternLength
def handleClock(self):
# Play upper gate
if self.step % self.upper == 0:
cv1.value(1)
# Output trigger with fixed and unrelated probabilities
if randint(0,99) < self.upperProb1:
cv2.value(1)
if randint(0,99) < self.upperProb2:
cv3.value(1)
# Play lower gate
if self.step % self.lower == 0:
cv4.value(1)
# Output trigger with fixed and unrelated probabilities
if randint(0,99) < self.lowerProb1:
cv5.value(1)
if randint(0,99) < self.lowerProb2:
cv6.value(1)
# Generate pattern length by finding the lowest common multiple (LCM) and greatest common divisor (GCD)
# https://www.programiz.com/python-programming/examples/lcm
def lcm(self, x, y):
return (x*y)//self.computeGcd(x,y)
def computeGcd(self, x, y):
while(y):
x, y = y, x % y
return x
def getUpper(self):
# Mode 2, use the analogue input voltage to set the upper ratio value
if self.ainValue > 0.9 and self.ainMode == 2:
self.upper = int((self.maxPolyVal / 100) * self.ainValue) + 1
else:
self.upper = k1.read_position(self.maxPolyVal) + 1
def getLower(self):
self.lower = k2.read_position(self.maxPolyVal) + 1
def getAinValue(self):
self.ainValue = 100 * ain.percent()
#print(self.ainValue)
def updateScreen(self):
# Clear the screen
oled.fill(0)
rectLeftX = 20
rectRightX = 44
rectLength = 20
# Calculate where the steps should be using left justification
if self.step <= 9:
stepLeftX = 86
elif self.step > 9 and self.step <= 99:
stepLeftX = 78
else:
stepLeftX = 70
# current step
oled.text(str(self.step) + '|' + str(self.patternLength), stepLeftX, 0, 1)
# Upper + lower values
oled.text(str(self.upper), 0, 6, 1)
oled.rect(0 , 17, 16, 1, 1)
oled.text(str(self.lower), 0, 22, 1)
# probabilities
oled.rect(rectLeftX, 6, rectLength, 8, 1)
oled.fill_rect(rectLeftX, 6, self.upperProb1//5, 8, 1)
oled.rect(rectRightX, 6, rectLength, 8, 1)
oled.fill_rect(rectRightX, 6, self.upperProb2//5, 8, 1)
oled.rect(rectLeftX, 22, rectLength, 8, 1)
oled.fill_rect(rectLeftX, 22, self.lowerProb1//5, 8, 1)
oled.rect(rectRightX, 22, rectLength, 8, 1)
oled.fill_rect(rectRightX, 22, self.lowerProb2//5, 8, 1)
if self.doubleTimeManualOverride or self.doubleTime:
oled.text('!!', 100, 22, 1)
if self.manualPatternLengthFeature:
oled.text('M', 119, 22, 1)
oled.show()
def main(self):
while True:
self.getLower()
self.getUpper()
self.getAinValue()
self.updateScreen()
# Ain CV toggles doubleTime feature
if self.ainMode == 1:
if self.ainValue > 10:
self.doubleTime = True
else:
self.doubleTime = False
# Ain CV controls probability
elif self.ainValue >= 0.9 and self.ainMode == 3:
self.upperProb1 = int(self.ainValue * 2)
self.upperProb2 = int(self.ainValue * 1)
self.lowerProb1 = int(self.ainValue * 2)
self.lowerProb2 = int(self.ainValue * 1)
if not self.manualPatternLengthFeature:
self.patternLength = self.lcm(self.upper, self.lower)
# If I have been running, then stopped for longer than reset_timeout, reset the steps and clock_step to 0
if self.clockStep != 0 and ticks_diff(ticks_ms(), din.last_triggered()) > self.resetTimeout:
self.step = 1
self.clockStep = 0
if __name__ == '__main__':
dm = Probapoly()
dm.main()