diff --git a/software/contrib/README.md b/software/contrib/README.md
index b5a4eab4a..42ab83a1c 100644
--- a/software/contrib/README.md
+++ b/software/contrib/README.md
@@ -54,6 +54,15 @@ Recording of CV can be primed so that you can record a movement without missing
Author: [anselln](https://github.com/anselln)
Labels: sequencer, CV, performance
+### 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.
+
+Author: [chrisib](https://github.com/chrisib)
+
Labels: sequencer, gate, cv, random, realtime clock
+
+
### Egressus Melodium \[ [documentation](/software/contrib/egressus_melodiam.md) | [script](/software/contrib/egressus_melodiam.py) \]
Clockable and free-running LFO and random CV pattern generator
diff --git a/software/contrib/daily_random.md b/software/contrib/daily_random.md
new file mode 100644
index 000000000..660865925
--- /dev/null
+++ b/software/contrib/daily_random.md
@@ -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.
diff --git a/software/contrib/daily_random.py b/software/contrib/daily_random.py
new file mode 100644
index 000000000..36ec65eac
--- /dev/null
+++ b/software/contrib/daily_random.py
@@ -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()
diff --git a/software/contrib/menu.py b/software/contrib/menu.py
index 70cd31ca6..3f07f53f8 100644
--- a/software/contrib/menu.py
+++ b/software/contrib/menu.py
@@ -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"],