diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7cdab1f..e8e2b2e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -12,8 +12,14 @@ IF(SDR_WITH_PORTAUDIO) add_executable(sdr_fm sdr_fm.cc) target_link_libraries(sdr_fm ${LIBS} libsdr) -add_executable(sdr_afsk1200 sdr_afsk1200.cc) -target_link_libraries(sdr_afsk1200 ${LIBS} libsdr) + add_executable(sdr_rec sdr_rec.cc) + target_link_libraries(sdr_rec ${LIBS} libsdr) + + add_executable(sdr_wspr sdr_wspr.cc) + target_link_libraries(sdr_wspr ${LIBS} libsdr) + + add_executable(sdr_afsk1200 sdr_afsk1200.cc) + target_link_libraries(sdr_afsk1200 ${LIBS} libsdr) ENDIF(SDR_WITH_PORTAUDIO) IF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) @@ -24,3 +30,6 @@ ENDIF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) add_executable(sdr_psk31 sdr_psk31.cc) target_link_libraries(sdr_psk31 ${LIBS} ${QT_LIBRARIES} libsdr libsdr-gui) +add_executable(sdr_rtty sdr_rtty.cc) +target_link_libraries(sdr_rtty ${LIBS} libsdr) + diff --git a/examples/sdr_afsk1200.cc b/examples/sdr_afsk1200.cc index 0596517..95ecc31 100644 --- a/examples/sdr_afsk1200.cc +++ b/examples/sdr_afsk1200.cc @@ -76,15 +76,13 @@ public: // The input sample rate _sampleRate = src_cfg.sampleRate(); - // Symbols per bit (limit to 32 symbols per bit) - _corrLen = std::min(int(_sampleRate/_baud), 32); - + // Samples per bit + _corrLen = int(_sampleRate/_baud); // Compute symbol rate: - _symbolRate = _baud*_corrLen; + _symbolRate = std::min(10*_baud, _baud*_corrLen); // Samples per symbol (fractional): _mu = 0.0; _muIncr = _sampleRate/_symbolRate; - // Delayline for interpolating sub-sampler _dl = Buffer(2*8); for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; } @@ -103,14 +101,17 @@ public: for (size_t i=0; i<_corrLen; i++) { _markLUT[i] = std::exp(std::complex(0.0, phiMark)); _spaceLUT[i] = std::exp(std::complex(0.0, phiSpace)); - phiMark += (2.*M_PI*_Fmark)/_symbolRate; - phiSpace += (2.*M_PI*_Fspace)/_symbolRate; + phiMark += (2.*M_PI*_Fmark)/_sampleRate; + phiSpace += (2.*M_PI*_Fspace)/_sampleRate; _markHist[i] = 0; _spaceHist[i] = 0; } _lutIdx = 0; // Get phase increment per symbol - _phase = 0; _phaseInc = _phaseMask/_corrLen; + _phase = 0; _omega = _baud/_symbolRate; + _omegaMin = _omega - 0.01*_omega; + _omegaMax = _omega + 0.01*_omega; + _gainOmega = 0.01; // Allocate output buffer: _buffer = Buffer(src_cfg.bufferSize()/_corrLen + 1); @@ -121,7 +122,7 @@ public: << " samples per symbol: " << _muIncr << std::endl << " symbols per bit: " << _corrLen << std::endl << " symbol rate: " << _symbolRate << "Hz" << std::endl - << " Phase incr/symbol: " << float(_phaseInc)/_phasePeriod; + << " Phase incr/symbol: " << float(_omega); Logger::get().log(msg); @@ -134,44 +135,46 @@ public: while (i1) && (i0); + _symbols <<= 1; _symbols |= (sample>0); + // Advance phase + _phase += _omega; + + // Sample bit + if (_phase >= 1) { + // Modulo "2 pi" + _phase = fmodf(_phase, 1.0); + // Store bit + _lastBits <<= 1; _lastBits |= (_symbols & 1); + // Put decoded bit in output buffer + // transition -> 0; no transition -> 1 + _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); + } // If transition if ((_symbols ^ (_symbols >> 1)) & 1) { // Phase correction - if (_phase < (_phasePeriod/2)) { - _phase += _phaseInc/8; - } else { - _phase -= _phaseInc/8; - } - } - - // Advance phase - _phase += _phaseInc; - - // Sample bit - if (_phase >= _phasePeriod) { - // Modulo 2 pi - _phase &= _phaseMask; - // Store bit - _lastBits <<= 1; _lastBits |= (_symbols & 1); - // Put decoded bit in output buffer - _buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1); + /*std::cerr << "Transition at phi=" << _phase << std::endl + << " update omega from " << _omega << " to "; */ + if (_phase < 0.5) { _omega -= _gainOmega*(_phase); } + else { _omega += _gainOmega*(1-_phase); } + // Limit omega + _omega = std::min(_omegaMax, std::max(_omegaMin, _omega)); + //std::cerr << _omega << std::endl; } } } @@ -213,8 +216,9 @@ protected: uint32_t _symbols; uint32_t _lastBits; - uint32_t _phase; - uint32_t _phaseInc; + float _phase; + float _omega, _omegaMin, _omegaMax; + float _gainOmega; static const uint32_t _phasePeriod = 0x10000u; static const uint32_t _phaseMask = 0x0ffffu; @@ -248,15 +252,23 @@ public: } _bitstream = 0; - _bitbuffer = 0x00; + _bitbuffer = 0; _state = 0; _ptr = _rxbuffer; + + // Allocate output buffer + _buffer = Buffer(512); + + // propergate config + this->setConfig(Config(Traits::scalarId, 0, 512, 1)); } virtual void process(const Buffer &buffer, bool allow_overwrite) { for (size_t i=0; i 2)) { *_ptr = 0; @@ -264,33 +276,46 @@ public: std::cerr << "Got invalid buffer: " << _rxbuffer << std::endl; } else { std::cerr << "GOT: " << _rxbuffer << std::endl; + memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer); + this->send(_buffer.head(_ptr-_rxbuffer)); } - } + } _state = 1; _ptr = _rxbuffer; - _bitbuffer = 0x80u; + _bitbuffer = 0x80; continue; } + // If 7 ones are received in a row -> error, wait or sync byte if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; } + // If state == wait for sync byte -> receive next bit if (!_state) { continue; } - if ((_bitstream & 0x3f) == 0x3e) { /* stuffed bit */ continue; } + /* stuffed bit */ + if ((_bitstream & 0x3f) == 0x3e) { continue; } - if (_bitstream & 1) { _bitbuffer |= 0x100; } + // prepend bit to bitbuffer + _bitbuffer |= ((_bitstream & 1) << 8); + // If 8 bits have been received (stored in b8-b1 of _bitbuffer) if (_bitbuffer & 1) { + // Check for buffer overrun if ((_ptr-_rxbuffer) >= 512) { Logger::get().log(LogMessage(LOG_ERROR, "AX.25 packet too long.")); - _state = 0; continue; + // Wait for next sync byte + _state = 0; + continue; } - std::cerr << "Got byte: " << std::hex << int( (_bitbuffer>>1) & 0xff ) - << ": " << char(_bitbuffer>>1) - << "(" << char(_bitbuffer>>2) << ")" << std::endl; - *_ptr++ = (_bitbuffer >> 1); _bitbuffer = 0x80u; continue; + + // Store received byte and ... + *_ptr++ = (_bitbuffer >> 1); + // reset bit buffer + _bitbuffer = 0x80; + continue; } + // Shift bitbuffer one to the left _bitbuffer >>= 1; } } @@ -302,6 +327,8 @@ protected: uint8_t _rxbuffer[512]; uint8_t *_ptr; + + Buffer _buffer; }; diff --git a/examples/sdr_rec.cc b/examples/sdr_rec.cc new file mode 100644 index 0000000..bd8e6c0 --- /dev/null +++ b/examples/sdr_rec.cc @@ -0,0 +1,130 @@ +#include "demod.hh" +#include "rtlsource.hh" +#include "baseband.hh" +#include "autocast.hh" +#include "portaudio.hh" +#include "wavfile.hh" + +#include +#include + +using namespace sdr; + +static void __sigint_handler(int signo) { + std::cerr << "Stop Queue..." << std::endl; + // On SIGINT -> stop queue properly + Queue::get().stop(); + Queue::get().wait(); +} + + +int main(int argc, char *argv[]) { + if (3 > argc) { + std::cout << "USAGE: sdr_rec FREQUENCY MODE [OUTPUT.wav]" << std::endl; return -1; + } + + // get frequency + double freq = atof(argv[1]); + std::string mode = argv[2]; + // Get output file (if given) + std::string outFile; + if (4 <= argc) { outFile = argv[3]; } + + sdr::Logger::get().addHandler( + new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); + + // Register handler: + signal(SIGINT, __sigint_handler); + + PortAudio::init(); + + // obtain base-band config + double f_center = 0, f_filter = 0, flt_width = 0; + int sub_sample = 1; double out_f_sample = 12e3; + if (mode == "WFM") { + f_center = 0; f_filter = 0; flt_width = 50e3; + out_f_sample = 48e3; + } else if (mode == "NFM") { + f_center = 0; f_filter = 0; flt_width = 12.5e3; + out_f_sample = 12e3; + } else if (mode == "AM") { + f_center = 0; f_filter = 0; flt_width = 15e3; + out_f_sample = 12e3; + } else if (mode == "USB") { + f_center = 0; f_filter = 1500; flt_width = 3e3; + out_f_sample = 12e3; + } else if (mode == "LSB") { + f_center = 0; f_filter = -1500; flt_width = 3e3; + out_f_sample = 12e3; + } else { + std::cerr << "Unknown mode '" << mode + << "': Possible values are WFM, NFM, AM, USB, LSB." << std::endl; + return -1; + } + + // Create nodes + RTLSource src(freq, 1e6); + AutoCast< std::complex > cast; + IQBaseBand baseband(f_center, f_filter, flt_width, 16, sub_sample, out_f_sample); + FMDemod *fm_demod = 0; + FMDeemph *fm_deemph = 0; + AMDemod *am_demod = 0; + USBDemod *usb_demod = 0; + PortSink audio; + WavSink *wav_sink = 0; + + if (outFile.size()) { + wav_sink = new WavSink(outFile); + } + + // Assemble processing chain: + src.connect(&cast, true); + cast.connect(&baseband); + + if (mode == "WFM") { + fm_demod = new FMDemod(); + fm_deemph= new FMDeemph(); + baseband.connect(fm_demod, true); + fm_demod->connect(fm_deemph, true); + fm_deemph->connect(&audio); + if (wav_sink) { fm_deemph->connect(wav_sink); } + } else if (mode == "NFM") { + fm_demod = new FMDemod(); + fm_deemph= new FMDeemph(); + fm_demod->connect(fm_deemph, true); + baseband.connect(fm_demod, true); + fm_demod->connect(fm_deemph, true); + fm_deemph->connect(&audio); + if (wav_sink) { fm_deemph->connect(wav_sink); } + } else if (mode == "AM") { + am_demod = new AMDemod(); + baseband.connect(am_demod); + am_demod->connect(&audio); + if (wav_sink) { am_demod->connect(wav_sink); } + } else if ((mode == "USB") || (mode == "LSB")){ + usb_demod = new USBDemod(); + baseband.connect(usb_demod); + usb_demod->connect(&audio); + if (wav_sink) { usb_demod->connect(wav_sink); } + } + + Queue::get().addStart(&src, &RTLSource::start); + Queue::get().addStop(&src, &RTLSource::stop); + + std::cerr << "Start recording at " << src.frequency() + << "Hz in mode " << mode << ". Press CTRL-C to stop recoding." << std::endl; + + Queue::get().start(); + Queue::get().wait(); + + if (fm_demod) { delete fm_demod; } + if (fm_deemph) { delete fm_deemph; } + if (usb_demod) { delete usb_demod; } + if (wav_sink) { delete wav_sink; } + + PortAudio::terminate(); + + std::cerr << "Recording stopped." << std::endl; + + return 0; +} diff --git a/src/baseband.hh b/src/baseband.hh index a079575..4304fe4 100644 --- a/src/baseband.hh +++ b/src/baseband.hh @@ -32,7 +32,7 @@ public: public: /** Constructor, the filter center frequency @c Ff equals the given center frequency @c Fc. */ IQBaseBand(double Fc, double width, size_t order, size_t sub_sample, double oFs=0.0) - : Sink(), Source(), FreqShiftBase(_Fc, 0), + : Sink(), Source(), FreqShiftBase(Fc, 0), _Fc(Fc), _Ff(Fc), _Fs(0), _width(width), _order(std::max(size_t(1), order)), _sub_sample(sub_sample), _oFs(oFs), _ring_offset(0), _sample_count(0), _last(0), _kernel(_order) @@ -44,7 +44,7 @@ public: /** Constructor. */ IQBaseBand(double Fc, double Ff, double width, size_t order, size_t sub_sample, double oFs=0.0) - : Sink(), Source(), FreqShiftBase(_Fc, 0), + : Sink(), Source(), FreqShiftBase(Fc, 0), _Fc(Fc), _Ff(Ff), _Fs(0), _width(width), _order(std::max(size_t(1), order)), _sub_sample(sub_sample), _oFs(oFs), _ring_offset(0), _sample_count(0), _last(0), _kernel(_order) diff --git a/src/demod.hh b/src/demod.hh index 7c7ea87..729fa43 100644 --- a/src/demod.hh +++ b/src/demod.hh @@ -108,6 +108,7 @@ public: { // pass... } + /** Destructor. */ virtual ~USBDemod() { // pass... diff --git a/src/freqshift.hh b/src/freqshift.hh index 7b035ae..c18fe4b 100644 --- a/src/freqshift.hh +++ b/src/freqshift.hh @@ -4,6 +4,7 @@ #include "config.hh" #include "traits.hh" #include "node.hh" +#include "operators.hh" namespace sdr { @@ -95,7 +96,7 @@ protected: protected: /** The size of the LUT. */ - static const size_t _lut_size = 127; + static const size_t _lut_size = 128; }; diff --git a/src/gui/spectrum.cc b/src/gui/spectrum.cc index 03c1361..811b5c4 100644 --- a/src/gui/spectrum.cc +++ b/src/gui/spectrum.cc @@ -5,8 +5,25 @@ using namespace sdr; using namespace sdr::gui; +/* ********************************************************************************************* * + * SpectrumProvider + * ********************************************************************************************* */ +SpectrumProvider::SpectrumProvider(QObject *parent) + : QObject(parent) +{ + // pass... +} + +SpectrumProvider::~SpectrumProvider() { + // pass... +} + + +/* ********************************************************************************************* * + * Spectrum + * ********************************************************************************************* */ Spectrum::Spectrum(double rrate, size_t fftsize, size_t max_avg, QObject *parent) : - QObject(parent), _rrate(rrate), _fft_size(fftsize), _fft_buffer(fftsize), + SpectrumProvider(parent), _rrate(rrate), _fft_size(fftsize), _fft_buffer(fftsize), _compute(fftsize), _spectrum(fftsize), _sample_count(0), _N_samples(0), _trafo_count(0), _Ntrafo(max_avg), _samples_left(0), _input_type(Config::Type_UNDEFINED), _sample_rate(0) @@ -43,7 +60,7 @@ Spectrum::config(const Config &src_cfg) { << " Data type: " << _input_type << std::endl << " sample-rate: " << _sample_rate << std::endl << " FFT size: " << _fft_size << std::endl - << " # sample drops: " << _N_samples << std::endl + << " # sample drops: " << _N_samples-1 << std::endl << " # averages: " << _Ntrafo << std::endl << " refresh rate: " << _sample_rate/(_N_samples*_Ntrafo) << "Hz"; Logger::get().log(msg); @@ -69,6 +86,20 @@ Spectrum::isInputReal() const { return false; } +double +Spectrum::sampleRate() const { + return _sample_rate; +} + +size_t +Spectrum::fftSize() const { + return _fft_size; +} + +const Buffer & +Spectrum::spectrum() const { + return _spectrum; +} void Spectrum::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) diff --git a/src/gui/spectrum.hh b/src/gui/spectrum.hh index 2780d77..60ed7e6 100644 --- a/src/gui/spectrum.hh +++ b/src/gui/spectrum.hh @@ -9,9 +9,39 @@ namespace sdr { namespace gui { + +class SpectrumProvider: public QObject +{ + Q_OBJECT + +public: + SpectrumProvider(QObject *parent=0); + + virtual ~SpectrumProvider(); + + /** Returns true if the input is real. */ + virtual bool isInputReal() const = 0; + + /** Retunrs the sample rate. */ + virtual double sampleRate() const = 0; + + /** Returns the FFT size. */ + virtual size_t fftSize() const = 0; + + /** Returns the current spectrum. */ + virtual const Buffer &spectrum() const = 0; + +signals: + /** Gets emitted once the spectrum was updated. */ + void spectrumUpdated(); + /** Gets emitted once the spectrum was reconfigured. */ + void spectrumConfigured(); +}; + + /** Calculates the power spectrum of a singnal. The spectrum gets updated with a specified * rate (if possible). */ -class Spectrum : public QObject, public SinkBase +class Spectrum : public SpectrumProvider, public SinkBase { Q_OBJECT @@ -36,20 +66,13 @@ public: bool isInputReal() const; /** Retunrs the sample rate. */ - inline double sampleRate() const { return _sample_rate; } + double sampleRate() const; /** Returns the FFT size. */ - inline size_t fftSize() const { return _fft_size; } + size_t fftSize() const; /** Returns the current spectrum. */ - inline const Buffer &spectrum() const { return _spectrum; } - - -signals: - /** Gets emitted once the spectrum was updated. */ - void spectrumUpdated(); - /** Gets emitted once the spectrum was reconfigured. */ - void spectrumConfigured(); + const Buffer &spectrum() const; protected: /** Updates the FFT in the _compute buffer. */ diff --git a/src/gui/spectrumview.cc b/src/gui/spectrumview.cc index 395a660..dc98ed0 100644 --- a/src/gui/spectrumview.cc +++ b/src/gui/spectrumview.cc @@ -8,7 +8,7 @@ using namespace sdr; using namespace sdr::gui; -SpectrumView::SpectrumView(Spectrum *spectrum, QWidget *parent) +SpectrumView::SpectrumView(SpectrumProvider *spectrum, QWidget *parent) : QWidget(parent), _spectrum(spectrum), _numXTicks(11), _numYTicks(6), _maxF(std::numeric_limits::infinity()), _mindB(-60) { diff --git a/src/gui/spectrumview.hh b/src/gui/spectrumview.hh index 12cb351..b61205d 100644 --- a/src/gui/spectrumview.hh +++ b/src/gui/spectrumview.hh @@ -18,7 +18,7 @@ Q_OBJECT public: /** @param rrate Specifies the (approx) refreshrate of the FFT plot. */ - explicit SpectrumView(Spectrum *spectrum, QWidget *parent=0); + explicit SpectrumView(SpectrumProvider *spectrum, QWidget *parent=0); inline size_t numXTicks() const { return _numXTicks; } inline void setNumXTicks(size_t N) { _numXTicks=N; update(); } @@ -51,7 +51,7 @@ protected: protected: /** Holds a weak reference to the spectrum recorder object. */ - Spectrum *_spectrum; + SpectrumProvider *_spectrum; /// The font being used for axis labels QFont _axisFont; /// The plot area diff --git a/src/gui/waterfallview.cc b/src/gui/waterfallview.cc index c39ba35..6735ddf 100644 --- a/src/gui/waterfallview.cc +++ b/src/gui/waterfallview.cc @@ -73,7 +73,7 @@ LinearColorMap::map(const double &value) { /* ****************************************************************************************** * * Implementation of WaterFallView * ****************************************************************************************** */ -WaterFallView::WaterFallView(Spectrum *spectrum, size_t height, QWidget *parent) +WaterFallView::WaterFallView(SpectrumProvider *spectrum, size_t height, QWidget *parent) : QWidget(parent), _spectrum(spectrum), _N(_spectrum->fftSize()), _M(height), _waterfall(_N,_M) { setMinimumHeight(height); diff --git a/src/gui/waterfallview.hh b/src/gui/waterfallview.hh index b6af394..c53265b 100644 --- a/src/gui/waterfallview.hh +++ b/src/gui/waterfallview.hh @@ -75,7 +75,7 @@ public: * @param spectrum Specifies the spectrum sink. * @param height Specifies the number of PSDs to display. * @param parent The parent widget. */ - explicit WaterFallView(Spectrum *spectrum, size_t height=100, QWidget *parent = 0); + explicit WaterFallView(SpectrumProvider *spectrum, size_t height=100, QWidget *parent = 0); signals: void click(double f); @@ -93,7 +93,7 @@ protected slots: protected: /** The spectrum sink. */ - Spectrum *_spectrum; + SpectrumProvider *_spectrum; /** The size of the spectrum. */ size_t _N; /** "Height of the spectrum. */ diff --git a/src/subsample.hh b/src/subsample.hh index 1814e7e..f5ac16d 100644 --- a/src/subsample.hh +++ b/src/subsample.hh @@ -234,22 +234,39 @@ public: for (size_t i=0; i<16; i++) { _dl[i] = 0; } _mu = 0; + LogMessage msg(LOG_DEBUG); + msg << "Configure InpolSubSampler node:" << std::endl + << " by: " << _frac << std::endl + << " type: " << src_cfg.type() << std::endl + << " sample-rate: " << src_cfg.sampleRate() + << " -> " << src_cfg.sampleRate()/_frac; + Logger::get().log(msg); + // Propergate config this->setConfig(Config(Traits::scalarId, src_cfg.sampleRate()/_frac, bufSize, 1)); } /** Performs the sub-sampling. */ - virtual void process(const Buffer &buffer, bool allow_overwrite) { + virtual void process(const Buffer &buffer, bool allow_overwrite) + { + // Short cut + if (1 == _frac) { + this->send(buffer, allow_overwrite); + return; + } + size_t i=0, o=0; while (i 1) && (i= 1) && (isend(_buffer.head(o)); } diff --git a/src/wavfile.cc b/src/wavfile.cc index c8fd260..ad87d29 100644 --- a/src/wavfile.cc +++ b/src/wavfile.cc @@ -203,9 +203,7 @@ WavSource::next() if ((0 == _frames_left)) { // Close file _file.close(); - Logger::get().log(LogMessage(LOG_DEBUG, "WavSource: End of file -> stop queue.")); - // and signal queue to stop - Queue::get().stop(); + signalEOS(); return; }