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"],