Skip to content

Commit

Permalink
Start working on a basic RTC-based random sequencer
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisib committed Jan 7, 2025
1 parent 1239ce0 commit 36b99df
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 0 deletions.
9 changes: 9 additions & 0 deletions software/contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ Recording of CV can be primed so that you can record a movement without missing
<i>Author: [anselln](https://github.com/anselln)</i>
<br><i>Labels: sequencer, CV, performance</i>

### Daily Random \[ [documentation](/software/contrib/daily_random.md) | [script](/software/contrib/daily_random.md) \]
A pseudo-random gate and CV sequencer that uses a realtime clock to generate patterns.

Requires installing and configuring a realtime clock module, connected to EuroPi's external I2C interface for best results.

<i>Author: [chrisib](https://github.com/chrisib)</i>
<br><i>Labels: sequencer, gate, cv, random, realtime clock</i>


### Egressus Melodium \[ [documentation](/software/contrib/egressus_melodiam.md) | [script](/software/contrib/egressus_melodiam.py) \]
Clockable and free-running LFO and random CV pattern generator

Expand Down
57 changes: 57 additions & 0 deletions software/contrib/daily_random.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Daily Random

Generates pseudo-random gate and CV patterns based on the current date and time.

## I/O Assignments

- `ain`: not used
- `din`: external clock input
- `b1`: not used
- `b2`: not used
- `k1`: not used
- `k2`: not used
- `cv1`: daily gate sequence (updates at midnight UTC)
- `cv2`: hourly gate sequence (updates at the top of every hour)
- `cv3`: minute gate sequence (updates at the top of every minute)
- `cv4`: daily CV sequence (updates at midnight UTC)
- `cv5`: hourly CV sequence (updates at the top of every hour)
- `cv6`: minute CV sequence (updates at the top of every minute)

## Required Hardware

This script _can_ be used on a normal EuroPi, but will result in highly predictable
patterns. For best result, connect a Realtime Clock (RTC) to EuroPi's secondary I2C
header pins, located on the underside of the board.

## Installing the clock

TODO: pictures of mounting a DS3231

## Configuring the clock

The default external I2C settings from `europi_config` should be used, unless you have
a specific need to change them in `config/EuroPiConfig.json`:
```json
{
"EXTERNAL_I2C_SDA": 2,
"EXTERNAL_I2C_SCL": 3,
"EXTERNAL_I2C_CHANNEL": 1,
"EXTERNAL_I2C_FREQUENCY": 100000,
"EXTERNAL_I2C_TIMEOUT": 50000,
}
```

You will also need to edit `config/ExperimentalConfig.json`:
```json
{
"RTC_IMPLEMENTATION": "ds3231"
}
```

Once installed and configured, if you have not already set the clock's time, you can do so by
connecting your EuroPi to Thonny's Python terminal and running the following commands:
```python
from experimental.rtc import clock, Month, Weekday
clock.source.set_datetime(clock.source.set_datetime((2025, Month.JUNE, 14, 22, 59, 0, Weekday.THURSDAY)))
```
You should change the day and time to match the current UTC time.
85 changes: 85 additions & 0 deletions software/contrib/daily_random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from europi import *
from europi_script import EuroPiScript

from experimental.rtc import clock

import random


class DailyRandom(EuroPiScript):
"""
Generates a set of pseudo-random gate and CV sequences every day
This script requires a realtime clock. Please refer to
experimental.clocks for supported clocks.
If no RTC is installed & configured, the default clock will be used,
but this will generate the same pattern every time the module is
restarted.
"""

SEQUENCE_LENGTH = 16

def __init__(self):
super().__init__()

current_time = clock.now()
self.regenerate_sequences(current_time)

self.sequence_index = 0

@din.handler
def advance_sequence():
self.sequence_index += 1

def regenerate_sequences(self, datetime):
(year, month, day, hour, minute) = datetime[0:5]

seed_1 = year ^ month ^ day
seed_2 = year ^ month ^ day ^ hour
seed_3 = year ^ month ^ day ^ hour ^ minute

def generate_gates(seed):
random.seed(seed)
bits = random.getrandbits(self.SEQUENCE_LENGTH)
pattern = []
for i in range(self.SEQUENCE_LENGTH):
pattern.append(bits & 0x01)
bits = bits >> 1
return pattern

def generate_cv(seed):
random.seed(seed)
pattern = []
for i in range(self.SEQUENCE_LENGTH):
pattern.append(random.random() * europi_config.MAX_OUTPUT_VOLTAGE)
return pattern

self.sequences = [
generate_gates(seed_1),
generate_gates(seed_2),
generate_gates(seed_3),
generate_cv(seed_1),
generate_cv(seed_2),
generate_cv(seed_3),
]

def main(self):
# clear the display
oled.fill(0)
oled.show()

while True:
# regenerate the patterns when the day rolls over
current_time = clock.now()
self.regenerate_sequences(current_time)

for i in range(len(cvs)):
if i < len(cvs) // 2:
cvs[i].voltage(self.sequences[i][self.sequence_index % self.SEQUENCE_LENGTH] * europi_config.GATE_VOLTAGE)
else:
cvs[i].voltage(self.sequences[i][self.sequence_index % self.SEQUENCE_LENGTH] * europi_config.MAX_OUTPUT_VOLTAGE)


if __name__ == "__main__":
DailyRandom().main()
1 change: 1 addition & 0 deletions software/contrib/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
["Consequencer", "contrib.consequencer.Consequencer"],
["Conway", "contrib.conway.Conway"],
["CVecorder", "contrib.cvecorder.CVecorder"],
["Daily Random", "contrib.daily_random.DailyRandom"],
["Diagnostic", "contrib.diagnostic.Diagnostic"],
["EgressusMelodiam", "contrib.egressus_melodiam.EgressusMelodiam"],
["EnvelopeGen", "contrib.envelope_generator.EnvelopeGenerator"],
Expand Down

0 comments on commit 36b99df

Please sign in to comment.