diff --git a/owrx/audio/chopper.py b/owrx/audio/chopper.py index cb910fd..d9c8851 100644 --- a/owrx/audio/chopper.py +++ b/owrx/audio/chopper.py @@ -74,5 +74,5 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber): def send(self, profile, line): data = self.parser.parse(profile, line) - if data is not None: + if data is not None and self.writer is not None: self.writer.write(pickle.dumps(data)) diff --git a/owrx/modes.py b/owrx/modes.py index 6bb292f..b6c73e4 100644 --- a/owrx/modes.py +++ b/owrx/modes.py @@ -10,8 +10,8 @@ class Bandpass(object): self.high_cut = high_cut -class Mode(object): - def __init__(self, modulation, name, bandpass: Bandpass = None, requirements=None, service=False, squelch=True): +class Mode: + def __init__(self, modulation: str, name: str, bandpass: Bandpass = None, requirements=None, service=False, squelch=True): self.modulation = modulation self.name = name self.requirements = requirements if requirements is not None else [] @@ -44,13 +44,16 @@ class DigitalMode(Mode): super().__init__(modulation, name, bandpass, requirements, service, squelch) self.underlying = underlying + def get_underlying_mode(self): + return Modes.findByModulation(self.underlying[0]) + def get_bandpass(self): if self.bandpass is not None: return self.bandpass - return Modes.findByModulation(self.underlying[0]).get_bandpass() + return self.get_underlying_mode().get_bandpass() def get_modulation(self): - return Modes.findByModulation(self.underlying[0]).get_modulation() + return self.get_underlying_mode().get_modulation() class AudioChopperMode(DigitalMode, metaclass=ABCMeta): diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 2206924..2a8d160 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -3,7 +3,6 @@ from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass from owrx.sdr import SdrService from owrx.bands import Bandplan from csdr.output import Output -from csdr import Dsp from owrx.wsjt import WsjtParser from owrx.aprs import AprsParser from owrx.js8 import Js8Parser @@ -14,7 +13,12 @@ from owrx.property import PropertyLayer, PropertyDeleted from js8py import Js8Frame from abc import ABCMeta, abstractmethod from owrx.service.schedule import ServiceScheduler -from owrx.modes import Modes +from owrx.service.chain import ServiceDemodulatorChain +from owrx.modes import Modes, DigitalMode +from typing import Union +from csdr.chain.demodulator import BaseDemodulatorChain, SecondaryDemodulator, FixedAudioRateChain +from csdr.chain.analog import NFm, Ssb +from csdr.chain.digimodes import AudioChopperDemodulator import logging @@ -294,21 +298,46 @@ class ServiceHandler(SdrSourceEventClient): output = Js8ServiceOutput(frequency) else: output = WsjtServiceOutput(frequency) - d = Dsp(output) - d.nc_port = source.getPort() - center_freq = source.getProps()["center_freq"] - d.set_offset_freq(frequency - center_freq) - d.set_center_freq(center_freq) + modeObject = Modes.findByModulation(mode) - d.set_demodulator(modeObject.get_modulation()) - d.set_bandpass(modeObject.get_bandpass()) - d.set_secondary_demodulator(mode) - d.set_audio_compression("none") - d.set_samp_rate(source.getProps()["samp_rate"]) - d.set_temporary_directory(CoreConfig().get_temporary_directory()) - d.set_service() - d.start() - return d + if not isinstance(modeObject, DigitalMode): + logger.warning("mode is not a digimode: %s", mode) + return None + + demod = self._getDemodulator(modeObject.get_modulation(), source.getProps()) + secondaryDemod = self._getSecondaryDemodulator(modeObject.modulation) + center_freq = source.getProps()["center_freq"] + sampleRate = source.getProps()["samp_rate"] + shift = (center_freq - frequency) / sampleRate + bandpass = modeObject.get_bandpass() + + chain = ServiceDemodulatorChain(demod, secondaryDemod, sampleRate, shift) + chain.setBandPass(bandpass.low_cut, bandpass.high_cut) + chain.setReader(source.getBuffer().getReader()) + return chain + + # TODO move this elsewhere + def _getDemodulator(self, demod: Union[str, BaseDemodulatorChain], props): + if isinstance(demod, BaseDemodulatorChain): + return demod + # TODO: move this to Modes + demodChain = None + if demod == "nfm": + demodChain = NFm(props["output_rate"]) + elif demod in ["usb", "lsb", "cw"]: + demodChain = Ssb() + + return demodChain + + # TODO move this elsewhere + def _getSecondaryDemodulator(self, mod): + if isinstance(mod, SecondaryDemodulator): + return mod + # TODO add remaining modes + if mod in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]: + return AudioChopperDemodulator(mod, WsjtParser()) + return None + class WsjtHandler(object): diff --git a/owrx/service/chain.py b/owrx/service/chain.py new file mode 100644 index 0000000..c275ae9 --- /dev/null +++ b/owrx/service/chain.py @@ -0,0 +1,21 @@ +from csdr.chain import Chain +from csdr.chain.selector import Selector +from csdr.chain.demodulator import BaseDemodulatorChain, SecondaryDemodulator, FixedAudioRateChain + + +class ServiceDemodulatorChain(Chain): + def __init__(self, demod: BaseDemodulatorChain, secondaryDemod: SecondaryDemodulator, sampleRate: int, shiftRate: float): + # TODO magic number... check if this edge case even exsists and change the api if possible + rate = secondaryDemod.getFixedAudioRate() if isinstance(secondaryDemod, FixedAudioRateChain) else 1200 + + self.selector = Selector(sampleRate, rate, shiftRate) + + workers = [ + self.selector, + demod, + secondaryDemod + ] + super().__init__(workers) + + def setBandPass(self, lowCut, highCut): + self.selector.setBandpass(lowCut, highCut)