Compare commits

...

85 Commits
v0.6.0 ... main

Author SHA1 Message Date
github-actions[bot]
9564510c9e chore: auto-update Go modules 2026-02-23 00:04:45 +00:00
229bc4a15f rewrite version func 2026-02-16 01:29:25 +00:00
github-actions[bot]
5057029267 chore: auto-update Go modules 2026-02-16 00:04:44 +00:00
4ed567e464 enable errcheck linter 2026-02-15 15:12:29 +00:00
c8cfef858c enable goconst linter
add package level constants for true/false str values
2026-02-15 14:58:59 +00:00
23285e6e50 add godot linter 2026-02-15 12:47:35 +00:00
b6b64781ed add staticcheck linter 2026-02-15 12:46:05 +00:00
9bc4d21739 enable unparam linter 2026-02-15 12:44:52 +00:00
7a0d5c56e5 enable revive linter
- add package comments
- fix redeclaration of built-in min/max
- add comments to exported types
- remove unused parameters
2026-02-15 12:22:22 +00:00
ac8f635263 litn fixes 2026-02-15 12:00:00 +00:00
19fda4daec migrate golangci-lint config to version 2
update golangci action
2026-02-15 11:59:50 +00:00
58866b794b implement info command
upd help mds
2026-02-11 21:47:56 +00:00
32a09db1a4 unexport client 2026-02-11 21:25:40 +00:00
106f896c45 upd strip send docstrings/help messages 2026-02-11 09:26:23 +00:00
6615eec466 mark eq/band index args as optional. this lets us differentiate between not set and zero.
add Validate method to SnapshotCmdGroup
2026-02-10 14:16:35 +00:00
942d1b18bd keep validation failure messages consistent.
they now print the incorrect value passed.
2026-02-10 01:24:55 +00:00
2e1c28c909 Validate methods should satisfy the interface Validate() error. This commit fixes that bug. 2026-02-10 01:17:35 +00:00
cf470181a1 implement dca commands:
- DCA added to Client struct
- dca command added to both CLIs

help mds updated
2026-02-10 01:16:44 +00:00
873ff87429 add pre-commit config 2026-02-09 13:59:30 +00:00
376f3109ef add separators 2026-02-09 11:59:35 +00:00
9484f8e4f3 upd Use section in README 2026-02-09 11:56:49 +00:00
a936a7e3db add help mds 2026-02-09 11:56:27 +00:00
github-actions[bot]
a0153f6b84 chore: auto-update Go modules 2026-02-09 00:05:05 +00:00
886c078f67 upd taskfiles 2026-02-08 06:27:28 +00:00
07ac73fbe4 fix dosctring 2026-02-08 05:53:22 +00:00
23422f9641 DRY up the factory methods
use optional functions to set address functions
2026-02-07 14:23:46 +00:00
3c47d12719 we no longer need to pass in the kind
remove option function WithKind
2026-02-07 13:58:37 +00:00
8a452c83b9 pass base addresses to factory methods 2026-02-07 09:24:41 +00:00
abfb1bf08d implement x32 matrix commands 2026-02-07 08:49:23 +00:00
0dc097c49e add example x32 .envrc 2026-02-07 04:48:07 +00:00
bc41de5668 upd goreleaser config 2026-02-07 04:38:44 +00:00
5aa833e2ca add separate taskfiles for x32,xair builds 2026-02-07 04:24:58 +00:00
ba0b0eaee2 update README 2026-02-07 03:54:48 +00:00
c19c7f278b remove --kind flag from the root command and split the package into two separate CLIs.
Each CLI uses the correct concrete client and it avoids mixing help output

update goreleaser config
2026-02-07 03:54:36 +00:00
904e6c02d8 implement Matrix type
add comp,eq factory methods for Matrix type

update address maps
2026-02-07 03:51:47 +00:00
65d20408b1 implement two separate concrete clients
add one factory function for each type
2026-02-07 03:49:44 +00:00
3fd6c52cad add .envrc to .gitignore 2026-02-07 03:26:49 +00:00
f3ad0de7db fix .envrc example 2026-02-07 03:26:17 +00:00
3ed0560d08 seems to fix bug when passing arg.
Perhaps I'm just misunderstading the Kong library here, not sure.
2026-02-07 01:39:20 +00:00
6a4577118f add --timeout to flags 2026-02-07 01:34:34 +00:00
1597f8f352 main comp/eq commands implemented
factory methods unexported

lr.go renamed to main.go
main.go renamed to cli.go
2026-02-07 01:28:59 +00:00
e80f17d211 upd .gitignore 2026-02-07 01:26:10 +00:00
40c7d03ff1 upd README 2026-02-07 01:25:06 +00:00
561faa0e95 fixes bug with x32 headamp address map 2026-02-06 17:53:52 +00:00
ce398e9a09 add snapshot example to README 2026-02-06 17:49:41 +00:00
b8b637b4b7 upd snapshot subcommand help output 2026-02-06 17:47:21 +00:00
52d51a7094 reword 2026-02-06 17:45:30 +00:00
09a4e9fbfc upd Configuration in README 2026-02-06 17:45:12 +00:00
04f7319776 add loglevel to Configuration section 2026-02-06 17:37:57 +00:00
0c67887ad7 upd README 2026-02-06 17:31:50 +00:00
4db173154b snapshot commands implemented 2026-02-06 17:31:41 +00:00
27922d41bb --loglevel flag added to the root command 2026-02-06 17:31:36 +00:00
e21fd59564 reword 2026-02-06 15:30:18 +00:00
2d829e28e1 upd README 2026-02-06 11:28:52 +00:00
3f8861ded2 timeout now a flag on the root command.
It applies to all messages sent

new option function WithTimeout added
2026-02-06 11:28:40 +00:00
1ad214ba4a add validation to --kind flag 2026-02-06 00:52:12 +00:00
66da965edd fix default value for kind 2026-02-06 00:47:34 +00:00
079a0b240d ensure all optional args are marked as optional 2026-02-06 00:41:26 +00:00
fe711f79f1 Main struct now uses address map 2026-02-06 00:41:06 +00:00
c94ac62cb8 upd address maps 2026-02-06 00:40:52 +00:00
5933b25114 move parser interface into engine.go
add snapshot field to Client
2026-02-06 00:40:42 +00:00
fa704832d5 reword 2026-02-05 21:44:39 +00:00
69925021af spell fix 2026-02-05 15:43:19 +00:00
e092ed3c4e add Configuration section to README 2026-02-05 15:42:39 +00:00
d87bc2678c add short Config flags 2026-02-05 15:42:07 +00:00
c3221f3df5 reduce default timeout to 100ms
add env var for raw timeout
2026-02-05 15:13:33 +00:00
dc733ba500 add macos target to Taskfile 2026-02-05 14:20:03 +00:00
2a5e0e022f upd README 2026-02-05 13:42:57 +00:00
663a863386 fix main fadein/fadeout help output 2026-02-05 13:23:36 +00:00
70889c47bc upd README 2026-02-05 13:20:20 +00:00
2ad9f2fd6a add docstrings
upd help messages
2026-02-05 13:20:05 +00:00
c20aa82e3b remove redundant util function 2026-02-05 04:15:21 +00:00
1c5a75ffe1 add bus eq band 3 gain example 2026-02-05 04:06:08 +00:00
b075515654 upd README 2026-02-05 04:02:01 +00:00
139e092937 raw doesn't need to be a command group
fix `nil pointer deference` for set commands
2026-02-05 04:01:34 +00:00
8539685770 upd README 2026-02-05 03:55:08 +00:00
8fc5a55eff fixes bug where strip/bus eq on would try to validate the band number. 2026-02-05 03:54:53 +00:00
128f0c1df6 migrate CLI component to Kong 2026-02-05 03:36:22 +00:00
49cf3ff49e upd Name methods 2026-02-04 11:18:17 +00:00
66ab937296 implement Snapshot on Client struct 2026-02-04 11:15:52 +00:00
6f995397a1 fix example 2026-02-04 10:46:20 +00:00
e6d6ac77f6 upd long desc 2026-02-04 10:45:26 +00:00
b39dc86236 add raw command for sending raw OSC messages 2026-02-04 10:44:06 +00:00
056ebc9116 add strip comp mode + strip gate subcommands 2026-02-04 07:31:00 +00:00
ce955deb38 add bus comp mode 2026-02-04 07:30:44 +00:00
59 changed files with 7810 additions and 3072 deletions

View File

@ -23,7 +23,8 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.24' go-version: '1.24'
- name: Install golangci-lint - name: golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest uses: golangci/golangci-lint-action@v9
- name: Run golangci-lint with:
run: golangci-lint run ./... version: v2.6.0
args: --config .golangci.yml

3
.gitignore vendored
View File

@ -30,9 +30,12 @@ go.work.sum
# env file # env file
.env .env
.envrc
# Editor/IDE # Editor/IDE
# .idea/ # .idea/
# .vscode/ # .vscode/
# End of ignr # End of ignr
update_readme.py

View File

@ -1,54 +1,142 @@
version: '2'
run: run:
# timeout for analysis, e.g. 30s, 3m, default is 1m
timeout: 3m timeout: 3m
# exclude test files
tests: true tests: true
go: '1.24'
linters: linters:
# Set to true runs only fast linters. disable: [dupl]
# Good option for 'lint on save', pre-commit hook or CI.
fast: true
disable-all: true
enable: enable:
- gosimple # Default enabled linters
- govet - errcheck # Check for unchecked errors
- ineffassign - govet # Go's built-in vetting tool
- staticcheck - ineffassign # Detect ineffectual assignments
- unused - staticcheck # Advanced static analysis
- gofmt - unused # Check for unused code
- gofumpt # Additional useful linters
- misspell - misspell # Detect common misspellings
- unparam - unparam # Check for unused function parameters
- gosec - gosec # Security checks
- asciicheck - asciicheck # Check for non-ASCII characters
- errname - errname # Check error variable names
- gci - godot # Check for missing periods in comments
- godot - revive # Highly configurable linter for style and correctness
- goimports - gocritic # Detect code issues and suggest improvements
- revive - gocyclo # Check for cyclomatic complexity
- dupl # Check for code duplication
linters-settings: - predeclared # Check for shadowing of predeclared identifiers
gofmt: - copyloopvar # Check for loop variable capture in goroutines
rewrite-rules: - errorlint # Check for common mistakes in error handling
- pattern: 'interface{}' - goconst # Check for repeated strings that could be constants
replacement: 'any' - gosmopolitan # Check for non-portable code
- pattern: 'a[b:len(a)]'
replacement: 'a[b:]'
settings:
misspell: misspell:
locale: UK locale: UK
errcheck: errcheck:
check-type-assertions: true check-type-assertions: true
check-blank: true
exclude-functions:
- fmt.Fprintf
- fmt.Fprintln
- fmt.Printf
- fmt.Println
- fmt.Errorf
revive:
severity: warning
rules:
# Code quality and style
- name: exported
arguments:
- 'checkPrivateReceivers'
- 'sayRepetitiveInsteadOfStutters'
- name: var-naming
- name: package-comments
- name: range-val-in-closure
- name: time-naming
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: empty-block
- name: error-return
- name: error-strings
- name: error-naming
- name: if-return
- name: increment-decrement
- name: indent-error-flow
- name: receiver-naming
- name: redefines-builtin-id
- name: superfluous-else
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: blank-imports
- name: range
# Disabled rules (can be enabled if needed)
# - name: line-length-limit
# arguments: [120]
# - name: function-length
# arguments: [50, 0]
# - name: cyclomatic
# arguments: [10]
gosec:
excludes:
- G104 # Duplicated errcheck checks
- G115 # integer overflow conversion int -> uint32
exclusions:
warn-unused: false
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
paths:
- vendor
# Formatters configuration
formatters:
# Enable specific formatters
enable:
- gofumpt # Stricter gofmt alternative
- goimports # Organizes imports
- gci # Controls import order/grouping
- golines # Enforces line length
# Formatter-specific settings
settings:
goimports:
local-prefixes: [github.com/onyx-and-iris/xair-cli]
gci:
# Define import sections order
sections:
- standard # Standard library
- default # Everything else
- prefix(github.com/onyx-and-iris/xair-cli) # Current module
gofumpt:
extra-rules: true # Enable additional formatting rules
exclusions:
warn-unused: true
paths:
- vendor
issues: issues:
max-same-issues: 0 # Limit the number of same issues reported to avoid spam
max-issues-per-linter: 0 max-same-issues: 50
exclude-use-default: false
exclude: # Limit the number of issues per linter to keep output manageable
# gosec: Duplicated errcheck checks max-issues-per-linter: 100
- G104
# gosec: integer overflow conversion int -> uint32
- G115

View File

@ -8,6 +8,9 @@
version: 2 version: 2
env:
- CGO_ENABLED=0
before: before:
hooks: hooks:
# You may remove this if you don't use go modules. # You may remove this if you don't use go modules.
@ -16,8 +19,19 @@ before:
- go generate ./... - go generate ./...
builds: builds:
- env: - main: ./cmd/x32-cli
- CGO_ENABLED=0 id: 'x32-cli'
binary: x32-cli
goos:
- linux
- windows
- darwin
goarch:
- amd64
- main: ./cmd/xair-cli
id: 'xair-cli'
binary: xair-cli
goos: goos:
- linux - linux
- windows - windows
@ -26,10 +40,28 @@ builds:
- amd64 - amd64
archives: archives:
- formats: ['tar.gz'] - id: 'x32-cli-archive'
builds: ['x32-cli']
formats: ['tar.gz']
# this name template makes the OS and Arch compatible with the results of `uname`. # this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >- name_template: >-
{{ .ProjectName }}_ x32-cli_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: ['zip']
- id: 'xair-cli-archive'
builds: ['xair-cli']
formats: ['tar.gz']
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
xair-cli_
{{- title .Os }}_ {{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64 {{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386 {{- else if eq .Arch "386" }}i386

8
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,8 @@
repos:
- repo: local
hooks:
- id: update-readme
name: update-readme
entry: python update_readme.py
language: python
pass_filenames: false

116
README.md
View File

@ -2,39 +2,66 @@
### Installation ### Installation
*xair-cli*
```console ```console
go install github.com/onyx-and-iris/xair-cli@latest go install github.com/onyx-and-iris/xair-cli/cmd/xair-cli@latest
``` ```
*x32-cli*
```console
go install github.com/onyx-and-iris/xair-cli/cmd/x32-cli@latest
```
### Configuration
#### Flags
- --host/-H: Host of the mixer.
- --port/-P: Port of the mixer.
- --timeout/-T: Timeout for OSC operations.
- --loglevel/-L: The application's logging verbosity.
Pass `--host` and any other configuration as flags on the root commmand:
```console
xair-cli --host mixer.local --timeout 50ms --help
```
#### Environment Variables
Or you may load them from your environment:
Example xair .envrc:
```bash
#!/usr/bin/env bash
export XAIR_CLI_HOST=mixer.local
export XAIR_CLI_PORT=10024
export XAIR_CLI_TIMEOUT=100ms
export XAIR_CLI_LOGLEVEL=warn
```
Example x32 .envrc:
```bash
#!/usr/bin/env bash
export X32_CLI_HOST=x32.local
export X32_CLI_PORT=10023
export X32_CLI_TIMEOUT=100ms
export X32_CLI_LOGLEVEL=warn
```
---
### Use ### Use
```console For each command/subcommand in the tree there exists a `--help` flag, use it to print usage information.
xair-cli is a command-line tool that allows users to send OSC messages
to Behringer X Air mixers for remote control and configuration. It supports
various commands to manage mixer settings directly from the terminal.
Usage: - [xair-cli](./xair-help.md)
xair-cli [flags] - [x32-cli](./x32-help.md)
xair-cli [command]
Available Commands:
bus Commands to control individual buses
completion Generate the autocompletion script for the specified shell
headamp Commands to control headamp gain and phantom power
help Help about any command
main Commands to control the main output
strip Commands to control individual strips
Flags:
-h, --help help for xair-cli
-H, --host string host address of the X Air mixer (default "mixer.local")
-k, --kind string Kind of mixer (xair, x32) (default "xair")
-l, --loglevel string Log level (debug, info, warn, error, fatal, panic) (default "warn")
-p, --port int Port number of the X Air mixer (default 10024)
-v, --version version for xair-cli
Use "xair-cli [command] --help" for more information about a command.
```
### Examples ### Examples
@ -44,36 +71,53 @@ Use "xair-cli [command] --help" for more information about a command.
xair-cli main fadeout xair-cli main fadeout
``` ```
*enable phantom power and set the gain to 28.0dB over a 10s duration for strip 09* *enable phantom power and set the gain to 28.0dB over a 10s duration for headamp (strip) 09*
```console ```console
xair-cli headamp phantom 9 on xair-cli headamp 9 phantom on
xair-cli headamp gain 9 28.0 --duration 10s xair-cli headamp 9 gain --duration 10s 18.0
``` ```
*set strip 09 send level for bus 5 to -18.0dB* *set strip 09 send level for bus 5 to -18.0dB*
```console ```console
xair-cli strip send 9 5 -- -18.0 xair-cli strip 9 send 5 -- -18.0
``` ```
*enable eq for strip 01* *enable eq for strip 01*
```console ```console
xair-cli strip eq on 1 true xair-cli strip 1 eq on true
``` ```
*rename bus 01 to 'vocal mix'* *rename bus 01 to 'vocal mix'*
```console ```console
xair-cli bus name 1 'vocal mix' xair-cli bus 1 name 'vocal mix'
``` ```
For every command/subcommand there exists a `--help` flag which you can use to get usage information. *set bus 03 eq band 03 (LoMid) gain*
```console
xair-cli bus 3 eq 3 gain -- -3.5
```
*Send a raw OSC message to the mixer*
```console
xair-cli raw /xinfo
### Notes xair-cli raw /ch/01/config/name 'rode podmic'
xair-cli raw /ch/01/config/name
```
This CLI is useful if just want to run some commands on the terminal using a single binary, no further downloads. However, there exists an alternative you should check out that has wider support of the XAir OSC protocol including support for operations like batch commands and network discovery (which I have no plans to implement on this CLI). Check out [dc-xair-cli](https://pypi.org/project/dc-xair-cli/) on pypi. *Save current mixer state to a snapshot*
```console
xair-cli snapshot 20 save 'twitch live'
```
---
### License ### License
`xair-cli` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. `xair-cli` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
### Notes
For an alternative, python implementation consider checking out [dc-xair-cli](https://pypi.org/project/dc-xair-cli/). It supports some operations like batch commands and network discovery which this CLI doesn't (and I have no plans to implement them).

22
Taskfile.build-x32.yml Normal file
View File

@ -0,0 +1,22 @@
version: '3'
vars:
WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe'
LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64'
MACOS: '{{.BIN_DIR}}/{{.PROGRAM}}_darwin_amd64'
tasks:
windows-amd64:
desc: Build the x32-cli project for Windows
cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}
linux-amd64:
desc: Build the x32-cli project for Linux
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}
darwin-amd64:
desc: Build the x32-cli project for macOS
cmds:
- GOOS=darwin GOARCH=amd64 go build -o {{.MACOS}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}

22
Taskfile.build-xair.yml Normal file
View File

@ -0,0 +1,22 @@
version: '3'
vars:
WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe'
LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64'
MACOS: '{{.BIN_DIR}}/{{.PROGRAM}}_darwin_amd64'
tasks:
windows-amd64:
desc: Build the xair-cli project for Windows
cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}
linux-amd64:
desc: Build the xair-cli project for Linux
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}
darwin-amd64:
desc: Build the xair-cli project for macOS
cmds:
- GOOS=darwin GOARCH=amd64 go build -o {{.MACOS}} -ldflags="-X main.version={{.VERSION}}" ./cmd/{{.PROGRAM}}

View File

@ -1,27 +1,38 @@
version: '3' version: '3'
vars: includes:
build-xair:
taskfile: ./Taskfile.build-xair.yml
internal: true
vars:
PROGRAM: xair-cli PROGRAM: xair-cli
build-x32:
taskfile: ./Taskfile.build-x32.yml
internal: true
vars:
PROGRAM: x32-cli
vars:
SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}' SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}'
BIN_DIR: bin BIN_DIR: bin
VERSION: VERSION:
sh: 'git describe --tags $(git rev-list --tags --max-count=1)' sh: 'git describe --tags $(git rev-list --tags --max-count=1)'
WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe'
LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64'
tasks: tasks:
default: default:
desc: Build the xair-cli project desc: Build the xair-cli and x32-cli projects for all platforms
cmds: cmds:
- task: build - task: build
build: build:
desc: Build the xair-cli project desc: Build the xair-cli and x32-cli projects for all platforms
deps: [vet] deps: [vet]
cmds: cmds:
- task: build-windows - for:
- task: build-linux matrix:
PROGRAM: ['build-xair', 'build-x32']
TARGET: ['windows-amd64', 'linux-amd64', 'darwin-amd64']
task: '{{.ITEM.PROGRAM}}:{{.ITEM.TARGET}}'
vet: vet:
desc: Vet the code desc: Vet the code
@ -34,18 +45,6 @@ tasks:
cmds: cmds:
- go fmt ./... - go fmt ./...
build-windows:
desc: Build the xair-cli project for Windows
cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.version={{.VERSION}}"
internal: true
build-linux:
desc: Build the xair-cli project for Linux
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.version={{.VERSION}}"
internal: true
test: test:
desc: Run tests desc: Run tests
cmds: cmds:

View File

@ -1,893 +0,0 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
// busCmd represents the bus command.
var busCmd = &cobra.Command{
Short: "Commands to control individual buses",
Long: `Commands to control individual buses of the XAir mixer, including mute status.`,
Use: "bus",
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
// busMuteCmd represents the bus mute command.
var busMuteCmd = &cobra.Command{
Short: "Get or set the bus mute status",
Long: `Get or set the mute status of a specific bus.`,
Use: "mute [bus number] [true|false]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and mute status (true/false)")
}
busNum := mustConvToInt(args[0])
var muted bool
switch args[1] {
case "true", "1":
muted = true
case "false", "0":
muted = false
default:
return fmt.Errorf("Invalid mute status. Use true/false or 1/0")
}
err := client.Bus.SetMute(busNum, muted)
if err != nil {
return fmt.Errorf("Error setting bus mute status: %w", err)
}
cmd.Printf("Bus %d mute set to %v\n", busNum, muted)
return nil
},
}
// busFaderCmd represents the bus fader command.
var busFaderCmd = &cobra.Command{
Short: "Get or set the bus fader level",
Long: `Get or set the fader level of a specific bus.
If no level argument is provided, the current fader level is retrieved.
If a level argument (in dB) is provided, the bus fader is set to that level.`,
Use: "fader [bus number] [level in dB]",
Example: ` # Get the current fader level of bus 1
xair-cli bus fader 1
# Set the fader level of bus 1 to -10.0 dB
xair-cli bus fader 1 -10.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
level, err := client.Bus.Fader(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus fader level: %w", err)
}
cmd.Printf("Bus %d fader level: %.1f dB\n", busIndex, level)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and fader level (in dB)")
}
level := mustConvToFloat64(args[1])
err := client.Bus.SetFader(busIndex, level)
if err != nil {
return fmt.Errorf("Error setting bus fader level: %w", err)
}
cmd.Printf("Bus %d fader set to %.2f dB\n", busIndex, level)
return nil
},
}
// busFadeOutCmd represents the bus fade out command.
var busFadeOutCmd = &cobra.Command{
Short: "Fade out the bus fader over a specified duration",
Long: "Fade out the bus fader to minimum level over a specified duration in seconds.",
Use: "fadeout [bus number] --duration [seconds] [target level in dB]",
Example: ` # Fade out bus 1 over 5 seconds
xair-cli bus fadeout 1 --duration 5s -- -90.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := -90.0
if len(args) > 1 {
target = mustConvToFloat64(args[1])
}
currentFader, err := client.Bus.Fader(busIndex)
if err != nil {
return fmt.Errorf("Error getting current bus fader level: %w", err)
}
// Calculate total steps needed to reach target dB
totalSteps := float64(currentFader - target)
if totalSteps <= 0 {
cmd.Println("Bus is already at or below target level")
return nil
}
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader > target {
currentFader -= 1.0
err := client.Bus.SetFader(busIndex, currentFader)
if err != nil {
return fmt.Errorf("Error setting bus fader level: %w", err)
}
time.Sleep(stepDelay)
}
cmd.Println("Bus fade out completed")
return nil
},
}
// BusFadeInCmd represents the bus fade in command.
var busFadeInCmd = &cobra.Command{
Short: "Fade in the bus fader over a specified duration",
Long: "Fade in the bus fader to maximum level over a specified duration in seconds.",
Use: "fadein [bus number] --duration [seconds] [target level in dB]",
Example: ` # Fade in bus 1 over 5 seconds
xair-cli bus fadein 1 --duration 5s -- 0.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := 0.0
if len(args) > 1 {
target = mustConvToFloat64(args[1])
}
currentFader, err := client.Bus.Fader(busIndex)
if err != nil {
return fmt.Errorf("Error getting current bus fader level: %w", err)
}
// Calculate total steps needed to reach target dB
totalSteps := float64(target - currentFader)
if totalSteps <= 0 {
cmd.Println("Bus is already at or above target level")
return nil
}
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader < target {
currentFader += 1.0
err := client.Bus.SetFader(busIndex, currentFader)
if err != nil {
return fmt.Errorf("Error setting bus fader level: %w", err)
}
time.Sleep(stepDelay)
}
cmd.Println("Bus fade in completed")
return nil
},
}
// busNameCmd represents the bus name command.
var busNameCmd = &cobra.Command{
Short: "Get or set the bus name",
Long: `Get or set the name of a specific bus.`,
Use: "name [bus number] [new name]",
Example: ` # Get the name of bus 1
xair-cli bus name 1
# Set the name of bus 1 to "Vocals"
xair-cli bus name 1 Vocals`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
name, err := client.Bus.Name(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus name: %w", err)
}
cmd.Printf("Bus %d name: %s\n", busIndex, name)
return nil
}
newName := args[1]
err := client.Bus.SetName(busIndex, newName)
if err != nil {
return fmt.Errorf("Error setting bus name: %w", err)
}
cmd.Printf("Bus %d name set to: %s\n", busIndex, newName)
return nil
},
}
// busEqCmd represents the bus EQ command.
var busEqCmd = &cobra.Command{
Short: "Commands to control bus EQ settings",
Long: `Commands to control the EQ of individual buses, including turning the EQ on or off.`,
Use: "eq",
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
// busEqOnCmd represents the bus EQ on/off command.
var busEqOnCmd = &cobra.Command{
Short: "Get or set the bus EQ on/off status",
Long: `Get or set the EQ on/off status of a specific bus.`,
Use: "on [bus number] [true|false]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and EQ on status (true/false)")
}
busNum := mustConvToInt(args[0])
var eqOn bool
switch args[1] {
case "true", "1":
eqOn = true
case "false", "0":
eqOn = false
default:
return fmt.Errorf("Invalid EQ on status. Use true/false or 1/0")
}
err := client.Bus.Eq.SetOn(busNum, eqOn)
if err != nil {
return fmt.Errorf("Error setting bus EQ on status: %w", err)
}
cmd.Printf("Bus %d EQ on set to %v\n", busNum, eqOn)
return nil
},
}
// busEqModeCmd represents the bus EQ mode command.
var busEqModeCmd = &cobra.Command{
Short: "Get or set the bus EQ mode",
Long: `Get or set the EQ mode of a specific bus.`,
Use: "mode [bus number] [mode]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
modeNames := []string{"peq", "geq", "teq"}
if len(args) == 1 {
mode, err := client.Bus.Eq.Mode(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus EQ mode: %w", err)
}
cmd.Printf("Bus %d EQ mode: %s\n", busIndex, modeNames[mode])
return nil
}
mode := indexOf(modeNames, args[1])
if mode == -1 {
return fmt.Errorf("Invalid EQ mode. Valid modes are: %v", modeNames)
}
err := client.Bus.Eq.SetMode(busIndex, mode)
if err != nil {
return fmt.Errorf("Error setting bus EQ mode: %w", err)
}
cmd.Printf("Bus %d EQ mode set to %s\n", busIndex, modeNames[mode])
return nil
},
}
// busEqGainCmd represents the bus EQ gain command.
var busEqGainCmd = &cobra.Command{
Short: "Get or set the bus EQ gain for a specific band",
Long: `Get or set the EQ gain (in dB) for a specific band of a bus.
Gain values range from -15.0 dB to +15.0 dB.`,
Use: "gain [bus number] [band number] [gain in dB]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and band number")
}
busIndex, bandIndex := func() (int, int) {
return mustConvToInt(args[0]), mustConvToInt(args[1])
}()
if len(args) == 2 {
gain, err := client.Bus.Eq.Gain(busIndex, bandIndex)
if err != nil {
return fmt.Errorf("Error getting bus EQ gain: %w", err)
}
cmd.Printf("Bus %d EQ band %d gain: %.1f dB\n", busIndex, bandIndex, gain)
return nil
}
if len(args) < 3 {
return fmt.Errorf("Please provide bus number, band number, and gain (in dB)")
}
gain := mustConvToFloat64(args[2])
err := client.Bus.Eq.SetGain(busIndex, bandIndex, gain)
if err != nil {
return fmt.Errorf("Error setting bus EQ gain: %w", err)
}
cmd.Printf("Bus %d EQ band %d gain set to %.1f dB\n", busIndex, bandIndex, gain)
return nil
},
}
// busEqFreqCmd represents the bus EQ frequency command.
var busEqFreqCmd = &cobra.Command{
Short: "Get or set the bus EQ frequency for a specific band",
Long: `Get or set the EQ frequency (in Hz) for a specific band of a bus.`,
Use: "freq [bus number] [band number] [frequency in Hz]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and band number")
}
busIndex, bandIndex := func() (int, int) {
return mustConvToInt(args[0]), mustConvToInt(args[1])
}()
if len(args) == 2 {
freq, err := client.Bus.Eq.Frequency(busIndex, bandIndex)
if err != nil {
return fmt.Errorf("Error getting bus EQ frequency: %w", err)
}
cmd.Printf("Bus %d EQ band %d frequency: %.1f Hz\n", busIndex, bandIndex, freq)
return nil
}
if len(args) < 3 {
return fmt.Errorf("Please provide bus number, band number, and frequency (in Hz)")
}
freq := mustConvToFloat64(args[2])
err := client.Bus.Eq.SetFrequency(busIndex, bandIndex, freq)
if err != nil {
return fmt.Errorf("Error setting bus EQ frequency: %w", err)
}
cmd.Printf("Bus %d EQ band %d frequency set to %.1f Hz\n", busIndex, bandIndex, freq)
return nil
},
}
// busEqQCmd represents the bus EQ Q command.
var busEqQCmd = &cobra.Command{
Short: "Get or set the bus EQ Q factor for a specific band",
Long: `Get or set the EQ Q factor for a specific band of a bus.`,
Use: "q [bus number] [band number] [Q factor]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and band number")
}
busIndex, bandIndex := func() (int, int) {
return mustConvToInt(args[0]), mustConvToInt(args[1])
}()
if len(args) == 2 {
qFactor, err := client.Bus.Eq.Q(busIndex, bandIndex)
if err != nil {
return fmt.Errorf("Error getting bus EQ Q factor: %w", err)
}
cmd.Printf("Bus %d EQ band %d Q factor: %.2f\n", busIndex, bandIndex, qFactor)
return nil
}
if len(args) < 3 {
return fmt.Errorf("Please provide bus number, band number, and Q factor")
}
qFactor := mustConvToFloat64(args[2])
err := client.Bus.Eq.SetQ(busIndex, bandIndex, qFactor)
if err != nil {
return fmt.Errorf("Error setting bus EQ Q factor: %w", err)
}
cmd.Printf("Bus %d EQ band %d Q factor set to %.2f\n", busIndex, bandIndex, qFactor)
return nil
},
}
// busEqTypeCmd represents the bus EQ type command.
var busEqTypeCmd = &cobra.Command{
Short: "Get or set the bus EQ band type",
Long: `Get or set the EQ band type for a specific band of a bus.`,
Use: "type [bus number] [band number] [type]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and band number")
}
busIndex, bandIndex := func() (int, int) {
return mustConvToInt(args[0]), mustConvToInt(args[1])
}()
eqTypeNames := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"}
if len(args) == 2 {
currentType, err := client.Bus.Eq.Type(busIndex, bandIndex)
if err != nil {
return fmt.Errorf("Error getting bus EQ band type: %w", err)
}
cmd.Printf("Bus %d EQ band %d type: %s\n", busIndex, bandIndex, eqTypeNames[currentType])
return nil
}
eqType := indexOf(eqTypeNames, args[2])
if eqType == -1 {
return fmt.Errorf("Invalid EQ band type. Valid types are: %v", eqTypeNames)
}
err := client.Bus.Eq.SetType(busIndex, bandIndex, eqType)
if err != nil {
return fmt.Errorf("Error setting bus EQ band type: %w", err)
}
cmd.Printf("Bus %d EQ band %d type set to %s\n", busIndex, bandIndex, eqTypeNames[eqType])
return nil
},
}
// busCompCmd represents the bus Compressor command.
var busCompCmd = &cobra.Command{
Short: "Commands to control bus Compressor settings",
Long: `Commands to control the Compressor of individual buses, including turning the Compressor on or off.`,
Use: "comp",
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
// busCompOnCmd represents the bus Compressor on/off command.
var busCompOnCmd = &cobra.Command{
Short: "Get or set the bus Compressor on/off status",
Long: `Get or set the Compressor on/off status of a specific bus.`,
Use: "on [bus number] [true|false]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and Compressor on status (true/false)")
}
busNum := mustConvToInt(args[0])
var compOn bool
switch args[1] {
case "true", "1":
compOn = true
case "false", "0":
compOn = false
default:
return fmt.Errorf("Invalid Compressor on status. Use true/false or 1/0")
}
err := client.Bus.Comp.SetOn(busNum, compOn)
if err != nil {
return fmt.Errorf("Error setting bus Compressor on status: %w", err)
}
cmd.Printf("Bus %d Compressor on set to %v\n", busNum, compOn)
return nil
},
}
// busCompThresholdCmd represents the bus Compressor threshold command.
var busCompThresholdCmd = &cobra.Command{
Short: "Get or set the bus Compressor threshold",
Long: `Get or set the Compressor threshold (in dB) for a specific bus.`,
Use: "threshold [bus number] [threshold in dB]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
threshold, err := client.Bus.Comp.Threshold(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor threshold: %w", err)
}
cmd.Printf("Bus %d Compressor threshold: %.1f dB\n", busIndex, threshold)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and threshold (in dB)")
}
threshold := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetThreshold(busIndex, threshold)
if err != nil {
return fmt.Errorf("Error setting bus Compressor threshold: %w", err)
}
cmd.Printf("Bus %d Compressor threshold set to %.1f dB\n", busIndex, threshold)
return nil
},
}
// busCompRatioCmd represents the bus Compressor ratio command.
var busCompRatioCmd = &cobra.Command{
Short: "Get or set the bus Compressor ratio",
Long: `Get or set the Compressor ratio for a specific bus.`,
Use: "ratio [bus number] [ratio]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
ratio, err := client.Bus.Comp.Ratio(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor ratio: %w", err)
}
cmd.Printf("Bus %d Compressor ratio: %.2f\n", busIndex, ratio)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and ratio")
}
ratio := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetRatio(busIndex, ratio)
if err != nil {
return fmt.Errorf("Error setting bus Compressor ratio: %w", err)
}
cmd.Printf("Bus %d Compressor ratio set to %.2f\n", busIndex, ratio)
return nil
},
}
// busMixCmd represents the bus Compressor mix command.
var busCompMixCmd = &cobra.Command{
Short: "Get or set the bus Compressor mix",
Long: `Get or set the Compressor mix (0-100%) for a specific bus.`,
Use: "mix [bus number] [mix percentage]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
mix, err := client.Bus.Comp.Mix(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor mix: %w", err)
}
cmd.Printf("Bus %d Compressor mix: %.1f%%\n", busIndex, mix)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and mix percentage")
}
mix := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetMix(busIndex, mix)
if err != nil {
return fmt.Errorf("Error setting bus Compressor mix: %w", err)
}
cmd.Printf("Bus %d Compressor mix set to %.1f%%\n", busIndex, mix)
return nil
},
}
// busMakeUpCmd represents the bus Compressor make-up gain command.
var busCompMakeUpCmd = &cobra.Command{
Short: "Get or set the bus Compressor make-up gain",
Long: `Get or set the Compressor make-up gain (in dB) for a specific bus.`,
Use: "makeup [bus number] [make-up gain in dB]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
makeUp, err := client.Bus.Comp.MakeUp(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor make-up gain: %w", err)
}
cmd.Printf("Bus %d Compressor make-up gain: %.1f dB\n", busIndex, makeUp)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and make-up gain (in dB)")
}
makeUp := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetMakeUp(busIndex, makeUp)
if err != nil {
return fmt.Errorf("Error setting bus Compressor make-up gain: %w", err)
}
cmd.Printf("Bus %d Compressor make-up gain set to %.1f dB\n", busIndex, makeUp)
return nil
},
}
// busAttackCmd represents the bus Compressor attack time command.
var busCompAttackCmd = &cobra.Command{
Short: "Get or set the bus Compressor attack time",
Long: `Get or set the Compressor attack time (in milliseconds) for a specific bus.`,
Use: "attack [bus number] [attack time in ms]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
attack, err := client.Bus.Comp.Attack(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor attack time: %w", err)
}
cmd.Printf("Bus %d Compressor attack time: %.1f ms\n", busIndex, attack)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and attack time (in ms)")
}
attack := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetAttack(busIndex, attack)
if err != nil {
return fmt.Errorf("Error setting bus Compressor attack time: %w", err)
}
cmd.Printf("Bus %d Compressor attack time set to %.1f ms\n", busIndex, attack)
return nil
},
}
// busHoldCmd represents the bus Compressor hold time command.
var busCompHoldCmd = &cobra.Command{
Short: "Get or set the bus Compressor hold time",
Long: `Get or set the Compressor hold time (in milliseconds) for a specific bus.`,
Use: "hold [bus number] [hold time in ms]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
hold, err := client.Bus.Comp.Hold(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor hold time: %w", err)
}
cmd.Printf("Bus %d Compressor hold time: %.2f ms\n", busIndex, hold)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and hold time (in ms)")
}
hold := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetHold(busIndex, hold)
if err != nil {
return fmt.Errorf("Error setting bus Compressor hold time: %w", err)
}
cmd.Printf("Bus %d Compressor hold time set to %.2f ms\n", busIndex, hold)
return nil
},
}
// busReleaseCmd represents the bus Compressor release time command.
var busCompReleaseCmd = &cobra.Command{
Short: "Get or set the bus Compressor release time",
Long: `Get or set the Compressor release time (in milliseconds) for a specific bus.`,
Use: "release [bus number] [release time in ms]",
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
if len(args) == 1 {
release, err := client.Bus.Comp.Release(busIndex)
if err != nil {
return fmt.Errorf("Error getting bus Compressor release time: %w", err)
}
cmd.Printf("Bus %d Compressor release time: %.1f ms\n", busIndex, release)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide bus number and release time (in ms)")
}
release := mustConvToFloat64(args[1])
err := client.Bus.Comp.SetRelease(busIndex, release)
if err != nil {
return fmt.Errorf("Error setting bus Compressor release time: %w", err)
}
cmd.Printf("Bus %d Compressor release time set to %.1f ms\n", busIndex, release)
return nil
},
}
func init() {
rootCmd.AddCommand(busCmd)
busCmd.AddCommand(busMuteCmd)
busCmd.AddCommand(busFaderCmd)
busCmd.AddCommand(busFadeOutCmd)
busFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade out in seconds")
busCmd.AddCommand(busFadeInCmd)
busFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade in in seconds")
busCmd.AddCommand(busNameCmd)
busCmd.AddCommand(busEqCmd)
busEqCmd.AddCommand(busEqOnCmd)
busEqCmd.AddCommand(busEqModeCmd)
busEqCmd.AddCommand(busEqGainCmd)
busEqCmd.AddCommand(busEqFreqCmd)
busEqCmd.AddCommand(busEqQCmd)
busEqCmd.AddCommand(busEqTypeCmd)
busCmd.AddCommand(busCompCmd)
busCompCmd.AddCommand(busCompOnCmd)
busCompCmd.AddCommand(busCompThresholdCmd)
busCompCmd.AddCommand(busCompRatioCmd)
busCompCmd.AddCommand(busCompMixCmd)
busCompCmd.AddCommand(busCompMakeUpCmd)
busCompCmd.AddCommand(busCompAttackCmd)
busCompCmd.AddCommand(busCompHoldCmd)
busCompCmd.AddCommand(busCompReleaseCmd)
}

View File

@ -1,22 +0,0 @@
package cmd
import (
"context"
"github.com/onyx-and-iris/xair-cli/internal/xair"
)
type clientKey string
// WithContext returns a new context with the provided xair.Client.
func WithContext(ctx context.Context, client *xair.Client) context.Context {
return context.WithValue(ctx, clientKey("oscClient"), client)
}
// ClientFromContext retrieves the xair.Client from the context.
func ClientFromContext(ctx context.Context) *xair.Client {
if client, ok := ctx.Value(clientKey("oscClient")).(*xair.Client); ok {
return client
}
return nil
}

View File

@ -1,211 +0,0 @@
package cmd
import (
"fmt"
"time"
"github.com/charmbracelet/log"
"github.com/onyx-and-iris/xair-cli/internal/xair"
"github.com/spf13/cobra"
)
// headampCmd represents the headamp command
var headampCmd = &cobra.Command{
Short: "Commands to control headamp gain and phantom power",
Long: `Commands to control the headamp gain and phantom power settings of the XAir mixer.
You can get or set the gain level for individual headamps, as well as enable or disable phantom power.`,
Use: "headamp",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
// headampGainCmd represents the headamp gain command
var headampGainCmd = &cobra.Command{
Use: "gain",
Short: "Get or set headamp gain level",
Long: `Get or set the gain level for a specified headamp index.
When setting gain, it will gradually increase from the current level to prevent
sudden jumps that could cause feedback or equipment damage.
Examples:
# Get gain level for headamp index 1
xair-cli headamp gain 1
# Set gain level for headamp index 1 to 3.5 dB (gradually over 5 seconds)
xair-cli headamp gain 1 3.5
# Set gain level for headamp index 1 to 3.5 dB over 10 seconds
xair-cli headamp gain 1 3.5 --duration 10s`,
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide a headamp index")
}
index := mustConvToInt(args[0])
if len(args) == 1 {
gain, err := client.HeadAmp.Gain(index)
if err != nil {
return fmt.Errorf("Error getting headamp gain level: %w", err)
}
cmd.Printf("Headamp %d Gain: %.2f dB\n", index, gain)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide a gain level in dB")
}
targetLevel := mustConvToFloat64(args[1])
currentGain, err := client.HeadAmp.Gain(index)
if err != nil {
return fmt.Errorf("Error getting current headamp gain level: %w", err)
}
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
return fmt.Errorf("Error getting duration flag: %w", err)
}
if currentGain == targetLevel {
cmd.Printf("Headamp %d Gain already at %.2f dB\n", index, targetLevel)
return nil
}
if err := gradualGainAdjust(client, cmd, index, currentGain, targetLevel, duration); err != nil {
return fmt.Errorf("Error adjusting headamp gain level: %w", err)
}
cmd.Printf("Headamp %d Gain set to %.2f dB\n", index, targetLevel)
return nil
},
}
// gradualGainAdjust gradually adjusts gain from current to target over specified duration
func gradualGainAdjust(
client *xair.Client,
cmd *cobra.Command,
index int,
currentGain, targetGain float64,
duration time.Duration,
) error {
gainDiff := targetGain - currentGain
stepInterval := 100 * time.Millisecond
totalSteps := int(duration / stepInterval)
if totalSteps < 1 {
totalSteps = 1
stepInterval = duration
}
stepIncrement := gainDiff / float64(totalSteps)
log.Debugf("Adjusting Headamp %d gain from %.2f dB to %.2f dB over %v...\n",
index, currentGain, targetGain, duration)
for step := 1; step <= totalSteps; step++ {
newGain := currentGain + (stepIncrement * float64(step))
if step == totalSteps {
newGain = targetGain
}
err := client.HeadAmp.SetGain(index, newGain)
if err != nil {
return err
}
if step%10 == 0 || step == totalSteps {
log.Debugf(" Step %d/%d: %.2f dB\n", step, totalSteps, newGain)
}
if step < totalSteps {
time.Sleep(stepInterval)
}
}
return nil
}
// headampPhantomPowerCmd represents the headamp phantom power command
var headampPhantomPowerCmd = &cobra.Command{
Use: "phantom",
Short: "Get or set headamp phantom power status",
Long: `Get or set the phantom power status for a specified headamp index.
Examples:
# Get phantom power status for headamp index 1
xairctl headamp phantom 1
# Enable phantom power for headamp index 1
xairctl headamp phantom 1 on
# Disable phantom power for headamp index 1
xairctl headamp phantom 1 off`,
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide a headamp index")
}
index := mustConvToInt(args[0])
if len(args) == 1 {
enabled, err := client.HeadAmp.PhantomPower(index)
if err != nil {
return fmt.Errorf("Error getting headamp phantom power status: %w", err)
}
status := "disabled"
if enabled {
status = "enabled"
}
cmd.Printf("Headamp %d Phantom Power is %s\n", index, status)
return nil
}
if len(args) < 2 {
return fmt.Errorf("Please provide phantom power status: on or off")
}
var enable bool
switch args[1] {
case "on", "enable":
enable = true
case "off", "disable":
enable = false
default:
return fmt.Errorf("Invalid phantom power status. Use 'on' or 'off'")
}
err := client.HeadAmp.SetPhantomPower(index, enable)
if err != nil {
return fmt.Errorf("Error setting headamp phantom power status: %w", err)
}
status := "disabled"
if enable {
status = "enabled"
}
cmd.Printf("Headamp %d Phantom Power %s successfully\n", index, status)
return nil
},
}
func init() {
rootCmd.AddCommand(headampCmd)
headampCmd.AddCommand(headampGainCmd)
headampGainCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration over which to gradually adjust gain")
headampCmd.AddCommand(headampPhantomPowerCmd)
}

View File

@ -1,237 +0,0 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
)
// mainCmd represents the main command.
var mainCmd = &cobra.Command{
Short: "Commands to control the main output",
Long: `Commands to control the main output of the XAir mixer, including fader level and mute status.`,
Use: "main",
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
// mainMuteCmd represents the main mute command.
var mainMuteCmd = &cobra.Command{
Short: "Get or set the main LR mute status",
Long: `Get or set the main L/R mute status.
If no argument is provided, the current mute status is retrieved.
If "true" or "1" is provided as an argument, the main output is muted.
If "false" or "0" is provided, the main output is unmuted.`,
Use: "mute [true|false]",
Example: ` # Get the current main LR mute status
xair-cli main mute
# Mute the main output
xair-cli main mute true
# Unmute the main output
xair-cli main mute false`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) == 0 {
resp, err := client.Main.Mute()
if err != nil {
return fmt.Errorf("Error getting main LR mute status: %w", err)
}
cmd.Printf("Main LR mute: %v\n", resp)
return nil
}
var muted bool
switch args[0] {
case "true", "1":
muted = true
case "false", "0":
muted = false
default:
return fmt.Errorf("Invalid mute status. Use true/false or 1/0")
}
err := client.Main.SetMute(muted)
if err != nil {
return fmt.Errorf("Error setting main LR mute status: %w", err)
}
cmd.Println("Main LR mute status set successfully")
return nil
},
}
// mainFaderCmd represents the main fader command.
var mainFaderCmd = &cobra.Command{
Short: "Set or get the main LR fader level",
Long: `Set or get the main L/R fader level in dB.
If no argument is provided, the current fader level is retrieved.
If a dB value is provided as an argument, the fader level is set to that value.`,
Use: "fader [level in dB]",
Example: ` # Get the current main LR fader level
xair-cli main fader
# Set the main LR fader level to -10.0 dB
xair-cli main fader -- -10.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) == 0 {
resp, err := client.Main.Fader()
if err != nil {
return fmt.Errorf("Error getting main LR fader: %w", err)
}
cmd.Printf("Main LR fader: %.1f dB\n", resp)
return nil
}
err := client.Main.SetFader(mustConvToFloat64(args[0]))
if err != nil {
return fmt.Errorf("Error setting main LR fader: %w", err)
}
cmd.Println("Main LR fader set successfully")
return nil
},
}
// mainFadeOutCmd represents the main fadeout command.
var mainFadeOutCmd = &cobra.Command{
Short: "Fade out the main output",
Long: `Fade out the main output over a specified duration.
This command will fade out the main output to the specified dB level.
`,
Use: "fadeout --duration [seconds] [target_db]",
Example: ` # Fade out main output over 5 seconds
xair-cli main fadeout --duration 5s -- -90.0`,
Run: func(cmd *cobra.Command, args []string) {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
}
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
}
// Default target for fadeout
target := -90.0
if len(args) > 0 {
target = mustConvToFloat64(args[0])
}
currentFader, err := client.Main.Fader()
if err != nil {
cmd.PrintErrln("Error getting current main LR fader:", err)
return
}
// Calculate total steps needed to reach target dB
totalSteps := float64(currentFader - target)
if totalSteps <= 0 {
cmd.Println("Main output is already faded out")
return
}
// Calculate delay per step to achieve exact duration
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader > target {
currentFader -= 1.0
err = client.Main.SetFader(currentFader)
if err != nil {
cmd.PrintErrln("Error setting main LR fader:", err)
return
}
time.Sleep(stepDelay)
}
cmd.Println("Main output faded out successfully")
},
}
// mainFadeInCmd represents the main fadein command.
var mainFadeInCmd = &cobra.Command{
Short: "Fade in the main output",
Long: `Fade in the main output over a specified duration.
This command will fade in the main output to the specified dB level.
`,
Use: "fadein --duration [seconds] [target_db]",
Example: ` # Fade in main output over 5 seconds
xair-cli main fadein --duration 5s -- 0.0`,
Run: func(cmd *cobra.Command, args []string) {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
}
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
}
target := 0.0
if len(args) > 0 {
target = mustConvToFloat64(args[0])
}
currentFader, err := client.Main.Fader()
if err != nil {
cmd.PrintErrln("Error getting current main LR fader:", err)
return
}
// Calculate total steps needed to reach target dB
totalSteps := float64(target - currentFader)
if totalSteps <= 0 {
cmd.Println("Main output is already at or above target level")
return
}
// Calculate delay per step to achieve exact duration
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader < target {
currentFader += 1.0
err = client.Main.SetFader(currentFader)
if err != nil {
cmd.PrintErrln("Error setting main LR fader:", err)
return
}
time.Sleep(stepDelay)
}
cmd.Println("Main output faded in successfully")
},
}
func init() {
rootCmd.AddCommand(mainCmd)
mainCmd.AddCommand(mainMuteCmd)
mainCmd.AddCommand(mainFaderCmd)
mainCmd.AddCommand(mainFadeOutCmd)
mainFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade out in seconds")
mainCmd.AddCommand(mainFadeInCmd)
mainFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade in in seconds")
}

