first steps at rewiring the dsp stuff

This commit is contained in:
Jakob Ketterl 2021-08-23 14:25:28 +02:00
parent 0f1feb9d47
commit 5032f4b66d
13 changed files with 465 additions and 194 deletions

View File

@ -37,10 +37,8 @@ from csdr.pipe import Pipe
from pycsdr.modules import Buffer
from pycsdr.types import Format
from csdr.chain.demodulator import DemodulatorChain
from csdr.chain.fm import NFm, WFm
from csdr.chain.am import Am
from csdr.chain.ssb import Ssb
from csdr.chain.selector import Selector
from csdr.chain.analog import Am, NFm, WFm, Ssb
from csdr.chain.digiham import Dstar, Nxdn, Dmr, Ysf
from csdr.chain.clientaudio import ClientAudioChain

View File

@ -1,12 +1,13 @@
from pycsdr.modules import Buffer, Writer
from pycsdr.modules import Buffer
from typing import Union, Callable
class Chain:
def __init__(self, *workers):
def __init__(self, workers):
self.reader = None
self.writer = None
self.clientReader = None
self.workers = list(workers)
self.workers = workers
for i in range(1, len(self.workers)):
self._connect(self.workers[i - 1], self.workers[i])
@ -29,19 +30,17 @@ class Chain:
if self.workers:
self.workers[-1].setWriter(writer)
def stop(self):
for w in self.workers:
w.stop()
if self.clientReader is not None:
# TODO should be covered by finalize
self.clientReader.stop()
self.clientReader = None
def indexOf(self, search: Union[Callable, object]) -> int:
def searchFn(x):
if callable(search):
return search(x)
else:
return x is search
def getOutputFormat(self):
if self.workers:
return self.workers[-1].getOutputFormat()
else:
raise BufferError("getOutputFormat on empty chain")
try:
return next(i for i, v in enumerate(self.workers) if searchFn(v))
except StopIteration:
return -1
def replace(self, index, newWorker):
if index >= len(self.workers):
@ -55,18 +54,59 @@ class Chain:
newWorker.setReader(self.reader)
else:
previousWorker = self.workers[index - 1]
buffer = Buffer(previousWorker.getOutputFormat())
previousWorker.setWriter(buffer)
newWorker.setReader(buffer.getReader())
self._connect(previousWorker, newWorker)
if index == len(self.workers) - 1:
if self.writer is not None:
newWorker.setWriter(self.writer)
else:
nextWorker = self.workers[index + 1]
buffer = Buffer(newWorker.getOutputFormat())
newWorker.setWriter(buffer)
nextWorker.setReader(buffer.getReader())
self._connect(newWorker, nextWorker)
def append(self, newWorker):
previousWorker = None
if self.workers:
previousWorker = self.workers[-1]
self.workers.append(newWorker)
if previousWorker:
self._connect(previousWorker, newWorker)
elif self.reader is not None:
newWorker.setReader(self.reader)
if self.writer is not None:
newWorker.setWriter(self.writer)
def remove(self, index):
removedWorker = self.workers[index]
self.workers.remove(removedWorker)
removedWorker.stop()
if index == 0:
if self.reader is not None:
self.workers[0].setReader(self.reader)
elif index == len(self.workers):
if self.writer is not None:
self.workers[-1].setWriter(self.writer)
else:
previousWorker = self.workers[index - 1]
nextWorker = self.workers[index]
self._connect(previousWorker, nextWorker)
def stop(self):
for w in self.workers:
w.stop()
if self.clientReader is not None:
# TODO should be covered by finalize
self.clientReader.stop()
self.clientReader = None
def getOutputFormat(self):
if self.workers:
return self.workers[-1].getOutputFormat()
else:
raise BufferError("getOutputFormat on empty chain")
def pump(self, write):
if self.writer is None:
@ -87,4 +127,3 @@ class Chain:
write(data)
return copy

View File

@ -1,17 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import AmDemod, DcBlock, Agc, Convert
from pycsdr.types import Format, AgcProfile
class Am(Chain):
def __init__(self):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setInitialGain(200)
workers = [
AmDemod(),
DcBlock(),
agc,
]
super().__init__(*workers)

54
csdr/chain/analog.py Normal file
View File

