-
Notifications
You must be signed in to change notification settings - Fork 92
/
Copy pathcoin_toss.py
136 lines (116 loc) · 4.66 KB
/
coin_toss.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
# Copyright 2024 Allen Synthesis
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from europi import *
from random import random
from time import sleep_ms, ticks_ms, ticks_add, ticks_diff
import machine
from europi_script import EuroPiScript
# Internal clock tempo range.
MAX_BPM = 280
MIN_BPM = 20
# Constant values for display.
FRAME_WIDTH = int(OLED_WIDTH / 8)
class CoinToss(EuroPiScript):
def __init__(self):
self.gate_mode = True
self.internal_clock = True
self._prev_clock = 0
self._tempo = 0
self._deadline = 0
@b1.handler
def toggle_clock():
"""Toggle between internal clock and external clock from digital in."""
self.internal_clock = not self.internal_clock
@b2.handler
def toggle_gate():
"""Toggle between gate and trigger mode."""
self.gate_mode = not self.gate_mode
turn_off_all_cvs()
def tempo(self):
"""Read the current tempo set by k1 within set range."""
return round(k1.read_position(MAX_BPM - MIN_BPM) + MIN_BPM)
def get_next_deadline(self):
"""Get the deadline for next clock tick whole note."""
# The duration of a quarter note in ms for the current tempo.
wait_ms = int(((60 / self.tempo()) / 4) * 1000)
return ticks_add(ticks_ms(), wait_ms)
def wait(self):
"""Pause script execution waiting for next quarter note in the clock cycle."""
if self.internal_clock:
while True:
if ticks_diff(self._deadline, ticks_ms()) <= 0:
self._deadline = self.get_next_deadline()
return
else: # External clock
# Loop until digital in goes high (clock pulse received).
while not self.internal_clock:
if din.value() != self._prev_clock:
# We've detected a new clock value.
self._prev_clock = 1 if self._prev_clock == 0 else 0
# If the previous value was just set to 1 then we are seeing
# a high value for the first time, break wait and return.
if self._prev_clock == 1:
return
def toss(self, a, b, draw=True):
"""If random value is below trigger a, otherwise trigger b.
If draw is true, then display visualization of the coin toss.
"""
coin = random()
# Sum the knob2 and analogue input values to determine threshold.
read_sum = k2.percent() + ain.read_voltage()/12
self.threshold = clamp(read_sum, 0, 1)
if self.gate_mode:
a.value(coin < self.threshold)
b.value(coin > self.threshold)
else:
(a if coin < self.threshold else b).on()
if not draw:
return
# Draw gate/trigger display graphics for coin toss
h = int(self.threshold * OLED_HEIGHT)
tick = FRAME_WIDTH if self.gate_mode else 1
offset = 8 # The amount of negative space before drawing gate.
if coin < self.threshold:
oled.fill_rect(offset, 0, tick, h, 1)
else:
oled.fill_rect(offset, h, tick, OLED_HEIGHT, 1)
def main(self):
# Start the main loop.
counter = 0
while True:
# Scroll and clear new screen area.
oled.scroll(FRAME_WIDTH, 0)
oled.fill_rect(0, 0, FRAME_WIDTH, OLED_HEIGHT, 0)
self.toss(cv1, cv2)
cv3.on() # First column clock trigger
if counter % 4 == 0:
self.toss(cv4, cv5, False)
cv6.on() # Second column clock trigger (1/4x speed)
sleep_ms(10)
if self.gate_mode:
# Only turn off clock triggers.
cv3.off()
cv6.off()
else:
# Turn of all cvs in trigger mode.
turn_off_all_cvs()
# Draw threshold line
oled.hline(0, int(self.threshold * OLED_HEIGHT), FRAME_WIDTH, 1)
oled.show()
counter += 1
self.wait()
if __name__ == '__main__':
# Reset module display state.
coin_toss = CoinToss()
coin_toss.main()