| .DS_Store | ||
| .gitignore | ||
| calibrate.py | ||
| common.py | ||
| debug_osm_connection.py | ||
| osmdca_config.json | ||
| README.md | ||
| requirements.txt | ||
| spl_dca_rider.py | ||
| test_mixingstation_dca.py | ||
| test_osm_reader.py | ||
SPL DCA Rider
Slow DCA fader rider for dance recitals. Reads SPL from Open Sound Meter, calculates smooth DCA reduction using dual fast/slow control, and sends MIDI CC to Mixing Station (PreSonus StudioLive 64S) via macOS IAC virtual MIDI.
This is a slow SPL rider, not a hard limiter. Use a real limiter on your PA / system processor for safety.
Dual Control Concept
The rider uses two independent SPL measurements:
| Control | Metric | Behavior |
|---|---|---|
| Fast | Short-term (LCeq1s, LCFast, CFast) | Accepts SPL within a target ± range. Above the high bound, applies proportional reduction. |
| Slow | Long-term (LCslow, CSlow, LCeq) | Acts as a ceiling. Above the max threshold, applies proportional reduction. |
Both control outputs are combined (max of the two) and smoothed before being sent as MIDI CC. This prevents short peaks from triggering large moves while still catching sustained loudness.
Defaults
- Fast target: 95 dB
- Fast range: ±10 dB (effective bounds: 85–105 dB)
- Slow max: 85 dB
- Smoothing: 4 seconds (first-order low-pass)
The DCA only moves downward (never boosts above base CC). Recovery happens at the smoothing rate when SPL drops below limits.
Install
python3 -m pip install -r requirements.txt
Requires Python 3.11+.
macOS IAC Driver Setup
- Open Audio MIDI Setup (
Applications/Utilities). - Go to Window → Show MIDI Studio.
- Double-click the IAC Driver icon.
- Check "Device is online".
- Add at least one bus (e.g. "Bus 1").
- Click Apply.
The default port name is "IAC Driver Bus 1".
Mixing Station MIDI Mapping
- Open Mixing Station in offline mode (no console connected) for testing, or connect to your StudioLive 64S.
- Go to Settings → MIDI → Learn Mode.
- Select the DCA fader you want to control.
- Send the MIDI CC (use
test_mixingstation_dca.pysweep mode). - Mixing Station will learn the mapping.
- Save the profile.
Quick Start
OSM v1.5.2 listens on TCP port 49007 for remote control. Your OSM
source UUID is auto-read from autosave.osm.
Step 1: Verify OSM SPL Reading
python3 test_osm_reader.py --transport tcp-request --target -40
Output (4 updates/sec):
TIME SPL PEAK REDUCTION CC PATH
13:30:50 -36.5 -20.3 0.00 dB 100 * spl=level peak=measurementPeak
The SPL value is in dBFS (relative to digital full scale). To work in dBC, provide a calibration offset.
Step 2: Calibrate (optional)
If you know the relationship between dBFS and dBC (e.g. 0 dBFS = 95 dBC):
python3 test_osm_reader.py --transport tcp-request --target 95 --cal-offset 95
This adds +95 dB to every SPL reading: -40 dBFS → +55 dBC, 0 dBFS → 95 dBC.
Step 3: Verify MIDI DCA Movement
python3 test_mixingstation_dca.py \
--midi-port "IAC Driver Bus 1" \
--cc 20 \
--channel 1 \
--sweep \
--min-cc 80 \
--max-cc 100
Step 4: Run the Rider
python3 spl_dca_rider.py \
--fast-target 95 \
--fast-range 10 \
--slow-max 85 \
--smoothing-time 4 \
--cal-offset 95 \
--midi-port "IAC Driver Bus 1" \
--cc 20 \
--channel 1
Console output:
12:41:03 fast 96.2 path meters.LCeq1s | slow 84.7 path meters.LCslow | peak N/A | fastReq 0.0 | slowReq 0.0 | req 0.0 | applied 0.00 | CC 100 sent no
12:41:04 fast 106.0 path meters.LCeq1s | slow 86.2 path meters.LCslow | peak N/A | fastReq 1.0 | slowReq 1.2 | req 1.2 | applied 0.30 | CC 99 sent yes
Other Transports
WebSocket:
python3 spl_dca_rider.py \
--transport websocket \
--osm-url ws://127.0.0.1:8080 \
--fast-target 95 --fast-range 10 --slow-max 85 \
--midi-port "IAC Driver Bus 1" --cc 20 --channel 1
HTTP polling:
python3 spl_dca_rider.py \
--transport http-poll \
--osm-url http://127.0.0.1:8080/api \
--fast-target 95 --fast-range 10 --slow-max 85 \
--midi-port "IAC Driver Bus 1" --cc 20 --channel 1
SPL Path Discovery
OSM sends data in different JSON formats depending on transport. The script uses priority-based hint matching for both fast and slow SPL:
Fast SPL hints (in priority order): LCeq1s, LCFast, CFast, fast, LCeq, splC, spl
Slow SPL hints (in priority order): LCslow, CSlow, slow, LCeq, splC, spl
If the detected paths don't match your OSM data, use explicit overrides:
python3 spl_dca_rider.py \
--fast-path "meters.LCeq1s" \
--slow-path "meters.LCslow" \
--peak-path "meters.LCpeak"
Exact paths use dot-separated JSON key traversal (e.g. data.meters.C.Fast).
How It Works
- OSM provides SPL data via the selected transport (TCP, WebSocket, HTTP, or OSC).
- The script extracts fast and slow SPL values using hint-based or explicit path matching.
- Fast control: if SPL exceeds
fast_target + fast_range, reduction =(SPL - high) * fast_ratio. Within the range, no additional reduction. Below the low bound, slow recovery. - Slow control: if SPL exceeds
slow_max, reduction =(SPL - slow_max) * slow_ratio. Below the max, slow recovery. - Combined:
requested = max(fast_requested, slow_requested), clamped tomax_reduction. - Smoothing: first-order low-pass filter:
applied += (requested - applied) * (dt / tau). Attack and release can use different time constants. - MIDI:
cc_value = base_cc - round(applied * cc_per_db), clamped 0–127, never above base CC. Sent only on change.
Command Reference
spl_dca_rider.py
| Argument | Default | Description |
|---|---|---|
| OSM connection | ||
--transport |
tcp-request | Transport: tcp-request, websocket, http-poll, osc-udp |
--osm-url |
ws://127.0.0.1:8080 | WebSocket/HTTP URL |
--osm-host |
127.0.0.1 | TCP host |
--osm-port |
49007 | TCP port |
--source-uuid |
— | Source UUID (auto-read from autosave.osm) |
--poll-interval |
0.25 | Poll/sample interval (s) |
--osc-host |
127.0.0.1 | OSC listen host |
--osc-port |
9000 | OSC listen port |
| Calibration | ||
--cal-offset |
0 | dBFS→dBC offset |
| SPL path overrides | ||
--fast-path |
— | Exact JSON path for fast SPL |
--slow-path |
— | Exact JSON path for slow SPL |
--peak-path |
— | Exact JSON path for peak |
| Control | ||
--fast-target |
95 | Fast SPL target center |
--fast-range |
10 | Fast SPL range (± around target) |
--fast-low |
target-range | Fast low bound override |
--fast-high |
target+range | Fast high bound override |
--slow-max |
85 | Slow SPL maximum |
--fast-ratio |
1.0 | Proportional gain for fast |
--slow-ratio |
1.0 | Proportional gain for slow |
--max-reduction |
12 | Maximum total reduction (dB) |
| Smoothing | ||
--smoothing-time |
4.0 | Time constant (s), used for both attack & release |
--attack-time |
— | Attack time constant (overrides smoothing-time) |
--release-time |
— | Release time constant (overrides smoothing-time) |
| MIDI | ||
--midi-port |
IAC Driver Bus 1 | MIDI port name substring |
--cc |
20 | MIDI CC number |
--channel |
1 | MIDI channel (1–16) |
--base-cc |
100 | MIDI CC at 0 dB reduction |
--cc-per-db |
2.0 | MIDI CC steps per dB |
| Startup / Exit | ||
--no-initial-send |
off | Skip sending base CC on startup |
--return-to-base-on-exit |
off | Send base CC on Ctrl+C |
test_osm_reader.py
| Argument | Default | Description |
|---|---|---|
--target |
95 | Target SPL |
--transport |
tcp-request | tcp-request, multicast, websocket, http-poll, osc-udp |
--cal-offset |
0 | Calibration offset dBFS→dBC |
--osm-host |
127.0.0.1 | OSM TCP host |
--osm-port |
49007 | OSM TCP port |
--source-uuid |
— | OSM source UUID (auto-read) |
--osm-url |
ws://127.0.0.1:8080 | OSM URL (websocket/http-poll) |
--poll-interval |
0.5 | Poll interval (s) |
--osc-host |
127.0.0.1 | OSC listen host |
--osc-port |
9000 | OSC listen port |
--deadband |
0.7 | dB deadband around target |
--down-rate |
0.75 | Reduction increase rate (dB/s) |
--up-rate |
0.25 | Reduction decrease rate (dB/s) |
--max-reduction |
6 | Maximum reduction (dB) |
--base-cc |
100 | MIDI CC at 0 dB reduction |
--cc-per-db |
2.0 | MIDI CC steps per dB |
debug_osm_connection.py
| Argument | Default | Description |
|---|---|---|
--host |
127.0.0.1 | Host to scan |
--ports |
3000,5000,8000,8080,8081,9000,49007 | Comma-separated ports |
--paths |
(empty),/,/api,/status,/metrics,/ws,/socket | Comma-separated paths |
--timeout |
3 | Connection timeout (s) |
--url |
— | Test a single URL (auto-detects protocol) |
--listen |
— | Listen for OSM UDP multicast |
test_mixingstation_dca.py
| Argument | Default | Description |
|---|---|---|
--midi-port |
IAC Driver Bus 1 | MIDI port name substring |
--cc |
20 | MIDI CC number |
--channel |
1 | MIDI channel (1–16) |
--value |
— | Send single CC value and exit |
--sweep |
off | Sweep from min to max and back |
--min-cc |
0 | Sweep minimum |
--max-cc |
127 | Sweep maximum |
--step |
1 | Sweep step |
--delay |
0.2 | Sweep step delay (s) |
Troubleshooting OSM Connection
-
Ensure OSM Server mode is enabled. Check the Server checkbox in OSM's settings (enables TCP on port 49007).
-
Source UUID discovery. If
autosave.osmisn't found, provide the UUID manually:--source-uuid "{d762b054-...}". Find it in~/Library/Application Support/OpenSoundMeter/OpenSoundMeter/autosave.osm. -
macOS network permissions. Allow Python in System Settings → Privacy & Security → Local Network if prompted.
-
Test localhost first. Always confirm connectivity on
127.0.0.1before trying a LAN IP.
OSM Protocol Details
OSM v1.5.2 uses the following protocol on port 49007:
- TCP (command/response): 4-byte little-endian length prefix + raw
JSON request. Response is 4-byte LE length +
qCompress-ed JSON. - UDP multicast (live data): Plain JSON broadcasts to
239.255.42.42:49007. Not usable from the same machine (port conflict).
TCP request types:
requestChangedwith source UUID → source settings (includinglevel)requestDatawith source UUID → FFT / time domain data
Safety
- Always use a real limiter on your PA/system processor.
- This tool is designed for slow musical riding, not peak limiting.
- Test thoroughly during rehearsal before any performance.
- Have a bypass plan (e.g. Mixing Station scene recall).