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