@ -0,0 +1,54 @@
from csdr.chain.demodulator import BaseDemodulatorChain
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, WfmDeemphasis, FractionalDecimator, RealPart
from pycsdr.types import Format, AgcProfile
class Am(BaseDemodulatorChain):
def __init__(self):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setInitialGain(200)
workers = [
AmDemod(),
DcBlock(),
agc,
]
super().__init__(workers)
class NFm(BaseDemodulatorChain):
def __init__(self, sampleRate: int):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setMaxGain(3)
workers = [
FmDemod(),
Limit(),
NfmDeemphasis(sampleRate),
agc,
]
super().__init__(workers)
class WFm(BaseDemodulatorChain):
def __init__(self, sampleRate: int, tau: float):
workers = [
FmDemod(),
Limit(),
FractionalDecimator(Format.FLOAT, 200000.0 / sampleRate, prefilter=True),
WfmDeemphasis(sampleRate, tau),
]
super().__init__(workers)
def getFixedIfSampleRate(self):
return 200000
class Ssb(BaseDemodulatorChain):
def __init__(self):
workers = [
RealPart(),
Agc(Format.FLOAT),
]
super().__init__(workers)

View File

@ -6,6 +6,8 @@ from pycsdr.types import Format
class ClientAudioChain(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str):
workers = []
self.inputRate = inputRate
self.clientRate = 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:
@ -15,4 +17,24 @@ class ClientAudioChain(Chain):
workers += [Convert(format, Format.SHORT)]
if compression == "adpcm":
workers += [AdpcmEncoder(sync=True)]
super().__init__(*workers)
super().__init__(workers)
def setFormat(self, format: Format) -> None:
pass
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
def setClientRate(self, clientRate: int) -> None:
if clientRate == self.clientRate:
return
def setAudioCompression(self, compression: str) -> None:
index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder))
if compression == "adpcm":
if index < 0:
self.append(AdpcmEncoder(sync=True))
else:
if index >= 0:
self.remove(index)

View File

@ -1,65 +1,12 @@
from csdr.chain import Chain
from pycsdr.modules import Shift, FirDecimate, Bandpass, Squelch, FractionalDecimator, Writer
from pycsdr.types import Format
from csdr.chain.digiham import Dmr
class DemodulatorChain(Chain):
def __init__(self, samp_rate: int, audioRate: int, shiftRate: float, demodulator: Chain):
self.demodulator = demodulator
class BaseDemodulatorChain(Chain):
def getFixedIfSampleRate(self):
return None
self.shift = Shift(shiftRate)
def getFixedAudioRate(self):
return None
decimation, fraction = self._getDecimation(samp_rate, audioRate)
if_samp_rate = samp_rate / decimation
transition = 0.15 * (if_samp_rate / float(samp_rate))
# set the cutoff on the fist decimation stage lower so that the resulting output
# is already prepared for the second (fractional) decimation stage.
# this spares us a second filter.
self.decimation = FirDecimate(decimation, transition, 0.5 * decimation / (samp_rate / audioRate))
bp_transition = 320.0 / audioRate
self.bandpass = Bandpass(transition=bp_transition, use_fft=True)
readings_per_second = 4
# s-meter readings are available every 1024 samples
# the reporting interval is measured in those 1024-sample blocks
self.squelch = Squelch(5, int(audioRate / (readings_per_second * 1024)))
workers = [self.shift, self.decimation]
if fraction != 1.0:
workers += [FractionalDecimator(Format.COMPLEX_FLOAT, fraction)]
workers += [self.bandpass, self.squelch, demodulator]
super().__init__(*workers)
def setShiftRate(self, rate: float):
self.shift.setRate(rate)
def setSquelchLevel(self, level: float):
self.squelch.setSquelchLevel(level)
def setBandpass(self, low_cut: float, high_cut: float):
self.bandpass.setBandpass(low_cut, high_cut)
def setPowerWriter(self, writer: Writer):
self.squelch.setPowerWriter(writer)
def setMetaWriter(self, writer: Writer):
self.demodulator.setMetaWriter(writer)
def setDmrFilter(self, filter: int) -> None:
if isinstance(self.demodulator, Dmr):
self.demodulator.setSlotFilter(filter)
def _getDecimation(self, input_rate, output_rate):
if output_rate <= 0:
raise ValueError("invalid output rate: {rate}".format(rate=output_rate))
decimation = 1
target_rate = output_rate
while input_rate / (decimation + 1) >= target_rate:
decimation += 1
fraction = float(input_rate / decimation) / output_rate
return decimation, fraction
def supportsSquelch(self):
return True

View File

