12 Commits

Author SHA1 Message Date
4bb79c8500 add update and release actions
Some checks failed
Auto-Update Go Modules / update-go-modules (push) Has been cancelled
2025-02-17 13:13:44 +00:00
db497a017b run through formatter 2025-02-07 23:15:05 +00:00
3afc5ee66c add taskfile 2025-02-03 18:34:22 +00:00
99b630dd47 upd readme 2025-01-23 17:30:45 +00:00
8bd62b72d0 remove cat 2025-01-23 02:56:42 +00:00
e36af2c059 add 0.2.1 to changelog 2024-11-07 19:39:03 +00:00
5a5a6fa893 fix pointer 2024-11-07 19:36:56 +00:00
be11239d39 header now uses reusable buffer 2024-11-07 19:34:44 +00:00
d72c6a2d17 rename client struct to udpConn 2024-11-05 18:35:39 +00:00
c063feb919 fix default config.toml dir in README 2024-11-04 14:37:32 +00:00
ae170ca572 move indexOf into the vbantxt package.
improve the warning message on invalid bps value
2024-11-03 16:15:05 +00:00
7a844e3624 add note about functional options to README 2024-11-03 15:54:34 +00:00
15 changed files with 252 additions and 68 deletions

31
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: goreleaser
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v5
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

30
.github/workflows/update-go-modules.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Auto-Update Go Modules
on:
schedule:
- cron: "0 0 * * 1" # Runs every Monday at midnight
jobs:
update-go-modules:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Update Dependencies
run: |
go get -u ./...
go mod tidy
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add go.mod go.sum
git commit -m "chore: auto-update Go modules"
git push

3
.gitignore vendored
View File

@@ -14,3 +14,6 @@ bin/
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# Added by goreleaser init:
dist/

55
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,55 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- main: ./cmd/vbantxt/
env:
- CGO_ENABLED=0
goos:
- linux
- windows
goarch:
- amd64
archives:
- formats: ['tar.gz']
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: ['zip']
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
release:
footer: >-
---
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).

View File

@@ -11,12 +11,19 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
- [x] - [x]
# [0.2.1] - 2024-11-07
### Fixed
- {packet}.header() now uses a reusable buffer.
# [0.2.0] - 2024-10-27 # [0.2.0] - 2024-10-27
### Added ### Added
- `config` flag (shorthand `C`), you may now specify a custom config directory. It defaults to `home directory / .config / vbantxt_cli /`. - `config` flag (shorthand `C`), you may now specify a custom config directory. It defaults to `home directory / .config / vbantxt_cli /`.
- please note, the default directory has changed from v0.1.0 - please note, the default directory has changed from v0.1.0
- Functional options `WithRateLimit` and `WithBPSOpt` and `WithChannel` added. Use them to configure the vbantxt client. See the [included vbantxt cli][vbantxt-cli] for an example of usage.
### Changed ### Changed
@@ -41,3 +48,5 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
- Initial release, package implements VBAN PROTOCOL TXT with a basic CLI for configuring options. - Initial release, package implements VBAN PROTOCOL TXT with a basic CLI for configuring options.
- Ability to load configuration settings from a config.toml. - Ability to load configuration settings from a config.toml.
[vbantxt-cli]: https://github.com/onyx-and-iris/vbantxt/blob/main/cmd/vbantxt/main.go

View File

