# 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 random import random
from time import sleep_ms, ticks_ms, ticks_add, ticks_diff
from europi_script import EuroPiScript
# Internal clock tempo range.
# Constant values for display.
FRAME_WIDTH = int(OLED_WIDTH / 8)
class CoinToss(EuroPiScript):
self.internal_clock = True
"""Toggle between internal clock and external clock from digital in."""
self.internal_clock = not self.internal_clock
"""Toggle between gate and trigger mode."""
self.gate_mode = not self.gate_mode
"""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)
"""Pause script execution waiting for next quarter note in the clock cycle."""
if ticks_diff(self._deadline, ticks_ms()) <= 0:
self._deadline = self.get_next_deadline()
# 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:
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.
# 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)
a.value(coin < self.threshold)
b.value(coin > self.threshold)
(a if coin < self.threshold else b).on()
# 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)
oled.fill_rect(offset, h, tick, OLED_HEIGHT, 1)
# Scroll and clear new screen area.
oled.scroll(FRAME_WIDTH, 0)
oled.fill_rect(0, 0, FRAME_WIDTH, OLED_HEIGHT, 0)
cv3.on() # First column clock trigger
self.toss(cv4, cv5, False)
cv6.on() # Second column clock trigger (1/4x speed)
# Only turn off clock triggers.
# Turn of all cvs in trigger mode.
oled.hline(0, int(self.threshold * OLED_HEIGHT), FRAME_WIDTH, 1)
if __name__ == '__main__':
# Reset module display state.