@ -1,11 +1,11 @@
from csdr.chain import Chain
from csdr.chain.demodulator import BaseDemodulatorChain
from pycsdr.modules import FmDemod, Agc, Writer
from pycsdr.types import Format
from digiham.modules import DstarDecoder, DcBlock, FskDemodulator, GfskDemodulator, DigitalVoiceFilter, MbeSynthesizer, NarrowRrcFilter, NxdnDecoder, DmrDecoder, WideRrcFilter, YsfDecoder
from digiham.ambe import Modes
class DigihamChain(Chain):
class DigihamChain(BaseDemodulatorChain):
def __init__(self, fskDemodulator, decoder, mbeMode, filter=None, codecserver: str = ""):
self.decoder = decoder
if codecserver is None:
@ -23,11 +23,20 @@ class DigihamChain(Chain):
DigitalVoiceFilter(),
agc
]
super().__init__(*workers)
super().__init__(workers)
def getFixedIfSampleRate(self):
return 48000
def getFixedAudioRate(self):
return 8000
def setMetaWriter(self, writer: Writer):
self.decoder.setMetaWriter(writer)
def supportsSquelch(self):
return False
class Dstar(DigihamChain):
def __init__(self, codecserver: str = ""):

View File

@ -7,7 +7,7 @@ class FftAverager(Chain):
self.fftSize = fft_size
self.fftAverages = fft_averages
workers = [self._getWorker()]
super().__init__(*workers)
super().__init__(workers)
def setFftAverages(self, fft_averages):
if self.fftAverages == fft_averages:
@ -46,7 +46,7 @@ class FftChain(Chain):
self._updateParameters()
super().__init__(*workers)
super().__init__(workers)
def _setBlockSize(self, fft_block_size):
if self.blockSize == int(fft_block_size):

View File

@ -1,28 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import FmDemod, Limit, NfmDeemphasis, Agc, WfmDeemphasis, FractionalDecimator
from pycsdr.types import Format, AgcProfile
class NFm(Chain):
def __init__(self, sampleRate: int):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setMaxGain(3)
workers = [
FmDemod(),
Limit(),
NfmDeemphasis(sampleRate),
agc,
]
super().__init__(*workers)
class WFm(Chain):
def __init__(self, sampleRate: int, tau: float):
workers = [
FmDemod(),
Limit(),
FractionalDecimator(Format.FLOAT, 200000.0 / sampleRate, prefilter=True),
WfmDeemphasis(sampleRate, tau),
]
super().__init__(*workers)

126
csdr/chain/selector.py Normal file
View File

@ -0,0 +1,126 @@
from csdr.chain import Chain
from pycsdr.modules import Shift, FirDecimate, Bandpass, Squelch, FractionalDecimator, Writer
from pycsdr.types import Format
import math
class Decimator(Chain):
def __init__(self, inputRate: int, outputRate: int):
if outputRate > inputRate:
raise ValueError("impossible decimation: cannot upsample {} to {}".format(inputRate, outputRate))
self.inputRate = inputRate
self.outputRate = outputRate
decimation, fraction = self._getDecimation(outputRate)
transition = 0.15 * (outputRate / float(self.inputRate))
# set the cutoff on the fist decimation stage lower so that the resulting output
# is already prepared for the second (fractional) decimation stage.
# this spares us a second filter.
cutoff = 0.5 * decimation / (self.inputRate / outputRate)
workers = [
FirDecimate(decimation, transition, cutoff),
]
if fraction != 1.0:
workers += [FractionalDecimator(Format.COMPLEX_FLOAT, fraction)]
super().__init__(workers)
def _getDecimation(self, outputRate: int) -> (int, float):
d = self.inputRate / outputRate
dInt = int(d)
dFloat = float(self.inputRate / dInt) / outputRate
return dInt, dFloat
def _reconfigure(self):
decimation, fraction = self._getDecimation(self.outputRate)
transition = 0.15 * (self.outputRate / float(self.inputRate))
cutoff = 0.5 * decimation / (self.inputRate / self.outputRate)
self.replace(0, FirDecimate(decimation, transition, cutoff))
index = self.indexOf(lambda x: isinstance(x, FractionalDecimator))
if fraction != 1.0:
decimator = FractionalDecimator(Format.COMPLEX_FLOAT, fraction)
if index >= 0:
self.replace(index, decimator)
else:
self.append(decimator)
elif index >= 0:
self.remove(index)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self._reconfigure()
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self._reconfigure()
class Selector(Chain):
def __init__(self, inputRate: int, outputRate: int, shiftRate: float):
self.outputRate = outputRate
self.shift = Shift(shiftRate)
self.decimation = Decimator(inputRate, outputRate)
self.bandpass = self._buildBandpass()
self.bandpassCutoffs = None
self.setBandpass(-4000, 4000)
self.readings_per_second = 4
# s-meter readings are available every 1024 samples
# the reporting interval is measured in those 1024-sample blocks
self.squelch = Squelch(5, int(outputRate / (self.readings_per_second * 1024)))
workers = [self.shift, self.decimation, self.bandpass, self.squelch]
super().__init__(workers)
def _buildBandpass(self) -> Bandpass:
bp_transition = 320.0 / self.outputRate
return Bandpass(transition=bp_transition, use_fft=True)
def setShiftRate(self, rate: float) -> None:
self.shift.setRate(rate)
def _convertToLinear(self, db: float) -> float:
return float(math.pow(10, db / 10))
def setSquelchLevel(self, level: float) -> None:
self.squelch.setSquelchLevel(self._convertToLinear(level))
def setBandpass(self, lowCut: float, highCut: float) -> None:
self.bandpassCutoffs = [lowCut, highCut]
scaled = [x / self.outputRate for x in self.bandpassCutoffs]
self.bandpass.setBandpass(*scaled)
def setLowCut(self, lowCut: float) -> None:
self.bandpassCutoffs[0] = lowCut
self.setBandpass(*self.bandpassCutoffs)
def setHighCut(self, highCut: float) -> None:
self.bandpassCutoffs[1] = highCut
self.setBandpass(*self.bandpassCutoffs)
def setPowerWriter(self, writer: Writer) -> None:
self.squelch.setPowerWriter(writer)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self.decimation.setOutputRate(outputRate)
self.squelch.setReportInterval(int(outputRate / (self.readings_per_second * 1024)))
self.bandpass = self._buildBandpass()
self.setBandpass(*self.bandpassCutoffs)
self.replace(2, self.bandpass)
def setInputRate(self, inputRate: int) -> None:
self.decimation.setInputRate(inputRate)

