fix dial frequencies

This commit is contained in:
Jakob Ketterl 2021-08-31 22:46:11 +02:00
parent 120328ce12
commit 51453662e2
8 changed files with 83 additions and 33 deletions

View File

@ -1,13 +1,9 @@
from pycsdr.modules import Reader
from csdr.chain import Chain
from abc import ABC, abstractmethod
class BaseDemodulatorChain(Chain):
def getFixedIfSampleRate(self):
return None
def supportsSquelch(self):
def supportsSquelch(self) -> bool:
return True
@ -17,14 +13,20 @@ class SecondaryDemodulator(Chain):
class FixedAudioRateChain(ABC):
@abstractmethod
def getFixedAudioRate(self):
def getFixedAudioRate(self) -> int:
pass
class FixedIfSampleRateChain(ABC):
@abstractmethod
def getFixedIfSampleRate(self):
return self.fixedIfSampleRate
def getFixedIfSampleRate(self) -> int:
pass
class DialFrequencyReceiver(ABC):
@abstractmethod
def setDialFrequency(self, frequency: int) -> None:
pass
# marker interface

View File

@ -1,14 +1,18 @@
from csdr.chain.demodulator import SecondaryDemodulator, FixedAudioRateChain
from csdr.chain.demodulator import SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver
from owrx.audio.chopper import AudioChopper
from pycsdr.modules import Agc, Convert
from pycsdr.types import Format
class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain):
class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver):
# TODO parser typing
def __init__(self, mode: str, parser):
workers = [Convert(Format.FLOAT, Format.SHORT), AudioChopper(mode, parser)]
self.chopper = AudioChopper(mode, parser)
workers = [Convert(Format.FLOAT, Format.SHORT), self.chopper]
super().__init__(workers)
def getFixedAudioRate(self):
return 12000
def setDialFrequency(self, frequency: int) -> None:
self.chopper.setDialFrequency(frequency)

View File

@ -3,6 +3,7 @@ from itertools import groupby
import threading
from owrx.audio import ProfileSourceSubscriber
from owrx.audio.wav import AudioWriter
from owrx.audio.queue import QueueJob
from csdr.chain import Chain
import pickle
@ -16,6 +17,7 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber):
# TODO parser typing
def __init__(self, mode_str: str, parser):
self.parser = parser
self.dialFrequency = None
self.doRun = True
self.writers = []
mode = Modes.findByModulation(mode_str)
@ -72,7 +74,14 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber):
logger.debug("profile change received, resetting writers...")
self.setup_writers()
def send(self, profile, line):
data = self.parser.parse(profile, line)
if data is not None and self.writer is not None:
self.writer.write(pickle.dumps(data))
def setDialFrequency(self, frequency: int) -> None:
self.dialFrequency = frequency
def createJob(self, profile, filename):
return QueueJob(profile, self.dialFrequency, self, filename)
def sendResult(self, result):
for line in result.lines:
data = self.parser.parse(result.profile, result.frequency, line)
if data is not None and self.writer is not None:
self.writer.write(pickle.dumps(data))

View File

@ -12,9 +12,17 @@ logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class QueueJob(object):
def __init__(self, profile, writer, file):
class QueueJobResult:
def __init__(self, profile, frequency, lines):
self.profile = profile
self.frequency = frequency
self.lines = lines
class QueueJob(object):
def __init__(self, profile, frequency, writer, file):
self.profile = profile
self.frequency = frequency
self.writer = writer
self.file = file
@ -27,13 +35,18 @@ class QueueJob(object):
cwd=tmp_dir,
close_fds=True,
)
lines = None
try:
for line in decoder.stdout:
self.writer.send(self.profile, line)
except (OSError, AttributeError):
lines = [l for l in decoder.stdout]
except OSError:
decoder.stdout.flush()
# TODO uncouple parsing from the output so that decodes can still go to the map and the spotters
logger.debug("output has gone away while decoding job.")
# keep this out of the try/except
if lines is not None:
self.writer.sendResult(QueueJobResult(self.profile, self.frequency, lines))
try:
rc = decoder.wait(timeout=10)
if rc != 0:

View File

