You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
13 KiB
Go
448 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/lawl/pulseaudio"
|
|
)
|
|
|
|
const (
|
|
loaded = iota
|
|
unloaded
|
|
inconsistent
|
|
)
|
|
|
|
// the ugly and (partially) repeated strings are unforunately difficult to avoid, as it's what pulse audio expects
|
|
|
|
func updateNoiseSupressorLoaded(ctx *ntcontext) {
|
|
c := ctx.paClient
|
|
upd, err := c.Updates()
|
|
if err != nil {
|
|
fmt.Printf("Error listening for updates: %v\n", err)
|
|
}
|
|
|
|
for {
|
|
ctx.noiseSupressorState = supressorState(ctx)
|
|
|
|
if !c.Connected() {
|
|
break
|
|
}
|
|
|
|
<-upd
|
|
}
|
|
}
|
|
|
|
func supressorState(ctx *ntcontext) int {
|
|
//perform some checks to see if it looks like the noise supressor is loaded
|
|
c := ctx.paClient
|
|
var inpLoaded, outLoaded, inputInc, outputInc bool
|
|
if ctx.config.FilterInput {
|
|
if ctx.serverInfo.servertype == servertype_pipewire {
|
|
_, ladspasource, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-ladspa-source: %v\n", err)
|
|
}
|
|
inpLoaded = ladspasource
|
|
inputInc = false
|
|
} else {
|
|
_, nullsink, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-null-sink: %v\n", err)
|
|
}
|
|
_, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
|
|
}
|
|
_, loopback, err := findModule(c, "module-loopback", "sink=nui_mic_raw_in")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err)
|
|
}
|
|
_, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err)
|
|
}
|
|
|
|
if nullsink && ladspasink && loopback && remap {
|
|
inpLoaded = true
|
|
} else if nullsink || ladspasink || loopback || remap {
|
|
inputInc = true
|
|
}
|
|
}
|
|
} else {
|
|
inpLoaded = true
|
|
}
|
|
|
|
if ctx.config.FilterOutput {
|
|
if ctx.serverInfo.servertype == servertype_pipewire {
|
|
_, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
|
|
}
|
|
outLoaded = ladspasink
|
|
outputInc = false
|
|
} else {
|
|
_, out, err := findModule(c, "module-null-sink", "sink_name=nui_out_out_sink")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
|
|
}
|
|
_, lad, err := findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
|
|
}
|
|
_, loop, err := findModule(c, "module-loopback", "source=nui_out_out_sink.monitor")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
|
|
}
|
|
_, outin, err := findModule(c, "module-null-sink", "sink_name=nui_out_in_sink")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
|
|
}
|
|
_, loop2, err := findModule(c, "module-loopback", "source=nui_out_in_sink.monitor")
|
|
if err != nil {
|
|
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
|
|
}
|
|
|
|
outLoaded = out && lad && loop && outin && loop2
|
|
outputInc = out || lad || loop || outin || loop2
|
|
}
|
|
} else {
|
|
outLoaded = true
|
|
}
|
|
|
|
if (inpLoaded || !ctx.config.FilterInput) && (outLoaded || !ctx.config.FilterOutput) && !inputInc {
|
|
return loaded
|
|
}
|
|
|
|
if (inpLoaded && ctx.config.FilterInput) || (outLoaded && ctx.config.FilterOutput) || inputInc || outputInc {
|
|
return inconsistent
|
|
}
|
|
|
|
return unloaded
|
|
}
|
|
|
|
func loadSupressor(ctx *ntcontext, inp *device, out *device) error {
|
|
if ctx.serverInfo.servertype == servertype_pulse {
|
|
log.Printf("Querying pulse rlimit\n")
|
|
pid, err := getPulsePid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lim, err := getRlimit(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Rlimit: %+v. Trying to remove.\n", lim)
|
|
|
|
removeRlimit(pid)
|
|
|
|
defer setRlimit(pid, &lim) // lowering RLIMIT doesn't require root
|
|
|
|
newLim, err := getRlimit(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Rlimit: %+v\n", newLim)
|
|
}
|
|
|
|
if inp.checked {
|
|
var err error
|
|
if ctx.serverInfo.servertype == servertype_pipewire {
|
|
err = loadPipeWireInput(ctx, inp)
|
|
} else {
|
|
err = loadPulseInput(ctx, inp)
|
|
}
|
|
if err != nil {
|
|
log.Printf("Error loading input: %v\n", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if out.checked {
|
|
var err error
|
|
if ctx.serverInfo.servertype == servertype_pipewire {
|
|
err = loadPipeWireOutput(ctx, out)
|
|
} else {
|
|
err = loadPulseOutput(ctx, out)
|
|
}
|
|
if err != nil {
|
|
log.Printf("Error loading output: %v\n", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func loadModule(ctx *ntcontext, module, args string) (uint32, error) {
|
|
idx, err := ctx.paClient.LoadModule(module, args)
|
|
|
|
//14 = module initialisation failed
|
|
if paErr, ok := err.(*pulseaudio.Error); ok && paErr.Code == 14 {
|
|
resetUI(ctx)
|
|
ctx.views.Push(makeErrorView(ctx, fmt.Sprintf("Could not load module '%s'. This is likely a problem with your system or distribution.", module)))
|
|
}
|
|
return idx, err
|
|
}
|
|
|
|
func loadPipeWireInput(ctx *ntcontext, inp *device) error {
|
|
log.Printf("Loading supressor for pipewire\n")
|
|
idx, err := loadModule(ctx, "module-ladspa-source",
|
|
fmt.Sprintf("source_name='NoiseTorch Microphone' master=%s "+
|
|
"rate=48000 channels=1 "+
|
|
"label=noisetorch plugin=%s control=%d", inp.ID, ctx.librnnoise, ctx.config.Threshold))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded ladspa source as idx: %d\n", idx)
|
|
return nil
|
|
}
|
|
|
|
func loadPipeWireOutput(ctx *ntcontext, out *device) error {
|
|
log.Printf("Loading supressor for pipewire\n")
|
|
idx, err := loadModule(ctx, "module-ladspa-sink",
|
|
fmt.Sprintf("sink_name='NoiseTorch Headphones' master=%s "+
|
|
"rate=48000 channels=1 "+
|
|
"label=noisetorch plugin=%s control=%d", out.ID, ctx.librnnoise, ctx.config.Threshold))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded ladspa source as idx: %d\n", idx)
|
|
return nil
|
|
}
|
|
|
|
func loadPulseInput(ctx *ntcontext, inp *device) error {
|
|
log.Printf("Loading supressor for pulse\n")
|
|
idx, err := loadModule(ctx, "module-null-sink", "sink_name=nui_mic_denoised_out rate=48000")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded null sink as idx: %d\n", idx)
|
|
|
|
idx, err = loadModule(ctx, "module-ladspa-sink",
|
|
fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+
|
|
"label=noisetorch plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded ladspa sink as idx: %d\n", idx)
|
|
|
|
if inp.dynamicLatency {
|
|
idx, err = loadModule(ctx, "module-loopback",
|
|
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded loopback as idx: %d\n", idx)
|
|
} else {
|
|
idx, err = loadModule(ctx, "module-loopback",
|
|
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true adjust_time=1", inp.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded fixed latency loopback as idx: %d\n", idx)
|
|
}
|
|
|
|
idx, err = loadModule(ctx, "module-remap-source", `master=nui_mic_denoised_out.monitor `+
|
|
`source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("Loaded remap source as idx: %d\n", idx)
|
|
return nil
|
|
}
|
|
|
|
func loadPulseOutput(ctx *ntcontext, out *device) error {
|
|
_, err := loadModule(ctx, "module-null-sink", `sink_name=nui_out_out_sink`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = loadModule(ctx, "module-null-sink", `sink_name=nui_out_in_sink sink_properties="device.description='NoiseTorch Headphones'"`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = loadModule(ctx, "module-ladspa-sink", fmt.Sprintf(`sink_name=nui_out_ladspa sink_master=nui_out_out_sink `+
|
|
`label=noisetorch channels=1 plugin=%s control=%d rate=%d`,
|
|
ctx.librnnoise, ctx.config.Threshold, 48000))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = loadModule(ctx, "module-loopback",
|
|
fmt.Sprintf("source=nui_out_out_sink.monitor sink=%s channels=2 latency_msec=50 source_dont_move=true sink_dont_move=true", out.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = loadModule(ctx, "module-loopback",
|
|
fmt.Sprintf("source=nui_out_in_sink.monitor sink=nui_out_ladspa channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unloadSupressor(ctx *ntcontext) error {
|
|
if ctx.serverInfo.servertype == servertype_pipewire {
|
|
return unloadSupressorPipeWire(ctx)
|
|
} else {
|
|
return unloadSupressorPulse(ctx)
|
|
}
|
|
}
|
|
|
|
func unloadSupressorPipeWire(ctx *ntcontext) error {
|
|
log.Printf("Unloading modules for pipewire\n")
|
|
|
|
log.Printf("Searching for module-ladspa-source\n")
|
|
c := ctx.paClient
|
|
m, found, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found module-ladspa-source at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for module-ladspa-sink\n")
|
|
m, found, err = findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found module-ladspa-sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func unloadSupressorPulse(ctx *ntcontext) error {
|
|
log.Printf("Unloading modules for pulseaudio\n")
|
|
|
|
if pid, err := getPulsePid(); err == nil {
|
|
if lim, err := getRlimit(pid); err == nil {
|
|
log.Printf("Trying to remove rlimit. Limit is: %+v\n", lim)
|
|
removeRlimit(pid)
|
|
newLim, _ := getRlimit(pid)
|
|
log.Printf("Rlimit: %+v\n", newLim)
|
|
defer setRlimit(pid, &lim)
|
|
}
|
|
|
|
}
|
|
|
|
log.Printf("Searching for null-sink\n")
|
|
c := ctx.paClient
|
|
m, found, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found null-sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for ladspa-sink\n")
|
|
m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found ladspa-sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for loopback\n")
|
|
m, found, err = findModule(c, "module-loopback", "sink=nui_mic_raw_in")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found loopback at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for remap-source\n")
|
|
m, found, err = findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found remap source at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for output module-null-sink\n")
|
|
m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_out_sink")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for output module-null-sink\n")
|
|
m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_in_sink")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for output module-ladspa-sink\n")
|
|
m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found output ladspa sink at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for output module-loopback\n")
|
|
m, found, err = findModule(c, "module-loopback", "source=nui_out_out_sink.monitor")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
log.Printf("Searching for output module-loopback\n")
|
|
m, found, err = findModule(c, "module-loopback", "source=nui_out_in_sink.monitor")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found {
|
|
log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index)
|
|
c.UnloadModule(m.Index)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Finds a module by exactly matching the module name, and checking if the second string is a substring of the argument
|
|
func findModule(c *pulseaudio.Client, name string, argMatch string) (module pulseaudio.Module, found bool, err error) {
|
|
lst, err := c.ModuleList()
|
|
|
|
if err != nil {
|
|
return pulseaudio.Module{}, false, err
|
|
}
|
|
for _, m := range lst {
|
|
if m.Name == name && strings.Contains(m.Argument, argMatch) {
|
|
return m, true, nil
|
|
}
|
|
}
|
|
|
|
return pulseaudio.Module{}, false, nil
|
|
}
|