From 9898c21197e7a21b6e9c2159cc1919b5c2131477 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 1 Feb 2026 01:25:47 +0000 Subject: [PATCH] add headamp gain and phantom commands --- cmd/headamp.go | 146 +++++++++++++++++++++++++++++++++++++++ internal/xair/address.go | 10 +-- internal/xair/client.go | 8 ++- internal/xair/headamp.go | 67 ++++++++++++++++++ internal/xair/strip.go | 48 ++++++------- internal/xair/util.go | 8 +++ 6 files changed, 256 insertions(+), 31 deletions(-) create mode 100644 cmd/headamp.go create mode 100644 internal/xair/headamp.go diff --git a/cmd/headamp.go b/cmd/headamp.go new file mode 100644 index 0000000..6dbed28 --- /dev/null +++ b/cmd/headamp.go @@ -0,0 +1,146 @@ +package cmd + +import ( + "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. + +Examples: + # Get gain level for headamp index 1 + xairctl headamp gain 1 + # Set gain level for headamp index 1 to 3.5 dB + xairctl headamp gain 1 3.5`, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + client := ClientFromContext(cmd.Context()) + if client == nil { + cmd.PrintErrln("OSC client not found in context") + return + } + + if len(args) < 1 { + cmd.PrintErrln("Please provide a headamp index") + return + } + + index := mustConvToInt(args[0]) + + if len(args) == 1 { + gain, err := client.HeadAmp.Gain(index) + if err != nil { + cmd.PrintErrln("Error getting headamp gain level:", err) + return + } + cmd.Printf("Headamp %d Gain: %.2f dB\n", index, gain) + return + } + + if len(args) < 2 { + cmd.PrintErrln("Please provide a gain level in dB") + return + } + + level := mustConvToFloat64(args[1]) + + err := client.HeadAmp.SetGain(index, level) + if err != nil { + cmd.PrintErrln("Error setting headamp gain level:", err) + return + } + cmd.Printf("Headamp %d Gain set to %.2f dB\n", index, level) + }, +} + +// 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), + Run: func(cmd *cobra.Command, args []string) { + client := ClientFromContext(cmd.Context()) + if client == nil { + cmd.PrintErrln("OSC client not found in context") + return + } + + if len(args) < 1 { + cmd.PrintErrln("Please provide a headamp index") + return + } + + index := mustConvToInt(args[0]) + + if len(args) == 1 { + enabled, err := client.HeadAmp.PhantomPower(index) + if err != nil { + cmd.PrintErrln("Error getting headamp phantom power status:", err) + return + } + status := "disabled" + if enabled { + status = "enabled" + } + cmd.Printf("Headamp %d Phantom Power is %s\n", index, status) + return + } + + if len(args) < 2 { + cmd.PrintErrln("Please provide phantom power status: on or off") + return + } + + var enable bool + switch args[1] { + case "on", "enable": + enable = true + case "off", "disable": + enable = false + default: + cmd.PrintErrln("Invalid phantom power status. Use 'on' or 'off'") + return + } + + err := client.HeadAmp.SetPhantomPower(index, enable) + if err != nil { + cmd.PrintErrln("Error setting headamp phantom power status:", err) + return + } + status := "disabled" + if enable { + status = "enabled" + } + cmd.Printf("Headamp %d Phantom Power %s successfully\n", index, status) + }, +} + +func init() { + rootCmd.AddCommand(headampCmd) + + headampCmd.AddCommand(headampGainCmd) + headampCmd.AddCommand(headampPhantomPowerCmd) +} diff --git a/internal/xair/address.go b/internal/xair/address.go index 8cc4410..f4e0682 100644 --- a/internal/xair/address.go +++ b/internal/xair/address.go @@ -1,13 +1,15 @@ package xair var xairAddressMap = map[string]string{ - "strip": "/ch/%02d", - "bus": "/bus/%01d", + "strip": "/ch/%02d", + "bus": "/bus/%01d", + "headamp": "/headamp/%02d", } var x32AddressMap = map[string]string{ - "strip": "/ch/%02d", - "bus": "/bus/%02d", + "strip": "/ch/%02d", + "bus": "/bus/%02d", + "headamp": "/headamp/%02d", } func addressMapForMixerKind(kind MixerKind) map[string]string { diff --git a/internal/xair/client.go b/internal/xair/client.go index bbd3b02..eb3903a 100644 --- a/internal/xair/client.go +++ b/internal/xair/client.go @@ -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 } diff --git a/internal/xair/headamp.go b/internal/xair/headamp.go new file mode 100644 index 0000000..8181aad --- /dev/null +++ b/internal/xair/headamp.go @@ -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) +} diff --git a/internal/xair/strip.go b/internal/xair/strip.go index 11bbfa7..6b000b1 100644 --- a/internal/xair/strip.go +++ b/internal/xair/strip.go @@ -15,8 +15,8 @@ func NewStrip(c Client) *Strip { } // Mute gets the mute status of the specified strip (1-based indexing). -func (s *Strip) Mute(strip int) (bool, error) { - address := fmt.Sprintf(s.baseAddress, strip) + "/mix/on" +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 @@ -63,28 +63,6 @@ func (s *Strip) SetFader(strip int, level float64) error { 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(s.baseAddress, strip) + "/mix/gain" - 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(s.baseAddress, strip) + "/mix/gain" - 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(s.baseAddress, strip) + "/config/name" @@ -150,3 +128,25 @@ func (s *Strip) SetSendLevel(strip int, bus int, level float64) error { address := fmt.Sprintf(s.baseAddress, strip) + fmt.Sprintf("/mix/%02d/level", bus) 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(s.baseAddress, strip) + "/mix/gain" + 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 float64) error { + address := fmt.Sprintf(s.baseAddress, strip) + "/mix/gain" + return s.client.SendMessage(address, float32(mustDbInto(gain))) +} diff --git a/internal/xair/util.go b/internal/xair/util.go index 1f20d56..ef1e0b2 100644 --- a/internal/xair/util.go +++ b/internal/xair/util.go @@ -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: