Skip to content

Commit d2ac00b

Browse files
authored
Add Slopes script (#375)
* Add Slopes script * Increase the output scaling; we're consistently well below 5V, even with pretty quick LFOs, so allowing amplification is probably worthwhile.
1 parent 0139a3d commit d2ac00b

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

software/contrib/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ Random CV, optionally quantized, voltages based on controllable normal distribut
206206
<i>Author: [chrisib](https://github.com/chrisib)</i>
207207
<br><i>Labels: random, quantizer</i>
208208

209+
### Sigma \[ [documentation](/software/contrib/slopes.md) | [script](/software/contrib/slopes.py) \]
210+
CV analyzer that produces gates & CV outputs based on the slope of the incoming signal
211+
212+
<i>Author: [chrisib](https://github.com/chrisib)</i>
213+
<br><i>Labels: gates, CV</i>
214+
209215
### Smooth Random Voltages \[ [script](/software/contrib/smooth_random_voltages.py) \]
210216
Random CV with adjustable slew rate, inspired by: https://youtu.be/tupkx3q7Dyw.
211217

software/contrib/menu.py

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
["Scope", "contrib.scope.Scope"],
5656
["Seq. Switch", "contrib.sequential_switch.SequentialSwitch"],
5757
["Sigma", "contrib.sigma.Sigma"],
58+
["Slopes", "contrib.slopes.Slopes"],
5859
["Smooth Rnd Volts", "contrib.smooth_random_voltages.SmoothRandomVoltages"],
5960
["StrangeAttractor", "contrib.strange_attractor.StrangeAttractor"],
6061
["Traffic", "contrib.traffic.Traffic"],

software/contrib/slopes.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Slopes
2+
3+
Slopes is a CV processor that analyzes an incoming signal and generates outputs based on the shape of that input.
4+
5+
## Inputs & Outputs
6+
7+
- `ain`: The analogue signal to process. Due to technical limitations this signal should ideally be an LFO, smooth
8+
random voltage, or envelope. Audio-rate signals can be patched in, but will likely not be analyzed correctly due
9+
to processing latency.
10+
- `k1`: Noise filter. Increasing `k1` will make the signal processing less sensitive, but will also reduce random
11+
noise.
12+
- `k2`: CV output attenuator. At maximum, `cv4`-`6` will output `0-10V`. At 50% `cv4`-`6` will output `0-5V`. At 0%
13+
`cv4`-`6` will produce no output
14+
15+
- `cv1`: A gate signal that is high when `ain` is rising and low when `ain` is falling
16+
- `cv2`: A gate signal that is high when `ain` is falling and low when `ain` is rising
17+
- `cv3`: A gate signal that is high when `ain` is flat (i.e. holding the same voltage across samples)
18+
- `cv4`: A CV signal representing the positive slope of `ain`; the faster `ain` is rising, the stronger the CV signal
19+
- `cv5`: A CV signal representing the negative slope of `ain`; the faster `ain` is falling, the stronger the CV signal
20+
- `cv6`: A CV signal representing the overall maginude of the slope, ignoring the direction (i.e. the sum of `cv4` and `cv5`)
21+
22+
Unused:
23+
- `din`
24+
- `b1`
25+
- `b2`
26+
27+
The display is not used by `Slopes` and will remain off at all times.

software/contrib/slopes.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generates CV and gate signals based on the slope of an incoming analogue signal
4+
"""
5+
6+
from europi import *
7+
from europi_script import EuroPiScript
8+
9+
from experimental.math_extras import median
10+
11+
class Slopes(EuroPiScript):
12+
noise_attenuator = k1
13+
output_attenuator = k2
14+
15+
ain_rising_gate = cv1
16+
ain_falling_gate = cv2
17+
ain_steady_gate = cv3
18+
ain_pos_slope_cv = cv4
19+
ain_neg_slope_cv = cv5
20+
ain_slope_magnitude_cv = cv6
21+
22+
MIN_BINS = 1
23+
MAX_BINS = 12
24+
25+
MIN_SAMPLES = 32
26+
MAX_SAMPLES = 512
27+
28+
MAX_SLOPE = europi_config.MAX_INPUT_VOLTAGE / 10.0
29+
30+
DEAD_ZONE = 0.01
31+
32+
def __init__(self):
33+
super().__init__()
34+
35+
# bins used for median smoothing of the raw AIN data
36+
self.bins = [0] * self.MAX_BINS
37+
38+
def denoise(self, window_size):
39+
return median(self.bins[len(self.bins) - window_size - 1:])
40+
41+
def main(self):
42+
turn_off_all_cvs()
43+
oled.fill(0)
44+
oled.show()
45+
46+
while True:
47+
denoise_percent = self.noise_attenuator.percent()
48+
n_bins = round(denoise_percent * (self.MAX_BINS - self.MIN_BINS) + self.MIN_BINS)
49+
n_samples = round(denoise_percent * (self.MAX_SAMPLES - self.MIN_SAMPLES) + self.MIN_SAMPLES)
50+
deadzone = self.DEAD_ZONE * denoise_percent
51+
output_attenuation_percent = self.output_attenuator.percent() * 2
52+
53+
# calculate the previous sample based on our current noise filter size
54+
prev_volts = self.denoise(n_bins)
55+
56+
# grab the new sample and add it to the end of the list
57+
self.bins.pop(0)
58+
self.bins.append(ain.read_voltage(samples=n_samples))
59+
curr_volts = self.denoise(n_bins)
60+
61+
# calculate the change in volts
62+
d_volts = curr_volts - prev_volts
63+
slope = d_volts / self.MAX_SLOPE
64+
if slope > 1:
65+
slope = 1.0
66+
elif slope < -1.0:
67+
slope = -1.0
68+
69+
# Set the outputs
70+
if d_volts > deadzone:
71+
# ain rising
72+
self.ain_rising_gate.on()
73+
self.ain_falling_gate.off()
74+
self.ain_steady_gate.off()
75+
76+
self.ain_pos_slope_cv.voltage(slope * output_attenuation_percent * europi_config.MAX_OUTPUT_VOLTAGE)
77+
self.ain_neg_slope_cv.off()
78+
self.ain_slope_magnitude_cv.voltage(slope * output_attenuation_percent * europi_config.MAX_OUTPUT_VOLTAGE)
79+
elif d_volts < -deadzone:
80+
# ain falling
81+
self.ain_rising_gate.off()
82+
self.ain_falling_gate.on()
83+
self.ain_steady_gate.off()
84+
85+
self.ain_pos_slope_cv.off()
86+
self.ain_neg_slope_cv.voltage(-slope * output_attenuation_percent * europi_config.MAX_OUTPUT_VOLTAGE)
87+
self.ain_slope_magnitude_cv.voltage(-slope * output_attenuation_percent * europi_config.MAX_OUTPUT_VOLTAGE)
88+
else:
89+
# ain steady
90+
self.ain_rising_gate.off()
91+
self.ain_falling_gate.off()
92+
self.ain_steady_gate.on()
93+
94+
self.ain_pos_slope_cv.off()
95+
self.ain_neg_slope_cv.off()
96+
self.ain_slope_magnitude_cv.off()
97+
98+
if __name__ == "__main__":
99+
Slopes().main()

0 commit comments

Comments
 (0)