From 1ed605135a0d9b6d0033f5d16084efc217b82176 Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Thu, 18 Dec 2014 13:43:59 +0100 Subject: [PATCH] Updated docs. --- src/autocast.hh | 1 + src/node.cc | 3 +- src/queue.hh | 5 + src/sdr.hh | 237 +++++++++++++++++++++++++++++++++++++++++++----- src/utils.hh | 15 ++- 5 files changed, 236 insertions(+), 25 deletions(-) diff --git a/src/autocast.hh b/src/autocast.hh index f04a75a..cbac8ad 100644 --- a/src/autocast.hh +++ b/src/autocast.hh @@ -139,6 +139,7 @@ protected: return 2*N; } + /** std::complex -> std::complex. */ static size_t _cuint8_cint8(const RawBuffer &in, const RawBuffer &out) { size_t N = in.bytesLen()/2; std::complex *values = reinterpret_cast *>(in.data()); diff --git a/src/node.cc b/src/node.cc index eb63fae..1ce2e85 100644 --- a/src/node.cc +++ b/src/node.cc @@ -75,8 +75,7 @@ Source::send(const RawBuffer &buffer, bool allow_overwrite) { allow_overwrite = allow_overwrite && (1 == _sinks.size()); // Call sink directly item->first->handleBuffer(buffer, allow_overwrite); - } - else { + } else { // otherwise, queue buffer allow_overwrite = allow_overwrite && (1 == _sinks.size()); Queue::get().send(buffer, item->first, allow_overwrite); diff --git a/src/queue.hh b/src/queue.hh index 27fe597..7c39e5b 100644 --- a/src/queue.hh +++ b/src/queue.hh @@ -18,6 +18,7 @@ class DelegateInterface { public: /** Call back interface. */ virtual void operator() () = 0; + /** Returns the instance of the delegate. */ virtual void *instance() = 0; }; @@ -32,6 +33,7 @@ public: virtual ~Delegate() {} /** Callback, simply calls the method of the instance given to the constructor. */ virtual void operator() () { (_instance->*_function)(); } + /** Returns the instance of the delegate. */ virtual void *instance() { return _instance; } protected: @@ -126,6 +128,7 @@ public: _idle.push_back(new Delegate(instance, function)); } + /** Removes all callbacks of the given instance from the idle signal. */ template void remIdle(T *instance) { std::list::iterator item = _idle.begin(); @@ -144,6 +147,7 @@ public: _onStart.push_back(new Delegate(instance, function)); } + /** Removes all callbacks of the given instance from the start signal. */ template void remStart(T *instance) { std::list::iterator item = _onStart.begin(); @@ -162,6 +166,7 @@ public: _onStop.push_back(new Delegate(instance, function)); } + /** Removes all callbacks of the given instance from the stop signal. */ template void remStop(T *instance) { std::list::iterator item = _onStop.begin(); diff --git a/src/sdr.hh b/src/sdr.hh index 8c7b2b5..4f110d9 100644 --- a/src/sdr.hh +++ b/src/sdr.hh @@ -14,37 +14,38 @@ * request further data from the sources once all present data has been processed. It also * routes the date from the sources to the sinks. * + * \section intro A practical introduction * The following examples shows a trivial application that recods some audio from the systems * default audio source and play it back. * \code - * #include + * #include * - * int main(int argc, char *argv[]) { - * // Initialize PortAudio system - * sdr::PortAudio::init(); + * int main(int argc, char *argv[]) { + * // Initialize PortAudio system + * sdr::PortAudio::init(); * - * // Create an audio source using PortAudio - * sdr::PortSource source(44.1e3); - * // Create an audio sink using PortAudio - * sdr::PortSink sink; + * // Create an audio source using PortAudio + * sdr::PortSource source(44.1e3); + * // Create an audio sink using PortAudio + * sdr::PortSink sink; * - * // Connect them - * source.connect(&sink); + * // Connect them + * source.connect(&sink); * - * // Read new data from audio sink if queue is idle - * sdr::Queue::get().addIdle(&source, &sdr::PortSource::next); + * // Read new data from audio sink if queue is idle + * sdr::Queue::get().addIdle(&source, &sdr::PortSource::next); * - * // Get and start queue - * sdr::Queue::get().start(); - * // Wait for queue to stop - * sdr::Queue::get().wait(); + * // Get and start queue + * sdr::Queue::get().start(); + * // Wait for queue to stop + * sdr::Queue::get().wait(); * - * // Terminate PortAudio system - * sdr::PortAudio::terminate(); + * // Terminate PortAudio system + * sdr::PortAudio::terminate(); * - * // done. - * return 0; - * } + * // done. + * return 0; + * } * \endcode * * First, the PortAudio system gets initialized. @@ -73,6 +74,192 @@ * * The queue can be stopped by calling the @c sdr::Queue::stop method. This can be implemented for * this example by the means of process signals. + * + * \code + * #include + * ... + * + * static void __sigint_handler(int signo) { + * // On SIGINT -> stop queue properly + * sdr::Queue::get().stop(); + * } + * + * int main(int argc, char *argv[]) { + * // Register signal handler + * signal(SIGINT, __sigint_handler); + * ... + * } + * \endcode + * + * Whenever a SIGINT is send to the process, i.e. by pressing CTRL+C, the @c sdr::Queue::stop method + * gets called. This will cause the processing thread of the queue to exit and the call to + * @c sdr::Queue::wait to return. + * + * \subsection example2 Queue less operation + * Sometimes, the queue is simply not needed. This is particularily the case if the data processing + * can happen in the main thread, i.e. if there is not GUI. The example above can be implemented + * without the Queue, as the main thread is just waiting for the processing thread to exit. + * + * \code + * #include + * + * int main(int argc, char *argv[]) { + * // Initialize PortAudio system + * sdr::PortAudio::init(); + * + * // Create an audio source using PortAudio + * sdr::PortSource source(44.1e3); + * // Create an audio sink using PortAudio + * sdr::PortSink sink; + * + * // Connect them directly + * source.connect(&sink, true); + * + * // Loop to infinity7 + * while(true) { source.next(); } + * + * // Terminate PortAudio system + * sdr::PortAudio::terminate(); + * + * // done. + * return 0; + * } + * \endcode + * + * The major difference between the first example and this one, is the way how the nodes are + * connected. The @c sdr::Source::connect method takes an optional argument specifying wheter the + * source is connected directly to the sink or not. If @c false (the default) is specified, the + * data of the source will be send to the Queue first. In a direct connection (passing @c true), the + * source will send the data directly to the sink, bypassing the queue. + * + * Instead of starting the processing thread of the queue, here the main thread is doing all the + * work by calling the @c next mehtod of the audio source. + * + * \subsection logging Log messages + * During configuration and operation, processing nodes will send log messages of different levels + * (DEBUG, INFO, WARNING, ERROR), which allow to debug the operation of the complete processing + * chain. These log messages are passed around using the build-in @c sdr::Logger class. To make + * them visible, a log handler must be installed. + * + * \code + * int main(int argc, char *argv[]) { + * ... + * // Install the log handler... + * sdr::Logger::get().addHandler( + * new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); + * ... + * } + * \endcode + * + * Like the @c sdr::Queue, the logger is also a singleton object, which can be obtained by + * @c sdr::Logger::get. By calling @c sdr::Logger::addHandler, a new message handler is installed. + * In this example, a @c sdr::StreamLogHandler instance is installed, which serializes the + * log messages into @c std::cerr. + * + * \subsection intro_summary In summary + * In summary, the complete example above using the queue including a singal handler to properly + * terminate the application by SIGINT and a log handler will look like + * + * \code + * #include + * #include + * + * static void __sigint_handler(int signo) { + * // On SIGINT -> stop queue properly + * sdr::Queue::get().stop(); + * } + * + * int main(int argc, char *argv[]) { + * // Register signal handler + * signal(SIGINT, __sigint_handler); + * + * // Initialize PortAudio system + * sdr::PortAudio::init(); + * + * // Create an audio source using PortAudio + * sdr::PortSource source(44.1e3); + * // Create an audio sink using PortAudio + * sdr::PortSink sink; + * + * // Connect them + * source.connect(&sink); + * + * // Read new data from audio sink if queue is idle + * sdr::Queue::get().addIdle(&source, &sdr::PortSource::next); + * + * // Get and start queue + * sdr::Queue::get().start(); + * // Wait for queue to stop + * sdr::Queue::get().wait(); + * + * // Terminate PortAudio system + * sdr::PortAudio::terminate(); + * + * // done. + * return 0; + * } + * \endcode + * This may appear quiet bloated for such a simple application. I designed the library to be rather + * explicit. No feature is implicit and hidden from the user. This turns simple examples like the + * one above quite bloated but it is imediately clear how the example works whithout any knowledge + * of "hidden features" and the complexity does not suddenly increases for non-trivial examples. + * + * Finally, we may have a look at a more relaistic example implementing a FM broadcast receiver + * using a RTL2832 based USB dongle as the input source. + * \code + * #include + * #include + * + * static void __sigint_handler(int signo) { + * // On SIGINT -> stop queue properly + * sdr::Queue::get().stop(); + * } + * + * int main(int argc, char *argv[]) { + * // Register signal handler + * signal(SIGINT, __sigint_handler); + * + * // Initialize PortAudio system + * sdr::PortAudio::init(); + * + * // Frequency of the FM station (in Hz) + * double freq = 100e6; + * + * // Create a RTL2832 input node + * sdr::RTLSource src(freq); + * // Filter 100kHz around the center frequency (0) with an 16th order FIR filter and + * // subsample the result to a sample rate of approx. 100kHz. + * sdr::IQBaseBand baseband(0, 100e3, 16, 0, 100e3); + * // FM demodulator, takes a complex int8_t stream and returns a real int16_t stream + * sdr::FMDemod demod; + * // Deemphesize the result (actually part of the demodulation) + * sdr::FMDeemph deemph; + * // Playback the final signal + * sdr::PortSink sink; + * + * // Connect signals + * src.connect(&baseband, true); + * baseband.connect(&demod); + * demod.connect(&deemph); + * deemph.connect(&sink); + * + * // Connect start and stop signals of Queue to RTL2832 source + * sdr::Queue::get().addStart(&src, &sdr::RTLSource::start); + * sdr::Queue::get().addStop(&src, &sdr::RTLSource::stop); + * + * // Start queue + * sdr::Queue::get().start(); + * // Wait for queue + * sdr::Queue::get().wait(); + * + * // Terminate PortAudio system + * sdr::PortAudio::terminate(); + * + * // Done... + * return 0; + * } + * \endcode + * */ #ifndef __SDR_HH__ @@ -91,12 +278,18 @@ #include "options.hh" #include "utils.hh" -#include "demod.hh" #include "siggen.hh" #include "buffernode.hh" #include "wavfile.hh" #include "firfilter.hh" #include "autocast.hh" +#include "freqshift.hh" +#include "interpolate.hh" +#include "subsample.hh" +#include "baseband.hh" + +#include "demod.hh" +#include "psk31.hh" #ifdef SDR_WITH_FFTW #include "filternode.hh" diff --git a/src/utils.hh b/src/utils.hh index e5fd8cc..fc3fd00 100644 --- a/src/utils.hh +++ b/src/utils.hh @@ -104,7 +104,7 @@ public: }; -/** A simple node, that allows to balance the IQ signal. */ +/** A simple node, that allows to balance an IQ signal. */ template class IQBalance: public Sink< std::complex >, public Source { @@ -113,6 +113,10 @@ public: typedef typename Traits::SScalar SScalar; public: + /** Constructor. + * @param balance Specifies the balance between the I and Q chanel. If @c balance = 1, only the + * I chanel remains, on @c balance = -1 only the Q chanel remains and on @c balance = 0 + * both chanels are balanced equally. */ IQBalance(double balance=0.0) : Sink< std::complex >(), Source(), _realFact(1), _imagFact(1) { @@ -128,10 +132,12 @@ public: } } + /** Destructor. */ virtual ~IQBalance() { _buffer.unref(); } + /** Retunrs the balance. */ double balance() const { if (_realFact != (1<<8)) { return (double(_realFact)/(1<<8)-1); @@ -139,6 +145,7 @@ public: return (1-double(_imagFact)/(1<<8)); } + /** Sets the I/Q balance. */ void setBalance(double balance) { if (balance < 0) { // scale real part @@ -152,6 +159,7 @@ public: } } + /** Configures the node. */ virtual void config(const Config &src_cfg) { // Check if config is complete if (! src_cfg.hasBufferSize()) { return; } @@ -162,6 +170,7 @@ public: this->setConfig(cfg); } + /** Processes a buffer. */ virtual void process(const Buffer > &buffer, bool allow_overwrite) { if (allow_overwrite) { _process(buffer, buffer); @@ -173,6 +182,7 @@ public: } protected: + /** The actual implementation. */ void _process(const Buffer< std::complex > &in, const Buffer< std::complex > &out) { for (size_t i=0; i((_realFact*SScalar(in[i].real()))/(1<<8), @@ -181,8 +191,11 @@ protected: } protected: + /** Scaleing factor for the real part. */ int32_t _realFact; + /** Scaleing factor for the imaginary part. */ int32_t _imagFact; + /** The working buffer. */ Buffer< std::complex > _buffer; };