add config option for rbds decoding (US)

This commit is contained in:
Jakob Ketterl 2024-01-18 16:59:10 +01:00
parent 91a7612d6b
commit 8b0b05e31d
7 changed files with 51 additions and 11 deletions

View file

@ -1,4 +1,5 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, DeemphasisTauChain, MetaProvider
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, DeemphasisTauChain, \
MetaProvider, RdsChain
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, WfmDeemphasis, FractionalDecimator, \
RealPart, Writer, Buffer
from pycsdr.types import Format, AgcProfile
@ -42,10 +43,11 @@ class NFm(BaseDemodulatorChain):
self.replace(2, NfmDeemphasis(sampleRate))
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio, MetaProvider):
def __init__(self, sampleRate: int, tau: float):
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio, MetaProvider, RdsChain):
def __init__(self, sampleRate: int, tau: float, rdsRbds: bool):
self.sampleRate = sampleRate
self.tau = tau
self.rdsRbds = rdsRbds
self.limit = Limit()
# this buffer is used to tap into the raw audio stream for redsea RDS decoding
self.metaTapBuffer = Buffer(Format.FLOAT)
@ -56,6 +58,7 @@ class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAu
WfmDeemphasis(self.sampleRate, self.tau),
]
self.metaChain = None
self.metaWriter = None
super().__init__(workers)
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
@ -83,15 +86,25 @@ class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAu
if not FeatureDetector().is_available("redsea"):
return
if self.metaChain is None:
self.metaChain = Redsea(self.getFixedIfSampleRate())
self.metaChain = Redsea(self.getFixedIfSampleRate(), self.rdsRbds)
self.metaChain.setReader(self.metaTapBuffer.getReader())
self.metaChain.setWriter(writer)
self.metaWriter = writer
self.metaChain.setWriter(self.metaWriter)
def stop(self):
super().stop()
if self.metaChain is not None:
self.metaChain.stop()
self.metaChain = None
self.metaWriter = None
def setRdsRbds(self, rdsRbds: bool) -> None:
self.rdsRbds = rdsRbds
if self.metaChain is not None:
self.metaChain.stop()
self.metaChain = Redsea(self.getFixedIfSampleRate(), self.rdsRbds)
self.metaChain.setReader(self.metaTapBuffer.getReader())
self.metaChain.setWriter(self.metaWriter)
class Ssb(BaseDemodulatorChain):

View file

@ -49,6 +49,12 @@ class DeemphasisTauChain(ABC):
pass
class RdsChain(ABC):
@abstractmethod
def setRdsRbds(self, rdsRbds: bool) -> None:
pass
class BaseDemodulatorChain(Chain):
def supportsSquelch(self) -> bool:
return True

View file

@ -6,9 +6,9 @@ from csdr.module import JsonParser
class Redsea(Chain):
def __init__(self, sampleRate: int):
def __init__(self, sampleRate: int, rbds: bool):
super().__init__([
Convert(Format.FLOAT, Format.SHORT),
RedseaModule(sampleRate),
RedseaModule(sampleRate, rbds),
JsonParser("WFM"),
])

View file

@ -17,6 +17,7 @@ defaultConfig = PropertyLayer(
audio_compression="adpcm",
fft_compression="adpcm",
wfm_deemphasis_tau=50e-6,
wfm_rds_rbds=False,
digimodes_fft_size=2048,
digital_voice_dmr_id_lookup=True,
digital_voice_nxdn_id_lookup=True,

View file

@ -32,6 +32,10 @@ class DecodingSettingsController(SettingsFormController):
infotext='See <a href="https://en.wikipedia.org/wiki/FM_broadcasting#Pre-emphasis_and_de-emphasis"'
+ ' target="_blank">this Wikipedia article</a> for more information',
),
CheckboxInput(
"wfm_rds_rbds",
"Enable RBDS decoding (US RDS standard)",
),
),
Section(
"Digital voice",

View file

@ -3,7 +3,7 @@ from owrx.property import PropertyStack, PropertyLayer, PropertyValidator, Prope
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes, DigitalMode
from csdr.chain import Chain
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator, DialFrequencyReceiver, MetaProvider, SlotFilterChain, SecondarySelectorChain, DeemphasisTauChain, DemodulatorError
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator, DialFrequencyReceiver, MetaProvider, SlotFilterChain, SecondarySelectorChain, DeemphasisTauChain, DemodulatorError, RdsChain
from csdr.chain.selector import Selector, SecondarySelector
from csdr.chain.clientaudio import ClientAudioChain
from csdr.chain.fft import FftChain
@ -47,6 +47,7 @@ class ClientDemodulatorChain(Chain):
self.centerFrequency = None
self.frequencyOffset = None
self.wfmDeemphasisTau = 50e-6
self.rdsRbds = False
inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate
oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate
self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression)
@ -109,6 +110,9 @@ class ClientDemodulatorChain(Chain):
if isinstance(self.demodulator, DeemphasisTauChain):
self.demodulator.setDeemphasisTau(self.wfmDeemphasisTau)
if isinstance(self.demodulator, RdsChain):
self.demodulator.setRdsRbds(self.rdsRbds)
self._updateDialFrequency()
self._syncSquelch()
@ -377,6 +381,13 @@ class ClientDemodulatorChain(Chain):
if isinstance(self.demodulator, DeemphasisTauChain):
self.demodulator.setDeemphasisTau(self.wfmDeemphasisTau)
def setRdsRbds(self, rdsRbds: bool) -> None:
if rdsRbds == self.rdsRbds:
return
self.rdsRbds = rdsRbds
if isinstance(self.demodulator, RdsChain):
self.demodulator.setRdsRbds(self.rdsRbds)
class ModulationValidator(OrValidator):
"""
@ -427,6 +438,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
"start_mod",
"start_freq",
"wfm_deemphasis_tau",
"wfm_rds_rbds",
"digital_voice_codecserver",
),
)
@ -491,6 +503,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.props.wireProperty("mod", self.setDemodulator),
self.props.wireProperty("dmr_filter", self.chain.setSlotFilter),
self.props.wireProperty("wfm_deemphasis_tau", self.chain.setWfmDeemphasisTau),
self.props.wireProperty("wfm_rds_rbds", self.chain.setRdsRbds),
self.props.wireProperty("secondary_mod", self.setSecondaryDemodulator),
self.props.wireProperty("secondary_offset_freq", self.chain.setSecondaryFrequencyOffset),
]
@ -532,7 +545,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
return NFm(self.props["output_rate"])
elif demod == "wfm":
from csdr.chain.analog import WFm
return WFm(self.props["hd_output_rate"], self.props["wfm_deemphasis_tau"])
return WFm(self.props["hd_output_rate"], self.props["wfm_deemphasis_tau"], self.props["wfm_rds_rbds"])
elif demod == "am":
from csdr.chain.analog import Am
return Am()

View file

@ -3,9 +3,12 @@ from pycsdr.types import Format
class RedseaModule(ExecModule):
def __init__(self, sampleRate: int):
def __init__(self, sampleRate: int, rbds: bool):
args = ["redsea", "--input", "mpx", "--samplerate", str(sampleRate)]
if rbds:
args += ["--rbds"]
super().__init__(
Format.SHORT,
Format.CHAR,
["redsea", "--input", "mpx", "--samplerate", str(sampleRate)]
args
)