Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat europi_config keys like named constants #346

Merged
merged 31 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
13662d6
Create a new ConfigSettings class that exposes the config dict's item…
chrisib Feb 17, 2024
c550d4d
Make the new class _not_ a dict just to avoid any chance of subscript…
chrisib Feb 17, 2024
872d319
Use double-quotes to make the linter happier
chrisib Feb 17, 2024
af00178
Update scripts that use their own config_point instances
chrisib Feb 17, 2024
851a8c9
Make the config object a dict again; the tests were failing and I don…
chrisib Feb 17, 2024
6d507d2
Hack the tests by providing an __eq__ method that supports comparing …
chrisib Feb 18, 2024
c402f67
Add additional documentation about the dict key -> attribute name con…
chrisib Feb 18, 2024
9986b5e
Single -> double quotes
chrisib Feb 18, 2024
2dba94e
Revert the config file to load as a dict to preserve read/write compa…
chrisib Feb 18, 2024
f7d0fbb
Fix the tests now that we've rolled back the JSON parsing
chrisib Feb 18, 2024
53a6546
Typo
chrisib Mar 1, 2024
cab1ade
Add a comment explaining why self.__dict__ is required
chrisib Mar 1, 2024
66f42e3
Revert accidental changes to turing_machine.py
chrisib Mar 1, 2024
593b8c3
Linting
chrisib Mar 1, 2024
16cd1ad
Tweak the example to not need to explicitly use the `europi` namespace
chrisib Mar 1, 2024
f2e9687
Update the new e. melodiam script to use the new constants instead of…
chrisib Mar 1, 2024
058d1fe
Fix bug in the quantizer
chrisib Mar 1, 2024
2ae4c83
Remove the intermediate `dict` objects, be less permissive with confi…
chrisib Mar 2, 2024
d99269d
Fix the tests, add an == comparator to the ConfigSettings, fix contri…
chrisib Mar 2, 2024
5fb1cf6
Linting
chrisib Mar 2, 2024
f5df7ed
Remove an unnecessary newline
chrisib Mar 4, 2024
68e2553
Fix some typos in the readme
chrisib Mar 4, 2024
9bada65
Simplify the ConfigSettings __eq__ implementation
chrisib Mar 5, 2024
aff93e0
Revert link to point back to the __init__ file
chrisib Mar 5, 2024
f95b3a3
Use ALL_CAPS for all configuration JSON keys; do not convert case. Up…
chrisib Mar 5, 2024
293f144
Linting
chrisib Mar 5, 2024
0759e7f
Update contrib scripts to use ALL_CAPS json keys for their own configs
chrisib Mar 5, 2024
c64169f
Remove notice about caps not being enforced
chrisib Mar 5, 2024
22f082a
Expand the text around the code examples
chrisib Mar 5, 2024
c9a3aca
Remove references to "namespace"
chrisib Mar 5, 2024
25c77a9
Reword the above/below text to be less confusing
chrisib Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 118 additions & 30 deletions software/CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,143 @@
# Configuration Customization

Certain properties of the module, such as screen rotation, CPU clock speed, and Raspberry Pi Pico model, can be
set using a static configuration file. This file is located at `/config/config_EuroPiConfig.json` on the
set using a static configuration file. This file is located at `/config/EuroPiConfig.json` on the
Raspberry Pi Pico. If this file does not exist, default settings will be loaded. The following shows the
default configuration:
```json
{
"europi_model": "europi",
"pico_model": "pico",
"cpu_freq": 250000000,
"rotate_display": false,
"display_width": 128,
"display_height": 32,
"display_sda": 0,
"display_scl": 1,
"display_channel": 0
"max_output_voltage": 10,
"max_input_voltage": 12,
"gate_voltage": 5
"EUROPI_MODEL": "europi",
"PICO_MODEL": "pico",
"CPU_FREQ": 250000000,
"ROTATE_DISPLAY": false,
"DISPLAY_WIDTH": 128,
"DISPLAY_HEIGHT": 32,
"DISPLAY_SDA": 0,
"DISPLAY_SCL": 1,
"DISPLAY_CHANNEL": 0,
"MAX_OUTPUT_VOLTAGE": 10,
"MAX_INPUT_VOLTAGE": 12,
"GATE_VOLTAGE": 5
}
```

