Skip to content

Commit c2f61b4

Browse files
authoredNov 5, 2024··
Add Bezier Script (#371)
* Initial commit; start blocking out the new script, add the new script to the menu & documentation * Add b2 handlers, fix some copy & paste errors * Rename experimental.math -> experimental.math_extras to avoid namespace conflicts. Add linear rescale function to math_extras. Implement initial cubic bezier curves * Use the new rescale function instead of redefining it in additional modules * Implement cubic equation solver to calculate the curves from their parametric forms, set the output voltages. Still need to write the GUI, handle clipping, and fix any bugs that come up during testing * Implement clipping * Fix some bugs, implement the basic UI. I think the program is functional enough for some serious testing * Add a rolling graph to the GUI, fix a bug that could cause the shift button's behaviour to invert (leading channel B to be the default). Fix a bug loading the persistent settings on startup * Implement CV control over curve & frequency * Add the .pixel function to the oled wrapper * Documentation improvements, intentionally overshoot the goal sometimes to force fold/thru clipping, enable screensaver since the UI is pretty static * Remove an image that accidentally got committed
1 parent 4d38213 commit c2f61b4

File tree

10 files changed

+661
-19
lines changed

10 files changed

+661
-19
lines changed
 

‎software/contrib/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ Two channels of probability-based routing, where the digital input will be route
1919
<i>Author: [Bridgee](https://github.com/Bridgee)</i>
2020
<br><i>Labels: Random</i>
2121

22+
### Bezier Curves \[ [documentation](/software/contrib/bezier.md) | [script](/software/contrib/bezier.py) \]
23+
Smooth random voltages based on bezier curves. Inspired by the ADDAC507 Random Bezier Waves module.
24+
25+
<i>Author: [chrisib](https://github.com/chrisib)</i>
26+
<br><i>Labels: Random</i>
27+
2228
### Clock Modifier \[ [documentation](/software/contrib/clock_mod.md) | [script](/software/contrib/clock_mod.md) \]
2329
A clock multiplier or divider. Each channel has an independently-controllable modifier, multiplying or dividing an external clock signal on `din`.
2430

33.4 KB
Loading

‎software/contrib/bezier.md

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Bezier Curves
2+
3+
This script generates two random CV outputs using [Bezier curves](https://en.wikipedia.org/wiki/Bezier_curve).
4+
5+
This work is inspired by the [ADDAC507](https://www.addacsystem.com/en/products/modules/addac500-series/addac507),
6+
a collaboration between ADDAC System and [Monotrail](https://youtu.be/9PxVmeMrOoQ?si=GsNDKNipjHtBIPT1)
7+
8+
## Input & Output
9+
10+
The module has two separate output channels, referred to as A and B. Each has identical controls, though channel B's
11+
controls can only be accessed by holding `b2`. Note that when changing channels, the knobs will "lock" in their
12+
previous positions, so you may need to sweep the knob to unlock it.
13+
14+
- `b1` -- press to change the clipping mode for both channels (see below)
15+
- `b2` -- hold to change `k1` and `k2` to channel B input
16+
17+
- `k1` -- set the frequency of channel A or B
18+
- `k2` -- set the curve of channel A or B
19+
- `ain` -- configurable CV input to control either the frequency or curve (see below for how to set routing)
20+
- `din` -- on a rising edge, force channels A and B to choose new goal voltages. This will not generate an output gate
21+
on `cv4` (see below), but may change the gate state on `cv5` and `cv6`.
22+
23+
- `cv1` -- CV output from channel A
24+
- `cv2` -- CV output from channel B
25+
- `cv3` -- the average of channel A and B CV outputs
26+
- `cv4` -- gate output of channel A
27+
- `cv5` -- gate output of channel B
28+
- `cv6` -- logic combination of gate outputs of channels A and B (see below for how to configure the logic mode)
29+
30+
Gate outputs are different for channels A and B:
31+
- Channel A (`cv4`) outputs a 10ms trigger every time a new sample is generated (determined by the frequency)
32+
- Channel B (`cv5`) is high whenever its voltage output is higher than 50%. e.g. if the range is set to 0-10V, `cv5`
33+
will be high if `cv2` is 5V or more.
34+
35+
Patching a clock/gate/trigger signal into `din` will force the output channels to choose a new goal voltage. This can
36+
(and frequently will) cause an abrupt change in the CV output of channels A and B, rather than the smoothly changing
37+
voltages they normally output.
38+
39+
## Additional Configuration
40+
41+
The script has additional parameters that can be set by manually editing/creating `/config/Bezier.json`. The default
42+
values for this file are below:
43+
44+
```json
45+
{
46+
"MIN_VOLTAGE": 0.0,
47+
"MAX_VOLTAGE": 10.0,
48+
"AIN_MODE": "frequency",
49+
"LOGIC_MODE": "xor",
50+
"MAX_INPUT_VOLTAGE": 10.0
51+
}
52+
```
53+
54+
The following fields may be set:
55+
56+
- `MIN_VOLTAGE` -- the minimum voltage that channel A or channel B may output. Default: 0.0
57+
- `MAX_VOLTAGE` -- the maximum voltage that channel A or channel B may output. Default 10.0
58+
- `AIN_MODE` -- changes what parameter voltage to `ain` controls. Must be one of `curve` or `frequency`. Default: `frequency`
59+
- `LOGIC_MODE` -- sets the logical operation used to determine the gate output of `cv6`. Must be one of
60+
`and`, `or`, `xor`, `nand`, `nor`, or `xnor`. Default: `xor`.
61+
- `MIN_FREQUENCY` -- the minumum frequency (Hz) for choosing new random values. Must be between 0.001 and 10.0. Default: 0.01
62+
- `MAX_FREQUENCY` -- the maximum frequency (Hz) for choosing new random values. Must be between 0.001 and 10.0. Default: 1.0
63+
- `MAX_INPUT_VOLTAGE` -- the maximum CV input voltage (default: 10.0)
64+
65+
Note that the maximum and minimum voltages must be defined such that:
66+
- `MIN_VOLTAGE` is less than `MAX_VOLTAGE`
67+
- `MIN_VOLTAGE` and `MAX_VOLTAGE` are positive
68+
- `MIN_VOLTAGE` and `MAX_VOLTAGE` do not fall outside the range defined by the module's master configuration
69+
- `MAX_INPUT_VOLTAGE` is less than the module's master configuration.
70+
71+
See [Configuration](/software/CONFIGURATION.md) for more information on input & output voltage options for the
72+
entire module.
73+
74+
If you plan on using CV to control Bezier, make sure you set the maximum input voltage according to your modules'
75+
output. For example if you plan on connecting it to an LFO that outputs 0-5V, you may find it helpful to set the
76+
`MAX_INPUT_VOLTAGE` to `5.0` to allow the full modulation range. If your LFO is bipolar (e.g. `+/-5V`), the input
77+
will be clamped at 0 as EuroPi does not support bipolar CV input. In this case, the use of a voltage rectifier is
78+
recommended.
79+
80+
## Clipping mode
81+
82+
The output can behave in one of 3 ways if the output wave moves outside the defined maximum/minimum voltage range:
83+
- `limit` -- the output is clipped to the maximum/minimum value
84+
- `fold` -- the output is flipped such that the shape of the curve is reflected
85+
- `thru` -- the output wraps around through the opposite end of the range
86+
87+
## Curve Shapes
88+
89+
The shape of the bezier curves is defined by the "curve constant" `k`. This value lies in the range `[-1, 1]`, and is
90+
interpreted as follows:
91+
- `0.0` -- linear interpolation between voltages
92+
- `k < 0` -- horizontal approach to each new voltage
93+
- `k > 0` -- vertical approach to each new voltage
94+
95+
The following image illustrates this concept, copied from the ADDAC507 manual:
96+
![Bezier Curves](bezier-docs/curve-knob.png.png)
97+
98+
Negative values of `k` will generally result in a smoother overall shape to the output voltage. Positive values will
99+
have more abrupt changes in voltage whenever a new goal value is generated.
100+
101+
## CV Control
102+
103+
If CV control is set to `frequency` (the default), `ain` will accept positive voltage, increasing the frequency of both
104+
channels as voltage increases.
105+
106+
If CV control is set to `curve`, `ain` will accept positive voltage, changing the curve constant of both channels. The
107+
channels' curve constants are set to the average between the knob value (`[-1, 1]`) and the CV value:
108+
- `0V` is equivalent to a curve constant of `-1`
109+
- `50%` of `MAX_INPUT_VOLTAGE` is equivalent to a curve constant of `0`
110+
- `100%` of `MAX_INPUT_VOLTAGE` is equivalent to a curve constant of `+1`

‎software/contrib/bezier.py

+519
Large diffs are not rendered by default.

‎software/contrib/lutra.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,9 @@
2323

2424
import _thread
2525

26+
from experimental.math_extras import rescale
2627
from experimental.thread import DigitalInputHelper
2728

28-
def rescale(x, old_min, old_max, new_min, new_max):
29-
"""Convert x in [old_min, old_max] -> y in [new_min, new_max]
30-
"""
31-
if x < old_min:
32-
return new_min
33-
elif x > old_max:
34-
return new_max
35-
else:
36-
return (x - old_min) * (new_max - new_min) / (old_max - old_min) + new_min
37-
3829
class WaveGenerator:
3930
"""Generates the output wave forms and sets the voltage going to one of the output jacks
4031

‎software/contrib/menu.py

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# ["0123456789abcdef", "contrib.spam.Eggs"],
2323
["Arpeggiator", "contrib.arp.Arpeggiator"],
2424
["Bernoulli Gates", "contrib.bernoulli_gates.BernoulliGates"],
25+
["Bezier Curves", "contrib.bezier.Bezier"],
2526
["Bit Garden", "contrib.bit_garden.BitGarden"],
2627
["Clock Modifier", "contrib.clock_mod.ClockModifier"],
2728
["Coin Toss", "contrib.coin_toss.CoinToss"],

‎software/contrib/particle_physics.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from europi_script import EuroPiScript
88

99
from experimental.knobs import KnobBank
10+
from experimental.math_extras import rescale
1011

1112
import math
1213
import time
@@ -28,14 +29,6 @@
2829
## If a bounce reaches no higher than this, assume we've come to rest
2930
ASSUME_STOP_PEAK = 0.002
3031

31-
def rescale(x, old_min, old_max, new_min, new_max):
32-
if x <= old_min:
33-
return new_min
34-
elif x >= old_max:
35-
return new_max
36-
else:
37-
return (x - old_min) / (old_max - old_min) * (new_max - new_min) + new_min
38-
3932

4033
class Particle:
4134
def __init__(self):

‎software/firmware/experimental/knobs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import OrderedDict
22
from europi import Knob, MAX_UINT16
33

4-
from experimental.math import median
4+
from experimental.math_extras import median
55

66

77
DEFAULT_THRESHOLD = 0.05

‎software/firmware/experimental/math.py ‎software/firmware/experimental/math_extras.py

+19
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,22 @@ def mean(l):
3030
if len(l) == 0:
3131
return 0
3232
return sum(l) / len(l)
33+
34+
35+
def rescale(x, old_min, old_max, new_min, new_max, clip=True):
36+
"""
37+
Convert x in [old_min, old_max] -> y in [new_min, new_max] using linear interpolation
38+
39+
@param x The value to convert
40+
@param old_min The old (inclusive) minimum
41+
@param old_max The old (inclusive) maximum
42+
@param new_min The new (inclusive) minimum
43+
@param new_max The new (inclusive) maximum
44+
@param clip If true, we clip values within the [min, max] range; otherwise we extrapolate based on the ranges
45+
"""
46+
if clip and x < old_min:
47+
return new_min
48+
elif clip and x > old_max:
49+
return new_max
50+
else:
51+
return (x - old_min) * (new_max - new_min) / (old_max - old_min) + new_min

‎software/firmware/experimental/screensaver.py

+3
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,6 @@ def invert(self, color=1):
170170

171171
def contrast(self, contrast):
172172
oled.contrast(contrast)
173+
174+
def pixel(self, x, y, color=1):
175+
oled.pixel(x, y, color)

0 commit comments

Comments
 (0)
Please sign in to comment.