2019-12-21 19:58:28 +00:00
|
|
|
from owrx.wsjt import WsjtParser
|
2020-04-12 11:10:23 +00:00
|
|
|
from owrx.js8 import Js8Parser
|
2021-03-18 18:34:53 +00:00
|
|
|
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
2021-01-24 21:29:23 +00:00
|
|
|
from owrx.property import PropertyStack, PropertyLayer, PropertyValidator
|
|
|
|
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
|
2020-04-26 20:46:30 +00:00
|
|
|
from owrx.modes import Modes
|
2021-04-09 16:16:25 +00:00
|
|
|
from csdr.output import Output
|
2021-08-23 12:25:28 +00:00
|
|
|
from csdr.chain import Chain
|
2021-08-31 20:46:11 +00:00
|
|
|
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator, DialFrequencyReceiver
|
2021-08-23 12:25:28 +00:00
|
|
|
from csdr.chain.selector import Selector
|
|
|
|
from csdr.chain.clientaudio import ClientAudioChain
|
|
|
|
from csdr.chain.analog import NFm, WFm, Am, Ssb
|
2021-08-26 13:58:02 +00:00
|
|
|
from csdr.chain.digiham import DigihamChain, Dmr, Dstar, Nxdn, Ysf
|
2021-09-07 12:45:52 +00:00
|
|
|
from csdr.chain.m17 import M17Chain
|
2021-09-13 14:58:02 +00:00
|
|
|
from csdr.chain.freedv import FreeDV
|
2021-09-07 15:31:32 +00:00
|
|
|
from csdr.chain.drm import Drm
|
2021-08-27 22:10:46 +00:00
|
|
|
from csdr.chain.fft import FftChain
|
2021-09-06 18:00:14 +00:00
|
|
|
from csdr.chain.digimodes import AudioChopperDemodulator, PacketDemodulator, PocsagDemodulator
|
2021-08-23 12:25:28 +00:00
|
|
|
from pycsdr.modules import Buffer, Writer
|
|
|
|
from pycsdr.types import Format
|
|
|
|
from typing import Union
|
2021-09-12 22:14:38 +00:00
|
|
|
from io import BytesIO
|
2019-12-21 19:58:28 +00:00
|
|
|
import threading
|
2021-01-24 21:29:23 +00:00
|
|
|
import re
|
2021-09-12 22:14:38 +00:00
|
|
|
import pickle
|
2019-12-21 19:58:28 +00:00
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
class ClientDemodulatorChain(Chain):
|
2021-08-27 16:30:46 +00:00
|
|
|
def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, hdOutputRate: int, audioCompression: str):
|
2021-08-23 12:25:28 +00:00
|
|
|
self.sampleRate = sampleRate
|
|
|
|
self.outputRate = outputRate
|
2021-08-27 16:30:46 +00:00
|
|
|
self.hdOutputRate = hdOutputRate
|
2021-08-23 12:25:28 +00:00
|
|
|
self.selector = Selector(sampleRate, outputRate, 0.0)
|
|
|
|
self.selector.setBandpass(-4000, 4000)
|
2021-08-27 22:10:46 +00:00
|
|
|
self.selectorBuffer = Buffer(Format.COMPLEX_FLOAT)
|
2021-08-31 14:54:37 +00:00
|
|
|
self.audioBuffer = None
|
2021-08-23 12:25:28 +00:00
|
|
|
self.demodulator = demod
|
2021-08-31 14:54:37 +00:00
|
|
|
self.secondaryDemodulator = None
|
2021-08-31 20:46:11 +00:00
|
|
|
self.centerFrequency = None
|
|
|
|
self.frequencyOffset = None
|
2021-08-27 16:30:46 +00:00
|
|
|
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)
|
2021-08-27 22:10:46 +00:00
|
|
|
self.secondaryFftChain = None
|
2021-08-26 13:58:02 +00:00
|
|
|
self.metaWriter = None
|
2021-08-31 14:54:37 +00:00
|
|
|
self.secondaryFftWriter = None
|
|
|
|
self.secondaryWriter = None
|
2021-08-26 13:58:02 +00:00
|
|
|
self.squelchLevel = -150
|
2021-08-23 12:25:28 +00:00
|
|
|
super().__init__([self.selector, self.demodulator, self.clientAudioChain])
|
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
def stop(self):
|
|
|
|
super().stop()
|
|
|
|
if self.secondaryFftChain is not None:
|
|
|
|
self.secondaryFftChain.stop()
|
|
|
|
self.secondaryFftChain = None
|
|
|
|
if self.secondaryDemodulator is not None:
|
|
|
|
self.secondaryDemodulator.stop()
|
|
|
|
self.secondaryDemodulator = None
|
|
|
|
|
2021-08-27 22:10:46 +00:00
|
|
|
def _connect(self, w1, w2, buffer: Union[Buffer, None] = None) -> None:
|
|
|
|
if w1 is self.selector:
|
|
|
|
super()._connect(w1, w2, self.selectorBuffer)
|
2021-08-31 14:54:37 +00:00
|
|
|
elif w2 is self.clientAudioChain:
|
|
|
|
format = w1.getOutputFormat()
|
|
|
|
if self.audioBuffer is None or self.audioBuffer.getFormat() != format:
|
|
|
|
self.audioBuffer = Buffer(format)
|
2021-09-06 13:05:33 +00:00
|
|
|
if self.secondaryDemodulator is not None and self.secondaryDemodulator.getInputFormat() is not Format.COMPLEX_FLOAT:
|
2021-08-31 14:54:37 +00:00
|
|
|
self.secondaryDemodulator.setReader(self.audioBuffer.getReader())
|
|
|
|
super()._connect(w1, w2, self.audioBuffer)
|
2021-08-27 22:10:46 +00:00
|
|
|
else:
|
|
|
|
super()._connect(w1, w2)
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
def setDemodulator(self, demodulator: BaseDemodulatorChain):
|
2021-08-31 14:54:37 +00:00
|
|
|
if demodulator is self.demodulator:
|
|
|
|
return
|
|
|
|
|
2021-08-26 13:58:02 +00:00
|
|
|
try:
|
|
|
|
self.clientAudioChain.setFormat(demodulator.getOutputFormat())
|
|
|
|
except ValueError:
|
|
|
|
# this will happen if the new format does not match the current demodulator.
|
|
|
|
# it's expected and should be mended when swapping out the demodulator in the next step
|
|
|
|
pass
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
self.replace(1, demodulator)
|
|
|
|
|
|
|
|
if self.demodulator is not None:
|
|
|
|
self.demodulator.stop()
|
|
|
|
|
|
|
|
self.demodulator = demodulator
|
|
|
|
|
2021-08-27 16:30:46 +00:00
|
|
|
outputRate = self.hdOutputRate if isinstance(self.demodulator, HdAudio) else self.outputRate
|
|
|
|
|
2021-08-27 15:34:48 +00:00
|
|
|
if isinstance(self.demodulator, FixedIfSampleRateChain):
|
|
|
|
self.selector.setOutputRate(self.demodulator.getFixedIfSampleRate())
|
2021-09-06 13:05:33 +00:00
|
|
|
elif isinstance(self.secondaryDemodulator, FixedAudioRateChain):
|
2021-08-31 14:54:37 +00:00
|
|
|
self.selector.setOutputRate(self.secondaryDemodulator.getFixedAudioRate())
|
2021-08-23 12:25:28 +00:00
|
|
|
else:
|
2021-08-27 16:30:46 +00:00
|
|
|
self.selector.setOutputRate(outputRate)
|
2021-09-06 13:05:33 +00:00
|
|
|
self.demodulator.setSampleRate(outputRate)
|
2021-08-23 12:25:28 +00:00
|
|
|
|
2021-08-27 15:34:48 +00:00
|
|
|
if isinstance(self.demodulator, FixedAudioRateChain):
|
|
|
|
self.clientAudioChain.setInputRate(self.demodulator.getFixedAudioRate())
|
2021-09-06 13:05:33 +00:00
|
|
|
elif isinstance(self.secondaryDemodulator, FixedAudioRateChain):
|
2021-08-31 14:54:37 +00:00
|
|
|
self.clientAudioChain.setInputRate(self.secondaryDemodulator.getFixedAudioRate())
|
2021-08-23 12:25:28 +00:00
|
|
|
else:
|
2021-08-27 16:30:46 +00:00
|
|
|
self.clientAudioChain.setInputRate(outputRate)
|
2021-08-23 12:25:28 +00:00
|
|
|
|
2021-09-09 20:24:41 +00:00
|
|
|
self._updateDialFrequency()
|
2021-09-06 13:05:33 +00:00
|
|
|
self._syncSquelch()
|
2021-08-23 12:25:28 +00:00
|
|
|
|
2021-08-27 16:30:46 +00:00
|
|
|
self.clientAudioChain.setClientRate(outputRate)
|
|
|
|
|
2021-08-26 13:58:02 +00:00
|
|
|
if self.metaWriter is not None and isinstance(demodulator, DigihamChain):
|
|
|
|
demodulator.setMetaWriter(self.metaWriter)
|
2021-08-23 12:25:28 +00:00
|
|
|
|
2021-09-06 13:05:33 +00:00
|
|
|
def _getSelectorOutputRate(self):
|
|
|
|
if isinstance(self.secondaryDemodulator, FixedAudioRateChain):
|
|
|
|
if isinstance(self.demodulator, FixedAudioRateChain) and self.demodulator.getFixedAudioRate() != self.secondaryDemodulator.getFixedAudioRate():
|
|
|
|
raise ValueError("secondary and primary demodulator chain audio rates do not match!")
|
|
|
|
return self.secondaryDemodulator.getFixedAudioRate()
|
|
|
|
return self.outputRate
|
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
def setSecondaryDemodulator(self, demod: Union[SecondaryDemodulator, None]):
|
|
|
|
if demod is self.secondaryDemodulator:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.secondaryDemodulator is not None:
|
|
|
|
self.secondaryDemodulator.stop()
|
|
|
|
|
|
|
|
self.secondaryDemodulator = demod
|
|
|
|
|
2021-09-06 13:05:33 +00:00
|
|
|
rate = self._getSelectorOutputRate()
|
2021-08-31 14:54:37 +00:00
|
|
|
self.selector.setOutputRate(rate)
|
|
|
|
self.clientAudioChain.setInputRate(rate)
|
2021-09-06 13:05:33 +00:00
|
|
|
self.demodulator.setSampleRate(rate)
|
2021-08-31 20:46:11 +00:00
|
|
|
self._updateDialFrequency()
|
2021-09-06 13:05:33 +00:00
|
|
|
self._syncSquelch()
|
2021-08-31 14:54:37 +00:00
|
|
|
|
|
|
|
if self.secondaryDemodulator is not None:
|
2021-09-06 13:05:33 +00:00
|
|
|
if self.secondaryDemodulator.getInputFormat() is Format.COMPLEX_FLOAT:
|
|
|
|
self.secondaryDemodulator.setReader(self.selectorBuffer.getReader())
|
|
|
|
else:
|
|
|
|
self.secondaryDemodulator.setReader(self.audioBuffer.getReader())
|
2021-08-31 14:54:37 +00:00
|
|
|
self.secondaryDemodulator.setWriter(self.secondaryWriter)
|
|
|
|
|
|
|
|
if self.secondaryDemodulator is None and self.secondaryFftChain is not None:
|
|
|
|
self.secondaryFftChain.stop()
|
|
|
|
self.secondaryFftChain = None
|
|
|
|
|
|
|
|
if self.secondaryDemodulator is not None and self.secondaryFftChain is None:
|
|
|
|
# TODO eliminate constants
|
2021-09-06 13:05:33 +00:00
|
|
|
self.secondaryFftChain = FftChain(self._getSelectorOutputRate(), 2048, 0.3, 9, "adpcm")
|
2021-08-31 14:54:37 +00:00
|
|
|
self.secondaryFftChain.setReader(self.selectorBuffer.getReader())
|
|
|
|
self.secondaryFftChain.setWriter(self.secondaryFftWriter)
|
|
|
|
|
2021-09-06 13:05:33 +00:00
|
|
|
if self.secondaryFftChain is not None:
|
|
|
|
self.secondaryFftChain.setSampleRate(rate)
|
|
|
|
|
|
|
|
def _syncSquelch(self):
|
|
|
|
if not self.demodulator.supportsSquelch() or (self.secondaryDemodulator is not None and not self.secondaryDemodulator.supportsSquelch()):
|
|
|
|
self.selector.setSquelchLevel(-150)
|
|
|
|
else:
|
|
|
|
self.selector.setSquelchLevel(self.squelchLevel)
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
def setLowCut(self, lowCut):
|
|
|
|
self.selector.setLowCut(lowCut)
|
|
|
|
|
|
|
|
def setHighCut(self, highCut):
|
|
|
|
self.selector.setHighCut(highCut)
|
|
|
|
|
|
|
|
def setBandpass(self, lowCut, highCut):
|
|
|
|
self.selector.setBandpass(lowCut, highCut)
|
|
|
|
|
|
|
|
def setFrequencyOffset(self, offset: int) -> None:
|
2021-08-31 20:46:11 +00:00
|
|
|
if offset == self.frequencyOffset:
|
|
|
|
return
|
|
|
|
self.frequencyOffset = offset
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
shift = -offset / self.sampleRate
|
|
|
|
self.selector.setShiftRate(shift)
|
|
|
|
|
2021-08-31 20:46:11 +00:00
|
|
|
self._updateDialFrequency()
|
|
|
|
|
|
|
|
def setCenterFrequency(self, frequency: int) -> None:
|
|
|
|
if frequency == self.centerFrequency:
|
|
|
|
return
|
|
|
|
self.centerFrequency = frequency
|
|
|
|
self._updateDialFrequency()
|
|
|
|
|
|
|
|
def _updateDialFrequency(self):
|
|
|
|
if self.centerFrequency is None or self.frequencyOffset is None:
|
|
|
|
return
|
|
|
|
dialFrequency = self.centerFrequency + self.frequencyOffset
|
2021-09-09 13:11:33 +00:00
|
|
|
if isinstance(self.demodulator, DialFrequencyReceiver):
|
|
|
|
self.demodulator.setDialFrequency(dialFrequency)
|
2021-08-31 20:46:11 +00:00
|
|
|
if isinstance(self.secondaryDemodulator, DialFrequencyReceiver):
|
|
|
|
self.secondaryDemodulator.setDialFrequency(dialFrequency)
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
def setAudioCompression(self, compression: str) -> None:
|
|
|
|
self.clientAudioChain.setAudioCompression(compression)
|
|
|
|
|
|
|
|
def setSquelchLevel(self, level: float) -> None:
|
2021-08-26 13:58:02 +00:00
|
|
|
if level == self.squelchLevel:
|
|
|
|
return
|
|
|
|
self.squelchLevel = level
|
2021-09-06 13:05:33 +00:00
|
|
|
self._syncSquelch()
|
2021-08-23 12:25:28 +00:00
|
|
|
|
|
|
|
def setOutputRate(self, outputRate) -> None:
|
|
|
|
if outputRate == self.outputRate:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.outputRate = outputRate
|
2021-08-27 16:30:46 +00:00
|
|
|
|
|
|
|
if isinstance(self.demodulator, HdAudio):
|
|
|
|
return
|
|
|
|
if not isinstance(self.demodulator, FixedIfSampleRateChain):
|
|
|
|
self.selector.setOutputRate(outputRate)
|
2021-09-06 13:05:33 +00:00
|
|
|
self.demodulator.setSampleRate(outputRate)
|
2021-08-27 16:30:46 +00:00
|
|
|
if not isinstance(self.demodulator, FixedAudioRateChain):
|
|
|
|
self.clientAudioChain.setClientRate(outputRate)
|
|
|
|
|
|
|
|
def setHdOutputRate(self, outputRate) -> None:
|
|
|
|
if outputRate == self.hdOutputRate:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.hdOutputRate = outputRate
|
|
|
|
|
|
|
|
if not isinstance(self.demodulator, HdAudio):
|
|
|
|
return
|
2021-08-27 15:34:48 +00:00
|
|
|
if not isinstance(self.demodulator, FixedIfSampleRateChain):
|
2021-08-23 12:25:28 +00:00
|
|
|
self.selector.setOutputRate(outputRate)
|
2021-08-27 15:34:48 +00:00
|
|
|
if not isinstance(self.demodulator, FixedAudioRateChain):
|
2021-08-23 12:25:28 +00:00
|
|
|
self.clientAudioChain.setClientRate(outputRate)
|
|
|
|
|
2021-08-27 22:10:46 +00:00
|
|
|
def setSampleRate(self, sampleRate: int) -> None:
|
|
|
|
if sampleRate == self.sampleRate:
|
|
|
|
return
|
|
|
|
self.sampleRate = sampleRate
|
|
|
|
self.selector.setInputRate(sampleRate)
|
|
|
|
# TODO update secondary FFT
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
def setPowerWriter(self, writer: Writer) -> None:
|
|
|
|
self.selector.setPowerWriter(writer)
|
|
|
|
|
2021-08-26 13:58:02 +00:00
|
|
|
def setMetaWriter(self, writer: Writer) -> None:
|
|
|
|
if writer is self.metaWriter:
|
|
|
|
return
|
|
|
|
self.metaWriter = writer
|
|
|
|
if isinstance(self.demodulator, DigihamChain):
|
|
|
|
self.demodulator.setMetaWriter(self.metaWriter)
|
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
def setSecondaryFftWriter(self, writer: Writer) -> None:
|
|
|
|
if writer is self.secondaryFftWriter:
|
|
|
|
return
|
|
|
|
self.secondaryFftWriter = writer
|
2021-08-27 22:10:46 +00:00
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
if self.secondaryFftChain is not None:
|
2021-08-27 22:10:46 +00:00
|
|
|
self.secondaryFftChain.setWriter(writer)
|
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
def setSecondaryWriter(self, writer: Writer) -> None:
|
|
|
|
if writer is self.secondaryWriter:
|
|
|
|
return
|
|
|
|
self.secondaryWriter = writer
|
|
|
|
if self.secondaryDemodulator is not None:
|
|
|
|
self.secondaryDemodulator.setWriter(writer)
|
|
|
|
|
2021-09-07 15:37:32 +00:00
|
|
|
def setDmrFilter(self, filter: int) -> None:
|
|
|
|
if not isinstance(self.demodulator, Dmr):
|
|
|
|
return
|
|
|
|
self.demodulator.setSlotFilter(filter)
|
|
|
|
|
2021-08-27 22:10:46 +00:00
|
|
|
def setSecondaryFftSize(self, size: int) -> None:
|
|
|
|
# TODO
|
|
|
|
pass
|
2021-08-23 12:25:28 +00:00
|
|
|
|
|
|
|
|
2021-01-24 21:29:23 +00:00
|
|
|
class ModulationValidator(OrValidator):
|
|
|
|
"""
|
|
|
|
This validator only allows alphanumeric characters and numbers, but no spaces or special characters
|
|
|
|
"""
|
2021-01-24 21:54:58 +00:00
|
|
|
|
2021-01-24 21:29:23 +00:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$")))
|
|
|
|
|
|
|
|
|
2021-04-09 16:16:25 +00:00
|
|
|
class DspManager(Output, SdrSourceEventClient):
|
2019-12-21 19:58:28 +00:00
|
|
|
def __init__(self, handler, sdrSource):
|
|
|
|
self.handler = handler
|
|
|
|
self.sdrSource = sdrSource
|
|
|
|
|
2020-03-24 21:13:42 +00:00
|
|
|
self.props = PropertyStack()
|
2021-01-24 21:29:23 +00:00
|
|
|
|
2020-03-24 21:13:42 +00:00
|
|
|
# local demodulator properties not forwarded to the sdr
|
2021-01-24 21:29:23 +00:00
|
|
|
# ensure strict validation since these can be set from the client
|
2021-01-24 21:47:08 +00:00
|
|
|
# and are used to build executable commands
|
|
|
|
validators = {
|
|
|
|
"output_rate": "int",
|
|
|
|
"hd_output_rate": "int",
|
|
|
|
"squelch_level": "num",
|
|
|
|
"secondary_mod": ModulationValidator(),
|
|
|
|
"low_cut": "num",
|
|
|
|
"high_cut": "num",
|
|
|
|
"offset_freq": "int",
|
|
|
|
"mod": ModulationValidator(),
|
|
|
|
"secondary_offset_freq": "int",
|
|
|
|
"dmr_filter": "int",
|
|
|
|
}
|
|
|
|
self.localProps = PropertyValidator(PropertyLayer().filter(*validators.keys()), validators)
|
|
|
|
|
2021-01-24 19:10:37 +00:00
|
|
|
self.props.addLayer(0, self.localProps)
|
2020-03-24 21:13:42 +00:00
|
|
|
# properties that we inherit from the sdr
|
2020-12-30 16:46:13 +00:00
|
|
|
self.props.addLayer(
|
|
|
|
1,
|
|
|
|
self.sdrSource.getProps().filter(
|
|
|
|
"audio_compression",
|
|
|
|
"fft_compression",
|
|
|
|
"digimodes_fft_size",
|
|
|
|
"samp_rate",
|
|
|
|
"center_freq",
|
|
|
|
"start_mod",
|
|
|
|
"start_freq",
|
|
|
|
"wfm_deemphasis_tau",
|
2021-05-29 16:50:17 +00:00
|
|
|
"digital_voice_codecserver",
|
2020-12-30 16:46:13 +00:00
|
|
|
),
|
|
|
|
)
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
# TODO wait for the rate to come from the client
|
|
|
|
if "output_rate" not in self.props:
|
|
|
|
self.props["output_rate"] = 12000
|
2021-08-27 16:30:46 +00:00
|
|
|
if "hd_output_rate" not in self.props:
|
|
|
|
self.props["hd_output_rate"] = 48000
|
2021-08-23 12:25:28 +00:00
|
|
|
|
|
|
|
self.chain = ClientDemodulatorChain(
|
|
|
|
self._getDemodulator("nfm"),
|
|
|
|
self.props["samp_rate"],
|
|
|
|
self.props["output_rate"],
|
2021-08-27 16:30:46 +00:00
|
|
|
self.props["hd_output_rate"],
|
2021-08-23 12:25:28 +00:00
|
|
|
self.props["audio_compression"]
|
|
|
|
)
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-08-27 14:11:03 +00:00
|
|
|
self.readers = {}
|
2021-08-26 15:21:52 +00:00
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
# wire audio output
|
|
|
|
buffer = Buffer(self.chain.getOutputFormat())
|
|
|
|
self.chain.setWriter(buffer)
|
2021-08-27 16:30:46 +00:00
|
|
|
# TODO check for hd audio
|
2021-08-26 15:21:52 +00:00
|
|
|
self.wireOutput("audio", buffer)
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
# wire power level output
|
|
|
|
buffer = Buffer(Format.FLOAT)
|
|
|
|
self.chain.setPowerWriter(buffer)
|
2021-08-26 15:21:52 +00:00
|
|
|
self.wireOutput("smeter", buffer)
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-08-26 13:58:02 +00:00
|
|
|
# wire meta output
|
|
|
|
buffer = Buffer(Format.CHAR)
|
|
|
|
self.chain.setMetaWriter(buffer)
|
2021-08-26 15:21:52 +00:00
|
|
|
self.wireOutput("meta", buffer)
|
2021-08-26 13:58:02 +00:00
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
# wire secondary FFT
|
|
|
|
# TODO format is different depending on compression
|
|
|
|
buffer = Buffer(Format.CHAR)
|
|
|
|
self.chain.setSecondaryFftWriter(buffer)
|
|
|
|
self.wireOutput("secondary_fft", buffer)
|
|
|
|
|
|
|
|
# wire secondary demodulator
|
|
|
|
buffer = Buffer(Format.CHAR)
|
|
|
|
self.chain.setSecondaryWriter(buffer)
|
|
|
|
# TODO there's multiple outputs depending on the modulation right now
|
2021-09-06 13:05:33 +00:00
|
|
|
self.wireOutput("secondary_demod", buffer)
|
2021-08-31 14:54:37 +00:00
|
|
|
|
2020-04-26 20:46:30 +00:00
|
|
|
if "start_mod" in self.props:
|
2021-08-23 12:25:28 +00:00
|
|
|
self.setDemodulator(self.props["start_mod"])
|
2020-04-26 20:46:30 +00:00
|
|
|
mode = Modes.findByModulation(self.props["start_mod"])
|
|
|
|
|
|
|
|
if mode and mode.bandpass:
|
2021-08-23 12:25:28 +00:00
|
|
|
bpf = [mode.bandpass.low_cut, mode.bandpass.high_cut]
|
|
|
|
self.chain.setBandpass(*bpf)
|
2020-04-26 20:46:30 +00:00
|
|
|
|
|
|
|
if "start_freq" in self.props and "center_freq" in self.props:
|
2021-08-23 12:25:28 +00:00
|
|
|
self.chain.setFrequencyOffset(self.props["start_freq"] - self.props["center_freq"])
|
2020-04-26 20:46:30 +00:00
|
|
|
else:
|
2021-08-23 12:25:28 +00:00
|
|
|
self.chain.setFrequencyOffset(0)
|
2020-04-26 20:46:30 +00:00
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
self.subscriptions = [
|
2021-08-27 14:11:03 +00:00
|
|
|
self.props.wireProperty("audio_compression", self.setAudioCompression),
|
2021-08-23 12:25:28 +00:00
|
|
|
# probably unused:
|
|
|
|
# self.props.wireProperty("fft_compression", self.dsp.set_fft_compression),
|
2021-08-27 22:10:46 +00:00
|
|
|
self.props.wireProperty("digimodes_fft_size", self.chain.setSecondaryFftSize),
|
2021-08-23 12:25:28 +00:00
|
|
|
self.props.wireProperty("samp_rate", self.chain.setSampleRate),
|
|
|
|
self.props.wireProperty("output_rate", self.chain.setOutputRate),
|
2021-08-27 16:30:46 +00:00
|
|
|
self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate),
|
2021-08-23 12:25:28 +00:00
|
|
|
self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset),
|
2021-08-31 20:46:11 +00:00
|
|
|
self.props.wireProperty("center_freq", self.chain.setCenterFrequency),
|
2021-08-23 12:25:28 +00:00
|
|
|
self.props.wireProperty("squelch_level", self.chain.setSquelchLevel),
|
|
|
|
self.props.wireProperty("low_cut", self.chain.setLowCut),
|
|
|
|
self.props.wireProperty("high_cut", self.chain.setHighCut),
|
|
|
|
self.props.wireProperty("mod", self.setDemodulator),
|
2021-09-07 15:37:32 +00:00
|
|
|
self.props.wireProperty("dmr_filter", self.chain.setDmrFilter),
|
2021-08-23 12:25:28 +00:00
|
|
|
# TODO
|
|
|
|
# self.props.wireProperty("wfm_deemphasis_tau", self.dsp.set_wfm_deemphasis_tau),
|
|
|
|
# TODO
|
|
|
|
# self.props.wireProperty("digital_voice_codecserver", self.dsp.set_codecserver),
|
2019-12-21 19:58:28 +00:00
|
|
|
]
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
# TODO
|
|
|
|
# sp.set_temporary_directory(CoreConfig().get_temporary_directory())
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-04-17 22:50:13 +00:00
|
|
|
def set_secondary_mod(mod):
|
|
|
|
if mod == False:
|
|
|
|
mod = None
|
|
|
|
self.dsp.set_secondary_demodulator(mod)
|
2021-08-27 22:10:46 +00:00
|
|
|
#if mod is not None:
|
|
|
|
#send_secondary_config()
|
2021-04-17 22:50:13 +00:00
|
|
|
|
|
|
|
self.subscriptions += [
|
2021-08-27 22:10:46 +00:00
|
|
|
self.props.wireProperty("secondary_mod", self.setSecondaryDemodulator),
|
|
|
|
self.props.wireProperty("digimodes_fft_size", self.chain.setSecondaryFftSize),
|
2021-08-23 12:25:28 +00:00
|
|
|
# TODO
|
|
|
|
# self.props.wireProperty("secondary_offset_freq", self.dsp.set_secondary_offset_freq),
|
2021-04-17 22:50:13 +00:00
|
|
|
]
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2020-04-30 20:54:44 +00:00
|
|
|
self.startOnAvailable = False
|
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
self.sdrSource.addClient(self)
|
|
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
2021-08-23 12:25:28 +00:00
|
|
|
def _getDemodulator(self, demod: Union[str, BaseDemodulatorChain]):
|
|
|
|
if isinstance(demod, BaseDemodulatorChain):
|
|
|
|
return demod
|
|
|
|
# TODO: move this to Modes
|
|
|
|
if demod == "nfm":
|
2021-09-07 12:45:52 +00:00
|
|
|
return NFm(self.props["output_rate"])
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "wfm":
|
2021-09-07 12:45:52 +00:00
|
|
|
return WFm(self.props["hd_output_rate"], self.props["wfm_deemphasis_tau"])
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "am":
|
2021-09-07 12:45:52 +00:00
|
|
|
return Am()
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod in ["usb", "lsb", "cw"]:
|
2021-09-07 12:45:52 +00:00
|
|
|
return Ssb()
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "dmr":
|
2021-09-07 12:45:52 +00:00
|
|
|
return Dmr(self.props["digital_voice_codecserver"])
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "dstar":
|
2021-09-07 12:45:52 +00:00
|
|
|
return Dstar(self.props["digital_voice_codecserver"])
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "ysf":
|
2021-09-07 12:45:52 +00:00
|
|
|
return Ysf(self.props["digital_voice_codecserver"])
|
2021-08-23 12:25:28 +00:00
|
|
|
elif demod == "nxdn":
|
2021-09-07 12:45:52 +00:00
|
|
|
return Nxdn(self.props["digital_voice_codecserver"])
|
|
|
|
elif demod == "m17":
|
|
|
|
return M17Chain()
|
2021-09-07 15:31:32 +00:00
|
|
|
elif demod == "drm":
|
|
|
|
return Drm()
|
2021-09-13 14:58:02 +00:00
|
|
|
elif demod == "freedv":
|
|
|
|
return FreeDV()
|
2021-08-23 12:25:28 +00:00
|
|
|
|
|
|
|
def setDemodulator(self, mod):
|
|
|
|
demodulator = self._getDemodulator(mod)
|
|
|
|
if demodulator is None:
|
|
|
|
raise ValueError("unsupported demodulator: {}".format(mod))
|
|
|
|
self.chain.setDemodulator(demodulator)
|
|
|
|
|
2021-08-27 16:30:46 +00:00
|
|
|
# re-wire the audio to the correct client API
|
|
|
|
buffer = Buffer(self.chain.getOutputFormat())
|
|
|
|
self.chain.setWriter(buffer)
|
|
|
|
if isinstance(demodulator, HdAudio):
|
|
|
|
self.wireOutput("hd_audio", buffer)
|
|
|
|
else:
|
|
|
|
self.wireOutput("audio", buffer)
|
|
|
|
|
2021-08-27 22:10:46 +00:00
|
|
|
def sendSecondaryConfig(self):
|
|
|
|
self.handler.write_secondary_dsp_config(
|
|
|
|
{
|
|
|
|
"secondary_fft_size": self.props["digimodes_fft_size"],
|
|
|
|
"if_samp_rate": self.props["output_rate"],
|
|
|
|
# TODO
|
|
|
|
"secondary_bw": 31.25
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2021-08-31 14:54:37 +00:00
|
|
|
def _getSecondaryDemodulator(self, mod):
|
|
|
|
if isinstance(mod, SecondaryDemodulator):
|
|
|
|
return mod
|
|
|
|
# TODO add remaining modes
|
2021-08-31 15:01:52 +00:00
|
|
|
if mod in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]:
|
2021-08-31 14:54:37 +00:00
|
|
|
return AudioChopperDemodulator(mod, WsjtParser())
|
2021-09-06 20:50:57 +00:00
|
|
|
elif mod == "js8":
|
|
|
|
return AudioChopperDemodulator(mod, Js8Parser())
|
2021-09-06 13:05:33 +00:00
|
|
|
elif mod == "packet":
|
|
|
|
return PacketDemodulator()
|
2021-09-06 18:00:14 +00:00
|
|
|
elif mod == "pocsag":
|
|
|
|
return PocsagDemodulator()
|
2021-08-31 14:54:37 +00:00
|
|
|
return None
|
|
|
|
|
2021-08-27 22:10:46 +00:00
|
|
|
def setSecondaryDemodulator(self, mod):
|
2021-08-31 14:54:37 +00:00
|
|
|
demodulator = self._getSecondaryDemodulator(mod)
|
|
|
|
if not demodulator:
|
|
|
|
self.chain.setSecondaryDemodulator(None)
|
2021-08-27 22:10:46 +00:00
|
|
|
else:
|
|
|
|
self.sendSecondaryConfig()
|
2021-08-31 14:54:37 +00:00
|
|
|
self.chain.setSecondaryDemodulator(demodulator)
|
2021-08-27 22:10:46 +00:00
|
|
|
|
2021-08-27 14:11:03 +00:00
|
|
|
def setAudioCompression(self, comp):
|
|
|
|
try:
|
|
|
|
self.chain.setAudioCompression(comp)
|
|
|
|
except ValueError:
|
|
|
|
# wrong output format... need to re-wire
|
|
|
|
buffer = Buffer(self.chain.getOutputFormat())
|
|
|
|
self.chain.setWriter(buffer)
|
2021-08-27 16:30:46 +00:00
|
|
|
# TODO check if this is hd audio
|
2021-08-27 14:11:03 +00:00
|
|
|
self.wireOutput("audio", buffer)
|
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
def start(self):
|
|
|
|
if self.sdrSource.isAvailable():
|
2021-08-23 12:25:28 +00:00
|
|
|
self.chain.setReader(self.sdrSource.getBuffer().getReader())
|
2020-04-30 20:54:44 +00:00
|
|
|
else:
|
|
|
|
self.startOnAvailable = True
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-08-27 16:30:46 +00:00
|
|
|
def unwireOutput(self, t: str):
|
|
|
|
if t in self.readers:
|
|
|
|
self.readers[t].stop()
|
|
|
|
del self.readers[t]
|
|
|
|
|
2021-08-26 15:21:52 +00:00
|
|
|
def wireOutput(self, t: str, buffer: Buffer):
|
|
|
|
logger.debug("wiring new output of type %s", t)
|
2019-12-21 19:58:28 +00:00
|
|
|
writers = {
|
|
|
|
"audio": self.handler.write_dsp_data,
|
2020-08-08 19:29:25 +00:00
|
|
|
"hd_audio": self.handler.write_hd_audio,
|
2019-12-21 19:58:28 +00:00
|
|
|
"smeter": self.handler.write_s_meter_level,
|
|
|
|
"secondary_fft": self.handler.write_secondary_fft,
|
2021-09-12 22:14:38 +00:00
|
|
|
"secondary_demod": self._unpickle(self.handler.write_secondary_demod),
|
|
|
|
"meta": self._unpickle(self.handler.write_metadata),
|
2019-12-21 19:58:28 +00:00
|
|
|
}
|
2020-01-09 14:11:53 +00:00
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
write = writers[t]
|
|
|
|
|
2021-08-27 16:30:46 +00:00
|
|
|
self.unwireOutput(t)
|
2021-08-27 14:11:03 +00:00
|
|
|
|
2021-08-26 15:21:52 +00:00
|
|
|
reader = buffer.getReader()
|
2021-08-27 14:11:03 +00:00
|
|
|
self.readers[t] = reader
|
2021-08-26 15:21:52 +00:00
|
|
|
threading.Thread(target=self.pump(reader.read, write), name="dsp_pump_{}".format(t)).start()
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-09-12 22:14:38 +00:00
|
|
|
def _unpickle(self, callback):
|
|
|
|
def unpickler(data):
|
|
|
|
io = BytesIO(data.tobytes())
|
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
callback(pickle.load(io))
|
|
|
|
except EOFError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return unpickler
|
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
def stop(self):
|
2021-08-23 12:25:28 +00:00
|
|
|
self.chain.stop()
|
|
|
|
self.chain = None
|
2021-08-27 14:11:03 +00:00
|
|
|
for reader in self.readers.values():
|
|
|
|
reader.stop()
|
|
|
|
self.readers = {}
|
2021-08-23 12:25:28 +00:00
|
|
|
|
2020-04-30 20:54:44 +00:00
|
|
|
self.startOnAvailable = False
|
2019-12-21 19:58:28 +00:00
|
|
|
self.sdrSource.removeClient(self)
|
|
|
|
for sub in self.subscriptions:
|
|
|
|
sub.cancel()
|
|
|
|
self.subscriptions = []
|
|
|
|
|
2020-04-30 20:07:19 +00:00
|
|
|
def setProperties(self, props):
|
|
|
|
for k, v in props.items():
|
|
|
|
self.setProperty(k, v)
|
|
|
|
|
2019-12-21 19:58:28 +00:00
|
|
|
def setProperty(self, prop, value):
|
2021-01-24 19:10:37 +00:00
|
|
|
self.localProps[prop] = value
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-02-20 21:54:07 +00:00
|
|
|
def getClientClass(self) -> SdrClientClass:
|
|
|
|
return SdrClientClass.USER
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-02-20 21:54:07 +00:00
|
|
|
def onStateChange(self, state: SdrSourceState):
|
|
|
|
if state is SdrSourceState.RUNNING:
|
2019-12-21 19:58:28 +00:00
|
|
|
logger.debug("received STATE_RUNNING, attempting DspSource restart")
|
2020-04-30 20:54:44 +00:00
|
|
|
if self.startOnAvailable:
|
2021-08-23 12:25:28 +00:00
|
|
|
self.chain.setReader(self.sdrSource.getBuffer().getReader())
|
2020-04-30 20:54:44 +00:00
|
|
|
self.startOnAvailable = False
|
2021-02-20 21:54:07 +00:00
|
|
|
elif state is SdrSourceState.STOPPING:
|
2019-12-21 19:58:28 +00:00
|
|
|
logger.debug("received STATE_STOPPING, shutting down DspSource")
|
2021-08-23 12:25:28 +00:00
|
|
|
self.stop()
|
2019-12-21 19:58:28 +00:00
|
|
|
|
2021-03-18 18:34:53 +00:00
|
|
|
def onFail(self):
|
|
|
|
logger.debug("received onFail(), shutting down DspSource")
|
2021-08-23 12:25:28 +00:00
|
|
|
self.stop()
|
2021-03-18 21:59:46 +00:00
|
|
|
|
|
|
|
def onShutdown(self):
|
2021-08-23 12:25:28 +00:00
|
|
|
self.stop()
|