Files
go-mix/routing.go
2024-08-29 00:34:01 +02:00

224 lines
5.4 KiB
Go

package main
import (
"fmt"
"github.com/xthexder/go-jack"
)
type PortState struct {
Mute bool `json:"mute"`
Volume float32 `json:"volume"`
Balance float32 `json:"balance"`
}
type PortProperties struct {
Backend string `json:"backend"`
Channels uint8 `json:"channels"`
}
type PortRoute struct {
ToUUID string `json:"toUUID"`
Mute bool `json:"mute"`
Volume float32 `json:"volume"`
Balance float32 `json:"balance"`
}
type Port struct {
UUID string `json:"UUID"`
Name string `json:"name"`
Properties PortProperties `json:"properties"`
State PortState `json:"state"`
Route []PortRoute `json:"route"`
object []*jack.Port
}
var portConfig struct {
Input []Port `json:"inputs"`
Output []Port `json:"outputs"`
}
var portsInited bool = false
// Find index and pointer of port
func findPort(findUUID string) (*Port, int, string) {
var port *Port
var portIndex int = -1
var portType string
for i, p := range portConfig.Input {
if p.UUID == findUUID {
port = &portConfig.Input[i]
portIndex = i
portType = "input"
}
}
for i, p := range portConfig.Output {
if p.UUID == findUUID {
port = &portConfig.Output[i]
portIndex = i
portType = "output"
}
}
return port, portIndex, portType
}
func registerPort(port *Port, portType string) {
switch backend := port.Properties.Backend; backend {
case "jack":
if !config.Jack.Enabled {
return
}
var jackFlag int
if portType == "input" {
jackFlag = jack.PortIsInput
} else if portType == "output" {
jackFlag = jack.PortIsOutput
} else {
fmt.Printf("WARN! Trying to register port with invalid type `%s`\n", portType)
return
}
port.object = registerJackPort(port.Name, port.Properties.Channels, uint64(jackFlag))
default:
fmt.Printf("WARN! Error loading port %s: Backend %s is not implemented!\n", port.UUID, backend)
}
}
func unregisterPort(port *Port) {
switch backend := port.Properties.Backend; backend {
case "jack":
if !config.Jack.Enabled {
return
}
unregisterJackPort(port.object)
port.object = nil
default:
fmt.Printf("WARN! Error unloading port %s: Backend %s is not implemented!\n", port.UUID, backend)
}
}
func startRouting() {
// Load port configuration from config file
loadConfig("ports", &portConfig)
// Register ports
for i := range portConfig.Input {
registerPort(&portConfig.Input[i], "input")
}
for i := range portConfig.Output {
registerPort(&portConfig.Output[i], "output")
}
portsInited = true
}
type JackOutputBuffers struct {
Output *Port
Buffers [][]jack.AudioSample
}
func addToBuffer(src *[]jack.AudioSample, dst *[]jack.AudioSample, volume float32) {
for i, sample := range *src {
(*dst)[i] += sample * jack.AudioSample(volume)
}
}
func multiplyBuffer(dst *[]jack.AudioSample, factor float32) {
for i := range *dst {
(*dst)[i] *= jack.AudioSample(factor)
}
}
func calcBalanceFactor(ch uint8, balance float32) float32 {
if ch == 0 {
return max(0, min(1, 1-balance))
} else {
return max(0, min(1, 1+balance))
}
}
func processJackCb(nframes uint32) int {
if !portsInited {
return 0
}
// Get memory addresses to write to for all outputs
var outputBuffers []JackOutputBuffers = []JackOutputBuffers{}
for i, out := range portConfig.Output {
outputBuffer := JackOutputBuffers{&portConfig.Output[i], [][]jack.AudioSample{}}
for _, port := range out.object {
buffer := port.GetBuffer(nframes)
for i := range buffer {
buffer[i] = 0
}
outputBuffer.Buffers = append(outputBuffer.Buffers, buffer)
}
outputBuffers = append(outputBuffers, outputBuffer)
}
// Write audio data from every input to all outputs it routes to
for _, in := range portConfig.Input {
if in.State.Mute {
// Input is muted
continue
}
// For every channel
for ch := uint8(0); ch < in.Properties.Channels; ch++ {
if in.object == nil || uint8(len(in.object)) <= ch {
// Input is not initialized
continue
}
chSamples := in.object[ch].GetBuffer(nframes)
// For every route that exists for the input
for _, route := range in.Route {
if route.Mute {
// Route is muted
continue
}
for _, out := range outputBuffers {
if out.Output.UUID != route.ToUUID {
continue
}
if out.Buffers == nil || uint8(len(out.Buffers)) <= ch {
// Output is not initialized
continue
}
if out.Output.State.Mute {
// Output is muted
continue
}
routeVolume := in.State.Volume * route.Volume * calcBalanceFactor(ch, in.State.Balance)
if in.Properties.Channels < uint8(len(out.Buffers)) {
// If input is mono and output is stereo, write to both output channels at once
for i, buf := range out.Buffers {
finalVolume := routeVolume * calcBalanceFactor(uint8(i), route.Balance)
addToBuffer(&chSamples, &buf, finalVolume)
}
} else {
// Else only write to current channel (or channel 0 if output is mono)
finalVolume := routeVolume * calcBalanceFactor(ch, route.Balance)
index := min(uint8(len(out.Buffers)-1), ch)
addToBuffer(&chSamples, &out.Buffers[index], finalVolume)
}
}
}
}
// Apply output volume and balance
// (mute state is already checked above)
for _, out := range outputBuffers {
for ch, buf := range out.Buffers {
multiplyBuffer(&buf, out.Output.State.Volume*calcBalanceFactor(uint8(ch), out.Output.State.Balance))
}
}
}
return 0
}