11 Commits

12 changed files with 593 additions and 198 deletions

View File

@@ -1,4 +1,10 @@
# Xair-CLI
# xair-cli
### Installation
```console
go install github.com/onyx-and-iris/xair-cli@latest
```
### Use
@@ -14,6 +20,7 @@ Usage:
Available Commands:
bus Commands to control individual buses
completion Generate the autocompletion script for the specified shell
headamp Commands to control headamp gain and phantom power
help Help about any command
main Commands to control the main output
strip Commands to control individual strips
@@ -24,6 +31,42 @@ Flags:
-k, --kind string Kind of mixer (xair, x32) (default "xair")
-l, --loglevel string Log level (debug, info, warn, error, fatal, panic) (default "warn")
-p, --port int Port number of the X Air mixer (default 10024)
-v, --version version for xair-cli
Use "xair-cli [command] --help" for more information about a command.
```
### Examples
*Fade out main LR all the way to -∞*
```console
xair-cli main fadeout
```
*enable phantom power and set the gain to 28.0dB over a 10s duration for strip 09*
```console
xair-cli headamp 9 phantom on
xair-cli headamp 9 gain 28.0 --duration 10s
```
*set strip 09 send level for bus 5 to -18.0dB*
```console
xair-cli strip send 9 5 -- -18.0
```
*rename bus 01 to 'vocal mix'*
```console
xair-cli bus 1 name 'vocal mix'
```
### Notes
I've only implemented the parts I personally need, I don't know how much more I intend to add.
### License
`xair-cli` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.

View File

@@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
@@ -21,16 +22,14 @@ 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]",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
cmd.PrintErrln("Please provide bus number and mute status (true/false)")
return
return fmt.Errorf("Please provide bus number and mute status (true/false)")
}
busNum := mustConvToInt(args[0])
@@ -41,16 +40,16 @@ var busMuteCmd = &cobra.Command{
case "false", "0":
muted = false
default:
cmd.PrintErrln("Invalid mute status. Use true/false or 1/0")
return
return fmt.Errorf("Invalid mute status. Use true/false or 1/0")
}
err := client.Bus.SetMute(busNum, muted)
if err != nil {
cmd.PrintErrln("Error setting bus mute status:", err)
return
return fmt.Errorf("Error setting bus mute status: %w", err)
}
cmd.Printf("Bus %d mute set to %v\n", busNum, muted)
return nil
},
}
@@ -66,11 +65,10 @@ If a level argument (in dB) is provided, the bus fader is set to that level.`,
# Set the fader level of bus 1 to -10.0 dB
xair-cli bus fader 1 -10.0`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
busIndex := mustConvToInt(args[0])
@@ -78,26 +76,25 @@ If a level argument (in dB) is provided, the bus fader is set to that level.`,
if len(args) == 1 {
level, err := client.Bus.Fader(busIndex)
if err != nil {
cmd.PrintErrln("Error getting bus fader level:", err)
return
return fmt.Errorf("Error getting bus fader level: %w", err)
}
cmd.Printf("Bus %d fader level: %.1f dB\n", busIndex, level)
return
return nil
}
if len(args) < 2 {
cmd.PrintErrln("Please provide bus number and fader level (in dB)")
return
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 {
cmd.PrintErrln("Error setting bus fader level:", err)
return
return fmt.Errorf("Error setting bus fader level: %w", err)
}
cmd.Printf("Bus %d fader set to %.2f dB\n", busIndex, level)
return nil
},
}
@@ -107,25 +104,22 @@ var busFadeOutCmd = &cobra.Command{
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 5 -- -90.0`,
Run: func(cmd *cobra.Command, args []string) {
xair-cli bus fadeout 1 --duration 5s -- -90.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide bus number")
return
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetFloat64("duration")
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := -90.0
@@ -135,30 +129,29 @@ var busFadeOutCmd = &cobra.Command{
currentFader, err := client.Bus.Fader(busIndex)
if err != nil {
cmd.PrintErrln("Error getting current bus fader level:", err)
return
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
return nil
}
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader > target {
currentFader -= 1.0
err := client.Bus.SetFader(busIndex, currentFader)
if err != nil {
cmd.PrintErrln("Error setting bus fader level:", err)
return
return fmt.Errorf("Error setting bus fader level: %w", err)
}
time.Sleep(stepDelay)
}
cmd.Println("Bus fade out completed")
return nil
},
}
@@ -168,25 +161,22 @@ var busFadeInCmd = &cobra.Command{
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 5 -- 0.0`,
Run: func(cmd *cobra.Command, args []string) {
xair-cli bus fadein 1 --duration 5s -- 0.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide bus number")
return
return fmt.Errorf("Please provide bus number")
}
busIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetFloat64("duration")
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := 0.0
@@ -196,30 +186,71 @@ var busFadeInCmd = &cobra.Command{
currentFader, err := client.Bus.Fader(busIndex)
if err != nil {
cmd.PrintErrln("Error getting current bus fader level:", err)
return
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
return nil
}
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader < target {
currentFader += 1.0
err := client.Bus.SetFader(busIndex, currentFader)
if err != nil {
cmd.PrintErrln("Error setting bus fader level:", err)
return
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
},
}
@@ -230,7 +261,9 @@ func init() {
busCmd.AddCommand(busFaderCmd)
busCmd.AddCommand(busFadeOutCmd)
busFadeOutCmd.Flags().Float64P("duration", "d", 5.0, "Duration for fade out in seconds")
busFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade out in seconds")
busCmd.AddCommand(busFadeInCmd)
busFadeInCmd.Flags().Float64P("duration", "d", 5.0, "Duration for fade in in seconds")
busFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade in in seconds")
busCmd.AddCommand(busNameCmd)
}

211
cmd/headamp.go Normal file
View File

@@ -0,0 +1,211 @@
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,6 +1,7 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
@@ -33,21 +34,19 @@ If "false" or "0" is provided, the main output is unmuted.`,
# Unmute the main output
xair-cli main mute false`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) == 0 {
resp, err := client.Main.Mute()
if err != nil {
cmd.PrintErrln("Error getting main LR mute status:", err)
return
return fmt.Errorf("Error getting main LR mute status: %w", err)
}
cmd.Printf("Main LR mute: %v\n", resp)
return
return nil
}
var muted bool
@@ -57,16 +56,16 @@ If "false" or "0" is provided, the main output is unmuted.`,
case "false", "0":
muted = false
default:
cmd.PrintErrln("Invalid mute status. Use true/false or 1/0")
return
return fmt.Errorf("Invalid mute status. Use true/false or 1/0")
}
err := client.Main.SetMute(muted)
if err != nil {
cmd.PrintErrln("Error setting main LR mute status:", err)
return
return fmt.Errorf("Error setting main LR mute status: %w", err)
}
cmd.Println("Main LR mute status set successfully")
return nil
},
}
@@ -83,29 +82,28 @@ If a dB value is provided as an argument, the fader level is set to that value.`
# Set the main LR fader level to -10.0 dB
xair-cli main fader -- -10.0`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) == 0 {
resp, err := client.Main.Fader()
if err != nil {
cmd.PrintErrln("Error getting main LR fader:", err)
return
return fmt.Errorf("Error getting main LR fader: %w", err)
}
cmd.Printf("Main LR fader: %.1f dB\n", resp)
return
return nil
}
err := client.Main.SetFader(mustConvToFloat64(args[0]))
if err != nil {
cmd.PrintErrln("Error setting main LR fader:", err)
return
return fmt.Errorf("Error setting main LR fader: %w", err)
}
cmd.Println("Main LR fader set successfully")
return nil
},
}
@@ -118,7 +116,7 @@ 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 5 -- -90.0`,
xair-cli main fadeout --duration 5s -- -90.0`,
Run: func(cmd *cobra.Command, args []string) {
client := ClientFromContext(cmd.Context())
if client == nil {
@@ -126,7 +124,7 @@ This command will fade out the main output to the specified dB level.
return
}
duration, err := cmd.Flags().GetFloat64("duration")
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
@@ -152,7 +150,7 @@ This command will fade out the main output to the specified dB level.
}
// Calculate delay per step to achieve exact duration
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader > target {
currentFader -= 1.0
@@ -163,6 +161,7 @@ This command will fade out the main output to the specified dB level.
}
time.Sleep(stepDelay)
}
cmd.Println("Main output faded out successfully")
},
}
@@ -176,7 +175,7 @@ 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 5 -- 0.0`,
xair-cli main fadein --duration 5s -- 0.0`,
Run: func(cmd *cobra.Command, args []string) {
client := ClientFromContext(cmd.Context())
if client == nil {
@@ -184,7 +183,7 @@ This command will fade in the main output to the specified dB level.
return
}
duration, err := cmd.Flags().GetFloat64("duration")
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
@@ -209,7 +208,7 @@ This command will fade in the main output to the specified dB level.
}
// Calculate delay per step to achieve exact duration
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader < target {
currentFader += 1.0
@@ -220,6 +219,7 @@ This command will fade in the main output to the specified dB level.
}
time.Sleep(stepDelay)
}
cmd.Println("Main output faded in successfully")
},
}
@@ -231,7 +231,7 @@ func init() {
mainCmd.AddCommand(mainFaderCmd)
mainCmd.AddCommand(mainFadeOutCmd)
mainFadeOutCmd.Flags().Float64P("duration", "d", 5, "Duration for fade out in seconds")
mainFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade out in seconds")
mainCmd.AddCommand(mainFadeInCmd)
mainFadeInCmd.Flags().Float64P("duration", "d", 5, "Duration for fade in in seconds")
mainFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration for fade in in seconds")
}

View File

@@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
@@ -32,16 +33,14 @@ If "false" or "0" is provided, the strip is unmuted.`,
xair-cli strip mute 1 true
# Unmute strip 1
xair-cli strip mute 1 false`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide a strip number")
return
return fmt.Errorf("Please provide a strip number")
}
stripIndex := mustConvToInt(args[0])
@@ -49,11 +48,10 @@ If "false" or "0" is provided, the strip is unmuted.`,
if len(args) == 1 {
resp, err := client.Strip.Mute(stripIndex)
if err != nil {
cmd.PrintErrln("Error getting strip mute status:", err)
return
return fmt.Errorf("Error getting strip mute status: %w", err)
}
cmd.Printf("Strip %d mute: %v\n", stripIndex, resp)
return
return nil
}
var muted bool
@@ -63,20 +61,20 @@ If "false" or "0" is provided, the strip is unmuted.`,
case "false", "0":
muted = false
default:
cmd.PrintErrln("Invalid mute status. Use true/false or 1/0")
return
return fmt.Errorf("Invalid mute status. Use true/false or 1/0")
}
err := client.Strip.SetMute(stripIndex, muted)
if err != nil {
cmd.PrintErrln("Error setting strip mute status:", err)
return
return fmt.Errorf("Error setting strip mute status: %w", err)
}
if muted {
cmd.Printf("Strip %d muted successfully\n", stripIndex)
} else {
cmd.Printf("Strip %d unmuted successfully\n", stripIndex)
}
return nil
},
}
@@ -93,16 +91,14 @@ If a level argument (in dB) is provided, the strip fader is set to that level.`,
# Set the fader level of strip 1 to -10.0 dB
xair-cli strip fader 1 -10.0`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide a strip number")
return
return fmt.Errorf("Please provide a strip number")
}
stripIndex := mustConvToInt(args[0])
@@ -110,26 +106,25 @@ If a level argument (in dB) is provided, the strip fader is set to that level.`,
if len(args) == 1 {
level, err := client.Strip.Fader(stripIndex)
if err != nil {
cmd.PrintErrln("Error getting strip fader level:", err)
return
return fmt.Errorf("Error getting strip fader level: %w", err)
}
cmd.Printf("Strip %d fader level: %.2f\n", stripIndex, level)
return
return nil
}
if len(args) < 2 {
cmd.PrintErrln("Please provide a fader level in dB")
return
return fmt.Errorf("Please provide a fader level in dB")
}
level := mustConvToFloat64(args[1])
err := client.Strip.SetFader(stripIndex, level)
if err != nil {
cmd.PrintErrln("Error setting strip fader level:", err)
return
return fmt.Errorf("Error setting strip fader level: %w", err)
}
cmd.Printf("Strip %d fader set to %.2f dB\n", stripIndex, level)
return nil
},
}
@@ -139,25 +134,22 @@ var stripFadeOutCmd = &cobra.Command{
Long: "Fade out the strip over a specified duration in seconds.",
Use: "fadeout [strip number] --duration [seconds] [target level in dB]",
Example: ` # Fade out strip 1 over 5 seconds
xair-cli strip fadeout 1 --duration 5.0 -- -90.0`,
Run: func(cmd *cobra.Command, args []string) {
xair-cli strip fadeout 1 --duration 5s -- -90.0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide strip number")
return
return fmt.Errorf("Please provide strip number")
}
stripIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetFloat64("duration")
duration, err := cmd.Flags().GetDuration("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := -90.0
@@ -167,29 +159,28 @@ var stripFadeOutCmd = &cobra.Command{
currentFader, err := client.Strip.Fader(stripIndex)
if err != nil {
cmd.PrintErrln("Error getting current strip fader level:", err)
return
return fmt.Errorf("Error getting current strip fader level: %w", err)
}
totalSteps := float64(currentFader - target)
if totalSteps <= 0 {
cmd.Println("Strip is already at or below target level")
return
return nil
}
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
stepDelay := time.Duration(duration.Seconds()*1000/totalSteps) * time.Millisecond
for currentFader > target {
currentFader -= 1.0
err := client.Strip.SetFader(stripIndex, currentFader)
if err != nil {
cmd.PrintErrln("Error setting strip fader level:", err)
return
return fmt.Errorf("Error setting strip fader level: %w", err)
}
time.Sleep(stepDelay)
}
cmd.Printf("Strip %d faded out to %.2f dB over %.2f seconds\n", stripIndex, target, duration)
cmd.Printf("Strip %d faded out to %.2f dB over %.2f seconds\n", stripIndex, target, duration.Seconds())
return nil
},
}
@@ -199,25 +190,22 @@ var stripFadeInCmd = &cobra.Command{
Long: "Fade in the strip over a specified duration in seconds.",
Use: "fadein [strip number] --duration [seconds] [target level in dB]",
Example: ` # Fade in strip 1 over 5 seconds
xair-cli strip fadein 1 --duration 5.0 0`,
Run: func(cmd *cobra.Command, args []string) {
xair-cli strip fadein 1 --duration 5s 0`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
cmd.PrintErrln("Please provide strip number")
return
return fmt.Errorf("Please provide strip number")
}
stripIndex := mustConvToInt(args[0])
duration, err := cmd.Flags().GetFloat64("duration")
if err != nil {
cmd.PrintErrln("Error getting duration flag:", err)
return
return fmt.Errorf("Error getting duration flag: %w", err)
}
target := 0.0
@@ -227,14 +215,13 @@ var stripFadeInCmd = &cobra.Command{
currentFader, err := client.Strip.Fader(stripIndex)
if err != nil {
cmd.PrintErrln("Error getting current strip fader level:", err)
return
return fmt.Errorf("Error getting current strip fader level: %w", err)
}
totalSteps := float64(target - currentFader)
if totalSteps <= 0 {
cmd.Println("Strip is already at or above target level")
return
return nil
}
stepDelay := time.Duration(duration*1000/totalSteps) * time.Millisecond
@@ -243,13 +230,13 @@ var stripFadeInCmd = &cobra.Command{
currentFader += 1.0
err := client.Strip.SetFader(stripIndex, currentFader)
if err != nil {
cmd.PrintErrln("Error setting strip fader level:", err)
return
return fmt.Errorf("Error setting strip fader level: %w", err)
}
time.Sleep(stepDelay)
}
cmd.Printf("Strip %d faded in to %.2f dB over %.2f seconds\n", stripIndex, target, duration)
return nil
},
}
@@ -263,16 +250,14 @@ var stripSendCmd = &cobra.Command{
# Set the send level of strip 1 to bus 1 to -5.0 dB
xair-cli strip send 1 1 -- -5.0`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
cmd.PrintErrln("OSC client not found in context")
return
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 2 {
cmd.PrintErrln("Please provide strip number and bus number")
return
return fmt.Errorf("Please provide strip number and bus number")
}
stripIndex, busIndex := func() (int, int) {
@@ -282,26 +267,69 @@ var stripSendCmd = &cobra.Command{
if len(args) == 2 {
currentLevel, err := client.Strip.SendLevel(stripIndex, busIndex)
if err != nil {
cmd.PrintErrln("Error getting strip send level:", err)
return
return fmt.Errorf("Error getting strip send level: %w", err)
}
cmd.Printf("Strip %d send level to bus %d: %.2f dB\n", stripIndex, busIndex, currentLevel)
return
return nil
}
if len(args) < 3 {
cmd.PrintErrln("Please provide a send level in dB")
return
return fmt.Errorf("Please provide a send level in dB")
}
level := mustConvToFloat64(args[2])
err := client.Strip.SetSendLevel(stripIndex, busIndex, level)
if err != nil {
cmd.PrintErrln("Error setting strip send level:", err)
return
return fmt.Errorf("Error setting strip send level: %w", err)
}
cmd.Printf("Strip %d send level to bus %d set to %.2f dB\n", stripIndex, busIndex, level)
return nil
},
}
// stripNameCmd represents the strip name command.
var stripNameCmd = &cobra.Command{
Short: "Get or set the name of a strip",
Long: `Get or set the name of a specific strip.
If no name argument is provided, the current strip name is retrieved.
If a name argument is provided, the strip name is set to that value.`,
Use: "name [strip number] [name]",
Example: ` # Get the current name of strip 1
xair-cli strip name 1
# Set the name of strip 1 to "Guitar"
xair-cli strip name 1 "Guitar"`,
RunE: func(cmd *cobra.Command, args []string) error {
client := ClientFromContext(cmd.Context())
if client == nil {
return fmt.Errorf("OSC client not found in context")
}
if len(args) < 1 {
return fmt.Errorf("Please provide a strip number")
}
stripIndex := mustConvToInt(args[0])
if len(args) == 1 {
name, err := client.Strip.Name(stripIndex)
if err != nil {
return fmt.Errorf("Error getting strip name: %w", err)
}
cmd.Printf("Strip %d name: %s\n", stripIndex, name)
return nil
}
name := args[1]
err := client.Strip.SetName(stripIndex, name)
if err != nil {
return fmt.Errorf("Error setting strip name: %w", err)
}
cmd.Printf("Strip %d name set to: %s\n", stripIndex, name)
return nil
},
}
@@ -312,9 +340,11 @@ func init() {
stripCmd.AddCommand(stripFaderCmd)
stripCmd.AddCommand(stripFadeOutCmd)
stripFadeOutCmd.Flags().Float64P("duration", "d", 5.0, "Duration of the fade out in seconds")
stripFadeOutCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration of the fade out in seconds")
stripCmd.AddCommand(stripFadeInCmd)
stripFadeInCmd.Flags().Float64P("duration", "d", 5.0, "Duration of the fade in in seconds")
stripFadeInCmd.Flags().DurationP("duration", "d", 5*time.Second, "Duration of the fade in in seconds")
stripCmd.AddCommand(stripSendCmd)
stripCmd.AddCommand(stripNameCmd)
}

View File

@@ -1,11 +1,15 @@
package xair
var xairAddressMap = map[string]string{
"bus": "/bus/%01d",
"strip": "/ch/%02d",
"bus": "/bus/%01d",
"headamp": "/headamp/%02d",
}
var x32AddressMap = map[string]string{
"bus": "/bus/%02d",
"strip": "/ch/%02d",
"bus": "/bus/%02d",
"headamp": "/headamp/%02d",
}
func addressMapForMixerKind(kind MixerKind) map[string]string {

View File

@@ -3,19 +3,20 @@ package xair
import "fmt"
type Bus struct {
client Client
baseAddress string
client Client
}
func NewBus(c Client) *Bus {
return &Bus{
client: c,
baseAddress: c.addressMap["bus"],
client: c,
}
}
// Mute requests the current mute status for a bus
func (b *Bus) Mute(bus int) (bool, error) {
formatter := b.client.addressMap["bus"]
address := fmt.Sprintf(formatter, bus) + "/mix/on"
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on"
err := b.client.SendMessage(address)
if err != nil {
return false, err
@@ -31,8 +32,7 @@ func (b *Bus) Mute(bus int) (bool, error) {
// SetMute sets the mute status for a specific bus (1-based indexing)
func (b *Bus) SetMute(bus int, muted bool) error {
formatter := b.client.addressMap["bus"]
address := fmt.Sprintf(formatter, bus) + "/mix/on"
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/on"
var value int32
if !muted {
value = 1
@@ -42,8 +42,7 @@ func (b *Bus) SetMute(bus int, muted bool) error {
// Fader requests the current fader level for a bus
func (b *Bus) Fader(bus int) (float64, error) {
formatter := b.client.addressMap["bus"]
address := fmt.Sprintf(formatter, bus) + "/mix/fader"
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader"
err := b.client.SendMessage(address)
if err != nil {
return 0, err
@@ -60,7 +59,28 @@ func (b *Bus) Fader(bus int) (float64, error) {
// SetFader sets the fader level for a specific bus (1-based indexing)
func (b *Bus) SetFader(bus int, level float64) error {
formatter := b.client.addressMap["bus"]
address := fmt.Sprintf(formatter, bus) + "/mix/fader"
address := fmt.Sprintf(b.baseAddress, bus) + "/mix/fader"
return b.client.SendMessage(address, float32(mustDbInto(level)))
}
// Name requests the name for a specific bus
func (b *Bus) Name(bus int) (string, error) {
address := fmt.Sprintf(b.baseAddress, bus) + "/config/name"
err := b.client.SendMessage(address)
if err != nil {
return "", fmt.Errorf("failed to send bus name request: %v", err)
}
resp := <-b.client.respChan
val, ok := resp.Arguments[0].(string)
if !ok {
return "", fmt.Errorf("unexpected argument type for bus name value")
}
return val, nil
}
// SetName sets the name for a specific bus
func (b *Bus) SetName(bus int, name string) error {
address := fmt.Sprintf(b.baseAddress, bus) + "/config/name"
return b.client.SendMessage(address, name)
}

View File

@@ -15,9 +15,10 @@ type parser interface {
type Client struct {
engine
Main *Main
Strip *Strip
Bus *Bus
Main *Main
Strip *Strip
Bus *Bus
HeadAmp *HeadAmp
}
// NewClient creates a new XAirClient instance
@@ -60,6 +61,7 @@ func NewClient(mixerIP string, mixerPort int, opts ...Option) (*Client, error) {
c.Main = newMain(*c)
c.Strip = NewStrip(*c)
c.Bus = NewBus(*c)
c.HeadAmp = NewHeadAmp(*c)
return c, nil
}

67
internal/xair/headamp.go Normal file
View File

@@ -0,0 +1,67 @@
package xair
import "fmt"
type HeadAmp struct {
baseAddress string
client Client
}
func NewHeadAmp(c Client) *HeadAmp {
return &HeadAmp{
baseAddress: c.addressMap["headamp"],
client: c,
}
}
// Gain gets the gain level for the specified headamp index.
func (h *HeadAmp) Gain(index int) (float64, error) {
address := fmt.Sprintf(h.baseAddress, index) + "/gain"
err := h.client.SendMessage(address)
if err != nil {
return 0, err
}
resp := <-h.client.respChan
val, ok := resp.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for headamp gain value")
}
return linGet(-12, 60, float64(val)), nil
}
// SetGain sets the gain level for the specified headamp index.
func (h *HeadAmp) SetGain(index int, level float64) error {
address := fmt.Sprintf(h.baseAddress, index) + "/gain"
return h.client.SendMessage(address, float32(linSet(-12, 60, level)))
}
// PhantomPower gets the phantom power status for the specified headamp index.
func (h *HeadAmp) PhantomPower(index int) (bool, error) {
address := fmt.Sprintf(h.baseAddress, index) + "/phantom"
err := h.client.SendMessage(address)
if err != nil {
return false, err
}
resp := <-h.client.respChan
val, ok := resp.Arguments[0].(int32)
if !ok {
return false, fmt.Errorf("unexpected argument type for phantom power value")
}
return val != 0, nil
}
// SetPhantomPower sets the phantom power status for the specified headamp index.
func (h *HeadAmp) SetPhantomPower(index int, enabled bool) error {
address := fmt.Sprintf(h.baseAddress, index) + "/phantom"
var val int32
if enabled {
val = 1
} else {
val = 0
}
return h.client.SendMessage(address, val)
}

View File

@@ -3,18 +3,20 @@ package xair
import "fmt"
type Strip struct {
client Client
baseAddress string
client Client
}
func NewStrip(c Client) *Strip {
return &Strip{
client: c,
baseAddress: c.addressMap["strip"],
client: c,
}
}
// Mute gets the mute status of the specified strip (1-based indexing).
func (s *Strip) Mute(strip int) (bool, error) {
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
func (s *Strip) Mute(index int) (bool, error) {
address := fmt.Sprintf(s.baseAddress, index) + "/mix/on"
err := s.client.SendMessage(address)
if err != nil {
return false, err
@@ -30,7 +32,7 @@ func (s *Strip) Mute(strip int) (bool, error) {
// SetMute sets the mute status of the specified strip (1-based indexing).
func (s *Strip) SetMute(strip int, muted bool) error {
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/mix/on"
var value int32 = 0
if !muted {
value = 1
@@ -40,7 +42,7 @@ func (s *Strip) SetMute(strip int, muted bool) error {
// Fader gets the fader level of the specified strip (1-based indexing).
func (s *Strip) Fader(strip int) (float64, error) {
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/mix/fader"
err := s.client.SendMessage(address)
if err != nil {
return 0, err
@@ -57,35 +59,13 @@ func (s *Strip) Fader(strip int) (float64, error) {
// SetFader sets the fader level of the specified strip (1-based indexing).
func (s *Strip) SetFader(strip int, level float64) error {
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/mix/fader"
return s.client.SendMessage(address, float32(mustDbInto(level)))
}
// MicGain requests the phantom gain for a specific strip (1-based indexing).
func (s *Strip) MicGain(strip int) (float64, error) {
address := fmt.Sprintf("/ch/%02d/mix/gain", strip)
err := s.client.SendMessage(address)
if err != nil {
return 0, fmt.Errorf("failed to send strip gain request: %v", err)
}
resp := <-s.client.respChan
val, ok := resp.Arguments[0].(float32)
if !ok {
return 0, fmt.Errorf("unexpected argument type for strip gain value")
}
return mustDbFrom(float64(val)), nil
}
// SetMicGain sets the phantom gain for a specific strip (1-based indexing).
func (s *Strip) SetMicGain(strip int, gain float32) error {
address := fmt.Sprintf("/ch/%02d/mix/gain", strip)
return s.client.SendMessage(address, gain)
}
// Name requests the name for a specific strip
func (s *Strip) Name(strip int) (string, error) {
address := fmt.Sprintf("/ch/%02d/config/name", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/config/name"
err := s.client.SendMessage(address)
if err != nil {
return "", fmt.Errorf("failed to send strip name request: %v", err)
@@ -101,13 +81,13 @@ func (s *Strip) Name(strip int) (string, error) {
// SetName sets the name for a specific strip
func (s *Strip) SetName(strip int, name string) error {
address := fmt.Sprintf("/ch/%02d/config/name", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/config/name"
return s.client.SendMessage(address, name)
}
// Color requests the color for a specific strip
func (s *Strip) Color(strip int) (int32, error) {
address := fmt.Sprintf("/ch/%02d/config/color", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/config/color"
err := s.client.SendMessage(address)
if err != nil {
return 0, fmt.Errorf("failed to send strip color request: %v", err)
@@ -123,13 +103,13 @@ func (s *Strip) Color(strip int) (int32, error) {
// SetColor sets the color for a specific strip (0-15)
func (s *Strip) SetColor(strip int, color int32) error {
address := fmt.Sprintf("/ch/%02d/config/color", strip)
address := fmt.Sprintf(s.baseAddress, strip) + "/config/color"
return s.client.SendMessage(address, color)
}
// Sends requests the sends level for a mixbus.
func (s *Strip) SendLevel(strip int, bus int) (float64, error) {
address := fmt.Sprintf("/ch/%02d/mix/%02d/level", strip, bus)
address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus)
err := s.client.SendMessage(address)
if err != nil {
return 0, fmt.Errorf("failed to send strip send level request: %v", err)
@@ -145,6 +125,6 @@ func (s *Strip) SendLevel(strip int, bus int) (float64, error) {
// SetSendLevel sets the sends level for a mixbus.
func (s *Strip) SetSendLevel(strip int, bus int, level float64) error {
address := fmt.Sprintf("/ch/%02d/mix/%02d/level", strip, bus)
address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus)
return s.client.SendMessage(address, float32(mustDbInto(level)))
}

View File

@@ -2,6 +2,14 @@ package xair
import "math"
func linGet(min float64, max float64, value float64) float64 {
return min + (max-min)*value
}
func linSet(min float64, max float64, value float64) float64 {
return (value - min) / (max - min)
}
func mustDbInto(db float64) float64 {
switch {
case db >= 10:

View File

@@ -1,6 +1,3 @@
/*
LICENSE: https://github.com/onyx-and-iris/xair-cli/blob/main/LICENSE
*/
package main
import "github.com/onyx-and-iris/xair-cli/cmd"