@ -1,6 +1,6 @@
from owrx.config.core import CoreConfig
from owrx.audio import AudioChopperProfile
from owrx.audio.queue import QueueJob, DecoderQueue
from owrx.audio.queue import DecoderQueue
import threading
import wave
import os
@ -47,8 +47,8 @@ class WaveFile(object):
class AudioWriter(object):
def __init__(self, outputWriter, interval, profiles: List[AudioChopperProfile]):
self.outputWriter = outputWriter
def __init__(self, chopper, interval, profiles: List[AudioChopperProfile]):
self.chopper = chopper
self.interval = interval
self.profiles = profiles
self.wavefile = None
@ -101,7 +101,7 @@ class AudioWriter(object):
logger.exception("Error while linking job files")
continue
job = QueueJob(profile, self.outputWriter, filename)
job = self.chopper.createJob(profile, filename)
try:
DecoderQueue.getSharedInstance().put(job)
except Full:

View File

@ -9,7 +9,7 @@ from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes
from csdr.output import Output
from csdr.chain import Chain
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator, DialFrequencyReceiver
from csdr.chain.selector import Selector
from csdr.chain.clientaudio import ClientAudioChain
from csdr.chain.analog import NFm, WFm, Am, Ssb
@ -38,6 +38,8 @@ class ClientDemodulatorChain(Chain):
self.audioBuffer = None
self.demodulator = demod
self.secondaryDemodulator = None
self.centerFrequency = None
self.frequencyOffset = None
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)
@ -132,6 +134,7 @@ class ClientDemodulatorChain(Chain):
rate = self.outputRate
self.selector.setOutputRate(rate)
self.clientAudioChain.setInputRate(rate)
self._updateDialFrequency()
if self.secondaryDemodulator is not None:
self.secondaryDemodulator.setReader(self.audioBuffer.getReader())
@ -157,9 +160,28 @@ class ClientDemodulatorChain(Chain):
self.selector.setBandpass(lowCut, highCut)
def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
shift = -offset / self.sampleRate
self.selector.setShiftRate(shift)
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
if isinstance(self.secondaryDemodulator, DialFrequencyReceiver):
self.secondaryDemodulator.setDialFrequency(dialFrequency)
def setAudioCompression(self, compression: str) -> None:
self.clientAudioChain.setAudioCompression(compression)
@ -368,8 +390,7 @@ class DspManager(Output, SdrSourceEventClient):
self.props.wireProperty("output_rate", self.chain.setOutputRate),
self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate),
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("center_freq", self.chain.setCenterFrequency),
self.props.wireProperty("squelch_level", self.chain.setSquelchLevel),
self.props.wireProperty("low_cut", self.chain.setLowCut),
self.props.wireProperty("high_cut", self.chain.setHighCut),

View File

@ -16,7 +16,7 @@ from owrx.service.schedule import ServiceScheduler
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.demodulator import BaseDemodulatorChain, SecondaryDemodulator, DialFrequencyReceiver
from csdr.chain.analog import NFm, Ssb
from csdr.chain.digimodes import AudioChopperDemodulator
@ -310,6 +310,8 @@ class ServiceHandler(SdrSourceEventClient):
sampleRate = source.getProps()["samp_rate"]
shift = (center_freq - frequency) / sampleRate
bandpass = modeObject.get_bandpass()
if isinstance(secondaryDemod, DialFrequencyReceiver):
secondaryDemod.setDialFrequency(frequency)
chain = ServiceDemodulatorChain(demod, secondaryDemod, sampleRate, shift)
chain.setBandPass(bandpass.low_cut, bandpass.high_cut)
@ -339,7 +341,6 @@ class ServiceHandler(SdrSourceEventClient):
return None
class WsjtHandler(object):
def write_wsjt_message(self, msg):
pass

View File

@ -245,11 +245,11 @@ class Q65Profile(WsjtProfile):
class WsjtParser:
def parse(self, profile, raw_msg):
def parse(self, profile, freq, raw_msg):
try:
# TODO get the frequency back from somewhere
freq = 14074000
band = Bandplan.getSharedInstance().findBand(freq)
band = None
if freq is not None:
band = Bandplan.getSharedInstance().findBand(freq)
msg = raw_msg.decode().rstrip()
# known debug messages we know to skip