@@ -41,13 +41,13 @@ func main() {
streamname string = "onyx" streamname string = "onyx"
) )
vbantxtClient, err := vbantxt.New(host, port, streamname) client, err := vbantxt.New(host, port, streamname)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer vbantxtClient.Close() defer client.Close()
err = vbantxtClient.Send("strip[0].mute=0") err = client.Send("strip[0].mute=0")
if err != nil { if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error: %s", err) _, _ = fmt.Fprintf(os.Stderr, "Error: %s", err)
os.Exit(1) os.Exit(1)
@@ -60,18 +60,18 @@ func main() {
Pass `host`, `port` and `streamname` as flags, for example: Pass `host`, `port` and `streamname` as flags, for example:
``` ```
vbantxt-cli -h="gamepc.local" -p=6980 -s=Command1 "strip[0].mute=1 strip[1].mono=1" vbantxt -h="gamepc.local" -p=6980 -s=Command1 "strip[0].mute=1 strip[1].mono=1"
``` ```
You may also store them in a `config.toml` located in `home directory / .config / vbantxt_cli /` You may also store them in a `config.toml` located in `home directory / .config / vbantxt /`
A valid `config.toml` might look like this: A valid `config.toml` might look like this:
```toml ```toml
[connection] [connection]
Host="gamepc.local" host="gamepc.local"
Port=6980 port=6980
Streamname="Command1" streamname="Command1"
``` ```
- `host` defaults to "localhost" - `host` defaults to "localhost"
@@ -88,11 +88,11 @@ The vbantxt-cli utility accepts a single string request or an array of string re
For example, in Windows with Powershell you could: For example, in Windows with Powershell you could:
`vbantxt-cli $(Get-Content .\script.txt)` `vbantxt $(Get-Content .\script.txt)`
Or with Bash: Or with Bash:
`cat script.txt | xargs vbantxt-cli` `xargs vbantxt < script.txt`
to load commands from a file: to load commands from a file:
@@ -109,7 +109,7 @@ bus[3].eq.On=0
Sending commands to VB-Audio Matrix is also possible, for example: Sending commands to VB-Audio Matrix is also possible, for example:
``` ```
vbantxt-cli -s=streamname "Point(ASIO128.IN[2],ASIO128.OUT[1]).dBGain = -8" vbantxt -s=streamname "Point(ASIO128.IN[2],ASIO128.OUT[1]).dBGain = -8"
``` ```
--- ---

55
Taskfile.yml Normal file
View File

@@ -0,0 +1,55 @@
version: '3'
vars:
PROGRAM: vbantxt
SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}'
BIN_DIR: bin
WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe'
LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64'
GIT_COMMIT:
sh: git log -n 1 --format=%h
tasks:
default:
desc: Build the vbantxt project
cmds:
- task: build
build:
desc: 'Build the vbantxt project'
deps: [vet]
cmds:
- task: build-windows
- task: build-linux
vet:
desc: Vet the code
deps: [fmt]
cmds:
- go vet ./...
fmt:
desc: Fmt the code
cmds:
- go fmt ./...
build-windows:
desc: Build the vbantxt project for Windows
cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.Version={{.GIT_COMMIT}}" ./cmd/{{.PROGRAM}}/
build-linux:
desc: Build the vbantxt project for Linux
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.Version={{.GIT_COMMIT}}" ./cmd/{{.PROGRAM}}/
test:
desc: Run tests
cmds:
- go test ./...
clean:
desc: Clean the build artifacts
cmds:
- '{{.SHELL}} rm -r {{.BIN_DIR}}'

View File

@@ -82,12 +82,12 @@ func main() {
} }
} }
func createClient(host string, port int, streamname string, bps int, channel, ratelimit int) (*vbantxt.VbanTxt, error) { func createClient(host string, port int, streamname string, bps, channel, ratelimit int) (*vbantxt.VbanTxt, error) {
client, err := vbantxt.New( client, err := vbantxt.New(
host, host,
port, port,
streamname, streamname,
vbantxt.WithBPSOpt(indexOf(vbantxt.BpsOpts, bps)), vbantxt.WithBPSOpt(bps),
vbantxt.WithChannel(channel), vbantxt.WithChannel(channel),
vbantxt.WithRateLimit(time.Duration(ratelimit)*time.Millisecond)) vbantxt.WithRateLimit(time.Duration(ratelimit)*time.Millisecond))
if err != nil { if err != nil {

View File

@@ -15,12 +15,3 @@ func flagsPassed(flags []string) bool {
}) })
return found return found
} }
func indexOf[T comparable](collection []T, e T) int {
for i, x := range collection {
if x == e {
return i
}
}
return -1
}

View File

@@ -1,11 +1,11 @@
program = vbantxt PROGRAM = vbantxt
GO = go GO = go
BIN_DIR := bin BIN_DIR := bin
WINDOWS=$(BIN_DIR)/$(program)_windows_amd64.exe WINDOWS=$(BIN_DIR)/$(PROGRAM)_windows_amd64.exe
LINUX=$(BIN_DIR)/$(program)_linux_amd64 LINUX=$(BIN_DIR)/$(PROGRAM)_linux_amd64
VERSION=$(shell git describe --tags --always --long --dirty) VERSION=$(shell git log -n 1 --format=%h)
.DEFAULT_GOAL := build .DEFAULT_GOAL := build
@@ -25,10 +25,10 @@ linux: $(LINUX)
$(WINDOWS): $(WINDOWS):
env GOOS=windows GOARCH=amd64 go build -v -o $(WINDOWS) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/vbantxt/ env GOOS=windows GOARCH=amd64 go build -v -o $(WINDOWS) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/$(PROGRAM)/
$(LINUX): $(LINUX):
env GOOS=linux GOARCH=amd64 go build -v -o $(LINUX) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/vbantxt/ env GOOS=linux GOARCH=amd64 go build -v -o $(LINUX) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/$(PROGRAM)/
test: test:
$(GO) test ./... $(GO) test ./...

