Skip to content

Commit 66a08aa

Browse files
authored
New script: Bouncing Pixels (#432)
1 parent 29dcf1b commit 66a08aa

4 files changed

Lines changed: 900 additions & 0 deletions

File tree

software/contrib/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Mirrors a gate/trigger input, with adjustable skip probability across channels.
4242
<i>Author: [awonak](https://github.com/awonak)</i>
4343
<br><i>Labels: random, triggers</i>
4444

45+
### Bouncing Pixels \[ [documentation](/software/contrib/bouncing_pixels.md) | [script](/software/contrib/bouncing_pixels.py) \]
46+
47+
Pixels bounce around on the display and trigger gates when hitting the edges.
48+
49+
<i>Author: [Jorin](https://github.com/jorins)</i>
50+
<br><i>Labels: gates, random, simulation, triggers</i>
51+
4552
### Clock Modifier \[ [documentation](/software/contrib/clock_mod.md) | [script](/software/contrib/clock_mod.md) \]
4653

4754
A clock multiplier or divider. Each channel has an independently-controllable modifier, multiplying or dividing an external clock signal on `din`.
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Bouncing Pixels
2+
3+
author: [Jorin](https://github.com/jorins)
4+
5+
date: 2025-04-12
6+
7+
labels: gates, random, simulation, triggers
8+
9+
Pixels bounce around the display and trigger gates when hitting the edges.
10+
Inspired by the classic bouncing DVD logo!
11+
12+
## Usage
13+
14+
Just boot up the script and you're already going! You can use the knobs to
15+
adjust several parameters. The analogue input can be bound to any of them.
16+
By default, it affects the speed. The analogue input is summed with the knob
17+
value of its affected parameter, meaning that the knob sets a minimum value
18+
for the analogue input to add to. Note that since the EuroPi takes analogue
19+
inputs between 0V and 10V, AC signals must be biased properly or the bottom
20+
half of the input will be clipped.
21+
22+
### Parameters
23+
24+
* *Speed* affects how fast the simulation runs. It translates to how fast the
25+
pixels move.
26+
* *Width* controls how wide the playing field is.
27+
* *Impulse speed* controls how much speed is added to each pixel when an impulse
28+
occurs.
29+
* *Ball count* controls how many pixels are in play.
30+
31+
### Impulses
32+
33+
Impulses can be caused globally by pressing B2 or sending a signal to the
34+
digital input (assuming it has not been reconfigured). When an impulse occurs,
35+
velocity in a random direction. The speed added is the product of the impulse
36+
speed parameter and a random number (between 0.5 and 2.0 by default).
37+
38+
### Deactivation
39+
40+
One possible over and under speed behaviour is that pixels are deactivated.
41+
When deactivated, a pixel will not be processed or rendered until reset. Resets
42+
can be manually triggered by pressing B1 or using the digital input (must be
43+
configured). A reset is automatically triggered when all pixels in play are
44+
deactivated.
45+
46+
## Outputs
47+
48+
* CV1: collision with top edge (25ms)
49+
* CV2: collision with left edge (25ms)
50+
* CV3: collision with right edge (25ms)
51+
* CV4: collision with bottom edge (25ms)
52+
* CV5: collision with any edge (10ms)
53+
* CV6: collision with corner (100ms)
54+
55+
Denoted gate hold lengths are defaults. They can be changed in the
56+
[configuration file](#Configuration).
57+
58+
## Controls
59+
60+
* K1: simulation speed
61+
* K2: width
62+
* Either button + K1: ball count
63+
* Either button + K2: impulse speed
64+
* B1 short press: reset
65+
* B2 short press: send impulse
66+
* Digital input: send impulse (customisable)
67+
* Analogue input: speed (customisable)
68+
69+
Second layer knobs are "locking", which is to say you need to physically set the
70+
knob to a position close to the registered value in order for it to latch and
71+
start changing. This is to prevent abrupt changes in values when you press the
72+
buttons. If you lose track of a knob position, try sweeping the full range to
73+
get it to latch.
74+
75+
The short press threshold is set to 500ms by default. You can adjust this
76+
length in the [configuration file](#Configuration).
77+
78+
The digital input can be set to trigger a reset instead of an impulse. Likewise,
79+
the analogue input can be set to control any of the parameters controlled by
80+
knobs. These behaviours can be defined [configuration file](#Configuration).
81+
82+
## Configuration
83+
84+
The script can be thoroughly customised using a configuration file. The
85+
configuration file must be located at `/config/BouncingPixels.json` on the micro
86+
controller.
87+
88+
### Sample configuration file
89+
90+
```json
91+
{
92+
"LONG_PRESS_LENGTH": 500.0,
93+
"TIMESCALE_MIN": 0.0,
94+
"TIMESCALE_MAX": 100.0,
95+
"KNOB_CHANGE_THRESHOLD": 0.01,
96+
"DIN_FUNCTION": "impulse",
97+
"AIN_FUNCTION": "speed",
98+
"GATE_HOLD_LENGTH_TOP": 25.0,
99+
"GATE_HOLD_LENGTH_LEFT": 25.0,
100+
"GATE_HOLD_LENGTH_RIGHT": 25.0,
101+
"GATE_HOLD_LENGTH_BOTTOM": 25.0,
102+
"GATE_HOLD_LENGTH_ANY": 10.0,
103+
"GATE_HOLD_LENGTH_CORNER": 100.0,
104+
"ARENA_WIDTH_MIN": 30.0,
105+
"ARENA_WIDTH_MAX": 1920.0,
106+
"BALL_COUNT_MAX": 100,
107+
"BALL_COUNT_MIN": 1,
108+
"CORNER_COLLISION_MARGIN": 15.0,
109+
"START_SPEED_MIN": 10.0,
110+
"START_SPEED_MAX": 100.0,
111+
"ACCEL_MIN": -5.0,
112+
"ACCEL_MAX": 5.0,
113+
"BOUNCINESS_MIN": 0.8,
114+
"BOUNCINESS_MAX": 1.2,
115+
"BOUNCE_ANGLE_DEVIATION_MAX": 15.0,
116+
"UNDER_SPEED_BEHAVIOUR": "reset",
117+
"OVER_SPEED_BEHAVIOUR": "reset",
118+
"UNDER_SPEED_THRESHOLD": 5.0,
119+
"OVER_SPEED_THRESHOLD": 1000000,
120+
"IMPULSE_SPEED_MIN": 0,
121+
"IMPULSE_SPEED_MAX": 100000,
122+
"IMPULSE_SPEED_VARIATION_MIN": 0.5,
123+
"IMPULSE_SPEED_VARIATION_MAX": 2.0,
124+
}
125+
```
126+
127+
### Configuration values
128+
129+
| Key | Type | Possible values | Default value | Description |
130+
|-------------------------------|-----------------------|--------------------------------------------------------|---------------|-------------|
131+
| `POLL_FREQUENCY` | Floating point number | >= 5 | 30 | How frequently the application polls for new inputs, expressed as times per second.<br><br>⚠️ **Changing this value is not recommended.** |
132+
| `SAVE_PERIOD` | Floating point number | >= 0 | 5000 | How frequently the application state is saved at most, expressed as seconds between saves.<br><br>⚠️ **Changing this value is not recommended.** |
133+
| `RENDER_FREQUENCY` | Floating point number | >= 1 | 30 | How frequently the display display is updated at most, expressed as times per second.<br><br>⚠️ **Changing this value is not recommended.** |
134+
| `LONG_PRESS_LENGTH` | Floating point number | >= 0 | 500 | How many milliseconds a button must be pressed for it to be considered a long press. |
135+
| `TIMESCALE_MIN` | Floating point number | any | 0 | The speed at which the simulation runs when the speed parameter is set to minimum. |
136+
| `TIMESCALE_MAX` | Floating point number | any | 100 | The speed at which the simulation runs when the speed parameter is set to maximum. |
137+
| `KNOB_CHANGE_THRESHOLD` | Floating point number | 0.0 - 0.1 | 0.01 | How much a knob must change for its value to update. A higher value reduces jitter, but decreases sensitivity. |
138+
| `DIN_FUNCTION` | Choice | One of `impulse`, `reset` | `impulse` | The function to trigger when the digital input is raised. |
139+
| `AIN_FUNCTION` | Choice | One of `speed`, `impulse_speed`, `ball_count`, `width` | `speed` | The parameter that the analogue input modulates. |
140+
| `GATE_HOLD_LENGTH_TOP` | Floating point number | 1 - 10,000 | 25 | How long the gate for top edge collisions is held open, expressed in milliseconds. |
141+
| `GATE_HOLD_LENGTH_LEFT` | Floating point number | 1 - 10,000 | 25 | How long the gate for left edge collisions is held open, expressed in milliseconds. |
142+
| `GATE_HOLD_LENGTH_RIGHT` | Floating point number | 1 - 10,000 | 25 | How long the gate for right edge collisions is held open, expressed in milliseconds. |
143+
| `GATE_HOLD_LENGTH_BOTTOM` | Floating point number | 1 - 10,000 | 25 | How long the gate for bottom edge collisions is held open, expressed in milliseconds. |
144+
| `GATE_HOLD_LENGTH_ANY` | Floating point number | 1 - 10,000 | 10 | How long the gate for any edge collisions is held open, expressed in milliseconds. |
145+
| `GATE_HOLD_LENGTH_CORNER` | Floating point number | 1 - 10,000 | 100 | How long the gate for corner collisions is held open, expressed in milliseconds. |
146+
| `ARENA_WIDTH_MIN` | Floating point number | 30 - 1,920 | 30 | The smallest width the playing field can have. Fifteen units equal one pixel. |
147+
| `ARENA_WIDTH_MAX` | Floating point number | 30 - 1,920 | 1920 | The largest width the playing field can have. Fifteen units equal one pixel. |
148+
| `BALL_COUNT_MIN` | Integer number | 1 - 100 | 1 | The number of balls in play when the `ball_count` parameter is set to minimum. |
149+
| `BALL_COUNT_MAX` | Integer number | 1 - 100 | 100 | The number of balls in play when the `ball_count` parameter is set to maximum. |
150+
| `CORNER_COLLISION_MARGIN` | Floating point number | 0 - 240 | 15 | How large a portion of the side edges are considered corners. Fifteen units equal one pixel on the display. |
151+
| `START_SPEED_MIN` | Floating point number | >= 0 | 10 | The minimum speed a pixel may get upon reset, expressed as units per second. |
152+
| `START_SPEED_MAX` | Floating point number | >= 0 | 100 | The maximum speed a pixel may get upon reset, expressed as units per second. |
153+
| `ACCEL_MIN` | Floating point number | any | -5.0 | The minimum acceleration a pixel may get upon reset, expressed as units per second squared. |
154+
| `ACCEL_MAX` | Floating point number | any | 5.0 | The maximum acceleration a pixel may get upon reset, expressed as units per second squared. |
155+
| `BOUNCINESS_MIN` | Floating point number | >= 0.0001 | 0.8 | The minimum bounce speed multiplier a pixel may get upon reset. |
156+
| `BOUNCINESS_MAX` | Floating point number | >= 0.0001 | 1.2 | The maximum bounce speed multiplier a pixel may get upon reset. |
157+
| `BOUNCE_ANGLE_DEVIATION_MAX` | Floating point number | 0 - 180 | 15 | The maximum angle a pixel might deviate from its calculated direction upon bouncing, expressed in degrees. |
158+
| `UNDER_SPEED_BEHAVIOUR` | Choice | One of `impulse`, `reset`, `deactivate`, `noop` | `reset` | What a pixel should do when its speed goes below the under speed threshold. |
159+
| `OVER_SPEED_BEHAVIOUR` | Choice | One of `reset`, `deactivate` | `reset` | What a pixel should do when its speed goes above the over speed threshold. |
160+
| `UNDER_SPEED_THRESHOLD` | Floating point number | >= 0 | 5.0 | The speed threshold under which a pixel activates its under speed behaviour. |
161+
| `OVER_SPEED_THRESHOLD` | Floating point number | >= 0 | 1,000,000 | The speed threshold over which a pixel activates its over speed threshold. |
162+
| `IMPULSE_SPEED_MIN` | Floating point number | >= 0 | 0 | The speed added to pixels by an impulse when the `impulse` parameter is at minimum. |
163+
| `IMPULSE_SPEED_MAX` | Floating point number | >= 0 | 100,000 | The speed added to pixels by an impulse when the `impulse` parameter is at maximum. |
164+
| `IMPULSE_SPEED_VARIATION_MIN` | Floating point number | >= 0 | 0.5 | The smallest possible random multiplier for impulse speed. |
165+
| `IMPULSE_SPEED_VARIATION_MAX` | Floating point number | >= 0 | 2.0 | The largest possible random multiplier for impulse speed. |
166+
167+
## Known issues & limitations
168+
169+
* Gate lengths are not entirely precise. They're guaranteed to be at least the
170+
set length, but they may exceed it slightly on account of running on
171+
tick-based timers.
172+
* Corners aren't actually corners, they're just the top and bottom most
173+
parts of the side edges. This means that if a pixel is traveling parallel and
174+
close to the top or bottom edge, it's possible that it bounces away from the
175+
edge upon hitting the corner collision area and thus triggers a corner gate
176+
without hitting both a horizontal and a vertical edge. Another consequence is
177+
that a corner gate will always trigger at the same time as a side gate,
178+
regardless of whether the pixel collides with a horizontal or vertical edge
179+
first.
180+
* Collision handling cannot handle a pixel going fast enough to bounce with two
181+
opposing walls in one tick. If a pixel goes fast enough to do this, it
182+
triggers the over speed behaviour (i.e. by default, they will reset).
183+
* This script consumes a relatively large amount of memory. Max ball counts over
184+
100 risk crashing the script. Ball counts over 20 consume enough memory to
185+
cause connection issues with Thonny. If you need to debug the script, you will
186+
probably want to lower the max ball count.
187+
188+
## Further development
189+
190+
If you fancy developing this script further, here are a few suggestions for what
191+
you can do:
192+
193+
* Address any of the issues mentioned above.
194+
* Implement gravity! You can add another set of knob banks (let the buttons
195+
trigger different banks) that control gravity magnitude and direction.
196+
The tricky part of implementing gravity comes from the fact that a pixel will
197+
have zero speed at the top of its arc when bouncing in place, and triggers the
198+
under speed behaviour.
199+
* Allow more in-app configuration instead of relying on the configuration file.
200+
* Implement visual feedback on the locked knob inputs. This would make the use
201+
of the second layer knobs easier.

0 commit comments

Comments
 (0)