From 128f0c1df6d391667b2577213dff2cf5ed4cd5f0 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 5 Feb 2026 03:36:22 +0000 Subject: [PATCH] migrate CLI component to Kong --- bus.go | 503 +++++++++++++++ cmd/bus.go | 935 --------------------------- cmd/context.go | 22 - cmd/headamp.go | 211 ------ cmd/main.go | 237 ------- cmd/raw.go | 58 -- cmd/root.go | 106 --- cmd/strip.go | 1354 --------------------------------------- cmd/util.go | 38 -- go.mod | 22 +- go.sum | 63 +- headamp.go | 124 ++++ internal/xair/client.go | 10 +- internal/xair/comp.go | 12 +- internal/xair/eq.go | 30 +- lr.go | 120 ++++ main.go | 110 +++- raw.go | 34 + strip.go | 663 +++++++++++++++++++ util.go | 12 + 20 files changed, 1624 insertions(+), 3040 deletions(-) create mode 100644 bus.go delete mode 100644 cmd/bus.go delete mode 100644 cmd/context.go delete mode 100644 cmd/headamp.go delete mode 100644 cmd/main.go delete mode 100644 cmd/raw.go delete mode 100644 cmd/root.go delete mode 100644 cmd/strip.go delete mode 100644 cmd/util.go create mode 100644 headamp.go create mode 100644 lr.go create mode 100644 raw.go create mode 100644 strip.go create mode 100644 util.go diff --git a/bus.go b/bus.go new file mode 100644 index 0000000..3401c29 --- /dev/null +++ b/bus.go @@ -0,0 +1,503 @@ +package main + +import ( + "fmt" + "time" +) + +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:"The index of the 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"` +} + +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 == "true"); err != nil { + return err + } + fmt.Fprintf(ctx.Out, "Bus %d mute state set to: %s\n", bus.Index.Index, *cmd.State) + return nil +} + +type BusFaderCmd struct { + Level *float64 `arg:"" help:"The fader level to set (in dB). If not provided, the current fader level will be returned."` +} + +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 +} + +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:""` +} + +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 +} + +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:""` +} + +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 +} + +type BusNameCmd struct { + Name *string `arg:"" help:"The name to set for the bus. If not provided, the current name will be returned."` +} + +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 +} + +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 (graphic or parametric)." cmd:"mode"` + Band struct { + Band int `arg:"" help:"The EQ band number."` + 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 (bell, high shelf, low shelf, high pass, low pass)." cmd:"type"` + } `help:"Commands for controlling a specific EQ band of the bus." arg:""` +} + +func (cmd *BusEqCmdGroup) Validate() error { + if cmd.Band.Band < 1 || cmd.Band.Band > 6 { + return fmt.Errorf("EQ band number must be between 1 and 6") + } + return nil +} + +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"` +} + +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 == "true"); err != nil { + return err + } + fmt.Fprintf(ctx.Out, "Bus %d EQ on state set to: %s\n", bus.Index.Index, *cmd.State) + return nil +} + +type BusEqModeCmd struct { + Mode *string `arg:"" help:"The EQ mode to set (graphic or parametric). If not provided, the current EQ mode will be returned." optional:"" enum:"peq,geq,teq"` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} + +type BusEqBandTypeCmd struct { + Type *string `arg:"" help:"The type to set for the EQ band (bell, high shelf, low shelf, high pass, low pass). If not provided, the current type will be returned." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"` +} + +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 +} + +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 (standard, vintage, or modern)." 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"` +} + +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"` +} + +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 == "true"); err != nil { + return err + } + fmt.Fprintf(ctx.Out, "Bus %d compressor on state set to: %s\n", bus.Index.Index, *cmd.State) + return nil +} + +type BusCompModeCmd struct { + Mode *string `arg:"" help:"The compressor mode to set (standard, vintage, or modern). If not provided, the current compressor mode will be returned." optional:"" enum:"comp,exp"` +} + +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 +} + +type BusCompThresholdCmd struct { + Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB). If not provided, the current compressor threshold will be returned."` +} + +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 +} + +type BusCompRatioCmd struct { + Ratio *float64 `arg:"" help:"The compressor ratio to set. If not provided, the current compressor ratio will be returned."` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} + +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."` +} + +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 +} diff --git a/cmd/bus.go b/cmd/bus.go deleted file mode 100644 index 4f510a6..0000000 --- a/cmd/bus.go +++ /dev/null @@ -1,935 +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 - }, -} - -// busCompModeCmd represents the bus Compressor mode command. -var busCompModeCmd = &cobra.Command{ - Short: "Get or set the bus Compressor mode", - Long: `Get or set the Compressor 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]) - - if len(args) == 1 { - mode, err := client.Bus.Comp.Mode(busIndex) - if err != nil { - return fmt.Errorf("Error getting bus Compressor mode: %w", err) - } - cmd.Printf("Bus %d Compressor mode: %s\n", busIndex, mode) - return nil - } - - mode := args[1] - if !contains([]string{"comp", "exp"}, mode) { - return fmt.Errorf("Invalid mode value. Valid values are: comp, exp") - } - - err := client.Bus.Comp.SetMode(busIndex, mode) - if err != nil { - return fmt.Errorf("Error setting bus Compressor mode: %w", err) - } - - cmd.Printf("Bus %d Compressor mode set to %s\n", busIndex, mode) - 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(busCompModeCmd) - busCompCmd.AddCommand(busCompThresholdCmd) - busCompCmd.AddCommand(busCompRatioCmd) - busCompCmd.AddCommand(busCompMixCmd) - busCompCmd.AddCommand(busCompMakeUpCmd) - busCompCmd.AddCommand(busCompAttackCmd) - busCompCmd.AddCommand(busCompHoldCmd) - busCompCmd.AddCommand(busCompReleaseCmd) -} diff --git a/cmd/context.go b/cmd/context.go deleted file mode 100644 index 3614b78..0000000 --- a/cmd/context.go +++ /dev/null @@ -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 -} diff --git a/cmd/headamp.go b/cmd/headamp.go deleted file mode 100644 index fb307fa..0000000 --- a/cmd/headamp.go +++ /dev/null @@ -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) -} diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index 69479d8..0000000 --- a/cmd/main.go +++ /dev/null @@ -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") -} diff --git a/cmd/raw.go b/cmd/raw.go deleted file mode 100644 index 92f4a45..0000000 --- a/cmd/raw.go +++ /dev/null @@ -1,58 +0,0 @@ -package cmd - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" -) - -// rawCmd represents the raw command -var rawCmd = &cobra.Command{ - Short: "Send a raw OSC message to the mixer", - Long: `Send a raw OSC message to the mixer. -You need to provide the OSC address and any parameters as arguments. - -Optionally provide a timeout duration to wait for a response from the mixer. Default is 200ms.`, - Use: "raw", - Example: ` xair-cli raw /xinfo - xair-cli raw /ch/01/mix/fader 0.75 - xair-cli raw --timeout 500ms /bus/2/mix/on 1`, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - client := ClientFromContext(cmd.Context()) - if client == nil { - return fmt.Errorf("no client found in context") - } - - command := args[0] - params := make([]any, len(args[1:])) - for i, arg := range args[1:] { - params[i] = arg - } - if err := client.SendMessage(command, params...); err != nil { - return fmt.Errorf("error sending message: %v", err) - } - - timeout, err := cmd.Flags().GetDuration("timeout") - if err != nil { - return fmt.Errorf("error getting timeout flag: %v", err) - } - - msg, err := client.ReceiveMessage(timeout) - if err != nil { - return fmt.Errorf("error receiving message: %v", err) - } - - if msg != nil { - fmt.Printf("Received response: %v\n", msg) - } - return nil - }, -} - -func init() { - rootCmd.AddCommand(rawCmd) - - rawCmd.Flags().DurationP("timeout", "t", 200*time.Millisecond, "Timeout duration for receiving a response") -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 4f78739..0000000 --- a/cmd/root.go +++ /dev/null @@ -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 -} diff --git a/cmd/strip.go b/cmd/strip.go deleted file mode 100644 index 97cb9d5..0000000 --- a/cmd/strip.go +++ /dev/null @@ -1,1354 +0,0 @@ -package cmd - -import ( - "fmt" - "time" - - "github.com/spf13/cobra" -) - -// stripCmd represents the strip command. -var stripCmd = &cobra.Command{ - Short: "Commands to control individual strips", - Long: `Commands to control individual strips of the XAir mixer, including fader level and mute status.`, - Use: "strip", - Run: func(cmd *cobra.Command, _ []string) { - cmd.Help() - }, -} - -// stripMuteCmd represents the strip mute command. -var stripMuteCmd = &cobra.Command{ - Short: "Get or set the mute status of a strip", - Long: `Get or set the mute status of a specific strip. - -If no argument is provided, the current mute status is retrieved. -If "true" or "1" is provided as an argument, the strip is muted. -If "false" or "0" is provided, the strip is unmuted.`, - Use: "mute [strip number] [true|false]", - Example: ` # Get the current mute status of strip 1 - xair-cli strip mute 1 - - # Mute strip 1 - xair-cli strip mute 1 true - # Unmute strip 1 - xair-cli strip mute 1 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) < 1 { - return fmt.Errorf("Please provide a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - resp, err := client.Strip.Mute(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip mute status: %w", err) - } - cmd.Printf("Strip %d mute: %v\n", stripIndex, resp) - return nil - } - - 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.Strip.SetMute(stripIndex, muted) - if err != nil { - return fmt.Errorf("Error setting strip mute status: %w", err) - } - - if muted { - cmd.Printf("Strip %d muted successfully\n", stripIndex) - } else { - cmd.Printf("Strip %d unmuted successfully\n", stripIndex) - } - return nil - }, -} - -// stripFaderCmd represents the strip fader command. -var stripFaderCmd = &cobra.Command{ - Short: "Get or set the fader level of a strip", - Long: `Get or set the fader level of a specific strip. - -If no level argument is provided, the current fader level is retrieved. -If a level argument (in dB) is provided, the strip fader is set to that level.`, - Use: "fader [strip number] [level in dB]", - Example: ` # Get the current fader level of strip 1 - xair-cli strip fader 1 - - # Set the fader level of strip 1 to -10.0 dB - xair-cli strip 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") - } - - if len(args) < 1 { - return fmt.Errorf("Please provide a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - level, err := client.Strip.Fader(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip fader level: %w", err) - } - cmd.Printf("Strip %d fader level: %.2f\n", stripIndex, level) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a fader level in dB") - } - - level := mustConvToFloat64(args[1]) - - err := client.Strip.SetFader(stripIndex, level) - if err != nil { - return fmt.Errorf("Error setting strip fader level: %w", err) - } - - cmd.Printf("Strip %d fader set to %.2f dB\n", stripIndex, level) - return nil - }, -} - -// stripFadeOutCmd represents the strip fade out command. -var stripFadeOutCmd = &cobra.Command{ - Short: "Fade out the strip over a specified duration", - Long: "Fade out the strip over a specified duration in seconds.", - Use: "fadeout [strip number] --duration [seconds] [target level in dB]", - Example: ` # Fade out strip 1 over 5 seconds - xair-cli strip 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 strip number") - } - - stripIndex := 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.Strip.Fader(stripIndex) - if err != nil { - return fmt.Errorf("Error getting current strip fader level: %w", err) - } - - totalSteps := float64(currentFader - target) - if totalSteps <= 0 { - cmd.Println("Strip 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.Strip.SetFader(stripIndex, currentFader) - if err != nil { - return fmt.Errorf("Error setting strip fader level: %w", err) - } - time.Sleep(stepDelay) - } - - cmd.Printf("Strip %d faded out to %.2f dB over %.2f seconds\n", stripIndex, target, duration.Seconds()) - return nil - }, -} - -// stripFadeInCmd represents the strip fade in command. -var stripFadeInCmd = &cobra.Command{ - Short: "Fade in the strip over a specified duration", - Long: "Fade in the strip over a specified duration in seconds.", - Use: "fadein [strip number] --duration [seconds] [target level in dB]", - Example: ` # Fade in strip 1 over 5 seconds - xair-cli strip fadein 1 --duration 5s 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 strip number") - } - - stripIndex := 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.Strip.Fader(stripIndex) - if err != nil { - return fmt.Errorf("Error getting current strip fader level: %w", err) - } - - totalSteps := float64(target - currentFader) - if totalSteps <= 0 { - cmd.Println("Strip 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.Strip.SetFader(stripIndex, currentFader) - if err != nil { - return fmt.Errorf("Error setting strip fader level: %w", err) - } - time.Sleep(stepDelay) - } - - cmd.Printf("Strip %d faded in to %.2f dB over %.2f seconds\n", stripIndex, target, duration.Seconds()) - return nil - }, -} - -// stripSendCmd represents the strip send command. -var stripSendCmd = &cobra.Command{ - Short: "Get or set the send levels for individual strips", - Long: "Get or set the send level from a specific strip to a specific bus.", - Use: "send [strip number] [bus number] [level in dB]", - Example: ` # Get the send level of strip 1 to bus 1 - xair-cli strip send 1 1 - - # Set the send level of strip 1 to bus 1 to -5.0 dB - xair-cli strip send 1 1 -- -5.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) < 2 { - return fmt.Errorf("Please provide strip number and bus number") - } - - stripIndex, busIndex := func() (int, int) { - return mustConvToInt(args[0]), mustConvToInt(args[1]) - }() - - if len(args) == 2 { - currentLevel, err := client.Strip.SendLevel(stripIndex, busIndex) - if err != nil { - return fmt.Errorf("Error getting strip send level: %w", err) - } - cmd.Printf("Strip %d send level to bus %d: %.2f dB\n", stripIndex, busIndex, currentLevel) - return nil - } - - if len(args) < 3 { - return fmt.Errorf("Please provide a send level in dB") - } - - level := mustConvToFloat64(args[2]) - - err := client.Strip.SetSendLevel(stripIndex, busIndex, level) - if err != nil { - return fmt.Errorf("Error setting strip send level: %w", err) - } - cmd.Printf("Strip %d send level to bus %d set to %.2f dB\n", stripIndex, busIndex, level) - return nil - }, -} - -// stripNameCmd represents the strip name command. -var stripNameCmd = &cobra.Command{ - Short: "Get or set the name of a strip", - Long: `Get or set the name of a specific strip. - -If no name argument is provided, the current strip name is retrieved. -If a name argument is provided, the strip name is set to that value.`, - Use: "name [strip number] [name]", - Example: ` # Get the current name of strip 1 - xair-cli strip name 1 - - # Set the name of strip 1 to "Guitar" - xair-cli strip name 1 "Guitar"`, - 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 strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - name, err := client.Strip.Name(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip name: %w", err) - } - cmd.Printf("Strip %d name: %s\n", stripIndex, name) - return nil - } - - name := args[1] - - err := client.Strip.SetName(stripIndex, name) - if err != nil { - return fmt.Errorf("Error setting strip name: %w", err) - } - cmd.Printf("Strip %d name set to: %s\n", stripIndex, name) - return nil - }, -} - -// stripGateCmd represents the strip Gate command. -var stripGateCmd = &cobra.Command{ - Short: "Commands to control the Gate of individual strips.", - Long: `Commands to control the Gate of individual strips, including turning the Gate on or off.`, - Use: "gate", - Run: func(cmd *cobra.Command, _ []string) { - cmd.Help() - }, -} - -// stripGateOnCmd represents the strip Gate on command. -var stripGateOnCmd = &cobra.Command{ - Short: "Get or set the Gate on/off status of a strip", - Long: `Get or set the Gate on/off status of a specific strip. - -If no status argument is provided, the current Gate status is retrieved. -If "true" or "1" is provided as an argument, the Gate is turned on. -If "false" or "0" is provided, the Gate is turned off.`, - Use: "on [strip number] [true|false]", - Example: ` # Get the current Gate status of strip 1 - xair-cli strip gate on 1 - - # Turn on Gate for strip 1 - xair-cli strip gate on 1 true - # Turn off Gate for strip 1 - xair-cli strip gate on 1 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) < 1 { - return fmt.Errorf("Please provide a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - on, err := client.Strip.Gate.On(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate on status: %w", err) - } - cmd.Printf("Strip %d Gate on: %v\n", stripIndex, on) - return nil - } - - var on bool - switch args[1] { - case "true", "1": - on = true - case "false", "0": - on = false - default: - return fmt.Errorf("Invalid Gate status. Use true/false or 1/0") - } - - err := client.Strip.Gate.SetOn(stripIndex, on) - if err != nil { - return fmt.Errorf("Error setting strip Gate on status: %w", err) - } - - if on { - cmd.Printf("Strip %d Gate turned on successfully\n", stripIndex) - } else { - cmd.Printf("Strip %d Gate turned off successfully\n", stripIndex) - } - return nil - }, -} - -// stripGateModeCmd represents the strip Gate Mode command. -var stripGateModeCmd = &cobra.Command{ - Short: "Get or set the Gate mode for a strip", - Long: "Get or set the Gate mode for a specific strip.", - Use: "mode [strip 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 a strip number") - } - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentMode, err := client.Strip.Gate.Mode(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate mode: %w", err) - } - cmd.Printf("Strip %d Gate mode: %s\n", stripIndex, currentMode) - return nil - } - if len(args) < 2 { - return fmt.Errorf("Please provide a mode") - } - - mode := args[1] - possibleModes := []string{"exp2", "exp3", "exp4", "gate", "duck"} - if !contains(possibleModes, mode) { - return fmt.Errorf("Invalid mode value. Valid values are: %v", possibleModes) - } - - err := client.Strip.Gate.SetMode(stripIndex, mode) - if err != nil { - return fmt.Errorf("Error setting strip Gate mode: %w", err) - } - - cmd.Printf("Strip %d Gate mode set to %s\n", stripIndex, mode) - return nil - }, -} - -// stripGateThresholdCmd represents the strip Gate Threshold command. -var stripGateThresholdCmd = &cobra.Command{ - Short: "Get or set the Gate threshold for a strip", - Long: "Get or set the Gate threshold for a specific strip.", - Use: "threshold [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentThreshold, err := client.Strip.Gate.Threshold(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate threshold: %w", err) - } - cmd.Printf("Strip %d Gate threshold: %.2f dB\n", stripIndex, currentThreshold) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a threshold in dB") - } - - threshold := mustConvToFloat64(args[1]) - err := client.Strip.Gate.SetThreshold(stripIndex, threshold) - if err != nil { - return fmt.Errorf("Error setting strip Gate threshold: %w", err) - } - - cmd.Printf("Strip %d Gate threshold set to %.2f dB\n", stripIndex, threshold) - return nil - }, -} - -// stripGateRangeCmd represents the strip Gate Range command. -var stripGateRangeCmd = &cobra.Command{ - Short: "Get or set the Gate range for a strip", - Long: "Get or set the Gate range for a specific strip.", - Use: "range [strip number] [range 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentRange, err := client.Strip.Gate.Range(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate range: %w", err) - } - cmd.Printf("Strip %d Gate range: %.2f dB\n", stripIndex, currentRange) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a range in dB") - } - - rangeDb := mustConvToFloat64(args[1]) - err := client.Strip.Gate.SetRange(stripIndex, rangeDb) - if err != nil { - return fmt.Errorf("Error setting strip Gate range: %w", err) - } - - cmd.Printf("Strip %d Gate range set to %.2f dB\n", stripIndex, rangeDb) - return nil - }, -} - -// stripGateAttackCmd represents the strip Gate Attack command. -var stripGateAttackCmd = &cobra.Command{ - Short: "Get or set the Gate attack time for a strip", - Long: "Get or set the Gate attack time for a specific strip.", - Use: "attack [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentAttack, err := client.Strip.Gate.Attack(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate attack time: %w", err) - } - cmd.Printf("Strip %d Gate attack time: %.2f ms\n", stripIndex, currentAttack) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide an attack time in ms") - } - - attack := mustConvToFloat64(args[1]) - err := client.Strip.Gate.SetAttack(stripIndex, attack) - if err != nil { - return fmt.Errorf("Error setting strip Gate attack time: %w", err) - } - - cmd.Printf("Strip %d Gate attack time set to %.2f ms\n", stripIndex, attack) - return nil - }, -} - -// stripGateHoldCmd represents the strip Gate Hold command. -var stripGateHoldCmd = &cobra.Command{ - Short: "Get or set the Gate hold time for a strip", - Long: "Get or set the Gate hold time for a specific strip.", - Use: "hold [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentHold, err := client.Strip.Gate.Hold(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate hold time: %w", err) - } - cmd.Printf("Strip %d Gate hold time: %.2f ms\n", stripIndex, currentHold) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a hold time in ms") - } - - hold := mustConvToFloat64(args[1]) - err := client.Strip.Gate.SetHold(stripIndex, hold) - if err != nil { - return fmt.Errorf("Error setting strip Gate hold time: %w", err) - } - - cmd.Printf("Strip %d Gate hold time set to %.2f ms\n", stripIndex, hold) - return nil - }, -} - -// stripGateReleaseCmd represents the strip Gate Release command. -var stripGateReleaseCmd = &cobra.Command{ - Short: "Get or set the Gate release time for a strip", - Long: "Get or set the Gate release time for a specific strip.", - Use: "release [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - if len(args) == 1 { - currentRelease, err := client.Strip.Gate.Release(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Gate release time: %w", err) - } - cmd.Printf("Strip %d Gate release time: %.2f ms\n", stripIndex, currentRelease) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a release time in ms") - } - - release := mustConvToFloat64(args[1]) - err := client.Strip.Gate.SetRelease(stripIndex, release) - if err != nil { - return fmt.Errorf("Error setting strip Gate release time: %w", err) - } - - cmd.Printf("Strip %d Gate release time set to %.2f ms\n", stripIndex, release) - return nil - }, -} - -// stripEqCmd represents the strip EQ command. -var stripEqCmd = &cobra.Command{ - Short: "Commands to control the EQ of individual strips.", - Long: `Commands to control the EQ of individual strips, including turning the EQ on or off.`, - Use: "eq", - Run: func(cmd *cobra.Command, _ []string) { - cmd.Help() - }, -} - -// stripEqOnCmd represents the strip EQ on command. -var stripEqOnCmd = &cobra.Command{ - Short: "Get or set the EQ on/off status of a strip", - Long: `Get or set the EQ on/off status of a specific strip. - -If no status argument is provided, the current EQ status is retrieved. -If "true" or "1" is provided as an argument, the EQ is turned on. -If "false" or "0" is provided, the EQ is turned off.`, - Use: "on [strip number] [true|false]", - Example: ` # Get the current EQ status of strip 1 - xair-cli strip eq on 1 - - # Turn on EQ for strip 1 - xair-cli strip eq on 1 true - # Turn off EQ for strip 1 - xair-cli strip eq on 1 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) < 1 { - return fmt.Errorf("Please provide a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - on, err := client.Strip.Eq.On(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip EQ on status: %w", err) - } - cmd.Printf("Strip %d EQ on: %v\n", stripIndex, on) - return nil - } - - var on bool - switch args[1] { - case "true", "1": - on = true - case "false", "0": - on = false - default: - return fmt.Errorf("Invalid EQ status. Use true/false or 1/0") - } - - err := client.Strip.Eq.SetOn(stripIndex, on) - if err != nil { - return fmt.Errorf("Error setting strip EQ on status: %w", err) - } - - if on { - cmd.Printf("Strip %d EQ turned on successfully\n", stripIndex) - } else { - cmd.Printf("Strip %d EQ turned off successfully\n", stripIndex) - } - return nil - }, -} - -// stripEqGainCmd represents the strip EQ Gain command. -var stripEqGainCmd = &cobra.Command{ - Short: "Get or set the EQ band gain for a strip", - Long: "Get or set the EQ band gain for a specific strip and band.", - Use: "gain [strip 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 strip number and band number") - } - - stripIndex, bandIndex := func() (int, int) { - return mustConvToInt(args[0]), mustConvToInt(args[1]) - }() - - if len(args) == 2 { - currentGain, err := client.Strip.Eq.Gain(stripIndex, bandIndex) - if err != nil { - return fmt.Errorf("Error getting strip EQ band gain: %w", err) - } - cmd.Printf("Strip %d EQ band %d gain: %.2f dB\n", stripIndex, bandIndex, currentGain) - return nil - } - - if len(args) < 3 { - return fmt.Errorf("Please provide a gain in dB") - } - - gain := mustConvToFloat64(args[2]) - - err := client.Strip.Eq.SetGain(stripIndex, bandIndex, gain) - if err != nil { - return fmt.Errorf("Error setting strip EQ band gain: %w", err) - } - - cmd.Printf("Strip %d EQ band %d gain set to %.2f dB\n", stripIndex, bandIndex, gain) - return nil - }, -} - -// stripEqFreqCmd represents the strip EQ Frequency command. -var stripEqFreqCmd = &cobra.Command{ - Short: "Get or set the EQ band frequency for a strip", - Long: "Get or set the EQ band frequency for a specific strip and band.", - Use: "freq [strip 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 strip number and band number") - } - - stripIndex, bandIndex := func() (int, int) { - return mustConvToInt(args[0]), mustConvToInt(args[1]) - }() - - if len(args) == 2 { - currentFreq, err := client.Strip.Eq.Frequency(stripIndex, bandIndex) - if err != nil { - return fmt.Errorf("Error getting strip EQ band frequency: %w", err) - } - cmd.Printf("Strip %d EQ band %d frequency: %.2f Hz\n", stripIndex, bandIndex, currentFreq) - return nil - } - - if len(args) < 3 { - return fmt.Errorf("Please provide a frequency in Hz") - } - - freq := mustConvToFloat64(args[2]) - - err := client.Strip.Eq.SetFrequency(stripIndex, bandIndex, freq) - if err != nil { - return fmt.Errorf("Error setting strip EQ band frequency: %w", err) - } - - cmd.Printf("Strip %d EQ band %d frequency set to %.2f Hz\n", stripIndex, bandIndex, freq) - return nil - }, -} - -// stripEqQCmd represents the strip EQ Q command. -var stripEqQCmd = &cobra.Command{ - Short: "Get or set the EQ band Q factor for a strip", - Long: "Get or set the EQ band Q factor for a specific strip and band.", - Use: "q [strip 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 strip number and band number") - } - - stripIndex, bandIndex := func() (int, int) { - return mustConvToInt(args[0]), mustConvToInt(args[1]) - }() - - if len(args) == 2 { - currentQ, err := client.Strip.Eq.Q(stripIndex, bandIndex) - if err != nil { - return fmt.Errorf("Error getting strip EQ band Q factor: %w", err) - } - cmd.Printf("Strip %d EQ band %d Q factor: %.2f\n", stripIndex, bandIndex, currentQ) - return nil - } - - if len(args) < 3 { - return fmt.Errorf("Please provide a Q factor") - } - - q := mustConvToFloat64(args[2]) - - err := client.Strip.Eq.SetQ(stripIndex, bandIndex, q) - if err != nil { - return fmt.Errorf("Error setting strip EQ band Q factor: %w", err) - } - - cmd.Printf("Strip %d EQ band %d Q factor set to %.2f\n", stripIndex, bandIndex, q) - return nil - }, -} - -// stripEqTypeCmd represents the strip EQ Type command. -var stripEqTypeCmd = &cobra.Command{ - Short: "Get or set the EQ band type for a strip", - Long: "Get or set the EQ band type for a specific strip and band.", - Use: "type [strip 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 strip number and band number") - } - - stripIndex, 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.Strip.Eq.Type(stripIndex, bandIndex) - if err != nil { - return fmt.Errorf("Error getting strip EQ band type: %w", err) - } - cmd.Printf("Strip %d EQ band %d type: %s\n", stripIndex, bandIndex, eqTypeNames[currentType]) - return nil - } - - if len(args) < 3 { - return fmt.Errorf("Please provide a type") - } - - eqType := indexOf(eqTypeNames, args[2]) - if eqType == -1 { - return fmt.Errorf("Invalid EQ band type. Valid types are: %v", eqTypeNames) - } - - err := client.Strip.Eq.SetType(stripIndex, bandIndex, eqType) - if err != nil { - return fmt.Errorf("Error setting strip EQ band type: %w", err) - } - - cmd.Printf("Strip %d EQ band %d type set to %s\n", stripIndex, bandIndex, eqTypeNames[eqType]) - return nil - }, -} - -// stripCompCmd represents the strip Compressor command. -var stripCompCmd = &cobra.Command{ - Short: "Commands to control the Compressor of individual strips.", - Long: `Commands to control the Compressor of individual strips, including turning the Compressor on or off.`, - Use: "comp", - Run: func(cmd *cobra.Command, _ []string) { - cmd.Help() - }, -} - -// stripCompOnCmd represents the strip Compressor on command. -var stripCompOnCmd = &cobra.Command{ - Short: "Get or set the Compressor on/off status of a strip", - Long: `Get or set the Compressor on/off status of a specific strip. - -If no status argument is provided, the current Compressor status is retrieved. -If "true" or "1" is provided as an argument, the Compressor is turned on. -If "false" or "0" is provided, the Compressor is turned off.`, - Use: "on [strip number] [true|false]", - Example: ` # Get the current Compressor status of strip 1 - xair-cli strip comp on 1 - - # Turn on Compressor for strip 1 - xair-cli strip comp on 1 true - # Turn off Compressor for strip 1 - xair-cli strip comp on 1 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) < 1 { - return fmt.Errorf("Please provide a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - on, err := client.Strip.Comp.On(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor on status: %w", err) - } - cmd.Printf("Strip %d Compressor on: %v\n", stripIndex, on) - return nil - } - - var on bool - switch args[1] { - case "true", "1": - on = true - case "false", "0": - on = false - default: - return fmt.Errorf("Invalid Compressor status. Use true/false or 1/0") - } - - err := client.Strip.Comp.SetOn(stripIndex, on) - if err != nil { - return fmt.Errorf("Error setting strip Compressor on status: %w", err) - } - - if on { - cmd.Printf("Strip %d Compressor turned on successfully\n", stripIndex) - } else { - cmd.Printf("Strip %d Compressor turned off successfully\n", stripIndex) - } - return nil - }, -} - -// stripCompModeCmd represents the strip Compressor Mode command. -var stripCompModeCmd = &cobra.Command{ - Short: "Get or set the Compressor mode for a strip", - Long: "Get or set the Compressor mode for a specific strip.", - Use: "mode [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentMode, err := client.Strip.Comp.Mode(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor mode: %w", err) - } - cmd.Printf("Strip %d Compressor mode: %s\n", stripIndex, currentMode) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a mode") - } - - mode := args[1] - if !contains([]string{"comp", "exp"}, mode) { - return fmt.Errorf("Invalid mode value. Valid values are: comp, exp") - } - - err := client.Strip.Comp.SetMode(stripIndex, mode) - if err != nil { - return fmt.Errorf("Error setting strip Compressor mode: %w", err) - } - - cmd.Printf("Strip %d Compressor mode set to %s\n", stripIndex, mode) - return nil - }, -} - -// stripCompThresholdCmd represents the strip Compressor Threshold command. -var stripCompThresholdCmd = &cobra.Command{ - Short: "Get or set the Compressor threshold for a strip", - Long: "Get or set the Compressor threshold for a specific strip.", - Use: "threshold [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentThreshold, err := client.Strip.Comp.Threshold(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor threshold: %w", err) - } - cmd.Printf("Strip %d Compressor threshold: %.2f dB\n", stripIndex, currentThreshold) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a threshold in dB") - } - - threshold := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetThreshold(stripIndex, threshold) - if err != nil { - return fmt.Errorf("Error setting strip Compressor threshold: %w", err) - } - - cmd.Printf("Strip %d Compressor threshold set to %.2f dB\n", stripIndex, threshold) - return nil - }, -} - -// stripCompRatioCmd represents the strip Compressor Ratio command. -var stripCompRatioCmd = &cobra.Command{ - Short: "Get or set the Compressor ratio for a strip", - Long: "Get or set the Compressor ratio for a specific strip.", - Use: "ratio [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentRatio, err := client.Strip.Comp.Ratio(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor ratio: %w", err) - } - cmd.Printf("Strip %d Compressor ratio: %.2f\n", stripIndex, currentRatio) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a ratio") - } - - ratio := mustConvToFloat64(args[1]) - possibleValues := []float64{1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100} - if !contains(possibleValues, ratio) { - return fmt.Errorf("Invalid ratio value. Valid values are: %v", possibleValues) - } - - err := client.Strip.Comp.SetRatio(stripIndex, ratio) - if err != nil { - return fmt.Errorf("Error setting strip Compressor ratio: %w", err) - } - - cmd.Printf("Strip %d Compressor ratio set to %.2f\n", stripIndex, ratio) - return nil - }, -} - -// stripCompMixCmd represents the strip Compressor Mix command. -var stripCompMixCmd = &cobra.Command{ - Short: "Get or set the Compressor mix for a strip", - Long: "Get or set the Compressor mix for a specific strip.", - Use: "mix [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentMix, err := client.Strip.Comp.Mix(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor mix: %w", err) - } - cmd.Printf("Strip %d Compressor mix: %.2f%%\n", stripIndex, currentMix) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a mix percentage") - } - - mix := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetMix(stripIndex, mix) - if err != nil { - return fmt.Errorf("Error setting strip Compressor mix: %w", err) - } - - cmd.Printf("Strip %d Compressor mix set to %.2f%%\n", stripIndex, mix) - return nil - }, -} - -// stripCompMakeUpCmd represents the strip Compressor Make-Up Gain command. -var stripCompMakeUpCmd = &cobra.Command{ - Short: "Get or set the Compressor make-up gain for a strip", - Long: "Get or set the Compressor make-up gain for a specific strip.", - Use: "makeup [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentMakeUp, err := client.Strip.Comp.MakeUp(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor make-up gain: %w", err) - } - cmd.Printf("Strip %d Compressor make-up gain: %.2f dB\n", stripIndex, currentMakeUp) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a make-up gain in dB") - } - - makeUp := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetMakeUp(stripIndex, makeUp) - if err != nil { - return fmt.Errorf("Error setting strip Compressor make-up gain: %w", err) - } - - cmd.Printf("Strip %d Compressor make-up gain set to %.2f dB\n", stripIndex, makeUp) - return nil - }, -} - -// stripCompAttackCmd represents the strip Compressor Attack command. -var stripCompAttackCmd = &cobra.Command{ - Short: "Get or set the Compressor attack time for a strip", - Long: "Get or set the Compressor attack time for a specific strip.", - Use: "attack [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentAttack, err := client.Strip.Comp.Attack(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor attack time: %w", err) - } - cmd.Printf("Strip %d Compressor attack time: %.2f ms\n", stripIndex, currentAttack) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide an attack time in ms") - } - - attack := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetAttack(stripIndex, attack) - if err != nil { - return fmt.Errorf("Error setting strip Compressor attack time: %w", err) - } - - cmd.Printf("Strip %d Compressor attack time set to %.2f ms\n", stripIndex, attack) - return nil - }, -} - -// stripCompHoldCmd represents the strip Compressor Hold command. -var stripCompHoldCmd = &cobra.Command{ - Short: "Get or set the Compressor hold time for a strip", - Long: "Get or set the Compressor hold time for a specific strip.", - Use: "hold [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentHold, err := client.Strip.Comp.Hold(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor hold time: %w", err) - } - cmd.Printf("Strip %d Compressor hold time: %.2f ms\n", stripIndex, currentHold) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a hold time in ms") - } - - hold := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetHold(stripIndex, hold) - if err != nil { - return fmt.Errorf("Error setting strip Compressor hold time: %w", err) - } - - cmd.Printf("Strip %d Compressor hold time set to %.2f ms\n", stripIndex, hold) - return nil - }, -} - -// stripCompReleaseCmd represents the strip Compressor Release command. -var stripCompReleaseCmd = &cobra.Command{ - Short: "Get or set the Compressor release time for a strip", - Long: "Get or set the Compressor release time for a specific strip.", - Use: "release [strip 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 a strip number") - } - - stripIndex := mustConvToInt(args[0]) - - if len(args) == 1 { - currentRelease, err := client.Strip.Comp.Release(stripIndex) - if err != nil { - return fmt.Errorf("Error getting strip Compressor release time: %w", err) - } - cmd.Printf("Strip %d Compressor release time: %.2f ms\n", stripIndex, currentRelease) - return nil - } - - if len(args) < 2 { - return fmt.Errorf("Please provide a release time in ms") - } - - release := mustConvToFloat64(args[1]) - - err := client.Strip.Comp.SetRelease(stripIndex, release) - if err != nil { - return fmt.Errorf("Error setting strip Compressor release time: %w", err) - } - - cmd.Printf("Strip %d Compressor release time set to %.2f ms\n", stripIndex, release) - return nil - }, -} - -func init() { - rootCmd.AddCommand(stripCmd) - - stripCmd.AddCommand(stripMuteCmd) - stripCmd.AddCommand(stripFaderCmd) - stripCmd.AddCommand(stripFadeOutCmd) - stripFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration of the fade out in seconds") - stripCmd.AddCommand(stripFadeInCmd) - stripFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration of the fade in in seconds") - stripCmd.AddCommand(stripSendCmd) - stripCmd.AddCommand(stripNameCmd) - - stripCmd.AddCommand(stripGateCmd) - stripGateCmd.AddCommand(stripGateOnCmd) - stripGateCmd.AddCommand(stripGateModeCmd) - stripGateCmd.AddCommand(stripGateThresholdCmd) - stripGateCmd.AddCommand(stripGateRangeCmd) - stripGateCmd.AddCommand(stripGateAttackCmd) - stripGateCmd.AddCommand(stripGateHoldCmd) - stripGateCmd.AddCommand(stripGateReleaseCmd) - - stripCmd.AddCommand(stripEqCmd) - stripEqCmd.AddCommand(stripEqOnCmd) - stripEqCmd.AddCommand(stripEqGainCmd) - stripEqCmd.AddCommand(stripEqFreqCmd) - stripEqCmd.AddCommand(stripEqQCmd) - stripEqCmd.AddCommand(stripEqTypeCmd) - - stripCmd.AddCommand(stripCompCmd) - stripCompCmd.AddCommand(stripCompOnCmd) - stripCompCmd.AddCommand(stripCompModeCmd) - stripCompCmd.AddCommand(stripCompThresholdCmd) - stripCompCmd.AddCommand(stripCompRatioCmd) - stripCompCmd.AddCommand(stripCompMixCmd) - stripCompCmd.AddCommand(stripCompMakeUpCmd) - stripCompCmd.AddCommand(stripCompAttackCmd) - stripCompCmd.AddCommand(stripCompHoldCmd) - stripCompCmd.AddCommand(stripCompReleaseCmd) -} diff --git a/cmd/util.go b/cmd/util.go deleted file mode 100644 index 39c5b40..0000000 --- a/cmd/util.go +++ /dev/null @@ -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 -} diff --git a/go.mod b/go.mod index d679888..5fdc5cc 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/onyx-and-iris/xair-cli -go 1.24.2 +go 1.25 require ( + github.com/alecthomas/kong v1.13.0 github.com/charmbracelet/log v0.4.2 github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 - github.com/spf13/cobra v1.10.2 - github.com/spf13/viper v1.21.0 + github.com/jotaen/kong-completion v0.0.11 ) require ( @@ -19,25 +19,17 @@ require ( github.com/clipperhouse/displaywidth v0.9.0 // indirect github.com/clipperhouse/stringish v0.1.1 // 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-viper/mapstructure/v2 v2.5.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/hashicorp/errwrap 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/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // 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/sagikazarmark/locafero v0.12.0 // 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/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // 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-20260112195511-716be5621a96 // 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 ) diff --git a/go.sum b/go.sum index abc2c5f..323dff8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +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.13.0 h1:5e/7XC3ugvhP1DQBmTS+WuHtCbcv44hsohMgcvVxSrA= +github.com/alecthomas/kong v1.13.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/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= @@ -18,27 +24,25 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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/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/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/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -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/jotaen/kong-completion v0.0.11 h1:ZRyQt+IwjcAObbiyxJZ3YR7r/o/f6HYidTK1+7YNtnE= +github.com/jotaen/kong-completion v0.0.11/go.mod h1:dyIG20e3qq128SUBtF8jzI7YtkfzjWMlgbqkAJd6xHQ= 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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -47,45 +51,26 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 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/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/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/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -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/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY= +github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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/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/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.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 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/headamp.go b/headamp.go new file mode 100644 index 0000000..fced692 --- /dev/null +++ b/headamp.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "time" + + "github.com/charmbracelet/log" +) + +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."` +} + +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:""` +} + +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 +} + +type HeadampPhantomCmd struct { + State *string `help:"The phantom power state of the headamp." arg:"" enum:"true,on,false,off" optional:""` +} + +func (cmd *HeadampPhantomCmd) Validate() error { + if cmd.State != nil { + switch *cmd.State { + case "true", "on": + *cmd.State = "true" + case "false", "off": + *cmd.State = "false" + default: + return fmt.Errorf("invalid phantom power state: %s", *cmd.State) + } + } + return nil +} + +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 == "true"); 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 +} diff --git a/internal/xair/client.go b/internal/xair/client.go index 1df86fb..7ad3a2f 100644 --- a/internal/xair/client.go +++ b/internal/xair/client.go @@ -74,8 +74,8 @@ func (c *Client) StartListening() { log.Debugf("Started listening on %s...", c.engine.conn.LocalAddr().String()) } -// Stop stops the client and closes the connection -func (c *Client) Stop() { +// Close stops the client and closes the connection +func (c *Client) Close() { close(c.engine.done) if c.engine.conn != nil { c.engine.conn.Close() @@ -102,10 +102,10 @@ func (c *Client) ReceiveMessage(timeout time.Duration) (*osc.Message, error) { } // RequestInfo requests mixer information -func (c *Client) RequestInfo() (error, InfoResponse) { +func (c *Client) RequestInfo() (InfoResponse, error) { err := c.SendMessage("/xinfo") if err != nil { - return err, InfoResponse{} + return InfoResponse{}, err } val := <-c.respChan @@ -115,7 +115,7 @@ func (c *Client) RequestInfo() (error, InfoResponse) { info.Name = val.Arguments[1].(string) info.Model = val.Arguments[2].(string) } - return nil, info + return info, nil } // KeepAlive sends keep-alive message (required for multi-client usage) diff --git a/internal/xair/comp.go b/internal/xair/comp.go index 94c59c9..bbc84ae 100644 --- a/internal/xair/comp.go +++ b/internal/xair/comp.go @@ -189,8 +189,8 @@ func (c *Comp) SetRelease(index int, release float64) error { 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). -func (c *Comp) MakeUp(index int) (float64, error) { +// 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" err := c.client.SendMessage(address) if err != nil { @@ -200,15 +200,15 @@ func (c *Comp) MakeUp(index int) (float64, error) { resp := <-c.client.respChan val, ok := resp.Arguments[0].(float32) 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 } -// SetMakeUp sets the make-up gain of the Compressor for a specific strip or bus (1-based indexing). -func (c *Comp) SetMakeUp(index int, makeUp 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" - 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). diff --git a/internal/xair/eq.go b/internal/xair/eq.go index e2b9f57..a5b826e 100644 --- a/internal/xair/eq.go +++ b/internal/xair/eq.go @@ -49,24 +49,27 @@ func (e *Eq) SetOn(index int, on bool) error { return e.client.SendMessage(address, value) } -func (e *Eq) Mode(index int) (int, error) { +func (e *Eq) Mode(index int) (string, error) { address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" err := e.client.SendMessage(address) if err != nil { - return 0, err + return "", err } + possibleModes := []string{"peq", "geq", "teq"} + resp := <-e.client.respChan val, ok := resp.Arguments[0].(int32) if !ok { - return 0, fmt.Errorf("unexpected argument type for EQ mode value") + return "", fmt.Errorf("unexpected argument type for EQ mode value") } - return int(val), nil + return possibleModes[val], nil } -func (e *Eq) SetMode(index int, mode int) error { +func (e *Eq) SetMode(index int, mode string) error { address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" - return e.client.SendMessage(address, int32(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). @@ -136,23 +139,26 @@ func (e *Eq) SetQ(index int, band int, q float64) error { } // 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 int, band int) (string, error) { address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band) err := e.client.SendMessage(address) if err != nil { - return 0, err + return "", err } + possibleTypes := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"} + resp := <-e.client.respChan val, ok := resp.Arguments[0].(int32) if !ok { - return 0, fmt.Errorf("unexpected argument type for EQ type value") + return "", fmt.Errorf("unexpected argument type for EQ type value") } - return int(val), nil + return possibleTypes[val], nil } // 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 int, band int, eqType string) error { address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%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))) } diff --git a/lr.go b/lr.go new file mode 100644 index 0000000..ae8e905 --- /dev/null +++ b/lr.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "time" +) + +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:"Get or set the fade-in time of the Main L/R output." cmd:""` + Fadeout MainFadeoutCmd `help:"Get or set the fade-out time of the Main L/R output." cmd:""` +} + +type MainMuteCmd struct { + Mute *bool `arg:"" help:"The mute state to set. If not provided, the current state will be printed."` +} + +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 MainFaderCmd struct { + Level *float64 `arg:"" help:"The fader level to set. If not provided, the current level will be printed."` +} + +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 +} + +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:""` +} + +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 +} + +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:""` +} + +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 65fca96..f454a72 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,113 @@ package main -import "github.com/onyx-and-iris/xair-cli/cmd" +import ( + "fmt" + "io" + "os" + "runtime/debug" + "strings" + + "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"` + Port int `default:"10024" help:"The port of the X-Air device." env:"XAIR_CLI_PORT"` + Kind string `default:"xr18" help:"The kind of the X-Air device." env:"XAIR_CLI_KIND"` +} + +type CLI struct { + Config `embed:"" prefix:"" help:"The configuration for the CLI."` + + Version VersionFlag `help:"Print gobs-cli version information and quit" name:"version" short:"v"` + + Completion kongcompletion.Completion `help:"Generate shell completion scripts." cmd:"" aliases:"c"` + + Raw RawCmdGroup `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"` +} func main() { - cmd.Execute() + 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 { + 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() +} + +func connect(config Config) (*xair.Client, error) { + client, err := xair.NewClient(config.Host, config.Port, xair.WithKind(config.Kind)) + if err != nil { + return nil, err + } + + return client, nil } diff --git a/raw.go b/raw.go new file mode 100644 index 0000000..6335f05 --- /dev/null +++ b/raw.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "time" +) + +type RawCmdGroup struct { + Send RawSendCmd `help:"Send a raw OSC message to the mixer." cmd:""` +} + +type RawSendCmd struct { + Timeout time.Duration `help:"Timeout for the OSC message send operation." default:"200ms" short:"t"` + 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:""` +} + +func (cmd *RawSendCmd) 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) + } + + msg, err := ctx.Client.ReceiveMessage(cmd.Timeout) + if err != nil { + return fmt.Errorf("failed to receive response for raw OSC message: %w", err) + } + fmt.Fprintf(ctx.Out, "Received response: %s with args: %v\n", msg.Address, msg.Arguments) + + return nil +} diff --git a/strip.go b/strip.go new file mode 100644 index 0000000..5e2a2a1 --- /dev/null +++ b/strip.go @@ -0,0 +1,663 @@ +package main + +import ( + "fmt" + "time" +) + +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:"The index of the 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"` +} + +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 == "true"); 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 +} + +type StripFaderCmd struct { + Level *float64 `arg:"" help:"The fader level to set (in dB)." optional:""` +} + +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 +} + +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:""` +} + +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 +} + +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:""` +} + +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 + } +} + +type StripSendCmd struct { + BusNum int `arg:"" help:"The bus number to get or set the send level for."` + Level *string `arg:"" help:"The send level to set (in dB)." optional:""` +} + +func (cmd *StripSendCmd) Run(ctx *context, strip *StripCmdGroup) error { + if cmd.Level == nil { + resp, err := ctx.Client.Strip.SendLevel(strip.Index.Index, cmd.BusNum) + if err != nil { + return fmt.Errorf("failed to get send level: %w", err) + } + fmt.Fprintf(ctx.Out, "Strip %d send level for bus %d: %.2f dB\n", strip.Index.Index, cmd.BusNum, resp) + return nil + } + + level := mustConvToFloat64(*cmd.Level) + if err := ctx.Client.Strip.SetSendLevel(strip.Index.Index, cmd.BusNum, level); err != nil { + return fmt.Errorf("failed to set send level: %w", err) + } + fmt.Fprintf(ctx.Out, "Strip %d send level for bus %d set to: %.2f dB\n", strip.Index.Index, cmd.BusNum, level) + return nil +} + +type StripNameCmd struct { + Name *string `arg:"" help:"The name to set for the strip." optional:""` +} + +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 +} + +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 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:""` +} + +type StripGateOnCmd struct { + Enable *string `arg:"" help:"Whether to enable or disable the gate." optional:"" enum:"true,false"` +} + +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 == "true"); 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 +} + +type StripGateModeCmd struct { + Mode *string `arg:"" help:"The gate mode to set." optional:"" enum:"exp2,exp3,exp4,gate,duck"` +} + +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 +} + +type StripGateThresholdCmd struct { + Threshold *float64 `arg:"" help:"The gate threshold to set (in dB)." optional:""` +} + +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 +} + +type StripGateRangeCmd struct { + Range *float64 `arg:"" help:"The gate range to set (in dB)." optional:""` +} + +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 +} + +type StripGateAttackCmd struct { + Attack *float64 `arg:"" help:"The gate attack time to set (in ms)." optional:""` +} + +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 +} + +type StripGateHoldCmd struct { + Hold *float64 `arg:"" help:"The gate hold time to set (in ms)." optional:""` +} + +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 +} + +type StripGateReleaseCmd struct { + Release *float64 `arg:"" help:"The gate release time to set (in ms)." optional:""` +} + +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 +} + +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."` + 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:""` +} + +func (cmd *StripEqCmdGroup) Validate() error { + if cmd.Band.Band < 1 || cmd.Band.Band > 4 { + return fmt.Errorf("EQ band number must be between 1 and 4") + } + return nil +} + +type StripEqOnCmd struct { + Enable *string `arg:"" help:"Whether to enable or disable the EQ." optional:"" enum:"true,false"` +} + +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 == "true"); 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 +} + +type StripEqBandGainCmd struct { + Gain *float64 `arg:"" help:"The gain to set for the EQ band (in dB)." optional:""` +} + +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 +} + +type StripEqBandFreqCmd struct { + Freq *float64 `arg:"" help:"The frequency to set for the EQ band (in Hz)." optional:""` +} + +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 +} + +type StripEqBandQCmd struct { + Q *float64 `arg:"" help:"The Q factor to set for the EQ band." optional:""` +} + +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 +} + +type StripEqBandTypeCmd struct { + Type *string `arg:"" help:"The type to set for the EQ band." optional:"" enum:"lcut,lshv,peq,veq,hshv,hcut"` +} + +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 +} + +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:""` +} + +type StripCompOnCmd struct { + Enable *string `arg:"" help:"Whether to enable or disable the compressor." optional:"" enum:"true,false"` +} + +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 == "true"); 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 +} + +type StripCompModeCmd struct { + Mode *string `arg:"" help:"The compressor mode to set." optional:"" enum:"comp,exp"` +} + +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 +} + +type StripCompThresholdCmd struct { + Threshold *float64 `arg:"" help:"The compressor threshold to set (in dB)." optional:""` +} + +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 +} + +type StripCompRatioCmd struct { + Ratio *float64 `arg:"" help:"The compressor ratio to set." optional:""` +} + +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 +} + +type StripCompMixCmd struct { + Mix *float64 `arg:"" help:"The compressor mix to set (0-100%)." optional:""` +} + +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 +} + +type StripCompMakeupCmd struct { + Makeup *float64 `arg:"" help:"The compressor makeup gain to set (in dB)." optional:""` +} + +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 +} + +type StripCompAttackCmd struct { + Attack *float64 `arg:"" help:"The compressor attack time to set (in ms)." optional:""` +} + +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 +} + +type StripCompHoldCmd struct { + Hold *float64 `arg:"" help:"The compressor hold time to set (in ms)." optional:""` +} + +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 +} + +type StripCompReleaseCmd struct { + Release *float64 `arg:"" help:"The compressor release time to set (in ms)." optional:""` +} + +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 +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..8cdfede --- /dev/null +++ b/util.go @@ -0,0 +1,12 @@ +package main + +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 +}