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 }