View File

@ -1,106 +0,0 @@
package cmd
import (
"os"
"runtime/debug"
"strings"
"github.com/charmbracelet/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/onyx-and-iris/xair-cli/internal/xair"
)
var version string // Version of the CLI, set during build time
// rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "xair-cli",
Short: "A command-line utility to interact with Behringer X Air mixers via OSC",
Long: `xair-cli is a command-line tool that allows users to send OSC messages
to Behringer X Air mixers for remote control and configuration. It supports
various commands to manage mixer settings directly from the terminal.`,
Version: versionFromBuild(),
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
level, err := log.ParseLevel(viper.GetString("loglevel"))
if err != nil {
return err
}
log.SetLevel(level)
kind := viper.GetString("kind")
log.Debugf("Initialising client for mixer kind: %s", kind)
if kind == "x32" && !viper.IsSet("port") {
viper.Set("port", 10023)
}
client, err := xair.NewClient(
viper.GetString("host"),
viper.GetInt("port"),
xair.WithKind(kind),
)
if err != nil {
return err
}
cmd.SetContext(WithContext(cmd.Context(), client))
client.StartListening()
err, resp := client.RequestInfo()
if err != nil {
return err
}
log.Infof("Received mixer info: %+v", resp)
return nil
},
PersistentPostRunE: func(cmd *cobra.Command, _ []string) error {
client := ClientFromContext(cmd.Context())
if client != nil {
client.Stop()
}
return nil
},
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringP("host", "H", "mixer.local", "host address of the X Air mixer")
rootCmd.PersistentFlags().IntP("port", "p", 10024, "Port number of the X Air mixer")
rootCmd.PersistentFlags().
StringP("loglevel", "l", "warn", "Log level (debug, info, warn, error, fatal, panic)")
rootCmd.PersistentFlags().StringP("kind", "k", "xair", "Kind of mixer (xair, x32)")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.SetEnvPrefix("XAIR_CLI")
viper.AutomaticEnv()
viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
viper.BindPFlag("kind", rootCmd.PersistentFlags().Lookup("kind"))
}
func versionFromBuild() string {
if version == "" {
info, ok := debug.ReadBuildInfo()
if !ok {
return "(unable to read version)"
}
version = strings.Split(info.Main.Version, "-")[0]
}
return version
}

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
package cmd
import (
"strconv"
)
// mustConvToFloat64 converts a string to float64, panicking on error.
func mustConvToFloat64(floatStr string) float64 {
level, err := strconv.ParseFloat(floatStr, 64)
if err != nil {
panic(err)
}
return level
}
// mustConvToInt converts a string to int, panicking on error.
func mustConvToInt(intStr string) int {
val, err := strconv.Atoi(intStr)
if err != nil {
panic(err)
}
return val
}
// generic indexOf returns the index of elem in slice, or -1 if not found.
func indexOf[T comparable](slice []T, elem T) int {
for i, v := range slice {
if v == elem {
return i
}
}
return -1
}
// generic contains checks if elem is in slice.
func contains[T comparable](slice []T, elem T) bool {
return indexOf(slice, elem) != -1
}

638
cmd/x32-cli/bus.go Normal file
View File

@ -0,0 +1,638 @@
package main
import (
"fmt"
"time"
)
// BusCmdGroup defines the commands related to controlling the buses of the X-Air device.
type BusCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the bus. (1-based indexing)"`
Mute BusMuteCmd ` help:"Get or set the mute state of the bus." cmd:""`
Fader BusFaderCmd ` help:"Get or set the fader level of the bus." cmd:""`
Fadein BusFadeinCmd ` help:"Fade in the bus over a specified duration." cmd:""`
Fadeout BusFadeoutCmd ` help:"Fade out the bus over a specified duration." cmd:""`
Name BusNameCmd ` help:"Get or set the name of the bus." cmd:""`
Eq BusEqCmdGroup ` help:"Commands related to the bus EQ." cmd:"eq"`
Comp BusCompCmdGroup ` help:"Commands related to the bus compressor." cmd:"comp"`
} `arg:"" help:"Control a specific bus by index."`
}
// BusMuteCmd defines the command for getting or setting the mute state of a bus.
type BusMuteCmd struct {
State *string `arg:"" help:"The mute state to set (true or false). If not provided, the current mute state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusMuteCmd command, either retrieving the current mute state or setting it based on the provided argument.
func (cmd *BusMuteCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Mute(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d mute state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetMute(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d mute state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusFaderCmd defines the command for getting or setting the fader level of a bus.
type BusFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set (in dB). If not provided, the current fader level will be returned." optional:""`
}
// Run executes the BusFaderCmd command, either retrieving the current fader level or setting it based on the provided argument.
func (cmd *BusFaderCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d fader level: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, *cmd.Level); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d fader level set to: %.2f dB\n", bus.Index.Index, *cmd.Level)
return nil
}
// BusFadeinCmd defines the command for fading in a bus over a specified duration to a target fader level.
type BusFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in effect." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"0.0" arg:""`
}
// Run executes the BusFadeinCmd command, gradually increasing the fader level of the bus from its current level to the target level over the specified duration.
func (cmd *BusFadeinCmd) Run(ctx *context, bus *BusCmdGroup) error {
currentLevel, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or above the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel += totalSteps / float64(cmd.Duration.Seconds()*1000/stepDuration.Seconds())
if currentLevel > cmd.Target {
currentLevel = cmd.Target
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Bus %d fade-in complete. Final level: %.2f dB\n",
bus.Index.Index,
cmd.Target,
)
return nil
}
// BusFadeoutCmd defines the command for fading out a bus over a specified duration to a target fader level.
type BusFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out effect." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"-90.0" arg:""`
}
// Run executes the BusFadeoutCmd command, gradually decreasing the fader level of the bus from its current level to the target level over the specified duration.
func (cmd *BusFadeoutCmd) Run(ctx *context, bus *BusCmdGroup) error {
currentLevel, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or below the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel -= totalSteps / float64(cmd.Duration.Seconds()*1000/stepDuration.Seconds())
if currentLevel < cmd.Target {
currentLevel = cmd.Target
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Bus %d fade-out complete. Final level: %.2f dB\n",
bus.Index.Index,
cmd.Target,
)
return nil
}
// BusNameCmd defines the command for getting or setting the name of a bus.
type BusNameCmd struct {
Name *string `arg:"" help:"The name to set for the bus. If not provided, the current name will be returned." optional:""`
}
// Run executes the BusNameCmd command, either retrieving the current name of the bus or setting it based on the provided argument.
func (cmd *BusNameCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.Bus.Name(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d name: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetName(bus.Index.Index, *cmd.Name); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d name set to: %s\n", bus.Index.Index, *cmd.Name)
return nil
}
// BusEqCmdGroup defines the commands related to controlling the EQ of a bus.
type BusEqCmdGroup struct {
On BusEqOnCmd `help:"Get or set the EQ on/off state of the bus." cmd:"on"`
Mode BusEqModeCmd `help:"Get or set the EQ mode of the bus (peq, geq or teq)." cmd:"mode"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain BusEqBandGainCmd `help:"Get or set the gain of the EQ band." cmd:"gain"`
Freq BusEqBandFreqCmd `help:"Get or set the frequency of the EQ band." cmd:"freq"`
Q BusEqBandQCmd `help:"Get or set the Q factor of the EQ band." cmd:"q"`
Type BusEqBandTypeCmd `help:"Get or set the type of the EQ band (lcut, lshv, peq, veq, hshv, hcut)." cmd:"type"`
} `help:"Commands for controlling a specific EQ band of the bus." arg:""`
}
// Validate checks that the provided EQ band number is within the valid range (1-6).
func (cmd *BusEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// BusCompCmdGroup defines the commands related to controlling the compressor of a bus.
type BusEqOnCmd struct {
State *string `arg:"" help:"The EQ on/off state to set (true or false). If not provided, the current EQ state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusEqOnCmd command, either retrieving the current EQ on/off state of the bus or setting it based on the provided argument.
func (cmd *BusEqOnCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Eq.On(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ on state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Eq.SetOn(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ on state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusEqModeCmd defines the command for getting or setting the EQ mode of a bus.
type BusEqModeCmd struct {
Mode *string `arg:"" help:"The EQ mode to set (peq, geq or teq). If not provided, the current EQ mode will be returned." optional:"" enum:"peq,geq,teq"`
}
// Run executes the BusEqModeCmd command, either retrieving the current EQ mode of the bus or setting it based on the provided argument.
func (cmd *BusEqModeCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Bus.Eq.Mode(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ mode: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Eq.SetMode(bus.Index.Index, *cmd.Mode); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ mode set to: %s\n", bus.Index.Index, *cmd.Mode)
return nil
}
// BusEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band of a bus.
type BusEqBandGainCmd struct {
Gain *float64 `arg:"" help:"The gain to set for the EQ band (in dB). If not provided, the current gain will be returned." optional:""`
}
// Run executes the BusEqBandGainCmd command, either retrieving the current gain of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandGainCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Gain == nil {
resp, err := ctx.Client.Bus.Eq.Gain(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d gain: %.2f dB\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetGain(bus.Index.Index, *busEq.Band.Band, *cmd.Gain); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d gain set to: %.2f dB\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Gain,
)
return nil
}
// BusEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band of a bus.
type BusEqBandFreqCmd struct {
Freq *float64 `arg:"" help:"The frequency to set for the EQ band (in Hz). If not provided, the current frequency will be returned." optional:""`
}
// Run executes the BusEqBandFreqCmd command, either retrieving the current frequency of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandFreqCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Freq == nil {
resp, err := ctx.Client.Bus.Eq.Frequency(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d frequency: %.2f Hz\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetFrequency(
bus.Index.Index,
*busEq.Band.Band,
*cmd.Freq,
); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d frequency set to: %.2f Hz\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Freq,
)
return nil
}
// BusEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band of a bus.
type BusEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the EQ band. If not provided, the current Q factor will be returned." optional:""`
}
// Run executes the BusEqBandQCmd command, either retrieving the current Q factor of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandQCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Q == nil {
resp, err := ctx.Client.Bus.Eq.Q(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d Q factor: %.2f\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetQ(bus.Index.Index, *busEq.Band.Band, *cmd.Q); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d Q factor set to: %.2f\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Q,
)
return nil
}
// BusEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band of a bus.
type BusEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the EQ band (lcut, lshv, peq, veq, hshv, hcut). If not provided, the current type will be returned." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"`
}
// Run executes the BusEqBandTypeCmd command, either retrieving the current type of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandTypeCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Type == nil {
resp, err := ctx.Client.Bus.Eq.Type(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d type: %s\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetType(bus.Index.Index, *busEq.Band.Band, *cmd.Type); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d type set to: %s\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Type,
)
return nil
}
// BusCompCmdGroup defines the commands related to controlling the compressor of a bus.
type BusCompCmdGroup struct {
On BusCompOnCmd `help:"Get or set the compressor on/off state of the bus." cmd:"on"`
Mode BusCompModeCmd `help:"Get or set the compressor mode of the bus (comp, exp)." cmd:"mode"`
Threshold BusCompThresholdCmd `help:"Get or set the compressor threshold of the bus (in dB)." cmd:"threshold"`
Ratio BusCompRatioCmd `help:"Get or set the compressor ratio of the bus." cmd:"ratio"`
Mix BusCompMixCmd `help:"Get or set the compressor mix level of the bus (in %)." cmd:"mix"`
Makeup BusCompMakeupCmd `help:"Get or set the compressor makeup gain of the bus (in dB)." cmd:"makeup"`
Attack BusCompAttackCmd `help:"Get or set the compressor attack time of the bus (in ms)." cmd:"attack"`
Hold BusCompHoldCmd `help:"Get or set the compressor hold time of the bus (in ms)." cmd:"hold"`
Release BusCompReleaseCmd `help:"Get or set the compressor release time of the bus (in ms)." cmd:"release"`
}
// BusCompOnCmd defines the command for getting or setting the compressor on/off state of a bus.
type BusCompOnCmd struct {
State *string `arg:"" help:"The compressor on/off state to set (true or false). If not provided, the current compressor state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusCompOnCmd command, either retrieving the current compressor on/off state of the bus or setting it based on the provided argument.
func (cmd *BusCompOnCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Comp.On(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor on state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetOn(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor on state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusCompModeCmd defines the command for getting or setting the compressor mode of a bus.
type BusCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set (comp, exp). If not provided, the current compressor mode will be returned." optional:"" enum:"comp,exp"`
}
// Run executes the BusCompModeCmd command, either retrieving the current compressor mode of the bus or setting it based on the provided argument.
func (cmd *BusCompModeCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Bus.Comp.Mode(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mode: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMode(bus.Index.Index, *cmd.Mode); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mode set to: %s\n", bus.Index.Index, *cmd.Mode)
return nil
}
// BusCompThresholdCmd defines the command for getting or setting the compressor threshold of a bus.
type BusCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB). If not provided, the current compressor threshold will be returned." optional:""`
}
// Run executes the BusCompThresholdCmd command, either retrieving the current compressor threshold of the bus or setting it based on the provided argument.
func (cmd *BusCompThresholdCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Bus.Comp.Threshold(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor threshold: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetThreshold(bus.Index.Index, *cmd.Threshold); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor threshold set to: %.2f dB\n",
bus.Index.Index,
*cmd.Threshold,
)
return nil
}
// BusCompRatioCmd defines the command for getting or setting the compressor ratio of a bus.
type BusCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current compressor ratio will be returned." optional:""`
}
// Run executes the BusCompRatioCmd command, either retrieving the current compressor ratio of the bus or setting it based on the provided argument.
func (cmd *BusCompRatioCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Bus.Comp.Ratio(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor ratio: %.2f\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetRatio(bus.Index.Index, *cmd.Ratio); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor ratio set to: %.2f\n", bus.Index.Index, *cmd.Ratio)
return nil
}
// BusCompMixCmd defines the command for getting or setting the compressor mix level of a bus.
type BusCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set (in %). If not provided, the current compressor mix level will be returned." optional:""`
}
// Run executes the BusCompMixCmd command, either retrieving the current compressor mix level of the bus or setting it based on the provided argument.
func (cmd *BusCompMixCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Bus.Comp.Mix(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mix level: %.2f%%\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMix(bus.Index.Index, *cmd.Mix); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mix level set to: %.2f%%\n", bus.Index.Index, *cmd.Mix)
return nil
}
// BusCompMakeupCmd defines the command for getting or setting the compressor makeup gain of a bus.
type BusCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set (in dB). If not provided, the current compressor makeup gain will be returned." optional:""`
}
// Run executes the BusCompMakeupCmd command, either retrieving the current compressor makeup gain of the bus or setting it based on the provided argument.
func (cmd *BusCompMakeupCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Bus.Comp.Makeup(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor makeup gain: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMakeup(bus.Index.Index, *cmd.Makeup); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor makeup gain set to: %.2f dB\n",
bus.Index.Index,
*cmd.Makeup,
)
return nil
}
// BusCompAttackCmd defines the command for getting or setting the compressor attack time of a bus.
type BusCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set (in ms). If not provided, the current compressor attack time will be returned." optional:""`
}
// Run executes the BusCompAttackCmd command, either retrieving the current compressor attack time of the bus or setting it based on the provided argument.
func (cmd *BusCompAttackCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Bus.Comp.Attack(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor attack time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetAttack(bus.Index.Index, *cmd.Attack); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor attack time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Attack,
)
return nil
}
// BusCompHoldCmd defines the command for getting or setting the compressor hold time of a bus.
type BusCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set (in ms). If not provided, the current compressor hold time will be returned." optional:""`
}
// Run executes the BusCompHoldCmd command, either retrieving the current compressor hold time of the bus or setting it based on the provided argument.
func (cmd *BusCompHoldCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Bus.Comp.Hold(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor hold time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetHold(bus.Index.Index, *cmd.Hold); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor hold time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Hold,
)
return nil
}
// BusCompReleaseCmd defines the command for getting or setting the compressor release time of a bus.
type BusCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set (in ms). If not provided, the current compressor release time will be returned." optional:""`
}
// Run executes the BusCompReleaseCmd command, either retrieving the current compressor release time of the bus or setting it based on the provided argument.
func (cmd *BusCompReleaseCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Bus.Comp.Release(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor release time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetRelease(bus.Index.Index, *cmd.Release); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor release time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Release,
)
return nil
}

144
cmd/x32-cli/cli.go Normal file
View File

@ -0,0 +1,144 @@
package main
import (
"fmt"
"io"
"os"
"runtime/debug"
"strings"
"time"
"github.com/alecthomas/kong"
"github.com/charmbracelet/log"
kongcompletion "github.com/jotaen/kong-completion"
"github.com/onyx-and-iris/xair-cli/internal/xair"
)
const (
trueStr = "true"
falseStr = "false"
)
var version string // Version of the CLI, set at build time.
// VersionFlag is a custom flag type that prints the version and exits.
type VersionFlag string
func (v VersionFlag) Decode(_ *kong.DecodeContext) error { return nil } // nolint: revive
func (v VersionFlag) IsBool() bool { return true } // nolint: revive
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { // nolint: revive, unparam
fmt.Printf("x32-cli version: %s\n", vars["version"])
app.Exit(0)
return nil
}
type context struct {
Client *xair.X32Client
Out io.Writer
}
type Config struct {
Host string `default:"mixer.local" help:"The host of the X32 device." env:"X32_CLI_HOST" short:"H"`
Port int `default:"10023" help:"The port of the X32 device." env:"X32_CLI_PORT" short:"P"`
Timeout time.Duration `default:"100ms" help:"Timeout for OSC operations." env:"X32_CLI_TIMEOUT" short:"T"`
Loglevel string `default:"warn" help:"Log level for the CLI." env:"X32_CLI_LOGLEVEL" short:"L" enum:"debug,info,warn,error,fatal"`
}
// CLI is the main struct for the command-line interface.
// It embeds the Config struct for global configuration and defines the available commands and flags.
type CLI struct {
Config `embed:"" prefix:"" help:"The configuration for the CLI."`
Version VersionFlag `help:"Print x32-cli version information and quit" name:"version" short:"v"`
Completion kongcompletion.Completion `help:"Generate shell completion scripts." cmd:""`
Info InfoCmd `help:"Print mixer information." cmd:""`
Raw RawCmd `help:"Send raw OSC messages to the mixer." cmd:""`
Main MainCmdGroup `help:"Control the Main L/R output" cmd:"" group:"Main"`
Mainmono MainMonoCmdGroup `help:"Control the Main Mono output" cmd:"" group:"MainMono"`
Matrix MatrixCmdGroup `help:"Control the matrix outputs." cmd:"" group:"Matrix"`
Strip StripCmdGroup `help:"Control the strips." cmd:"" group:"Strip"`
Bus BusCmdGroup `help:"Control the buses." cmd:"" group:"Bus"`
Headamp HeadampCmdGroup `help:"Control input gain and phantom power." cmd:"" group:"Headamp"`
Snapshot SnapshotCmdGroup `help:"Save and load mixer states." cmd:"" group:"Snapshot"`
Dca DCACmdGroup `help:"Control DCA groups." cmd:"" group:"DCA"`
}
func main() {
var cli CLI
kongcompletion.Register(kong.Must(&cli))
ctx := kong.Parse(
&cli,
kong.Name("x32-cli"),
kong.Description("A CLI to control Behringer X32 mixers."),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"version": func() string {
if version != "" {
return version
}
info, ok := debug.ReadBuildInfo()
if !ok {
return "(unable to read version)"
}
return strings.Split(info.Main.Version, "-")[0]
}(),
},
)
ctx.FatalIfErrorf(run(ctx, cli.Config))
}
// run is the main entry point for the CLI.
// It connects to the X32 device, retrieves mixer info, and then runs the command.
func run(ctx *kong.Context, config Config) error {
loglevel, err := log.ParseLevel(config.Loglevel)
if err != nil {
return fmt.Errorf("invalid log level: %w", err)
}
log.SetLevel(loglevel)
client, err := connect(config)
if err != nil {
return fmt.Errorf("failed to connect to X32 device: %w", err)
}
defer func() {
if err := client.Close(); err != nil {
log.Errorf("failed to close client connection: %v", err)
}
}()
client.StartListening()
resp, err := client.RequestInfo()
if err != nil {
return err
}
log.Infof("Received mixer info: %+v", resp)
ctx.Bind(&context{
Client: client,
Out: os.Stdout,
})
return ctx.Run()
}
// connect creates a new X32 client based on the provided configuration.
func connect(config Config) (*xair.X32Client, error) {
client, err := xair.NewX32Client(
config.Host,
config.Port,
xair.WithTimeout(config.Timeout),
)
if err != nil {
return nil, fmt.Errorf("failed to create X32 client: %w", err)
}
return client, nil
}

67
cmd/x32-cli/dca.go Normal file
View File

@ -0,0 +1,67 @@
package main
import (
"fmt"
)
type DCACmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the DCA group (1-8)."`
Mute DCAMuteCmd `help:"Get or set the mute status of the DCA group." cmd:""`
Name DCANameCmd `help:"Get or set the name of the DCA group." cmd:""`
} `arg:"" help:"Control a specific DCA group by its index."`
}
// Validate checks if the provided index is within the valid range.
func (cmd *DCACmdGroup) Validate() error {
if cmd.Index.Index < 1 || cmd.Index.Index > 8 {
return fmt.Errorf("DCA group index must be between 1 and 8, got %d", cmd.Index.Index)
}
return nil
}
// DCAMuteCmd is the command to get or set the mute status of a DCA group.
type DCAMuteCmd struct {
State *string `arg:"" help:"Set the mute status of the DCA group." optional:"" enum:"true,false"`
}
// Run executes the DCAMuteCmd command.
func (cmd *DCAMuteCmd) Run(ctx *context, dca *DCACmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.DCA.Mute(dca.Index.Index)
if err != nil {
return fmt.Errorf("failed to get DCA mute status: %w", err)
}
fmt.Fprintf(ctx.Out, "DCA Group %d mute state: %t\n", dca.Index.Index, resp)
return nil
}
if err := ctx.Client.DCA.SetMute(dca.Index.Index, *cmd.State == trueStr); err != nil {
return fmt.Errorf("failed to set DCA mute status: %w", err)
}
return nil
}
// DCANameCmd is the command to get or set the name of a DCA group.
type DCANameCmd struct {
Name *string `arg:"" help:"Set the name of the DCA group." optional:""`
}
// Run executes the DCANameCmd command.
func (cmd *DCANameCmd) Run(ctx *context, dca *DCACmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.DCA.Name(dca.Index.Index)
if err != nil {
return fmt.Errorf("failed to get DCA name: %w", err)
}
if resp == "" {
resp = fmt.Sprintf("DCA %d", dca.Index.Index)
}
fmt.Fprintf(ctx.Out, "DCA Group %d is named '%s'\n", dca.Index.Index, resp)
return nil
}
if err := ctx.Client.DCA.SetName(dca.Index.Index, *cmd.Name); err != nil {
return fmt.Errorf("failed to set DCA name: %w", err)
}
return nil
}

