SolarOS

SolarOS 4.17.7 manual · hardware

Continuous controls

A control maps a measurement or script value to an app setting or MIDI controller. For example, a potentiometer can change Synth's filter cutoff. You can calibrate its range, smooth noisy readings, or reverse its direction.

List the scalar streams that exist on the running board:

stream

For a potentiometer whose wiper is connected to an ADC-capable expansion pin, create a control using its measured millivolt endpoints. This example uses adc1 and maps 0 through 3300 mV to the full control range:

control create cutoff adc1 0 3300 smooth=40 deadband=8

smooth= is an exponential smoothing time in milliseconds. deadband= uses the source unit, so it is millivolts for an ADC stream. Add invert when the physical direction is reversed. Use the actual endpoint readings when the potentiometer does not reach 0 or 3300 mV.

Native application parameters

Parameters are named settings published by running native apps, with defined ranges and units. List the currently available settings:

control parameters

The Synth app publishes parameters such as synth.filter.cutoff. Bind the potentiometer to it and start the sampler:

control bind cutoff parameter synth.filter.cutoff pickup=on
job start controls
synth

Pickup is soft takeover. The hardware knob does not change the parameter until it reaches or crosses the app's current value, preventing a sudden jump when a preset loads or the app resumes. The binding remains configured while its app is suspended or stopped and applies again when the parameter path returns.

For direct inspection or testing:

control parameter get synth.filter.cutoff
control parameter set synth.filter.cutoff 1200

MIDI CC

Control to MIDI CC

A control can drive a MIDI controller after the MIDI bus and worker are running:

expansion bus create midi midi0 tx=gpio2 rx=gpio3
job start midi midi0
control bind cutoff midi 1 74
job start controls

The normalized 16-bit control value is scaled to the MIDI range 0..127. Channel numbers are 1..16; controller numbers are 0..127.

MIDI CC to application parameter

Expose an incoming MIDI controller as a scalar stream, then use the same control-to-parameter path as an ADC or other measurement:

expansion bus create midi midi0 tx=gpio2 rx=gpio3
job start midi midi0
midi monitor
midi stream add 1 74
control create cutoff midi.cc.1.74 0 127
control bind cutoff parameter synth.filter.cutoff pickup=off
job start controls
synth

Move a controller while midi monitor is running to discover its channel and controller number. It prints mapping-oriented lines such as:

CC: 1 74 64
KEY: 1 60 100
KEY: 1 60 0

The fields are channel, controller/note, and value/velocity. Stop the monitor with the app-exit key, Esc, or q, then create the matching stream.

midi stream add <channel> <controller> registers an exact scalar stream named midi.cc.<channel>.<controller>. It reports 0..127 and retains the latest matching value while the MIDI job is running. It is non-consuming, so MIDI subscribers such as Synth still receive the original message.

Python and Lua can perform the same management with solaros.midi.streams(), stream_add(), stream_remove(), and stream_clear(). Their solaros.midi.read() API uses its own non-consuming subscription, so a script can observe messages without stealing them from Synth or another subscriber.

You can configure up to 16 MIDI CC streams. A new stream reports waiting until its first matching message. It returns to waiting when the MIDI job stops or restarts.

Inspect or remove the definitions with:

midi stream list
midi stream remove 1 74
midi stream clear

MIDI CC stream definitions are volatile. Restore midi stream add commands from the startup script selected by setterm startup, together with the related control and job commands.

Manual and script controls

Use manual instead of a stream when a Python or Lua program supplies the value. Scripts can either use an existing shell configuration or create the control and its typed binding directly:

control create expression manual 0 1
control set expression 32768

Python and Lua expose values as floating-point numbers from 0.0 through 1.0:

import solaros

solaros.controls.set("expression", 0.5)
print(solaros.controls.get("expression"))
local solaros = require("solaros")

solaros.controls.set("expression", 0.5)
print(solaros.controls.get("expression"))

The equivalent complete Python setup is:

solaros.controls.create("expression")
solaros.controls.bind_parameter(
    "expression", "synth.filter.resonance", False
)
solaros.jobs.start("controls")

Lua uses the same function names and positional arguments. Both runtimes also provide controls.delete(), clear(), bindings(), bind_midi(), and unbind(). The solaros.parameters table lists dynamic native parameters and gets or sets their values without going through a control.

Inspection and removal

control list
control bindings
control get cutoff
control unbind cutoff
control delete cutoff
control clear
job stop controls

control list reports normalized and raw values, sample and update counts, and the last source error. control bindings reports target state, pickup state, application counts, and the last target error. control unbind <name> removes all parameter and MIDI targets owned by that control. Control and binding definitions last until reboot. Run setterm startup to find the script in which to save their creation, binding, and job-start commands.

Quick reference

Controls normalize scalar streams to 0..65535, apply optional smoothing, deadband, and inversion, and fan out to typed targets. Native app targets can use soft takeover and survive temporary parameter absence. MIDI targets emit CC values from 0..127. Python and Lua use normalized values from 0.0 to 1.0 and expose the complete configuration, binding, and dynamic-parameter management surface.