- `europi_model` specifies the type of EuroPi module. Currently only `"europi"` is supported
- `pico_model` must be one of `"pico"` or `"pico w"`
- `cpu_freq` must be one of `250000000` or `125000000`
- `rotate_display` must be one of `false` or `true`
- `display_width` is the width of the screen in pixels. The standard EuroPi screen is 128 pixels wide
- `display_height` is the height of the screen in pixels. The standard EuroPi screen is 32 pixels tall
- `display_sda` is the I²C SDA pin used for the display. Only SDA capable pins can be selected
- `display_scl` is the I²C SCL pin used for the display. Only SCL capable pins can be selected
- `display_channel` is the I²C channel used for the display, either 0 or 1.
- `volts_per_octave` must be one of `1.0` (Eurorack standard) or `1.2` (Buchla standard)
- `max_output_voltage` is an integer in the range `[0, 10]` indicating the maximum voltage CV output can generate.
- `EUROPI_MODEL` specifies the type of EuroPi module. Currently only `"europi"` is supported
- `PICO_MODEL` must be one of `"pico"` or `"pico w"`
- `CPU_FREQ` must be one of `250000000` or `125000000`
- `ROTATE_DISPLAY` must be one of `false` or `true`
- `DISPLAY_WIDTH` is the width of the screen in pixels. The standard EuroPi screen is 128 pixels wide
- `DISPLAY_HEIGHT` is the height of the screen in pixels. The standard EuroPi screen is 32 pixels tall
- `DISPLAY_SDA` is the I²C SDA pin used for the display. Only SDA capable pins can be selected
- `DISPLAY_SCL` is the I²C SCL pin used for the display. Only SCL capable pins can be selected
- `DISPLAY_CHANNEL` is the I²C channel used for the display, either 0 or 1.
- `MAX_OUTPUT_VOLTAGE` is an integer in the range `[0, 10]` indicating the maximum voltage CV output can generate.
The hardware is capable of 10V maximum
- `max_input_voltage` is an integer in the range `[0, 12]` indicating the maximum allowed voltage into the `ain` jack.
- `MAX_INPUT_VOLTAGE` is an integer in the range `[0, 12]` indicating the maximum allowed voltage into the `ain` jack.
The hardware is capable of 12V maximum
- `gate_voltage` is an integer in the range `[0, 12]` indicating the voltage that an output will produce when `cvx.on()` is called
- `GATE_VOLTAGE` is an integer in the range `[0, 10]` indicating the voltage that an output will produce when `cvx.on()`
is called. This value must not be higher than `MAX_OUTPUT_VOLTAGE`



# Experimental configuration

Other configuration properties are used by [experimental features](software/firmware/experimental/__init__.py)
and can be set using a similar static configuration file. This file is located at `/config/config_ExperimentalConfig.json`
Other configuration properties are used by [experimental features](/software/firmware/experimental/__init__.py)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this link is broken again because it's already inside software

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still seems broken, I think it's the fact CONFIGURATION is inside software already, so it would need either ./ to go up and then down one folder, or just start with firmware/
[experimental features](firmware/experimental/__init__.py)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I view the file in question here: https://github.com/Allen-Synthesis/EuroPi/blob/c9a3acac9fc9f2b350c4e3f5a93e47575781845f/software/CONFIGURATION.md the link is working. How/where are you looking at it where the link is broken?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'll probably be fine once merged. It works for me in that linked file, but not in the rich diff when I look at the PR

and can be set using a similar static configuration file. This file is located at `/config/ExperimentalConfig.json`
on the Raspberry Pi Pico. If this file does not exist, default settings will be loaded. The following
shows the default configuration:

```json
{
"volts_per_octave": 1.0,
"VOLTS_PER_OCTAVE": 1.0,
}
```

- `volts_per_octave` must be one of `1.0` (Eurorack standard) or `1.2` (Buchla standard)
- `VOLTS_PER_OCTAVE` must be one of `1.0` (Eurorack standard) or `1.2` (Buchla standard)


# Accessing config members in Python code

The firmware converts the JSON file into a `ConfigSettings` object, where the JSON keys are converted
to Python attributes. The JSON object's keys must follow these rules, otherwise a `ValueError` will be raised:

1. The string may not be empty
1. The string may only contain letters, numbers, and the underscore (`_`) character
1. The string may not begin with a number
1. The string should be in `ALL_CAPS`

The JSON key is converted into a Python attribute of the configuration object. For example, this JSON file
```json
{
"CLOCK_MULTIPLIER": 4,
"HARD_SYNC": true,
"WAVE_SHAPE": "sine"
}
```
would produce a Python object with these attributes:
```python
>>> dir(config_object)
[
'__class__',
'__init__',
'__module__',
'__qualname__',
'__dict__',
'to_attr_name',
'CLOCK_MULTIPLIER',
'HARD_SYNC',
'WAVE_SHAPE'
]

>>> config_object.CLOCK_MULTIPLIER
4

>>> config_object.HARD_SYNC
True

>>> config_object.WAVE_SHAPE
'sine'
```