139
cmd/x32-cli/headamp.go Normal file
View File

@ -0,0 +1,139 @@
package main
import (
"fmt"
"time"
"github.com/charmbracelet/log"
)
// HeadampCmdGroup defines the command group for controlling input gain and phantom power of a headamp, allowing users to specify the index of the headamp they want to control.
type HeadampCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the headamp."`
Gain HeadampGainCmd `help:"Get or set the gain of the headamp." cmd:""`
Phantom HeadampPhantomCmd `help:"Get or set the phantom power state of the headamp." cmd:""`
} `arg:"" help:"Control a specific headamp by index."`
}
// HeadampGainCmd defines the command for getting or setting the gain of a headamp, allowing users to specify the gain in dB and an optional duration for a gradual fade when setting the gain.
type HeadampGainCmd struct {
Duration time.Duration `help:"The duration of the fade in/out when setting the gain." default:"5s"`
Gain *float64 `help:"The gain of the headamp in dB." arg:"" optional:""`
}
// Run executes the HeadampGainCmd command, either retrieving the current gain of the headamp or setting it based on the provided argument, with an optional fade duration for smooth transitions.
func (cmd *HeadampGainCmd) Run(ctx *context, headamp *HeadampCmdGroup) error {
if cmd.Gain == nil {
resp, err := ctx.Client.HeadAmp.Gain(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get headamp gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d gain: %.2f dB\n", headamp.Index.Index, resp)
return nil
}
currentGain, err := ctx.Client.HeadAmp.Gain(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current headamp gain: %w", err)
}
if err := gradualGainAdjust(
ctx,
headamp.Index.Index,
currentGain,
*cmd.Gain,
cmd.Duration,
); err != nil {
return fmt.Errorf("failed to set headamp gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d gain set to: %.2f dB\n", headamp.Index.Index, *cmd.Gain)
return nil
}
// gradualGainAdjust gradually adjusts gain from current to target over specified duration.
func gradualGainAdjust(
ctx *context,
index int,
currentGain, targetGain float64,
duration time.Duration,
) error {
gainDiff := targetGain - currentGain
stepInterval := 100 * time.Millisecond
totalSteps := int(duration / stepInterval)
if totalSteps < 1 {
totalSteps = 1
stepInterval = duration
}
stepIncrement := gainDiff / float64(totalSteps)
log.Debugf("Adjusting Headamp %d gain from %.2f dB to %.2f dB over %v...\n",
index, currentGain, targetGain, duration)
for step := 1; step <= totalSteps; step++ {
newGain := currentGain + (stepIncrement * float64(step))
if step == totalSteps {
newGain = targetGain
}
err := ctx.Client.HeadAmp.SetGain(index, newGain)
if err != nil {
return err
}
if step%10 == 0 || step == totalSteps {
log.Debugf(" Step %d/%d: %.2f dB\n", step, totalSteps, newGain)
}
if step < totalSteps {
time.Sleep(stepInterval)
}
}
return nil
}
// HeadampPhantomCmd defines the command for getting or setting the phantom power state of a headamp, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type HeadampPhantomCmd struct {
State *string `help:"The phantom power state of the headamp." arg:"" enum:"true,on,false,off" optional:""`
}
// Validate checks if the provided phantom power state is valid and normalises it to "true" or "false".
func (cmd *HeadampPhantomCmd) Validate() error {
if cmd.State != nil {
switch *cmd.State {
case trueStr, "on":
*cmd.State = trueStr
case falseStr, "off":
*cmd.State = falseStr
default:
return fmt.Errorf("invalid phantom power state: %s", *cmd.State)
}
}
return nil
}
// Run executes the HeadampPhantomCmd command, either retrieving the current phantom power state of the headamp or setting it based on the provided argument.
func (cmd *HeadampPhantomCmd) Run(ctx *context, headamp *HeadampCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.HeadAmp.PhantomPower(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get headamp phantom power state: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d phantom power: %t\n", headamp.Index.Index, resp)
return nil
}
if err := ctx.Client.HeadAmp.SetPhantomPower(
headamp.Index.Index,
*cmd.State == trueStr,
); err != nil {
return fmt.Errorf("failed to set headamp phantom power state: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d phantom power set to: %s\n", headamp.Index.Index, *cmd.State)
return nil
}

17
cmd/x32-cli/info.go Normal file
View File

@ -0,0 +1,17 @@
package main
import "fmt"
type InfoCmd struct{}
func (cmd *InfoCmd) Run(ctx *context) error { // nolint: unparam
fmt.Fprintf(
ctx.Out,
"Host: %s | Name: %s | Model: %s | Firmware: %s\n",
ctx.Client.Info.Host,
ctx.Client.Info.Name,
ctx.Client.Info.Model,
ctx.Client.Info.Firmware,
)
return nil
}

510
cmd/x32-cli/main.go Normal file
View File

@ -0,0 +1,510 @@
// Package main implements the command-line interface for controlling an X32 digital mixer.
package main
import (
"fmt"
"time"
)
// MainCmdGroup defines the command group for controlling the Main L/R output, including commands for mute state, fader level, and fade-in/fade-out times.
type MainCmdGroup struct {
Mute MainMuteCmd `help:"Get or set the mute state of the Main L/R output." cmd:""`
Fader MainFaderCmd `help:"Get or set the fader level of the Main L/R output." cmd:""`
Fadein MainFadeinCmd `help:"Fade in the Main L/R output over a specified duration." cmd:""`
Fadeout MainFadeoutCmd `help:"Fade out the Main L/R output over a specified duration." cmd:""`
Eq MainEqCmdGroup `help:"Commands for controlling the equaliser settings of the Main L/R output." cmd:"eq"`
Comp MainCompCmdGroup `help:"Commands for controlling the compressor settings of the Main L/R output." cmd:"comp"`
}
// MainMuteCmd defines the command for getting or setting the mute state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainMuteCmd struct {
Mute *string `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainMuteCmd command, either retrieving the current mute state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainMuteCmd) Run(ctx *context) error {
if cmd.Mute == nil {
resp, err := ctx.Client.Main.Mute()
if err != nil {
return fmt.Errorf("failed to get Main L/R mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R mute state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.SetMute(*cmd.Mute == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R mute state set to: %s\n", *cmd.Mute)
return nil
}
// MainFaderCmd defines the command for getting or setting the fader level of the Main L/R output, allowing users to specify the desired level in dB.
type MainFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set. If not provided, the current level will be printed." optional:""`
}
// Run executes the MainFaderCmd command, either retrieving the current fader level of the Main L/R output or setting it based on the provided argument.
func (cmd *MainFaderCmd) Run(ctx *context) error {
if cmd.Level == nil {
resp, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R fader level: %.2f\n", resp)
return nil
}
if err := ctx.Client.Main.SetFader(*cmd.Level); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R fader level set to: %.2f\n", *cmd.Level)
return nil
}
// MainFadeinCmd defines the command for getting or setting the fade-in time of the Main L/R output, allowing users to specify the desired duration for the fade-in effect.
type MainFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-in. If not provided, the current target level will be printed." default:"0.0" arg:""`
}
// Run executes the MainFadeinCmd command, either retrieving the current fade-in time of the Main L/R output or setting it based on the provided argument, with an optional target level for the fade-in effect.
func (cmd *MainFadeinCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or above the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.Main.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main L/R fade-in completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainFadeoutCmd defines the command for getting or setting the fade-out time of the Main L/R output, allowing users to specify the desired duration for the fade-out effect and an optional target level to fade out to.
type MainFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-out. If not provided, the current target level will be printed." default:"-90.0" arg:""`
}
// Run executes the MainFadeoutCmd command, either retrieving the current fade-out time of the Main L/R output or setting it based on the provided argument, with an optional target level for the fade-out effect.
func (cmd *MainFadeoutCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or below the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.Main.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main L/R fade-out completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainEqCmdGroup defines the command group for controlling the equaliser settings of the Main L/R output, including commands for getting or setting the EQ parameters.
type MainEqCmdGroup struct {
On MainEqOnCmd `help:"Get or set the EQ on/off state of the Main L/R output." cmd:"on"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain MainEqBandGainCmd `help:"Get or set the gain of the specified EQ band." cmd:"gain"`
Freq MainEqBandFreqCmd `help:"Get or set the frequency of the specified EQ band." cmd:"freq"`
Q MainEqBandQCmd `help:"Get or set the Q factor of the specified EQ band." cmd:"q"`
Type MainEqBandTypeCmd `help:"Get or set the type of the specified EQ band." cmd:"type"`
} `help:"Commands for controlling individual EQ bands of the Main L/R output." arg:""`
}
// Validate checks if the provided EQ band number is within the valid range (1-6) for the Main L/R output.
func (cmd *MainEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// MainEqOnCmd defines the command for getting or setting the EQ on/off state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainEqOnCmd struct {
Enable *string `arg:"" help:"The EQ on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainEqOnCmd command, either retrieving the current EQ on/off state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Main.Eq.On(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.Eq.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on the Main L/R output, allowing users to specify the desired gain in dB.
type MainEqBandGainCmd struct {
Level *float64 `arg:"" help:"The gain level to set for the specified EQ band. If not provided, the current gain will be printed." optional:""`
}
// Run executes the MainEqBandGainCmd command, either retrieving the current gain of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandGainCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Main.Eq.Gain(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d gain: %.2f dB\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetGain(0, *mainEq.Band.Band, *cmd.Level); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Main L/R EQ band %d gain set to: %.2f dB\n",
*mainEq.Band.Band,
*cmd.Level,
)
return nil
}
// MainEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on the Main L/R output, allowing users to specify the desired frequency in Hz.
type MainEqBandFreqCmd struct {
Frequency *float64 `arg:"" help:"The frequency to set for the specified EQ band. If not provided, the current frequency will be printed." optional:""`
}
// Run executes the MainEqBandFreqCmd command, either retrieving the current frequency of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandFreqCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Frequency == nil {
resp, err := ctx.Client.Main.Eq.Frequency(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main L/R EQ band %d frequency: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d frequency: %.2f Hz\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetFrequency(0, *mainEq.Band.Band, *cmd.Frequency); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d frequency: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Main L/R EQ band %d frequency set to: %.2f Hz\n",
*mainEq.Band.Band,
*cmd.Frequency,
)
return nil
}
// MainEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on the Main L/R output, allowing users to specify the desired Q factor.
type MainEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the specified EQ band. If not provided, the current Q factor will be printed." optional:""`
}
// Run executes the MainEqBandQCmd command, either retrieving the current Q factor of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandQCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Q == nil {
resp, err := ctx.Client.Main.Eq.Q(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main L/R EQ band %d Q factor: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d Q factor: %.2f\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetQ(0, *mainEq.Band.Band, *cmd.Q); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d Q factor: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d Q factor set to: %.2f\n", *mainEq.Band.Band, *cmd.Q)
return nil
}
// MainEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on the Main L/R output, allowing users to specify the desired type as "peaking", "low_shelf", "high_shelf", "low_pass", or "high_pass".
type MainEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the specified EQ band. If not provided, the current type will be printed." optional:"" enum:"peaking,low_shelf,high_shelf,low_pass,high_pass"`
}
// Run executes the MainEqBandTypeCmd command, either retrieving the current type of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandTypeCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Type == nil {
resp, err := ctx.Client.Main.Eq.Type(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d type: %s\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetType(0, *mainEq.Band.Band, *cmd.Type); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d type set to: %s\n", *mainEq.Band.Band, *cmd.Type)
return nil
}
// MainCompCmdGroup defines the command group for controlling the compressor settings of the Main L/R output, including commands for getting or setting the compressor parameters.
type MainCompCmdGroup struct {
On MainCompOnCmd `help:"Get or set the compressor on/off state of the Main L/R output." cmd:"on"`
Mode MainCompModeCmd `help:"Get or set the compressor mode of the Main L/R output." cmd:"mode"`
Threshold MainCompThresholdCmd `help:"Get or set the compressor threshold of the Main L/R output." cmd:"threshold"`
Ratio MainCompRatioCmd `help:"Get or set the compressor ratio of the Main L/R output." cmd:"ratio"`
Mix MainCompMixCmd `help:"Get or set the compressor mix level of the Main L/R output." cmd:"mix"`
Makeup MainCompMakeupCmd `help:"Get or set the compressor makeup gain of the Main L/R output." cmd:"makeup"`
Attack MainCompAttackCmd `help:"Get or set the compressor attack time of the Main L/R output." cmd:"attack"`
Hold MainCompHoldCmd `help:"Get or set the compressor hold time of the Main L/R output." cmd:"hold"`
Release MainCompReleaseCmd `help:"Get or set the compressor release time of the Main L/R output." cmd:"release"`
}
// MainCompOnCmd defines the command for getting or setting the compressor on/off state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainCompOnCmd struct {
Enable *string `arg:"" help:"The compressor on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainCompOnCmd command, either retrieving the current compressor on/off state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Main.Comp.On(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainCompModeCmd defines the command for getting or setting the compressor mode of the Main L/R output, allowing users to specify the desired mode as "comp" or "exp".
type MainCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set. If not provided, the current mode will be printed." optional:"" enum:"comp,exp"`
}
// Run executes the MainCompModeCmd command, either retrieving the current compressor mode of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompModeCmd) Run(ctx *context) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Main.Comp.Mode(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mode: %s\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMode(0, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set Main L/R compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mode set to: %s\n", *cmd.Mode)
return nil
}
// MainCompThresholdCmd defines the command for getting or setting the compressor threshold of the Main L/R output, allowing users to specify the desired threshold in dB.
type MainCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set. If not provided, the current threshold will be printed." optional:""`
}
// Run executes the MainCompThresholdCmd command, either retrieving the current compressor threshold of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompThresholdCmd) Run(ctx *context) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Main.Comp.Threshold(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor threshold: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetThreshold(0, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set Main L/R compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor threshold set to: %.2f dB\n", *cmd.Threshold)
return nil
}
// MainCompRatioCmd defines the command for getting or setting the compressor ratio of the Main L/R output, allowing users to specify the desired ratio.
type MainCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current ratio will be printed." optional:""`
}
// Run executes the MainCompRatioCmd command, either retrieving the current compressor ratio of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompRatioCmd) Run(ctx *context) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Main.Comp.Ratio(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor ratio: %.2f\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetRatio(0, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set Main L/R compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor ratio set to: %.2f\n", *cmd.Ratio)
return nil
}
// MainCompMixCmd defines the command for getting or setting the compressor mix level of the Main L/R output, allowing users to specify the desired mix level in percentage.
type MainCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set. If not provided, the current mix level will be printed." optional:""`
}
// Run executes the MainCompMixCmd command, either retrieving the current compressor mix level of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompMixCmd) Run(ctx *context) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Main.Comp.Mix(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mix level: %.2f%%\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMix(0, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set Main L/R compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mix level set to: %.2f%%\n", *cmd.Mix)
return nil
}
// MainCompMakeupCmd defines the command for getting or setting the compressor makeup gain of the Main L/R output, allowing users to specify the desired makeup gain in dB.
type MainCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set. If not provided, the current makeup gain will be printed." optional:""`
}
// Run executes the MainCompMakeupCmd command, either retrieving the current compressor makeup gain of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompMakeupCmd) Run(ctx *context) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Main.Comp.Makeup(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor makeup gain: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMakeup(0, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set Main L/R compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor makeup gain set to: %.2f dB\n", *cmd.Makeup)
return nil
}
// MainCompAttackCmd defines the command for getting or setting the compressor attack time of the Main L/R output, allowing users to specify the desired attack time in milliseconds.
type MainCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set. If not provided, the current attack time will be printed." optional:""`
}
// Run executes the MainCompAttackCmd command, either retrieving the current compressor attack time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompAttackCmd) Run(ctx *context) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Main.Comp.Attack(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor attack time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetAttack(0, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set Main L/R compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor attack time set to: %.2f ms\n", *cmd.Attack)
return nil
}
// MainCompHoldCmd defines the command for getting or setting the compressor hold time of the Main L/R output, allowing users to specify the desired hold time in milliseconds.
type MainCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set. If not provided, the current hold time will be printed." optional:""`
}
// Run executes the MainCompHoldCmd command, either retrieving the current compressor hold time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompHoldCmd) Run(ctx *context) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Main.Comp.Hold(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor hold time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetHold(0, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set Main L/R compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor hold time set to: %.2f ms\n", *cmd.Hold)
return nil
}
// MainCompReleaseCmd defines the command for getting or setting the compressor release time of the Main L/R output, allowing users to specify the desired release time in milliseconds.
type MainCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set. If not provided, the current release time will be printed." optional:""`
}
// Run executes the MainCompReleaseCmd command, either retrieving the current compressor release time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompReleaseCmd) Run(ctx *context) error {
if cmd.Release == nil {
resp, err := ctx.Client.Main.Comp.Release(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor release time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetRelease(0, *cmd.Release); err != nil {
return fmt.Errorf("failed to set Main L/R compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor release time set to: %.2f ms\n", *cmd.Release)
return nil
}

529
cmd/x32-cli/mainmono.go Normal file
View File

@ -0,0 +1,529 @@
package main
import (
"fmt"
"time"
)
// MainMonoCmdGroup defines the command group for controlling the Main Mono output, including commands for mute state, fader level, and fade-in/fade-out times.
type MainMonoCmdGroup struct {
Mute MainMonoMuteCmd `help:"Get or set the mute state of the Main Mono output." cmd:""`
Fader MainMonoFaderCmd `help:"Get or set the fader level of the Main Mono output." cmd:""`
Fadein MainMonoFadeinCmd `help:"Fade in the Main Mono output over a specified duration." cmd:""`
Fadeout MainMonoFadeoutCmd `help:"Fade out the Main Mono output over a specified duration." cmd:""`
Eq MainMonoEqCmdGroup `help:"Commands for controlling the equaliser settings of the Main Mono output." cmd:"eq"`
Comp MainMonoCompCmdGroup `help:"Commands for controlling the compressor settings of the Main Mono output." cmd:"comp"`
}
// MainMonoMuteCmd defines the command for getting or setting the mute state of the Main Mono output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainMonoMuteCmd struct {
Mute *string `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainMonoMuteCmd command, either retrieving the current mute state of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoMuteCmd) Run(ctx *context) error {
if cmd.Mute == nil {
resp, err := ctx.Client.MainMono.Mute()
if err != nil {
return fmt.Errorf("failed to get Main Mono mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono mute state: %t\n", resp)
return nil
}
if err := ctx.Client.MainMono.SetMute(*cmd.Mute == trueStr); err != nil {
return fmt.Errorf("failed to set Main Mono mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono mute state set to: %s\n", *cmd.Mute)
return nil
}
// MainMonoFaderCmd defines the command for getting or setting the fader level of the Main Mono output, allowing users to specify the desired level in dB.
type MainMonoFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set. If not provided, the current level will be printed." optional:""`
}
// Run executes the MainMonoFaderCmd command, either retrieving the current fader level of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoFaderCmd) Run(ctx *context) error {
if cmd.Level == nil {
resp, err := ctx.Client.MainMono.Fader()
if err != nil {
return fmt.Errorf("failed to get Main Mono fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono fader level: %.2f\n", resp)
return nil
}
if err := ctx.Client.MainMono.SetFader(*cmd.Level); err != nil {
return fmt.Errorf("failed to set Main Mono fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono fader level set to: %.2f\n", *cmd.Level)
return nil
}
// MainMonoFadeinCmd defines the command for getting or setting the fade-in time of the Main Mono output, allowing users to specify the desired duration for the fade-in effect.
type MainMonoFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-in. If not provided, the current target level will be printed." default:"0.0" arg:""`
}
// Run executes the MainMonoFadeinCmd command, either retrieving the current fade-in time of the Main Mono output or setting it based on the provided argument, with an optional target level for the fade-in effect.
func (cmd *MainMonoFadeinCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.MainMono.Fader()
if err != nil {
return fmt.Errorf("failed to get Main Mono fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or above the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.MainMono.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main Mono fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main Mono fade-in completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainMonoFadeoutCmd defines the command for getting or setting the fade-out time of the Main Mono output, allowing users to specify the desired duration for the fade-out effect and an optional target level to fade out to.
type MainMonoFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-out. If not provided, the current target level will be printed." default:"-90.0" arg:""`
}
// Run executes the MainMonoFadeoutCmd command, either retrieving the current fade-out time of the Main Mono output or setting it based on the provided argument, with an optional target level for the fade-out effect.
func (cmd *MainMonoFadeoutCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.MainMono.Fader()
if err != nil {
return fmt.Errorf("failed to get Main Mono fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or below the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.MainMono.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main Mono fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main Mono fade-out completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainMonoEqCmdGroup defines the command group for controlling the equaliser settings of the Main Mono output, including commands for getting or setting the EQ parameters.
type MainMonoEqCmdGroup struct {
On MainMonoEqOnCmd `help:"Get or set the EQ on/off state of the Main Mono output." cmd:"on"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain MainMonoEqBandGainCmd `help:"Get or set the gain of the specified EQ band." cmd:"gain"`
Freq MainMonoEqBandFreqCmd `help:"Get or set the frequency of the specified EQ band." cmd:"freq"`
Q MainMonoEqBandQCmd `help:"Get or set the Q factor of the specified EQ band." cmd:"q"`
Type MainMonoEqBandTypeCmd `help:"Get or set the type of the specified EQ band." cmd:"type"`
} `help:"Commands for controlling individual EQ bands of the Main Mono output." arg:""`
}
// Validate checks if the provided EQ band number is within the valid range (1-6) for the Main Mono output.
func (cmd *MainMonoEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// MainMonoEqOnCmd defines the command for getting or setting the EQ on/off state of the Main Mono output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainMonoEqOnCmd struct {
Enable *string `arg:"" help:"The EQ on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainMonoEqOnCmd command, either retrieving the current EQ on/off state of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoEqOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.MainMono.Eq.On(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.MainMono.Eq.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main Mono EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainMonoEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on the Main Mono output, allowing users to specify the desired gain in dB.
type MainMonoEqBandGainCmd struct {
Level *float64 `arg:"" help:"The gain level to set for the specified EQ band. If not provided, the current gain will be printed." optional:""`
}
// Run executes the MainMonoEqBandGainCmd command, either retrieving the current gain of a specific EQ band on the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoEqBandGainCmd) Run(
ctx *context,
mainEq *MainMonoEqCmdGroup,
) error {
if cmd.Level == nil {
resp, err := ctx.Client.MainMono.Eq.Gain(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main Mono EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d gain: %.2f dB\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.MainMono.Eq.SetGain(0, *mainEq.Band.Band, *cmd.Level); err != nil {
return fmt.Errorf("failed to set Main Mono EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Main Mono EQ band %d gain set to: %.2f dB\n",
*mainEq.Band.Band,
*cmd.Level,
)
return nil
}
// MainMonoEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on the Main Mono output, allowing users to specify the desired frequency in Hz.
type MainMonoEqBandFreqCmd struct {
Frequency *float64 `arg:"" help:"The frequency to set for the specified EQ band. If not provided, the current frequency will be printed." optional:""`
}
// Run executes the MainMonoEqBandFreqCmd command, either retrieving the current frequency of a specific EQ band on the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoEqBandFreqCmd) Run(
ctx *context,
mainEq *MainMonoEqCmdGroup,
) error {
if cmd.Frequency == nil {
resp, err := ctx.Client.MainMono.Eq.Frequency(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main Mono EQ band %d frequency: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d frequency: %.2f Hz\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.MainMono.Eq.SetFrequency(
0,
*mainEq.Band.Band,
*cmd.Frequency,
); err != nil {
return fmt.Errorf(
"failed to set Main Mono EQ band %d frequency: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(
ctx.Out,
"Main Mono EQ band %d frequency set to: %.2f Hz\n",
*mainEq.Band.Band,
*cmd.Frequency,
)
return nil
}
// MainMonoEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on the Main Mono output, allowing users to specify the desired Q factor.
type MainMonoEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the specified EQ band. If not provided, the current Q factor will be printed." optional:""`
}
// Run executes the MainMonoEqBandQCmd command, either retrieving the current Q factor of a specific EQ band on the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoEqBandQCmd) Run(
ctx *context,
mainEq *MainMonoEqCmdGroup,
) error {
if cmd.Q == nil {
resp, err := ctx.Client.MainMono.Eq.Q(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main Mono EQ band %d Q factor: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d Q factor: %.2f\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.MainMono.Eq.SetQ(0, *mainEq.Band.Band, *cmd.Q); err != nil {
return fmt.Errorf("failed to set Main Mono EQ band %d Q factor: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d Q factor set to: %.2f\n", *mainEq.Band.Band, *cmd.Q)
return nil
}
// MainMonoEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on the Main Mono output, allowing users to specify the desired type as "peaking", "low_shelf", "high_shelf", "low_pass", or "high_pass".
type MainMonoEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the specified EQ band. If not provided, the current type will be printed." optional:"" enum:"peaking,low_shelf,high_shelf,low_pass,high_pass"`
}
// Run executes the MainMonoEqBandTypeCmd command, either retrieving the current type of a specific EQ band on the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoEqBandTypeCmd) Run(
ctx *context,
mainEq *MainMonoEqCmdGroup,
) error {
if cmd.Type == nil {
resp, err := ctx.Client.MainMono.Eq.Type(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main Mono EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d type: %s\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.MainMono.Eq.SetType(0, *mainEq.Band.Band, *cmd.Type); err != nil {
return fmt.Errorf("failed to set Main Mono EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main Mono EQ band %d type set to: %s\n", *mainEq.Band.Band, *cmd.Type)
return nil
}
// MainMonoCompCmdGroup defines the command group for controlling the compressor settings of the Main Mono output, including commands for getting or setting the compressor parameters.
type MainMonoCompCmdGroup struct {
On MainMonoCompOnCmd `help:"Get or set the compressor on/off state of the Main Mono output." cmd:"on"`
Mode MainMonoCompModeCmd `help:"Get or set the compressor mode of the Main Mono output." cmd:"mode"`
Threshold MainMonoCompThresholdCmd `help:"Get or set the compressor threshold of the Main Mono output." cmd:"threshold"`
Ratio MainMonoCompRatioCmd `help:"Get or set the compressor ratio of the Main Mono output." cmd:"ratio"`
Mix MainMonoCompMixCmd `help:"Get or set the compressor mix level of the Main Mono output." cmd:"mix"`
Makeup MainMonoCompMakeupCmd `help:"Get or set the compressor makeup gain of the Main Mono output." cmd:"makeup"`
Attack MainMonoCompAttackCmd `help:"Get or set the compressor attack time of the Main Mono output." cmd:"attack"`
Hold MainMonoCompHoldCmd `help:"Get or set the compressor hold time of the Main Mono output." cmd:"hold"`
Release MainMonoCompReleaseCmd `help:"Get or set the compressor release time of the Main Mono output." cmd:"release"`
}
// MainMonoCompOnCmd defines the command for getting or setting the compressor on/off state of the Main Mono output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainMonoCompOnCmd struct {
Enable *string `arg:"" help:"The compressor on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainMonoCompOnCmd command, either retrieving the current compressor on/off state of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.MainMono.Comp.On(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main Mono compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainMonoCompModeCmd defines the command for getting or setting the compressor mode of the Main Mono output, allowing users to specify the desired mode as "comp" or "exp".
type MainMonoCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set. If not provided, the current mode will be printed." optional:"" enum:"comp,exp"`
}
// Run executes the MainMonoCompModeCmd command, either retrieving the current compressor mode of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompModeCmd) Run(ctx *context) error {
if cmd.Mode == nil {
resp, err := ctx.Client.MainMono.Comp.Mode(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor mode: %s\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetMode(0, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set Main Mono compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor mode set to: %s\n", *cmd.Mode)
return nil
}
// MainMonoCompThresholdCmd defines the command for getting or setting the compressor threshold of the Main Mono output, allowing users to specify the desired threshold in dB.
type MainMonoCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set. If not provided, the current threshold will be printed." optional:""`
}
// Run executes the MainMonoCompThresholdCmd command, either retrieving the current compressor threshold of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompThresholdCmd) Run(ctx *context) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.MainMono.Comp.Threshold(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor threshold: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetThreshold(0, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set Main Mono compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor threshold set to: %.2f dB\n", *cmd.Threshold)
return nil
}
// MainMonoCompRatioCmd defines the command for getting or setting the compressor ratio of the Main Mono output, allowing users to specify the desired ratio.
type MainMonoCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current ratio will be printed." optional:""`
}
// Run executes the MainMonoCompRatioCmd command, either retrieving the current compressor ratio of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompRatioCmd) Run(ctx *context) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.MainMono.Comp.Ratio(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor ratio: %.2f\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetRatio(0, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set Main Mono compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor ratio set to: %.2f\n", *cmd.Ratio)
return nil
}
// MainMonoCompMixCmd defines the command for getting or setting the compressor mix level of the Main Mono output, allowing users to specify the desired mix level in percentage.
type MainMonoCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set. If not provided, the current mix level will be printed." optional:""`
}
// Run executes the MainMonoCompMixCmd command, either retrieving the current compressor mix level of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompMixCmd) Run(ctx *context) error {
if cmd.Mix == nil {
resp, err := ctx.Client.MainMono.Comp.Mix(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor mix level: %.2f%%\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetMix(0, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set Main Mono compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor mix level set to: %.2f%%\n", *cmd.Mix)
return nil
}
// MainMonoCompMakeupCmd defines the command for getting or setting the compressor makeup gain of the Main Mono output, allowing users to specify the desired makeup gain in dB.
type MainMonoCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set. If not provided, the current makeup gain will be printed." optional:""`
}
// Run executes the MainMonoCompMakeupCmd command, either retrieving the current compressor makeup gain of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompMakeupCmd) Run(ctx *context) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.MainMono.Comp.Makeup(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor makeup gain: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetMakeup(0, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set Main Mono compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor makeup gain set to: %.2f dB\n", *cmd.Makeup)
return nil
}
// MainMonoCompAttackCmd defines the command for getting or setting the compressor attack time of the Main Mono output, allowing users to specify the desired attack time in milliseconds.
type MainMonoCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set. If not provided, the current attack time will be printed." optional:""`
}
// Run executes the MainMonoCompAttackCmd command, either retrieving the current compressor attack time of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompAttackCmd) Run(ctx *context) error {
if cmd.Attack == nil {
resp, err := ctx.Client.MainMono.Comp.Attack(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor attack time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetAttack(0, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set Main Mono compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor attack time set to: %.2f ms\n", *cmd.Attack)
return nil
}
// MainMonoCompHoldCmd defines the command for getting or setting the compressor hold time of the Main Mono output, allowing users to specify the desired hold time in milliseconds.
type MainMonoCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set. If not provided, the current hold time will be printed." optional:""`
}
// Run executes the MainMonoCompHoldCmd command, either retrieving the current compressor hold time of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompHoldCmd) Run(ctx *context) error {
if cmd.Hold == nil {
resp, err := ctx.Client.MainMono.Comp.Hold(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor hold time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetHold(0, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set Main Mono compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor hold time set to: %.2f ms\n", *cmd.Hold)
return nil
}
// MainMonoCompReleaseCmd defines the command for getting or setting the compressor release time of the Main Mono output, allowing users to specify the desired release time in milliseconds.
type MainMonoCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set. If not provided, the current release time will be printed." optional:""`
}
// Run executes the MainMonoCompReleaseCmd command, either retrieving the current compressor release time of the Main Mono output or setting it based on the provided argument.
func (cmd *MainMonoCompReleaseCmd) Run(ctx *context) error {
if cmd.Release == nil {
resp, err := ctx.Client.MainMono.Comp.Release(0)
if err != nil {
return fmt.Errorf("failed to get Main Mono compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor release time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.MainMono.Comp.SetRelease(0, *cmd.Release); err != nil {
return fmt.Errorf("failed to set Main Mono compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main Mono compressor release time set to: %.2f ms\n", *cmd.Release)
return nil
}

551
cmd/x32-cli/matrix.go Normal file
View File

@ -0,0 +1,551 @@
package main
import (
"fmt"
"time"
)
// MatrixCmdGroup defines the command group for controlling the Matrix outputs, including commands for mute state, fader level, and fade-in/fade-out times.
type MatrixCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the Matrix output (1-6)."`
Mute MatrixMuteCmd `help:"Get or set the mute state of the Matrix output." cmd:""`
Fader MatrixFaderCmd `help:"Get or set the fader level of the Matrix output." cmd:""`
Fadein MatrixFadeinCmd `help:"Fade in the Matrix output over a specified duration." cmd:""`
Fadeout MatrixFadeoutCmd `help:"Fade out the Matrix output over a specified duration." cmd:""`
Eq MatrixEqCmdGroup `help:"Commands for controlling the equaliser settings of the Matrix output." cmd:"eq"`
Comp MatrixCompCmdGroup `help:"Commands for controlling the compressor settings of the Matrix output." cmd:"comp"`
} `help:"Commands for controlling individual Matrix outputs." arg:""`
}
func (cmd *MatrixCmdGroup) Validate() error {
if cmd.Index.Index < 1 || cmd.Index.Index > 6 {
return fmt.Errorf("matrix output index must be between 1 and 6, got %d", cmd.Index.Index)
}
return nil
}
// MatrixMuteCmd defines the command for getting or setting the mute state of the Matrix output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MatrixMuteCmd struct {
Mute *string `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MatrixMuteCmd command, either retrieving the current mute state of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixMuteCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Mute == nil {
resp, err := ctx.Client.Matrix.Mute(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix mute state: %t\n", resp)
return nil
}
if err := ctx.Client.Matrix.SetMute(matrix.Index.Index, *cmd.Mute == trueStr); err != nil {
return fmt.Errorf("failed to set Matrix mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix mute state set to: %s\n", *cmd.Mute)
return nil
}
// MatrixFaderCmd defines the command for getting or setting the fader level of the Matrix output, allowing users to specify the desired level in dB.
type MatrixFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set. If not provided, the current level will be printed." optional:""`
}
// Run executes the MatrixFaderCmd command, either retrieving the current fader level of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixFaderCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Matrix.Fader(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix fader level: %.2f\n", resp)
return nil
}
if err := ctx.Client.Matrix.SetFader(matrix.Index.Index, *cmd.Level); err != nil {
return fmt.Errorf("failed to set Matrix fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix fader level set to: %.2f\n", *cmd.Level)
return nil
}
// MatrixFadeinCmd defines the command for getting or setting the fade-in time of the Matrix output, allowing users to specify the desired duration for the fade-in effect.
type MatrixFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-in. If not provided, the current target level will be printed." default:"0.0" arg:""`
}
// Run executes the MatrixFadeinCmd command, either retrieving the current fade-in time of the Matrix output or setting it based on the provided argument, with an optional target level for the fade-in effect.
func (cmd *MatrixFadeinCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
currentLevel, err := ctx.Client.Matrix.Fader(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or above the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.Matrix.SetFader(matrix.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set Matrix fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Matrix fade-in completed. Final level: %.2f\n", currentLevel)
return nil
}
// MatrixFadeoutCmd defines the command for getting or setting the fade-out time of the Matrix output, allowing users to specify the desired duration for the fade-out effect and an optional target level to fade out to.
type MatrixFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-out. If not provided, the current target level will be printed." default:"-90.0" arg:""`
}
// Run executes the MatrixFadeoutCmd command, either retrieving the current fade-out time of the Matrix output or setting it based on the provided argument, with an optional target level for the fade-out effect.
func (cmd *MatrixFadeoutCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
currentLevel, err := ctx.Client.Matrix.Fader(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or below the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.Matrix.SetFader(matrix.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set Matrix fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Matrix fade-out completed. Final level: %.2f\n", currentLevel)
return nil
}
// MatrixEqCmdGroup defines the command group for controlling the equaliser settings of the Matrix output, including commands for getting or setting the EQ parameters.
type MatrixEqCmdGroup struct {
On MatrixEqOnCmd `help:"Get or set the EQ on/off state of the Matrix output." cmd:"on"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain MatrixEqBandGainCmd `help:"Get or set the gain of the specified EQ band." cmd:"gain"`
Freq MatrixEqBandFreqCmd `help:"Get or set the frequency of the specified EQ band." cmd:"freq"`
Q MatrixEqBandQCmd `help:"Get or set the Q factor of the specified EQ band." cmd:"q"`
Type MatrixEqBandTypeCmd `help:"Get or set the type of the specified EQ band." cmd:"type"`
} `help:"Commands for controlling individual EQ bands of the Matrix output." arg:""`
}
// Validate checks if the provided EQ band number is within the valid range (1-6) for the Matrix output.
func (cmd *MatrixEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// MatrixEqOnCmd defines the command for getting or setting the EQ on/off state of the Matrix output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MatrixEqOnCmd struct {
Enable *string `arg:"" help:"The EQ on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MatrixEqOnCmd command, either retrieving the current EQ on/off state of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixEqOnCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Matrix.Eq.On(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Matrix.Eq.SetOn(matrix.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Matrix EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MatrixEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on the Matrix output, allowing users to specify the desired gain in dB.
type MatrixEqBandGainCmd struct {
Level *float64 `arg:"" help:"The gain level to set for the specified EQ band. If not provided, the current gain will be printed." optional:""`
}
// Run executes the MatrixEqBandGainCmd command, either retrieving the current gain of a specific EQ band on the Matrix output or setting it based on the provided argument.
func (cmd *MatrixEqBandGainCmd) Run(
ctx *context,
matrix *MatrixCmdGroup,
matrixEq *MatrixEqCmdGroup,
) error {
if cmd.Level == nil {
resp, err := ctx.Client.Matrix.Eq.Gain(matrix.Index.Index, *matrixEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Matrix EQ band %d gain: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d gain: %.2f dB\n", *matrixEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Matrix.Eq.SetGain(
matrix.Index.Index,
*matrixEq.Band.Band,
*cmd.Level,
); err != nil {
return fmt.Errorf("failed to set Matrix EQ band %d gain: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Matrix EQ band %d gain set to: %.2f dB\n",
*matrixEq.Band.Band,
*cmd.Level,
)
return nil
}
// MatrixEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on the Matrix output, allowing users to specify the desired frequency in Hz.
type MatrixEqBandFreqCmd struct {
Frequency *float64 `arg:"" help:"The frequency to set for the specified EQ band. If not provided, the current frequency will be printed." optional:""`
}
// Run executes the MatrixEqBandFreqCmd command, either retrieving the current frequency of a specific EQ band on the Matrix output or setting it based on the provided argument.
func (cmd *MatrixEqBandFreqCmd) Run(
ctx *context,
matrix *MatrixCmdGroup,
matrixEq *MatrixEqCmdGroup,
) error {
if cmd.Frequency == nil {
resp, err := ctx.Client.Matrix.Eq.Frequency(matrix.Index.Index, *matrixEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Matrix EQ band %d frequency: %w",
*matrixEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d frequency: %.2f Hz\n", *matrixEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Matrix.Eq.SetFrequency(
matrix.Index.Index,
*matrixEq.Band.Band,
*cmd.Frequency,
); err != nil {
return fmt.Errorf("failed to set Matrix EQ band %d frequency: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Matrix EQ band %d frequency set to: %.2f Hz\n",
*matrixEq.Band.Band,
*cmd.Frequency,
)
return nil
}
// MatrixEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on the Matrix output, allowing users to specify the desired Q factor.
type MatrixEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the specified EQ band. If not provided, the current Q factor will be printed." optional:""`
}
// Run executes the MatrixEqBandQCmd command, either retrieving the current Q factor of a specific EQ band on the Matrix output or setting it based on the provided argument.
func (cmd *MatrixEqBandQCmd) Run(
ctx *context,
matrix *MatrixCmdGroup,
matrixEq *MatrixEqCmdGroup,
) error {
if cmd.Q == nil {
resp, err := ctx.Client.Matrix.Eq.Q(matrix.Index.Index, *matrixEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Matrix EQ band %d Q factor: %w",
*matrixEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d Q factor: %.2f\n", *matrixEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Matrix.Eq.SetQ(
matrix.Index.Index,
*matrixEq.Band.Band,
*cmd.Q,
); err != nil {
return fmt.Errorf("failed to set Matrix EQ band %d Q factor: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d Q factor set to: %.2f\n", *matrixEq.Band.Band, *cmd.Q)
return nil
}
// MatrixEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on the Matrix output, allowing users to specify the desired type as "peaking", "low_shelf", "high_shelf", "low_pass", or "high_pass".
type MatrixEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the specified EQ band. If not provided, the current type will be printed." optional:"" enum:"peaking,low_shelf,high_shelf,low_pass,high_pass"`
}
// Run executes the MatrixEqBandTypeCmd command, either retrieving the current type of a specific EQ band on the Matrix output or setting it based on the provided argument.
func (cmd *MatrixEqBandTypeCmd) Run(
ctx *context,
matrix *MatrixCmdGroup,
matrixEq *MatrixEqCmdGroup,
) error {
if cmd.Type == nil {
resp, err := ctx.Client.Matrix.Eq.Type(matrix.Index.Index, *matrixEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Matrix EQ band %d type: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d type: %s\n", *matrixEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Matrix.Eq.SetType(
matrix.Index.Index,
*matrixEq.Band.Band,
*cmd.Type,
); err != nil {
return fmt.Errorf("failed to set Matrix EQ band %d type: %w", *matrixEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Matrix EQ band %d type set to: %s\n", *matrixEq.Band.Band, *cmd.Type)
return nil
}
// MatrixCompCmdGroup defines the command group for controlling the compressor settings of the Matrix output, including commands for getting or setting the compressor parameters.
type MatrixCompCmdGroup struct {
On MatrixCompOnCmd `help:"Get or set the compressor on/off state of the Matrix output." cmd:"on"`
Mode MatrixCompModeCmd `help:"Get or set the compressor mode of the Matrix output." cmd:"mode"`
Threshold MatrixCompThresholdCmd `help:"Get or set the compressor threshold of the Matrix output." cmd:"threshold"`
Ratio MatrixCompRatioCmd `help:"Get or set the compressor ratio of the Matrix output." cmd:"ratio"`
Mix MatrixCompMixCmd `help:"Get or set the compressor mix level of the Matrix output." cmd:"mix"`
Makeup MatrixCompMakeupCmd `help:"Get or set the compressor makeup gain of the Matrix output." cmd:"makeup"`
Attack MatrixCompAttackCmd `help:"Get or set the compressor attack time of the Matrix output." cmd:"attack"`
Hold MatrixCompHoldCmd `help:"Get or set the compressor hold time of the Matrix output." cmd:"hold"`
Release MatrixCompReleaseCmd `help:"Get or set the compressor release time of the Matrix output." cmd:"release"`
}
// MatrixCompOnCmd defines the command for getting or setting the compressor on/off state of the Matrix output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MatrixCompOnCmd struct {
Enable *string `arg:"" help:"The compressor on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MatrixCompOnCmd command, either retrieving the current compressor on/off state of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompOnCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Matrix.Comp.On(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetOn(matrix.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Matrix compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MatrixCompModeCmd defines the command for getting or setting the compressor mode of the Matrix output, allowing users to specify the desired mode as "comp" or "exp".
type MatrixCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set. If not provided, the current mode will be printed." optional:"" enum:"comp,exp"`
}
// Run executes the MatrixCompModeCmd command, either retrieving the current compressor mode of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompModeCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Matrix.Comp.Mode(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor mode: %s\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetMode(matrix.Index.Index, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set Matrix compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor mode set to: %s\n", *cmd.Mode)
return nil
}
// MatrixCompThresholdCmd defines the command for getting or setting the compressor threshold of the Matrix output, allowing users to specify the desired threshold in dB.
type MatrixCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set. If not provided, the current threshold will be printed." optional:""`
}
// Run executes the MatrixCompThresholdCmd command, either retrieving the current compressor threshold of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompThresholdCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Matrix.Comp.Threshold(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor threshold: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetThreshold(matrix.Index.Index, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set Matrix compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor threshold set to: %.2f dB\n", *cmd.Threshold)
return nil
}
// MatrixCompRatioCmd defines the command for getting or setting the compressor ratio of the Matrix output, allowing users to specify the desired ratio.
type MatrixCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current ratio will be printed." optional:""`
}
// Run executes the MatrixCompRatioCmd command, either retrieving the current compressor ratio of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompRatioCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Matrix.Comp.Ratio(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor ratio: %.2f\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetRatio(matrix.Index.Index, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set Matrix compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor ratio set to: %.2f\n", *cmd.Ratio)
return nil
}
// MatrixCompMixCmd defines the command for getting or setting the compressor mix level of the Matrix output, allowing users to specify the desired mix level in percentage.
type MatrixCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set. If not provided, the current mix level will be printed." optional:""`
}
// Run executes the MatrixCompMixCmd command, either retrieving the current compressor mix level of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompMixCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Matrix.Comp.Mix(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor mix level: %.2f%%\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetMix(matrix.Index.Index, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set Matrix compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor mix level set to: %.2f%%\n", *cmd.Mix)
return nil
}
// MatrixCompMakeupCmd defines the command for getting or setting the compressor makeup gain of the Matrix output, allowing users to specify the desired makeup gain in dB.
type MatrixCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set. If not provided, the current makeup gain will be printed." optional:""`
}
// Run executes the MatrixCompMakeupCmd command, either retrieving the current compressor makeup gain of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompMakeupCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Matrix.Comp.Makeup(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor makeup gain: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetMakeup(matrix.Index.Index, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set Matrix compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor makeup gain set to: %.2f dB\n", *cmd.Makeup)
return nil
}
// MatrixCompAttackCmd defines the command for getting or setting the compressor attack time of the Matrix output, allowing users to specify the desired attack time in milliseconds.
type MatrixCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set. If not provided, the current attack time will be printed." optional:""`
}
// Run executes the MatrixCompAttackCmd command, either retrieving the current compressor attack time of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompAttackCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Matrix.Comp.Attack(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor attack time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetAttack(matrix.Index.Index, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set Matrix compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor attack time set to: %.2f ms\n", *cmd.Attack)
return nil
}
// MatrixCompHoldCmd defines the command for getting or setting the compressor hold time of the Matrix output, allowing users to specify the desired hold time in milliseconds.
type MatrixCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set. If not provided, the current hold time will be printed." optional:""`
}
// Run executes the MatrixCompHoldCmd command, either retrieving the current compressor hold time of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompHoldCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Matrix.Comp.Hold(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor hold time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetHold(matrix.Index.Index, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set Matrix compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor hold time set to: %.2f ms\n", *cmd.Hold)
return nil
}
// MatrixCompReleaseCmd defines the command for getting or setting the compressor release time of the Matrix output, allowing users to specify the desired release time in milliseconds.
type MatrixCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set. If not provided, the current release time will be printed." optional:""`
}
// Run executes the MatrixCompReleaseCmd command, either retrieving the current compressor release time of the Matrix output or setting it based on the provided argument.
func (cmd *MatrixCompReleaseCmd) Run(ctx *context, matrix *MatrixCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Matrix.Comp.Release(matrix.Index.Index)
if err != nil {
return fmt.Errorf("failed to get Matrix compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor release time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Matrix.Comp.SetRelease(matrix.Index.Index, *cmd.Release); err != nil {
return fmt.Errorf("failed to set Matrix compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Matrix compressor release time set to: %.2f ms\n", *cmd.Release)
return nil
}

39
cmd/x32-cli/raw.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"github.com/charmbracelet/log"
)
// RawCmd represents the command to send raw OSC messages to the mixer.
type RawCmd struct {
Address string `help:"The OSC address to send the message to." arg:""`
Args []string `help:"The arguments to include in the OSC message." arg:"" optional:""`
}
// Run executes the RawCmd by sending the specified OSC message to the mixer and optionally waiting for a response.
func (cmd *RawCmd) Run(ctx *context) error {
params := make([]any, len(cmd.Args))
for i, arg := range cmd.Args {
params[i] = arg
}
if err := ctx.Client.SendMessage(cmd.Address, params...); err != nil {
return fmt.Errorf("failed to send raw OSC message: %w", err)
}
if len(params) > 0 {
log.Debugf("Sent OSC message: %s with args: %v\n", cmd.Address, cmd.Args)
return nil
}
msg, err := ctx.Client.ReceiveMessage()
if err != nil {
return fmt.Errorf("failed to receive response for raw OSC message: %w", err)
}
if msg != nil {
fmt.Fprintf(ctx.Out, "Received response: %s with args: %v\n", msg.Address, msg.Arguments)
}
return nil
}

85
cmd/x32-cli/snapshot.go Normal file
View File

@ -0,0 +1,85 @@
package main
import "fmt"
type SnapshotCmdGroup struct {
List ListCmd `help:"List all snapshots." cmd:"list"`
Index struct {
Index *int `arg:"" help:"The index of the snapshot." optional:""`
Name NameCmd `help:"Get or set the name of a snapshot." cmd:"name"`
Save SaveCmd `help:"Save the current mixer state to a snapshot." cmd:"save"`
Load LoadCmd `help:"Load a mixer state from a snapshot." cmd:"load"`
Delete DeleteCmd `help:"Delete a snapshot." cmd:"delete"`
} `help:"The index of the snapshot." arg:""`
}
// Validate checks if the provided snapshot index is within the valid range (1-64) when any of the subcommands that require an index are used.
func (cmd *SnapshotCmdGroup) Validate() error {
if cmd.Index.Index == nil {
return nil
}
if *cmd.Index.Index < 1 || *cmd.Index.Index > 64 {
return fmt.Errorf("snapshot index must be between 1 and 64")
}
return nil
}
type ListCmd struct{}
func (cmd *ListCmd) Run(ctx *context) error {
for i := range 64 {
name, err := ctx.Client.Snapshot.Name(i + 1)
if err != nil {
return fmt.Errorf("failed to get name for snapshot %d: %w", i+1, err)
}
if name == "" {
continue
}
fmt.Fprintf(ctx.Out, "%d: %s\n", i+1, name)
}
return nil
}
type NameCmd struct {
Name *string `arg:"" help:"The name of the snapshot." optional:""`
}
func (cmd *NameCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
if cmd.Name == nil {
name, err := ctx.Client.Snapshot.Name(*snapshot.Index.Index)
if err != nil {
return err
}
fmt.Fprintln(ctx.Out, name)
return nil
}
return ctx.Client.Snapshot.SetName(*snapshot.Index.Index, *cmd.Name)
}
type SaveCmd struct {
Name string `arg:"" help:"The name of the snapshot."`
}
func (cmd *SaveCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
err := ctx.Client.Snapshot.CurrentName(cmd.Name)
if err != nil {
return err
}
return ctx.Client.Snapshot.CurrentSave(*snapshot.Index.Index)
}
type LoadCmd struct{}
func (cmd *LoadCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
return ctx.Client.Snapshot.CurrentLoad(*snapshot.Index.Index)
}
type DeleteCmd struct{}
func (cmd *DeleteCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
return ctx.Client.Snapshot.CurrentDelete(*snapshot.Index.Index)
}

863
cmd/x32-cli/strip.go Normal file
View File

@ -0,0 +1,863 @@
package main
import (
"fmt"
"time"
)
// StripCmdGroup defines the command group for controlling the strips of the mixer, including commands for getting and setting various parameters such as mute state, fader level, send levels, and EQ settings.
type StripCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the strip. (1-based indexing)"`
Mute StripMuteCmd ` help:"Get or set the mute state of the strip." cmd:""`
Fader StripFaderCmd ` help:"Get or set the fader level of the strip." cmd:""`
Fadein StripFadeinCmd ` help:"Fade in the strip over a specified duration." cmd:""`
Fadeout StripFadeoutCmd ` help:"Fade out the strip over a specified duration." cmd:""`
Send StripSendCmd ` help:"Get or set the send level for a specific bus." cmd:""`
Name StripNameCmd ` help:"Get or set the name of the strip." cmd:""`
Gate StripGateCmdGroup ` help:"Commands related to the strip gate." cmd:"gate"`
Eq StripEqCmdGroup ` help:"Commands related to the strip EQ." cmd:"eq"`
Comp StripCompCmdGroup ` help:"Commands related to the strip compressor." cmd:"comp"`
} `arg:"" help:"Control a specific strip by index."`
}
// StripMuteCmd defines the command for getting or setting the mute state of a strip.
type StripMuteCmd struct {
State *string `arg:"" help:"The mute state to set (true or false). If not provided, the current mute state will be returned." optional:"" enum:"true,false"`
}
// Run executes the StripMuteCmd command, either retrieving the current mute state of the strip or setting it based on the provided argument.
func (cmd *StripMuteCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Strip.Mute(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d mute state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetMute(strip.Index.Index, *cmd.State == trueStr); err != nil {
return fmt.Errorf("failed to set mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d mute state set to: %s\n", strip.Index.Index, *cmd.State)
return nil
}
// StripFaderCmd defines the command for getting or setting the fader level of a strip.
type StripFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set (in dB)." optional:""`
}
// Run executes the StripFaderCmd command, either retrieving the current fader level of the strip or setting it based on the provided argument.
func (cmd *StripFaderCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d fader level: %.2f dB\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetFader(strip.Index.Index, *cmd.Level); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d fader level set to: %.2f dB\n", strip.Index.Index, *cmd.Level)
return nil
}
// StripFadeinCmd defines the command for fading in a strip over a specified duration, gradually increasing the fader level from its current value to a target value.
type StripFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in (in seconds)." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"0.0" arg:""`
}
// Run executes the StripFadeinCmd command, gradually increasing the fader level of the strip from its current value to the specified target value over the specified duration.
func (cmd *StripFadeinCmd) Run(ctx *context, strip *StripCmdGroup) error {
currentLevel, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or above the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.Strip.SetFader(strip.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level during fade-in: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Strip %d fade-in complete. Final level: %.2f dB\n",
strip.Index.Index,
cmd.Target,
)
return nil
}
// StripFadeoutCmd defines the command for fading out a strip over a specified duration, gradually decreasing the fader level from its current value to a target value.
type StripFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out (in seconds)." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"-90.0" arg:""`
}
// Run executes the StripFadeoutCmd command, gradually decreasing the fader level of the strip from its current value to the specified target value over the specified duration.
func (cmd *StripFadeoutCmd) Run(ctx *context, strip *StripCmdGroup) error {
{
currentLevel, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or below the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.Strip.SetFader(strip.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level during fade-out: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Strip %d fade-out complete. Final level: %.2f dB\n",
strip.Index.Index,
cmd.Target,
)
return nil
}
}
// StripSendCmd defines the command for getting or setting the auxiliary send level
// for a specific send destination (mix bus) on a strip.
type StripSendCmd struct {
SendIndex int `arg:"" help:"The index of the send destination (mix bus). (1-based indexing)"`
Level *float64 `arg:"" help:"The send level to set (in dB)." optional:""`
}
// Run executes the StripSendCmd command, either retrieving the current send level for the specified send destination
// or setting it based on the provided argument.
func (cmd *StripSendCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Strip.SendLevel(strip.Index.Index, cmd.SendIndex)
if err != nil {
return fmt.Errorf("failed to get send level: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d send %d level: %.2f dB\n",
strip.Index.Index,
cmd.SendIndex,
resp,
)
return nil
}
if err := ctx.Client.Strip.SetSendLevel(
strip.Index.Index,
cmd.SendIndex,
*cmd.Level,
); err != nil {
return fmt.Errorf("failed to set send level: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d send %d level set to: %.2f dB\n",
strip.Index.Index,
cmd.SendIndex,
*cmd.Level,
)
return nil
}
// StripNameCmd defines the command for getting or setting the name of a strip, allowing users to assign custom names to strips for easier identification and organisation.
type StripNameCmd struct {
Name *string `arg:"" help:"The name to set for the strip." optional:""`
}
// Run executes the StripNameCmd command, either retrieving the current name of the strip or setting it based on the provided argument.
func (cmd *StripNameCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.Strip.Name(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get strip name: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d name: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetName(strip.Index.Index, *cmd.Name); err != nil {
return fmt.Errorf("failed to set strip name: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d name set to: %s\n", strip.Index.Index, *cmd.Name)
return nil
}
// StripGateCmdGroup defines the command group for controlling the gate settings of a strip, including commands for getting and setting the gate on/off state, mode, threshold, range, attack time, hold time, and release time.
type StripGateCmdGroup struct {
On StripGateOnCmd `help:"Get or set the gate on/off state of the strip." cmd:""`
Mode StripGateModeCmd `help:"Get or set the gate mode of the strip." cmd:""`
Threshold StripGateThresholdCmd `help:"Get or set the gate threshold of the strip." cmd:""`
Range StripGateRangeCmd `help:"Get or set the gate range of the strip." cmd:""`
Attack StripGateAttackCmd `help:"Get or set the gate attack time of the strip." cmd:""`
Hold StripGateHoldCmd `help:"Get or set the gate hold time of the strip." cmd:""`
Release StripGateReleaseCmd `help:"Get or set the gate release time of the strip." cmd:""`
}
// StripGateOnCmd defines the command for getting or setting the gate on/off state of a strip, allowing users to enable or disable the gate effect on the strip.
type StripGateOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the gate." optional:"" enum:"true,false"`
}
// Run executes the StripGateOnCmd command, either retrieving the current gate on/off state of the strip or setting it based on the provided argument.
func (cmd *StripGateOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Gate.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set gate state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripGateModeCmd defines the command for getting or setting the gate mode of a strip, allowing users to choose from different gate modes such as exp2, exp3, exp4, gate, or duck.
type StripGateModeCmd struct {
Mode *string `arg:"" help:"The gate mode to set." optional:"" enum:"exp2,exp3,exp4,gate,duck"`
}
// Run executes the StripGateModeCmd command, either retrieving the current gate mode of the strip or setting it based on the provided argument.
func (cmd *StripGateModeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Strip.Gate.Mode(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate mode: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetMode(strip.Index.Index, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set gate mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate mode set to: %s\n", strip.Index.Index, *cmd.Mode)
return nil
}
// StripGateThresholdCmd defines the command for getting or setting the gate threshold of a strip, allowing users to specify the threshold level at which the gate will start to attenuate the signal.
type StripGateThresholdCmd struct {
Threshold *float64 `arg:"" help:"The gate threshold to set (in dB)." optional:""`
}
// Run executes the StripGateThresholdCmd command, either retrieving the current gate threshold of the strip or setting it based on the provided argument.
func (cmd *StripGateThresholdCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Strip.Gate.Threshold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate threshold: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetThreshold(strip.Index.Index, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set gate threshold: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate threshold set to: %.2f\n",
strip.Index.Index,
*cmd.Threshold,
)
return nil
}
// StripGateRangeCmd defines the command for getting or setting the gate range of a strip, allowing users to specify the amount of attenuation applied by the gate when the signal falls below the threshold.
type StripGateRangeCmd struct {
Range *float64 `arg:"" help:"The gate range to set (in dB)." optional:""`
}
// Run executes the StripGateRangeCmd command, either retrieving the current gate range of the strip or setting it based on the provided argument.
func (cmd *StripGateRangeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Range == nil {
resp, err := ctx.Client.Strip.Gate.Range(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate range: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate range: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetRange(strip.Index.Index, *cmd.Range); err != nil {
return fmt.Errorf("failed to set gate range: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate range set to: %.2f\n", strip.Index.Index, *cmd.Range)
return nil
}
// StripGateAttackCmd defines the command for getting or setting the gate attack time of a strip, allowing users to specify the time it takes for the gate to fully open after the signal exceeds the threshold.
type StripGateAttackCmd struct {
Attack *float64 `arg:"" help:"The gate attack time to set (in ms)." optional:""`
}
// Run executes the StripGateAttackCmd command, either retrieving the current gate attack time of the strip or setting it based on the provided argument.
func (cmd *StripGateAttackCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Strip.Gate.Attack(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate attack time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetAttack(strip.Index.Index, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set gate attack time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate attack time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Attack,
)
return nil
}
// StripGateHoldCmd defines the command for getting or setting the gate hold time of a strip, allowing users to specify the time that the gate remains open after the signal falls below the threshold before it starts to close.
type StripGateHoldCmd struct {
Hold *float64 `arg:"" help:"The gate hold time to set (in ms)." optional:""`
}
// Run executes the StripGateHoldCmd command, either retrieving the current gate hold time of the strip or setting it based on the provided argument.
func (cmd *StripGateHoldCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Strip.Gate.Hold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate hold time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetHold(strip.Index.Index, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set gate hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate hold time set to: %.2f ms\n", strip.Index.Index, *cmd.Hold)
return nil
}
// StripGateReleaseCmd defines the command for getting or setting the gate release time of a strip, allowing users to specify the time it takes for the gate to fully close after the signal falls below the threshold and the hold time has elapsed.
type StripGateReleaseCmd struct {
Release *float64 `arg:"" help:"The gate release time to set (in ms)." optional:""`
}
// Run executes the StripGateReleaseCmd command, either retrieving the current gate release time of the strip or setting it based on the provided argument.
func (cmd *StripGateReleaseCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Strip.Gate.Release(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate release time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetRelease(strip.Index.Index, *cmd.Release); err != nil {
return fmt.Errorf("failed to set gate release time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate release time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Release,
)
return nil
}
// StripEqCmdGroup defines the command group for controlling the EQ settings of a strip, including commands for getting and setting the EQ on/off state and parameters for each EQ band such as gain, frequency, Q factor, and type.
type StripEqCmdGroup struct {
On StripEqOnCmd `help:"Get or set the EQ on/off state of the strip." cmd:""`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain StripEqBandGainCmd `help:"Get or set the gain of the EQ band." cmd:""`
Freq StripEqBandFreqCmd `help:"Get or set the frequency of the EQ band." cmd:""`
Q StripEqBandQCmd `help:"Get or set the Q factor of the EQ band." cmd:""`
Type StripEqBandTypeCmd `help:"Get or set the type of the EQ band." cmd:""`
} `help:"Commands for controlling a specific EQ band of the strip." arg:""`
}
// Validate checks if the provided EQ band number is valid (between 1 and 4) and returns an error if it is not.
func (cmd *StripEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 4 {
return fmt.Errorf("EQ band number must be between 1 and 4, got %d", *cmd.Band.Band)
}
return nil
}
// StripEqOnCmd defines the command for getting or setting the EQ on/off state of a strip, allowing users to enable or disable the EQ effect on the strip.
type StripEqOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the EQ." optional:"" enum:"true,false"`
}
// Run executes the StripEqOnCmd command, either retrieving the current EQ on/off state of the strip or setting it based on the provided argument.
func (cmd *StripEqOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Eq.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get EQ state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d EQ state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Eq.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set EQ state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d EQ state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on a strip, allowing users to adjust the level of the signal for that band in decibels (dB).
type StripEqBandGainCmd struct {
Gain *float64 `arg:"" help:"The gain to set for the EQ band (in dB)." optional:""`
}
// Run executes the StripEqBandGainCmd command, either retrieving the current gain of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandGainCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Gain == nil {
resp, err := ctx.Client.Strip.Eq.Gain(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d gain: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetGain(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Gain,
); err != nil {
return fmt.Errorf("failed to set EQ band gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d gain set to: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Gain,
)
return nil
}
// StripEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on a strip, allowing users to adjust the centre frequency of the band in hertz (Hz).
type StripEqBandFreqCmd struct {
Freq *float64 `arg:"" help:"The frequency to set for the EQ band (in Hz)." optional:""`
}
// Run executes the StripEqBandFreqCmd command, either retrieving the current frequency of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandFreqCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Freq == nil {
resp, err := ctx.Client.Strip.Eq.Frequency(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band frequency: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d frequency: %.2f Hz\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetFrequency(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Freq,
); err != nil {
return fmt.Errorf("failed to set EQ band frequency: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d frequency set to: %.2f Hz\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Freq,
)
return nil
}
// StripEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on a strip, allowing users to adjust the bandwidth of the band, which determines how wide or narrow the affected frequency range is.
type StripEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the EQ band." optional:""`
}
// Run executes the StripEqBandQCmd command, either retrieving the current Q factor of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandQCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Q == nil {
resp, err := ctx.Client.Strip.Eq.Q(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band Q factor: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d Q factor: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetQ(strip.Index.Index, *stripEq.Band.Band, *cmd.Q); err != nil {
return fmt.Errorf("failed to set EQ band Q factor: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d Q factor set to: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Q,
)
return nil
}
// StripEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on a strip, allowing users to choose from different EQ types such as low cut (lcut), low shelf (lshv), parametric (peq), variable Q (veq), high shelf (hshv), or high cut (hcut).
type StripEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the EQ band." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"`
}
// Run executes the StripEqBandTypeCmd command, either retrieving the current type of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandTypeCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Type == nil {
resp, err := ctx.Client.Strip.Eq.Type(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band type: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d type: %s\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetType(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Type,
); err != nil {
return fmt.Errorf("failed to set EQ band type: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d type set to: %s\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Type,
)
return nil
}
// StripCompCmdGroup defines the command group for controlling the compressor settings of a strip, including commands for getting and setting the compressor on/off state, mode, threshold, ratio, mix, makeup gain, attack time, hold time, and release time.
type StripCompCmdGroup struct {
On StripCompOnCmd `help:"Get or set the compressor on/off state of the strip." cmd:""`
Mode StripCompModeCmd `help:"Get or set the compressor mode of the strip." cmd:""`
Threshold StripCompThresholdCmd `help:"Get or set the compressor threshold of the strip." cmd:""`
Ratio StripCompRatioCmd `help:"Get or set the compressor ratio of the strip." cmd:""`
Mix StripCompMixCmd `help:"Get or set the compressor mix of the strip." cmd:""`
Makeup StripCompMakeupCmd `help:"Get or set the compressor makeup gain of the strip." cmd:""`
Attack StripCompAttackCmd `help:"Get or set the compressor attack time of the strip." cmd:""`
Hold StripCompHoldCmd `help:"Get or set the compressor hold time of the strip." cmd:""`
Release StripCompReleaseCmd `help:"Get or set the compressor release time of the strip." cmd:""`
}
// StripCompOnCmd defines the command for getting or setting the compressor on/off state of a strip, allowing users to enable or disable the compressor effect on the strip.
type StripCompOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the compressor." optional:"" enum:"true,false"`
}
// Run executes the StripCompOnCmd command, either retrieving the current compressor on/off state of the strip or setting it based on the provided argument.
func (cmd *StripCompOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Comp.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set compressor state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripCompModeCmd defines the command for getting or setting the compressor mode of a strip, allowing users to choose from different compressor modes such as comp (standard compression) or exp (expander).
type StripCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set." optional:"" enum:"comp,exp"`
}
// Run executes the StripCompModeCmd command, either retrieving the current compressor mode of the strip or setting it based on the provided argument.
func (cmd *StripCompModeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Strip.Comp.Mode(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mode: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMode(strip.Index.Index, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mode set to: %s\n", strip.Index.Index, *cmd.Mode)
return nil
}
// StripCompThresholdCmd defines the command for getting or setting the compressor threshold of a strip, allowing users to specify the threshold level at which the compressor will start to reduce the signal level.
type StripCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB)." optional:""`
}
// Run executes the StripCompThresholdCmd command, either retrieving the current compressor threshold of the strip or setting it based on the provided argument.
func (cmd *StripCompThresholdCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Strip.Comp.Threshold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor threshold: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetThreshold(strip.Index.Index, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set compressor threshold: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor threshold set to: %.2f\n",
strip.Index.Index,
*cmd.Threshold,
)
return nil
}
// StripCompRatioCmd defines the command for getting or setting the compressor ratio of a strip, allowing users to specify the amount of gain reduction applied by the compressor once the signal exceeds the threshold.
type StripCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set." optional:""`
}
// Run executes the StripCompRatioCmd command, either retrieving the current compressor ratio of the strip or setting it based on the provided argument.
func (cmd *StripCompRatioCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Strip.Comp.Ratio(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor ratio: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetRatio(strip.Index.Index, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor ratio set to: %.2f\n", strip.Index.Index, *cmd.Ratio)
return nil
}
// StripCompMixCmd defines the command for getting or setting the compressor mix of a strip, allowing users to specify the blend between the dry (unprocessed) signal and the wet (compressed) signal, typically expressed as a percentage.
type StripCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix to set (0-100%)." optional:""`
}
// Run executes the StripCompMixCmd command, either retrieving the current compressor mix of the strip or setting it based on the provided argument.
func (cmd *StripCompMixCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Strip.Comp.Mix(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor mix: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mix: %.2f%%\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMix(strip.Index.Index, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set compressor mix: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mix set to: %.2f%%\n", strip.Index.Index, *cmd.Mix)
return nil
}
// StripCompMakeupCmd defines the command for getting or setting the compressor makeup gain of a strip, allowing users to specify the amount of gain applied to the signal after compression to compensate for any reduction in level caused by the compressor.
type StripCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set (in dB)." optional:""`
}
// Run executes the StripCompMakeupCmd command, either retrieving the current compressor makeup gain of the strip or setting it based on the provided argument.
func (cmd *StripCompMakeupCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Strip.Comp.Makeup(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor makeup gain: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMakeup(strip.Index.Index, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set compressor makeup gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor makeup gain set to: %.2f\n",
strip.Index.Index,
*cmd.Makeup,
)
return nil
}
// StripCompAttackCmd defines the command for getting or setting the compressor attack time of a strip, allowing users to specify the time it takes for the compressor to start reducing the signal level after the signal exceeds the threshold.
type StripCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set (in ms)." optional:""`
}
// Run executes the StripCompAttackCmd command, either retrieving the current compressor attack time of the strip or setting it based on the provided argument.
func (cmd *StripCompAttackCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Strip.Comp.Attack(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor attack time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetAttack(strip.Index.Index, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set compressor attack time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor attack time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Attack,
)
return nil
}
// StripCompHoldCmd defines the command for getting or setting the compressor hold time of a strip, allowing users to specify the time that the compressor continues to reduce the signal level after the signal falls below the threshold before it starts to release.
type StripCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set (in ms)." optional:""`
}
// Run executes the StripCompHoldCmd command, either retrieving the current compressor hold time of the strip or setting it based on the provided argument.
func (cmd *StripCompHoldCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Strip.Comp.Hold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor hold time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetHold(strip.Index.Index, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set compressor hold time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor hold time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Hold,
)
return nil
}
// StripCompReleaseCmd defines the command for getting or setting the compressor release time of a strip, allowing users to specify the time it takes for the compressor to stop reducing the signal level after the signal falls below the threshold and the hold time has elapsed.
type StripCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set (in ms)." optional:""`
}
// Run executes the StripCompReleaseCmd command, either retrieving the current compressor release time of the strip or setting it based on the provided argument.
func (cmd *StripCompReleaseCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Strip.Comp.Release(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor release time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetRelease(strip.Index.Index, *cmd.Release); err != nil {
return fmt.Errorf("failed to set compressor release time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor release time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Release,
)
return nil
}

638
cmd/xair-cli/bus.go Normal file
View File

@ -0,0 +1,638 @@
package main
import (
"fmt"
"time"
)
// BusCmdGroup defines the commands related to controlling the buses of the X-Air device.
type BusCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the bus. (1-based indexing)"`
Mute BusMuteCmd ` help:"Get or set the mute state of the bus." cmd:""`
Fader BusFaderCmd ` help:"Get or set the fader level of the bus." cmd:""`
Fadein BusFadeinCmd ` help:"Fade in the bus over a specified duration." cmd:""`
Fadeout BusFadeoutCmd ` help:"Fade out the bus over a specified duration." cmd:""`
Name BusNameCmd ` help:"Get or set the name of the bus." cmd:""`
Eq BusEqCmdGroup ` help:"Commands related to the bus EQ." cmd:"eq"`
Comp BusCompCmdGroup ` help:"Commands related to the bus compressor." cmd:"comp"`
} `arg:"" help:"Control a specific bus by index."`
}
// BusMuteCmd defines the command for getting or setting the mute state of a bus.
type BusMuteCmd struct {
State *string `arg:"" help:"The mute state to set (true or false). If not provided, the current mute state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusMuteCmd command, either retrieving the current mute state or setting it based on the provided argument.
func (cmd *BusMuteCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Mute(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d mute state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetMute(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d mute state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusFaderCmd defines the command for getting or setting the fader level of a bus.
type BusFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set (in dB). If not provided, the current fader level will be returned." optional:""`
}
// Run executes the BusFaderCmd command, either retrieving the current fader level or setting it based on the provided argument.
func (cmd *BusFaderCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d fader level: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, *cmd.Level); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d fader level set to: %.2f dB\n", bus.Index.Index, *cmd.Level)
return nil
}
// BusFadeinCmd defines the command for fading in a bus over a specified duration to a target fader level.
type BusFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in effect." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"0.0" arg:""`
}
// Run executes the BusFadeinCmd command, gradually increasing the fader level of the bus from its current level to the target level over the specified duration.
func (cmd *BusFadeinCmd) Run(ctx *context, bus *BusCmdGroup) error {
currentLevel, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or above the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel += totalSteps / float64(cmd.Duration.Seconds()*1000/stepDuration.Seconds())
if currentLevel > cmd.Target {
currentLevel = cmd.Target
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Bus %d fade-in complete. Final level: %.2f dB\n",
bus.Index.Index,
cmd.Target,
)
return nil
}
// BusFadeoutCmd defines the command for fading out a bus over a specified duration to a target fader level.
type BusFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out effect." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"-90.0" arg:""`
}
// Run executes the BusFadeoutCmd command, gradually decreasing the fader level of the bus from its current level to the target level over the specified duration.
func (cmd *BusFadeoutCmd) Run(ctx *context, bus *BusCmdGroup) error {
currentLevel, err := ctx.Client.Bus.Fader(bus.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or below the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel -= totalSteps / float64(cmd.Duration.Seconds()*1000/stepDuration.Seconds())
if currentLevel < cmd.Target {
currentLevel = cmd.Target
}
if err := ctx.Client.Bus.SetFader(bus.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Bus %d fade-out complete. Final level: %.2f dB\n",
bus.Index.Index,
cmd.Target,
)
return nil
}
// BusNameCmd defines the command for getting or setting the name of a bus.
type BusNameCmd struct {
Name *string `arg:"" help:"The name to set for the bus. If not provided, the current name will be returned." optional:""`
}
// Run executes the BusNameCmd command, either retrieving the current name of the bus or setting it based on the provided argument.
func (cmd *BusNameCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.Bus.Name(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d name: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.SetName(bus.Index.Index, *cmd.Name); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d name set to: %s\n", bus.Index.Index, *cmd.Name)
return nil
}
// BusEqCmdGroup defines the commands related to controlling the EQ of a bus.
type BusEqCmdGroup struct {
On BusEqOnCmd `help:"Get or set the EQ on/off state of the bus." cmd:"on"`
Mode BusEqModeCmd `help:"Get or set the EQ mode of the bus (peq, geq or teq)." cmd:"mode"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain BusEqBandGainCmd `help:"Get or set the gain of the EQ band." cmd:"gain"`
Freq BusEqBandFreqCmd `help:"Get or set the frequency of the EQ band." cmd:"freq"`
Q BusEqBandQCmd `help:"Get or set the Q factor of the EQ band." cmd:"q"`
Type BusEqBandTypeCmd `help:"Get or set the type of the EQ band (lcut, lshv, peq, veq, hshv, hcut)." cmd:"type"`
} `help:"Commands for controlling a specific EQ band of the bus." arg:""`
}
// Validate checks that the provided EQ band number is within the valid range (1-6).
func (cmd *BusEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// BusCompCmdGroup defines the commands related to controlling the compressor of a bus.
type BusEqOnCmd struct {
State *string `arg:"" help:"The EQ on/off state to set (true or false). If not provided, the current EQ state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusEqOnCmd command, either retrieving the current EQ on/off state of the bus or setting it based on the provided argument.
func (cmd *BusEqOnCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Eq.On(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ on state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Eq.SetOn(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ on state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusEqModeCmd defines the command for getting or setting the EQ mode of a bus.
type BusEqModeCmd struct {
Mode *string `arg:"" help:"The EQ mode to set (peq, geq or teq). If not provided, the current EQ mode will be returned." optional:"" enum:"peq,geq,teq"`
}
// Run executes the BusEqModeCmd command, either retrieving the current EQ mode of the bus or setting it based on the provided argument.
func (cmd *BusEqModeCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Bus.Eq.Mode(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ mode: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Eq.SetMode(bus.Index.Index, *cmd.Mode); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d EQ mode set to: %s\n", bus.Index.Index, *cmd.Mode)
return nil
}
// BusEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band of a bus.
type BusEqBandGainCmd struct {
Gain *float64 `arg:"" help:"The gain to set for the EQ band (in dB). If not provided, the current gain will be returned." optional:""`
}
// Run executes the BusEqBandGainCmd command, either retrieving the current gain of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandGainCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Gain == nil {
resp, err := ctx.Client.Bus.Eq.Gain(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d gain: %.2f dB\n",
bus.Index.Index,
busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetGain(bus.Index.Index, *busEq.Band.Band, *cmd.Gain); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d gain set to: %.2f dB\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Gain,
)
return nil
}
// BusEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band of a bus.
type BusEqBandFreqCmd struct {
Freq *float64 `arg:"" help:"The frequency to set for the EQ band (in Hz). If not provided, the current frequency will be returned." optional:""`
}
// Run executes the BusEqBandFreqCmd command, either retrieving the current frequency of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandFreqCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Freq == nil {
resp, err := ctx.Client.Bus.Eq.Frequency(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d frequency: %.2f Hz\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetFrequency(
bus.Index.Index,
*busEq.Band.Band,
*cmd.Freq,
); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d frequency set to: %.2f Hz\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Freq,
)
return nil
}
// BusEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band of a bus.
type BusEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the EQ band. If not provided, the current Q factor will be returned." optional:""`
}
// Run executes the BusEqBandQCmd command, either retrieving the current Q factor of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandQCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Q == nil {
resp, err := ctx.Client.Bus.Eq.Q(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d Q factor: %.2f\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetQ(bus.Index.Index, *busEq.Band.Band, *cmd.Q); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d Q factor set to: %.2f\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Q,
)
return nil
}
// BusEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band of a bus.
type BusEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the EQ band (lcut, lshv, peq, veq, hshv, hcut). If not provided, the current type will be returned." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"`
}
// Run executes the BusEqBandTypeCmd command, either retrieving the current type of the specified EQ band of the bus or setting it based on the provided argument.
func (cmd *BusEqBandTypeCmd) Run(ctx *context, bus *BusCmdGroup, busEq *BusEqCmdGroup) error {
if cmd.Type == nil {
resp, err := ctx.Client.Bus.Eq.Type(bus.Index.Index, *busEq.Band.Band)
if err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d type: %s\n",
bus.Index.Index,
*busEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Bus.Eq.SetType(bus.Index.Index, *busEq.Band.Band, *cmd.Type); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d EQ band %d type set to: %s\n",
bus.Index.Index,
*busEq.Band.Band,
*cmd.Type,
)
return nil
}
// BusCompCmdGroup defines the commands related to controlling the compressor of a bus.
type BusCompCmdGroup struct {
On BusCompOnCmd `help:"Get or set the compressor on/off state of the bus." cmd:"on"`
Mode BusCompModeCmd `help:"Get or set the compressor mode of the bus (comp, exp)." cmd:"mode"`
Threshold BusCompThresholdCmd `help:"Get or set the compressor threshold of the bus (in dB)." cmd:"threshold"`
Ratio BusCompRatioCmd `help:"Get or set the compressor ratio of the bus." cmd:"ratio"`
Mix BusCompMixCmd `help:"Get or set the compressor mix level of the bus (in %)." cmd:"mix"`
Makeup BusCompMakeupCmd `help:"Get or set the compressor makeup gain of the bus (in dB)." cmd:"makeup"`
Attack BusCompAttackCmd `help:"Get or set the compressor attack time of the bus (in ms)." cmd:"attack"`
Hold BusCompHoldCmd `help:"Get or set the compressor hold time of the bus (in ms)." cmd:"hold"`
Release BusCompReleaseCmd `help:"Get or set the compressor release time of the bus (in ms)." cmd:"release"`
}
// BusCompOnCmd defines the command for getting or setting the compressor on/off state of a bus.
type BusCompOnCmd struct {
State *string `arg:"" help:"The compressor on/off state to set (true or false). If not provided, the current compressor state will be returned." optional:"" enum:"true,false"`
}
// Run executes the BusCompOnCmd command, either retrieving the current compressor on/off state of the bus or setting it based on the provided argument.
func (cmd *BusCompOnCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Bus.Comp.On(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor on state: %t\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetOn(bus.Index.Index, *cmd.State == trueStr); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor on state set to: %s\n", bus.Index.Index, *cmd.State)
return nil
}
// BusCompModeCmd defines the command for getting or setting the compressor mode of a bus.
type BusCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set (comp, exp). If not provided, the current compressor mode will be returned." optional:"" enum:"comp,exp"`
}
// Run executes the BusCompModeCmd command, either retrieving the current compressor mode of the bus or setting it based on the provided argument.
func (cmd *BusCompModeCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Bus.Comp.Mode(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mode: %s\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMode(bus.Index.Index, *cmd.Mode); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mode set to: %s\n", bus.Index.Index, *cmd.Mode)
return nil
}
// BusCompThresholdCmd defines the command for getting or setting the compressor threshold of a bus.
type BusCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB). If not provided, the current compressor threshold will be returned." optional:""`
}
// Run executes the BusCompThresholdCmd command, either retrieving the current compressor threshold of the bus or setting it based on the provided argument.
func (cmd *BusCompThresholdCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Bus.Comp.Threshold(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor threshold: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetThreshold(bus.Index.Index, *cmd.Threshold); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor threshold set to: %.2f dB\n",
bus.Index.Index,
*cmd.Threshold,
)
return nil
}
// BusCompRatioCmd defines the command for getting or setting the compressor ratio of a bus.
type BusCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current compressor ratio will be returned." optional:""`
}
// Run executes the BusCompRatioCmd command, either retrieving the current compressor ratio of the bus or setting it based on the provided argument.
func (cmd *BusCompRatioCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Bus.Comp.Ratio(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor ratio: %.2f\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetRatio(bus.Index.Index, *cmd.Ratio); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor ratio set to: %.2f\n", bus.Index.Index, *cmd.Ratio)
return nil
}
// BusCompMixCmd defines the command for getting or setting the compressor mix level of a bus.
type BusCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set (in %). If not provided, the current compressor mix level will be returned." optional:""`
}
// Run executes the BusCompMixCmd command, either retrieving the current compressor mix level of the bus or setting it based on the provided argument.
func (cmd *BusCompMixCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Bus.Comp.Mix(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mix level: %.2f%%\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMix(bus.Index.Index, *cmd.Mix); err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor mix level set to: %.2f%%\n", bus.Index.Index, *cmd.Mix)
return nil
}
// BusCompMakeupCmd defines the command for getting or setting the compressor makeup gain of a bus.
type BusCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set (in dB). If not provided, the current compressor makeup gain will be returned." optional:""`
}
// Run executes the BusCompMakeupCmd command, either retrieving the current compressor makeup gain of the bus or setting it based on the provided argument.
func (cmd *BusCompMakeupCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Bus.Comp.Makeup(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor makeup gain: %.2f dB\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetMakeup(bus.Index.Index, *cmd.Makeup); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor makeup gain set to: %.2f dB\n",
bus.Index.Index,
*cmd.Makeup,
)
return nil
}
// BusCompAttackCmd defines the command for getting or setting the compressor attack time of a bus.
type BusCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set (in ms). If not provided, the current compressor attack time will be returned." optional:""`
}
// Run executes the BusCompAttackCmd command, either retrieving the current compressor attack time of the bus or setting it based on the provided argument.
func (cmd *BusCompAttackCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Bus.Comp.Attack(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor attack time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetAttack(bus.Index.Index, *cmd.Attack); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor attack time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Attack,
)
return nil
}
// BusCompHoldCmd defines the command for getting or setting the compressor hold time of a bus.
type BusCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set (in ms). If not provided, the current compressor hold time will be returned." optional:""`
}
// Run executes the BusCompHoldCmd command, either retrieving the current compressor hold time of the bus or setting it based on the provided argument.
func (cmd *BusCompHoldCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Bus.Comp.Hold(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor hold time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetHold(bus.Index.Index, *cmd.Hold); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor hold time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Hold,
)
return nil
}
// BusCompReleaseCmd defines the command for getting or setting the compressor release time of a bus.
type BusCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set (in ms). If not provided, the current compressor release time will be returned." optional:""`
}
// Run executes the BusCompReleaseCmd command, either retrieving the current compressor release time of the bus or setting it based on the provided argument.
func (cmd *BusCompReleaseCmd) Run(ctx *context, bus *BusCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Bus.Comp.Release(bus.Index.Index)
if err != nil {
return err
}
fmt.Fprintf(ctx.Out, "Bus %d compressor release time: %.2f ms\n", bus.Index.Index, resp)
return nil
}
if err := ctx.Client.Bus.Comp.SetRelease(bus.Index.Index, *cmd.Release); err != nil {
return err
}
fmt.Fprintf(
ctx.Out,
"Bus %d compressor release time set to: %.2f ms\n",
bus.Index.Index,
*cmd.Release,
)
return nil
}

142
cmd/xair-cli/cli.go Normal file
View File

@ -0,0 +1,142 @@
package main
import (
"fmt"
"io"
"os"
"runtime/debug"
"strings"
"time"
"github.com/alecthomas/kong"
"github.com/charmbracelet/log"
kongcompletion "github.com/jotaen/kong-completion"
"github.com/onyx-and-iris/xair-cli/internal/xair"
)
const (
trueStr = "true"
falseStr = "false"
)
var version string // Version of the CLI, set at build time.
// VersionFlag is a custom flag type that prints the version and exits.
type VersionFlag string
func (v VersionFlag) Decode(_ *kong.DecodeContext) error { return nil } // nolint: revive
func (v VersionFlag) IsBool() bool { return true } // nolint: revive
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { // nolint: revive, unparam
fmt.Printf("xair-cli version: %s\n", vars["version"])
app.Exit(0)
return nil
}
type context struct {
Client *xair.XAirClient
Out io.Writer
}
type Config struct {
Host string `default:"mixer.local" help:"The host of the X-Air device." env:"XAIR_CLI_HOST" short:"H"`
Port int `default:"10024" help:"The port of the X-Air device." env:"XAIR_CLI_PORT" short:"P"`
Timeout time.Duration `default:"100ms" help:"Timeout for OSC operations." env:"XAIR_CLI_TIMEOUT" short:"T"`
Loglevel string `default:"warn" help:"Log level for the CLI." env:"XAIR_CLI_LOGLEVEL" short:"L" enum:"debug,info,warn,error,fatal"`
}
// CLI is the main struct for the command-line interface.
// It embeds the Config struct for global configuration and defines the available commands and flags.
type CLI struct {
Config `embed:"" prefix:"" help:"The configuration for the CLI."`
Version VersionFlag `help:"Print xair-cli version information and quit" name:"version" short:"v"`
Completion kongcompletion.Completion `help:"Generate shell completion scripts." cmd:""`
Info InfoCmd `help:"Print mixer information." cmd:""`
Raw RawCmd `help:"Send raw OSC messages to the mixer." cmd:""`
Main MainCmdGroup `help:"Control the Main L/R output" cmd:"" group:"Main"`
Strip StripCmdGroup `help:"Control the strips." cmd:"" group:"Strip"`
Bus BusCmdGroup `help:"Control the buses." cmd:"" group:"Bus"`
Headamp HeadampCmdGroup `help:"Control input gain and phantom power." cmd:"" group:"Headamp"`
Snapshot SnapshotCmdGroup `help:"Save and load mixer states." cmd:"" group:"Snapshot"`
Dca DCACmdGroup `help:"Control DCA groups." cmd:"" group:"DCA"`
}
func main() {
var cli CLI
kongcompletion.Register(kong.Must(&cli))
ctx := kong.Parse(
&cli,
kong.Name("xair-cli"),
kong.Description("A CLI to control Behringer X-Air mixers."),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"version": func() string {
if version != "" {
return version
}
info, ok := debug.ReadBuildInfo()
if !ok {
return "(unable to read version)"
}
return strings.Split(info.Main.Version, "-")[0]
}(),
},
)
ctx.FatalIfErrorf(run(ctx, cli.Config))
}
// run is the main entry point for the CLI.
// It connects to the X-Air device, retrieves mixer info, and then runs the command.
func run(ctx *kong.Context, config Config) error {
loglevel, err := log.ParseLevel(config.Loglevel)
if err != nil {
return fmt.Errorf("invalid log level: %w", err)
}
log.SetLevel(loglevel)
client, err := connect(config)
if err != nil {
return fmt.Errorf("failed to connect to X-Air device: %w", err)
}
defer func() {
if err := client.Close(); err != nil {
log.Errorf("failed to close client connection: %v", err)
}
}()
client.StartListening()
resp, err := client.RequestInfo()
if err != nil {
return err
}
log.Infof("Received mixer info: %+v", resp)
ctx.Bind(&context{
Client: client,
Out: os.Stdout,
})
return ctx.Run()
}
// connect creates a new X-Air client based on the provided configuration.
func connect(config Config) (*xair.XAirClient, error) {
client, err := xair.NewXAirClient(
config.Host,
config.Port,
xair.WithTimeout(config.Timeout),
)
if err != nil {
return nil, fmt.Errorf("failed to create X-Air client: %w", err)
}
return client, nil
}

68
cmd/xair-cli/dca.go Normal file
View File

@ -0,0 +1,68 @@
package main
import (
"fmt"
)
// DCACmdGroup is the command group for controlling DCA groups.
type DCACmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the DCA group (1-4)."`
Mute DCAMuteCmd `help:"Get or set the mute status of the DCA group." cmd:""`
Name DCANameCmd `help:"Get or set the name of the DCA group." cmd:""`
} `arg:"" help:"Control a specific DCA group by its index."`
}
// Validate checks if the provided index is within the valid range.
func (cmd *DCACmdGroup) Validate() error {
if cmd.Index.Index < 1 || cmd.Index.Index > 4 {
return fmt.Errorf("DCA group index must be between 1 and 4, got %d", cmd.Index.Index)
}
return nil
}
// DCAMuteCmd is the command to get or set the mute status of a DCA group.
type DCAMuteCmd struct {
State *string `arg:"" help:"Set the mute status of the DCA group." optional:"" enum:"true,false"`
}
// Run executes the DCAMuteCmd command.
func (cmd *DCAMuteCmd) Run(ctx *context, dca *DCACmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.DCA.Mute(dca.Index.Index)
if err != nil {
return fmt.Errorf("failed to get DCA mute status: %w", err)
}
fmt.Fprintf(ctx.Out, "DCA Group %d mute state: %t\n", dca.Index.Index, resp)
return nil
}
if err := ctx.Client.DCA.SetMute(dca.Index.Index, *cmd.State == trueStr); err != nil {
return fmt.Errorf("failed to set DCA mute status: %w", err)
}
return nil
}
// DCANameCmd is the command to get or set the name of a DCA group.
type DCANameCmd struct {
Name *string `arg:"" help:"Set the name of the DCA group." optional:""`
}
// Run executes the DCANameCmd command.
func (cmd *DCANameCmd) Run(ctx *context, dca *DCACmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.DCA.Name(dca.Index.Index)
if err != nil {
return fmt.Errorf("failed to get DCA name: %w", err)
}
if resp == "" {
resp = fmt.Sprintf("DCA %d", dca.Index.Index)
}
fmt.Fprintf(ctx.Out, "DCA Group %d is named '%s'\n", dca.Index.Index, resp)
return nil
}
if err := ctx.Client.DCA.SetName(dca.Index.Index, *cmd.Name); err != nil {
return fmt.Errorf("failed to set DCA name: %w", err)
}
return nil
}

139
cmd/xair-cli/headamp.go Normal file
View File

@ -0,0 +1,139 @@
package main
import (
"fmt"
"time"
"github.com/charmbracelet/log"
)
// HeadampCmdGroup defines the command group for controlling input gain and phantom power of a headamp, allowing users to specify the index of the headamp they want to control.
type HeadampCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the headamp."`
Gain HeadampGainCmd `help:"Get or set the gain of the headamp." cmd:""`
Phantom HeadampPhantomCmd `help:"Get or set the phantom power state of the headamp." cmd:""`
} `arg:"" help:"Control a specific headamp by index."`
}
// HeadampGainCmd defines the command for getting or setting the gain of a headamp, allowing users to specify the gain in dB and an optional duration for a gradual fade when setting the gain.
type HeadampGainCmd struct {
Duration time.Duration `help:"The duration of the fade in/out when setting the gain." default:"5s"`
Gain *float64 `help:"The gain of the headamp in dB." arg:"" optional:""`
}
// Run executes the HeadampGainCmd command, either retrieving the current gain of the headamp or setting it based on the provided argument, with an optional fade duration for smooth transitions.
func (cmd *HeadampGainCmd) Run(ctx *context, headamp *HeadampCmdGroup) error {
if cmd.Gain == nil {
resp, err := ctx.Client.HeadAmp.Gain(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get headamp gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d gain: %.2f dB\n", headamp.Index.Index, resp)
return nil
}
currentGain, err := ctx.Client.HeadAmp.Gain(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current headamp gain: %w", err)
}
if err := gradualGainAdjust(
ctx,
headamp.Index.Index,
currentGain,
*cmd.Gain,
cmd.Duration,
); err != nil {
return fmt.Errorf("failed to set headamp gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d gain set to: %.2f dB\n", headamp.Index.Index, *cmd.Gain)
return nil
}
// gradualGainAdjust gradually adjusts gain from current to target over specified duration.
func gradualGainAdjust(
ctx *context,
index int,
currentGain, targetGain float64,
duration time.Duration,
) error {
gainDiff := targetGain - currentGain
stepInterval := 100 * time.Millisecond
totalSteps := int(duration / stepInterval)
if totalSteps < 1 {
totalSteps = 1
stepInterval = duration
}
stepIncrement := gainDiff / float64(totalSteps)
log.Debugf("Adjusting Headamp %d gain from %.2f dB to %.2f dB over %v...\n",
index, currentGain, targetGain, duration)
for step := 1; step <= totalSteps; step++ {
newGain := currentGain + (stepIncrement * float64(step))
if step == totalSteps {
newGain = targetGain
}
err := ctx.Client.HeadAmp.SetGain(index, newGain)
if err != nil {
return err
}
if step%10 == 0 || step == totalSteps {
log.Debugf(" Step %d/%d: %.2f dB\n", step, totalSteps, newGain)
}
if step < totalSteps {
time.Sleep(stepInterval)
}
}
return nil
}
// HeadampPhantomCmd defines the command for getting or setting the phantom power state of a headamp, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type HeadampPhantomCmd struct {
State *string `help:"The phantom power state of the headamp." arg:"" enum:"true,on,false,off" optional:""`
}
// Validate checks if the provided phantom power state is valid and normalises it to "true" or "false".
func (cmd *HeadampPhantomCmd) Validate() error {
if cmd.State != nil {
switch *cmd.State {
case trueStr, "on":
*cmd.State = trueStr
case falseStr, "off":
*cmd.State = falseStr
default:
return fmt.Errorf("invalid phantom power state: %s", *cmd.State)
}
}
return nil
}
// Run executes the HeadampPhantomCmd command, either retrieving the current phantom power state of the headamp or setting it based on the provided argument.
func (cmd *HeadampPhantomCmd) Run(ctx *context, headamp *HeadampCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.HeadAmp.PhantomPower(headamp.Index.Index)
if err != nil {
return fmt.Errorf("failed to get headamp phantom power state: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d phantom power: %t\n", headamp.Index.Index, resp)
return nil
}
if err := ctx.Client.HeadAmp.SetPhantomPower(
headamp.Index.Index,
*cmd.State == trueStr,
); err != nil {
return fmt.Errorf("failed to set headamp phantom power state: %w", err)
}
fmt.Fprintf(ctx.Out, "Headamp %d phantom power set to: %s\n", headamp.Index.Index, *cmd.State)
return nil
}

17
cmd/xair-cli/info.go Normal file
View File

@ -0,0 +1,17 @@
package main
import "fmt"
type InfoCmd struct{}
func (cmd *InfoCmd) Run(ctx *context) error { // nolint: unparam
fmt.Fprintf(
ctx.Out,
"Host: %s | Name: %s | Model: %s | Firmware: %s\n",
ctx.Client.Info.Host,
ctx.Client.Info.Name,
ctx.Client.Info.Model,
ctx.Client.Info.Firmware,
)
return nil
}

510
cmd/xair-cli/main.go Normal file
View File

@ -0,0 +1,510 @@
// Package main implements the command-line interface for controlling an XAir digital mixer.
package main
import (
"fmt"
"time"
)
// MainCmdGroup defines the command group for controlling the Main L/R output, including commands for mute state, fader level, and fade-in/fade-out times.
type MainCmdGroup struct {
Mute MainMuteCmd `help:"Get or set the mute state of the Main L/R output." cmd:""`
Fader MainFaderCmd `help:"Get or set the fader level of the Main L/R output." cmd:""`
Fadein MainFadeinCmd `help:"Fade in the Main L/R output over a specified duration." cmd:""`
Fadeout MainFadeoutCmd `help:"Fade out the Main L/R output over a specified duration." cmd:""`
Eq MainEqCmdGroup `help:"Commands for controlling the equaliser settings of the Main L/R output." cmd:"eq"`
Comp MainCompCmdGroup `help:"Commands for controlling the compressor settings of the Main L/R output." cmd:"comp"`
}
// MainMuteCmd defines the command for getting or setting the mute state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainMuteCmd struct {
Mute *string `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainMuteCmd command, either retrieving the current mute state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainMuteCmd) Run(ctx *context) error {
if cmd.Mute == nil {
resp, err := ctx.Client.Main.Mute()
if err != nil {
return fmt.Errorf("failed to get Main L/R mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R mute state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.SetMute(*cmd.Mute == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R mute state set to: %s\n", *cmd.Mute)
return nil
}
// MainFaderCmd defines the command for getting or setting the fader level of the Main L/R output, allowing users to specify the desired level in dB.
type MainFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set. If not provided, the current level will be printed." optional:""`
}
// Run executes the MainFaderCmd command, either retrieving the current fader level of the Main L/R output or setting it based on the provided argument.
func (cmd *MainFaderCmd) Run(ctx *context) error {
if cmd.Level == nil {
resp, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R fader level: %.2f\n", resp)
return nil
}
if err := ctx.Client.Main.SetFader(*cmd.Level); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R fader level set to: %.2f\n", *cmd.Level)
return nil
}
// MainFadeinCmd defines the command for getting or setting the fade-in time of the Main L/R output, allowing users to specify the desired duration for the fade-in effect.
type MainFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-in. If not provided, the current target level will be printed." default:"0.0" arg:""`
}
// Run executes the MainFadeinCmd command, either retrieving the current fade-in time of the Main L/R output or setting it based on the provided argument, with an optional target level for the fade-in effect.
func (cmd *MainFadeinCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or above the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.Main.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main L/R fade-in completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainFadeoutCmd defines the command for getting or setting the fade-out time of the Main L/R output, allowing users to specify the desired duration for the fade-out effect and an optional target level to fade out to.
type MainFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out. (in seconds.)" default:"5s"`
Target float64 ` help:"The target level for the fade-out. If not provided, the current target level will be printed." default:"-90.0" arg:""`
}
// Run executes the MainFadeoutCmd command, either retrieving the current fade-out time of the Main L/R output or setting it based on the provided argument, with an optional target level for the fade-out effect.
func (cmd *MainFadeoutCmd) Run(ctx *context) error {
currentLevel, err := ctx.Client.Main.Fader()
if err != nil {
return fmt.Errorf("failed to get Main L/R fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f) is already at or below the target level (%.2f)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.Main.SetFader(currentLevel); err != nil {
return fmt.Errorf("failed to set Main L/R fader level: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(ctx.Out, "Main L/R fade-out completed. Final level: %.2f\n", currentLevel)
return nil
}
// MainEqCmdGroup defines the command group for controlling the equaliser settings of the Main L/R output, including commands for getting or setting the EQ parameters.
type MainEqCmdGroup struct {
On MainEqOnCmd `help:"Get or set the EQ on/off state of the Main L/R output." cmd:"on"`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain MainEqBandGainCmd `help:"Get or set the gain of the specified EQ band." cmd:"gain"`
Freq MainEqBandFreqCmd `help:"Get or set the frequency of the specified EQ band." cmd:"freq"`
Q MainEqBandQCmd `help:"Get or set the Q factor of the specified EQ band." cmd:"q"`
Type MainEqBandTypeCmd `help:"Get or set the type of the specified EQ band." cmd:"type"`
} `help:"Commands for controlling individual EQ bands of the Main L/R output." arg:""`
}
// Validate checks if the provided EQ band number is within the valid range (1-6) for the Main L/R output.
func (cmd *MainEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 6 {
return fmt.Errorf("EQ band number must be between 1 and 6, got %d", *cmd.Band.Band)
}
return nil
}
// MainEqOnCmd defines the command for getting or setting the EQ on/off state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainEqOnCmd struct {
Enable *string `arg:"" help:"The EQ on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainEqOnCmd command, either retrieving the current EQ on/off state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Main.Eq.On(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.Eq.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R EQ on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on the Main L/R output, allowing users to specify the desired gain in dB.
type MainEqBandGainCmd struct {
Level *float64 `arg:"" help:"The gain level to set for the specified EQ band. If not provided, the current gain will be printed." optional:""`
}
// Run executes the MainEqBandGainCmd command, either retrieving the current gain of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandGainCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Main.Eq.Gain(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d gain: %.2f dB\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetGain(0, *mainEq.Band.Band, *cmd.Level); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d gain: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Main L/R EQ band %d gain set to: %.2f dB\n",
*mainEq.Band.Band,
*cmd.Level,
)
return nil
}
// MainEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on the Main L/R output, allowing users to specify the desired frequency in Hz.
type MainEqBandFreqCmd struct {
Frequency *float64 `arg:"" help:"The frequency to set for the specified EQ band. If not provided, the current frequency will be printed." optional:""`
}
// Run executes the MainEqBandFreqCmd command, either retrieving the current frequency of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandFreqCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Frequency == nil {
resp, err := ctx.Client.Main.Eq.Frequency(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main L/R EQ band %d frequency: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d frequency: %.2f Hz\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetFrequency(0, *mainEq.Band.Band, *cmd.Frequency); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d frequency: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(
ctx.Out,
"Main L/R EQ band %d frequency set to: %.2f Hz\n",
*mainEq.Band.Band,
*cmd.Frequency,
)
return nil
}
// MainEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on the Main L/R output, allowing users to specify the desired Q factor.
type MainEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the specified EQ band. If not provided, the current Q factor will be printed." optional:""`
}
// Run executes the MainEqBandQCmd command, either retrieving the current Q factor of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandQCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Q == nil {
resp, err := ctx.Client.Main.Eq.Q(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf(
"failed to get Main L/R EQ band %d Q factor: %w",
*mainEq.Band.Band,
err,
)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d Q factor: %.2f\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetQ(0, *mainEq.Band.Band, *cmd.Q); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d Q factor: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d Q factor set to: %.2f\n", *mainEq.Band.Band, *cmd.Q)
return nil
}
// MainEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on the Main L/R output, allowing users to specify the desired type as "peaking", "low_shelf", "high_shelf", "low_pass", or "high_pass".
type MainEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the specified EQ band. If not provided, the current type will be printed." optional:"" enum:"peaking,low_shelf,high_shelf,low_pass,high_pass"`
}
// Run executes the MainEqBandTypeCmd command, either retrieving the current type of a specific EQ band on the Main L/R output or setting it based on the provided argument.
func (cmd *MainEqBandTypeCmd) Run(ctx *context, mainEq *MainEqCmdGroup) error {
if cmd.Type == nil {
resp, err := ctx.Client.Main.Eq.Type(0, *mainEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get Main L/R EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d type: %s\n", *mainEq.Band.Band, resp)
return nil
}
if err := ctx.Client.Main.Eq.SetType(0, *mainEq.Band.Band, *cmd.Type); err != nil {
return fmt.Errorf("failed to set Main L/R EQ band %d type: %w", *mainEq.Band.Band, err)
}
fmt.Fprintf(ctx.Out, "Main L/R EQ band %d type set to: %s\n", *mainEq.Band.Band, *cmd.Type)
return nil
}
// MainCompCmdGroup defines the command group for controlling the compressor settings of the Main L/R output, including commands for getting or setting the compressor parameters.
type MainCompCmdGroup struct {
On MainCompOnCmd `help:"Get or set the compressor on/off state of the Main L/R output." cmd:"on"`
Mode MainCompModeCmd `help:"Get or set the compressor mode of the Main L/R output." cmd:"mode"`
Threshold MainCompThresholdCmd `help:"Get or set the compressor threshold of the Main L/R output." cmd:"threshold"`
Ratio MainCompRatioCmd `help:"Get or set the compressor ratio of the Main L/R output." cmd:"ratio"`
Mix MainCompMixCmd `help:"Get or set the compressor mix level of the Main L/R output." cmd:"mix"`
Makeup MainCompMakeupCmd `help:"Get or set the compressor makeup gain of the Main L/R output." cmd:"makeup"`
Attack MainCompAttackCmd `help:"Get or set the compressor attack time of the Main L/R output." cmd:"attack"`
Hold MainCompHoldCmd `help:"Get or set the compressor hold time of the Main L/R output." cmd:"hold"`
Release MainCompReleaseCmd `help:"Get or set the compressor release time of the Main L/R output." cmd:"release"`
}
// MainCompOnCmd defines the command for getting or setting the compressor on/off state of the Main L/R output, allowing users to specify the desired state as "true"/"on" or "false"/"off".
type MainCompOnCmd struct {
Enable *string `arg:"" help:"The compressor on/off state to set. If not provided, the current state will be printed." optional:"" enum:"true,false"`
}
// Run executes the MainCompOnCmd command, either retrieving the current compressor on/off state of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompOnCmd) Run(ctx *context) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Main.Comp.On(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor on/off state: %t\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetOn(0, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set Main L/R compressor on/off state: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor on/off state set to: %t\n", *cmd.Enable == trueStr)
return nil
}
// MainCompModeCmd defines the command for getting or setting the compressor mode of the Main L/R output, allowing users to specify the desired mode as "comp" or "exp".
type MainCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set. If not provided, the current mode will be printed." optional:"" enum:"comp,exp"`
}
// Run executes the MainCompModeCmd command, either retrieving the current compressor mode of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompModeCmd) Run(ctx *context) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Main.Comp.Mode(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mode: %s\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMode(0, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set Main L/R compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mode set to: %s\n", *cmd.Mode)
return nil
}
// MainCompThresholdCmd defines the command for getting or setting the compressor threshold of the Main L/R output, allowing users to specify the desired threshold in dB.
type MainCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set. If not provided, the current threshold will be printed." optional:""`
}
// Run executes the MainCompThresholdCmd command, either retrieving the current compressor threshold of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompThresholdCmd) Run(ctx *context) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Main.Comp.Threshold(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor threshold: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetThreshold(0, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set Main L/R compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor threshold set to: %.2f dB\n", *cmd.Threshold)
return nil
}
// MainCompRatioCmd defines the command for getting or setting the compressor ratio of the Main L/R output, allowing users to specify the desired ratio.
type MainCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current ratio will be printed." optional:""`
}
// Run executes the MainCompRatioCmd command, either retrieving the current compressor ratio of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompRatioCmd) Run(ctx *context) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Main.Comp.Ratio(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor ratio: %.2f\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetRatio(0, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set Main L/R compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor ratio set to: %.2f\n", *cmd.Ratio)
return nil
}
// MainCompMixCmd defines the command for getting or setting the compressor mix level of the Main L/R output, allowing users to specify the desired mix level in percentage.
type MainCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix level to set. If not provided, the current mix level will be printed." optional:""`
}
// Run executes the MainCompMixCmd command, either retrieving the current compressor mix level of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompMixCmd) Run(ctx *context) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Main.Comp.Mix(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mix level: %.2f%%\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMix(0, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set Main L/R compressor mix level: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor mix level set to: %.2f%%\n", *cmd.Mix)
return nil
}
// MainCompMakeupCmd defines the command for getting or setting the compressor makeup gain of the Main L/R output, allowing users to specify the desired makeup gain in dB.
type MainCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set. If not provided, the current makeup gain will be printed." optional:""`
}
// Run executes the MainCompMakeupCmd command, either retrieving the current compressor makeup gain of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompMakeupCmd) Run(ctx *context) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Main.Comp.Makeup(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor makeup gain: %.2f dB\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetMakeup(0, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set Main L/R compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor makeup gain set to: %.2f dB\n", *cmd.Makeup)
return nil
}
// MainCompAttackCmd defines the command for getting or setting the compressor attack time of the Main L/R output, allowing users to specify the desired attack time in milliseconds.
type MainCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set. If not provided, the current attack time will be printed." optional:""`
}
// Run executes the MainCompAttackCmd command, either retrieving the current compressor attack time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompAttackCmd) Run(ctx *context) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Main.Comp.Attack(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor attack time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetAttack(0, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set Main L/R compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor attack time set to: %.2f ms\n", *cmd.Attack)
return nil
}
// MainCompHoldCmd defines the command for getting or setting the compressor hold time of the Main L/R output, allowing users to specify the desired hold time in milliseconds.
type MainCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set. If not provided, the current hold time will be printed." optional:""`
}
// Run executes the MainCompHoldCmd command, either retrieving the current compressor hold time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompHoldCmd) Run(ctx *context) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Main.Comp.Hold(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor hold time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetHold(0, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set Main L/R compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor hold time set to: %.2f ms\n", *cmd.Hold)
return nil
}
// MainCompReleaseCmd defines the command for getting or setting the compressor release time of the Main L/R output, allowing users to specify the desired release time in milliseconds.
type MainCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set. If not provided, the current release time will be printed." optional:""`
}
// Run executes the MainCompReleaseCmd command, either retrieving the current compressor release time of the Main L/R output or setting it based on the provided argument.
func (cmd *MainCompReleaseCmd) Run(ctx *context) error {
if cmd.Release == nil {
resp, err := ctx.Client.Main.Comp.Release(0)
if err != nil {
return fmt.Errorf("failed to get Main L/R compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor release time: %.2f ms\n", resp)
return nil
}
if err := ctx.Client.Main.Comp.SetRelease(0, *cmd.Release); err != nil {
return fmt.Errorf("failed to set Main L/R compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Main L/R compressor release time set to: %.2f ms\n", *cmd.Release)
return nil
}

39
cmd/xair-cli/raw.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"github.com/charmbracelet/log"
)
// RawCmd represents the command to send raw OSC messages to the mixer.
type RawCmd struct {
Address string `help:"The OSC address to send the message to." arg:""`
Args []string `help:"The arguments to include in the OSC message." arg:"" optional:""`
}
// Run executes the RawCmd by sending the specified OSC message to the mixer and optionally waiting for a response.
func (cmd *RawCmd) Run(ctx *context) error {
params := make([]any, len(cmd.Args))
for i, arg := range cmd.Args {
params[i] = arg
}
if err := ctx.Client.SendMessage(cmd.Address, params...); err != nil {
return fmt.Errorf("failed to send raw OSC message: %w", err)
}
if len(params) > 0 {
log.Debugf("Sent OSC message: %s with args: %v\n", cmd.Address, cmd.Args)
return nil
}
msg, err := ctx.Client.ReceiveMessage()
if err != nil {
return fmt.Errorf("failed to receive response for raw OSC message: %w", err)
}
if msg != nil {
fmt.Fprintf(ctx.Out, "Received response: %s with args: %v\n", msg.Address, msg.Arguments)
}
return nil
}

85
cmd/xair-cli/snapshot.go Normal file
View File

@ -0,0 +1,85 @@
package main
import "fmt"
type SnapshotCmdGroup struct {
List ListCmd `help:"List all snapshots." cmd:"list"`
Index struct {
Index *int `arg:"" help:"The index of the snapshot." optional:""`
Name NameCmd `help:"Get or set the name of a snapshot." cmd:"name"`
Save SaveCmd `help:"Save the current mixer state to a snapshot." cmd:"save"`
Load LoadCmd `help:"Load a mixer state from a snapshot." cmd:"load"`
Delete DeleteCmd `help:"Delete a snapshot." cmd:"delete"`
} `help:"The index of the snapshot." arg:""`
}
// Validate checks if the provided snapshot index is within the valid range (1-64) when any of the subcommands that require an index are used.
func (c *SnapshotCmdGroup) Validate() error {
if c.Index.Index == nil {
return nil
}
if *c.Index.Index < 1 || *c.Index.Index > 64 {
return fmt.Errorf("snapshot index must be between 1 and 64")
}
return nil
}
type ListCmd struct{}
func (c *ListCmd) Run(ctx *context) error {
for i := range 64 {
name, err := ctx.Client.Snapshot.Name(i + 1)
if err != nil {
return fmt.Errorf("failed to get name for snapshot %d: %w", i+1, err)
}
if name == "" {
continue
}
fmt.Fprintf(ctx.Out, "%d: %s\n", i+1, name)
}
return nil
}
type NameCmd struct {
Name *string `arg:"" help:"The name of the snapshot." optional:""`
}
func (c *NameCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
if c.Name == nil {
name, err := ctx.Client.Snapshot.Name(*snapshot.Index.Index)
if err != nil {
return err
}
fmt.Fprintln(ctx.Out, name)
return nil
}
return ctx.Client.Snapshot.SetName(*snapshot.Index.Index, *c.Name)
}
type SaveCmd struct {
Name string `arg:"" help:"The name of the snapshot."`
}
func (c *SaveCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
err := ctx.Client.Snapshot.CurrentName(c.Name)
if err != nil {
return err
}
return ctx.Client.Snapshot.CurrentSave(*snapshot.Index.Index)
}
type LoadCmd struct{}
func (c *LoadCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
return ctx.Client.Snapshot.CurrentLoad(*snapshot.Index.Index)
}
type DeleteCmd struct{}
func (c *DeleteCmd) Run(ctx *context, snapshot *SnapshotCmdGroup) error {
return ctx.Client.Snapshot.CurrentDelete(*snapshot.Index.Index)
}

863
cmd/xair-cli/strip.go Normal file
View File

@ -0,0 +1,863 @@
package main
import (
"fmt"
"time"
)
// StripCmdGroup defines the command group for controlling the strips of the mixer, including commands for getting and setting various parameters such as mute state, fader level, send levels, and EQ settings.
type StripCmdGroup struct {
Index struct {
Index int `arg:"" help:"The index of the strip. (1-based indexing)"`
Mute StripMuteCmd ` help:"Get or set the mute state of the strip." cmd:""`
Fader StripFaderCmd ` help:"Get or set the fader level of the strip." cmd:""`
Fadein StripFadeinCmd ` help:"Fade in the strip over a specified duration." cmd:""`
Fadeout StripFadeoutCmd ` help:"Fade out the strip over a specified duration." cmd:""`
Send StripSendCmd ` help:"Get or set the send level for a specific bus." cmd:""`
Name StripNameCmd ` help:"Get or set the name of the strip." cmd:""`
Gate StripGateCmdGroup ` help:"Commands related to the strip gate." cmd:"gate"`
Eq StripEqCmdGroup ` help:"Commands related to the strip EQ." cmd:"eq"`
Comp StripCompCmdGroup ` help:"Commands related to the strip compressor." cmd:"comp"`
} `arg:"" help:"Control a specific strip by index."`
}
// StripMuteCmd defines the command for getting or setting the mute state of a strip.
type StripMuteCmd struct {
State *string `arg:"" help:"The mute state to set (true or false). If not provided, the current mute state will be returned." optional:"" enum:"true,false"`
}
// Run executes the StripMuteCmd command, either retrieving the current mute state of the strip or setting it based on the provided argument.
func (cmd *StripMuteCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.State == nil {
resp, err := ctx.Client.Strip.Mute(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d mute state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetMute(strip.Index.Index, *cmd.State == trueStr); err != nil {
return fmt.Errorf("failed to set mute state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d mute state set to: %s\n", strip.Index.Index, *cmd.State)
return nil
}
// StripFaderCmd defines the command for getting or setting the fader level of a strip.
type StripFaderCmd struct {
Level *float64 `arg:"" help:"The fader level to set (in dB)." optional:""`
}
// Run executes the StripFaderCmd command, either retrieving the current fader level of the strip or setting it based on the provided argument.
func (cmd *StripFaderCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d fader level: %.2f dB\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetFader(strip.Index.Index, *cmd.Level); err != nil {
return fmt.Errorf("failed to set fader level: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d fader level set to: %.2f dB\n", strip.Index.Index, *cmd.Level)
return nil
}
// StripFadeinCmd defines the command for fading in a strip over a specified duration, gradually increasing the fader level from its current value to a target value.
type StripFadeinCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-in (in seconds)." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"0.0" arg:""`
}
// Run executes the StripFadeinCmd command, gradually increasing the fader level of the strip from its current value to the specified target value over the specified duration.
func (cmd *StripFadeinCmd) Run(ctx *context, strip *StripCmdGroup) error {
currentLevel, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel >= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or above the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(cmd.Target - currentLevel)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel < cmd.Target {
currentLevel++
if err := ctx.Client.Strip.SetFader(strip.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level during fade-in: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Strip %d fade-in complete. Final level: %.2f dB\n",
strip.Index.Index,
cmd.Target,
)
return nil
}
// StripFadeoutCmd defines the command for fading out a strip over a specified duration, gradually decreasing the fader level from its current value to a target value.
type StripFadeoutCmd struct {
Duration time.Duration `flag:"" help:"The duration of the fade-out (in seconds)." default:"5s"`
Target float64 ` help:"The target fader level (in dB)." default:"-90.0" arg:""`
}
// Run executes the StripFadeoutCmd command, gradually decreasing the fader level of the strip from its current value to the specified target value over the specified duration.
func (cmd *StripFadeoutCmd) Run(ctx *context, strip *StripCmdGroup) error {
{
currentLevel, err := ctx.Client.Strip.Fader(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get current fader level: %w", err)
}
if currentLevel <= cmd.Target {
return fmt.Errorf(
"current fader level (%.2f dB) is already at or below the target level (%.2f dB)",
currentLevel,
cmd.Target,
)
}
totalSteps := float64(currentLevel - cmd.Target)
stepDuration := time.Duration(cmd.Duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentLevel > cmd.Target {
currentLevel--
if err := ctx.Client.Strip.SetFader(strip.Index.Index, currentLevel); err != nil {
return fmt.Errorf("failed to set fader level during fade-out: %w", err)
}
time.Sleep(stepDuration)
}
fmt.Fprintf(
ctx.Out,
"Strip %d fade-out complete. Final level: %.2f dB\n",
strip.Index.Index,
cmd.Target,
)
return nil
}
}
// StripSendCmd defines the command for getting or setting the auxiliary send level
// for a specific send destination (mix bus or effects channel) on a strip.
type StripSendCmd struct {
SendIndex int `arg:"" help:"The index of the send destination (mix bus or effects channel). (1-based indexing)"`
Level *float64 `arg:"" help:"The send level to set (in dB)." optional:""`
}
// Run executes the StripSendCmd command, either retrieving the current send level for the specified destination
// or setting it based on the provided argument.
func (cmd *StripSendCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Level == nil {
resp, err := ctx.Client.Strip.SendLevel(strip.Index.Index, cmd.SendIndex)
if err != nil {
return fmt.Errorf("failed to get send level: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d send %d level: %.2f dB\n",
strip.Index.Index,
cmd.SendIndex,
resp,
)
return nil
}
if err := ctx.Client.Strip.SetSendLevel(
strip.Index.Index,
cmd.SendIndex,
*cmd.Level,
); err != nil {
return fmt.Errorf("failed to set send level: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d send %d level set to: %.2f dB\n",
strip.Index.Index,
cmd.SendIndex,
*cmd.Level,
)
return nil
}
// StripNameCmd defines the command for getting or setting the name of a strip, allowing users to assign custom names to strips for easier identification and organisation.
type StripNameCmd struct {
Name *string `arg:"" help:"The name to set for the strip." optional:""`
}
// Run executes the StripNameCmd command, either retrieving the current name of the strip or setting it based on the provided argument.
func (cmd *StripNameCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Name == nil {
resp, err := ctx.Client.Strip.Name(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get strip name: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d name: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.SetName(strip.Index.Index, *cmd.Name); err != nil {
return fmt.Errorf("failed to set strip name: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d name set to: %s\n", strip.Index.Index, *cmd.Name)
return nil
}
// StripGateCmdGroup defines the command group for controlling the gate settings of a strip, including commands for getting and setting the gate on/off state, mode, threshold, range, attack time, hold time, and release time.
type StripGateCmdGroup struct {
On StripGateOnCmd `help:"Get or set the gate on/off state of the strip." cmd:""`
Mode StripGateModeCmd `help:"Get or set the gate mode of the strip." cmd:""`
Threshold StripGateThresholdCmd `help:"Get or set the gate threshold of the strip." cmd:""`
Range StripGateRangeCmd `help:"Get or set the gate range of the strip." cmd:""`
Attack StripGateAttackCmd `help:"Get or set the gate attack time of the strip." cmd:""`
Hold StripGateHoldCmd `help:"Get or set the gate hold time of the strip." cmd:""`
Release StripGateReleaseCmd `help:"Get or set the gate release time of the strip." cmd:""`
}
// StripGateOnCmd defines the command for getting or setting the gate on/off state of a strip, allowing users to enable or disable the gate effect on the strip.
type StripGateOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the gate." optional:"" enum:"true,false"`
}
// Run executes the StripGateOnCmd command, either retrieving the current gate on/off state of the strip or setting it based on the provided argument.
func (cmd *StripGateOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Gate.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set gate state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripGateModeCmd defines the command for getting or setting the gate mode of a strip, allowing users to choose from different gate modes such as exp2, exp3, exp4, gate, or duck.
type StripGateModeCmd struct {
Mode *string `arg:"" help:"The gate mode to set." optional:"" enum:"exp2,exp3,exp4,gate,duck"`
}
// Run executes the StripGateModeCmd command, either retrieving the current gate mode of the strip or setting it based on the provided argument.
func (cmd *StripGateModeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Strip.Gate.Mode(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate mode: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetMode(strip.Index.Index, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set gate mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate mode set to: %s\n", strip.Index.Index, *cmd.Mode)
return nil
}
// StripGateThresholdCmd defines the command for getting or setting the gate threshold of a strip, allowing users to specify the threshold level at which the gate will start to attenuate the signal.
type StripGateThresholdCmd struct {
Threshold *float64 `arg:"" help:"The gate threshold to set (in dB)." optional:""`
}
// Run executes the StripGateThresholdCmd command, either retrieving the current gate threshold of the strip or setting it based on the provided argument.
func (cmd *StripGateThresholdCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Strip.Gate.Threshold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate threshold: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetThreshold(strip.Index.Index, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set gate threshold: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate threshold set to: %.2f\n",
strip.Index.Index,
*cmd.Threshold,
)
return nil
}
// StripGateRangeCmd defines the command for getting or setting the gate range of a strip, allowing users to specify the amount of attenuation applied by the gate when the signal falls below the threshold.
type StripGateRangeCmd struct {
Range *float64 `arg:"" help:"The gate range to set (in dB)." optional:""`
}
// Run executes the StripGateRangeCmd command, either retrieving the current gate range of the strip or setting it based on the provided argument.
func (cmd *StripGateRangeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Range == nil {
resp, err := ctx.Client.Strip.Gate.Range(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate range: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate range: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetRange(strip.Index.Index, *cmd.Range); err != nil {
return fmt.Errorf("failed to set gate range: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate range set to: %.2f\n", strip.Index.Index, *cmd.Range)
return nil
}
// StripGateAttackCmd defines the command for getting or setting the gate attack time of a strip, allowing users to specify the time it takes for the gate to fully open after the signal exceeds the threshold.
type StripGateAttackCmd struct {
Attack *float64 `arg:"" help:"The gate attack time to set (in ms)." optional:""`
}
// Run executes the StripGateAttackCmd command, either retrieving the current gate attack time of the strip or setting it based on the provided argument.
func (cmd *StripGateAttackCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Strip.Gate.Attack(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate attack time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetAttack(strip.Index.Index, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set gate attack time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate attack time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Attack,
)
return nil
}
// StripGateHoldCmd defines the command for getting or setting the gate hold time of a strip, allowing users to specify the time that the gate remains open after the signal falls below the threshold before it starts to close.
type StripGateHoldCmd struct {
Hold *float64 `arg:"" help:"The gate hold time to set (in ms)." optional:""`
}
// Run executes the StripGateHoldCmd command, either retrieving the current gate hold time of the strip or setting it based on the provided argument.
func (cmd *StripGateHoldCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Strip.Gate.Hold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate hold time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetHold(strip.Index.Index, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set gate hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate hold time set to: %.2f ms\n", strip.Index.Index, *cmd.Hold)
return nil
}
// StripGateReleaseCmd defines the command for getting or setting the gate release time of a strip, allowing users to specify the time it takes for the gate to fully close after the signal falls below the threshold and the hold time has elapsed.
type StripGateReleaseCmd struct {
Release *float64 `arg:"" help:"The gate release time to set (in ms)." optional:""`
}
// Run executes the StripGateReleaseCmd command, either retrieving the current gate release time of the strip or setting it based on the provided argument.
func (cmd *StripGateReleaseCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Strip.Gate.Release(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get gate release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d gate release time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Gate.SetRelease(strip.Index.Index, *cmd.Release); err != nil {
return fmt.Errorf("failed to set gate release time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d gate release time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Release,
)
return nil
}
// StripEqCmdGroup defines the command group for controlling the EQ settings of a strip, including commands for getting and setting the EQ on/off state and parameters for each EQ band such as gain, frequency, Q factor, and type.
type StripEqCmdGroup struct {
On StripEqOnCmd `help:"Get or set the EQ on/off state of the strip." cmd:""`
Band struct {
Band *int `arg:"" help:"The EQ band number." optional:""`
Gain StripEqBandGainCmd `help:"Get or set the gain of the EQ band." cmd:""`
Freq StripEqBandFreqCmd `help:"Get or set the frequency of the EQ band." cmd:""`
Q StripEqBandQCmd `help:"Get or set the Q factor of the EQ band." cmd:""`
Type StripEqBandTypeCmd `help:"Get or set the type of the EQ band." cmd:""`
} `help:"Commands for controlling a specific EQ band of the strip." arg:""`
}
// Validate checks if the provided EQ band number is valid (between 1 and 4) and returns an error if it is not.
func (cmd *StripEqCmdGroup) Validate() error {
if cmd.Band.Band == nil {
return nil
}
if *cmd.Band.Band < 1 || *cmd.Band.Band > 4 {
return fmt.Errorf("EQ band number must be between 1 and 4, got %d", *cmd.Band.Band)
}
return nil
}
// StripEqOnCmd defines the command for getting or setting the EQ on/off state of a strip, allowing users to enable or disable the EQ effect on the strip.
type StripEqOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the EQ." optional:"" enum:"true,false"`
}
// Run executes the StripEqOnCmd command, either retrieving the current EQ on/off state of the strip or setting it based on the provided argument.
func (cmd *StripEqOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Eq.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get EQ state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d EQ state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Eq.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set EQ state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d EQ state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripEqBandGainCmd defines the command for getting or setting the gain of a specific EQ band on a strip, allowing users to adjust the level of the signal for that band in decibels (dB).
type StripEqBandGainCmd struct {
Gain *float64 `arg:"" help:"The gain to set for the EQ band (in dB)." optional:""`
}
// Run executes the StripEqBandGainCmd command, either retrieving the current gain of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandGainCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Gain == nil {
resp, err := ctx.Client.Strip.Eq.Gain(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d gain: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetGain(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Gain,
); err != nil {
return fmt.Errorf("failed to set EQ band gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d gain set to: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Gain,
)
return nil
}
// StripEqBandFreqCmd defines the command for getting or setting the frequency of a specific EQ band on a strip, allowing users to adjust the centre frequency of the band in hertz (Hz).
type StripEqBandFreqCmd struct {
Freq *float64 `arg:"" help:"The frequency to set for the EQ band (in Hz)." optional:""`
}
// Run executes the StripEqBandFreqCmd command, either retrieving the current frequency of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandFreqCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Freq == nil {
resp, err := ctx.Client.Strip.Eq.Frequency(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band frequency: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d frequency: %.2f Hz\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetFrequency(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Freq,
); err != nil {
return fmt.Errorf("failed to set EQ band frequency: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d frequency set to: %.2f Hz\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Freq,
)
return nil
}
// StripEqBandQCmd defines the command for getting or setting the Q factor of a specific EQ band on a strip, allowing users to adjust the bandwidth of the band, which determines how wide or narrow the affected frequency range is.
type StripEqBandQCmd struct {
Q *float64 `arg:"" help:"The Q factor to set for the EQ band." optional:""`
}
// Run executes the StripEqBandQCmd command, either retrieving the current Q factor of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandQCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Q == nil {
resp, err := ctx.Client.Strip.Eq.Q(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band Q factor: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d Q factor: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetQ(strip.Index.Index, *stripEq.Band.Band, *cmd.Q); err != nil {
return fmt.Errorf("failed to set EQ band Q factor: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d Q factor set to: %.2f\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Q,
)
return nil
}
// StripEqBandTypeCmd defines the command for getting or setting the type of a specific EQ band on a strip, allowing users to choose from different EQ types such as low cut (lcut), low shelf (lshv), parametric (peq), variable Q (veq), high shelf (hshv), or high cut (hcut).
type StripEqBandTypeCmd struct {
Type *string `arg:"" help:"The type to set for the EQ band." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"`
}
// Run executes the StripEqBandTypeCmd command, either retrieving the current type of the specified EQ band on the strip or setting it based on the provided argument.
func (cmd *StripEqBandTypeCmd) Run(
ctx *context,
strip *StripCmdGroup,
stripEq *StripEqCmdGroup,
) error {
if cmd.Type == nil {
resp, err := ctx.Client.Strip.Eq.Type(strip.Index.Index, *stripEq.Band.Band)
if err != nil {
return fmt.Errorf("failed to get EQ band type: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d type: %s\n",
strip.Index.Index,
*stripEq.Band.Band,
resp,
)
return nil
}
if err := ctx.Client.Strip.Eq.SetType(
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Type,
); err != nil {
return fmt.Errorf("failed to set EQ band type: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d EQ band %d type set to: %s\n",
strip.Index.Index,
*stripEq.Band.Band,
*cmd.Type,
)
return nil
}
// StripCompCmdGroup defines the command group for controlling the compressor settings of a strip, including commands for getting and setting the compressor on/off state, mode, threshold, ratio, mix, makeup gain, attack time, hold time, and release time.
type StripCompCmdGroup struct {
On StripCompOnCmd `help:"Get or set the compressor on/off state of the strip." cmd:""`
Mode StripCompModeCmd `help:"Get or set the compressor mode of the strip." cmd:""`
Threshold StripCompThresholdCmd `help:"Get or set the compressor threshold of the strip." cmd:""`
Ratio StripCompRatioCmd `help:"Get or set the compressor ratio of the strip." cmd:""`
Mix StripCompMixCmd `help:"Get or set the compressor mix of the strip." cmd:""`
Makeup StripCompMakeupCmd `help:"Get or set the compressor makeup gain of the strip." cmd:""`
Attack StripCompAttackCmd `help:"Get or set the compressor attack time of the strip." cmd:""`
Hold StripCompHoldCmd `help:"Get or set the compressor hold time of the strip." cmd:""`
Release StripCompReleaseCmd `help:"Get or set the compressor release time of the strip." cmd:""`
}
// StripCompOnCmd defines the command for getting or setting the compressor on/off state of a strip, allowing users to enable or disable the compressor effect on the strip.
type StripCompOnCmd struct {
Enable *string `arg:"" help:"Whether to enable or disable the compressor." optional:"" enum:"true,false"`
}
// Run executes the StripCompOnCmd command, either retrieving the current compressor on/off state of the strip or setting it based on the provided argument.
func (cmd *StripCompOnCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Enable == nil {
resp, err := ctx.Client.Strip.Comp.On(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor state: %t\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetOn(strip.Index.Index, *cmd.Enable == trueStr); err != nil {
return fmt.Errorf("failed to set compressor state: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor state set to: %s\n", strip.Index.Index, *cmd.Enable)
return nil
}
// StripCompModeCmd defines the command for getting or setting the compressor mode of a strip, allowing users to choose from different compressor modes such as comp (standard compression) or exp (expander).
type StripCompModeCmd struct {
Mode *string `arg:"" help:"The compressor mode to set." optional:"" enum:"comp,exp"`
}
// Run executes the StripCompModeCmd command, either retrieving the current compressor mode of the strip or setting it based on the provided argument.
func (cmd *StripCompModeCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mode == nil {
resp, err := ctx.Client.Strip.Comp.Mode(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mode: %s\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMode(strip.Index.Index, *cmd.Mode); err != nil {
return fmt.Errorf("failed to set compressor mode: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mode set to: %s\n", strip.Index.Index, *cmd.Mode)
return nil
}
// StripCompThresholdCmd defines the command for getting or setting the compressor threshold of a strip, allowing users to specify the threshold level at which the compressor will start to reduce the signal level.
type StripCompThresholdCmd struct {
Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB)." optional:""`
}
// Run executes the StripCompThresholdCmd command, either retrieving the current compressor threshold of the strip or setting it based on the provided argument.
func (cmd *StripCompThresholdCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Threshold == nil {
resp, err := ctx.Client.Strip.Comp.Threshold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor threshold: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor threshold: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetThreshold(strip.Index.Index, *cmd.Threshold); err != nil {
return fmt.Errorf("failed to set compressor threshold: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor threshold set to: %.2f\n",
strip.Index.Index,
*cmd.Threshold,
)
return nil
}
// StripCompRatioCmd defines the command for getting or setting the compressor ratio of a strip, allowing users to specify the amount of gain reduction applied by the compressor once the signal exceeds the threshold.
type StripCompRatioCmd struct {
Ratio *float64 `arg:"" help:"The compressor ratio to set." optional:""`
}
// Run executes the StripCompRatioCmd command, either retrieving the current compressor ratio of the strip or setting it based on the provided argument.
func (cmd *StripCompRatioCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Ratio == nil {
resp, err := ctx.Client.Strip.Comp.Ratio(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor ratio: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetRatio(strip.Index.Index, *cmd.Ratio); err != nil {
return fmt.Errorf("failed to set compressor ratio: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor ratio set to: %.2f\n", strip.Index.Index, *cmd.Ratio)
return nil
}
// StripCompMixCmd defines the command for getting or setting the compressor mix of a strip, allowing users to specify the blend between the dry (unprocessed) signal and the wet (compressed) signal, typically expressed as a percentage.
type StripCompMixCmd struct {
Mix *float64 `arg:"" help:"The compressor mix to set (0-100%)." optional:""`
}
// Run executes the StripCompMixCmd command, either retrieving the current compressor mix of the strip or setting it based on the provided argument.
func (cmd *StripCompMixCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Mix == nil {
resp, err := ctx.Client.Strip.Comp.Mix(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor mix: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mix: %.2f%%\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMix(strip.Index.Index, *cmd.Mix); err != nil {
return fmt.Errorf("failed to set compressor mix: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor mix set to: %.2f%%\n", strip.Index.Index, *cmd.Mix)
return nil
}
// StripCompMakeupCmd defines the command for getting or setting the compressor makeup gain of a strip, allowing users to specify the amount of gain applied to the signal after compression to compensate for any reduction in level caused by the compressor.
type StripCompMakeupCmd struct {
Makeup *float64 `arg:"" help:"The compressor makeup gain to set (in dB)." optional:""`
}
// Run executes the StripCompMakeupCmd command, either retrieving the current compressor makeup gain of the strip or setting it based on the provided argument.
func (cmd *StripCompMakeupCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Makeup == nil {
resp, err := ctx.Client.Strip.Comp.Makeup(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor makeup gain: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor makeup gain: %.2f\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetMakeup(strip.Index.Index, *cmd.Makeup); err != nil {
return fmt.Errorf("failed to set compressor makeup gain: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor makeup gain set to: %.2f\n",
strip.Index.Index,
*cmd.Makeup,
)
return nil
}
// StripCompAttackCmd defines the command for getting or setting the compressor attack time of a strip, allowing users to specify the time it takes for the compressor to start reducing the signal level after the signal exceeds the threshold.
type StripCompAttackCmd struct {
Attack *float64 `arg:"" help:"The compressor attack time to set (in ms)." optional:""`
}
// Run executes the StripCompAttackCmd command, either retrieving the current compressor attack time of the strip or setting it based on the provided argument.
func (cmd *StripCompAttackCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Attack == nil {
resp, err := ctx.Client.Strip.Comp.Attack(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor attack time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor attack time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetAttack(strip.Index.Index, *cmd.Attack); err != nil {
return fmt.Errorf("failed to set compressor attack time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor attack time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Attack,
)
return nil
}
// StripCompHoldCmd defines the command for getting or setting the compressor hold time of a strip, allowing users to specify the time that the compressor continues to reduce the signal level after the signal falls below the threshold before it starts to release.
type StripCompHoldCmd struct {
Hold *float64 `arg:"" help:"The compressor hold time to set (in ms)." optional:""`
}
// Run executes the StripCompHoldCmd command, either retrieving the current compressor hold time of the strip or setting it based on the provided argument.
func (cmd *StripCompHoldCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Hold == nil {
resp, err := ctx.Client.Strip.Comp.Hold(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor hold time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor hold time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetHold(strip.Index.Index, *cmd.Hold); err != nil {
return fmt.Errorf("failed to set compressor hold time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor hold time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Hold,
)
return nil
}
// StripCompReleaseCmd defines the command for getting or setting the compressor release time of a strip, allowing users to specify the time it takes for the compressor to stop reducing the signal level after the signal falls below the threshold and the hold time has elapsed.
type StripCompReleaseCmd struct {
Release *float64 `arg:"" help:"The compressor release time to set (in ms)." optional:""`
}
// Run executes the StripCompReleaseCmd command, either retrieving the current compressor release time of the strip or setting it based on the provided argument.
func (cmd *StripCompReleaseCmd) Run(ctx *context, strip *StripCmdGroup) error {
if cmd.Release == nil {
resp, err := ctx.Client.Strip.Comp.Release(strip.Index.Index)
if err != nil {
return fmt.Errorf("failed to get compressor release time: %w", err)
}
fmt.Fprintf(ctx.Out, "Strip %d compressor release time: %.2f ms\n", strip.Index.Index, resp)
return nil
}
if err := ctx.Client.Strip.Comp.SetRelease(strip.Index.Index, *cmd.Release); err != nil {
return fmt.Errorf("failed to set compressor release time: %w", err)
}
fmt.Fprintf(
ctx.Out,
"Strip %d compressor release time set to: %.2f ms\n",
strip.Index.Index,
*cmd.Release,
)
return nil
}

39
go.mod
View File

@ -1,43 +1,34 @@
module github.com/onyx-and-iris/xair-cli module github.com/onyx-and-iris/xair-cli
go 1.24.2 go 1.25.0
require ( require (
github.com/alecthomas/kong v1.14.0
github.com/charmbracelet/log v0.4.2 github.com/charmbracelet/log v0.4.2
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5
github.com/spf13/cobra v1.10.2 github.com/jotaen/kong-completion v0.0.12
github.com/spf13/viper v1.21.0
) )
require ( require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect github.com/charmbracelet/colorprofile v0.4.2 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.11.4 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.9.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/posener/complete v1.2.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/sys v0.41.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
) )

97
go.sum
View File

@ -1,91 +1,74 @@
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4= github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ= github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY= github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/jotaen/kong-completion v0.0.12 h1:a9jmSaWgkdAUMQT583UxLIJrO9tfdSmYqcIxrBByjPc=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jotaen/kong-completion v0.0.12/go.mod h1:dyIG20e3qq128SUBtF8jzI7YtkfzjWMlgbqkAJd6xHQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,20 +1,28 @@
package xair package xair
var xairAddressMap = map[string]string{ var xairAddressMap = map[string]string{
"main": "/lr",
"strip": "/ch/%02d", "strip": "/ch/%02d",
"bus": "/bus/%01d", "bus": "/bus/%d",
"headamp": "/headamp/%02d", "headamp": "/headamp/%02d",
"snapshot": "/-snap",
"dca": "/dca/%d",
} }
var x32AddressMap = map[string]string{ var x32AddressMap = map[string]string{
"main": "/main/st",
"mainmono": "/main/m",
"matrix": "/mtx/%02d",
"strip": "/ch/%02d", "strip": "/ch/%02d",
"bus": "/bus/%02d", "bus": "/bus/%02d",
"headamp": "/headamp/%02d", "headamp": "/headamp/%03d",
"snapshot": "/-snap",
"dca": "/dca/%d",
} }
func addressMapForMixerKind(kind MixerKind) map[string]string { func addressMapFromMixerKind(kind mixerKind) map[string]string {
switch kind { switch kind {
case KindX32: case kindX32:
return x32AddressMap return x32AddressMap
default: default:
return xairAddressMap return xairAddressMap

View File

@ -2,23 +2,25 @@ package xair
import "fmt" import "fmt"
// Bus represents the bus parameters of the mixer.
type Bus struct { type Bus struct {
client *client
baseAddress string baseAddress string
client *Client
Eq *Eq Eq *Eq
Comp *Comp Comp *Comp
} }
func NewBus(c *Client) *Bus { // newBus creates a new Bus instance.
func newBus(c *client) *Bus {
return &Bus{ return &Bus{
baseAddress: c.addressMap["bus"],
client: c, client: c,
Eq: newEqForBus(c), baseAddress: c.addressMap["bus"],
Comp: newCompForBus(c), Eq: newEq(c, c.addressMap["bus"]),
Comp: newComp(c, c.addressMap["bus"]),
} }
} }
// Mute requests the current mute status for a bus // Mute requests the current mute status for a bus.
func (b *Bus) Mute(bus int) (bool, error) { func (b *Bus) Mute(bus int) (bool, error) {
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on" address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on"
err := b.client.SendMessage(address) err := b.client.SendMessage(address)
@ -26,15 +28,18 @@ func (b *Bus) Mute(bus int) (bool, error) {
return false, err return false, err
} }
resp := <-b.client.respChan msg, err := b.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for bus mute value") return false, fmt.Errorf("unexpected argument type for bus mute value")
} }
return val == 0, nil return val == 0, nil
} }
// SetMute sets the mute status for a specific bus (1-based indexing) // SetMute sets the mute status for a specific bus (1-based indexing).
func (b *Bus) SetMute(bus int, muted bool) error { func (b *Bus) SetMute(bus int, muted bool) error {
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on" address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on"
var value int32 var value int32
@ -44,7 +49,7 @@ func (b *Bus) SetMute(bus int, muted bool) error {
return b.client.SendMessage(address, value) return b.client.SendMessage(address, value)
} }
// Fader requests the current fader level for a bus // Fader requests the current fader level for a bus.
func (b *Bus) Fader(bus int) (float64, error) { func (b *Bus) Fader(bus int) (float64, error) {
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader" address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader"
err := b.client.SendMessage(address) err := b.client.SendMessage(address)
@ -52,8 +57,11 @@ func (b *Bus) Fader(bus int) (float64, error) {
return 0, err return 0, err
} }
resp := <-b.client.respChan msg, err := b.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for bus fader value") return 0, fmt.Errorf("unexpected argument type for bus fader value")
} }
@ -61,29 +69,32 @@ func (b *Bus) Fader(bus int) (float64, error) {
return mustDbFrom(float64(val)), nil return mustDbFrom(float64(val)), nil
} }
// SetFader sets the fader level for a specific bus (1-based indexing) // SetFader sets the fader level for a specific bus (1-based indexing).
func (b *Bus) SetFader(bus int, level float64) error { func (b *Bus) SetFader(bus int, level float64) error {
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader" address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader"
return b.client.SendMessage(address, float32(mustDbInto(level))) return b.client.SendMessage(address, float32(mustDbInto(level)))
} }
// Name requests the name for a specific bus // Name requests the name for a specific bus.
func (b *Bus) Name(bus int) (string, error) { func (b *Bus) Name(bus int) (string, error) {
address := fmt.Sprintf(b.baseAddress, bus) + "/config/name" address := fmt.Sprintf(b.baseAddress, bus) + "/config/name"
err := b.client.SendMessage(address) err := b.client.SendMessage(address)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to send bus name request: %v", err) return "", fmt.Errorf("failed to send bus name request: %w", err)
} }
resp := <-b.client.respChan msg, err := b.client.ReceiveMessage()
val, ok := resp.Arguments[0].(string) if err != nil {
return "", err
}
val, ok := msg.Arguments[0].(string)
if !ok { if !ok {
return "", fmt.Errorf("unexpected argument type for bus name value") return "", fmt.Errorf("unexpected argument type for bus name value")
} }
return val, nil return val, nil
} }
// SetName sets the name for a specific bus // SetName sets the name for a specific bus.
func (b *Bus) SetName(bus int, name string) error { func (b *Bus) SetName(bus int, name string) error {
address := fmt.Sprintf(b.baseAddress, bus) + "/config/name" address := fmt.Sprintf(b.baseAddress, bus) + "/config/name"
return b.client.SendMessage(address, name) return b.client.SendMessage(address, name)

View File

@ -1,113 +1,158 @@
// Package xair provides a client for controlling XAir and X32 mixers using OSC messages.
package xair package xair
import ( import (
"fmt" "fmt"
"net" "time"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/hypebeast/go-osc/osc" "github.com/hypebeast/go-osc/osc"
) )
type parser interface { // XAirClient is a client for controlling XAir mixers.
Parse(data []byte) (*osc.Message, error) type XAirClient struct { // nolint: revive
} client
type Client struct {
engine
Main *Main Main *Main
Strip *Strip Strip *Strip
Bus *Bus Bus *Bus
HeadAmp *HeadAmp HeadAmp *HeadAmp
Snapshot *Snapshot
DCA *DCA
} }
// NewClient creates a new XAirClient instance // NewXAirClient creates a new XAirClient instance with optional engine configuration.
func NewClient(mixerIP string, mixerPort int, opts ...Option) (*Client, error) { func NewXAirClient(mixerIP string, mixerPort int, opts ...EngineOption) (*XAirClient, error) {
localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", 0)) e, err := newEngine(mixerIP, mixerPort, kindXAir, opts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve local address: %v", err) return nil, err
} }
conn, err := net.ListenUDP("udp", localAddr) c := &XAirClient{
if err != nil { client: client{e, InfoResponse{}},
return nil, fmt.Errorf("failed to create UDP connection: %v", err)
} }
c.Main = newMainStereo(&c.client)
mixerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", mixerIP, mixerPort)) c.Strip = newStrip(&c.client)
if err != nil { c.Bus = newBus(&c.client)
conn.Close() c.HeadAmp = newHeadAmp(&c.client)
return nil, fmt.Errorf("failed to resolve mixer address: %v", err) c.Snapshot = newSnapshot(&c.client)
} c.DCA = newDCA(&c.client)
log.Debugf("Local UDP connection: %s ", conn.LocalAddr().String())
e := &engine{
Kind: KindXAir,
conn: conn,
mixerAddr: mixerAddr,
parser: newParser(),
addressMap: addressMapForMixerKind(KindXAir),
done: make(chan bool),
respChan: make(chan *osc.Message, 100),
}
for _, opt := range opts {
opt(e)
}
c := &Client{
engine: *e,
}
c.Main = newMain(c)
c.Strip = NewStrip(c)
c.Bus = NewBus(c)
c.HeadAmp = NewHeadAmp(c)
return c, nil return c, nil
} }
// Start begins listening for messages in a goroutine // X32Client is a client for controlling X32 mixers.
func (c *Client) StartListening() { type X32Client struct { // nolint: revive
go c.engine.receiveLoop() client
Main *Main
MainMono *Main
Matrix *Matrix
Strip *Strip
Bus *Bus
HeadAmp *HeadAmp
Snapshot *Snapshot
DCA *DCA
}
// NewX32Client creates a new X32Client instance with optional engine configuration.
func NewX32Client(mixerIP string, mixerPort int, opts ...EngineOption) (*X32Client, error) {
e, err := newEngine(mixerIP, mixerPort, kindX32, opts...)
if err != nil {
return nil, err
}
c := &X32Client{
client: client{e, InfoResponse{}},
}
c.Main = newMainStereo(&c.client)
c.MainMono = newMainMono(&c.client)
c.Matrix = newMatrix(&c.client)
c.Strip = newStrip(&c.client)
c.Bus = newBus(&c.client)
c.HeadAmp = newHeadAmp(&c.client)
c.Snapshot = newSnapshot(&c.client)
c.DCA = newDCA(&c.client)
return c, nil
}
type client struct {
*engine
Info InfoResponse
}
// StartListening begins listening for messages in a goroutine.
func (c *client) StartListening() {
go c.receiveLoop()
log.Debugf("Started listening on %s...", c.engine.conn.LocalAddr().String()) log.Debugf("Started listening on %s...", c.engine.conn.LocalAddr().String())
} }
// Stop stops the client and closes the connection // Close stops the client and closes the connection.
func (c *Client) Stop() { func (c *client) Close() error {
close(c.engine.done) close(c.done)
if c.engine.conn != nil { if c.conn != nil {
c.engine.conn.Close() if err := c.conn.Close(); err != nil {
return fmt.Errorf("failed to close connection: %w", err)
}
}
return nil
}
// SendMessage sends an OSC message to the mixer using the unified connection.
func (c *client) SendMessage(address string, args ...any) error {
return c.sendToAddress(c.mixerAddr, address, args...)
}
// ReceiveMessage receives an OSC message from the mixer.
func (c *client) ReceiveMessage() (*osc.Message, error) {
t := time.Tick(c.timeout)
select {
case <-t:
return nil, fmt.Errorf("timeout waiting for response")
case msg := <-c.respChan:
if msg == nil {
return nil, fmt.Errorf("no message received")
}
return msg, nil
} }
} }
// SendMessage sends an OSC message to the mixer using the unified connection // RequestInfo requests mixer information.
func (c *Client) SendMessage(address string, args ...any) error { func (c *client) RequestInfo() (InfoResponse, error) {
return c.engine.sendToAddress(c.mixerAddr, address, args...) var info InfoResponse
}
// RequestInfo requests mixer information
func (c *Client) RequestInfo() (error, InfoResponse) {
err := c.SendMessage("/xinfo") err := c.SendMessage("/xinfo")
if err != nil { if err != nil {
return err, InfoResponse{} return info, err
} }
val := <-c.respChan msg, err := c.ReceiveMessage()
var info InfoResponse if err != nil {
if len(val.Arguments) >= 3 { return info, err
info.Host = val.Arguments[0].(string)
info.Name = val.Arguments[1].(string)
info.Model = val.Arguments[2].(string)
} }
return nil, info if len(msg.Arguments) == 4 {
if host, ok := msg.Arguments[0].(string); ok {
info.Host = host
}
if name, ok := msg.Arguments[1].(string); ok {
info.Name = name
}
if model, ok := msg.Arguments[2].(string); ok {
info.Model = model
}
if firmware, ok := msg.Arguments[3].(string); ok {
info.Firmware = firmware
}
}
c.Info = info
return info, nil
} }
// KeepAlive sends keep-alive message (required for multi-client usage) // KeepAlive sends keep-alive message (required for multi-client usage).
func (c *Client) KeepAlive() error { func (c *client) KeepAlive() error {
return c.SendMessage("/xremote") return c.SendMessage("/xremote")
} }
// RequestStatus requests mixer status // RequestStatus requests mixer status.
func (c *Client) RequestStatus() error { func (c *client) RequestStatus() error {
return c.SendMessage("/status") return c.SendMessage("/status")
} }

View File

@ -2,37 +2,41 @@ package xair
import "fmt" import "fmt"
// Comp represents the compressor parameters.
type Comp struct { type Comp struct {
client *Client client *client
baseAddress string baseAddress string
AddressFunc func(fmtString string, args ...any) string
} }
// Factory function to create Comp instance for Strip // Factory function to create Comp instance with optional configuration.
func newCompForStrip(c *Client) *Comp { func newComp(c *client, baseAddress string, opts ...CompOption) *Comp {
return &Comp{ comp := &Comp{
client: c, client: c,
baseAddress: c.addressMap["strip"], baseAddress: fmt.Sprintf("%s/dyn", baseAddress),
AddressFunc: fmt.Sprintf,
} }
}
// Factory function to create Comp instance for Bus for _, opt := range opts {
func newCompForBus(c *Client) *Comp { opt(comp)
return &Comp{
client: c,
baseAddress: c.addressMap["bus"],
} }
return comp
} }
// On retrieves the on/off status of the Compressor for a specific strip or bus (1-based indexing). // On retrieves the on/off status of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) On(index int) (bool, error) { func (c *Comp) On(index int) (bool, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/on" address := c.AddressFunc(c.baseAddress, index) + "/on"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return false, err return false, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for Compressor on value") return false, fmt.Errorf("unexpected argument type for Compressor on value")
} }
@ -41,7 +45,7 @@ func (c *Comp) On(index int) (bool, error) {
// SetOn sets the on/off status of the Compressor for a specific strip or bus (1-based indexing). // SetOn sets the on/off status of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetOn(index int, on bool) error { func (c *Comp) SetOn(index int, on bool) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/on" address := c.AddressFunc(c.baseAddress, index) + "/on"
var value int32 var value int32
if on { if on {
value = 1 value = 1
@ -49,16 +53,47 @@ func (c *Comp) SetOn(index int, on bool) error {
return c.client.SendMessage(address, value) return c.client.SendMessage(address, value)
} }
// Mode retrieves the current mode of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Mode(index int) (string, error) {
address := c.AddressFunc(c.baseAddress, index) + "/mode"
err := c.client.SendMessage(address)
if err != nil {
return "", err
}
possibleModes := []string{"comp", "exp"}
msg, err := c.client.ReceiveMessage()
if err != nil {
return "", err
}
val, ok := msg.Arguments[0].(int32)
if !ok {
return "", fmt.Errorf("unexpected argument type for Compressor mode value")
}
return possibleModes[val], nil
}
// SetMode sets the mode of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetMode(index int, mode string) error {
address := c.AddressFunc(c.baseAddress, index) + "/mode"
possibleModes := []string{"comp", "exp"}
return c.client.SendMessage(address, int32(indexOf(possibleModes, mode)))
}
// Threshold retrieves the threshold value of the Compressor for a specific strip or bus (1-based indexing). // Threshold retrieves the threshold value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Threshold(index int) (float64, error) { func (c *Comp) Threshold(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/thr" address := c.AddressFunc(c.baseAddress, index) + "/thr"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor threshold value") return 0, fmt.Errorf("unexpected argument type for Compressor threshold value")
} }
@ -67,13 +102,13 @@ func (c *Comp) Threshold(index int) (float64, error) {
// SetThreshold sets the threshold value of the Compressor for a specific strip or bus (1-based indexing). // SetThreshold sets the threshold value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetThreshold(index int, threshold float64) error { func (c *Comp) SetThreshold(index int, threshold float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/thr" address := c.AddressFunc(c.baseAddress, index) + "/thr"
return c.client.SendMessage(address, float32(linSet(-60, 0, threshold))) return c.client.SendMessage(address, float32(linSet(-60, 0, threshold)))
} }
// Ratio retrieves the ratio value of the Compressor for a specific strip or bus (1-based indexing). // Ratio retrieves the ratio value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Ratio(index int) (float32, error) { func (c *Comp) Ratio(index int) (float32, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/ratio" address := c.AddressFunc(c.baseAddress, index) + "/ratio"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
@ -81,8 +116,11 @@ func (c *Comp) Ratio(index int) (float32, error) {
possibleValues := []float32{1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100} possibleValues := []float32{1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100}
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor ratio value") return 0, fmt.Errorf("unexpected argument type for Compressor ratio value")
} }
@ -92,7 +130,7 @@ func (c *Comp) Ratio(index int) (float32, error) {
// SetRatio sets the ratio value of the Compressor for a specific strip or bus (1-based indexing). // SetRatio sets the ratio value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetRatio(index int, ratio float64) error { func (c *Comp) SetRatio(index int, ratio float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/ratio" address := c.AddressFunc(c.baseAddress, index) + "/ratio"
possibleValues := []float32{1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100} possibleValues := []float32{1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100}
return c.client.SendMessage(address, int32(indexOf(possibleValues, float32(ratio)))) return c.client.SendMessage(address, int32(indexOf(possibleValues, float32(ratio))))
@ -100,14 +138,17 @@ func (c *Comp) SetRatio(index int, ratio float64) error {
// Attack retrieves the attack time of the Compressor for a specific strip or bus (1-based indexing). // Attack retrieves the attack time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Attack(index int) (float64, error) { func (c *Comp) Attack(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/attack" address := c.AddressFunc(c.baseAddress, index) + "/attack"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor attack value") return 0, fmt.Errorf("unexpected argument type for Compressor attack value")
} }
@ -116,20 +157,23 @@ func (c *Comp) Attack(index int) (float64, error) {
// SetAttack sets the attack time of the Compressor for a specific strip or bus (1-based indexing). // SetAttack sets the attack time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetAttack(index int, attack float64) error { func (c *Comp) SetAttack(index int, attack float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/attack" address := c.AddressFunc(c.baseAddress, index) + "/attack"
return c.client.SendMessage(address, float32(linSet(0, 120, attack))) return c.client.SendMessage(address, float32(linSet(0, 120, attack)))
} }
// Hold retrieves the hold time of the Compressor for a specific strip or bus (1-based indexing). // Hold retrieves the hold time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Hold(index int) (float64, error) { func (c *Comp) Hold(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/hold" address := c.AddressFunc(c.baseAddress, index) + "/hold"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor hold value") return 0, fmt.Errorf("unexpected argument type for Compressor hold value")
} }
@ -138,20 +182,23 @@ func (c *Comp) Hold(index int) (float64, error) {
// SetHold sets the hold time of the Compressor for a specific strip or bus (1-based indexing). // SetHold sets the hold time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetHold(index int, hold float64) error { func (c *Comp) SetHold(index int, hold float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/hold" address := c.AddressFunc(c.baseAddress, index) + "/hold"
return c.client.SendMessage(address, float32(logSet(0.02, 2000, hold))) return c.client.SendMessage(address, float32(logSet(0.02, 2000, hold)))
} }
// Release retrieves the release time of the Compressor for a specific strip or bus (1-based indexing). // Release retrieves the release time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Release(index int) (float64, error) { func (c *Comp) Release(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/release" address := c.AddressFunc(c.baseAddress, index) + "/release"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor release value") return 0, fmt.Errorf("unexpected argument type for Compressor release value")
} }
@ -160,42 +207,48 @@ func (c *Comp) Release(index int) (float64, error) {
// SetRelease sets the release time of the Compressor for a specific strip or bus (1-based indexing). // SetRelease sets the release time of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetRelease(index int, release float64) error { func (c *Comp) SetRelease(index int, release float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/release" address := c.AddressFunc(c.baseAddress, index) + "/release"
return c.client.SendMessage(address, float32(logSet(4, 4000, release))) return c.client.SendMessage(address, float32(logSet(4, 4000, release)))
} }
// MakeUp retrieves the make-up gain of the Compressor for a specific strip or bus (1-based indexing). // Makeup retrieves the makeup gain of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) MakeUp(index int) (float64, error) { func (c *Comp) Makeup(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" address := c.AddressFunc(c.baseAddress, index) + "/mgain"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor make-up gain value") return 0, fmt.Errorf("unexpected argument type for Compressor makeup gain value")
} }
return linGet(0, 24, float64(val)), nil return linGet(0, 24, float64(val)), nil
} }
// SetMakeUp sets the make-up gain of the Compressor for a specific strip or bus (1-based indexing). // SetMakeup sets the makeup gain of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetMakeUp(index int, makeUp float64) error { func (c *Comp) SetMakeup(index int, makeup float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" address := c.AddressFunc(c.baseAddress, index) + "/mgain"
return c.client.SendMessage(address, float32(linSet(0, 24, makeUp))) return c.client.SendMessage(address, float32(linSet(0, 24, makeup)))
} }
// Mix retrieves the mix value of the Compressor for a specific strip or bus (1-based indexing). // Mix retrieves the mix value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) Mix(index int) (float64, error) { func (c *Comp) Mix(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mix" address := c.AddressFunc(c.baseAddress, index) + "/mix"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-c.client.respChan msg, err := c.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor mix value") return 0, fmt.Errorf("unexpected argument type for Compressor mix value")
} }
@ -204,6 +257,6 @@ func (c *Comp) Mix(index int) (float64, error) {
// SetMix sets the mix value of the Compressor for a specific strip or bus (1-based indexing). // SetMix sets the mix value of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetMix(index int, mix float64) error { func (c *Comp) SetMix(index int, mix float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mix" address := c.AddressFunc(c.baseAddress, index) + "/mix"
return c.client.SendMessage(address, float32(linSet(0, 100, mix))) return c.client.SendMessage(address, float32(linSet(0, 100, mix)))
} }

96
internal/xair/dca.go Normal file
View File

@ -0,0 +1,96 @@
package xair
import "fmt"
// DCA represents the DCA group parameters of the mixer.
type DCA struct {
client *client
baseAddress string
}
// newDCA creates a new DCA instance.
func newDCA(c *client) *DCA {
return &DCA{
client: c,
baseAddress: c.addressMap["dca"],
}
}
// Mute requests the current mute status for a DCA group.
func (d *DCA) Mute(group int) (bool, error) {
address := fmt.Sprintf(d.baseAddress, group) + "/on"
err := d.client.SendMessage(address)
if err != nil {
return false, err
}
msg, err := d.client.ReceiveMessage()
if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok {
return false, fmt.Errorf("unexpected argument type for DCA mute value")
}
return val == 0, nil
}
// SetMute sets the mute status for a specific DCA group (1-based indexing).
func (d *DCA) SetMute(group int, muted bool) error {
address := fmt.Sprintf(d.baseAddress, group) + "/on"
var value int32
if !muted {
value = 1
}
return d.client.SendMessage(address, value)
}
// Name requests the current name for a DCA group.
func (d *DCA) Name(group int) (string, error) {
address := fmt.Sprintf(d.baseAddress, group) + "/config/name"
err := d.client.SendMessage(address)
if err != nil {
return "", err
}
msg, err := d.client.ReceiveMessage()
if err != nil {
return "", err
}
name, ok := msg.Arguments[0].(string)
if !ok {
return "", fmt.Errorf("unexpected argument type for DCA name value")
}
return name, nil
}
// SetName sets the name for a specific DCA group (1-based indexing).
func (d *DCA) SetName(group int, name string) error {
address := fmt.Sprintf(d.baseAddress, group) + "/config/name"
return d.client.SendMessage(address, name)
}
// Colour requests the current colour for a DCA group.
func (d *DCA) Colour(group int) (int32, error) {
address := fmt.Sprintf(d.baseAddress, group) + "/config/colour"
err := d.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := d.client.ReceiveMessage()
if err != nil {
return 0, err
}
colour, ok := msg.Arguments[0].(int32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for DCA colour value")
}
return colour, nil
}
// SetColor sets the colour for a specific DCA group (1-based indexing).
func (d *DCA) SetColor(group int, colour int32) error {
address := fmt.Sprintf(d.baseAddress, group) + "/config/colour"
return d.client.SendMessage(address, colour)
}

View File

@ -1,6 +1,7 @@
package xair package xair
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"time" "time"
@ -9,8 +10,13 @@ import (
"github.com/hypebeast/go-osc/osc" "github.com/hypebeast/go-osc/osc"
) )
type parser interface {
Parse(data []byte) (*osc.Message, error)
}
type engine struct { type engine struct {
Kind MixerKind Kind mixerKind
timeout time.Duration
conn *net.UDPConn conn *net.UDPConn
mixerAddr *net.UDPAddr mixerAddr *net.UDPAddr
@ -21,7 +27,50 @@ type engine struct {
respChan chan *osc.Message respChan chan *osc.Message
} }
// receiveLoop handles incoming OSC messages func newEngine(
mixerIP string,
mixerPort int,
kind mixerKind,
opts ...EngineOption,
) (*engine, error) {
localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", 0))
if err != nil {
return nil, fmt.Errorf("failed to resolve local address: %w", err)
}
conn, err := net.ListenUDP("udp", localAddr)
if err != nil {
return nil, fmt.Errorf("failed to create UDP connection: %w", err)
}
mixerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", mixerIP, mixerPort))
if err != nil {
if err := conn.Close(); err != nil {
log.Errorf("failed to close UDP connection: %v", err)
}
return nil, fmt.Errorf("failed to resolve mixer address: %w", err)
}
log.Debugf("Local UDP connection: %s ", conn.LocalAddr().String())
e := &engine{
timeout: 100 * time.Millisecond,
conn: conn,
mixerAddr: mixerAddr,
parser: newParser(),
addressMap: addressMapFromMixerKind(kind),
done: make(chan bool),
respChan: make(chan *osc.Message, 100),
}
for _, opt := range opts {
opt(e)
}
return e, nil
}
// receiveLoop handles incoming OSC messages.
func (e *engine) receiveLoop() { func (e *engine) receiveLoop() {
buffer := make([]byte, 4096) buffer := make([]byte, 4096)
@ -30,11 +79,15 @@ func (e *engine) receiveLoop() {
case <-e.done: case <-e.done:
return return
default: default:
// Set read timeout to avoid blocking forever // Set a short read deadline to prevent blocking indefinitely
e.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) if err := e.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
log.Errorf("Failed to set read deadline: %v", err)
continue
}
n, _, err := e.conn.ReadFromUDP(buffer) n, _, err := e.conn.ReadFromUDP(buffer)
if err != nil { if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() { var netErr net.Error
if errors.As(err, &netErr) {
// Timeout is expected, continue loop // Timeout is expected, continue loop
continue continue
} }
@ -58,7 +111,7 @@ func (e *engine) receiveLoop() {
} }
} }
// parseOSCMessage parses raw bytes into an OSC message with improved error handling // parseOSCMessage parses raw bytes into an OSC message with improved error handling.
func (e *engine) parseOSCMessage(data []byte) (*osc.Message, error) { func (e *engine) parseOSCMessage(data []byte) (*osc.Message, error) {
msg, err := e.parser.Parse(data) msg, err := e.parser.Parse(data)
if err != nil { if err != nil {
@ -68,7 +121,7 @@ func (e *engine) parseOSCMessage(data []byte) (*osc.Message, error) {
return msg, nil return msg, nil
} }
// sendToAddress sends an OSC message to a specific address (enables replying to different ports) // sendToAddress sends an OSC message to a specific address (enables replying to different ports).
func (e *engine) sendToAddress(addr *net.UDPAddr, oscAddress string, args ...any) error { func (e *engine) sendToAddress(addr *net.UDPAddr, oscAddress string, args ...any) error {
msg := osc.NewMessage(oscAddress) msg := osc.NewMessage(oscAddress)
for _, arg := range args { for _, arg := range args {
@ -89,7 +142,7 @@ func (e *engine) sendToAddress(addr *net.UDPAddr, oscAddress string, args ...any
data, err := msg.MarshalBinary() data, err := msg.MarshalBinary()
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal message: %v", err) return fmt.Errorf("failed to marshal message: %w", err)
} }
_, err = e.conn.WriteToUDP(data, addr) _, err = e.conn.WriteToUDP(data, addr)

View File

@ -1,38 +1,44 @@
package xair package xair
import "fmt" import (
"fmt"
)
// Eq represents the EQ parameters.
type Eq struct { type Eq struct {
client *Client client *client
baseAddress string baseAddress string
AddressFunc func(fmtString string, args ...any) string
} }
// Factory function to create Eq instance for Strip // Factory function to create Eq instance with optional configuration.
func newEqForStrip(c *Client) *Eq { func newEq(c *client, baseAddress string, opts ...EqOption) *Eq {
return &Eq{ eq := &Eq{
client: c, client: c,
baseAddress: c.addressMap["strip"], baseAddress: fmt.Sprintf("%s/eq", baseAddress),
AddressFunc: fmt.Sprintf,
} }
}
// Factory function to create Eq instance for Bus for _, opt := range opts {
func newEqForBus(c *Client) *Eq { opt(eq)
return &Eq{
client: c,
baseAddress: c.addressMap["bus"],
} }
return eq
} }
// On retrieves the on/off status of the EQ for a specific strip or bus (1-based indexing). // On retrieves the on/off status of the EQ for a specific strip or bus (1-based indexing).
func (e *Eq) On(index int) (bool, error) { func (e *Eq) On(index int) (bool, error) {
address := fmt.Sprintf(e.baseAddress, index) + "/eq/on" address := e.AddressFunc(e.baseAddress, index) + "/on"
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return false, err return false, err
} }
resp := <-e.client.respChan msg, err := e.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for EQ on value") return false, fmt.Errorf("unexpected argument type for EQ on value")
} }
@ -41,7 +47,7 @@ func (e *Eq) On(index int) (bool, error) {
// SetOn sets the on/off status of the EQ for a specific strip or bus (1-based indexing). // SetOn sets the on/off status of the EQ for a specific strip or bus (1-based indexing).
func (e *Eq) SetOn(index int, on bool) error { func (e *Eq) SetOn(index int, on bool) error {
address := fmt.Sprintf(e.baseAddress, index) + "/eq/on" address := e.AddressFunc(e.baseAddress, index) + "/on"
var value int32 var value int32
if on { if on {
value = 1 value = 1
@ -49,36 +55,47 @@ func (e *Eq) SetOn(index int, on bool) error {
return e.client.SendMessage(address, value) return e.client.SendMessage(address, value)
} }
func (e *Eq) Mode(index int) (int, error) { // Mode retrieves the EQ mode for a specific strip or bus (1-based indexing).
address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" func (e *Eq) Mode(index int) (string, error) {
address := e.AddressFunc(e.baseAddress, index) + "/mode"
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return "", err
} }
resp := <-e.client.respChan possibleModes := []string{"peq", "geq", "teq"}
val, ok := resp.Arguments[0].(int32)
if !ok { msg, err := e.client.ReceiveMessage()
return 0, fmt.Errorf("unexpected argument type for EQ mode value") if err != nil {
return "", err
} }
return int(val), nil val, ok := msg.Arguments[0].(int32)
if !ok {
return "", fmt.Errorf("unexpected argument type for EQ mode value")
}
return possibleModes[val], nil
} }
func (e *Eq) SetMode(index int, mode int) error { // SetMode sets the EQ mode for a specific strip or bus (1-based indexing).
address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" func (e *Eq) SetMode(index int, mode string) error {
return e.client.SendMessage(address, int32(mode)) address := e.AddressFunc(e.baseAddress, index) + "/mode"
possibleModes := []string{"peq", "geq", "teq"}
return e.client.SendMessage(address, int32(indexOf(possibleModes, mode)))
} }
// Gain retrieves the gain for a specific EQ band on a strip or bus (1-based indexing). // Gain retrieves the gain for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) Gain(index int, band int) (float64, error) { func (e *Eq) Gain(index, band int) (float64, error) {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/g", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/g", band)
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-e.client.respChan msg, err := e.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for EQ gain value") return 0, fmt.Errorf("unexpected argument type for EQ gain value")
} }
@ -86,21 +103,24 @@ func (e *Eq) Gain(index int, band int) (float64, error) {
} }
// SetGain sets the gain for a specific EQ band on a strip or bus (1-based indexing). // SetGain sets the gain for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) SetGain(index int, band int, gain float64) error { func (e *Eq) SetGain(index, band int, gain float64) error {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/g", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/g", band)
return e.client.SendMessage(address, float32(linSet(-15, 15, gain))) return e.client.SendMessage(address, float32(linSet(-15, 15, gain)))
} }
// Frequency retrieves the frequency for a specific EQ band on a strip or bus (1-based indexing). // Frequency retrieves the frequency for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) Frequency(index int, band int) (float64, error) { func (e *Eq) Frequency(index, band int) (float64, error) {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/f", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/f", band)
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-e.client.respChan msg, err := e.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for EQ frequency value") return 0, fmt.Errorf("unexpected argument type for EQ frequency value")
} }
@ -108,21 +128,24 @@ func (e *Eq) Frequency(index int, band int) (float64, error) {
} }
// SetFrequency sets the frequency for a specific EQ band on a strip or bus (1-based indexing). // SetFrequency sets the frequency for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) SetFrequency(index int, band int, frequency float64) error { func (e *Eq) SetFrequency(index, band int, frequency float64) error {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/f", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/f", band)
return e.client.SendMessage(address, float32(logSet(20, 20000, frequency))) return e.client.SendMessage(address, float32(logSet(20, 20000, frequency)))
} }
// Q retrieves the Q factor for a specific EQ band on a strip or bus (1-based indexing). // Q retrieves the Q factor for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) Q(index int, band int) (float64, error) { func (e *Eq) Q(index, band int) (float64, error) {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/q", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/q", band)
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-e.client.respChan msg, err := e.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for EQ Q value") return 0, fmt.Errorf("unexpected argument type for EQ Q value")
} }
@ -130,29 +153,35 @@ func (e *Eq) Q(index int, band int) (float64, error) {
} }
// SetQ sets the Q factor for a specific EQ band on a strip or bus (1-based indexing). // SetQ sets the Q factor for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) SetQ(index int, band int, q float64) error { func (e *Eq) SetQ(index, band int, q float64) error {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/q", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/q", band)
return e.client.SendMessage(address, float32(1.0-logSet(0.3, 10, q))) return e.client.SendMessage(address, float32(1.0-logSet(0.3, 10, q)))
} }
// Type retrieves the type for a specific EQ band on a strip or bus (1-based indexing). // Type retrieves the type for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) Type(index int, band int) (int, error) { func (e *Eq) Type(index, band int) (string, error) {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/type", band)
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return "", err
} }
resp := <-e.client.respChan possibleTypes := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"}
val, ok := resp.Arguments[0].(int32)
if !ok { msg, err := e.client.ReceiveMessage()
return 0, fmt.Errorf("unexpected argument type for EQ type value") if err != nil {
return "", err
} }
return int(val), nil val, ok := msg.Arguments[0].(int32)
if !ok {
return "", fmt.Errorf("unexpected argument type for EQ type value")
}
return possibleTypes[val], nil
} }
// SetType sets the type for a specific EQ band on a strip or bus (1-based indexing). // SetType sets the type for a specific EQ band on a strip or bus (1-based indexing).
func (e *Eq) SetType(index int, band int, eqType int) error { func (e *Eq) SetType(index, band int, eqType string) error {
address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/%d/type", band)
return e.client.SendMessage(address, int32(eqType)) possibleTypes := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"}
return e.client.SendMessage(address, int32(indexOf(possibleTypes, eqType)))
} }

View File

@ -2,25 +2,41 @@ package xair
import "fmt" import "fmt"
// Gate represents the gate parameters.
type Gate struct { type Gate struct {
client *Client client *client
baseAddress string baseAddress string
AddressFunc func(fmtString string, args ...any) string
} }
func newGate(c *Client) *Gate { // Factory function to create Gate instance with optional configuration.
return &Gate{client: c, baseAddress: c.addressMap["strip"]} func newGate(c *client, baseAddress string, opts ...GateOption) *Gate {
gate := &Gate{
client: c,
baseAddress: fmt.Sprintf("%s/gate", baseAddress),
AddressFunc: fmt.Sprintf,
}
for _, opt := range opts {
opt(gate)
}
return gate
} }
// On retrieves the on/off status of the Gate for a specific strip (1-based indexing). // On retrieves the on/off status of the Gate for a specific strip (1-based indexing).
func (g *Gate) On(index int) (bool, error) { func (g *Gate) On(index int) (bool, error) {
address := fmt.Sprintf(g.baseAddress, index) + "/gate/on" address := g.AddressFunc(g.baseAddress, index) + "/on"
err := g.client.SendMessage(address) err := g.client.SendMessage(address)
if err != nil { if err != nil {
return false, err return false, err
} }
resp := <-g.client.respChan msg, err := g.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for Gate on value") return false, fmt.Errorf("unexpected argument type for Gate on value")
} }
@ -29,10 +45,164 @@ func (g *Gate) On(index int) (bool, error) {
// SetOn sets the on/off status of the Gate for a specific strip (1-based indexing). // SetOn sets the on/off status of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetOn(index int, on bool) error { func (g *Gate) SetOn(index int, on bool) error {
address := fmt.Sprintf(g.baseAddress, index) + "/gate/on" address := g.AddressFunc(g.baseAddress, index) + "/on"
var value int32 var value int32
if on { if on {
value = 1 value = 1
} }
return g.client.SendMessage(address, value) return g.client.SendMessage(address, value)
} }
// Mode retrieves the current mode of the Gate for a specific strip (1-based indexing).
func (g *Gate) Mode(index int) (string, error) {
address := g.AddressFunc(g.baseAddress, index) + "/mode"
err := g.client.SendMessage(address)
if err != nil {
return "", err
}
possibleModes := []string{"exp2", "exp3", "exp4", "gate", "duck"}
msg, err := g.client.ReceiveMessage()
if err != nil {
return "", err
}
val, ok := msg.Arguments[0].(int32)
if !ok {
return "", fmt.Errorf("unexpected argument type for Gate mode value")
}
return possibleModes[val], nil
}
// SetMode sets the mode of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetMode(index int, mode string) error {
address := g.AddressFunc(g.baseAddress, index) + "/mode"
possibleModes := []string{"exp2", "exp3", "exp4", "gate", "duck"}
return g.client.SendMessage(address, int32(indexOf(possibleModes, mode)))
}
// Threshold retrieves the threshold value of the Gate for a specific strip (1-based indexing).
func (g *Gate) Threshold(index int) (float64, error) {
address := g.AddressFunc(g.baseAddress, index) + "/thr"
err := g.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := g.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for Gate threshold value")
}
return linGet(-80, 0, float64(val)), nil
}
// SetThreshold sets the threshold value of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetThreshold(index int, threshold float64) error {
address := g.AddressFunc(g.baseAddress, index) + "/thr"
return g.client.SendMessage(address, float32(linSet(-80, 0, threshold)))
}
// Range retrieves the range value of the Gate for a specific strip (1-based indexing).
func (g *Gate) Range(index int) (float64, error) {
address := g.AddressFunc(g.baseAddress, index) + "/range"
err := g.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := g.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for Gate range value")
}
return linGet(3, 60, float64(val)), nil
}
// SetRange sets the range value of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetRange(index int, rangeVal float64) error {
address := g.AddressFunc(g.baseAddress, index) + "/range"
return g.client.SendMessage(address, float32(linSet(3, 60, rangeVal)))
}
// Attack retrieves the attack time of the Gate for a specific strip (1-based indexing).
func (g *Gate) Attack(index int) (float64, error) {
address := g.AddressFunc(g.baseAddress, index) + "/attack"
err := g.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := g.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for Gate attack value")
}
return linGet(0, 120, float64(val)), nil
}
// SetAttack sets the attack time of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetAttack(index int, attack float64) error {
address := g.AddressFunc(g.baseAddress, index) + "/attack"
return g.client.SendMessage(address, float32(linSet(0, 120, attack)))
}
// Hold retrieves the hold time of the Gate for a specific strip (1-based indexing).
func (g *Gate) Hold(index int) (float64, error) {
address := g.AddressFunc(g.baseAddress, index) + "/hold"
err := g.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := g.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for Gate hold value")
}
return logGet(0.02, 2000, float64(val)), nil
}
// SetHold sets the hold time of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetHold(index int, hold float64) error {
address := g.AddressFunc(g.baseAddress, index) + "/hold"
return g.client.SendMessage(address, float32(logSet(0.02, 2000, hold)))
}
// Release retrieves the release time of the Gate for a specific strip (1-based indexing).
func (g *Gate) Release(index int) (float64, error) {
address := g.AddressFunc(g.baseAddress, index) + "/release"
err := g.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := g.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for Gate release value")
}
return logGet(5, 4000, float64(val)), nil
}
// SetRelease sets the release time of the Gate for a specific strip (1-based indexing).
func (g *Gate) SetRelease(index int, release float64) error {
address := g.AddressFunc(g.baseAddress, index) + "/release"
return g.client.SendMessage(address, float32(logSet(5, 4000, release)))
}

