diff options
| author | Vasile Vilvoiu <vasi@vilvoiu.ro> | 2021-07-21 21:14:30 +0300 |
|---|---|---|
| committer | Vasile Vilvoiu <vasi@vilvoiu.ro> | 2021-07-21 21:14:30 +0300 |
| commit | ee8a1573204f76b16b9fb711608447aabee55696 (patch) | |
| tree | 50bbcf182716ee0b5b2e5c1ecf104f7143d0bbfe | |
| parent | a7c430fa81c9e22dbce74869a0a27304da78855b (diff) | |
Added header file comments for classes and methods.
Renamed all factory methods to ::Build().
| -rw-r--r-- | src/color-map.cpp | 4 | ||||
| -rw-r--r-- | src/color-map.hpp | 65 | ||||
| -rw-r--r-- | src/configuration.cpp | 2 | ||||
| -rw-r--r-- | src/configuration.hpp | 95 | ||||
| -rw-r--r-- | src/fft.hpp | 77 | ||||
| -rw-r--r-- | src/input-parser.cpp | 2 | ||||
| -rw-r--r-- | src/input-parser.hpp | 73 | ||||
| -rw-r--r-- | src/input-reader.hpp | 31 | ||||
| -rw-r--r-- | src/live.hpp | 22 | ||||
| -rw-r--r-- | src/renderer.cpp | 4 | ||||
| -rw-r--r-- | src/renderer.hpp | 64 | ||||
| -rw-r--r-- | src/specgram.cpp | 11 | ||||
| -rw-r--r-- | src/value-map.hpp | 64 | ||||
| -rw-r--r-- | src/window-function.cpp | 2 | ||||
| -rw-r--r-- | src/window-function.hpp | 45 | ||||
| -rw-r--r-- | test/test-color-map.cpp | 2 | ||||
| -rw-r--r-- | test/test-input-parser.cpp | 106 | ||||
| -rw-r--r-- | test/test-window-function.cpp | 8 |
18 files changed, 526 insertions, 151 deletions
diff --git a/src/color-map.cpp b/src/color-map.cpp index d1d199a..7bf7e4e 100644 --- a/src/color-map.cpp +++ b/src/color-map.cpp @@ -10,9 +10,7 @@ #include <cassert> std::unique_ptr<ColorMap> -ColorMap::FromType(ColorMapType type, - const sf::Color& bg_color, - const sf::Color& custom_color) +ColorMap::Build(ColorMapType type, const sf::Color& bg_color, const sf::Color& custom_color) { /* colormaps are not allowed to be translucent */ sf::Color opaque_bg_color(bg_color.r, bg_color.g, bg_color.b, 255); diff --git a/src/color-map.hpp b/src/color-map.hpp index 139edaf..f466923 100644 --- a/src/color-map.hpp +++ b/src/color-map.hpp @@ -31,6 +31,12 @@ enum class ColorMapType { kCustom }; +/** + * Base color map class. + * + * This maps values in the domain [0..1] to 32bit RGBA colors, based on + * various color schemes. + */ class ColorMap { protected: ColorMap() = default; @@ -38,16 +44,41 @@ protected: public: ColorMap(const ColorMap&) = default; - static std::unique_ptr<ColorMap> FromType(ColorMapType type, - const sf::Color& bg_color, - const sf::Color& custom_color); - + /** + * Factory method for colormaps. + * @param type One of ColorMapType. + * @param bg_color Background color (where applicable). + * @param custom_color Custom color (where applicable). + * @return A new ColorMap object. + */ + static std::unique_ptr<ColorMap> Build(ColorMapType type, + const sf::Color& bg_color, + const sf::Color& custom_color); + + /** + * Map a window of real values to RGBA colours. + * @param input Array of floating point values in the domain [0..1]. + * @return Array of bytes, 4 for each input value, RGBA format. + */ virtual std::vector<uint8_t> Map(const RealWindow& input) const = 0; + + /** + * Create a gradient of the colormap, displaying all possible colors. + * @param width Width of the gradient (in pixels). + * @return Array of bytes, one for each pixel, RGBA format. + */ std::vector<uint8_t> Gradient(std::size_t width) const; + /** + * @return Copy of this colormap. + */ virtual std::unique_ptr<ColorMap> Copy() const = 0; }; +/** + * Color map that linearly interpolates between a number of specified colors, + * based on value landmarks. + */ class InterpolationColorMap : public ColorMap { private: const std::vector<sf::Color> colors_; @@ -56,6 +87,14 @@ private: std::vector<uint8_t> GetColor(double value) const; public: + /** + * Create an interpolation-based colormap. + * @param colors Vectors of colors used for interpolation. + * @param vals The corresponding value for each color. + * + * NOTE: First value in "vals" must be 0.0 and last value must be "1.0". + * Thus, the whole [0..1] domain is covered. + */ InterpolationColorMap(const std::vector<sf::Color>& colors, const std::vector<double>& vals); InterpolationColorMap() = delete; @@ -63,16 +102,34 @@ public: std::unique_ptr<ColorMap> Copy() const override; }; +/** + * Specialization for only two colors. + */ class TwoColorMap : public InterpolationColorMap { public: + /** + * @param c1 First color, corresponding to 0.0. + * @param c2 Second color, corresponding to 1.0. + */ TwoColorMap(const sf::Color& c1, const sf::Color& c2); }; +/** + * Specialization for only three colors. + */ class ThreeColorMap : public InterpolationColorMap { public: + /** + * @param c1 First color, corresponding to 0.0. + * @param c2 Second color, corresponding to 0.5. + * @param c3 Third color, corresponding to 1.0. + */ ThreeColorMap(const sf::Color& c1, const sf::Color& c2, const sf::Color& c3); }; +/** + * Jet colormap, as seen in MATLAB, matplotlib and others. + */ class JetColorMap : public InterpolationColorMap { public: JetColorMap(); diff --git a/src/configuration.cpp b/src/configuration.cpp index 14bcacb..8071345 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -146,7 +146,7 @@ Configuration::StringToScale(const std::string &str) } std::tuple<Configuration, int, bool> -Configuration::FromArgs(int argc, char **argv) +Configuration::Build(int argc, char **argv) { Configuration conf; diff --git a/src/configuration.hpp b/src/configuration.hpp index f6fff18..7659237 100644 --- a/src/configuration.hpp +++ b/src/configuration.hpp @@ -17,48 +17,52 @@ #include <optional> #include <tuple> +/** + * Configuration God object. This parses program arguments into usable, + * structured configuration options. + */ class Configuration { private: std::optional<std::string> input_filename_; std::optional<std::string> output_filename_; - bool dump_to_stdout_; - - std::size_t block_size_; - double rate_; - DataType datatype_; - bool has_complex_input_; - double prescale_factor_; - - std::size_t fft_width_; - std::size_t fft_stride_; - WindowFunctionType window_function_; - std::size_t average_count_; - bool alias_negative_; - - bool no_resampling_; - std::size_t width_; - double min_freq_; - double max_freq_; - ValueMapType scale_type_; - std::string scale_unit_; + bool dump_to_stdout_; /* true if output PNG image must go to stdout */ + + std::size_t block_size_; /* group read values in blocks of block_size_ items */ + double rate_; /* sampling rate of signal, in Hz */ + DataType datatype_; /* input data type (does not cover complex/real discrimination) */ + bool has_complex_input_; /* true if input is complex */ + double prescale_factor_; /* value to scale input with before applying other transformations */ + + std::size_t fft_width_; /* size of FFT window, in values */ + std::size_t fft_stride_; /* stride of FFT window, in values */ + WindowFunctionType window_function_; /* window function to apply before FFT */ + std::size_t average_count_; /* number of windows to average for each displayed window */ + bool alias_negative_; /* alias negative frequencies to positive */ + + bool no_resampling_; /* do not perform resampling; if true, width_ is meaningless */ + std::size_t width_; /* width of resampled output window, in values or pixels */ + double min_freq_; /* lower bound of displayed frewquency band */ + double max_freq_; /* upper bound of displayed frewquency band */ + ValueMapType scale_type_; /* type of scale used on FFT output */ + std::string scale_unit_; /* unit of the scale (just the text) */ double scale_lower_bound_; double scale_upper_bound_; - ColorMapType color_map_; - sf::Color color_map_custom_color_; - sf::Color background_color_; - sf::Color foreground_color_; - bool has_axes_; - bool has_legend_; - bool is_horizontal_; - bool print_input_; - bool print_fft_; - bool print_output_; - - bool live_; - std::size_t count_; - std::string title_; - - bool has_live_window_; + ColorMapType color_map_; /* color map to apply to scaled output */ + sf::Color color_map_custom_color_; /* custom color for color map (where applicable) */ + sf::Color background_color_; /* background color of rendered output */ + sf::Color foreground_color_; /* foreground color of rendered output (text, axes etc) */ + bool has_axes_; /* render axes */ + bool has_legend_; /* render legend */ + bool is_horizontal_; /* flows from left to right, instead of top to bottom */ + bool print_input_; /* debug printing of input */ + bool print_fft_; /* debug printing of FFT values */ + bool print_output_; /* debug printing of output */ + + bool live_; /* whether we have live output or not */ + std::size_t count_; /* number of output windows to display in spectrogoram */ + std::string title_; /* window title */ + + bool has_live_window_; /* display a live plot of the current FFT window */ std::size_t margin_size_; std::size_t live_margin_size_; @@ -69,15 +73,30 @@ private: Configuration(); + /** + * Translate a hex color to a sf::Color. + * @param str RGB/RGBA hex color code. + * @return Color. + */ static sf::Color StringToColor(const std::string& str); + /** + * Parse a scale string. + * @param str Scale string. + * @return Optional bounds and unit string. + */ using OptionalBound = std::optional<double>; using ScaleProperties = std::tuple<OptionalBound, OptionalBound, std::string>; static ScaleProperties StringToScale(const std::string& str); public: - /* parse command line arguments and return a configuration object */ - static std::tuple<Configuration, int, bool> FromArgs(int argc, char **argv); + /** + * Parse command line arguments and return a configuration object. + * @param argc Argument strings. + * @param argv Argument count. + * @return New Configuration object instance. + */ + static std::tuple<Configuration, int, bool> Build(int argc, char **argv); /* generic getters */ Configuration GetForLive() const; diff --git a/src/fft.hpp b/src/fft.hpp index 9008c05..1cf3e7e 100644 --- a/src/fft.hpp +++ b/src/fft.hpp @@ -12,6 +12,9 @@ #include <fftw3.h> +/** + * Computes the fast Fourier transform of an input window. + */ class FFT { private: /* FFT window width */ @@ -28,21 +31,95 @@ private: std::unique_ptr<WindowFunction> window_function_; public: + /* plan and buffers are not copiable */ FFT() = delete; FFT(const FFT &c) = delete; FFT(FFT &&) = delete; FFT & operator=(const FFT&) = delete; + /** + * @param win_width Width of window this object must support. + * NOTE: Only supports this window size! + */ explicit FFT(std::size_t win_width); + + /** + * @param win_width Width of window this object must support. + * @param win_func Window function to apply before FFT computations. + * NOTE: Only supports this window size! + */ FFT(std::size_t win_width, std::unique_ptr<WindowFunction>& win_func); + virtual ~FFT(); + /** + * Compute the fast Fourier transform. + * @param input Array of complex input values. + * @return Complex values corresponding to the FFT terms, equal in size to input. + * + * NOTE: This functions normalizes the output by 1/N. + * NOTE: This function provides the negative frequencies in the first half + * and the positive frequencies in the second half. + * NOTE: For even-sized inputs, the Nyquist frequency term is the last in + * the output vector. + */ ComplexWindow Compute(const ComplexWindow& input); + /** + * Retrieve the (real) magnitudes of a complex input vector (window). + * @param input Array of complex values. + * @param alias If true, will alias negative and positive frequencies. + * @return Array of real values, containing amplitudes. + * + * NOTE: When aliasing, each output value will be the sum of that term's + * magnitude as well as the corresponding negative or positive + * frequency term's magnitude. + */ static RealWindow GetMagnitude(const ComplexWindow& input, bool alias); + + /** + * Compute the frequency bounds of a specific FFT window. + * @param rate Sampling rate of the input signal. + * @param width Width of the FFT window. + * @return Lower and upper bounds, in Hz. + */ static std::tuple<double, double> GetFrequencyLimits(double rate, std::size_t width); + + /** + * Compute the index in the FFT window of a given frequency. + * @param rate Sampling rate of input. + * @param width Width of the FFT window. + * @param f Frequency, in Hz. + * @return Index (computed exactly) of the frequency. + */ static double GetFrequencyIndex(double rate, std::size_t width, double f); + + /** + * Resample a real, scaled output of the FFT. + * @param input Real window, values between [0..1]. + * @param rate Sampling rate of input. + * @param width Desired output width. + * @param fmin Frequency lower bound (from input). + * @param fmax Frequency upper bound (from input). + * @return Resampled values, clamped to [0..1]. + * + * NOTE: Uses Lanczos resampling algorithm. + * NOTE: Will resize the [fmin..fmax] band from input (computed as if + * input is a FFT output) to a width-sized output window. + */ static RealWindow Resample(const RealWindow& input, double rate, std::size_t width, double fmin, double fmax); + + /** + * Crop a real, scaled output of the FFT. + * @param input Real window, values between [0..1]. + * @param rate Sampling rate of input. + * @param fmin fmin Frequency lower bound (from input). + * @param fmax fmax Frequency upper bound (from input). + * @return Cropped window. + * + * NOTE: The [fmin..fmax] band from input is computed as if input is a FFT + * output, which under normal circumstances it should be. + */ static RealWindow Crop(const RealWindow& input, double rate, double fmin, double fmax); }; diff --git a/src/input-parser.cpp b/src/input-parser.cpp index b5dd55b..677aa48 100644 --- a/src/input-parser.cpp +++ b/src/input-parser.cpp @@ -38,7 +38,7 @@ InputParser::RemoveValues(std::size_t count) } std::unique_ptr<InputParser> -InputParser::FromDataType(DataType dtype, double prescale, bool is_complex) +InputParser::Build(DataType dtype, double prescale, bool is_complex) { if (dtype == DataType::kSignedInt8) { return std::make_unique<IntegerInputParser<std::int8_t>>(prescale, is_complex); diff --git a/src/input-parser.hpp b/src/input-parser.hpp index 8f925bc..9992b31 100644 --- a/src/input-parser.hpp +++ b/src/input-parser.hpp @@ -11,7 +11,9 @@ #include <complex> #include <memory> -/* Input data type */ +/** + * Input data type + */ enum class DataType { /* signed integer */ kSignedInt8, @@ -39,16 +41,22 @@ typedef std::vector<double> RealWindow; /* Window of complex numbers */ typedef std::vector<Complex> ComplexWindow; -/* +/** * Input parser base class */ class InputParser { protected: - double prescale_factor_; - bool is_complex_; - std::vector<Complex> values_; + double prescale_factor_; /* factor that is applied before further processing */ + bool is_complex_; /* input is complex? */ + std::vector<Complex> values_; /* parsed values */ InputParser() = delete; + + /** + * @param prescale Prescale factor, applied after parsing. + * @param is_complex If true, input is treated as complex-typed. Two input + * values will be read for each output value. + */ explicit InputParser(double prescale, bool is_complex); public: @@ -57,22 +65,65 @@ public: InputParser & operator=(const InputParser&) = delete; virtual ~InputParser() = default; - static std::unique_ptr<InputParser> FromDataType(DataType dtype, double prescale, bool is_complex); - + /** + * Factory method for retrieving a suitable parser. + * @param dtype Input data type. + * @param prescale Prescale factor to apply after parsing. + * @param is_complex If true, input is treated as complex-typed. Two input + * values will be read for each output value. + * @return New Parser object. + */ + static std::unique_ptr<InputParser> Build(DataType dtype, double prescale, bool is_complex); + + /** + * @return The number of values that have been parsed, but not yet retrieved. + */ std::size_t GetBufferedValueCount() const; + /** + * Retrieves, without removing, from the parsed (buffered) value array. + * @param count Number of values to retrieve. + * @return + */ std::vector<Complex> PeekValues(std::size_t count) const; + + /** + * Removes values from the parsed (buffered) values array. + * @param count Number of values to remove. + */ void RemoveValues(std::size_t count); + /** + * Parses a block of bytes. + * @param block Block of bytes that must have a size that is a multiple of + * the underlying data type size (or twice that for complex). + * @return Number of parsed values. + */ virtual std::size_t ParseBlock(const std::vector<char> &block) = 0; + + /** + * @return Size of the underlying data type (or twice for complex). + */ virtual std::size_t GetDataTypeSize() const = 0; + + /** + * @return True if underlying data type is signed. + */ virtual bool IsSigned() const = 0; + + /** + * @return True if underlying data type is floating point. + */ virtual bool IsFloatingPoint() const = 0; + + /** + * @return True if parsing complex input. + */ virtual bool IsComplex() const { return is_complex_; }; }; -/* - * Integer input parser +/** + * Specialized parser for integer input. */ template <class T> class IntegerInputParser : public InputParser { @@ -87,8 +138,8 @@ public: bool IsFloatingPoint() const override { return false; }; }; -/* - * Floating point input parser +/** + * Specialized parser for floating point input. */ template <class T> class FloatInputParser : public InputParser { diff --git a/src/input-reader.hpp b/src/input-reader.hpp index 3a98ef8..88843aa 100644 --- a/src/input-reader.hpp +++ b/src/input-reader.hpp @@ -13,14 +13,18 @@ #include <thread> #include <vector> -/* +/** * Input reader base class */ class InputReader { protected: - std::istream * const stream_; - const std::size_t block_size_bytes_; + std::istream * const stream_; /* stream we are straddling */ + const std::size_t block_size_bytes_; /* the size of a block in bytes */ + /** + * Retrieves an internal buffer that may or may not be block sized. + * @return Buffer. + */ virtual std::vector<char> GetBuffer() = 0; public: @@ -29,15 +33,26 @@ public: InputReader(InputReader&&) = delete; InputReader & operator=(const InputReader&) = delete; + /** + * @param stream Input stream to use. + * @param block_size_bytes Block size in bytes. + */ InputReader(std::istream * stream, std::size_t block_size_bytes); virtual ~InputReader() = default; + /** + * @return True if end of stream was received. + */ virtual bool ReachedEOF() const = 0; + + /** + * @return A block of bytes, if such a block exists. + */ virtual std::optional<std::vector<char>> GetBlock() = 0; }; -/* - * Synchronous input reader +/** + * Synchronous input reader specialization */ class SyncInputReader : public InputReader { protected: @@ -50,8 +65,8 @@ public: std::optional<std::vector<char>> GetBlock() override; }; -/* - * Asynchronous input reader +/** + * Asynchronous input reader specialization. */ class AsyncInputReader : public InputReader { private: @@ -75,7 +90,7 @@ public: AsyncInputReader(std::istream * stream, std::size_t block_size_bytes); ~AsyncInputReader() override; - bool ReachedEOF() const override; + bool ReachedEOF() const override; /* no EOF support is assumed in async input */ std::optional<std::vector<char>> GetBlock() override; }; diff --git a/src/live.hpp b/src/live.hpp index d7baa61..33f3b3d 100644 --- a/src/live.hpp +++ b/src/live.hpp @@ -12,6 +12,9 @@ #include "color-map.hpp" #include <SFML/Graphics.hpp> +/** + * Live output God object, keeping track of rendering, window, history etc. + */ class LiveOutput { private: /* configuration */ @@ -30,10 +33,29 @@ public: LiveOutput(LiveOutput &&) = delete; LiveOutput & operator=(const LiveOutput&) = delete; + /** + * @param conf Configuration to use. + * @param cmap Color map instance to use. + * @param vmap Value map instance to use. + */ LiveOutput(const Configuration& conf, const ColorMap& cmap, const ValueMap& vmap); + /** + * Add a FFT window to the history and render it. + * @param win_values Window values, real, scaled. + * @return A copy of the colorized window that is rendered. + */ std::vector<uint8_t> AddWindow(const RealWindow& win_values); + + /** + * Handle window events. + * @return True if window was closed. + */ bool HandleEvents(); + + /** + * Render live window. + */ void Render(); }; diff --git a/src/renderer.cpp b/src/renderer.cpp index b8303f5..141383e 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -255,7 +255,7 @@ Renderer::Renderer(const Configuration& conf, const ColorMap& cmap, const ValueM } } -[[maybe_unused]] std::list<AxisTick> +[[maybe_unused]] std::list<Renderer::AxisTick> Renderer::GetLinearTicks(double v_min, double v_max, const std::string& v_unit, unsigned int num_ticks) { if (num_ticks <= 1) { @@ -286,7 +286,7 @@ Renderer::GetLinearTicks(double v_min, double v_max, const std::string& v_unit, return ticks; } -std::list<AxisTick> +std::list<Renderer::AxisTick> Renderer::GetNiceTicks(double v_min, double v_max, const std::string& v_unit, unsigned int length_px, unsigned int min_tick_length_px, bool rotated) { diff --git a/src/renderer.hpp b/src/renderer.hpp index f67596a..1a27276 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -12,9 +12,6 @@ #include <vector> #include <list> -/* Axis tick */ -typedef std::tuple<double, std::string> AxisTick; - /* Orientation */ enum class Orientation { k90CCW, @@ -23,19 +20,19 @@ enum class Orientation { k180 }; -/* +/** * Spectrogram rendering class */ class Renderer { private: - const Configuration configuration_; - const std::size_t fft_count_; - const std::unique_ptr<const ColorMap> color_map_; + const Configuration configuration_; /* configuration is cached as it contains multiple settings regarding spacing and sizing */ + const std::size_t fft_count_; /* number of windows to render */ + const std::unique_ptr<const ColorMap> color_map_; /* color map used for rendering */ sf::Font font_; sf::RenderTexture canvas_; - sf::Texture fft_area_texture_; + sf::Texture fft_area_texture_; /* actual spectrogram area */ std::size_t width_; std::size_t height_; @@ -44,15 +41,52 @@ private: sf::Transform fft_live_transform_; sf::Transform fft_area_transform_; + /** + * First value specifies the position of the tick in the domain [0..1]. + * Second value is the text of the tick. + */ + using AxisTick = std::tuple<double, std::string>; + std::list<AxisTick> frequency_ticks_; std::list<AxisTick> live_ticks_; + /** + * Build an array of ticks with linear spacing. + * @param v_min Lowest value on the axis. + * @param v_max Highest value on the axis. + * @param v_unit Unit of the axis. + * @param num_ticks Number of ticks to generate. + * @return Array of ticks. + */ [[maybe_unused]] /* need for this method disappeared when fixing #9, might be useful in the future */ static std::list<AxisTick> GetLinearTicks(double v_min, double v_max, const std::string& v_unit, unsigned int num_ticks); + + /** + * Build an array of nicely spaced ticks. + * @param v_min Lowest value on the axis. + * @param v_max Highest value on the axis. + * @param v_unit Unit of the axis. + * @param length_px Length of the scale, in pixels (used for spacing estimation). + * @param min_tick_length_px Minimum tick spacing. + * @param rotated If true then vertical. Otherwise horizontal. + * @return Array of ticks. + * + * NOTE: This method attempts to find some nice values for the ticks that + * also have sufficient spacing for the text to fit properly. + */ std::list<AxisTick> GetNiceTicks(double v_min, double v_max, const std::string& v_unit, unsigned int length_px, unsigned int min_tick_length_px, bool rotated); + /** + * Render an axis upon a texture + * @param texture Texture to render to. + * @param t Transform to use. + * @param lhs True if has left-hand side text. + * @param orientation One of Orientation. + * @param length Length in pixels. + * @param ticks Ticks. + */ void RenderAxis(sf::RenderTexture& texture, const sf::Transform& t, bool lhs, Orientation orientation, double length, const std::list<AxisTick>& ticks); @@ -61,12 +95,22 @@ public: Renderer() = delete; Renderer(const Configuration& conf, const ColorMap& cmap, const ValueMap& vmap, std::size_t fft_count); - /* render commands */ + /** + * Render the spectrogram area. + * @param memory RGBA memory of the colorized spectrogram. + */ void RenderFFTArea(const std::vector<uint8_t>& memory); + + /** + * Render the spectrogram area. + * @param history List of RGBA colorized windows. + */ void RenderFFTArea(const std::list<std::vector<uint8_t>>& history); std::vector<uint8_t> RenderLiveFFT(const RealWindow& window); - /* canvas builder */ + /** + * @return The rendered canvas texture. + */ sf::Texture GetCanvas(); /* size getters */ diff --git a/src/specgram.cpp b/src/specgram.cpp index 4b37cd5..6d84135 100644 --- a/src/specgram.cpp +++ b/src/specgram.cpp @@ -138,7 +138,7 @@ int main(int argc, char** argv) { /* parse command line arguments into global settings */ - auto [conf, conf_rc, conf_must_exit] = Configuration::FromArgs(argc, argv); + auto [conf, conf_rc, conf_must_exit] = Configuration::Build(argc, argv); if (conf_must_exit) { return conf_rc; } @@ -147,7 +147,7 @@ main(int argc, char** argv) bool have_output = conf.GetOutputFilename().has_value() || conf.MustDumpToStdout(); /* create window function */ - auto win_function = WindowFunction::FromType(conf.GetWindowFunction(), conf.GetFFTWidth()); + auto win_function = WindowFunction::Build(conf.GetWindowFunction(), conf.GetFFTWidth()); /* create FFT */ INFO("Creating " << conf.GetFFTWidth() << "-wide FFTW plan"); @@ -163,9 +163,8 @@ main(int argc, char** argv) conf.GetScaleUnit()); /* create color map */ - auto color_map = ColorMap::FromType(conf.GetColorMap(), - conf.GetBackgroundColor(), - conf.GetColorMapCustomColor()); + auto color_map = ColorMap::Build(conf.GetColorMap(), conf.GetBackgroundColor(), + conf.GetColorMapCustomColor()); /* create live window */ std::unique_ptr<LiveOutput> live = nullptr; @@ -175,7 +174,7 @@ main(int argc, char** argv) } /* create input parser */ - auto input = InputParser::FromDataType(conf.GetDataType(), conf.GetPrescaleFactor(), conf.HasComplexInput()); + auto input = InputParser::Build(conf.GetDataType(), conf.GetPrescaleFactor(), conf.HasComplexInput()); if (input == nullptr) { return 1; } diff --git a/src/value-map.hpp b/src/value-map.hpp index 2f32d92..3d5a160 100644 --- a/src/value-map.hpp +++ b/src/value-map.hpp @@ -14,17 +14,28 @@ #include <vector> #include <complex> +/** + * Type of supported value maps + */ enum class ValueMapType { kLinear, kDecibel }; +/** + * Base value map class + */ class ValueMap { protected: - const double lower_; - const double upper_; - const std::string unit_; + const double lower_; /* lower bound, in whatever unit */ + const double upper_; /* upper bound, in whatever unit */ + const std::string unit_; /* unit */ + /** + * @param lower_ Lower bound of scale. + * @param upper Upper bound of scale. + * @param unit Unit. + */ ValueMap(double lower_, double upper, const std::string& unit); public: @@ -32,26 +43,69 @@ public: auto GetLowerBound() const { return lower_; } auto GetUpperBound() const { return upper_; } + virtual std::string GetUnit() const; + /** + * Maps all values to [0..1], based on bounds. + * @param input Input values in whatever unit. + * @return Output corresponding values, in the [0..1] domain. + */ virtual RealWindow Map(const RealWindow& input) = 0; - virtual std::string GetUnit() const; + /** + * Build a fitting value map. + * @param type One of ValueMapType. + * @param lower Lower bound. + * @param upper Upper bound. + * @param unit Unit. + * @return New ValueMap instance. + */ static std::unique_ptr<ValueMap> Build(ValueMapType type, double lower, double upper, std::string unit); }; +/** + * Specialization for linear maps. + */ class LinearValueMap : public ValueMap { public: LinearValueMap(double lower, double upper, const std::string& unit); + /** + * Linearly map input to output. + * @param input Input values in whatever unit. + * @return Output corresponding values, in the [0..1] domain. + * + * NOTE: Transformation is + * x : [lower_ .. upper_] ---> [0 .. 1]. + */ RealWindow Map(const RealWindow& input) override; }; +/** + * Specialization for logarithmic maps in some dB unit + */ class DecibelValueMap : public ValueMap { public: - /* unit parameter should NOT contain "dB" prefix */ + /** + * @param lower Lower bound. + * @param upper Upper bound. + * @param unit Unit without dB prefix. + */ DecibelValueMap(double lower, double upper, const std::string& unit); + /** + * Logarithmically map input to output. + * @param input Input values in whatever unit. + * @return Output corresponding values, in the [0..1] domain. + * + * NOTE: Transformation is + * 20*log10(x) : [lower_ .. upper_] ---> [0 .. 1]. + */ RealWindow Map(const RealWindow& input) override; + + /** + * @return Unit with dB prefix. + */ std::string GetUnit() const override; }; diff --git a/src/window-function.cpp b/src/window-function.cpp index d52ae6d..d9141c1 100644 --- a/src/window-function.cpp +++ b/src/window-function.cpp @@ -14,7 +14,7 @@ WindowFunction::WindowFunction(std::size_t window_size) : window_size_(window_si } std::unique_ptr<WindowFunction> -WindowFunction::FromType(WindowFunctionType type, std::size_t window_size) +WindowFunction::Build(WindowFunctionType type, std::size_t window_size) { switch (type) { case WindowFunctionType::kNone: diff --git a/src/window-function.hpp b/src/window-function.hpp index 05f1e13..1f212a7 100644 --- a/src/window-function.hpp +++ b/src/window-function.hpp @@ -13,6 +13,9 @@ #include <complex> #include <memory> +/** + * Types of supported window functions + */ enum class WindowFunctionType { kNone, kHann, @@ -21,42 +24,78 @@ enum class WindowFunctionType { kNuttall }; +/** + * Base window function class. + */ class WindowFunction { protected: - const std::size_t window_size_; - std::vector<double> cached_factors_; + const std::size_t window_size_; /* size of window */ + std::vector<double> cached_factors_; /* precomputed factors for each element in window */ + /** + * @param window_size Window size. + */ explicit WindowFunction(std::size_t window_size); public: WindowFunction() = delete; + /** + * Apply function to a window. + * @param window Array of complex numbers. + * @return Element-wise multiplication between window and precomputed factors. + */ ComplexWindow Apply(const ComplexWindow& window) const; - static std::unique_ptr<WindowFunction> FromType(WindowFunctionType type, std::size_t window_size); + /** + * Build a fitting window function. + * @param type One of WindowFunctionType + * @param window_size Size of window. + * @return New WindowFunction instance. + */ + static std::unique_ptr<WindowFunction> Build(WindowFunctionType type, std::size_t window_size); }; +/** + * Specialization for generalized cosine window functions. + */ class GeneralizedCosineWindowFunction : public WindowFunction { protected: public: + /** + * @param window_size Window size. + * @param a List of coefficients. + */ GeneralizedCosineWindowFunction(std::size_t window_size, const std::vector<double>& a); }; +/** + * Specialization for Hann windows. + */ class HannWindowFunction : public GeneralizedCosineWindowFunction { public: explicit HannWindowFunction(std::size_t window_size); }; +/** + * Specialization for Hamming windows. + */ class HammingWindowFunction : public GeneralizedCosineWindowFunction { public: explicit HammingWindowFunction(std::size_t window_size); }; +/** + * Specialization for Blackman windows. + */ class BlackmanWindowFunction : public GeneralizedCosineWindowFunction { public: explicit BlackmanWindowFunction(std::size_t window_size); }; +/** + * Specialization for Nuttall windows. + */ class NuttallWindowFunction : public GeneralizedCosineWindowFunction { public: explicit NuttallWindowFunction(std::size_t window_size); diff --git a/test/test-color-map.cpp b/test/test-color-map.cpp index 8c74482..b40457a 100644 --- a/test/test-color-map.cpp +++ b/test/test-color-map.cpp @@ -28,7 +28,7 @@ const sf::Color WHITE(255, 255, 255); TEST(TestColorMap, FactoryWrongType) { - EXPECT_THROW_MATCH(auto ret = ColorMap::FromType((ColorMapType)999, sf::Color(0, 0, 0), sf::Color(0, 0, 0)), + EXPECT_THROW_MATCH(auto ret = ColorMap::Build((ColorMapType)999, sf::Color(0, 0, 0), sf::Color(0, 0, 0)), std::runtime_error, "unknown color map"); } diff --git a/test/test-input-parser.cpp b/test/test-input-parser.cpp index 606ea22..9b8dc3d 100644 --- a/test/test-input-parser.cpp +++ b/test/test-input-parser.cpp @@ -41,18 +41,18 @@ TEST(TestInputParser, IsSigned) EXPECT_TRUE(FloatInputParser<double>(1.0, is_complex).IsSigned()); /* factory */ - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt8, 1.0, is_complex)->IsSigned()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt16, 1.0, is_complex)->IsSigned()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt32, 1.0, is_complex)->IsSigned()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt64, 1.0, is_complex)->IsSigned()); - - EXPECT_TRUE(InputParser::FromDataType(DataType::kSignedInt8, 1.0, is_complex)->IsSigned()); - EXPECT_TRUE(InputParser::FromDataType(DataType::kSignedInt16, 1.0, is_complex)->IsSigned()); - EXPECT_TRUE(InputParser::FromDataType(DataType::kSignedInt32, 1.0, is_complex)->IsSigned()); - EXPECT_TRUE(InputParser::FromDataType(DataType::kSignedInt64, 1.0, is_complex)->IsSigned()); - - EXPECT_TRUE(InputParser::FromDataType(DataType::kFloat32, 1.0, is_complex)->IsSigned()); - EXPECT_TRUE(InputParser::FromDataType(DataType::kFloat64, 1.0, is_complex)->IsSigned()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt8, 1.0, is_complex)->IsSigned()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt16, 1.0, is_complex)->IsSigned()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt32, 1.0, is_complex)->IsSigned()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt64, 1.0, is_complex)->IsSigned()); + + EXPECT_TRUE(InputParser::Build(DataType::kSignedInt8, 1.0, is_complex)->IsSigned()); + EXPECT_TRUE(InputParser::Build(DataType::kSignedInt16, 1.0, is_complex)->IsSigned()); + EXPECT_TRUE(InputParser::Build(DataType::kSignedInt32, 1.0, is_complex)->IsSigned()); + EXPECT_TRUE(InputParser::Build(DataType::kSignedInt64, 1.0, is_complex)->IsSigned()); + + EXPECT_TRUE(InputParser::Build(DataType::kFloat32, 1.0, is_complex)->IsSigned()); + EXPECT_TRUE(InputParser::Build(DataType::kFloat64, 1.0, is_complex)->IsSigned()); } } @@ -74,18 +74,18 @@ TEST(TestInputParser, IsFloatingPoint) EXPECT_TRUE(FloatInputParser<double>(1.0, is_complex).IsFloatingPoint()); /* factory */ - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt8, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt16, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt32, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kUnsignedInt64, 1.0, is_complex)->IsFloatingPoint()); - - EXPECT_FALSE(InputParser::FromDataType(DataType::kSignedInt8, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kSignedInt16, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kSignedInt32, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_FALSE(InputParser::FromDataType(DataType::kSignedInt64, 1.0, is_complex)->IsFloatingPoint()); - - EXPECT_TRUE(InputParser::FromDataType(DataType::kFloat32, 1.0, is_complex)->IsFloatingPoint()); - EXPECT_TRUE(InputParser::FromDataType(DataType::kFloat64, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt8, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt16, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt32, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kUnsignedInt64, 1.0, is_complex)->IsFloatingPoint()); + + EXPECT_FALSE(InputParser::Build(DataType::kSignedInt8, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kSignedInt16, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kSignedInt32, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_FALSE(InputParser::Build(DataType::kSignedInt64, 1.0, is_complex)->IsFloatingPoint()); + + EXPECT_TRUE(InputParser::Build(DataType::kFloat32, 1.0, is_complex)->IsFloatingPoint()); + EXPECT_TRUE(InputParser::Build(DataType::kFloat64, 1.0, is_complex)->IsFloatingPoint()); } } @@ -107,18 +107,18 @@ TEST(TestInputParser, IsComplex) EXPECT_EQ(is_complex, FloatInputParser<double>(1.0, is_complex).IsComplex()); /* factory */ - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kUnsignedInt8, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kUnsignedInt16, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kUnsignedInt32, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kUnsignedInt64, 1.0, is_complex)->IsComplex()); - - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kSignedInt8, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kSignedInt16, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kSignedInt32, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kSignedInt64, 1.0, is_complex)->IsComplex()); - - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kFloat32, 1.0, is_complex)->IsComplex()); - EXPECT_EQ(is_complex, InputParser::FromDataType(DataType::kFloat64, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kUnsignedInt8, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kUnsignedInt16, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kUnsignedInt32, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kUnsignedInt64, 1.0, is_complex)->IsComplex()); + + EXPECT_EQ(is_complex, InputParser::Build(DataType::kSignedInt8, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kSignedInt16, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kSignedInt32, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kSignedInt64, 1.0, is_complex)->IsComplex()); + + EXPECT_EQ(is_complex, InputParser::Build(DataType::kFloat32, 1.0, is_complex)->IsComplex()); + EXPECT_EQ(is_complex, InputParser::Build(DataType::kFloat64, 1.0, is_complex)->IsComplex()); } } @@ -140,18 +140,18 @@ TEST(TestInputParser, GetDataTypeSize) EXPECT_EQ(8 * (is_complex ? 2 : 1), FloatInputParser<double>(1.0, is_complex).GetDataTypeSize()); /* factory */ - EXPECT_EQ(1 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kUnsignedInt8, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(2 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kUnsignedInt16, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kUnsignedInt32, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kUnsignedInt64, 1.0, is_complex)->GetDataTypeSize()); - - EXPECT_EQ(1 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kSignedInt8, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(2 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kSignedInt16, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kSignedInt32, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kSignedInt64, 1.0, is_complex)->GetDataTypeSize()); - - EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kFloat32, 1.0, is_complex)->GetDataTypeSize()); - EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::FromDataType(DataType::kFloat64, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(1 * (is_complex ? 2 : 1), InputParser::Build(DataType::kUnsignedInt8, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(2 * (is_complex ? 2 : 1), InputParser::Build(DataType::kUnsignedInt16, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::Build(DataType::kUnsignedInt32, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::Build(DataType::kUnsignedInt64, 1.0, is_complex)->GetDataTypeSize()); + + EXPECT_EQ(1 * (is_complex ? 2 : 1), InputParser::Build(DataType::kSignedInt8, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(2 * (is_complex ? 2 : 1), InputParser::Build(DataType::kSignedInt16, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::Build(DataType::kSignedInt32, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::Build(DataType::kSignedInt64, 1.0, is_complex)->GetDataTypeSize()); + + EXPECT_EQ(4 * (is_complex ? 2 : 1), InputParser::Build(DataType::kFloat32, 1.0, is_complex)->GetDataTypeSize()); + EXPECT_EQ(8 * (is_complex ? 2 : 1), InputParser::Build(DataType::kFloat64, 1.0, is_complex)->GetDataTypeSize()); } } @@ -159,7 +159,7 @@ TEST(TestInputParser, ParseBlockWrongSize) { for (bool is_complex : { false, true }) { for (auto dt : ALL_DATA_TYPES) { - auto parser = InputParser::FromDataType(dt, 1.0, is_complex); + auto parser = InputParser::Build(dt, 1.0, is_complex); for (std::size_t bs = 0; bs < 512; bs++) { if ((bs % parser->GetDataTypeSize()) == 0) { EXPECT_NO_THROW(parser->ParseBlock(std::vector<char>(bs))); @@ -398,7 +398,7 @@ TEST(TestInputParser, ParseBlock) for (bool is_complex : { false, true }) { for (auto dt : ALL_DATA_TYPES) { for (double prescale : {1.0, 33.49}) { - auto parser = InputParser::FromDataType(dt, prescale, is_complex); + auto parser = InputParser::Build(dt, prescale, is_complex); auto[buf, res] = make_test(dt, is_complex); std::size_t bs_idx = 0; @@ -444,7 +444,7 @@ TEST(TestInputParser, PeekValues) std::vector<char> block(memory); for (bool is_complex : { false, true }) { for (auto dt : ALL_DATA_TYPES) { - auto parser = InputParser::FromDataType(dt, 1.0, is_complex); + auto parser = InputParser::Build(dt, 1.0, is_complex); std::size_t parsed_count = 0; EXPECT_NO_THROW(parsed_count = parser->ParseBlock(block)); for (std::size_t i = 0; i < parsed_count + 100; i ++) { @@ -462,8 +462,8 @@ TEST(TestInputParser, RemoveValues) std::vector<char> block(memory); for (bool is_complex : { false, true }) { for (auto dt : ALL_DATA_TYPES) { - for (std::size_t i = 0; i < (memory / InputParser::FromDataType(dt, 1.0, is_complex)->GetDataTypeSize()) + 100; i++) { - auto parser = InputParser::FromDataType(dt, 1.0, is_complex); + for (std::size_t i = 0; i < (memory / InputParser::Build(dt, 1.0, is_complex)->GetDataTypeSize()) + 100; i++) { + auto parser = InputParser::Build(dt, 1.0, is_complex); std::size_t parsed_count = 0; EXPECT_NO_THROW(parsed_count = parser->ParseBlock(block)); diff --git a/test/test-window-function.cpp b/test/test-window-function.cpp index 37c04a7..959d81e 100644 --- a/test/test-window-function.cpp +++ b/test/test-window-function.cpp @@ -22,7 +22,7 @@ void run_tests(const std::list<std::vector<double>>& tests, WindowFunctionType t { for (const auto& test : tests) { /* create window */ - auto win = WindowFunction::FromType(type, test.size()); + auto win = WindowFunction::Build(type, test.size()); /* create identity input */ ComplexWindow input(test.size()); for (auto& i : input) { i = 1.0; } @@ -38,7 +38,7 @@ void run_tests(const std::list<std::vector<double>>& tests, WindowFunctionType t TEST(TestWindowFunction, FactoryWrongType) { - EXPECT_THROW_MATCH(auto ret = WindowFunction::FromType((WindowFunctionType)999, 10), + EXPECT_THROW_MATCH(auto ret = WindowFunction::Build((WindowFunctionType)999, 10), std::runtime_error, "unknown window function"); } @@ -129,6 +129,6 @@ TEST(TestWindowFunction, TypeNuttall) TEST(TestWindowFunction, TypeNone) { - EXPECT_EQ(WindowFunction::FromType(WindowFunctionType::kNone, 1), nullptr); - EXPECT_EQ(WindowFunction::FromType(WindowFunctionType::kNone, 10), nullptr); + EXPECT_EQ(WindowFunction::Build(WindowFunctionType::kNone, 1), nullptr); + EXPECT_EQ(WindowFunction::Build(WindowFunctionType::kNone, 10), nullptr); }
\ No newline at end of file |
