mirror of
https://github.com/onyx-and-iris/xair-cli.git
synced 2026-02-04 07:27:47 +00:00
start adding x32 support
This commit is contained in:
parent
a821392517
commit
ddbf52430a
9
internal/xair/address.go
Normal file
9
internal/xair/address.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package xair
|
||||||
|
|
||||||
|
var xairAddressMap = map[string]string{
|
||||||
|
"bus": "/bus/%01d",
|
||||||
|
}
|
||||||
|
|
||||||
|
var x32AddressMap = map[string]string{
|
||||||
|
"bus": "/bus/%02d",
|
||||||
|
}
|
||||||
@ -17,18 +17,24 @@ type parser interface {
|
|||||||
Parse(data []byte) (*osc.Message, error)
|
Parse(data []byte) (*osc.Message, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type XAirClient struct {
|
type engine struct {
|
||||||
|
Kind string
|
||||||
conn *net.UDPConn
|
conn *net.UDPConn
|
||||||
mixerAddr *net.UDPAddr
|
mixerAddr *net.UDPAddr
|
||||||
|
|
||||||
parser parser
|
parser parser
|
||||||
|
addressMap map[string]string
|
||||||
|
|
||||||
done chan bool
|
done chan bool
|
||||||
respChan chan *osc.Message
|
respChan chan *osc.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient creates a new XAirClient instance
|
// NewClient creates a new XAirClient instance
|
||||||
func NewClient(mixerIP string, mixerPort int) (*XAirClient, error) {
|
func NewClient(mixerIP string, mixerPort int, opts ...Option) (*Client, error) {
|
||||||
localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", 0))
|
localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", 0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve local address: %v", err)
|
return nil, fmt.Errorf("failed to resolve local address: %v", err)
|
||||||
@ -47,23 +53,32 @@ func NewClient(mixerIP string, mixerPort int) (*XAirClient, error) {
|
|||||||
|
|
||||||
log.Debugf("Local UDP connection: %s ", conn.LocalAddr().String())
|
log.Debugf("Local UDP connection: %s ", conn.LocalAddr().String())
|
||||||
|
|
||||||
return &XAirClient{
|
e := &engine{
|
||||||
|
Kind: "xair",
|
||||||
conn: conn,
|
conn: conn,
|
||||||
mixerAddr: mixerAddr,
|
mixerAddr: mixerAddr,
|
||||||
parser: newParser(),
|
parser: newParser(),
|
||||||
done: make(chan bool),
|
done: make(chan bool),
|
||||||
respChan: make(chan *osc.Message),
|
respChan: make(chan *osc.Message, 100),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
engine: *e,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins listening for messages in a goroutine
|
// Start begins listening for messages in a goroutine
|
||||||
func (x *XAirClient) StartListening() {
|
func (x *Client) StartListening() {
|
||||||
go x.receiveLoop()
|
go x.receiveLoop()
|
||||||
log.Debugf("Started listening on %s...", x.conn.LocalAddr().String())
|
log.Debugf("Started listening on %s...", x.conn.LocalAddr().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiveLoop handles incoming OSC messages
|
// receiveLoop handles incoming OSC messages
|
||||||
func (x *XAirClient) receiveLoop() {
|
func (x *Client) receiveLoop() {
|
||||||
buffer := make([]byte, 4096)
|
buffer := make([]byte, 4096)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -101,7 +116,7 @@ func (x *XAirClient) receiveLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseOSCMessage parses raw bytes into an OSC message with improved error handling
|
// parseOSCMessage parses raw bytes into an OSC message with improved error handling
|
||||||
func (x *XAirClient) parseOSCMessage(data []byte) (*osc.Message, error) {
|
func (x *Client) parseOSCMessage(data []byte) (*osc.Message, error) {
|
||||||
msg, err := x.parser.Parse(data)
|
msg, err := x.parser.Parse(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -111,7 +126,7 @@ func (x *XAirClient) parseOSCMessage(data []byte) (*osc.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the client and closes the connection
|
// Stop stops the client and closes the connection
|
||||||
func (x *XAirClient) Stop() {
|
func (x *Client) Stop() {
|
||||||
close(x.done)
|
close(x.done)
|
||||||
if x.conn != nil {
|
if x.conn != nil {
|
||||||
x.conn.Close()
|
x.conn.Close()
|
||||||
@ -119,12 +134,12 @@ func (x *XAirClient) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage sends an OSC message to the mixer using the unified connection
|
// SendMessage sends an OSC message to the mixer using the unified connection
|
||||||
func (x *XAirClient) SendMessage(address string, args ...any) error {
|
func (x *Client) SendMessage(address string, args ...any) error {
|
||||||
return x.SendToAddress(x.mixerAddr, address, args...)
|
return x.SendToAddress(x.mixerAddr, address, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendToAddress sends an OSC message to a specific address (enables replying to different ports)
|
// SendToAddress sends an OSC message to a specific address (enables replying to different ports)
|
||||||
func (x *XAirClient) SendToAddress(addr *net.UDPAddr, oscAddress string, args ...any) error {
|
func (x *Client) SendToAddress(addr *net.UDPAddr, oscAddress string, args ...any) error {
|
||||||
msg := osc.NewMessage(oscAddress)
|
msg := osc.NewMessage(oscAddress)
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
msg.Append(arg)
|
msg.Append(arg)
|
||||||
@ -152,7 +167,7 @@ func (x *XAirClient) SendToAddress(addr *net.UDPAddr, oscAddress string, args ..
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequestInfo requests mixer information
|
// RequestInfo requests mixer information
|
||||||
func (x *XAirClient) RequestInfo() (error, InfoResponse) {
|
func (x *Client) RequestInfo() (error, InfoResponse) {
|
||||||
err := x.SendMessage("/xinfo")
|
err := x.SendMessage("/xinfo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, InfoResponse{}
|
return err, InfoResponse{}
|
||||||
@ -169,19 +184,45 @@ func (x *XAirClient) RequestInfo() (error, InfoResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KeepAlive sends keep-alive message (required for multi-client usage)
|
// KeepAlive sends keep-alive message (required for multi-client usage)
|
||||||
func (x *XAirClient) KeepAlive() error {
|
func (x *Client) KeepAlive() error {
|
||||||
return x.SendMessage("/xremote")
|
return x.SendMessage("/xremote")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestStatus requests mixer status
|
// RequestStatus requests mixer status
|
||||||
func (x *XAirClient) RequestStatus() error {
|
func (x *Client) RequestStatus() error {
|
||||||
return x.SendMessage("/status")
|
return x.SendMessage("/status")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STRIP METHODS */
|
/* STRIP METHODS */
|
||||||
|
|
||||||
|
// StripMute gets mute state for a specific strip (1-based indexing)
|
||||||
|
func (x *Client) StripMute(strip int) (bool, error) {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
|
||||||
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := <-x.respChan
|
||||||
|
val, ok := resp.Arguments[0].(int32)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("unexpected argument type for strip mute value")
|
||||||
|
}
|
||||||
|
return val == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStripMute sets mute state for a specific strip (1-based indexing)
|
||||||
|
func (x *Client) SetStripMute(strip int, muted bool) error {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
|
||||||
|
var value int32 = 0
|
||||||
|
if !muted {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
return x.SendMessage(address, value)
|
||||||
|
}
|
||||||
|
|
||||||
// StripFader requests the current fader level for a strip
|
// StripFader requests the current fader level for a strip
|
||||||
func (x *XAirClient) StripFader(strip int) (float64, error) {
|
func (x *Client) StripFader(strip int) (float64, error) {
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
||||||
err := x.SendMessage(address)
|
err := x.SendMessage(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -198,32 +239,100 @@ func (x *XAirClient) StripFader(strip int) (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetStripFader sets the fader level for a specific strip (1-based indexing)
|
// SetStripFader sets the fader level for a specific strip (1-based indexing)
|
||||||
func (x *XAirClient) SetStripFader(strip int, level float64) error {
|
func (x *Client) SetStripFader(strip int, level float64) error {
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
||||||
return x.SendMessage(address, float32(mustDbInto(level)))
|
return x.SendMessage(address, float32(mustDbInto(level)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripGain requests gain for a specific strip (1-based indexing)
|
// StripMicGain requests the phantom gain for a specific strip
|
||||||
func (x *XAirClient) StripGain(strip int) error {
|
func (x *Client) StripMicGain(strip int) (float64, error) {
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
address := fmt.Sprintf("/ch/%02d/mix/gain", strip)
|
||||||
return x.SendMessage(address)
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to send strip gain request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStripGain sets gain for a specific strip (1-based indexing)
|
resp := <-x.respChan
|
||||||
func (x *XAirClient) SetStripGain(strip int, gain float32) error {
|
val, ok := resp.Arguments[0].(float32)
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/fader", strip)
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unexpected argument type for strip gain value")
|
||||||
|
}
|
||||||
|
return mustDbFrom(float64(val)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStripMicGain sets the phantom gain for a specific strip (1-based indexing)
|
||||||
|
func (x *Client) SetStripMicGain(strip int, gain float32) error {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/mix/gain", strip)
|
||||||
return x.SendMessage(address, gain)
|
return x.SendMessage(address, gain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripMute gets mute state for a specific strip (1-based indexing)
|
// StripName requests the name for a specific strip
|
||||||
func (x *XAirClient) StripMute(strip int) error {
|
func (x *Client) StripName(strip int) (string, error) {
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
|
address := fmt.Sprintf("/ch/%02d/config/name", strip)
|
||||||
return x.SendMessage(address)
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to send strip name request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStripMute sets mute state for a specific strip (1-based indexing)
|
resp := <-x.respChan
|
||||||
func (x *XAirClient) SetStripMute(strip int, muted bool) error {
|
val, ok := resp.Arguments[0].(string)
|
||||||
address := fmt.Sprintf("/ch/%02d/mix/on", strip)
|
if !ok {
|
||||||
|
return "", fmt.Errorf("unexpected argument type for strip name value")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStripName sets the name for a specific strip
|
||||||
|
func (x *Client) SetStripName(strip int, name string) error {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/config/name", strip)
|
||||||
|
return x.SendMessage(address, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StripColor requests the color for a specific strip
|
||||||
|
func (x *Client) StripColor(strip int) (int32, error) {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/config/color", strip)
|
||||||
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to send strip color request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := <-x.respChan
|
||||||
|
val, ok := resp.Arguments[0].(int32)
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unexpected argument type for strip color value")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStripColor sets the color for a specific strip (0-15)
|
||||||
|
func (x *Client) SetStripColor(strip int, color int32) error {
|
||||||
|
address := fmt.Sprintf("/ch/%02d/config/color", strip)
|
||||||
|
return x.SendMessage(address, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BUS METHODS */
|
||||||
|
|
||||||
|
// BusMute requests the current mute status for a bus
|
||||||
|
func (x *Client) BusMute(bus int) (bool, error) {
|
||||||
|
formatter := x.addressMap["bus"]
|
||||||
|
address := fmt.Sprintf(formatter, bus) + "/mix/on"
|
||||||
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := <-x.respChan
|
||||||
|
val, ok := resp.Arguments[0].(int32)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("unexpected argument type for bus mute value")
|
||||||
|
}
|
||||||
|
return val == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBusMute sets the mute status for a specific bus (1-based indexing)
|
||||||
|
func (x *Client) SetBusMute(bus int, muted bool) error {
|
||||||
|
formatter := x.addressMap["bus"]
|
||||||
|
address := fmt.Sprintf(formatter, bus) + "/mix/on"
|
||||||
var value int32 = 0
|
var value int32 = 0
|
||||||
if !muted {
|
if !muted {
|
||||||
value = 1
|
value = 1
|
||||||
@ -231,32 +340,33 @@ func (x *XAirClient) SetStripMute(strip int, muted bool) error {
|
|||||||
return x.SendMessage(address, value)
|
return x.SendMessage(address, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripName requests the name for a specific strip
|
// BusFader requests the current fader level for a bus
|
||||||
func (x *XAirClient) StripName(strip int) error {
|
func (x *Client) BusFader(bus int) (float64, error) {
|
||||||
address := fmt.Sprintf("/ch/%02d/config/name", strip)
|
address := fmt.Sprintf("/bus/%01d/mix/fader", bus)
|
||||||
return x.SendMessage(address)
|
err := x.SendMessage(address)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStripName sets the name for a specific strip
|
resp := <-x.respChan
|
||||||
func (x *XAirClient) SetStripName(strip int, name string) error {
|
val, ok := resp.Arguments[0].(float32)
|
||||||
address := fmt.Sprintf("/ch/%02d/config/name", strip)
|
if !ok {
|
||||||
return x.SendMessage(address, name)
|
return 0, fmt.Errorf("unexpected argument type for bus fader value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripColor requests the color for a specific strip
|
return mustDbFrom(float64(val)), nil
|
||||||
func (x *XAirClient) StripColor(strip int) error {
|
|
||||||
address := fmt.Sprintf("/ch/%02d/config/color", strip)
|
|
||||||
return x.SendMessage(address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStripColor sets the color for a specific strip (0-15)
|
// SetBusFader sets the fader level for a specific bus (1-based indexing)
|
||||||
func (x *XAirClient) SetStripColor(strip int, color int32) error {
|
func (x *Client) SetBusFader(bus int, level float64) error {
|
||||||
address := fmt.Sprintf("/ch/%02d/config/color", strip)
|
address := fmt.Sprintf("/bus/%01d/mix/fader", bus)
|
||||||
return x.SendMessage(address, color)
|
return x.SendMessage(address, float32(mustDbInto(level)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MAIN LR METHODS */
|
||||||
|
|
||||||
// MainLRFader requests the current main L/R fader level
|
// MainLRFader requests the current main L/R fader level
|
||||||
func (x *XAirClient) MainLRFader() (float64, error) {
|
func (x *Client) MainLRFader() (float64, error) {
|
||||||
err := x.SendMessage("/lr/mix/fader")
|
err := x.SendMessage("/lr/mix/fader")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -271,12 +381,12 @@ func (x *XAirClient) MainLRFader() (float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetMainLRFader sets the main L/R fader level
|
// SetMainLRFader sets the main L/R fader level
|
||||||
func (x *XAirClient) SetMainLRFader(level float64) error {
|
func (x *Client) SetMainLRFader(level float64) error {
|
||||||
return x.SendMessage("/lr/mix/fader", float32(mustDbInto(level)))
|
return x.SendMessage("/lr/mix/fader", float32(mustDbInto(level)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MainLRMute requests the current main L/R mute status
|
// MainLRMute requests the current main L/R mute status
|
||||||
func (x *XAirClient) MainLRMute() (bool, error) {
|
func (x *Client) MainLRMute() (bool, error) {
|
||||||
err := x.SendMessage("/lr/mix/on")
|
err := x.SendMessage("/lr/mix/on")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -287,11 +397,11 @@ func (x *XAirClient) MainLRMute() (bool, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("unexpected argument type for main LR mute value")
|
return false, fmt.Errorf("unexpected argument type for main LR mute value")
|
||||||
}
|
}
|
||||||
return val == 0, nil // 0 = muted, 1 = unmuted
|
return val == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMainLRMute sets the main L/R mute status
|
// SetMainLRMute sets the main L/R mute status
|
||||||
func (x *XAirClient) SetMainLRMute(muted bool) error {
|
func (x *Client) SetMainLRMute(muted bool) error {
|
||||||
var value int32 = 0
|
var value int32 = 0
|
||||||
if !muted {
|
if !muted {
|
||||||
value = 1
|
value = 1
|
||||||
|
|||||||
18
internal/xair/option.go
Normal file
18
internal/xair/option.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package xair
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Option func(*engine)
|
||||||
|
|
||||||
|
func WithKind(kind string) Option {
|
||||||
|
if strings.EqualFold(kind, "x32") {
|
||||||
|
return func(c *engine) {
|
||||||
|
c.Kind = kind
|
||||||
|
c.addressMap = x32AddressMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func(c *engine) {
|
||||||
|
c.Kind = "xair"
|
||||||
|
c.addressMap = xairAddressMap
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user