View File

@ -2,15 +2,17 @@ package xair
import "fmt" import "fmt"
// HeadAmp represents the headphone amplifier parameters of the mixer.
type HeadAmp struct { type HeadAmp struct {
client *client
baseAddress string baseAddress string
client *Client
} }
func NewHeadAmp(c *Client) *HeadAmp { // newHeadAmp creates a new HeadAmp instance with the provided client.
func newHeadAmp(c *client) *HeadAmp {
return &HeadAmp{ return &HeadAmp{
baseAddress: c.addressMap["headamp"],
client: c, client: c,
baseAddress: c.addressMap["headamp"],
} }
} }
@ -22,8 +24,11 @@ func (h *HeadAmp) Gain(index int) (float64, error) {
return 0, err return 0, err
} }
resp := <-h.client.respChan msg, err := h.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for headamp gain value") return 0, fmt.Errorf("unexpected argument type for headamp gain value")
} }
@ -45,8 +50,11 @@ func (h *HeadAmp) PhantomPower(index int) (bool, error) {
return false, err return false, err
} }
resp := <-h.client.respChan msg, err := h.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for phantom power value") return false, fmt.Errorf("unexpected argument type for phantom power value")
} }

View File

@ -1,8 +1,8 @@
package xair package xair
type MixerKind string type mixerKind string
const ( const (
KindXAir MixerKind = "xair" kindXAir mixerKind = "xair"
KindX32 MixerKind = "x32" kindX32 mixerKind = "x32"
) )

