mirror of
https://github.com/onyx-and-iris/vbantxt.git
synced 2024-12-04 07:40:47 +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…
Reference in New Issue
Block a user