migrate CLI component to Kong

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

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
}