`europi.py` contains objects called `europi_config` and `experimental_config` which implement the core & experimental
customizations described in the sections above. Below is a detailed summary of the contents of these objects:

```python
>>> from europi import europi_config
>>> dir(europi_config)
[
'__class__',
'__init__',
'__module__',
'__qualname__',
'__dict__',
'to_attr_name',
'CPU_FREQ',
'DISPLAY_CHANNEL',
'DISPLAY_HEIGHT',
'DISPLAY_SCL',
'DISPLAY_SDA',
'DISPLAY_WIDTH',
'EUROPI_MODEL',
'GATE_VOLTAGE',
'MAX_INPUT_VOLTAGE',
'MAX_OUTPUT_VOLTAGE',
'PICO_MODEL',
'ROTATE_DISPLAY'
]
```

When you import `europi` into your project you can access the `europi_config` object like this:
```python
from europi import *

# A voltage range we can select from in a user menu
VOLTAGE_RANGE = range(0, europi_config.MAX_OUTPUT_VOLTAGE)
```

Alternatively, you can access it using the fully qualified namespace:
```python
import europi
mjaskula marked this conversation as resolved.
Show resolved Hide resolved

# A voltage range we can select from in a user menu
VOLTAGE_RANGE = range(0, europi.europi_config.MAX_OUTPUT_VOLTAGE)
```
4 changes: 2 additions & 2 deletions software/contrib/consequencer.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def __init__(self):
self.CvPattern_prev = 0
self.reset_timeout = 1000
self.maxRandomPatterns = 32 # This prevents a memory allocation error
self.maxCvVoltage = clamp(europi_config["max_output_voltage"], 0, 9) # The maximum is 9 to maintain single digits in the voltage list
self.gateVoltage = europi_config["gate_voltage"]
self.maxCvVoltage = clamp(europi_config.MAX_OUTPUT_VOLTAGE, 0, 9) # The maximum is 9 to maintain single digits in the voltage list
self.gateVoltage = europi_config.GATE_VOLTAGE
self.gateVoltages = [0, self.gateVoltage]

self.ainVal = 0
Expand Down
4 changes: 2 additions & 2 deletions software/contrib/diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ def __init__(self):
5,
10, # max
]
self.temp_units = self.config["temp_units"]
self.temp_units = self.config.TEMP_UNITS
self.use_fahrenheit = self.temp_units == "F"

@classmethod
def config_points(cls):
return [configuration.choice(name="temp_units", choices=["C", "F"], default="C")]
return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")]

def calc_temp(self):
# see the pico's datasheet for the details of this calculation
Expand Down
24 changes: 12 additions & 12 deletions software/contrib/egressus_melodiam.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
Each slewGeneratorObjects[] object is a reference to a copy of the self.slewGenerator() function.
The self.slewGenerator() function receives a buffer filled with samples and yields one sample each time it is called using next()

In order to maintain the best balance of smooth waves and Rpi pico memory usage, an algorithm is used to vary the
In order to maintain the best balance of smooth waves and Rpi pico memory usage, an algorithm is used to vary the
sample rate automatically based on the selected output division.
This causes the sample rate to be at minium when there is a slow clock and high output division.
Conversely, when there is a fast clock and low output division a higher sample rate is used to avoid unwanted steps
Expand Down Expand Up @@ -91,7 +91,7 @@

# Set the maximum CV voltage using a global config value
# Important: Needs firmware v0.12.1 or higher
MAX_CV_VOLTAGE = europi_config["max_output_voltage"]
MAX_CV_VOLTAGE = europi_config.MAX_OUTPUT_VOLTAGE

MAX_STEP_LENGTH = 32

Expand Down Expand Up @@ -316,7 +316,7 @@ def initCvPatternBanks(self):

def generateNewRandomCVPattern(self, new=True, activePatternOnly=False):
"""Generate new CV pattern for existing bank or create a new bank

@param new If true, create a new pattern/overwrite the existing one. Otherwise re-use the existing pattern
@param activePatternOnly If true, generate pattern for selected output. Otherwise generate pattern for all outputs

Expand Down Expand Up @@ -435,7 +435,7 @@ def main(self):

# Update the last sample output time
self.lastSlewVoltageOutputTime[idx] = ticks_ms()

except StopIteration:
continue

Expand Down Expand Up @@ -717,7 +717,7 @@ def updateScreen(self):

def stepUpStepDown(self, start, stop, num, buffer):
"""Produces step up, step down

@param start Starting value
@param stop Target value
@param num Number of samples required
Expand All @@ -741,7 +741,7 @@ def stepUpStepDown(self, start, stop, num, buffer):

