224 lines
5.4 KiB
Go
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:"input"`
|
|
Output []Port `json:"output"`
|
|
}
|
|
|
|
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!", 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!", 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 _, out := range portConfig.Output {
|
|
outputBuffer := JackOutputBuffers{&out, [][]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
|
|
}
|