🎉 Implement working Audio Mixer backend with Jack support
This commit is contained in:
198
routing.go
Normal file
198
routing.go
Normal file
@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xthexder/go-jack"
|
||||
)
|
||||
|
||||
type PortState struct {
|
||||
Mute bool
|
||||
Volume float32
|
||||
Balance float32
|
||||
}
|
||||
|
||||
type PortProperties struct {
|
||||
Backend string
|
||||
Channels uint8
|
||||
}
|
||||
|
||||
type PortRoute struct {
|
||||
ToUUID string
|
||||
Mute bool
|
||||
Volume float32
|
||||
Balance float32
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
UUID string
|
||||
Name string
|
||||
Properties PortProperties
|
||||
State PortState
|
||||
Route []PortRoute
|
||||
object []*jack.Port
|
||||
}
|
||||
|
||||
var portConfig struct {
|
||||
Input []Port
|
||||
Output []Port
|
||||
}
|
||||
|
||||
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 {
|
||||
UUID string
|
||||
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 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.UUID, [][]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 {
|
||||
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 {
|
||||
for _, out := range outputBuffers {
|
||||
if out.UUID != route.ToUUID {
|
||||
continue
|
||||
}
|
||||
|
||||
if out.Buffers == nil || uint8(len(out.Buffers)) <= ch {
|
||||
// Output is not initialized
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
Reference in New Issue
Block a user