Unofficial Go library and CLI for controlling PreSonus StudioLive Series III mixers over the UCNET network protocol.
Unofficial. ucmix is not affiliated with, authorized, or endorsed by PreSonus Audio Electronics, Inc. "PreSonus", "StudioLive", "UC Surface", and "UCNET" are trademarks of their respective owners. This project communicates with the mixer's network protocol for interoperability only. Field names and encodings are reverse-engineered and can differ across firmware revisions.
Read and write a StudioLive mixer's state programmatically — channel names,
input patch, 48V, high-pass filters, monitor mixes, sends, limiters, reverb, and
scene recall/store. The headline feature is board as code: verify and
apply an entire mixer configuration from a declarative YAML file instead of
tapping it into UC Surface by hand.
$ ucmix apply front-of-house.yml
applying 47 settings…
applied 47 settings; verify cleanHomebrew (macOS/Linux):
brew install steveclarke/tap/ucmixThis installs both ucmix (StudioLive mixers) and ucdaw (Studio Pro).
Debian/RPM packages are attached to each release.
From source (Go 1.26+):
go install github.com/steveclarke/ucmix/cmd/ucmix@latest
go install github.com/steveclarke/ucmix/cmd/ucdaw@latestFind a mixer and save it, then read and write state:
ucmix setup # scan the LAN, pick a board, save it as a profile
# (or: export UCMIX_HOST=192.168.1.50)
ucmix dump line/ch1 # every ch1 setting, humanized
ucmix get line/ch1/volume # -6 dB
ucmix set line/ch1/volume -3dB # faders speak dB
ucmix set line/ch1/username "Kick" # names, icons
ucmix set line/ch1/48v on # phantom power
ucmix set line/ch1/48v=on line/ch1/mute=off # many writes, one connection
ucmix set -f strip.txt # a `path value` per line, one connection
ucmix recall "Main Live" "Opening" # recall a stored sceneEvery command accepts --json for machine-readable output and --no-color for
plain text. Values use human units: -6dB, 100Hz, on/off, a physical
input number for adc_src, a hex string for color. Paths use slashes
(line/ch1/volume) or dots (line.ch1.volume).
A color is written as 6 or 8 hex digits, with or without a leading #, and is
always read back as 8 lowercase RGBA hex digits (4ed2ff → 4ed2ffff).
That one rendering is what get, dump, dump --as-config, and verify all
print, so a color compares equal to itself however it was written.
Writes are read back. set and the noun commands re-read every path they wrote
on a fresh connection and report what the board holds, so a value the mixer
clamps or rejects is named rather than reported as a success:
✗ set line/ch3/filter/hpf: wrote 2000, board holds 1
That exits 1. The read-back costs one extra connection per command, not per
write; --no-verify skips it and reports on send. Write commands take their
values verbatim (so -6dB is not read as a flag), so --no-verify goes before
the path — ucmix set --no-verify line/ch1/mute on.
For the common actions there are noun-grouped shortcuts — a thin veneer over
set that needs no path knowledge. Each verb builds one path and writes one
value; anything not covered stays ucmix set <path> <value>.
ucmix channel 3 name "Drums" # line/ch3/username
ucmix channel 3 patch 5 # line/ch3/adc_src (physical input)
ucmix channel 3 phantom on # line/ch3/48v
ucmix channel 3 fader -6dB # line/ch3/volume
ucmix channel 3 mute on # line/ch3/mute
ucmix channel 3 color blue # line/ch3/color (name or hex)
ucmix channel 3 icon drums # line/ch3/iconid (name or id)
ucmix channel 3 hpf 100Hz # line/ch3/filter/hpf (24 Hz - 1 kHz)
ucmix mix 1 name "Steve" # aux/ch1/username
ucmix mix Steve fader -6dB # a mix answers to its name or its number
ucmix mix 1 stereo on # aux/ch1/link
ucmix mix 1 limiter on --threshold -6 --release 400
ucmix send 3 1 -6dB # channel 3 into mix 1: line/ch3/aux1Run ucmix channel --help or ucmix mix --help for the full verb list. Color
names (blue, red, green, …) and icon names (drums, bass, vocal)
resolve to wire values; a hex color or a raw icon id still works.
The mixer stores state in two layers. A scene is the mix — fat channel, mutes, mix levels, FX, DCA and mute groups. A project is the setup under it — input source and patching, AVB/SD/USB routing, flex mode, GEQ, solo. Scenes live inside a project.
ucmix ls projects # projects on the board
ucmix ls scenes "135 Main Live" # scenes in one project (title or slot name)
ucmix project ls # projects, marking the loaded one
ucmix store "135 Main Live" "Opening" # store the current state as a new scene
ucmix recall "135 Main Live" "Opening" # recall a stored scene
ucmix rename "135 Main Live" "Opening" "Opening Set"
ucmix delete "135 Main Live" "Soundcheck" --yesstore allocates the next free scene slot and refuses to overwrite an existing
scene without --replace. store, recall and delete report success only
after the board acknowledges the write.
Storing and recalling a project as a unit is not implemented: the request UC Surface sends for it has not been captured, and the preset layer is not a place to guess.
Scope filters decide what a store, recall or reset actually touches — the
blue Project-Filter and Scene-Filter tiles in UC Surface. A tile is either
included in the operation or excluded from it, and a board ships with the scene
filter's 48v tile excluded, which is why a scene recall leaves phantom power
alone.
ucmix filters ls # every tile in every group
ucmix filters ls scene # one group: scene, advanced, project
ucmix filters set scene 48v on # include phantom power in store/recall
ucmix filters set project inputpatching offThe three groups are scene (the Scene Filter tiles), advanced (Advanced
Scene Filter) and project (Project Filter). Tile names are the board's own key
names, so a tile always names the parameter it writes; - and _ are
interchangeable. filters set is read back on a fresh connection like every
other write, so it reports the value the board holds.
ucmix resolves which mixer to talk to in this order: the --host flag, a named
--profile, the UCMIX_HOST environment variable, the current saved profile,
and a legacy host: in the config file. The UCNET control port 53000 is
assumed when none is given.
Discovery and setup. StudioLive mixers announce themselves on the LAN, so ucmix can find them:
ucmix discover # list mixers on the network
ucmix setup # find one, name it, save it as a profileProfiles. Save multiple boards and switch between them (front-of-house, monitors, a rehearsal rig):
ucmix profile add foh --host 192.168.1.50
ucmix profile add monitor --host 192.168.1.51 --use
ucmix profile ls # list, * marks the current one
ucmix profile use foh # switch the current profile
ucmix -p monitor dump line/ch1 # use a profile for one command, no switchProfiles live in ~/.config/ucmix/config.yml (or $XDG_CONFIG_HOME/ucmix);
ucmix config path prints the location and ucmix config edit opens it.
Describe the board in YAML, then verify or apply it. Only the fields you declare participate — the config is a statement of intent, not a full dump.
version: 1
channels:
1:
name: Kick
icon: drums/drumset
patch: 1 # physical input
phantom: true # 48V
hpf: 80 # Hz, 24-1000 (or `off`)
fader: -6 # dB
main: true # assign to main L/R
sends:
Monitor 1: -3 # send level in dB, by mix name
mixes:
1:
name: Monitor 1
stereo: true
fader: -6
limiter:
"on": true
threshold: -12 # dB
release: 400 # msucmix verify board.yml # diff the live board against the file
# exit 0 = clean, 1 = drift, 2 = error
ucmix apply board.yml # write every declared setting, then verify
ucmix apply board.yml --dry-run # print the ordered write plan, change nothing
ucmix apply board.yml --reset # factory-reset first (destructive; --yes to skip prompt)
ucmix dump --as-config # emit the live board as a config fileapply writes on one connection and verifies on a fresh one — the mixer's
in-session read-back returns unparsed values, so verification requires a new
snapshot.
ucdaw (in cmd/ucdaw, released with ucmix) drives Fender Studio Pro 8 over the
same UCNET transport, speaking as the Studio Pro Remote app. It reads every
change back from Studio Pro before it answers, keeps a live model of the song
from Studio Pro's own updates, and runs any of Studio Pro's own commands.
Unofficial. Not affiliated with or endorsed by Fender or PreSonus. "Studio Pro" and "Studio One" are trademarks of their owners. ucdaw reads Studio Pro's installed
dawremote.xmland plug-in list at runtime and does not redistribute them.
$ ucdaw serve & # optional: holds one session, ~40 ms answers
$ ucdaw song new --tempo 72 --click on
Started a new song (2026-10-12 Stephen Clarke(2)) at 72 bpm, click on.
$ ucdaw track add --name Guitar --insert "Guitar Rig 7"
Added mono audio track Guitar; added Guitar Rig 7 to Guitar (insert 1).
$ ucdaw instrument add "Syntronik 2" --name Pad
Added instrument track Pad with Syntronik 2.
$ ucdaw section add Verse Chorus Bridge
Added sections Verse, Chorus and Bridge, 8 bars each; sections now: Verse bar 1, Chorus bar 9, Bridge bar 17.
$ ucdaw section repeat Chorus after Bridge
Repeated Chorus after Bridge, at bar 25.
$ ucdaw fader Pad --down 3
Pad at -3 dB (was 0 dB).
$ ucdaw fader Pad 0
Pad at 0 dB (was -3 dB).
$ ucdaw import ~/Music/stems
Imported 4 stems onto new tracks at bar 1: Kick (mono), Bass DI (stereo), Pad (stereo) and Lead Vox (mono). Skipped notes.txt (not audio). Tracks read back; Studio Pro does not report clips to remotes, so placement is not read back (Studio Pro drew each file's waveform, so it read every file).
$ ucdaw analyze Bass Loops
Measured 2 stems against a -18 dBFS average target:
Bass: average -46.5 dBFS over 4:07 of 4:45, peak -30.5 dBFS, -45.5 LUFS; trim +24 dB, as far as the input trim goes (it stops at +24 dB): reaching -18 dBFS needs +28.5 dB, so it stays 4.5 dB short.
Loops is silent: an empty export (peak -62 dBFS over 4:45). Re-export it before mixing.
$ ucdaw trim Bass 20
Bass trim +20 dB (was 0 dB).
$ ucdaw gainstage
Gain-staged 7 tracks to a -18 dBFS average; faders untouched:
Drums: trim 0 → +5.7 dB (stem averages -23.7 dBFS).
…Commands: discover, state [prefix], state record <file>, song new [--tempo bpm] [--click on|off],
transport play|stop|rtz|loop, click, tempo, track add, insert,
instrument add, section list|add|duplicate|repeat|move|delete|rename|goto|play,
audio fade-out|fade-in|split|trim-start|trim-end|crossfade|quantize,
fader, trim, analyze [file|folder|track]... [--target dBFS], gainstage [track]... [--target dBFS],
import <file|folder>... [--onto track] [--at bar], favorite set|list|remove, plugins, presets, param get|set,
params, command list|run [--arg name=value], serve, mcp.
Every command takes --json.
song new runs File > New and presses OK in Studio Pro's New window (matched by
its label), so the song gets that window's current template; the open song stays
open in its own tab and nothing is saved. Faders read and take dB on Studio Pro's own fader curve (measured against its
console). Audio edits are sent but not read back: Studio Pro does not report
audio events to remote apps. import puts each WAV or AIFF stem on its own new
mono or stereo track at bar 1, named from the file ("01_Kick_Final.wav" is Kick),
one file at a time; it hands files to Studio Pro with open, so it works only with
Studio Pro on the same Mac. Favorites (favorite set "clean guitar" "Guitar Rig 7" --preset Clean) are kept in the user config folder, readable only by the user.
Mixing: analyze measures stems (files and folders on this Mac without Studio Pro,
or the stem on a named track, or every audio track): average level (RMS over active
audio, gaps below -60 dBFS left out), peak, integrated loudness (ITU-R BS.1770-4,
matches ffmpeg's ebur128 to 0.1 LU), active time, empty exports, clipping, and the
input trim that brings each to the -18 dBFS target. It streams WAV/RF64/AIFF/AIFC
(8/16/24/32-bit integer, 32/64-bit float): a 300 MB stem takes about 1.3 s in 15 MB
of memory. trim sets a channel's input gain (-24 to +24 dB, before plug-ins and
fader); gainstage sets every audio track's trim to the target and leaves faders,
mutes and pans alone. Level arguments may be negative (ucdaw fader Drums -3).
Which file is on which track comes from ucdaw's own import log (imports.json in
the user config folder), or from the song's waveform cache plus Spotlight.
For Studio Pro users who are not developers. You need a Mac, Studio Pro 8 and Claude Code.
-
Install with Homebrew:
brew install steveclarke/tap/ucmix
This puts
ucmixanducdawon your Mac. You only useucdaw. -
In Studio Pro, open the settings and turn on Remote Control Support ("Allow remote control apps to discover this DAW"). This is the same switch the Studio Pro Remote app needs.
-
Open a song in Studio Pro. Run ucdaw on the same Mac, or on another computer on the same network.
-
Check the connection:
ucdaw discover
It prints your Studio Pro. If it prints nothing, recheck step 2.
-
Add ucdaw to Claude Code once:
claude mcp add --scope user studio-pro-ucnet -- ucdaw mcp
-
Optional: run
ucdaw servein a Terminal window and leave it open. Answers come back in a fraction of a second instead of after a reconnect. -
Start Claude Code and talk to it. Good first things to say:
- "What is in my song?"
- "Start a new song at 72 BPM with the click on."
- "Add a guitar track with Guitar Rig 7, and a pad track with Syntronik."
- "Add Verse, Chorus and Bridge, then repeat the chorus after the bridge."
- "Bring the pad down 3 dB."
- "Import the stems in my Music/stems folder and gain-stage them."
- "Undo that."
Claude shows what Studio Pro reported back for every change. Audio edits (fades, splits) are sent but cannot be confirmed, because Studio Pro does not report audio clips to remote apps; Claude says so.
For Claude Code from source:
$ go install ./cmd/ucdaw
$ claude mcp add --scope user studio-pro-ucnet -- "$(go env GOPATH)/bin/ucdaw" mcpThe agent skill is in skills/ucdaw/SKILL.md; the mix engineer's workflow is in
skills/ucdaw/mixing.md (and, in brief, in the MCP server's instructions).
import ucmix "github.com/steveclarke/ucmix"
client, err := ucmix.Connect(ctx, "mixer.local:53000")
if err != nil {
log.Fatal(err)
}
defer client.Close()
client.SetFaderDB(ctx, ucmix.Line, 1, -6) // −6 dB on channel 1
client.SetName(ctx, ucmix.Line, 1, "Kick")
client.Set48V(ctx, ucmix.Line, 1, true)
level, _ := client.Get("line/ch1/volume") // humanized readThe UCNET wire protocol is documented in PROTOCOL.md: packet framing, message codes, value encodings, tapers, and the JSON scene/preset commands.
The fader, limiter-threshold, input-patch, and high-pass-filter conversions are
calibrated against a real StudioLive 32R. The high-pass sweeps 24 Hz to 1 kHz
logarithmically, pos = ln(Hz / 24) / ln(1000 / 24), with position 0 the bottom
of the sweep and how the board stores a filter that is off.
The limiter-release and reverb-type curves are not yet fully characterized —
those fields round-trip on the wire but their human-unit conversions are
provisional. A raw: escape hatch in the config accepts wire values directly for
anything uncalibrated.
Humanized reads round to one decimal. A wire position is a 32-bit float, so
inverting one lands a hair off the number that was dialed; get --raw shows the
exact position.
- featherbear/presonus-studiolive-api — the Node/TypeScript reference implementation this work builds on.
- samovesel/PreSonus-StudioLive-API and martinspinler/osclive.
MIT — see LICENSE.