View File

@ -2,56 +2,92 @@ package xair
import "fmt" import "fmt"
// Main represents the main output parameters of the mixer.
type Main struct { type Main struct {
client *Client client *client
baseAddress string
Eq *Eq
Comp *Comp
} }
func newMain(c *Client) *Main { // newMainStereo creates a new Main instance for stereo main output.
func newMainStereo(c *client) *Main {
addressFunc := func(fmtString string, _ ...any) string {
return fmtString
}
return &Main{ return &Main{
client: c, client: c,
baseAddress: c.addressMap["main"],
Eq: newEq(c, c.addressMap["main"], WithEqAddressFunc(addressFunc)),
Comp: newComp(c, c.addressMap["main"], WithCompAddressFunc(addressFunc)),
} }
} }
// Fader requests the current main L/R fader level // newMainMono creates a new MainMono instance for mono main output (X32 only).
func newMainMono(c *client) *Main {
addressFunc := func(fmtString string, _ ...any) string {
return fmtString
}
return &Main{
baseAddress: c.addressMap["mainmono"],
client: c,
Eq: newEq(c, c.addressMap["mainmono"], WithEqAddressFunc(addressFunc)),
Comp: newComp(c, c.addressMap["mainmono"], WithCompAddressFunc(addressFunc)),
}
}
// Fader requests the current main L/R fader level.
func (m *Main) Fader() (float64, error) { func (m *Main) Fader() (float64, error) {
err := m.client.SendMessage("/lr/mix/fader") address := m.baseAddress + "/mix/fader"
err := m.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return 0, err
} }
resp := <-m.client.respChan msg, err := m.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for main LR fader value") return 0, fmt.Errorf("unexpected argument type for main LR fader value")
} }
return mustDbFrom(float64(val)), nil return mustDbFrom(float64(val)), nil
} }
// SetFader sets the main L/R fader level // SetFader sets the main L/R fader level.
func (m *Main) SetFader(level float64) error { func (m *Main) SetFader(level float64) error {
return m.client.SendMessage("/lr/mix/fader", float32(mustDbInto(level))) address := m.baseAddress + "/mix/fader"
return m.client.SendMessage(address, float32(mustDbInto(level)))
} }
// Mute requests the current main L/R mute status // Mute requests the current main L/R mute status.
func (m *Main) Mute() (bool, error) { func (m *Main) Mute() (bool, error) {
err := m.client.SendMessage("/lr/mix/on") address := m.baseAddress + "/mix/on"
err := m.client.SendMessage(address)
if err != nil { if err != nil {
return false, err return false, err
} }
resp := <-m.client.respChan msg, err := m.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for main LR mute value") return false, fmt.Errorf("unexpected argument type for main LR mute value")
} }
return val == 0, nil return val == 0, nil
} }
// SetMute sets the main L/R mute status // SetMute sets the main L/R mute status.
func (m *Main) SetMute(muted bool) error { func (m *Main) SetMute(muted bool) error {
address := m.baseAddress + "/mix/on"
var value int32 var value int32
if !muted { if !muted {
value = 1 value = 1
} }
return m.client.SendMessage("/lr/mix/on", value) return m.client.SendMessage(address, value)
} }

