# 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 sleep_ms
from random import random
from europi_script import EuroPiScript
# Constant values for display
# Trigger Time (some module may not be able to catch up the trigger if it is too short)
class SingleBernoulliGate():
def __init__(self, control_knob = k1, control_port = ain, out_port = (cv1, cv2, cv3), visualization_para = ('P1', (0, 0), 0, -3), port3_func = 'clock', port3_source_cv = None):
1. control_knob and control_port tunes the probability threshold
if control_port == None, probability threshold is only determined by control_knob
2. out_port assigns left, right, and function port accordingly
out_port shoud have at least 2 ports (port3_func == 'none')
3. visualization_para = (text, text_pos, tex1_pos, bar_pos_offset)
4. port3_func and port3_source_cv determine the function of port3,
where port3_func can be 'none', 'clock', 'and', 'or', 'xor',
and port3_source_cv can be None or any other output port
the output of function_port = logic(current left, source), or clock(trigger)
self.mode_flg = 0 # mode 0: Trigger, mode 1: Gate, mode 2: Toggle
self.coin = 0 # probability
self.right_possibility = 0 # change every iteration
self.left_possibility = 0
self.right_possibility_sampled = 0 # only change when triggered
self.left_possibility_sampled = 0
self.port3_func = port3_func
self.port3_source_cv = port3_source_cv
self.control_knob = control_knob
self.control_port = control_port
self.left_port = out_port[0]
self.right_port = out_port[1]
self.function_port = out_port[2]
self.text = visualization_para[0]
self.text_pos = visualization_para[1]
self.text1_pos = visualization_para[2]
self.bar_pos_offset = visualization_para[3]
self.right_possibility = self.control_knob.percent() + self.control_port.read_voltage()/12
self.left_possibility = 1 - self.right_possibility
self.right_possibility = self.control_knob.percent()
self.left_possibility = 1 - self.right_possibility
def probability_text_visualization(self):
oled.text(self.text + f':{self.right_possibility:.2f}', self.text_pos[0], self.text_pos[1], 1)
def bar_visualization(self):
oled.rect(int(OLED_WIDTH / 2),
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
int(self.right_possibility * BERNOULLI_BAR_LEN),
oled.fill_rect(int(OLED_WIDTH / 2 - self.left_possibility * BERNOULLI_BAR_LEN),
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
int(self.left_possibility * BERNOULLI_BAR_LEN),
def probability_sample(self):
self.right_possibility_sampled = self.right_possibility
self.left_possibility_sampled = self.left_possibility
def triggered_maneuver(self):
if self.mode_flg == 0 or self.mode_flg == 1:
if self.coin < (self.right_possibility_sampled):
oled.fill_rect(int(OLED_WIDTH / 2 + self.right_possibility * BERNOULLI_BAR_LEN) + 2,
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
oled.rect(int(OLED_WIDTH / 2 - self.left_possibility * BERNOULLI_BAR_LEN) - BERNOULLI_INDI_WID - 2,
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
if self.coin < (self.right_possibility_sampled):
self.right_port.value(self.left_port._duty == 0)
def function_port_maneuver(self):
if self.port3_func == 'none':
elif self.port3_func == 'clock':
if self.port3_func == 'and':
self.function_port.value((self.port3_source_cv._duty and self.left_port._duty) != 0)
elif self.port3_func == 'or':
self.function_port.value((self.port3_source_cv._duty or self.left_port._duty) != 0)
elif self.port3_func == 'xor':
self.function_port.value((self.port3_source_cv._duty ^ self.left_port._duty) != 0)
def regular_visualization(self):
oled.text('Tr', self.text1_pos, OLED_HEIGHT-8, 1)
oled.text('G', self.text1_pos, OLED_HEIGHT-8, 1)
if self.coin < (self.right_possibility_sampled):
oled.fill_rect(int(OLED_WIDTH / 2 + self.right_possibility * BERNOULLI_BAR_LEN) + 2,
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
oled.rect(int(OLED_WIDTH / 2 - self.left_possibility * BERNOULLI_BAR_LEN) - BERNOULLI_INDI_WID - 2,
int((OLED_HEIGHT - BERNOULLI_BAR_WID) / 2) + self.bar_pos_offset,
oled.text('Tg', self.text1_pos, OLED_HEIGHT-8, 1)
def regular_maneuver(self):
if self.port3_func != 'none':
if self.port3_func == 'clock':
if self.port3_func == 'none':
class BernoulliGates(EuroPiScript):
self.first_gate = SingleBernoulliGate(control_knob = k1,
out_port = (cv1, cv2, cv3),
visualization_para = ('P1', (0, 0), 0, -3),
port3_func = 'clock', port3_source_cv = None)
self.second_gate = SingleBernoulliGate(control_knob = k2,
out_port = (cv4, cv5, cv6),
visualization_para = ('P2', (int(OLED_WIDTH / 2) + 8, 0), 110, BERNOULLI_BAR_WID - 1),
port3_func = 'and', port3_source_cv = cv1)
self.first_gate.mode_flg += 1
if self.first_gate.mode_flg == 3:
self.first_gate.mode_flg = 0
self.second_gate.mode_flg += 1
if self.second_gate.mode_flg == 3:
self.second_gate.mode_flg = 0
self.first_gate.get_prob()
self.second_gate.get_prob()
# OLED Show Possibility Bar
self.first_gate.probability_text_visualization()
self.second_gate.probability_text_visualization()
self.first_gate.bar_visualization()
self.second_gate.bar_visualization()
self.first_gate.probability_sample()
self.second_gate.probability_sample()
self.first_gate.triggered_maneuver()
self.second_gate.triggered_maneuver()
self.first_gate.function_port_maneuver()
self.second_gate.function_port_maneuver()
self.first_gate.regular_visualization()
self.second_gate.regular_visualization()
sleep_ms(BERNOULLI_TRG_T)
self.first_gate.regular_maneuver()
self.second_gate.regular_maneuver()
if __name__ == "__main__":
bernoulli_gates = BernoulliGates()