def linspace(self, start, stop, num, buffer):
"""Produces a linear transition

@param start Starting value
@param stop Target value
@param num Number of samples required
Expand All @@ -760,7 +760,7 @@ def linspace(self, start, stop, num, buffer):

def logUpStepDown(self, start, stop, num, buffer):
"""Produces a log up/step down transition

@param start Starting value
@param stop Target value
@param num Number of samples required
Expand Down Expand Up @@ -793,7 +793,7 @@ def logUpStepDown(self, start, stop, num, buffer):

def stepUpExpDown(self, start, stop, num, buffer):
"""Produces a step up, exponential down transition

@param start Starting value
@param stop Target value
@param num Number of samples required
Expand All @@ -816,7 +816,7 @@ def stepUpExpDown(self, start, stop, num, buffer):

def smooth(self, start, stop, num, buffer):
"""Produces smooth curve using half a cosine wave

@param start Starting value
@param stop Target value
@param num The number of samples required
Expand Down Expand Up @@ -848,7 +848,7 @@ def smooth(self, start, stop, num, buffer):

def expUpexpDown(self, start, stop, num, buffer):
"""Produces pointy exponential wave using a quarter cosine up and a quarter cosine down

@param start Starting value
@param stop Target value
@param num The number of samples required
Expand Down Expand Up @@ -886,7 +886,7 @@ def expUpexpDown(self, start, stop, num, buffer):
def sharkTooth(self, start, stop, num, buffer):
"""Produces a sharktooth wave with an approximate log curve up and approximate
exponential curve down

@param start Starting value
@param stop Target value
@param num The number of samples required
Expand Down Expand Up @@ -924,7 +924,7 @@ def sharkTooth(self, start, stop, num, buffer):
def sharkToothReverse(self, start, stop, num, buffer):
"""Produces a reverse sharktooth wave with an approximate exponential curve up and approximate
log curve down

@param start Starting value
@param stop Target value
@param num The number of samples required
Expand Down
3 changes: 1 addition & 2 deletions software/contrib/envelope_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self):
self.sustain_mode = state.get("sustain_mode", 1)
self.looping_mode = state.get("looping_mode", 0)

self.max_output_voltage = europi_config["max_output_voltage"]
self.max_output_voltage = europi_config.MAX_OUTPUT_VOLTAGE

#Distance of envelope voltage from max voltage/0 before 'jumping' to it - prevents large logarithmic calculations
self.voltage_threshold = 0.1
Expand Down Expand Up @@ -224,4 +224,3 @@ def main(self):

if __name__ == "__main__":
EnvelopeGenerator().main()

16 changes: 8 additions & 8 deletions software/contrib/turing_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def __init__(self, bit_count=DEFAULT_BIT_COUNT, max_output_voltage=MAX_OUTPUT_VO
self.tm = TuringMachine(
bit_count=bit_count,
max_output_voltage=max_output_voltage,
clear_on_write=self.config["write_value"] == 0,
clear_on_write=self.config.WRITE_VALUE == 0,
length=initial_length,
scale=MAX_OUTPUT_VOLTAGE * initial_scale_percent,
)
Expand All @@ -232,9 +232,9 @@ def __init__(self, bit_count=DEFAULT_BIT_COUNT, max_output_voltage=MAX_OUTPUT_VO
.build()
)

self.cv1_pulse_bit = self.config["cv1_pulse_bit"]
self.cv2_pulse_bit = self.config["cv2_pulse_bit"]
self.cv3_pulse_bit = self.config["cv3_pulse_bit"]
self.cv1_pulse_bit = self.config.CV1_PULSE_BIT
self.cv2_pulse_bit = self.config.CV2_PULSE_BIT
self.cv3_pulse_bit = self.config.CV3_PULSE_BIT

@din.handler
def clock():
Expand Down Expand Up @@ -297,11 +297,11 @@ def config_points(cls):
bitcount_range = range(1, min(DEFAULT_BIT_COUNT, 8))

return [
configuration.choice(name="write_value", choices=[0, 1], default=0),
configuration.choice(name="WRITE_VALUE", choices=[0, 1], default=0),
# simulate the actual bits available in the pulses expander (1-7)
configuration.integer(name="cv1_pulse_bit", range=bitcount_range, default=1),
configuration.integer(name="cv2_pulse_bit", range=bitcount_range, default=2),
configuration.integer(name="cv3_pulse_bit", range=bitcount_range, default=4),
configuration.integer(name="CV1_PULSE_BIT", range=bitcount_range, default=1),
configuration.integer(name="CV2_PULSE_BIT", range=bitcount_range, default=2),
configuration.integer(name="CV3_PULSE_BIT", range=bitcount_range, default=4),
]

def main(self):
Expand Down
Loading
Loading