75
internal/xair/matrix.go Normal file
View File

@ -0,0 +1,75 @@
package xair
import "fmt"
// Matrix represents the matrix parameters of the mixer.
type Matrix struct {
client *client
baseAddress string
Eq *Eq
Comp *Comp
}
// newMatrix creates a new Matrix instance.
func newMatrix(c *client) *Matrix {
return &Matrix{
client: c,
baseAddress: c.addressMap["matrix"],
Eq: newEq(c, c.addressMap["matrix"]),
Comp: newComp(c, c.addressMap["matrix"]),
}
}
// Fader requests the current main L/R fader level.
func (m *Matrix) Fader(index int) (float64, error) {
address := fmt.Sprintf(m.baseAddress, index) + "/mix/fader"
err := m.client.SendMessage(address)
if err != nil {
return 0, err
}
msg, err := m.client.ReceiveMessage()
if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for matrix fader value")
}
return mustDbFrom(float64(val)), nil
}
// SetFader sets the matrix fader level.
func (m *Matrix) SetFader(index int, level float64) error {
address := fmt.Sprintf(m.baseAddress, index) + "/mix/fader"
return m.client.SendMessage(address, float32(mustDbInto(level)))
}
// Mute requests the current matrix mute status.
func (m *Matrix) Mute(index int) (bool, error) {
address := fmt.Sprintf(m.baseAddress, index) + "/mix/on"
err := m.client.SendMessage(address)
if err != nil {
return false, err
}
msg, err := m.client.ReceiveMessage()
if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok {
return false, fmt.Errorf("unexpected argument type for matrix mute value")
}
return val == 0, nil
}
// SetMute sets the matrix mute status.
func (m *Matrix) SetMute(index int, muted bool) error {
address := fmt.Sprintf(m.baseAddress, index) + "/mix/on"
var value int32
if !muted {
value = 1
}
return m.client.SendMessage(address, value)
}

