# 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 time import ticks_diff, ticks_ms, sleep
from random import randint, uniform
from europi_script import EuroPiScript
author: Nik Ansell (github.com/gamecat69)
A polyrhythmic sequencer with probability
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):
# Needed if using europi_script
self.upperBernoulliProb = 50
self.lowerBernoulliProb = 50
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
# 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:
if self.doubleTimeManualOverride or self.doubleTime:
# Reached end of pattern, or a shorter pattern is now needed, reset step to 1
if self.step > self.patternLength:
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
# Short press, decrease manual pattern length if self.manualPatternLengthFeature is True
if self.manualPatternLengthFeature:
self.manualPatternLength -= 1
self.patternLength = self.manualPatternLength
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
# Short press, increase manual pattern length if self.manualPatternLengthFeature is True
if self.manualPatternLengthFeature:
self.manualPatternLength += 1
self.patternLength = self.manualPatternLength
if self.step % self.upper == 0:
# Output trigger with fixed and unrelated probabilities
if randint(0,99) < self.upperProb1:
if randint(0,99) < self.upperProb2:
if self.step % self.lower == 0:
# Output trigger with fixed and unrelated probabilities
if randint(0,99) < self.lowerProb1:
if randint(0,99) < self.lowerProb2:
# Generate pattern length by finding the lowest common multiple (LCM) and greatest common divisor (GCD)
# https://www.programiz.com/python-programming/examples/lcm
return (x*y)//self.computeGcd(x,y)
def computeGcd(self, x, y):
# 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
self.upper = k1.read_position(self.maxPolyVal) + 1
self.lower = k2.read_position(self.maxPolyVal) + 1
self.ainValue = 100 * ain.percent()
# Calculate where the steps should be using left justification
elif self.step > 9 and self.step <= 99:
oled.text(str(self.step) + '|' + str(self.patternLength), stepLeftX, 0, 1)
oled.text(str(self.upper), 0, 6, 1)
oled.rect(0 , 17, 16, 1, 1)
oled.text(str(self.lower), 0, 22, 1)
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)
# Ain CV toggles doubleTime feature
# 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:
if __name__ == '__main__':