From 1597f8f3522a07f1f34cc69d95093269b5ff5efd Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sat, 7 Feb 2026 01:28:59 +0000 Subject: [PATCH] main comp/eq commands implemented factory methods unexported lr.go renamed to main.go main.go renamed to cli.go --- cli.go | 131 +++++++++ internal/xair/bus.go | 6 +- internal/xair/client.go | 8 +- internal/xair/comp.go | 54 ++-- internal/xair/eq.go | 46 ++- internal/xair/gate.go | 8 +- internal/xair/headamp.go | 7 +- internal/xair/main.go | 8 +- internal/xair/snapshot.go | 6 +- internal/xair/strip.go | 8 +- lr.go | 129 --------- main.go | 582 ++++++++++++++++++++++++++++++-------- 12 files changed, 700 insertions(+), 293 deletions(-) create mode 100644 cli.go delete mode 100644 lr.go diff --git a/cli.go b/cli.go new file mode 100644 index 0000000..7cc72d6 --- /dev/null +++ b/cli.go @@ -0,0 +1,131 @@ +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" +) + +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.Client + 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"` + Kind string `default:"xair" help:"The kind of the X-Air device." env:"XAIR_CLI_KIND" short:"K" enum:"xair,x32"` + 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:"" aliases:"c"` + + Raw RawCmd `help:"Send raw OSC messages to the mixer." cmd:"" group:"Raw"` + 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"` +} + +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 == "" { + info, ok := debug.ReadBuildInfo() + if !ok { + return "(unable to read build info)" + } + version = strings.Split(info.Main.Version, "-")[0] + } + return version + }(), + }, + ) + + 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 client.Close() + + 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.Client, error) { + client, err := xair.NewClient( + config.Host, + config.Port, + xair.WithKind(config.Kind), + xair.WithTimeout(config.Timeout), + ) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/xair/bus.go b/internal/xair/bus.go index e5f63af..a18c223 100644 --- a/internal/xair/bus.go +++ b/internal/xair/bus.go @@ -3,16 +3,16 @@ package xair import "fmt" type Bus struct { - baseAddress string client *Client + baseAddress string Eq *Eq Comp *Comp } -func NewBus(c *Client) *Bus { +func newBus(c *Client) *Bus { return &Bus{ - baseAddress: c.addressMap["bus"], client: c, + baseAddress: c.addressMap["bus"], Eq: newEqForBus(c), Comp: newCompForBus(c), } diff --git a/internal/xair/client.go b/internal/xair/client.go index ad0345a..3ef6e29 100644 --- a/internal/xair/client.go +++ b/internal/xair/client.go @@ -58,10 +58,10 @@ func NewClient(mixerIP string, mixerPort int, opts ...Option) (*Client, error) { engine: *e, } c.Main = newMainStereo(c) - c.Strip = NewStrip(c) - c.Bus = NewBus(c) - c.HeadAmp = NewHeadAmp(c) - c.Snapshot = NewSnapshot(c) + c.Strip = newStrip(c) + c.Bus = newBus(c) + c.HeadAmp = newHeadAmp(c) + c.Snapshot = newSnapshot(c) return c, nil } diff --git a/internal/xair/comp.go b/internal/xair/comp.go index 5589be1..1da575e 100644 --- a/internal/xair/comp.go +++ b/internal/xair/comp.go @@ -5,6 +5,18 @@ import "fmt" type Comp struct { client *Client baseAddress string + AddressFunc func(fmtString string, args ...any) string +} + +// Factory function to create Comp instance for Main +func newCompForMain(c *Client) *Comp { + return &Comp{ + client: c, + baseAddress: c.addressMap["main"], + AddressFunc: func(fmtString string, args ...any) string { + return fmtString + }, + } } // Factory function to create Comp instance for Strip @@ -12,6 +24,9 @@ func newCompForStrip(c *Client) *Comp { return &Comp{ client: c, baseAddress: c.addressMap["strip"], + AddressFunc: func(fmtString string, args ...any) string { + return fmt.Sprintf(fmtString, args...) + }, } } @@ -20,12 +35,15 @@ func newCompForBus(c *Client) *Comp { return &Comp{ client: c, baseAddress: c.addressMap["bus"], + AddressFunc: func(fmtString string, args ...any) string { + return fmt.Sprintf(fmtString, args...) + }, } } // 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) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/on" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/on" err := c.client.SendMessage(address) if err != nil { return false, err @@ -44,7 +62,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). func (c *Comp) SetOn(index int, on bool) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/on" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/on" var value int32 if on { value = 1 @@ -54,7 +72,7 @@ func (c *Comp) SetOn(index int, on bool) error { // 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 := fmt.Sprintf(c.baseAddress, index) + "/dyn/mode" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/mode" err := c.client.SendMessage(address) if err != nil { return "", err @@ -75,14 +93,14 @@ func (c *Comp) Mode(index int) (string, error) { // 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 := fmt.Sprintf(c.baseAddress, index) + "/dyn/mode" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/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). func (c *Comp) Threshold(index int) (float64, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/thr" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/thr" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -101,13 +119,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). func (c *Comp) SetThreshold(index int, threshold float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/thr" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/thr" 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). func (c *Comp) Ratio(index int) (float32, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/ratio" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/ratio" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -129,7 +147,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). func (c *Comp) SetRatio(index int, ratio float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/ratio" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/ratio" 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)))) @@ -137,7 +155,7 @@ 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). func (c *Comp) Attack(index int) (float64, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/attack" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/attack" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -156,13 +174,13 @@ 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). func (c *Comp) SetAttack(index int, attack float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/attack" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/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). func (c *Comp) Hold(index int) (float64, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/hold" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/hold" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -181,13 +199,13 @@ 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). func (c *Comp) SetHold(index int, hold float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/hold" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/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). func (c *Comp) Release(index int) (float64, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/release" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/release" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -206,13 +224,13 @@ 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). func (c *Comp) SetRelease(index int, release float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/release" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/release" return c.client.SendMessage(address, float32(logSet(4, 4000, release))) } // 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) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/mgain" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -231,13 +249,13 @@ func (c *Comp) Makeup(index int) (float64, error) { // 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 { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/mgain" 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). func (c *Comp) Mix(index int) (float64, error) { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mix" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/mix" err := c.client.SendMessage(address) if err != nil { return 0, err @@ -256,6 +274,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). func (c *Comp) SetMix(index int, mix float64) error { - address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mix" + address := c.AddressFunc(c.baseAddress, index) + "/dyn/mix" return c.client.SendMessage(address, float32(linSet(0, 100, mix))) } diff --git a/internal/xair/eq.go b/internal/xair/eq.go index 7c90f7b..d204f27 100644 --- a/internal/xair/eq.go +++ b/internal/xair/eq.go @@ -1,10 +1,24 @@ package xair -import "fmt" +import ( + "fmt" +) type Eq struct { client *Client baseAddress string + AddressFunc func(fmtString string, args ...any) string +} + +// Factory function to create Eq instance for Main +func newEqForMain(c *Client) *Eq { + return &Eq{ + client: c, + baseAddress: c.addressMap["main"], + AddressFunc: func(fmtString string, args ...any) string { + return fmtString + }, + } } // Factory function to create Eq instance for Strip @@ -12,6 +26,9 @@ func newEqForStrip(c *Client) *Eq { return &Eq{ client: c, baseAddress: c.addressMap["strip"], + AddressFunc: func(fmtString string, args ...any) string { + return fmt.Sprintf(fmtString, args...) + }, } } @@ -20,12 +37,15 @@ func newEqForBus(c *Client) *Eq { return &Eq{ client: c, baseAddress: c.addressMap["bus"], + AddressFunc: func(fmtString string, args ...any) string { + return fmt.Sprintf(fmtString, args...) + }, } } // 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) { - address := fmt.Sprintf(e.baseAddress, index) + "/eq/on" + address := e.AddressFunc(e.baseAddress, index) + "/eq/on" err := e.client.SendMessage(address) if err != nil { return false, err @@ -44,7 +64,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). func (e *Eq) SetOn(index int, on bool) error { - address := fmt.Sprintf(e.baseAddress, index) + "/eq/on" + address := e.AddressFunc(e.baseAddress, index) + "/eq/on" var value int32 if on { value = 1 @@ -53,7 +73,7 @@ func (e *Eq) SetOn(index int, on bool) error { } func (e *Eq) Mode(index int) (string, error) { - address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" + address := e.AddressFunc(e.baseAddress, index) + "/eq/mode" err := e.client.SendMessage(address) if err != nil { return "", err @@ -73,14 +93,14 @@ func (e *Eq) Mode(index int) (string, error) { } func (e *Eq) SetMode(index int, mode string) error { - address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" + address := e.AddressFunc(e.baseAddress, index) + "/eq/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). func (e *Eq) Gain(index int, band int) (float64, error) { - address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/g", band) + address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/eq/%d/g", band) err := e.client.SendMessage(address) if err != nil { return 0, err @@ -99,13 +119,13 @@ 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). func (e *Eq) SetGain(index int, 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("/eq/%d/g", band) 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). func (e *Eq) Frequency(index int, band int) (float64, error) { - address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/f", band) + address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/eq/%d/f", band) err := e.client.SendMessage(address) if err != nil { return 0, err @@ -124,13 +144,13 @@ 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). func (e *Eq) SetFrequency(index int, 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("/eq/%d/f", band) 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). func (e *Eq) Q(index int, band int) (float64, error) { - address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/q", band) + address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/eq/%d/q", band) err := e.client.SendMessage(address) if err != nil { return 0, err @@ -149,13 +169,13 @@ 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). func (e *Eq) SetQ(index int, 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("/eq/%d/q", band) 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). func (e *Eq) Type(index int, band int) (string, error) { - address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) + address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) err := e.client.SendMessage(address) if err != nil { return "", err @@ -176,7 +196,7 @@ func (e *Eq) Type(index int, band int) (string, error) { // 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 string) error { - address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) + address := e.AddressFunc(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) possibleTypes := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"} return e.client.SendMessage(address, int32(indexOf(possibleTypes, eqType))) } diff --git a/internal/xair/gate.go b/internal/xair/gate.go index 96d5556..aa2d7d8 100644 --- a/internal/xair/gate.go +++ b/internal/xair/gate.go @@ -7,8 +7,12 @@ type Gate struct { baseAddress string } -func newGate(c *Client) *Gate { - return &Gate{client: c, baseAddress: c.addressMap["strip"]} +// Factory function to create Gate instance for Strip +func newGateForStrip(c *Client) *Gate { + return &Gate{ + client: c, + baseAddress: c.addressMap["strip"], + } } // On retrieves the on/off status of the Gate for a specific strip (1-based indexing). diff --git a/internal/xair/headamp.go b/internal/xair/headamp.go index 10d1b24..a45e61b 100644 --- a/internal/xair/headamp.go +++ b/internal/xair/headamp.go @@ -3,14 +3,15 @@ package xair import "fmt" type HeadAmp struct { - baseAddress string client *Client + baseAddress string } -func NewHeadAmp(c *Client) *HeadAmp { +// newHeadAmp creates a new HeadAmp instance with the provided client. +func newHeadAmp(c *Client) *HeadAmp { return &HeadAmp{ - baseAddress: c.addressMap["headamp"], client: c, + baseAddress: c.addressMap["headamp"], } } diff --git a/internal/xair/main.go b/internal/xair/main.go index e680136..5eea62e 100644 --- a/internal/xair/main.go +++ b/internal/xair/main.go @@ -3,14 +3,18 @@ package xair import "fmt" type Main struct { - baseAddress string client *Client + baseAddress string + Eq *Eq + Comp *Comp } func newMainStereo(c *Client) *Main { return &Main{ - baseAddress: c.addressMap["main"], client: c, + baseAddress: c.addressMap["main"], + Eq: newEqForMain(c), + Comp: newCompForMain(c), } } diff --git a/internal/xair/snapshot.go b/internal/xair/snapshot.go index 3104ec5..cc30f2e 100644 --- a/internal/xair/snapshot.go +++ b/internal/xair/snapshot.go @@ -3,14 +3,14 @@ package xair import "fmt" type Snapshot struct { - baseAddress string client *Client + baseAddress string } -func NewSnapshot(c *Client) *Snapshot { +func newSnapshot(c *Client) *Snapshot { return &Snapshot{ - baseAddress: c.addressMap["snapshot"], client: c, + baseAddress: c.addressMap["snapshot"], } } diff --git a/internal/xair/strip.go b/internal/xair/strip.go index 8b4fecb..d0f9747 100644 --- a/internal/xair/strip.go +++ b/internal/xair/strip.go @@ -3,18 +3,18 @@ package xair import "fmt" type Strip struct { - baseAddress string client *Client + baseAddress string Gate *Gate Eq *Eq Comp *Comp } -func NewStrip(c *Client) *Strip { +func newStrip(c *Client) *Strip { return &Strip{ - baseAddress: c.addressMap["strip"], client: c, - Gate: newGate(c), + baseAddress: c.addressMap["strip"], + Gate: newGateForStrip(c), Eq: newEqForStrip(c), Comp: newCompForStrip(c), } diff --git a/lr.go b/lr.go deleted file mode 100644 index 032df25..0000000 --- a/lr.go +++ /dev/null @@ -1,129 +0,0 @@ -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:""` -} - -// 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 *bool `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:""` -} - -// 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); 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: %t\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 -} diff --git a/main.go b/main.go index 7cc72d6..36f3652 100644 --- a/main.go +++ b/main.go @@ -2,130 +2,488 @@ 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" ) -var version string // Version of the CLI, set at build 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:""` -// VersionFlag is a custom flag type that prints the version and exits. -type VersionFlag string + 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:""` -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) + Eq MainEqCmdGroup `help:"Commands for controlling the equalizer 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 *bool `arg:"" help:"The mute state to set. If not provided, the current state will be printed." optional:""` +} + +// 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); 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: %t\n", *cmd.Mute) return nil } -type context struct { - Client *xair.Client - Out io.Writer +// 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:""` } -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"` - Kind string `default:"xair" help:"The kind of the X-Air device." env:"XAIR_CLI_KIND" short:"K" enum:"xair,x32"` - 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:"" aliases:"c"` - - Raw RawCmd `help:"Send raw OSC messages to the mixer." cmd:"" group:"Raw"` - 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"` -} - -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 == "" { - info, ok := debug.ReadBuildInfo() - if !ok { - return "(unable to read build info)" - } - version = strings.Split(info.Main.Version, "-")[0] - } - return version - }(), - }, - ) - - 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 client.Close() - - 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.Client, error) { - client, err := xair.NewClient( - config.Host, - config.Port, - xair.WithKind(config.Kind), - xair.WithTimeout(config.Timeout), - ) - if err != nil { - return nil, err +// 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 } - return client, 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 equalizer 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."` + 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(ctx kong.Context) error { + if cmd.Band.Band < 1 || cmd.Band.Band > 6 { + return fmt.Errorf("invalid EQ band number: %d. Valid range is 1-6", 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, main *MainCmdGroup) 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 == "true"); 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 == "true") + 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, main *MainCmdGroup, 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, main *MainCmdGroup, 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, main *MainCmdGroup, 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, main *MainCmdGroup, 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, main *MainCmdGroup) 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 == "true"); 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 == "true") + 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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, main *MainCmdGroup) 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 }