View File

@ -1,7 +1,9 @@
package xair package xair
// InfoResponse represents the response from the /info OSC address, containing details about the X-Air device.
type InfoResponse struct { type InfoResponse struct {
Host string Host string
Name string Name string
Model string Model string
Firmware string
} }

View File

@ -1,10 +1,43 @@
package xair package xair
type Option func(*engine) import "time"
func WithKind(kind string) Option { // EngineOption defines a functional option for configuring the engine.
type EngineOption func(*engine)
// WithTimeout sets the timeout duration for OSC message responses.
func WithTimeout(timeout time.Duration) EngineOption {
return func(e *engine) { return func(e *engine) {
e.Kind = MixerKind(kind) e.timeout = timeout
e.addressMap = addressMapForMixerKind(e.Kind) }
}
// CompOption defines a functional option for configuring Comp parameters.
type CompOption func(*Comp)
// WithCompAddressFunc allows customization of the OSC address formatting for Comp parameters.
func WithCompAddressFunc(f func(fmtString string, args ...any) string) CompOption {
return func(c *Comp) {
c.AddressFunc = f
}
}
// EqOption defines a functional option for configuring Eq parameters.
type EqOption func(*Eq)
// WithEqAddressFunc allows customization of the OSC address formatting for Eq parameters.
func WithEqAddressFunc(f func(fmtString string, args ...any) string) EqOption {
return func(e *Eq) {
e.AddressFunc = f
}
}
// GateOption defines a functional option for configuring Gate parameters.
type GateOption func(*Gate)
// WithGateAddressFunc allows customization of the OSC address formatting for Gate parameters.
func WithGateAddressFunc(f func(fmtString string, args ...any) string) GateOption {
return func(g *Gate) {
g.AddressFunc = f
} }
} }

