# 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.
Two 8-step trigger, gate & CV sequencers based on the binary representation of an 8-bit number
from europi_script import EuroPiScript
from experimental.math_extras import gray_encode
A container class for one sequencer
def __init__(self, button_in, trigger_out, gate_out, cv_out, use_gray_encoding=False):
@param button_in The button the user can press to manually advance the sequence
@param trigger_out The CV output for the trigger signal
@param gate_out The CV output for the gate signal
@param cv_out The CV output for the CV signal
@param use_gray_encoding If true, we use gray encoding instead of traditional binary
button_in.handler(self.advance)
button_in.handler_falling(self.trigger_off)
self.trigger_out = trigger_out
self.use_gray_encoding = use_gray_encoding
# the integer representation of our sequence
# if we're not using gray encoding this should be the same as our binary sequence
# the raw binary pattern that represents our sequence
self.binary_sequence = 0x00
# is the current output state in need of refreshing?
self.output_dirty = False
# turn everything off initially
self.step = (self.step + 1) & 0x07 # restrict this to 0-7
self.cv_out.voltage(europi_config.MAX_OUTPUT_VOLTAGE * self.cv_sequence / 255)
self.output_dirty = False
def change_sequence(self, n):
if self.use_gray_encoding:
def shifted_sequence(self):
return ((self.binary_sequence << self.step) & 0xff) | ((self.binary_sequence & 0xff) >> (8 - self.step))
# reverse the bits of the shifted sequence
s = self.shifted_sequence
return (self.shifted_sequence >> 7) & 0x01
class IttyBitty(EuroPiScript):
BittySequence(b1, cv1, cv2, cv3, use_gray_encoding=self.config.USE_GRAY_ENCODING),
BittySequence(b2, cv4, cv5, cv6, use_gray_encoding=self.config.USE_GRAY_ENCODING),
for s in self.sequencers:
for s in self.sequencers:
# If true, use gray encoding instead of standard binary
# Gray encding flips a single bit at each step, meaning any two adjacent
# sequences differ by only 1 bit
# Flags to enable AIN to control channel A and/or channel B
# when enabled, the knob acts as an attenuator instead of a selector
BITS_LEFT = CHAR_WIDTH * 6
cv = ain.percent(samples=N_SAMPLES)
if self.config.USE_AIN_A:
atten = k1.percent(samples=N_SAMPLES)
n1 = round(cv * atten * N_STEPS)
# prevent bounds problems since percent() returns [0, 1], not [0, 1)
n1 = k1.read_position(steps=N_STEPS, samples=N_SAMPLES)
if self.config.USE_AIN_B:
atten = k2.percent(samples=N_SAMPLES)
n2 = round(cv * atten * N_STEPS)
n2 = k2.read_position(steps=N_STEPS, samples=N_SAMPLES)
self.sequencers[0].change_sequence(n1)
self.sequencers[1].change_sequence(n2)
for i in range(len(self.sequencers)):
# Set the output voltages if needed
# Show the sequence number, sequence, and draw a box around the active bit
oled.text(f"{s.sequence_n:5} {s.binary_sequence:08b}", 0, CHAR_HEIGHT*i + TEXT_TOP, 1)
BITS_LEFT + s.step * CHAR_WIDTH,
CHAR_HEIGHT*i + TEXT_TOP,
BITS_LEFT + s.step * CHAR_WIDTH,
CHAR_HEIGHT*i + TEXT_TOP,
if __name__ == "__main__":