View File

@ -1,12 +0,0 @@
from csdr.chain import Chain
from pycsdr.modules import RealPart, Agc, Convert
from pycsdr.types import Format
class Ssb(Chain):
def __init__(self):
workers = [
RealPart(),
Agc(Format.FLOAT),
]
super().__init__(*workers)

View File

@ -378,7 +378,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
def write_s_meter_level(self, level):
if isinstance(level, memoryview):
level, = struct.unpack('f', level)
# may contain more than one sample, so only take the last 4 bytes = 1 float
level, = struct.unpack('f', level[-4:])
if not isinstance(level, float):
logger.warning("s-meter value has unexpected type")
return

View File

@ -9,7 +9,15 @@ from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes
from owrx.config.core import CoreConfig
from csdr.output import Output
from csdr import Dsp
from csdr.chain import Chain
from csdr.chain.demodulator import BaseDemodulatorChain
from csdr.chain.selector import Selector
from csdr.chain.clientaudio import ClientAudioChain
from csdr.chain.analog import NFm, WFm, Am, Ssb
from csdr.chain.digiham import Dmr, Dstar, Nxdn, Ysf
from pycsdr.modules import Buffer, Writer
from pycsdr.types import Format
from typing import Union
import threading
import re
@ -18,6 +26,82 @@ import logging
logger = logging.getLogger(__name__)
class ClientDemodulatorChain(Chain):
def __init__(self, demod: BaseDemodulatorChain, sampleRate: int, outputRate: int, audioCompression: str):
self.sampleRate = sampleRate
self.outputRate = outputRate
self.selector = Selector(sampleRate, outputRate, 0.0)
self.selector.setBandpass(-4000, 4000)
self.demodulator = demod
self.clientAudioChain = ClientAudioChain(Format.FLOAT, outputRate, outputRate, audioCompression)
super().__init__([self.selector, self.demodulator, self.clientAudioChain])
def setDemodulator(self, demodulator: BaseDemodulatorChain):
self.replace(1, demodulator)
if self.demodulator is not None:
self.demodulator.stop()
self.demodulator = demodulator
ifRate = self.demodulator.getFixedIfSampleRate()
if ifRate is not None:
self.selector.setOutputRate(ifRate)
else:
self.selector.setOutputRate(self.outputRate)
audioRate = self.demodulator.getFixedAudioRate()
if audioRate is not None:
self.clientAudioChain.setInputRate(audioRate)
else:
self.clientAudioChain.setInputRate(self.outputRate)
if not demodulator.supportsSquelch():
self.selector.setSquelchLevel(-150)
self.clientAudioChain.setFormat(demodulator.getOutputFormat())
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:
shift = -offset / self.sampleRate
self.selector.setShiftRate(shift)
def setAudioCompression(self, compression: str) -> None:
self.clientAudioChain.setAudioCompression(compression)
def setSquelchLevel(self, level: float) -> None:
if not self.demodulator.supportsSquelch():
return
self.selector.setSquelchLevel(level)
def setOutputRate(self, outputRate) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
if self.demodulator.getFixedIfSampleRate() is None:
self.selector.setOutputRate(outputRate)
if self.demodulator.getFixedAudioRate() is None:
self.clientAudioChain.setClientRate(outputRate)
def setPowerWriter(self, writer: Writer) -> None:
self.selector.setPowerWriter(writer)
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
self.selector.setInputRate(sampleRate)
class ModulationValidator(OrValidator):
"""
This validator only allows alphanumeric characters and numbers, but no spaces or special characters
@ -75,18 +159,28 @@ class DspManager(Output, SdrSourceEventClient):
),
)
self.dsp = Dsp(self)
self.dsp.nc_port = self.sdrSource.getPort()
# TODO wait for the rate to come from the client
if "output_rate" not in self.props:
self.props["output_rate"] = 12000
def set_low_cut(cut):
bpf = self.dsp.get_bpf()
bpf[0] = cut
self.dsp.set_bpf(*bpf)
self.chain = ClientDemodulatorChain(
self._getDemodulator("nfm"),
self.props["samp_rate"],
self.props["output_rate"],
self.props["audio_compression"]
)
def set_high_cut(cut):
bpf = self.dsp.get_bpf()
bpf[1] = cut
self.dsp.set_bpf(*bpf)
# wire audio output
buffer = Buffer(self.chain.getOutputFormat())
self.chain.setWriter(buffer)
reader = buffer.getReader()
self.send_output("audio", reader.read)
# wire power level output
buffer = Buffer(Format.FLOAT)
self.chain.setPowerWriter(buffer)
reader = buffer.getReader()
self.send_output("smeter", reader.read)
def set_dial_freq(changes):
if (
@ -101,39 +195,46 @@ class DspManager(Output, SdrSourceEventClient):
parser.setDialFrequency(freq)
if "start_mod" in self.props:
self.dsp.set_demodulator(self.props["start_mod"])
self.setDemodulator(self.props["start_mod"])
mode = Modes.findByModulation(self.props["start_mod"])
if mode and mode.bandpass:
self.dsp.set_bpf(mode.bandpass.low_cut, mode.bandpass.high_cut)
else:
self.dsp.set_bpf(-4000, 4000)
bpf = [mode.bandpass.low_cut, mode.bandpass.high_cut]
self.chain.setBandpass(*bpf)
if "start_freq" in self.props and "center_freq" in self.props:
self.dsp.set_offset_freq(self.props["start_freq"] - self.props["center_freq"])
self.chain.setFrequencyOffset(self.props["start_freq"] - self.props["center_freq"])
else:
self.dsp.set_offset_freq(0)
self.chain.setFrequencyOffset(0)
self.subscriptions = [
self.props.wireProperty("audio_compression", self.dsp.set_audio_compression),
self.props.wireProperty("fft_compression", self.dsp.set_fft_compression),
self.props.wireProperty("digimodes_fft_size", self.dsp.set_secondary_fft_size),
self.props.wireProperty("samp_rate", self.dsp.set_samp_rate),
self.props.wireProperty("output_rate", self.dsp.set_output_rate),
self.props.wireProperty("hd_output_rate", self.dsp.set_hd_output_rate),
self.props.wireProperty("offset_freq", self.dsp.set_offset_freq),
self.props.wireProperty("center_freq", self.dsp.set_center_freq),
self.props.wireProperty("squelch_level", self.dsp.set_squelch_level),
self.props.wireProperty("low_cut", set_low_cut),
self.props.wireProperty("high_cut", set_high_cut),
self.props.wireProperty("mod", self.dsp.set_demodulator),
self.props.wireProperty("dmr_filter", self.dsp.set_dmr_filter),
self.props.wireProperty("wfm_deemphasis_tau", self.dsp.set_wfm_deemphasis_tau),
self.props.wireProperty("digital_voice_codecserver", self.dsp.set_codecserver),
self.props.wireProperty("audio_compression", self.chain.setAudioCompression),
# probably unused:
# self.props.wireProperty("fft_compression", self.dsp.set_fft_compression),
# TODO
# self.props.wireProperty("digimodes_fft_size", self.dsp.set_secondary_fft_size),
self.props.wireProperty("samp_rate", self.chain.setSampleRate),
self.props.wireProperty("output_rate", self.chain.setOutputRate),
# TODO
# self.props.wireProperty("hd_output_rate", self.dsp.set_hd_output_rate),
self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset),
# TODO check, this was used for wsjt-x
# self.props.wireProperty("center_freq", self.dsp.set_center_freq),
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),
# TODO
# self.props.wireProperty("dmr_filter", self.dsp.set_dmr_filter),
# TODO
# self.props.wireProperty("wfm_deemphasis_tau", self.dsp.set_wfm_deemphasis_tau),
# TODO
# self.props.wireProperty("digital_voice_codecserver", self.dsp.set_codecserver),
self.props.filter("center_freq", "offset_freq").wire(set_dial_freq),
]
self.dsp.set_temporary_directory(CoreConfig().get_temporary_directory())
# TODO
# sp.set_temporary_directory(CoreConfig().get_temporary_directory())
def send_secondary_config(*args):
self.handler.write_secondary_dsp_config(
@ -152,9 +253,10 @@ class DspManager(Output, SdrSourceEventClient):
send_secondary_config()
self.subscriptions += [
self.props.wireProperty("secondary_mod", set_secondary_mod),
self.props.wireProperty("digimodes_fft_size", send_secondary_config),
self.props.wireProperty("secondary_offset_freq", self.dsp.set_secondary_offset_freq),
# TODO
# self.props.wireProperty("secondary_mod", set_secondary_mod),
# self.props.wireProperty("digimodes_fft_size", send_secondary_config),
# self.props.wireProperty("secondary_offset_freq", self.dsp.set_secondary_offset_freq),
]
self.startOnAvailable = False
@ -163,10 +265,39 @@ class DspManager(Output, SdrSourceEventClient):
super().__init__()
def _getDemodulator(self, demod: Union[str, BaseDemodulatorChain]):
if isinstance(demod, BaseDemodulatorChain):
return demod
# TODO: move this to Modes
demodChain = None
if demod == "nfm":
demodChain = NFm(self.props["output_rate"])
elif demod == "wfm":
demodChain = WFm(self.props["output_rate"], self.props["wfm_deemphasis_tau"])
elif demod == "am":
demodChain = Am()
elif demod in ["usb", "lsb", "cw"]:
demodChain = Ssb()
elif demod == "dmr":
demodChain = Dmr(self.props["digital_voice_codecserver"])
elif demod == "dstar":
demodChain = Dstar(self.props["digital_voice_codecserver"])
elif demod == "ysf":
demodChain = Ysf(self.props["digital_voice_codecserver"])
elif demod == "nxdn":
demodChain = Nxdn(self.props["digital_voice_codecserver"])
return demodChain
def setDemodulator(self, mod):
demodulator = self._getDemodulator(mod)
if demodulator is None:
raise ValueError("unsupported demodulator: {}".format(mod))
self.chain.setDemodulator(demodulator)
def start(self):
if self.sdrSource.isAvailable():
self.dsp.setBuffer(self.sdrSource.getBuffer())
self.dsp.start()
self.chain.setReader(self.sdrSource.getBuffer().getReader())
else:
self.startOnAvailable = True
@ -187,7 +318,9 @@ class DspManager(Output, SdrSourceEventClient):
threading.Thread(target=self.pump(read_fn, write), name="dsp_pump_{}".format(t)).start()
def stop(self):
self.dsp.stop()
self.chain.stop()
self.chain = None
self.startOnAvailable = False
self.sdrSource.removeClient(self)
for sub in self.subscriptions:
@ -208,16 +341,15 @@ class DspManager(Output, SdrSourceEventClient):
if state is SdrSourceState.RUNNING:
logger.debug("received STATE_RUNNING, attempting DspSource restart")
if self.startOnAvailable:
self.dsp.setBuffer(self.sdrSource.getBuffer())
self.dsp.start()
self.chain.setReader(self.sdrSource.getBuffer().getReader())
self.startOnAvailable = False
elif state is SdrSourceState.STOPPING:
logger.debug("received STATE_STOPPING, shutting down DspSource")
self.dsp.stop()
self.stop()
def onFail(self):
logger.debug("received onFail(), shutting down DspSource")
self.dsp.stop()
self.stop()
def onShutdown(self):
self.dsp.stop()
self.stop()