migrate CLI component to Kong

This commit is contained in:
onyx-and-iris 2026-02-05 03:36:22 +00:00
parent 49cf3ff49e
commit 128f0c1df6
20 changed files with 1624 additions and 3040 deletions

503
bus.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

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

View File

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

View File

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

View File

@ -1,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")
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

22
go.mod
View File

@ -1,12 +1,12 @@
module github.com/onyx-and-iris/xair-cli module github.com/onyx-and-iris/xair-cli
go 1.24.2 go 1.25
require ( require (
github.com/alecthomas/kong v1.13.0
github.com/charmbracelet/log v0.4.2 github.com/charmbracelet/log v0.4.2
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5
github.com/spf13/cobra v1.10.2 github.com/jotaen/kong-completion v0.0.11
github.com/spf13/viper v1.21.0
) )
require ( require (
@ -19,25 +19,17 @@ require (
github.com/clipperhouse/displaywidth v0.9.0 // indirect github.com/clipperhouse/displaywidth v0.9.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/posener/complete v1.2.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
) )

63
go.sum
View File

@ -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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= github.com/charmbracelet/colorprofile v0.4.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/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 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ= github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY= github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/jotaen/kong-completion v0.0.11 h1:ZRyQt+IwjcAObbiyxJZ3YR7r/o/f6HYidTK1+7YNtnE=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jotaen/kong-completion v0.0.11/go.mod h1:dyIG20e3qq128SUBtF8jzI7YtkfzjWMlgbqkAJd6xHQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -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/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 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
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 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

124
headamp.go Normal file
View File

@ -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
}

View File

@ -74,8 +74,8 @@ func (c *Client) StartListening() {
log.Debugf("Started listening on %s...", c.engine.conn.LocalAddr().String()) log.Debugf("Started listening on %s...", c.engine.conn.LocalAddr().String())
} }
// Stop stops the client and closes the connection // Close stops the client and closes the connection
func (c *Client) Stop() { func (c *Client) Close() {
close(c.engine.done) close(c.engine.done)
if c.engine.conn != nil { if c.engine.conn != nil {
c.engine.conn.Close() c.engine.conn.Close()
@ -102,10 +102,10 @@ func (c *Client) ReceiveMessage(timeout time.Duration) (*osc.Message, error) {
} }
// RequestInfo requests mixer information // RequestInfo requests mixer information
func (c *Client) RequestInfo() (error, InfoResponse) { func (c *Client) RequestInfo() (InfoResponse, error) {
err := c.SendMessage("/xinfo") err := c.SendMessage("/xinfo")
if err != nil { if err != nil {
return err, InfoResponse{} return InfoResponse{}, err
} }
val := <-c.respChan val := <-c.respChan
@ -115,7 +115,7 @@ func (c *Client) RequestInfo() (error, InfoResponse) {
info.Name = val.Arguments[1].(string) info.Name = val.Arguments[1].(string)
info.Model = val.Arguments[2].(string) info.Model = val.Arguments[2].(string)
} }
return nil, info return info, nil
} }
// KeepAlive sends keep-alive message (required for multi-client usage) // KeepAlive sends keep-alive message (required for multi-client usage)

View File

@ -189,8 +189,8 @@ func (c *Comp) SetRelease(index int, release float64) error {
return c.client.SendMessage(address, float32(logSet(4, 4000, release))) return c.client.SendMessage(address, float32(logSet(4, 4000, release)))
} }
// MakeUp retrieves the make-up gain of the Compressor for a specific strip or bus (1-based indexing). // Makeup retrieves the makeup gain of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) MakeUp(index int) (float64, error) { func (c *Comp) Makeup(index int) (float64, error) {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain"
err := c.client.SendMessage(address) err := c.client.SendMessage(address)
if err != nil { if err != nil {
@ -200,15 +200,15 @@ func (c *Comp) MakeUp(index int) (float64, error) {
resp := <-c.client.respChan resp := <-c.client.respChan
val, ok := resp.Arguments[0].(float32) val, ok := resp.Arguments[0].(float32)
if !ok { if !ok {
return 0, fmt.Errorf("unexpected argument type for Compressor make-up gain value") return 0, fmt.Errorf("unexpected argument type for Compressor makeup gain value")
} }
return linGet(0, 24, float64(val)), nil return linGet(0, 24, float64(val)), nil
} }
// SetMakeUp sets the make-up gain of the Compressor for a specific strip or bus (1-based indexing). // SetMakeup sets the makeup gain of the Compressor for a specific strip or bus (1-based indexing).
func (c *Comp) SetMakeUp(index int, makeUp float64) error { func (c *Comp) SetMakeup(index int, makeup float64) error {
address := fmt.Sprintf(c.baseAddress, index) + "/dyn/mgain" address := 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). // Mix retrieves the mix value of the Compressor for a specific strip or bus (1-based indexing).

View File

@ -49,24 +49,27 @@ func (e *Eq) SetOn(index int, on bool) error {
return e.client.SendMessage(address, value) return e.client.SendMessage(address, value)
} }
func (e *Eq) Mode(index int) (int, error) { func (e *Eq) Mode(index int) (string, error) {
address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode" address := fmt.Sprintf(e.baseAddress, index) + "/eq/mode"
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return "", err
} }
possibleModes := []string{"peq", "geq", "teq"}
resp := <-e.client.respChan resp := <-e.client.respChan
val, ok := resp.Arguments[0].(int32) val, ok := resp.Arguments[0].(int32)
if !ok { 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" 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). // 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). // 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) address := fmt.Sprintf(e.baseAddress, index) + fmt.Sprintf("/eq/%d/type", band)
err := e.client.SendMessage(address) err := e.client.SendMessage(address)
if err != nil { if err != nil {
return 0, err return "", err
} }
possibleTypes := []string{"lcut", "lshv", "peq", "veq", "hshv", "hcut"}
resp := <-e.client.respChan resp := <-e.client.respChan
val, ok := resp.Arguments[0].(int32) val, ok := resp.Arguments[0].(int32)
if !ok { 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). // 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) 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)))
} }

120
lr.go Normal file
View File

@ -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
}

110
main.go
View File

@ -1,7 +1,113 @@
package main 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() { 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
} }

34
raw.go Normal file
View File

@ -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
}

663
strip.go Normal file
View File

@ -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
}

12
util.go Normal file
View File

@ -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
}