fix dial frequencies
This commit is contained in:
		@@ -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
 | 
				
			||||||
        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))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user