Adding support for noise reduction to the client audio chain.

This commit is contained in:
Marat Fayzullin 2022-11-17 18:51:04 -05:00
parent 90ed47a115
commit 5892ad5364
3 changed files with 43 additions and 9 deletions

View File

@ -1,15 +1,18 @@
from csdr.chain import Chain from csdr.chain import Chain
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit, NoiseFilter
from pycsdr.types import Format from pycsdr.types import Format
class Converter(Chain): class Converter(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int): def __init__(self, format: Format, inputRate: int, clientRate: int, nrEnabled: bool, nrThreshold: int):
workers = [] workers = []
# we only have an audio resampler and noise filter for float ATM,
# so if we need to resample or remove noise, we need to convert
if (inputRate != clientRate or nrEnabled) and format != Format.FLOAT:
workers += [Convert(format, Format.FLOAT)]
if nrEnabled:
workers += [NoiseFilter(nrThreshold)]
if inputRate != clientRate: if inputRate != clientRate:
# we only have an audio resampler for float ATM so if we need to resample, we need to convert
if format != Format.FLOAT:
workers += [Convert(format, Format.FLOAT)]
workers += [AudioResampler(inputRate, clientRate), Limit(), Convert(Format.FLOAT, Format.SHORT)] workers += [AudioResampler(inputRate, clientRate), Limit(), Convert(Format.FLOAT, Format.SHORT)]
elif format != Format.SHORT: elif format != Format.SHORT:
workers += [Convert(format, Format.SHORT)] workers += [Convert(format, Format.SHORT)]
@ -17,10 +20,12 @@ class Converter(Chain):
class ClientAudioChain(Chain): class ClientAudioChain(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str): def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str, nrEnabled: bool, nrThreshold: int):
self.format = format self.format = format
self.inputRate = inputRate self.inputRate = inputRate
self.clientRate = clientRate self.clientRate = clientRate
self.nrEnabled = nrEnabled
self.nrThreshold = nrThreshold
workers = [] workers = []
converter = self._buildConverter() converter = self._buildConverter()
if not converter.empty(): if not converter.empty():
@ -30,7 +35,7 @@ class ClientAudioChain(Chain):
super().__init__(workers) super().__init__(workers)
def _buildConverter(self): def _buildConverter(self):
return Converter(self.format, self.inputRate, self.clientRate) return Converter(self.format, self.inputRate, self.clientRate, self.nrEnabled, self.nrThreshold)
def _updateConverter(self): def _updateConverter(self):
converter = self._buildConverter() converter = self._buildConverter()
@ -70,3 +75,15 @@ class ClientAudioChain(Chain):
else: else:
if index >= 0: if index >= 0:
self.remove(index) self.remove(index)
def setNrEnabled(self, nrEnabled: bool) -> None:
if nrEnabled == self.nrEnabled:
return
self.nrEnabled = nrEnabled
self._updateConverter()
def setNrThreshold(self, nrThreshold: int) -> None:
if nrThreshold == self.nrThreshold:
return
self.nrThreshold = nrThreshold
self._updateConverter()

View File

@ -34,10 +34,12 @@ class ClientDemodulatorSecondaryDspEventClient(ABC):
class ClientDemodulatorChain(Chain): class ClientDemodulatorChain(Chain):
def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, hdOutputRate: int, audioCompression: str, secondaryDspEventReceiver: ClientDemodulatorSecondaryDspEventClient): def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, hdOutputRate: int, audioCompression: str, nrEnabled: bool, nrThreshold: int, secondaryDspEventReceiver: ClientDemodulatorSecondaryDspEventClient):
self.sampleRate = sampleRate self.sampleRate = sampleRate
self.outputRate = outputRate self.outputRate = outputRate
self.hdOutputRate = hdOutputRate self.hdOutputRate = hdOutputRate
self.nrEnabled = nrEnabled
self.nrThreshold = nrThreshold
self.secondaryDspEventReceiver = secondaryDspEventReceiver self.secondaryDspEventReceiver = secondaryDspEventReceiver
self.selector = Selector(sampleRate, outputRate) self.selector = Selector(sampleRate, outputRate)
self.selector.setBandpass(-4000, 4000) self.selector.setBandpass(-4000, 4000)
@ -50,7 +52,7 @@ class ClientDemodulatorChain(Chain):
self.wfmDeemphasisTau = 50e-6 self.wfmDeemphasisTau = 50e-6
inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate
oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate
self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression) self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression, nrEnabled, nrThreshold)
self.secondaryFftSize = 2048 self.secondaryFftSize = 2048
self.secondaryFftOverlapFactor = 0.3 self.secondaryFftOverlapFactor = 0.3
self.secondaryFftFps = 9 self.secondaryFftFps = 9
@ -251,6 +253,12 @@ class ClientDemodulatorChain(Chain):
def setAudioCompression(self, compression: str) -> None: def setAudioCompression(self, compression: str) -> None:
self.clientAudioChain.setAudioCompression(compression) self.clientAudioChain.setAudioCompression(compression)
def setNrEnabled(self, nrEnabled: bool) -> None:
self.clientAudioChain.setNrEnabled(nrEnabled)
def setNrThreshold(self, nrThreshold: int) -> None:
self.clientAudioChain.setNrThreshold(nrThreshold)
def setSquelchLevel(self, level: float) -> None: def setSquelchLevel(self, level: float) -> None:
if level == self.squelchLevel: if level == self.squelchLevel:
return return
@ -409,6 +417,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
"mod": ModulationValidator(), "mod": ModulationValidator(),
"secondary_offset_freq": "int", "secondary_offset_freq": "int",
"dmr_filter": "int", "dmr_filter": "int",
"nr_enabled": "bool",
"nr_threshold": "int",
} }
self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators) self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators)
@ -436,6 +446,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
output_rate=12000, output_rate=12000,
hd_output_rate=48000, hd_output_rate=48000,
digital_voice_codecserver="", digital_voice_codecserver="",
nr_enabled=False,
nr_threshold=10
).readonly() ).readonly()
) )
@ -445,6 +457,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.props["output_rate"], self.props["output_rate"],
self.props["hd_output_rate"], self.props["hd_output_rate"],
self.props["audio_compression"], self.props["audio_compression"],
self.props["nr_enabled"],
self.props["nr_threshold"],
self self
) )
@ -487,6 +501,8 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.props.wireProperty("wfm_deemphasis_tau", self.chain.setWfmDeemphasisTau), self.props.wireProperty("wfm_deemphasis_tau", self.chain.setWfmDeemphasisTau),
self.props.wireProperty("secondary_mod", self.setSecondaryDemodulator), self.props.wireProperty("secondary_mod", self.setSecondaryDemodulator),
self.props.wireProperty("secondary_offset_freq", self.chain.setSecondaryFrequencyOffset), self.props.wireProperty("secondary_offset_freq", self.chain.setSecondaryFrequencyOffset),
self.props.wireProperty("nr_enabled", self.chain.setNrEnabled),
self.props.wireProperty("nr_threshold", self.chain.setNrThreshold),
] ]
# wire power level output # wire power level output

View File

@ -94,4 +94,5 @@ validator_types = {
"int": IntegerValidator, "int": IntegerValidator,
"number": NumberValidator, "number": NumberValidator,
"num": NumberValidator, "num": NumberValidator,
"bool": BoolValidator,
} }