first working nfm chain using pycsdr
This commit is contained in:
parent
bb77d2ce0a
commit
5bb14a8997
@ -35,6 +35,9 @@ from owrx.audio.chopper import AudioChopper
|
||||
|
||||
from csdr.pipe import Pipe
|
||||
|
||||
from csdr.chain.demodulator import DemodulatorChain
|
||||
from csdr.chain.fm import Fm
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -115,6 +118,10 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
self.pipe_base_path = "{tmp_dir}/openwebrx_pipe_".format(tmp_dir=self.temporary_directory)
|
||||
|
||||
def chain(self, which):
|
||||
if self.pycsdr_enabled and which == "nfm":
|
||||
self.pycsdr_chain = DemodulatorChain(self.samp_rate, self.get_audio_rate(), 0.0, Fm(self.get_audio_rate()))
|
||||
return self.pycsdr_chain
|
||||
|
||||
chain = ["nc -v 127.0.0.1 {nc_port}"]
|
||||
chain += ["csdr shift_addfast_cc --fifo {shift_pipe}"]
|
||||
if self.decimation > 1:
|
||||
@ -463,9 +470,6 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
self.samp_rate = samp_rate
|
||||
self.calculate_decimation()
|
||||
if self.running:
|
||||
if self.pycsdr_chain is not None:
|
||||
self.pycsdr_chain.setSampleRate(self.samp_rate)
|
||||
else:
|
||||
self.restart()
|
||||
|
||||
def calculate_decimation(self):
|
||||
@ -580,8 +584,12 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
return
|
||||
self.offset_freq = offset_freq
|
||||
if self.running:
|
||||
if self.pycsdr_chain is not None and isinstance(self.pycsdr_chain, DemodulatorChain):
|
||||
self.pycsdr_chain.setShiftRate(-float(self.offset_freq) / self.samp_rate)
|
||||
else:
|
||||
self.pipes["shift_pipe"].write("%g\n" % (-float(self.offset_freq) / self.samp_rate))
|
||||
|
||||
|
||||
def set_center_freq(self, center_freq):
|
||||
# dsp only needs to know this to be able to pass it to decoders in the form of get_operating_freq()
|
||||
self.center_freq = center_freq
|
||||
@ -596,6 +604,9 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
self.low_cut = low_cut
|
||||
self.high_cut = high_cut
|
||||
if self.running:
|
||||
if self.pycsdr_chain is not None and isinstance(self.pycsdr_chain, DemodulatorChain):
|
||||
self.pycsdr_chain.setBandpass(low_cut, high_cut)
|
||||
else:
|
||||
self.pipes["bpf_pipe"].write(
|
||||
"%g %g\n" % (float(self.low_cut) / self.if_samp_rate(), float(self.high_cut) / self.if_samp_rate())
|
||||
)
|
||||
@ -615,6 +626,9 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
else self.squelch_level
|
||||
)
|
||||
if self.running:
|
||||
if self.pycsdr_chain is not None and isinstance(self.pycsdr_chain, DemodulatorChain):
|
||||
self.pycsdr_chain.setSquelchLevel(self.convertToLinear(actual_squelch))
|
||||
else:
|
||||
self.pipes["squelch_pipe"].write("%g\n" % (self.convertToLinear(actual_squelch)))
|
||||
|
||||
def set_unvoiced_quality(self, q):
|
||||
@ -708,7 +722,13 @@ class Dsp(DirewolfConfigSubscriber):
|
||||
return
|
||||
self.running = True
|
||||
|
||||
command_base = " | ".join(self.chain(self.demodulator))
|
||||
chain = self.chain(self.demodulator)
|
||||
if self.pycsdr_enabled and isinstance(chain, DemodulatorChain):
|
||||
chain.setInput(self.buffer)
|
||||
self.output.send_output("audio", chain.getOutput().read)
|
||||
return
|
||||
|
||||
command_base = " | ".join(chain)
|
||||
|
||||
# create control pipes for csdr
|
||||
self.try_create_pipes(self.pipe_names, command_base)
|
||||
|
@ -13,6 +13,9 @@ class Chain:
|
||||
self._connect(self.workers[i - 1], self.workers[i])
|
||||
|
||||
def _connect(self, w1, w2):
|
||||
if isinstance(w1, Chain):
|
||||
buffer = w1.getOutput()
|
||||
else:
|
||||
buffer = Buffer(w1.getOutputFormat())
|
||||
w1.setOutput(buffer)
|
||||
w2.setInput(buffer)
|
||||
@ -21,35 +24,46 @@ class Chain:
|
||||
for w in self.workers:
|
||||
w.stop()
|
||||
self.setInput(None)
|
||||
self.setOutput(None)
|
||||
if self.output is not None:
|
||||
self.output.stop()
|
||||
|
||||
def setInput(self, buffer):
|
||||
if self.input == buffer:
|
||||
return
|
||||
self.input = buffer
|
||||
if self.workers:
|
||||
self.workers[0].setInput(buffer)
|
||||
else:
|
||||
self.output = self.input
|
||||
|
||||
def setOutput(self, buffer):
|
||||
if self.output == buffer:
|
||||
return
|
||||
if self.output is not None:
|
||||
self.output.stop()
|
||||
self.output = buffer
|
||||
self.workers[-1].setOutput(buffer)
|
||||
def getOutput(self):
|
||||
if self.output is None:
|
||||
if self.workers:
|
||||
lastWorker = self.workers[-1]
|
||||
if isinstance(lastWorker, Chain):
|
||||
self.output = lastWorker.getOutput()
|
||||
else:
|
||||
self.output = Buffer(self.getOutputFormat())
|
||||
self.workers[-1].setOutput(self.output)
|
||||
else:
|
||||
self.output = self.input
|
||||
return self.output
|
||||
|
||||
def getOutputFormat(self):
|
||||
if self.workers:
|
||||
return self.workers[-1].getOutputFormat()
|
||||
else:
|
||||
return self.input.getOutputFormat()
|
||||
|
||||
def pump(self, write):
|
||||
if self.output is None:
|
||||
self.setOutput(Buffer(self.getOutputFormat()))
|
||||
output = self.getOutput()
|
||||
|
||||
def copy():
|
||||
run = True
|
||||
while run:
|
||||
data = None
|
||||
try:
|
||||
data = self.output.read()
|
||||
data = output.read()
|
||||
except ValueError:
|
||||
pass
|
||||
if data is None or (isinstance(data, bytes) and len(data) == 0):
|
||||
|
56
csdr/chain/demodulator.py
Normal file
56
csdr/chain/demodulator.py
Normal file
@ -0,0 +1,56 @@
|
||||
from csdr.chain import Chain
|
||||
from pycsdr.modules import Shift, FirDecimate, Bandpass, Squelch, FractionalDecimator
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Demodulator(Chain, metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def setLastDecimation(self, decimation: Chain):
|
||||
pass
|
||||
|
||||
|
||||
class DemodulatorChain(Chain):
|
||||
def __init__(self, samp_rate: int, audioRate: int, shiftRate: float, demodulator: Demodulator):
|
||||
self.shift = Shift(shiftRate)
|
||||
|
||||
decimation, fraction = self._getDecimation(samp_rate, audioRate)
|
||||
if_samp_rate = samp_rate / decimation
|
||||
transition = 0.15 * (if_samp_rate / float(samp_rate))
|
||||
self.decimation = FirDecimate(decimation, transition)
|
||||
|
||||
bp_transition = 320.0 / if_samp_rate
|
||||
self.bandpass = Bandpass(bp_transition, use_fft=True)
|
||||
|
||||
self.squelch = Squelch(5)
|
||||
|
||||
if fraction != 1.0:
|
||||
demodulator.setLastDecimation(Chain(FractionalDecimator(fraction)))
|
||||
|
||||
workers = [
|
||||
self.shift,
|
||||
self.decimation,
|
||||
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 _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
|
22
csdr/chain/fm.py
Normal file
22
csdr/chain/fm.py
Normal file
@ -0,0 +1,22 @@
|
||||
from csdr.chain.demodulator import Demodulator, Chain
|
||||
from pycsdr.modules import FmDemod, Limit, NfmDeemphasis, Agc, Convert
|
||||
from pycsdr.types import Format
|
||||
|
||||
|
||||
class Fm(Demodulator):
|
||||
def __init__(self, sampleRate: int):
|
||||
workers = [
|
||||
FmDemod(),
|
||||
Limit(),
|
||||
# empty chain as placeholder for the "last decimation"
|
||||
Chain(),
|
||||
NfmDeemphasis(sampleRate),
|
||||
Agc(Format.FLOAT),
|
||||
Convert(Format.FLOAT, Format.SHORT),
|
||||
]
|
||||
super().__init__(*workers)
|
||||
|
||||
def setLastDecimation(self, decimation: Chain):
|
||||
# TODO: build api to replace workers
|
||||
# TODO: replace placeholder
|
||||
pass
|
@ -167,6 +167,7 @@ class DspManager(Output, SdrSourceEventClient):
|
||||
|
||||
def start(self):
|
||||
if self.sdrSource.isAvailable():
|
||||
self.dsp.setBuffer(self.sdrSource.getBuffer())
|
||||
self.dsp.start()
|
||||
else:
|
||||
self.startOnAvailable = True
|
||||
@ -209,6 +210,7 @@ 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.startOnAvailable = False
|
||||
elif state is SdrSourceState.STOPPING:
|
||||
|
Loading…
Reference in New Issue
Block a user