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

@ -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)