# Copyright 2022 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.
author: Adam Wonak (github.com/awonak)
Random cv with adjustable slew rate.
Inspired by: https://youtu.be/tupkx3q7Dyw
3 random or analog input s&h voltages with changable slew smoothness.
New voltages assigned upon each digital input trigger. Top row outputs move
towards target voltage according to slew rate set by knob 1. Bottom row outputs
immediately change to new target voltage. Voltage source toggled by button 2,
which will switch between random and analog input voltage source. Button 1 will
toggle various display types for the oled.
Col 1, change on each trigger.
Col 2, change on each trigger if target voltage has been reached.
Col 3, change on trigger with knob 2 probability.
digital_input: trigger to set a new target voltage.
analog_input: source for new target value when enabled.
knob_2: probability of new Col 3 voltage
button_1: cycle through the visualizations: Bars, Scope, Blank.
button_2: cycle through voltage sources: random, analog input.
output_1: smooth random voltage 1 - new voltage on trigger
output_2: smooth random voltage 2 - new voltage after target reached
output_3: smooth random voltage 3 - new voltage based on k2 probability
output_4: stepped random voltage 1 - new voltage on trigger (no slew)
output_5: stepped random voltage 2 - new voltage after target reached (no slew)
output_6: stepped random voltage 3 - new voltage based on k2 probability (no slew)
from random import random, uniform
from utime import ticks_diff, ticks_ms
from software.firmware import europi
from software.firmware.europi import CHAR_HEIGHT, OLED_HEIGHT, OLED_WIDTH
from software.firmware.europi_script import EuroPiScript
from europi import CHAR_HEIGHT, OLED_HEIGHT, OLED_WIDTH
from europi_script import EuroPiScript
def envelope_generator(start=0, target=10, slew_rate=512):
Creates a generator function returning the absolute value differece between
the start and target voltages over the given duration in ms.
cv_diff = abs(target - start)
_target = max(start, target)
slew_rate = max(slew_rate, 1) # avoid div by zero
time_since_start = ticks_diff(ticks_ms(), env_start)
env_progress = time_since_start / slew_rate
cur = env_progress * cv_diff
cur = europi.clamp(cur, europi.MIN_OUTPUT_VOLTAGE, europi.MAX_OUTPUT_VOLTAGE)
class SmoothRandomVoltages(EuroPiScript):
self.voltages = [0, 0, 0]
self.target_voltages = [0, 0, 0]
# Maximim ms duration for slew rate.
self.slew_rate = lambda: europi.k1.range(MAX_SLEW_RATE)
# Visualization display choice.
self.visualization = 0 # 0: Bars, 1: Scope, 2: Blank.
self.voltage_source = 0 # 0: random, 1: analog input.
self.voltage_source_display = ["random", "analog"]
envelope_generator(0, 10),
envelope_generator(0, 10),
envelope_generator(0, 10),
# Register digital input handler
def new_target_voltages():
"""Check if next voltage conditions and update if ready."""
self.set_target_voltages()
# Cycle through the visualizations
def change_visualization():
# 0: Bars, 1: Scope, 2: Blank.
self.visualization = (self.visualization + 1) % 3
# Toggle between random and analog input voltage source
def change_voltage_source():
# 0: random, 1: analog input.
self.voltage_source = (self.voltage_source + 1) % 2
def set_target_voltages(self):
"""Get next random voltage value."""
# Col 1, change on each trigger.
self.target_voltages[0] = self.get_new_voltage()
self.env[0] = envelope_generator(self.voltages[0], self.target_voltages[0], self.slew_rate())
# Col 2, change on each trigger if target voltage has been reached.
if self.voltages[1] == self.target_voltages[1]:
self.target_voltages[1] = self.get_new_voltage()
self.env[1] = envelope_generator(
self.voltages[1], self.target_voltages[1], self.slew_rate()
# Col 3, change on trigger with knob 2 probability.
if europi.k2.percent() > random():
self.target_voltages[2] = self.get_new_voltage()
self.env[2] = envelope_generator(
self.voltages[2], self.target_voltages[2], self.slew_rate()
def get_new_voltage(self):
"""Return a new voltage from analog in or random value."""
# 0: random, 1: analog input.
if self.voltage_source == 0:
return uniform(europi.MIN_OUTPUT_VOLTAGE, europi.MAX_OUTPUT_VOLTAGE)
return europi.ain.read_voltage()
def update_display(self):
"""Show current voltage visualizations."""
if self.visualization == 0:
elif self.visualization == 1:
def show_menu_header(self):
"""When button2 has been pressed, show the current changed source value."""
if ticks_diff(ticks_ms(), europi.b2.last_pressed()) < MENU_DURATION:
europi.oled.fill_rect(0, 0, OLED_WIDTH, CHAR_HEIGHT, 1)
msg = f"Source: {self.voltage_source_display[self.voltage_source]}"
europi.oled.text(msg, 0, 0, 0)
"""Draw a bar representing the slew / target for each of the 3 voltages."""
y1 = int(i * (OLED_HEIGHT / 3)) + 2
y2 = int(OLED_HEIGHT / 3) - 2
x2_slew = int((self.voltages[i] / europi.MAX_OUTPUT_VOLTAGE) * OLED_WIDTH)
x2_target = int((self.target_voltages[i] / europi.MAX_OUTPUT_VOLTAGE) * OLED_WIDTH)
if self.voltages[i] < self.target_voltages[i]:
europi.oled.fill_rect(x1, y1, x2_slew, y2, 1)
europi.oled.rect(x1, y1, x2_target, y2, 1)
europi.oled.rect(x1, y1, x2_slew, y2, 1)
europi.oled.fill_rect(x1, y1, x2_target, y2, 1)
"""Draw a real-time line representing the slew value for each of the 3 voltages."""
pixel_y = europi.OLED_HEIGHT - 1
europi.oled.vline(pixel_x, 0, europi.OLED_HEIGHT, 0)
europi.oled.pixel(pixel_x, pixel_y - int(self.voltages[0] * (pixel_y / 10)), 1)
europi.oled.pixel(pixel_x, pixel_y - int(self.voltages[1] * (pixel_y / 10)), 1)
europi.oled.pixel(pixel_x, pixel_y - int(self.voltages[2] * (pixel_y / 10)), 1)
for i in range(len(self.voltages)):
if self.voltages[i] < self.target_voltages[i]:
self.voltages[i] += next(self.env[i])
self.voltages[i] = min(self.voltages[i], self.target_voltages[i])
elif self.voltages[i] > self.target_voltages[i]:
self.voltages[i] -= next(self.env[i])
self.voltages[i] = max(self.voltages[i], self.target_voltages[i])
# Set the current smooth / stepped voltage.
europi.cvs[i].voltage(self.voltages[i])
europi.cvs[i + 3].voltage(self.target_voltages[i])
if __name__ == "__main__":
srv = SmoothRandomVoltages()