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 csdr.chain import Chain
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
class BaseDemodulatorChain(Chain): class BaseDemodulatorChain(Chain):
def getFixedIfSampleRate(self): def supportsSquelch(self) -> bool:
return None
def supportsSquelch(self):
return True return True
@ -17,14 +13,20 @@ class SecondaryDemodulator(Chain):
class FixedAudioRateChain(ABC): class FixedAudioRateChain(ABC):
@abstractmethod @abstractmethod
def getFixedAudioRate(self): def getFixedAudioRate(self) -> int:
pass pass
class FixedIfSampleRateChain(ABC): class FixedIfSampleRateChain(ABC):
@abstractmethod @abstractmethod
def getFixedIfSampleRate(self): def getFixedIfSampleRate(self) -> int:
return self.fixedIfSampleRate pass
class DialFrequencyReceiver(ABC):
@abstractmethod
def setDialFrequency(self, frequency: int) -> None:
pass
# marker interface # 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 owrx.audio.chopper import AudioChopper
from pycsdr.modules import Agc, Convert from pycsdr.modules import Agc, Convert
from pycsdr.types import Format from pycsdr.types import Format
class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain): class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver):
# TODO parser typing # TODO parser typing
def __init__(self, mode: str, parser): 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) super().__init__(workers)
def getFixedAudioRate(self): def getFixedAudioRate(self):
return 12000 return 12000
def setDialFrequency(self, frequency: int) -> None:
self.chopper.setDialFrequency(frequency)

View File

@ -3,6 +3,7 @@ from itertools import groupby
import threading import threading
from owrx.audio import ProfileSourceSubscriber from owrx.audio import ProfileSourceSubscriber
from owrx.audio.wav import AudioWriter from owrx.audio.wav import AudioWriter
from owrx.audio.queue import QueueJob
from csdr.chain import Chain from csdr.chain import Chain
import pickle import pickle
@ -16,6 +17,7 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber):
# TODO parser typing # TODO parser typing
def __init__(self, mode_str: str, parser): def __init__(self, mode_str: str, parser):
self.parser = parser self.parser = parser
self.dialFrequency = None
self.doRun = True self.doRun = True
self.writers = [] self.writers = []
mode = Modes.findByModulation(mode_str) mode = Modes.findByModulation(mode_str)
@ -72,7 +74,14 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber):
logger.debug("profile change received, resetting writers...") logger.debug("profile change received, resetting writers...")
self.setup_writers() self.setup_writers()
def send(self, profile, line): def setDialFrequency(self, frequency: int) -> None:
data = self.parser.parse(profile, line) self.dialFrequency = frequency
if data is not None and self.writer is not None:
self.writer.write(pickle.dumps(data)) 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) logger.setLevel(logging.INFO)
class QueueJob(object): class QueueJobResult:
def __init__(self, profile, writer, file): def __init__(self, profile, frequency, lines):
self.profile = profile 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.writer = writer
self.file = file self.file = file
@ -27,13 +35,18 @@ class QueueJob(object):
cwd=tmp_dir, cwd=tmp_dir,
close_fds=True, close_fds=True,
) )
lines = None
try: try:
for line in decoder.stdout: lines = [l for l in decoder.stdout]
self.writer.send(self.profile, line) except OSError:
except (OSError, AttributeError):
decoder.stdout.flush() decoder.stdout.flush()
# TODO uncouple parsing from the output so that decodes can still go to the map and the spotters # 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.") 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: try:
rc = decoder.wait(timeout=10) rc = decoder.wait(timeout=10)
if rc != 0: if rc != 0:

View File

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

View File

@ -9,7 +9,7 @@ from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes from owrx.modes import Modes
from csdr.output import Output from csdr.output import Output
from csdr.chain import Chain 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.selector import Selector
from csdr.chain.clientaudio import ClientAudioChain from csdr.chain.clientaudio import ClientAudioChain
from csdr.chain.analog import NFm, WFm, Am, Ssb from csdr.chain.analog import NFm, WFm, Am, Ssb
@ -38,6 +38,8 @@ class ClientDemodulatorChain(Chain):
self.audioBuffer = None self.audioBuffer = None
self.demodulator = demod self.demodulator = demod
self.secondaryDemodulator = None self.secondaryDemodulator = None
self.centerFrequency = None
self.frequencyOffset = None
inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate
oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate
self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression) self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression)
@ -132,6 +134,7 @@ class ClientDemodulatorChain(Chain):
rate = self.outputRate rate = self.outputRate
self.selector.setOutputRate(rate) self.selector.setOutputRate(rate)
self.clientAudioChain.setInputRate(rate) self.clientAudioChain.setInputRate(rate)
self._updateDialFrequency()
if self.secondaryDemodulator is not None: if self.secondaryDemodulator is not None:
self.secondaryDemodulator.setReader(self.audioBuffer.getReader()) self.secondaryDemodulator.setReader(self.audioBuffer.getReader())
@ -157,9 +160,28 @@ class ClientDemodulatorChain(Chain):
self.selector.setBandpass(lowCut, highCut) self.selector.setBandpass(lowCut, highCut)
def setFrequencyOffset(self, offset: int) -> None: def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
shift = -offset / self.sampleRate shift = -offset / self.sampleRate
self.selector.setShiftRate(shift) 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: def setAudioCompression(self, compression: str) -> None:
self.clientAudioChain.setAudioCompression(compression) self.clientAudioChain.setAudioCompression(compression)
@ -368,8 +390,7 @@ class DspManager(Output, SdrSourceEventClient):
self.props.wireProperty("output_rate", self.chain.setOutputRate), self.props.wireProperty("output_rate", self.chain.setOutputRate),
self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate), self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate),
self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset), self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset),
# TODO check, this was used for wsjt-x self.props.wireProperty("center_freq", self.chain.setCenterFrequency),
# self.props.wireProperty("center_freq", self.dsp.set_center_freq),
self.props.wireProperty("squelch_level", self.chain.setSquelchLevel), self.props.wireProperty("squelch_level", self.chain.setSquelchLevel),
self.props.wireProperty("low_cut", self.chain.setLowCut), self.props.wireProperty("low_cut", self.chain.setLowCut),
self.props.wireProperty("high_cut", self.chain.setHighCut), 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.service.chain import ServiceDemodulatorChain
from owrx.modes import Modes, DigitalMode from owrx.modes import Modes, DigitalMode
from typing import Union 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.analog import NFm, Ssb
from csdr.chain.digimodes import AudioChopperDemodulator from csdr.chain.digimodes import AudioChopperDemodulator
@ -310,6 +310,8 @@ class ServiceHandler(SdrSourceEventClient):
sampleRate = source.getProps()["samp_rate"] sampleRate = source.getProps()["samp_rate"]
shift = (center_freq - frequency) / sampleRate shift = (center_freq - frequency) / sampleRate
bandpass = modeObject.get_bandpass() bandpass = modeObject.get_bandpass()
if isinstance(secondaryDemod, DialFrequencyReceiver):
secondaryDemod.setDialFrequency(frequency)
chain = ServiceDemodulatorChain(demod, secondaryDemod, sampleRate, shift) chain = ServiceDemodulatorChain(demod, secondaryDemod, sampleRate, shift)
chain.setBandPass(bandpass.low_cut, bandpass.high_cut) chain.setBandPass(bandpass.low_cut, bandpass.high_cut)
@ -339,7 +341,6 @@ class ServiceHandler(SdrSourceEventClient):
return None return None
class WsjtHandler(object): class WsjtHandler(object):
def write_wsjt_message(self, msg): def write_wsjt_message(self, msg):
pass pass

View File

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