Skip to content
steveclarkePublic

About

Unofficial Go library and CLI for PreSonus StudioLive mixers (UCNET protocol)

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ucmix

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.

What it does

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 clean

Install

Homebrew (macOS/Linux):

brew install steveclarke/tap/ucmix

This 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@latest

Quickstart

Find 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 scene

Every 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.

Noun commands (channel / mix / send)

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/aux1

Run 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.

Scenes, projects, and scope filters

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" --yes

store 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 off

The 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.

Connecting to a mixer

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 profile

Profiles. 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 switch

Profiles live in ~/.config/ucmix/config.yml (or $XDG_CONFIG_HOME/ucmix); ucmix config path prints the location and ucmix config edit opens it.

Board as code

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      # ms
ucmix 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 file

apply 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: Studio Pro over UCNET

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.xml and 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.

Getting started with ucdaw

For Studio Pro users who are not developers. You need a Mac, Studio Pro 8 and Claude Code.

  1. Install with Homebrew:

    brew install steveclarke/tap/ucmix

    This puts ucmix and ucdaw on your Mac. You only use ucdaw.

  2. 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.

  3. Open a song in Studio Pro. Run ucdaw on the same Mac, or on another computer on the same network.

  4. Check the connection:

    ucdaw discover

    It prints your Studio Pro. If it prints nothing, recheck step 2.

  5. Add ucdaw to Claude Code once:

    claude mcp add --scope user studio-pro-ucnet -- ucdaw mcp
  6. Optional: run ucdaw serve in a Terminal window and leave it open. Answers come back in a fraction of a second instead of after a reconnect.

  7. 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" mcp

The 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).

Library

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 read

Protocol

The UCNET wire protocol is documented in PROTOCOL.md: packet framing, message codes, value encodings, tapers, and the JSON scene/preset commands.

Calibration status

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.

Prior art

License

MIT — see LICENSE.

About

Unofficial Go library and CLI for PreSonus StudioLive mixers (UCNET protocol)

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages