diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0e0b0c1..d0f75de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,12 +2,12 @@ set(LIBSDR_SOURCES buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc - exception.cc logger.cc psk31.cc) + exception.cc logger.cc psk31.cc options.cc) set(LIBSDR_HEADERS sdr.hh math.hh buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh - combine.hh logger.hh psk31.hh interpolate.hh operators.hh) + combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh) if(SDR_WITH_PORTAUDIO) set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) diff --git a/src/options.cc b/src/options.cc new file mode 100644 index 0000000..27463ff --- /dev/null +++ b/src/options.cc @@ -0,0 +1,208 @@ +#include "options.hh" +#include +#include +#include +#include + +#include + +using namespace sdr; + + +/* ********************************************************************************************* * + * Implementation of Options + * ********************************************************************************************* */ +Options::Options() + : _options() +{ + // pass... +} + +bool +Options::has(const char *name) { + return _options.end() != _options.find(name); +} + +const Options::Value & +Options::get(const char *name) { + return _options[name]; +} + +bool +Options::parse(const Definition defs[], int argc, char *argv[], Options &options) +{ + // Get number of definitions + const Definition *def = defs; + int nopts = 0; + while (def->name) { nopts++; def++; } + + struct option *getopt_options = (struct option *)malloc((nopts+1)*sizeof(struct option)); + std::map long_options; + std::map short_options; + + def = defs; struct option *getopt_opt = getopt_options; + std::string short_opt_str; + for (int i=0; iname = def->name; + if (FLAG == def->type) { getopt_opt->has_arg = no_argument; } + else { getopt_opt->has_arg = required_argument; } + getopt_opt->flag = 0; + getopt_opt->val = def->short_name; + long_options[def->name] = *def; + if (def->short_name) { + short_options[def->short_name] = *def; + short_opt_str += def->short_name; + if (FLAG != def->type) { short_opt_str += ':'; } + } + } + // add sentinel + getopt_opt->name = 0; getopt_opt->has_arg = 0; getopt_opt->flag = 0; getopt_opt->val = 0; + + // Parse options using getopt + while (true) { + int option_index = 0; + int c = getopt_long(argc, argv, short_opt_str.c_str(), getopt_options, &option_index); + if (-1 == c) { break; } + + // Handle long option: + if (0 == c) { + const char *name = getopt_options[option_index].name; + if (long_options.end() == long_options.find(name)) { + free(getopt_options); + return false; + } + const Definition &opt = long_options[name]; + if (FLAG == opt.type) { options._options[name] = Value(); } + else if (INTEGER == opt.type) { options._options[name] = atol(optarg); } + else if (FLOAT == opt.type) { options._options[name] = atof(optarg); } + else if (ANY == opt.type) { options._options[name] = Value(optarg); } + } else { + if (short_options.end() == short_options.find(c)) { + free(getopt_options); + return false; + } + const Definition &opt = short_options[c]; + const char *name = opt.name; + if (FLAG == opt.type) { options._options[name] = Value(); } + else if (INTEGER == opt.type) { options._options[name] = atol(optarg); } + else if (FLOAT == opt.type) { options._options[name] = atof(optarg); } + else if (ANY == opt.type) { options._options[name] = Value(optarg); } + } + } + + free(getopt_options); + return true; +} + + +void +Options::print_help(std::ostream &stream, const Definition defs[]) +{ + for (const Definition *def = defs; def->name; def++) { + stream << "--" << def->name; + if (def->short_name) { stream << ", -" << def->short_name; } + if (INTEGER == def->type) { stream << " INTEGER"; } + else if (FLOAT == def->type) { stream << " FLOAT"; } + else if (ANY == def->type) { stream << " VALUE"; } + stream << std::endl; + if (def->help) { + std::istringstream iss(def->help); + std::string line(" "); + do { + std::string word; iss >> word; + if ((line.length()+word.length()) > 78) { + stream << line << std::endl; + line = " "; + } + line += word + " "; + } while (iss); + if (! line.empty()) { stream << line << std::endl; } + } + stream << std::endl; + } +} + + + +/* ********************************************************************************************* * + * Implementation of Value + * ********************************************************************************************* */ +Options::Value::Value() + : _type(NONE) +{ + // pass... +} + +Options::Value::Value(long value) + : _type(INTEGER) +{ + _value.as_int = value; +} + +Options::Value::Value(double value) + : _type(FLOAT) +{ + _value.as_float = value; +} + +Options::Value::Value(const std::string &value) + : _type(STRING) +{ + _value.as_string = strdup(value.c_str()); +} + +Options::Value::~Value() { + if (STRING == _type) { free(_value.as_string); } +} + +Options::Value::Value(const Value &other) + : _type(other._type), _value(other._value) +{ + if (STRING == _type) { _value.as_string = strdup(_value.as_string); } +} + +const Options::Value & +Options::Value::operator =(const Value &other) { + if (STRING == _type) { free(_value.as_string); } + _type = other._type; + if (NONE == _type) { /* pass...*/ } + else if (INTEGER == _type) { _value.as_int = other._value.as_int; } + else if (FLOAT == _type) { _value.as_float = other._value.as_float; } + else if (STRING == _type) { _value.as_string = strdup(other._value.as_string); } + return *this; +} + +bool +Options::Value::isNone() const { + return NONE == _type; +} + +bool +Options::Value::isInteger() const { + return INTEGER == _type; +} + +bool +Options::Value::isFloat() const { + return FLOAT == _type; +} + +bool +Options::Value::isString() const { + return STRING == _type; +} + +long +Options::Value::toInteger() const { + return _value.as_int; +} + +double +Options::Value::toFloat() const { + return _value.as_float; +} + +std::string +Options::Value::toString() const { + return _value.as_string; +} diff --git a/src/options.hh b/src/options.hh new file mode 100644 index 0000000..019c36f --- /dev/null +++ b/src/options.hh @@ -0,0 +1,102 @@ +#ifndef __SDR_OPTIONS_HH__ +#define __SDR_OPTIONS_HH__ + +#include +#include + +namespace sdr { + +/** Convenience functions for command line arguments. */ +class Options +{ +public: + /** The argument value. */ + class Value { + protected: + /** Value type. */ + typedef enum { + NONE, ///< Empty or invalid value. + INTEGER, ///< An integer (long int). + FLOAT, ///< A floating point number (double). + STRING ///< An ASCII string. + } Type; + + public: + /** Empty constructor. */ + Value(); + /** Integer constructor. */ + Value(long value); + /** Floating point constructor. */ + Value(double value); + /** String constructor. */ + Value(const std::string &value); + /** Copy constructor. */ + Value(const Value &other); + /** Destructor. */ + ~Value(); + /** Assignment. */ + const Value &operator=(const Value &other); + /** Returns @c true if the value is empty. */ + bool isNone() const; + /** Returns @c true if the value is an integer. */ + bool isInteger() const; + /** Returns @c true if the value is a floating point number. */ + bool isFloat() const; + /** Returns @c true if the value is a string. */ + bool isString() const; + /** Turns the value into an integer. */ + long toInteger() const; + /** Turns the value into a floating point number. */ + double toFloat() const; + /** Turns the value into a string. */ + std::string toString() const; + + protected: + /** The type of the value. */ + Type _type; + /** Values. */ + union { + long as_int; + double as_float; + char *as_string; + } _value; + }; + + /** Possible argument types. */ + typedef enum { + FLAG, ///< No argument (flag). + INTEGER, ///< Integer argument. + FLOAT, ///< Floating point argument. + ANY ///< Any argument (string). + } ArgType; + + /** Argument definition. */ + typedef struct { + const char *name; ///< Argument name (long). + char short_name; ///< Argument name (short). + ArgType type; ///< Argument type. + const char *help; ///< Help string. + } Definition; + + /** Parse the given arguments (@c argc, @c argv) using the definitions @c defs and stores + * the results into @c options. Returns @c false on error. */ + static bool parse(const Definition defs[], int argc, char *argv[], Options &options); + /** Serializes a help text derived from given the definitions into @c stream. */ + static void print_help(std::ostream &stream, const Definition defs[]); + +public: + /** Empty constructor. */ + Options(); + /** Returns @c true if the specified option was found (long name). */ + bool has(const char *name); + /** Returns the argument of the specified option (long name). */ + const Value &get(const char *name); + +protected: + /** The table of option names and argument values. */ + std::map _options; +}; + +} + +#endif // __SDR_OPTIONS_HH__ diff --git a/src/sdr.hh b/src/sdr.hh index 4fbfbb4..8c7b2b5 100644 --- a/src/sdr.hh +++ b/src/sdr.hh @@ -1,4 +1,78 @@ /** @mainpage A C++ library for software defined radio (SDR). + * + * libsdr is a simple C++ library allowing to assemble software defined radio (SDR) applications + * easily. The library is a collection of (mostly) template classes implementing a wide varity of + * procssing nodes. By connecting these processing nodes, a stream-processing chain is constructed + * which turns raw input data into something meaningful. + * + * Please note, I have written this library for my own amusement and to learn something about SDR. + * If you search for a more complete and efficient SDR library, consider GNU radio. + * + * A processing node is either a @c sdr::Source, @c sdr::Sink, both or may provide one or more + * sources or sinks. A source is always connected to a sink. Another important object is the + * @c sdr::Queue. It is a singleton class that orchestrates the processing of the data. It may + * request further data from the sources once all present data has been processed. It also + * routes the date from the sources to the sinks. + * + * The following examples shows a trivial application that recods some audio from the systems + * default audio source and play it back. + * \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 + * 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 + * + * First, the PortAudio system gets initialized. + * + * Then, the audio source is constructed. The + * argument specifies the sample rate in Hz. Here a sample rate of 44100 Hz is used. The template + * argument of @c sdr::PortSource specifies the input type. Here a signed 16bit integer is used. + * The audio source will have only one channel (mono). + * + * The second node is the audio (playback) sink, which takes no arguments. It gets configured once + * the source is connected to the sink with @c source.connect(&sink). + * + * In a next step, the sources @c next method gets connected to the "idle" signal of the queue. + * This is necessary as the audio source does not send data by its own. Whenever the @c next method + * gets called, the source will send a certain amount of captured data to the connected sinks. Some + * nodes will send data to the connected sinks without the need to explicit triggering. The + * @c sdr::PortSource node, however, needs that explicit triggering. The "idle" event gets emitted + * once the queue gets empty, means whenever all data has been processes (here, played back). + * + * As mentioned aboce, the queue is a singleton class. Means that for every process, there is + * exactly one instance of the queue. This singleton instance is accessed by calling the static + * method @c sdr::Queue::get. By calling @c sdr::Queue::start, the queue is started in a separate + * thread. This threads is responsible for all the processing of the data which allows to perform + * other tasks in the main thread, i.e. GUI stuff. A call to @c sdr::Queue::wait, will wait for + * the processing thread to exit, which will never happen in that particular example. + * + * 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. */ #ifndef __SDR_HH__ @@ -14,6 +88,7 @@ #include "queue.hh" #include "combine.hh" #include "logger.hh" +#include "options.hh" #include "utils.hh" #include "demod.hh"