mirror of
				https://github.com/onyx-and-iris/vbantxt.git
				synced 2025-10-31 05:21:51 +00:00 
			
		
		
		
	reorganise some of the internals of the package.
functional options added.
This commit is contained in:
		
							parent
							
								
									ee781ea586
								
							
						
					
					
						commit
						6c53cfa383
					
				
							
								
								
									
										48
									
								
								client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								client.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | package vbantxt | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net" | ||||||
|  | 
 | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // client represents the UDP client | ||||||
|  | type client struct { | ||||||
|  | 	conn *net.UDPConn | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewClient returns a UDP client | ||||||
|  | func newClient(host string, port int) (client, error) { | ||||||
|  | 	udpAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", host, port)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return client{}, err | ||||||
|  | 	} | ||||||
|  | 	conn, err := net.DialUDP("udp4", nil, udpAddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return client{}, err | ||||||
|  | 	} | ||||||
|  | 	log.Infof("Outgoing address %s", conn.RemoteAddr()) | ||||||
|  | 
 | ||||||
|  | 	return client{conn: conn}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write implements the io.WriteCloser interface | ||||||
|  | func (c client) Write(buf []byte) (int, error) { | ||||||
|  | 	n, err := c.conn.Write(buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	log.Debugf("Sending '%s' to: %s", string(buf), c.conn.RemoteAddr()) | ||||||
|  | 
 | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close implements the io.WriteCloser interface | ||||||
|  | func (c client) Close() error { | ||||||
|  | 	err := c.conn.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								errors.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | package vbantxt | ||||||
|  | 
 | ||||||
|  | // Error is used to define sentinel errors. | ||||||
|  | type Error string | ||||||
|  | 
 | ||||||
|  | // Error implements the error interface. | ||||||
|  | func (r Error) Error() string { | ||||||
|  | 	return string(r) | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								option.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								option.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | package vbantxt | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Option is a functional option type that allows us to configure the VbanTxt. | ||||||
|  | type Option func(*VbanTxt) | ||||||
|  | 
 | ||||||
|  | // WithRateLimit is a functional option to set the ratelimit for requests | ||||||
|  | func WithRateLimit(ratelimit time.Duration) Option { | ||||||
|  | 	return func(vt *VbanTxt) { | ||||||
|  | 		vt.ratelimit = ratelimit | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithBPSOpt is a functional option to set the bps index for {VbanTx}.{Packet}.bpsIndex | ||||||
|  | func WithBPSOpt(bpsIndex int) Option { | ||||||
|  | 	return func(vt *VbanTxt) { | ||||||
|  | 		if bpsIndex < 0 || bpsIndex >= len(BpsOpts) { | ||||||
|  | 			log.Warnf("invalid bpsIndex %d, defaulting to 0", bpsIndex) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		vt.packet.bpsIndex = bpsIndex | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithChannel is a functional option to set the bps index for {VbanTx}.{Packet}.channel | ||||||
|  | func WithChannel(channel int) Option { | ||||||
|  | 	return func(vt *VbanTxt) { | ||||||
|  | 		vt.packet.channel = channel | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								packet.go
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								packet.go
									
									
									
									
									
								
							| @ -1,50 +1,72 @@ | |||||||
| package main | package vbantxt | ||||||
| 
 | 
 | ||||||
| var r *requestHeader | import ( | ||||||
|  | 	"encoding/binary" | ||||||
| 
 | 
 | ||||||
| const VBAN_PROTOCOL_TXT = 0x40 | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| // requestHeader represents a single request header | const ( | ||||||
| type requestHeader struct { | 	vbanProtocolTxt = 0x40 | ||||||
|  | 	streamNameSz    = 16 | ||||||
|  | 	headerSz        = 4 + 1 + 1 + 1 + 1 + 16 + 4 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var BpsOpts = []int{0, 110, 150, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 31250, | ||||||
|  | 	38400, 57600, 115200, 128000, 230400, 250000, 256000, 460800, 921600, | ||||||
|  | 	1000000, 1500000, 2000000, 3000000} | ||||||
|  | 
 | ||||||
|  | type packet struct { | ||||||
| 	name         string | 	name         string | ||||||
| 	bpsIndex     int | 	bpsIndex     int | ||||||
| 	channel      int | 	channel      int | ||||||
| 	framecounter []byte | 	framecounter []byte | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // newRequestHeader returns a pointer to a requestHeader struct as a singleton | // newPacket returns a packet struct with default values, framecounter at 0. | ||||||
| func newRequestHeader(streamname string, bpsI, channel int) *requestHeader { | func newPacket(streamname string) packet { | ||||||
| 	if r != nil { | 	return packet{ | ||||||
| 		return r | 		name:         streamname, | ||||||
|  | 		bpsIndex:     0, | ||||||
|  | 		channel:      0, | ||||||
|  | 		framecounter: make([]byte, 4), | ||||||
| 	} | 	} | ||||||
| 	return &requestHeader{streamname, bpsI, channel, make([]byte, 4)} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // sr defines the samplerate for the request | // sr defines the samplerate for the request | ||||||
| func (r *requestHeader) sr() byte { | func (p *packet) sr() byte { | ||||||
| 	return byte(VBAN_PROTOCOL_TXT + r.bpsIndex) | 	return byte(vbanProtocolTxt + p.bpsIndex) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // nbc defines the channel of the request | // nbc defines the channel of the request | ||||||
| func (r *requestHeader) nbc() byte { | func (p *packet) nbc() byte { | ||||||
| 	return byte(r.channel) | 	return byte(p.channel) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // streamname defines the stream name of the text request | // streamname defines the stream name of the text request | ||||||
| func (r *requestHeader) streamname() []byte { | func (p *packet) streamname() []byte { | ||||||
| 	b := make([]byte, 16) | 	b := make([]byte, streamNameSz) | ||||||
| 	copy(b, r.name) | 	copy(b, p.name) | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // header returns a fully formed text request packet header | // header returns a fully formed packet header | ||||||
| func (t *requestHeader) header() []byte { | func (p *packet) header() []byte { | ||||||
| 	h := []byte("VBAN") | 	h := make([]byte, 0, headerSz) | ||||||
| 	h = append(h, t.sr()) | 	h = append(h, []byte("VBAN")...) | ||||||
|  | 	h = append(h, p.sr()) | ||||||
| 	h = append(h, byte(0)) | 	h = append(h, byte(0)) | ||||||
| 	h = append(h, t.nbc()) | 	h = append(h, p.nbc()) | ||||||
| 	h = append(h, byte(0x10)) | 	h = append(h, byte(0x10)) | ||||||
| 	h = append(h, t.streamname()...) | 	h = append(h, p.streamname()...) | ||||||
| 	h = append(h, t.framecounter...) | 	h = append(h, p.framecounter...) | ||||||
| 	return h | 	return h | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // bumpFrameCounter increments the frame counter by 1 | ||||||
|  | func (p *packet) bumpFrameCounter() { | ||||||
|  | 	x := binary.LittleEndian.Uint32(p.framecounter) | ||||||
|  | 	binary.LittleEndian.PutUint32(p.framecounter, x+1) | ||||||
|  | 
 | ||||||
|  | 	log.Tracef("framecounter: %d", x) | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								vbantxt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								vbantxt.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | package vbantxt | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // VbanTxt is used to send VBAN-TXT requests to a distant Voicemeeter/Matrix. | ||||||
|  | type VbanTxt struct { | ||||||
|  | 	client    io.WriteCloser | ||||||
|  | 	packet    packet | ||||||
|  | 	ratelimit time.Duration | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New constructs a fully formed VbanTxt instance. This is the package's entry point. | ||||||
|  | // It sets default values for it's fields and then runs the option functions. | ||||||
|  | func New(host string, port int, streamname string, options ...Option) (*VbanTxt, error) { | ||||||
|  | 	client, err := newClient(host, port) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error creating UDP client for (%s:%d): %w", host, port, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vt := &VbanTxt{ | ||||||
|  | 		client:    client, | ||||||
|  | 		packet:    newPacket(streamname), | ||||||
|  | 		ratelimit: time.Duration(20) * time.Millisecond, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, o := range options { | ||||||
|  | 		o(vt) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return vt, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Send is resonsible for firing each VBAN-TXT request. | ||||||
|  | // It waits for {vt.ratelimit} time before returning. | ||||||
|  | func (vt VbanTxt) Send(cmd string) error { | ||||||
|  | 	_, err := vt.client.Write(append(vt.packet.header(), []byte(cmd)...)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error sending command (%s): %w", cmd, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vt.packet.bumpFrameCounter() | ||||||
|  | 
 | ||||||
|  | 	time.Sleep(vt.ratelimit) | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close is responsible for closing the UDP Client connection | ||||||
|  | func (vt VbanTxt) Close() error { | ||||||
|  | 	err := vt.client.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error attempting to close UDP Client: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user