View File

@@ -16,18 +16,19 @@ func WithRateLimit(ratelimit time.Duration) Option {
} }
} }
// WithBPSOpt is a functional option to set the bps index for {VbanTx}.{Packet}.bpsIndex // WithBPSOpt is a functional option to set the bps index for {VbanTxt}.packet
func WithBPSOpt(bpsIndex int) Option { func WithBPSOpt(bps int) Option {
return func(vt *VbanTxt) { return func(vt *VbanTxt) {
if bpsIndex < 0 || bpsIndex >= len(BpsOpts) { bpsIndex := indexOf(BpsOpts, bps)
log.Warnf("invalid bpsIndex %d, defaulting to 0", bpsIndex) if bpsIndex == -1 {
log.Warnf("invalid bps value %d, expected one of %v, defaulting to 0", bps, BpsOpts)
return return
} }
vt.packet.bpsIndex = bpsIndex vt.packet.bpsIndex = bpsIndex
} }
} }
// WithChannel is a functional option to set the bps index for {VbanTx}.{Packet}.channel // WithChannel is a functional option to set the channel for {VbanTxt}.packet
func WithChannel(channel int) Option { func WithChannel(channel int) Option {
return func(vt *VbanTxt) { return func(vt *VbanTxt) {
vt.packet.channel = channel vt.packet.channel = channel

View File

@@ -1,6 +1,7 @@
package vbantxt package vbantxt
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -17,19 +18,24 @@ var BpsOpts = []int{0, 110, 150, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200,
1000000, 1500000, 2000000, 3000000} 1000000, 1500000, 2000000, 3000000}
type packet struct { type packet struct {
name string streamname []byte
bpsIndex int bpsIndex int
channel int channel int
framecounter []byte framecounter []byte
hbuf *bytes.Buffer
} }
// newPacket returns a packet struct with default values, framecounter at 0. // newPacket returns a packet struct with default values, framecounter at 0.
func newPacket(streamname string) packet { func newPacket(streamname string) packet {
streamnameBuf := make([]byte, streamNameSz)
copy(streamnameBuf, streamname)
return packet{ return packet{
name: streamname, streamname: streamnameBuf,
bpsIndex: 0, bpsIndex: 0,
channel: 0, channel: 0,
framecounter: make([]byte, 4), framecounter: make([]byte, 4),
hbuf: bytes.NewBuffer(make([]byte, headerSz)),
} }
} }
@@ -43,24 +49,17 @@ func (p *packet) nbc() byte {
return byte(p.channel) return byte(p.channel)
} }
// streamname defines the stream name of the text request
func (p *packet) streamname() []byte {
b := make([]byte, streamNameSz)
copy(b, p.name)
return b
}
// header returns a fully formed packet header // header returns a fully formed packet header
func (p *packet) header() []byte { func (p *packet) header() []byte {
h := make([]byte, 0, headerSz) p.hbuf.Reset()
h = append(h, []byte("VBAN")...) p.hbuf.WriteString("VBAN")
h = append(h, p.sr()) p.hbuf.WriteByte(p.sr())
h = append(h, byte(0)) p.hbuf.WriteByte(byte(0))
h = append(h, p.nbc()) p.hbuf.WriteByte(p.nbc())
h = append(h, byte(0x10)) p.hbuf.WriteByte(byte(0x10))
h = append(h, p.streamname()...) p.hbuf.Write(p.streamname)
h = append(h, p.framecounter...) p.hbuf.Write(p.framecounter)
return h return p.hbuf.Bytes()
} }
// bumpFrameCounter increments the frame counter by 1 // bumpFrameCounter increments the frame counter by 1

View File

@@ -7,40 +7,40 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// client represents the UDP client // udpConn represents the UDP client
type client struct { type udpConn struct {
conn *net.UDPConn conn *net.UDPConn
} }
// NewClient returns a UDP client // newUDPConn returns a UDP client
func newClient(host string, port int) (client, error) { func newUDPConn(host string, port int) (udpConn, error) {
udpAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", host, port)) udpAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", host, port))
if err != nil { if err != nil {
return client{}, err return udpConn{}, err
} }
conn, err := net.DialUDP("udp4", nil, udpAddr) conn, err := net.DialUDP("udp4", nil, udpAddr)
if err != nil { if err != nil {
return client{}, err return udpConn{}, err
} }
log.Infof("Outgoing address %s", conn.RemoteAddr()) log.Infof("Outgoing address %s", conn.RemoteAddr())
return client{conn: conn}, nil return udpConn{conn: conn}, nil
} }
// Write implements the io.WriteCloser interface // Write implements the io.WriteCloser interface
func (c client) Write(buf []byte) (int, error) { func (u udpConn) Write(buf []byte) (int, error) {
n, err := c.conn.Write(buf) n, err := u.conn.Write(buf)
if err != nil { if err != nil {
return 0, err return 0, err
} }
log.Debugf("Sending '%s' to: %s", string(buf), c.conn.RemoteAddr()) log.Debugf("Sending '%s' to: %s", string(buf), u.conn.RemoteAddr())
return n, nil return n, nil
} }
// Close implements the io.WriteCloser interface // Close implements the io.WriteCloser interface
func (c client) Close() error { func (u udpConn) Close() error {
err := c.conn.Close() err := u.conn.Close()
if err != nil { if err != nil {
return err return err
} }

10
util.go Normal file
View File

@@ -0,0 +1,10 @@
package vbantxt
func indexOf[T comparable](collection []T, e T) int {
for i, x := range collection {
if x == e {
return i
}
}
return -1
}

View File

@@ -8,7 +8,7 @@ import (
// VbanTxt is used to send VBAN-TXT requests to a distant Voicemeeter/Matrix. // VbanTxt is used to send VBAN-TXT requests to a distant Voicemeeter/Matrix.
type VbanTxt struct { type VbanTxt struct {
client io.WriteCloser udpConn io.WriteCloser
packet packet packet packet
ratelimit time.Duration ratelimit time.Duration
} }
@@ -16,13 +16,13 @@ type VbanTxt struct {
// New constructs a fully formed VbanTxt instance. This is the package's entry point. // 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. // 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) { func New(host string, port int, streamname string, options ...Option) (*VbanTxt, error) {
client, err := newClient(host, port) udpConn, err := newUDPConn(host, port)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating UDP client for (%s:%d): %w", host, port, err) return nil, fmt.Errorf("error creating UDP client for (%s:%d): %w", host, port, err)
} }
vt := &VbanTxt{ vt := &VbanTxt{
client: client, udpConn: udpConn,
packet: newPacket(streamname), packet: newPacket(streamname),
ratelimit: time.Duration(20) * time.Millisecond, ratelimit: time.Duration(20) * time.Millisecond,
} }
@@ -37,7 +37,7 @@ func New(host string, port int, streamname string, options ...Option) (*VbanTxt,
// Send is resonsible for firing each VBAN-TXT request. // Send is resonsible for firing each VBAN-TXT request.
// It waits for {vt.ratelimit} time before returning. // It waits for {vt.ratelimit} time before returning.
func (vt VbanTxt) Send(cmd string) error { func (vt VbanTxt) Send(cmd string) error {
_, err := vt.client.Write(append(vt.packet.header(), []byte(cmd)...)) _, err := vt.udpConn.Write(append(vt.packet.header(), []byte(cmd)...))
if err != nil { if err != nil {
return fmt.Errorf("error sending command (%s): %w", cmd, err) return fmt.Errorf("error sending command (%s): %w", cmd, err)
} }
@@ -51,7 +51,7 @@ func (vt VbanTxt) Send(cmd string) error {
// Close is responsible for closing the UDP Client connection // Close is responsible for closing the UDP Client connection
func (vt VbanTxt) Close() error { func (vt VbanTxt) Close() error {
err := vt.client.Close() err := vt.udpConn.Close()
if err != nil { if err != nil {
return fmt.Errorf("error attempting to close UDP Client: %w", err) return fmt.Errorf("error attempting to close UDP Client: %w", err)
} }