View File

@ -10,14 +10,13 @@ import (
"github.com/hypebeast/go-osc/osc" "github.com/hypebeast/go-osc/osc"
) )
type xairParser struct { type xairParser struct{}
}
func newParser() *xairParser { func newParser() *xairParser {
return &xairParser{} return &xairParser{}
} }
// parseOSCMessage parses raw bytes into an OSC message with improved error handling // Parse parses raw bytes into an OSC message with improved error handling.
func (p *xairParser) Parse(data []byte) (*osc.Message, error) { func (p *xairParser) Parse(data []byte) (*osc.Message, error) {
log.Debug("=== PARSING OSC MESSAGE BEGIN ===") log.Debug("=== PARSING OSC MESSAGE BEGIN ===")
defer log.Debug("=== PARSING OSC MESSAGE END ===") defer log.Debug("=== PARSING OSC MESSAGE END ===")
@ -47,7 +46,7 @@ func (p *xairParser) Parse(data []byte) (*osc.Message, error) {
return msg, nil return msg, nil
} }
// validateOSCData performs basic validation on OSC message data // validateOSCData performs basic validation on OSC message data.
func (p *xairParser) validateOSCData(data []byte) error { func (p *xairParser) validateOSCData(data []byte) error {
if len(data) < 4 { if len(data) < 4 {
return fmt.Errorf("data too short for OSC message") return fmt.Errorf("data too short for OSC message")
@ -58,7 +57,7 @@ func (p *xairParser) validateOSCData(data []byte) error {
return nil return nil
} }
// extractOSCAddress extracts the OSC address from the message data // extractOSCAddress extracts the OSC address from the message data.
func (p *xairParser) extractOSCAddress(data []byte) (address string, nextPos int, err error) { func (p *xairParser) extractOSCAddress(data []byte) (address string, nextPos int, err error) {
nullPos := bytes.IndexByte(data, 0) nullPos := bytes.IndexByte(data, 0)
if nullPos <= 0 { if nullPos <= 0 {
@ -73,8 +72,11 @@ func (p *xairParser) extractOSCAddress(data []byte) (address string, nextPos int
return address, nextPos, nil return address, nextPos, nil
} }
// extractOSCTypeTags extracts and validates OSC type tags // extractOSCTypeTags extracts and validates OSC type tags.
func (p *xairParser) extractOSCTypeTags(data []byte, start int) (typeTags string, nextPos int, err error) { func (p *xairParser) extractOSCTypeTags(
data []byte,
start int,
) (typeTags string, nextPos int, err error) { // nolint: unparam
if start >= len(data) { if start >= len(data) {
return "", start, nil // No type tags available return "", start, nil // No type tags available
} }
@ -97,8 +99,13 @@ func (p *xairParser) extractOSCTypeTags(data []byte, start int) (typeTags string
return typeTags, nextPos, nil return typeTags, nextPos, nil
} }
// parseOSCArguments parses OSC arguments based on type tags // parseOSCArguments parses OSC arguments based on type tags.
func (p *xairParser) parseOSCArguments(data []byte, argsStart int, typeTags string, msg *osc.Message) error { func (p *xairParser) parseOSCArguments(
data []byte,
argsStart int,
typeTags string,
msg *osc.Message,
) error { // nolint: unparam
argData := data[argsStart:] argData := data[argsStart:]
argNum := 0 argNum := 0
@ -138,7 +145,7 @@ func (p *xairParser) parseOSCArguments(data []byte, argsStart int, typeTags stri
return nil return nil
} }
// parseStringArgument parses a string argument from OSC data // parseStringArgument parses a string argument from OSC data.
func (p *xairParser) parseStringArgument(data []byte, msg *osc.Message, argNum int) (int, error) { func (p *xairParser) parseStringArgument(data []byte, msg *osc.Message, argNum int) (int, error) {
nullPos := bytes.IndexByte(data, 0) nullPos := bytes.IndexByte(data, 0)
if nullPos < 0 { if nullPos < 0 {
@ -153,7 +160,7 @@ func (p *xairParser) parseStringArgument(data []byte, msg *osc.Message, argNum i
return ((nullPos + 4) / 4) * 4, nil return ((nullPos + 4) / 4) * 4, nil
} }
// parseInt32Argument parses an int32 argument from OSC data // parseInt32Argument parses an int32 argument from OSC data.
func (p *xairParser) parseInt32Argument(data []byte, msg *osc.Message, argNum int) (int, error) { func (p *xairParser) parseInt32Argument(data []byte, msg *osc.Message, argNum int) (int, error) {
if len(data) < 4 { if len(data) < 4 {
return 0, fmt.Errorf("insufficient data for int32") return 0, fmt.Errorf("insufficient data for int32")
@ -166,7 +173,7 @@ func (p *xairParser) parseInt32Argument(data []byte, msg *osc.Message, argNum in
return 4, nil return 4, nil
} }
// parseFloat32Argument parses a float32 argument from OSC data // parseFloat32Argument parses a float32 argument from OSC data.
func (p *xairParser) parseFloat32Argument(data []byte, msg *osc.Message, argNum int) (int, error) { func (p *xairParser) parseFloat32Argument(data []byte, msg *osc.Message, argNum int) (int, error) {
if len(data) < 4 { if len(data) < 4 {
return 0, fmt.Errorf("insufficient data for float32") return 0, fmt.Errorf("insufficient data for float32")
@ -179,7 +186,7 @@ func (p *xairParser) parseFloat32Argument(data []byte, msg *osc.Message, argNum
return 4, nil return 4, nil
} }
// parseBlobArgument parses a blob argument from OSC data // parseBlobArgument parses a blob argument from OSC data.
func (p *xairParser) parseBlobArgument(data []byte, msg *osc.Message, argNum int) (int, error) { func (p *xairParser) parseBlobArgument(data []byte, msg *osc.Message, argNum int) (int, error) {
if len(data) < 4 { if len(data) < 4 {
return 0, fmt.Errorf("insufficient data for blob size") return 0, fmt.Errorf("insufficient data for blob size")
@ -203,7 +210,7 @@ func (p *xairParser) parseBlobArgument(data []byte, msg *osc.Message, argNum int
return ((4 + int(size) + 3) / 4) * 4, nil return ((4 + int(size) + 3) / 4) * 4, nil
} }
// skipUnknownArgument skips an unknown argument type // skipUnknownArgument skips an unknown argument type.
func (p *xairParser) skipUnknownArgument(data []byte) int { func (p *xairParser) skipUnknownArgument(data []byte) int {
// Skip unknown types by moving 4 bytes if available // Skip unknown types by moving 4 bytes if available
if len(data) >= 4 { if len(data) >= 4 {

66
internal/xair/snapshot.go Normal file
View File

@ -0,0 +1,66 @@
package xair
import "fmt"
// Snapshot represents a snapshot of the mixer's state, allowing for saving and recalling settings.
type Snapshot struct {
client *client
baseAddress string
}
// newSnapshot creates a new Snapshot instance.
func newSnapshot(c *client) *Snapshot {
return &Snapshot{
client: c,
baseAddress: c.addressMap["snapshot"],
}
}
// Name gets the name of the snapshot at the given index.
func (s *Snapshot) Name(index int) (string, error) {
address := s.baseAddress + fmt.Sprintf("/%02d/name", index)
err := s.client.SendMessage(address)
if err != nil {
return "", err
}
msg, err := s.client.ReceiveMessage()
if err != nil {
return "", err
}
name, ok := msg.Arguments[0].(string)
if !ok {
return "", fmt.Errorf("unexpected argument type for snapshot name")
}
return name, nil
}
// SetName sets the name of the snapshot at the given index.
func (s *Snapshot) SetName(index int, name string) error {
address := s.baseAddress + fmt.Sprintf("/%02d/name", index)
return s.client.SendMessage(address, name)
}
// CurrentName sets the name of the current snapshot.
func (s *Snapshot) CurrentName(name string) error {
address := s.baseAddress + "/name"
return s.client.SendMessage(address, name)
}
// CurrentLoad loads the snapshot at the given index.
func (s *Snapshot) CurrentLoad(index int) error {
address := s.baseAddress + "/load"
return s.client.SendMessage(address, int32(index))
}
// CurrentSave saves the current state to the snapshot at the given index.
func (s *Snapshot) CurrentSave(index int) error {
address := s.baseAddress + "/save"
return s.client.SendMessage(address, int32(index))
}
// CurrentDelete deletes the snapshot at the given index.
func (s *Snapshot) CurrentDelete(index int) error {
address := s.baseAddress + "/delete"
return s.client.SendMessage(address, int32(index))
}

View File

@ -2,21 +2,23 @@ package xair
import "fmt" import "fmt"
// Strip represents an input channel strip on the mixer.
type Strip struct { type Strip struct {
client *client
baseAddress string baseAddress string
client *Client
Gate *Gate Gate *Gate
Eq *Eq Eq *Eq
Comp *Comp Comp *Comp
} }
func NewStrip(c *Client) *Strip { // newStrip creates a new Strip instance.
func newStrip(c *client) *Strip {
return &Strip{ return &Strip{
baseAddress: c.addressMap["strip"],
client: c, client: c,
Gate: newGate(c), baseAddress: c.addressMap["strip"],
Eq: newEqForStrip(c), Gate: newGate(c, c.addressMap["strip"]),
Comp: newCompForStrip(c), Eq: newEq(c, c.addressMap["strip"]),
Comp: newComp(c, c.addressMap["strip"]),
} }
} }
@ -28,8 +30,11 @@ func (s *Strip) Mute(index int) (bool, error) {
return false, err return false, err
} }
resp := <-s.client.respChan msg, err := s.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return false, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return false, fmt.Errorf("unexpected argument type for strip mute value") return false, fmt.Errorf("unexpected argument type for strip mute value")
} }
@ -39,7 +44,7 @@ func (s *Strip) Mute(index int) (bool, error) {
// SetMute sets the mute status of the specified strip (1-based indexing). // SetMute sets the mute status of the specified strip (1-based indexing).
func (s *Strip) SetMute(strip int, muted bool) error { func (s *Strip) SetMute(strip int, muted bool) error {
address := fmt.Sprintf(s.baseAddress, strip) + "/mix/on" address := fmt.Sprintf(s.baseAddress, strip) + "/mix/on"
var value int32 = 0 var value int32
if !muted { if !muted {
value = 1 value = 1
} }
@ -54,8 +59,11 @@ func (s *Strip) Fader(strip int) (float64, error) {
return 0, err return 0, err
} }
resp := <-s.client.respChan msg, err := s.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for fader value") return 0, fmt.Errorf("unexpected argument type for fader value")
} }
@ -69,68 +77,77 @@ func (s *Strip) SetFader(strip int, level float64) error {
return s.client.SendMessage(address, float32(mustDbInto(level))) return s.client.SendMessage(address, float32(mustDbInto(level)))
} }
// Name requests the name for a specific strip // Name requests the name for a specific strip.
func (s *Strip) Name(strip int) (string, error) { func (s *Strip) Name(strip int) (string, error) {
address := fmt.Sprintf(s.baseAddress, strip) + "/config/name" address := fmt.Sprintf(s.baseAddress, strip) + "/config/name"
err := s.client.SendMessage(address) err := s.client.SendMessage(address)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to send strip name request: %v", err) return "", fmt.Errorf("failed to send strip name request: %w", err)
} }
resp := <-s.client.respChan msg, err := s.client.ReceiveMessage()
val, ok := resp.Arguments[0].(string) if err != nil {
return "", err
}
val, ok := msg.Arguments[0].(string)
if !ok { if !ok {
return "", fmt.Errorf("unexpected argument type for strip name value") return "", fmt.Errorf("unexpected argument type for strip name value")
} }
return val, nil return val, nil
} }
// SetName sets the name for a specific strip // SetName sets the name for a specific strip.
func (s *Strip) SetName(strip int, name string) error { func (s *Strip) SetName(strip int, name string) error {
address := fmt.Sprintf(s.baseAddress, strip) + "/config/name" address := fmt.Sprintf(s.baseAddress, strip) + "/config/name"
return s.client.SendMessage(address, name) return s.client.SendMessage(address, name)
} }
// Color requests the color for a specific strip // Colour requests the colour for a specific strip.
func (s *Strip) Color(strip int) (int32, error) { func (s *Strip) Colour(strip int) (int32, error) {
address := fmt.Sprintf(s.baseAddress, strip) + "/config/color" address := fmt.Sprintf(s.baseAddress, strip) + "/config/colour"
err := s.client.SendMessage(address) err := s.client.SendMessage(address)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to send strip color request: %v", err) return 0, fmt.Errorf("failed to send strip colour request: %w", err)
} }
resp := <-s.client.respChan msg, err := s.client.ReceiveMessage()
val, ok := resp.Arguments[0].(int32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(int32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for strip color value") return 0, fmt.Errorf("unexpected argument type for strip colour value")
} }
return val, nil return val, nil
} }
// SetColor sets the color for a specific strip (0-15) // SetColor sets the colour for a specific strip (0-15).
func (s *Strip) SetColor(strip int, color int32) error { func (s *Strip) SetColor(strip int, colour int32) error {
address := fmt.Sprintf(s.baseAddress, strip) + "/config/color" address := fmt.Sprintf(s.baseAddress, strip) + "/config/colour"
return s.client.SendMessage(address, color) return s.client.SendMessage(address, colour)
} }
// Sends requests the sends level for a mixbus. // SendLevel requests auxiliary send level for a send destination.
func (s *Strip) SendLevel(strip int, bus int) (float64, error) { func (s *Strip) SendLevel(strip, bus int) (float64, error) {
address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus) address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus)
err := s.client.SendMessage(address) err := s.client.SendMessage(address)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to send strip send level request: %v", err) return 0, fmt.Errorf("failed to send strip send level request: %w", err)
} }
resp := <-s.client.respChan msg, err := s.client.ReceiveMessage()
val, ok := resp.Arguments[0].(float32) if err != nil {
return 0, err
}
val, ok := msg.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for strip send level value") return 0, fmt.Errorf("unexpected argument type for strip send level value")
} }
return mustDbFrom(float64(val)), nil return mustDbFrom(float64(val)), nil
} }
// SetSendLevel sets the sends level for a mixbus. // SetSendLevel sets the auxiliary send level for a send destination.
func (s *Strip) SetSendLevel(strip int, bus int, level float64) error { func (s *Strip) SetSendLevel(strip, bus int, level float64) error {
address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus) address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus)
return s.client.SendMessage(address, float32(mustDbInto(level))) return s.client.SendMessage(address, float32(mustDbInto(level)))
} }

View File

@ -2,20 +2,20 @@ package xair
import "math" import "math"
func linGet(min float64, max float64, value float64) float64 { func linGet(minVal, maxVal, value float64) float64 {
return min + (max-min)*value return minVal + (maxVal-minVal)*value
} }
func linSet(min float64, max float64, value float64) float64 { func linSet(minVal, maxVal, value float64) float64 {
return (value - min) / (max - min) return (value - minVal) / (maxVal - minVal)
} }
func logGet(min float64, max float64, value float64) float64 { func logGet(minVal, maxVal, value float64) float64 {
return min * math.Exp(math.Log(max/min)*value) return minVal * math.Exp(math.Log(maxVal/minVal)*value)
} }
func logSet(min float64, max float64, value float64) float64 { func logSet(minVal, maxVal, value float64) float64 {
return math.Log(value/min) / math.Log(max/min) return math.Log(value/minVal) / math.Log(maxVal/minVal)
} }
func mustDbInto(db float64) float64 { func mustDbInto(db float64) float64 {
@ -52,7 +52,7 @@ func mustDbFrom(level float64) float64 {
} }
} }
func toFixed(num float64, precision int) float64 { func toFixed(num float64, precision int) float64 { // nolint: unparam
output := math.Pow(10, float64(precision)) output := math.Pow(10, float64(precision))
return float64(math.Round(num*output)) / output return float64(math.Round(num*output)) / output
} }

View File

@ -1,7 +0,0 @@
package main
import "github.com/onyx-and-iris/xair-cli/cmd"
func main() {
cmd.Execute()
}

197
x32-help.md Normal file
View File

@ -0,0 +1,197 @@
```console
Usage: x32-cli <command> [flags]
A CLI to control Behringer X32 mixers.
Flags:
-h, --help Show context-sensitive help.
-H, --host="mixer.local" The host of the X32 device ($X32_CLI_HOST).
-P, --port=10023 The port of the X32 device ($X32_CLI_PORT).
-T, --timeout=100ms Timeout for OSC operations ($X32_CLI_TIMEOUT).
-L, --loglevel="warn" Log level for the CLI ($X32_CLI_LOGLEVEL).
-v, --version Print x32-cli version information and quit
Commands:
completion Generate shell completion scripts.
info Print mixer information.
raw Send raw OSC messages to the mixer.
Main
main mute Get or set the mute state of the Main L/R output.
main fader Get or set the fader level of the Main L/R output.
main fadein Fade in the Main L/R output over a specified duration.
main fadeout Fade out the Main L/R output over a specified duration.
main eq on Get or set the EQ on/off state of the Main L/R output.
main eq <band> gain Get or set the gain of the specified EQ band.
main eq <band> freq Get or set the frequency of the specified EQ band.
main eq <band> q Get or set the Q factor of the specified EQ band.
main eq <band> type Get or set the type of the specified EQ band.
main comp on Get or set the compressor on/off state of the Main L/R
output.
main comp mode Get or set the compressor mode of the Main L/R output.
main comp threshold Get or set the compressor threshold of the Main L/R
output.
main comp ratio Get or set the compressor ratio of the Main L/R output.
main comp mix Get or set the compressor mix level of the Main L/R
output.
main comp makeup Get or set the compressor makeup gain of the Main L/R
output.
main comp attack Get or set the compressor attack time of the Main L/R
output.
main comp hold Get or set the compressor hold time of the Main L/R
output.
main comp release Get or set the compressor release time of the Main L/R
output.
MainMono
mainmono mute Get or set the mute state of the Main Mono output.
mainmono fader Get or set the fader level of the Main Mono output.
mainmono fadein Fade in the Main Mono output over a specified
duration.
mainmono fadeout Fade out the Main Mono output over a specified
duration.
mainmono eq on Get or set the EQ on/off state of the Main Mono
output.
mainmono eq <band> gain Get or set the gain of the specified EQ band.
mainmono eq <band> freq Get or set the frequency of the specified EQ band.
mainmono eq <band> q Get or set the Q factor of the specified EQ band.
mainmono eq <band> type Get or set the type of the specified EQ band.
mainmono comp on Get or set the compressor on/off state of the Main
Mono output.
mainmono comp mode Get or set the compressor mode of the Main Mono
output.
mainmono comp threshold Get or set the compressor threshold of the Main
Mono output.
mainmono comp ratio Get or set the compressor ratio of the Main Mono
output.
mainmono comp mix Get or set the compressor mix level of the Main
Mono output.
mainmono comp makeup Get or set the compressor makeup gain of the Main
Mono output.
mainmono comp attack Get or set the compressor attack time of the Main
Mono output.
mainmono comp hold Get or set the compressor hold time of the Main
Mono output.
mainmono comp release Get or set the compressor release time of the Main
Mono output.
Matrix
matrix <index> mute Get or set the mute state of the Matrix
output.
matrix <index> fader Get or set the fader level of the Matrix
output.
matrix <index> fadein Fade in the Matrix output over a specified
duration.
matrix <index> fadeout Fade out the Matrix output over a specified
duration.
matrix <index> eq on Get or set the EQ on/off state of the Matrix
output.
matrix <index> eq <band> gain Get or set the gain of the specified EQ band.
matrix <index> eq <band> freq Get or set the frequency of the specified EQ
band.
matrix <index> eq <band> q Get or set the Q factor of the specified EQ
band.
matrix <index> eq <band> type Get or set the type of the specified EQ band.
matrix <index> comp on Get or set the compressor on/off state of the
Matrix output.
matrix <index> comp mode Get or set the compressor mode of the Matrix
output.
matrix <index> comp threshold Get or set the compressor threshold of the
Matrix output.
matrix <index> comp ratio Get or set the compressor ratio of the Matrix
output.
matrix <index> comp mix Get or set the compressor mix level of the
Matrix output.
matrix <index> comp makeup Get or set the compressor makeup gain of the
Matrix output.
matrix <index> comp attack Get or set the compressor attack time of the
Matrix output.
matrix <index> comp hold Get or set the compressor hold time of the
Matrix output.
matrix <index> comp release Get or set the compressor release time of the
Matrix output.
Strip
strip <index> mute Get or set the mute state of the strip.
strip <index> fader Get or set the fader level of the strip.
strip <index> fadein Fade in the strip over a specified duration.
strip <index> fadeout Fade out the strip over a specified duration.
strip <index> send Get or set the send level for a specific bus.
strip <index> name Get or set the name of the strip.
strip <index> gate on Get or set the gate on/off state of the strip.
strip <index> gate mode Get or set the gate mode of the strip.
strip <index> gate threshold Get or set the gate threshold of the strip.
strip <index> gate range Get or set the gate range of the strip.
strip <index> gate attack Get or set the gate attack time of the strip.
strip <index> gate hold Get or set the gate hold time of the strip.
strip <index> gate release Get or set the gate release time of the strip.
strip <index> eq on Get or set the EQ on/off state of the strip.
strip <index> eq <band> gain Get or set the gain of the EQ band.
strip <index> eq <band> freq Get or set the frequency of the EQ band.
strip <index> eq <band> q Get or set the Q factor of the EQ band.
strip <index> eq <band> type Get or set the type of the EQ band.
strip <index> comp on Get or set the compressor on/off state of the
strip.
strip <index> comp mode Get or set the compressor mode of the strip.
strip <index> comp threshold Get or set the compressor threshold of the
strip.
strip <index> comp ratio Get or set the compressor ratio of the strip.
strip <index> comp mix Get or set the compressor mix of the strip.
strip <index> comp makeup Get or set the compressor makeup gain of the
strip.
strip <index> comp attack Get or set the compressor attack time of the
strip.
strip <index> comp hold Get or set the compressor hold time of the
strip.
strip <index> comp release Get or set the compressor release time of the
strip.
Bus
bus <index> mute Get or set the mute state of the bus.
bus <index> fader Get or set the fader level of the bus.
bus <index> fadein Fade in the bus over a specified duration.
bus <index> fadeout Fade out the bus over a specified duration.
bus <index> name Get or set the name of the bus.
bus <index> eq on Get or set the EQ on/off state of the bus.
bus <index> eq mode Get or set the EQ mode of the bus (peq, geq or
teq).
bus <index> eq <band> gain Get or set the gain of the EQ band.
bus <index> eq <band> freq Get or set the frequency of the EQ band.
bus <index> eq <band> q Get or set the Q factor of the EQ band.
bus <index> eq <band> type Get or set the type of the EQ band (lcut, lshv,
peq, veq, hshv, hcut).
bus <index> comp on Get or set the compressor on/off state of the
bus.
bus <index> comp mode Get or set the compressor mode of the bus (comp,
exp).
bus <index> comp threshold Get or set the compressor threshold of the bus
(in dB).
bus <index> comp ratio Get or set the compressor ratio of the bus.
bus <index> comp mix Get or set the compressor mix level of the bus
(in %).
bus <index> comp makeup Get or set the compressor makeup gain of the bus
(in dB).
bus <index> comp attack Get or set the compressor attack time of the bus
(in ms).
bus <index> comp hold Get or set the compressor hold time of the bus
(in ms).
bus <index> comp release Get or set the compressor release time of the
bus (in ms).
Headamp
headamp <index> gain Get or set the gain of the headamp.
headamp <index> phantom Get or set the phantom power state of the headamp.
Snapshot
snapshot list List all snapshots.
snapshot <index> name Get or set the name of a snapshot.
snapshot <index> save Save the current mixer state to a snapshot.
snapshot <index> load Load a mixer state from a snapshot.
snapshot <index> delete Delete a snapshot.
DCA
dca <index> mute Get or set the mute status of the DCA group.
dca <index> name Get or set the name of the DCA group.
Run "x32-cli <command> --help" for more information on a command.
```

129
xair-help.md Normal file
View File

@ -0,0 +1,129 @@
```console
Usage: xair-cli <command> [flags]
A CLI to control Behringer X-Air mixers.
Flags:
-h, --help Show context-sensitive help.
-H, --host="mixer.local" The host of the X-Air device ($XAIR_CLI_HOST).
-P, --port=10024 The port of the X-Air device ($XAIR_CLI_PORT).
-T, --timeout=100ms Timeout for OSC operations ($XAIR_CLI_TIMEOUT).
-L, --loglevel="warn" Log level for the CLI ($XAIR_CLI_LOGLEVEL).
-v, --version Print xair-cli version information and quit
Commands:
completion Generate shell completion scripts.
info Print mixer information.
raw Send raw OSC messages to the mixer.
Main
main mute Get or set the mute state of the Main L/R output.
main fader Get or set the fader level of the Main L/R output.
main fadein Fade in the Main L/R output over a specified duration.
main fadeout Fade out the Main L/R output over a specified duration.
main eq on Get or set the EQ on/off state of the Main L/R output.
main eq <band> gain Get or set the gain of the specified EQ band.
main eq <band> freq Get or set the frequency of the specified EQ band.
main eq <band> q Get or set the Q factor of the specified EQ band.
main eq <band> type Get or set the type of the specified EQ band.
main comp on Get or set the compressor on/off state of the Main L/R
output.
main comp mode Get or set the compressor mode of the Main L/R output.
main comp threshold Get or set the compressor threshold of the Main L/R
output.
main comp ratio Get or set the compressor ratio of the Main L/R output.
main comp mix Get or set the compressor mix level of the Main L/R
output.
main comp makeup Get or set the compressor makeup gain of the Main L/R
output.
main comp attack Get or set the compressor attack time of the Main L/R
output.
main comp hold Get or set the compressor hold time of the Main L/R
output.
main comp release Get or set the compressor release time of the Main L/R
output.
Strip
strip <index> mute Get or set the mute state of the strip.
strip <index> fader Get or set the fader level of the strip.
strip <index> fadein Fade in the strip over a specified duration.
strip <index> fadeout Fade out the strip over a specified duration.
strip <index> send Get or set the send level for a specific bus.
strip <index> name Get or set the name of the strip.
strip <index> gate on Get or set the gate on/off state of the strip.
strip <index> gate mode Get or set the gate mode of the strip.
strip <index> gate threshold Get or set the gate threshold of the strip.
strip <index> gate range Get or set the gate range of the strip.
strip <index> gate attack Get or set the gate attack time of the strip.
strip <index> gate hold Get or set the gate hold time of the strip.
strip <index> gate release Get or set the gate release time of the strip.
strip <index> eq on Get or set the EQ on/off state of the strip.
strip <index> eq <band> gain Get or set the gain of the EQ band.
strip <index> eq <band> freq Get or set the frequency of the EQ band.
strip <index> eq <band> q Get or set the Q factor of the EQ band.
strip <index> eq <band> type Get or set the type of the EQ band.
strip <index> comp on Get or set the compressor on/off state of the
strip.
strip <index> comp mode Get or set the compressor mode of the strip.
strip <index> comp threshold Get or set the compressor threshold of the
strip.
strip <index> comp ratio Get or set the compressor ratio of the strip.
strip <index> comp mix Get or set the compressor mix of the strip.
strip <index> comp makeup Get or set the compressor makeup gain of the
strip.
strip <index> comp attack Get or set the compressor attack time of the
strip.
strip <index> comp hold Get or set the compressor hold time of the
strip.
strip <index> comp release Get or set the compressor release time of the
strip.
Bus
bus <index> mute Get or set the mute state of the bus.
bus <index> fader Get or set the fader level of the bus.
bus <index> fadein Fade in the bus over a specified duration.
bus <index> fadeout Fade out the bus over a specified duration.
bus <index> name Get or set the name of the bus.
bus <index> eq on Get or set the EQ on/off state of the bus.
bus <index> eq mode Get or set the EQ mode of the bus (peq, geq or
teq).
bus <index> eq <band> gain Get or set the gain of the EQ band.
bus <index> eq <band> freq Get or set the frequency of the EQ band.
bus <index> eq <band> q Get or set the Q factor of the EQ band.
bus <index> eq <band> type Get or set the type of the EQ band (lcut, lshv,
peq, veq, hshv, hcut).
bus <index> comp on Get or set the compressor on/off state of the
bus.
bus <index> comp mode Get or set the compressor mode of the bus (comp,
exp).
bus <index> comp threshold Get or set the compressor threshold of the bus
(in dB).
bus <index> comp ratio Get or set the compressor ratio of the bus.
bus <index> comp mix Get or set the compressor mix level of the bus
(in %).
bus <index> comp makeup Get or set the compressor makeup gain of the bus
(in dB).
bus <index> comp attack Get or set the compressor attack time of the bus
(in ms).
bus <index> comp hold Get or set the compressor hold time of the bus
(in ms).
bus <index> comp release Get or set the compressor release time of the
bus (in ms).
Headamp
headamp <index> gain Get or set the gain of the headamp.
headamp <index> phantom Get or set the phantom power state of the headamp.
Snapshot
snapshot list List all snapshots.
snapshot <index> name Get or set the name of a snapshot.
snapshot <index> save Save the current mixer state to a snapshot.
snapshot <index> load Load a mixer state from a snapshot.
snapshot <index> delete Delete a snapshot.
DCA
dca <index> mute Get or set the mute status of the DCA group.
dca <index> name Get or set the name of the DCA group.
Run "xair-cli <command> --help" for more information on a command.
```