fix dial frequencies
This commit is contained in:
parent
120328ce12
commit
51453662e2
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
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:
|
if data is not None and self.writer is not None:
|
||||||
self.writer.write(pickle.dumps(data))
|
self.writer.write(pickle.dumps(data))
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
27
owrx/dsp.py
27
owrx/dsp.py
@ -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),
|
||||||
|
@ -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
|
||||||
|
@ -245,10 +245,10 @@ 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()
|
||||||
|
Loading…
Reference in New Issue
Block a user