From 7e865113ab2ae488a3ab8810ee974922e88af63c Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Sun, 12 Nov 2023 15:54:31 -0500 Subject: [PATCH 01/17] Streaming Utility Modifications We desired binary output data in various precision, chunks size and such. --- sundry/streamFlyingPhasorGen.cpp | 150 +++++++++++++++++++++++----- testUtilities/CommandLineParser.cpp | 81 ++++++++------- testUtilities/CommandLineParser.h | 20 +++- 3 files changed, 190 insertions(+), 61 deletions(-) diff --git a/sundry/streamFlyingPhasorGen.cpp b/sundry/streamFlyingPhasorGen.cpp index 312d83c..96780fd 100644 --- a/sundry/streamFlyingPhasorGen.cpp +++ b/sundry/streamFlyingPhasorGen.cpp @@ -9,43 +9,141 @@ using namespace ReiserRT::Signal; +void printHelpScreen() +{ + std::cout << "Usage:" << std::endl; + std::cout << " streamFlyingPhasorGen [options]" << std::endl; + std::cout << "Available Options:" << std::endl; + std::cout << " --help" << std::endl; + std::cout << " Displays this help screen and exits." << std::endl; + std::cout << " --radsPerSample=" << std::endl; + std::cout << " The number of radians per sample to be generated." << std::endl; + std::cout << " Defaults to pi/256 radians per sample if unspecified." << std::endl; + std::cout << " --phase=" << std::endl; + std::cout << " The initial phase of the starting sample in radians." << std::endl; + std::cout << " Defaults to 0.0 radians if unspecified." << std::endl; + std::cout << " --chunkSize=" << std::endl; + std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; + std::cout << " Defaults to 4096 radians if unspecified." << std::endl; + std::cout << " --numChunks=" << std::endl; + std::cout << " The number of chunks to generate. If zero, runs continually." << std::endl; + std::cout << " Defaults to 1 radians if unspecified." << std::endl; + std::cout << " --streamFormat=" << std::endl; + std::cout << " t32 - Outputs samples in text format with floating point precision of (9 decimal places)." << std::endl; + std::cout << " t64 - Outputs samples in text format with floating point precision (17 decimal places)." << std::endl; + std::cout << " b32 - Outputs data in raw binary with 32bit precision (uint32 and float), native endian-ness." << std::endl; + std::cout << " b64 - Outputs data in raw binary 64bit precision (uint64 and double), native endian-ness." << std::endl; + std::cout << " Defaults to t64 if unspecified." << std::endl; + std::cout << " --includeX" << std::endl; + std::cout << " Include sample count in the output stream. This is useful for gnuplot using any format" << std::endl; + std::cout << " Defaults to no inclusion if unspecified." << std::endl; + std::cout << std::endl; + std::cout << "Error Returns:" << std::endl; + std::cout << " 1 - Command Line Parsing Error - Unrecognized Long Option." << std::endl; + std::cout << " 2 - Command Line Parsing Error - Unrecognized Short Option (none supported)." << std::endl; + std::cout << " 3 - Invalid Stream Format Specified" << std::endl; +} + int main( int argc, char * argv[] ) { // Parse potential command line. Defaults provided otherwise. CommandLineParser cmdLineParser{}; - if ( 0 != cmdLineParser.parseCommandLine(argc, argv) ) + + auto parseRes = cmdLineParser.parseCommandLine(argc, argv); + if ( 0 != parseRes ) { - std::cout << "Failed parsing command line" << std::endl; - std::cout << "Optional Arguments are:" << std::endl; - std::cout << "\t--radsPerSample=: The radians per sample to used." << std::endl; - std::cout << "\t--phase=: The initial phase in radians." << std::endl; + std::cout << "streamFlyingPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; + exit(parseRes); + } - exit( -1 ); + if ( cmdLineParser.getHelpFlag() ) + { + printHelpScreen(); + exit( 0 ); } -#if 0 - else + + // If one of the text formats, set output precision appropriately + auto streamFormat = cmdLineParser.getStreamFormat(); + if ( CommandLineParser::StreamFormat::Invalid == streamFormat ) { - std::cout << "Parsed: --radiansPerSample=" << cmdLineParser.getRadsPerSample() - << " --phase=" << cmdLineParser.getPhase() << std::endl << std::endl; + std::cout << "streamFlyingPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; + exit( 3 ); } -#endif - double radiansPerSample = cmdLineParser.getRadsPerSample(); - double phi = cmdLineParser.getPhase(); - - // Generate Samples - std::unique_ptr< FlyingPhasorToneGenerator > pFlyingPhasorToneGen{ new FlyingPhasorToneGenerator{ radiansPerSample, phi } }; - constexpr size_t numSamples = 4096; - std::unique_ptr< FlyingPhasorElementType[] > pToneSeries{new FlyingPhasorElementType [ numSamples] }; - constexpr FlyingPhasorElementType j{0.0, 1.0 }; - FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); - pFlyingPhasorToneGen->getSamples( p, numSamples ); - // Write to standard out. It can be redirected. - std::cout << std::scientific; - std::cout.precision(17); - for ( size_t n = 0; numSamples != n; ++n ) + // Get Frequency and Starting Phase + auto radiansPerSample = cmdLineParser.getRadsPerSample(); + auto phi = cmdLineParser.getPhase(); + + // Get Chunk Size and Number of Chunks. + const auto chunkSize = cmdLineParser.getChunkSize(); + const auto numChunks = cmdLineParser.getNumChunks(); + + // Instantiate a FlyingPhasor + FlyingPhasorToneGenerator flyingPhasorToneGenerator{ radiansPerSample, phi }; + + // Allocate Memory for Chunk Size + std::unique_ptr< FlyingPhasorElementType[] > pToneSeries{new FlyingPhasorElementType [ chunkSize ] }; + + // If we are using a text stream format, set the output precision + if ( CommandLineParser::StreamFormat::Text32 == streamFormat) + { + std::cout << std::scientific; + std::cout.precision(9); + } + else if ( CommandLineParser::StreamFormat::Text64 == streamFormat) + { + std::cout << std::scientific; + std::cout.precision(17); + } + + // Are we including Sample count in the output? + auto includeX = cmdLineParser.getIncludeX(); + + FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); + size_t sampleCount = 0; + for ( size_t chunk = 0; numChunks != chunk; ++chunk ) { - std::cout << p[n].real() << " " << p[n].imag() << std::endl; + flyingPhasorToneGenerator.getSamples( p, chunkSize ); + if ( CommandLineParser::StreamFormat::Text32 == streamFormat || + CommandLineParser::StreamFormat::Text64 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) std::cout << sampleCount++ << " "; + std::cout << p[n].real() << " " << p[n].imag() << std::endl; + } + } + else if ( CommandLineParser::StreamFormat::Bin32 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) + { + auto sVal = uint32_t( sampleCount++); + std::cout.write(reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + } + auto fVal = float( p[n].real() ); + std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + fVal = float( p[n].imag() ); + std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + } + } + else if ( CommandLineParser::StreamFormat::Bin64 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) + { + auto sVal = sampleCount++; + std::cout.write(reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + } + auto fVal = p[n].real(); + std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + fVal = p[n].imag(); + std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + } + } + std::cout.flush(); } exit( 0 ); diff --git a/testUtilities/CommandLineParser.cpp b/testUtilities/CommandLineParser.cpp index ff920a5..8d08777 100644 --- a/testUtilities/CommandLineParser.cpp +++ b/testUtilities/CommandLineParser.cpp @@ -11,19 +11,27 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) // int digitOptIndex = 0; int retCode = 0; - enum eOptions { RadsPerSample=1, Phase=2 }; + enum eOptions { RadsPerSample=1, Phase, ChunkSize, NumChunks, StreamFormat, Help, IncludeX }; + // While options still left to parse while (true) { // int thisOptionOptIndex = optind ? optind : 1; int optionIndex = 0; static struct option longOptions[] = { {"radsPerSample", required_argument, nullptr, RadsPerSample }, {"phase", required_argument, nullptr, Phase }, + {"chunkSize", required_argument, nullptr, ChunkSize }, + {"numChunks", required_argument, nullptr, NumChunks }, + {"streamFormatIn", required_argument, nullptr, StreamFormat }, + {"help", no_argument, nullptr, Help }, + {"includeX", no_argument, nullptr, IncludeX }, {nullptr, 0, nullptr, 0 } }; c = getopt_long(argc, argv, "", longOptions, &optionIndex); + + // When getopt_long has completed the parsing of the command line, it returns -1. if (c == -1) { break; } @@ -31,50 +39,55 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) switch (c) { case RadsPerSample: radsPerSampleIn = std::stod( optarg ); -#if 0 - std::cout << "The getopt_long call detected the --radsPerSample=" << optarg - << ". Value extracted = " << radsPerSampleIn << "." << std::endl; -#endif break; + case Phase: phaseIn = std::stod( optarg ); -#if 0 - std::cout << "The getopt_long call detected the --phase=" << optarg - << ". Value extracted = " << phaseIn << "." << std::endl; -#endif break; - case '?': - std::cout << "The getopt_long call returned '?'" << std::endl; - retCode = -1; + + case ChunkSize: + chunkSizeIn = std::stoul( optarg ); break; - default: - std::cout << "The getopt_long call returned character code" << c << std::endl; - retCode = -1; + + case NumChunks: + numChunksIn = std::stoul( optarg ); break; - } - } - return retCode; -} + case StreamFormat: + { + // This one is more complicated. We either detect a valid string here, or we don't. + const std::string streamFormatStr{ optarg }; + if ( streamFormatStr == "t32" ) + streamFormatIn = StreamFormat::Text32; + else if ( streamFormatStr == "t64" ) + streamFormatIn = StreamFormat::Text64; + else if ( streamFormatStr == "b32" ) + streamFormatIn = StreamFormat::Bin32; + else if ( streamFormatStr == "b64" ) + streamFormatIn = StreamFormat::Bin64; + else + streamFormatIn = StreamFormat::Invalid; + break; + } -#if 0 -int main( int argc, char * argv[] ) { + case Help: + helpFlagIn = true; + break; - std::cout << "Hello, World!" << " The value of argc is " << argc << std::endl; + case IncludeX: + includeX_In = true; + break; - if (1 < argc) - { - if ( 0 != parseCommandLine(argc, argv) ) - { - std::cout << "Failed parsing command line" << std::endl; - return -1; + case '?': +// std::cout << "The getopt_long call returned '?'" << std::endl; + retCode = 1; + break; + default: +// std::cout << "The getopt_long call returned character code" << c << std::endl; + retCode = 2; + break; } } - else - { - std::cout << "No program arguments, entering interactive mode" << std::endl; - } - return 0; + return retCode; } -#endif diff --git a/testUtilities/CommandLineParser.h b/testUtilities/CommandLineParser.h index d0b60f8..600d658 100644 --- a/testUtilities/CommandLineParser.h +++ b/testUtilities/CommandLineParser.h @@ -3,6 +3,8 @@ #ifndef TSG_COMPLEXTONEGEN_COMMANDLINEPARSER_H #define TSG_COMPLEXTONEGEN_COMMANDLINEPARSER_H +#include + class CommandLineParser { public: @@ -14,9 +16,25 @@ class CommandLineParser inline double getRadsPerSample() const { return radsPerSampleIn; } inline double getPhase() const { return phaseIn; } + inline unsigned long getChunkSize() const { return chunkSizeIn; } + inline unsigned long getNumChunks() const { return numChunksIn; } + + enum class StreamFormat : short { Invalid=0, Text32, Text64, Bin32, Bin64 }; + StreamFormat getStreamFormat() const { return streamFormatIn; } + + inline bool getHelpFlag() const { return helpFlagIn; } + inline bool getIncludeX() const { return includeX_In; } + + private: - double radsPerSampleIn{ 1.0 }; + double radsPerSampleIn{ M_PI / 256 }; double phaseIn{ 0.0 }; + unsigned long chunkSizeIn{ 4096 }; + unsigned long numChunksIn{ 1 }; + bool helpFlagIn{ false }; + bool includeX_In{ false }; + + StreamFormat streamFormatIn{ StreamFormat::Text64 }; }; From 2c8200a03321409e02c53e35252d5339f5eefca8 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Mon, 13 Nov 2023 15:06:32 -0500 Subject: [PATCH 02/17] Renamed Sundry App File Being more explicit about the name of the legacy generator streaming utility as I will move to install this in the future. --- sundry/CMakeLists.txt | 10 +++++----- .../{streamLegacyGen.cpp => streamLegacyPhasorGen.cpp} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename sundry/{streamLegacyGen.cpp => streamLegacyPhasorGen.cpp} (100%) diff --git a/sundry/CMakeLists.txt b/sundry/CMakeLists.txt index 0a1b86c..aa29230 100644 --- a/sundry/CMakeLists.txt +++ b/sundry/CMakeLists.txt @@ -1,8 +1,8 @@ -add_executable( streamLegacyGen "" ) -target_sources( streamLegacyGen PRIVATE streamLegacyGen.cpp ) -target_include_directories( streamLegacyGen PUBLIC ../src ../testUtilities ) -target_link_libraries( streamLegacyGen ReiserRT_FlyingPhasor TestUtilities ) -target_compile_options( streamLegacyGen PRIVATE +add_executable( streamLegacyPhasorGen "" ) +target_sources( streamLegacyPhasorGen PRIVATE streamLegacyPhasorGen.cpp) +target_include_directories( streamLegacyPhasorGen PUBLIC ../src ../testUtilities ) +target_link_libraries( streamLegacyPhasorGen ReiserRT_FlyingPhasor TestUtilities ) +target_compile_options( streamLegacyPhasorGen PRIVATE $<$:/W4 /WX> $<$>:-Wall -Wextra -Wpedantic -Werror> ) diff --git a/sundry/streamLegacyGen.cpp b/sundry/streamLegacyPhasorGen.cpp similarity index 100% rename from sundry/streamLegacyGen.cpp rename to sundry/streamLegacyPhasorGen.cpp From c70535bfe44fc699bddaaecc5139e6aa8179ed35 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Mon, 13 Nov 2023 15:36:51 -0500 Subject: [PATCH 03/17] Reworking Legacy Streamer for new Command Line --- sundry/CMakeLists.txt | 3 + sundry/streamFlyingPhasorGen.cpp | 12 +-- sundry/streamLegacyPhasorGen.cpp | 149 ++++++++++++++++++++++++++---- testUtilities/CommandLineParser.h | 2 + 4 files changed, 144 insertions(+), 22 deletions(-) diff --git a/sundry/CMakeLists.txt b/sundry/CMakeLists.txt index aa29230..0f5edfa 100644 --- a/sundry/CMakeLists.txt +++ b/sundry/CMakeLists.txt @@ -24,3 +24,6 @@ target_compile_options( twoToneTest PRIVATE $<$:/W4 /WX> $<$>:-Wall -Wextra -Wpedantic -Werror> ) + +# TODO Install the two streaming utilities. + diff --git a/sundry/streamFlyingPhasorGen.cpp b/sundry/streamFlyingPhasorGen.cpp index 96780fd..96cf6b7 100644 --- a/sundry/streamFlyingPhasorGen.cpp +++ b/sundry/streamFlyingPhasorGen.cpp @@ -120,12 +120,12 @@ int main( int argc, char * argv[] ) if ( includeX ) { auto sVal = uint32_t( sampleCount++); - std::cout.write(reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + std::cout.write( reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); } auto fVal = float( p[n].real() ); - std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); fVal = float( p[n].imag() ); - std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); } } else if ( CommandLineParser::StreamFormat::Bin64 == streamFormat ) @@ -135,12 +135,12 @@ int main( int argc, char * argv[] ) if ( includeX ) { auto sVal = sampleCount++; - std::cout.write(reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + std::cout.write( reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); } auto fVal = p[n].real(); - std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); fVal = p[n].imag(); - std::cout.write(reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); } } std::cout.flush(); diff --git a/sundry/streamLegacyPhasorGen.cpp b/sundry/streamLegacyPhasorGen.cpp index c7de18a..7b4e013 100644 --- a/sundry/streamLegacyPhasorGen.cpp +++ b/sundry/streamLegacyPhasorGen.cpp @@ -9,33 +9,149 @@ using namespace ReiserRT::Signal; +void printHelpScreen() +{ + std::cout << "Usage:" << std::endl; + std::cout << " streamLegacyPhasorGen [options]" << std::endl; + std::cout << "Available Options:" << std::endl; + std::cout << " --help" << std::endl; + std::cout << " Displays this help screen and exits." << std::endl; + std::cout << " --radsPerSample=" << std::endl; + std::cout << " The number of radians per sample to be generated." << std::endl; + std::cout << " Defaults to pi/256 radians per sample if unspecified." << std::endl; + std::cout << " --phase=" << std::endl; + std::cout << " The initial phase of the starting sample in radians." << std::endl; + std::cout << " Defaults to 0.0 radians if unspecified." << std::endl; + std::cout << " --chunkSize=" << std::endl; + std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; + std::cout << " Defaults to 4096 radians if unspecified." << std::endl; + std::cout << " --numChunks=" << std::endl; + std::cout << " The number of chunks to generate. If zero, runs continually." << std::endl; + std::cout << " Defaults to 1 radians if unspecified." << std::endl; + std::cout << " --streamFormat=" << std::endl; + std::cout << " t32 - Outputs samples in text format with floating point precision of (9 decimal places)." << std::endl; + std::cout << " t64 - Outputs samples in text format with floating point precision (17 decimal places)." << std::endl; + std::cout << " b32 - Outputs data in raw binary with 32bit precision (uint32 and float), native endian-ness." << std::endl; + std::cout << " b64 - Outputs data in raw binary 64bit precision (uint64 and double), native endian-ness." << std::endl; + std::cout << " Defaults to t64 if unspecified." << std::endl; + std::cout << " --includeX" << std::endl; + std::cout << " Include sample count in the output stream. This is useful for gnuplot using any format" << std::endl; + std::cout << " Defaults to no inclusion if unspecified." << std::endl; + std::cout << std::endl; + std::cout << "Error Returns:" << std::endl; + std::cout << " 1 - Command Line Parsing Error - Unrecognized Long Option." << std::endl; + std::cout << " 2 - Command Line Parsing Error - Unrecognized Short Option (none supported)." << std::endl; + std::cout << " 3 - Invalid Stream Format Specified" << std::endl; +} + int main( int argc, char * argv[] ) { // Parse potential command line. Defaults provided otherwise. CommandLineParser cmdLineParser{}; - if ( 0 != cmdLineParser.parseCommandLine(argc, argv) ) + + auto parseRes = cmdLineParser.parseCommandLine(argc, argv); + if ( 0 != parseRes ) { - std::cout << "Failed parsing command line" << std::endl; - std::cout << "Optional Arguments are:" << std::endl; - std::cout << "\t--radsPerSample=: The radians per sample to used." << std::endl; - std::cout << "\t--phase=: The initial phase in radians." << std::endl; + std::cout << "streamFlyingPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; + exit(parseRes); + } - exit( -1 ); + if ( cmdLineParser.getHelpFlag() ) + { + printHelpScreen(); + exit( 0 ); } -#if 0 - else + + // If one of the text formats, set output precision appropriately + auto streamFormat = cmdLineParser.getStreamFormat(); + if ( CommandLineParser::StreamFormat::Invalid == streamFormat ) { - std::cout << "Parsed: --radiansPerSample=" << cmdLineParser.getRadsPerSample() - << " --phase=" << cmdLineParser.getPhase() << std::endl << std::endl; + std::cout << "streamFlyingPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; + exit( 3 ); } -#endif - double radiansPerSample = cmdLineParser.getRadsPerSample(); - double phi = cmdLineParser.getPhase(); + // Get Frequency and Starting Phase + auto radiansPerSample = cmdLineParser.getRadsPerSample(); + auto phi = cmdLineParser.getPhase(); + + // Get Chunk Size and Number of Chunks. + const auto chunkSize = cmdLineParser.getChunkSize(); + const auto numChunks = cmdLineParser.getNumChunks(); + + // Allocate Memory for Chunk Size + std::unique_ptr< FlyingPhasorElementType[] > pToneSeries{ new FlyingPhasorElementType [ chunkSize ] }; + + // If we are using a text stream format, set the output precision + if ( CommandLineParser::StreamFormat::Text32 == streamFormat) + { + std::cout << std::scientific; + std::cout.precision(9); + } + else if ( CommandLineParser::StreamFormat::Text64 == streamFormat) + { + std::cout << std::scientific; + std::cout.precision(17); + } + + // Are we including Sample count in the output? + auto includeX = cmdLineParser.getIncludeX(); + + constexpr FlyingPhasorElementType j{ 0.0, 1.0 }; + FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); + size_t sampleCount = 0; + for ( size_t chunk = 0; numChunks != chunk; ++chunk ) + { + // Get Samples using complex exponential function + for ( size_t n = 0; chunkSize != n; ++n ) + { + // Legacy Complex Exponential Equivalent + p[n] = exp( j * ( double( chunkSize * chunk + n ) * radiansPerSample + phi ) ); + } + + if ( CommandLineParser::StreamFormat::Text32 == streamFormat || + CommandLineParser::StreamFormat::Text64 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) std::cout << sampleCount++ << " "; + std::cout << p[n].real() << " " << p[n].imag() << std::endl; + } + } + else if ( CommandLineParser::StreamFormat::Bin32 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) + { + auto sVal = uint32_t( sampleCount++); + std::cout.write( reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + } + auto fVal = float( p[n].real() ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + fVal = float( p[n].imag() ); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + } + } + else if ( CommandLineParser::StreamFormat::Bin64 == streamFormat ) + { + for ( size_t n = 0; chunkSize != n; ++n ) + { + if ( includeX ) + { + auto sVal = sampleCount++; + std::cout.write( reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); + } + auto fVal = p[n].real(); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + fVal = p[n].imag(); + std::cout.write( reinterpret_cast< const char * >(&fVal), sizeof( fVal ) ); + } + } + std::cout.flush(); + } + +#if 0 // Generate Samples - constexpr size_t numSamples = 4096; - std::unique_ptr< FlyingPhasorElementType[] > pToneSeries{new FlyingPhasorElementType [ numSamples] }; - constexpr FlyingPhasorElementType j{0.0, 1.0 }; FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); for ( size_t n = 0; numSamples != n; ++n ) { @@ -50,6 +166,7 @@ int main( int argc, char * argv[] ) { std::cout << p[n].real() << " " << p[n].imag() << std::endl; } +#endif exit( 0 ); return 0; diff --git a/testUtilities/CommandLineParser.h b/testUtilities/CommandLineParser.h index 600d658..c37ca3c 100644 --- a/testUtilities/CommandLineParser.h +++ b/testUtilities/CommandLineParser.h @@ -5,6 +5,8 @@ #include +///@todo Support a "Drop first N chunks" feature so we can look further out in time + class CommandLineParser { public: From e7c3d1dd7aafe45796c96f85c44156002675c5cd Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Tue, 14 Nov 2023 15:53:07 -0500 Subject: [PATCH 04/17] Added Skip Chunks Capability This was provided so we could study further down the stream more easily. --- sundry/streamFlyingPhasorGen.cpp | 53 ++++++++++++++++++++------- sundry/streamLegacyPhasorGen.cpp | 55 ++++++++++++++++++++--------- testUtilities/CommandLineParser.cpp | 7 +++- testUtilities/CommandLineParser.h | 5 ++- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/sundry/streamFlyingPhasorGen.cpp b/sundry/streamFlyingPhasorGen.cpp index 96cf6b7..9884401 100644 --- a/sundry/streamFlyingPhasorGen.cpp +++ b/sundry/streamFlyingPhasorGen.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace ReiserRT::Signal; @@ -26,8 +27,13 @@ void printHelpScreen() std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; std::cout << " Defaults to 4096 radians if unspecified." << std::endl; std::cout << " --numChunks=" << std::endl; - std::cout << " The number of chunks to generate. If zero, runs continually." << std::endl; - std::cout << " Defaults to 1 radians if unspecified." << std::endl; + std::cout << " The number of chunks to generate. If zero, runs continually up to max uint64 chunks." << std::endl; + std::cout << " This maximum value is inclusive of any skipped chunks." << std::endl; + std::cout << " Defaults to 1 chunk if unspecified." << std::endl; + std::cout << " --skipChunks=" << std::endl; + std::cout << " The number of chunks to skip before any chunks are output. Does not effect the numChunks output." << std::endl; + std::cout << " In essence if numChunks is 1 and skip chunks is 4, chunk number 5 is the only chunk output." << std::endl; + std::cout << " Defaults to 0 chunks skipped if unspecified." << std::endl; std::cout << " --streamFormat=" << std::endl; std::cout << " t32 - Outputs samples in text format with floating point precision of (9 decimal places)." << std::endl; std::cout << " t64 - Outputs samples in text format with floating point precision (17 decimal places)." << std::endl; @@ -41,7 +47,7 @@ void printHelpScreen() std::cout << "Error Returns:" << std::endl; std::cout << " 1 - Command Line Parsing Error - Unrecognized Long Option." << std::endl; std::cout << " 2 - Command Line Parsing Error - Unrecognized Short Option (none supported)." << std::endl; - std::cout << " 3 - Invalid Stream Format Specified" << std::endl; + std::cout << " 3 - Invalid streamFormat specified." << std::endl; } int main( int argc, char * argv[] ) @@ -52,7 +58,7 @@ int main( int argc, char * argv[] ) auto parseRes = cmdLineParser.parseCommandLine(argc, argv); if ( 0 != parseRes ) { - std::cout << "streamFlyingPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; + std::cerr << "streamFlyingPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; exit(parseRes); } @@ -62,22 +68,31 @@ int main( int argc, char * argv[] ) exit( 0 ); } + // Get Frequency and Starting Phase + auto radiansPerSample = cmdLineParser.getRadsPerSample(); + auto phi = cmdLineParser.getPhase(); + + // Get the Skip Chunk Count + const auto skipChunks = cmdLineParser.getSkipChunks(); + + // Get Chunk Size. + const auto chunkSize = cmdLineParser.getChunkSize(); + + // Condition Number of Chunks. If it's zero, we set to maximum less skipChunks + // because we will incorporate skipChunks into numChunks to simplify logic. + auto numChunks = cmdLineParser.getNumChunks(); + if ( 0 == numChunks ) + numChunks = std::numeric_limits::max() - skipChunks; + numChunks += skipChunks; + // If one of the text formats, set output precision appropriately auto streamFormat = cmdLineParser.getStreamFormat(); if ( CommandLineParser::StreamFormat::Invalid == streamFormat ) { - std::cout << "streamFlyingPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; + std::cerr << "streamFlyingPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; exit( 3 ); } - // Get Frequency and Starting Phase - auto radiansPerSample = cmdLineParser.getRadsPerSample(); - auto phi = cmdLineParser.getPhase(); - - // Get Chunk Size and Number of Chunks. - const auto chunkSize = cmdLineParser.getChunkSize(); - const auto numChunks = cmdLineParser.getNumChunks(); - // Instantiate a FlyingPhasor FlyingPhasorToneGenerator flyingPhasorToneGenerator{ radiansPerSample, phi }; @@ -101,9 +116,21 @@ int main( int argc, char * argv[] ) FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); size_t sampleCount = 0; + size_t skippedChunks = 0; for ( size_t chunk = 0; numChunks != chunk; ++chunk ) { + // Get Samples. If we are skipping chunks, we may not output, but we must + // maintain flying phasor state. flyingPhasorToneGenerator.getSamples( p, chunkSize ); + + // Skip this Chunk? + if ( skipChunks != skippedChunks ) + { + ++skippedChunks; + sampleCount += chunkSize; + continue; + } + if ( CommandLineParser::StreamFormat::Text32 == streamFormat || CommandLineParser::StreamFormat::Text64 == streamFormat ) { diff --git a/sundry/streamLegacyPhasorGen.cpp b/sundry/streamLegacyPhasorGen.cpp index 7b4e013..0509e14 100644 --- a/sundry/streamLegacyPhasorGen.cpp +++ b/sundry/streamLegacyPhasorGen.cpp @@ -6,11 +6,11 @@ #include #include +#include using namespace ReiserRT::Signal; -void printHelpScreen() -{ +void printHelpScreen() { std::cout << "Usage:" << std::endl; std::cout << " streamLegacyPhasorGen [options]" << std::endl; std::cout << "Available Options:" << std::endl; @@ -26,8 +26,13 @@ void printHelpScreen() std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; std::cout << " Defaults to 4096 radians if unspecified." << std::endl; std::cout << " --numChunks=" << std::endl; - std::cout << " The number of chunks to generate. If zero, runs continually." << std::endl; - std::cout << " Defaults to 1 radians if unspecified." << std::endl; + std::cout << " The number of chunks to generate. If zero, runs continually up to max uint64 chunks." << std::endl; + std::cout << " This maximum value is inclusive of any skipped chunks." << std::endl; + std::cout << " Defaults to 1 chunk if unspecified." << std::endl; + std::cout << " --skipChunks=" << std::endl; + std::cout << " The number of chunks to skip before any chunks are output. Does not effect the numChunks output." << std::endl; + std::cout << " In essence if numChunks is 1 and skip chunks is 4, chunk number 5 is the only chunk output." << std::endl; + std::cout << " Defaults to 0 chunks skipped if unspecified." << std::endl; std::cout << " --streamFormat=" << std::endl; std::cout << " t32 - Outputs samples in text format with floating point precision of (9 decimal places)." << std::endl; std::cout << " t64 - Outputs samples in text format with floating point precision (17 decimal places)." << std::endl; @@ -41,7 +46,7 @@ void printHelpScreen() std::cout << "Error Returns:" << std::endl; std::cout << " 1 - Command Line Parsing Error - Unrecognized Long Option." << std::endl; std::cout << " 2 - Command Line Parsing Error - Unrecognized Short Option (none supported)." << std::endl; - std::cout << " 3 - Invalid Stream Format Specified" << std::endl; + std::cout << " 3 - Invalid streamFormat specified." << std::endl; } int main( int argc, char * argv[] ) @@ -52,7 +57,7 @@ int main( int argc, char * argv[] ) auto parseRes = cmdLineParser.parseCommandLine(argc, argv); if ( 0 != parseRes ) { - std::cout << "streamFlyingPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; + std::cerr << "streamLegacyPhasorGen Parse Error: Use command line argument --help for instructions" << std::endl; exit(parseRes); } @@ -62,22 +67,31 @@ int main( int argc, char * argv[] ) exit( 0 ); } + // Get Frequency and Starting Phase + auto radiansPerSample = cmdLineParser.getRadsPerSample(); + auto phi = cmdLineParser.getPhase(); + + // Get the Skip Chunk Count + const auto skipChunks = cmdLineParser.getSkipChunks(); + + // Get Chunk Size. + const auto chunkSize = cmdLineParser.getChunkSize(); + + // Condition Number of Chunks. If it's zero, we set to maximum less skipChunks + // because we will incorporate skipChunks into numChunks to simplify logic. + auto numChunks = cmdLineParser.getNumChunks(); + if ( 0 == numChunks ) + numChunks = std::numeric_limits::max() - skipChunks; + numChunks += skipChunks; + // If one of the text formats, set output precision appropriately auto streamFormat = cmdLineParser.getStreamFormat(); if ( CommandLineParser::StreamFormat::Invalid == streamFormat ) { - std::cout << "streamFlyingPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; + std::cerr << "streamLegacyPhasorGen Error: Invalid Stream Format Specified. Use --help for instructions" << std::endl; exit( 3 ); } - // Get Frequency and Starting Phase - auto radiansPerSample = cmdLineParser.getRadsPerSample(); - auto phi = cmdLineParser.getPhase(); - - // Get Chunk Size and Number of Chunks. - const auto chunkSize = cmdLineParser.getChunkSize(); - const auto numChunks = cmdLineParser.getNumChunks(); - // Allocate Memory for Chunk Size std::unique_ptr< FlyingPhasorElementType[] > pToneSeries{ new FlyingPhasorElementType [ chunkSize ] }; @@ -99,8 +113,17 @@ int main( int argc, char * argv[] ) constexpr FlyingPhasorElementType j{ 0.0, 1.0 }; FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); size_t sampleCount = 0; + size_t skippedChunks = 0; for ( size_t chunk = 0; numChunks != chunk; ++chunk ) { + // Skip this Chunk? + if ( skipChunks != skippedChunks ) + { + ++skippedChunks; + sampleCount += chunkSize; + continue; + } + // Get Samples using complex exponential function for ( size_t n = 0; chunkSize != n; ++n ) { @@ -123,7 +146,7 @@ int main( int argc, char * argv[] ) { if ( includeX ) { - auto sVal = uint32_t( sampleCount++); + auto sVal = uint32_t( sampleCount++ ); std::cout.write( reinterpret_cast< const char * >(&sVal), sizeof( sVal ) ); } auto fVal = float( p[n].real() ); diff --git a/testUtilities/CommandLineParser.cpp b/testUtilities/CommandLineParser.cpp index 8d08777..8ca3ebd 100644 --- a/testUtilities/CommandLineParser.cpp +++ b/testUtilities/CommandLineParser.cpp @@ -11,7 +11,7 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) // int digitOptIndex = 0; int retCode = 0; - enum eOptions { RadsPerSample=1, Phase, ChunkSize, NumChunks, StreamFormat, Help, IncludeX }; + enum eOptions { RadsPerSample=1, Phase, ChunkSize, NumChunks, SkipChunks, StreamFormat, Help, IncludeX }; // While options still left to parse while (true) { @@ -22,6 +22,7 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) {"phase", required_argument, nullptr, Phase }, {"chunkSize", required_argument, nullptr, ChunkSize }, {"numChunks", required_argument, nullptr, NumChunks }, + {"skipChunks", required_argument, nullptr, SkipChunks }, {"streamFormatIn", required_argument, nullptr, StreamFormat }, {"help", no_argument, nullptr, Help }, {"includeX", no_argument, nullptr, IncludeX }, @@ -53,6 +54,10 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) numChunksIn = std::stoul( optarg ); break; + case SkipChunks: + skipChunksIn = std::stoul( optarg ); + break; + case StreamFormat: { // This one is more complicated. We either detect a valid string here, or we don't. diff --git a/testUtilities/CommandLineParser.h b/testUtilities/CommandLineParser.h index c37ca3c..4f299bc 100644 --- a/testUtilities/CommandLineParser.h +++ b/testUtilities/CommandLineParser.h @@ -5,8 +5,6 @@ #include -///@todo Support a "Drop first N chunks" feature so we can look further out in time - class CommandLineParser { public: @@ -20,6 +18,7 @@ class CommandLineParser inline unsigned long getChunkSize() const { return chunkSizeIn; } inline unsigned long getNumChunks() const { return numChunksIn; } + inline unsigned long getSkipChunks() const { return skipChunksIn; } enum class StreamFormat : short { Invalid=0, Text32, Text64, Bin32, Bin64 }; StreamFormat getStreamFormat() const { return streamFormatIn; } @@ -27,12 +26,12 @@ class CommandLineParser inline bool getHelpFlag() const { return helpFlagIn; } inline bool getIncludeX() const { return includeX_In; } - private: double radsPerSampleIn{ M_PI / 256 }; double phaseIn{ 0.0 }; unsigned long chunkSizeIn{ 4096 }; unsigned long numChunksIn{ 1 }; + unsigned long skipChunksIn{ 0 }; bool helpFlagIn{ false }; bool includeX_In{ false }; From dbeed16cc5caac438ef0d7bbd410acfa7d0aafda Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Tue, 14 Nov 2023 16:16:30 -0500 Subject: [PATCH 05/17] Delete Old Code --- sundry/streamLegacyPhasorGen.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/sundry/streamLegacyPhasorGen.cpp b/sundry/streamLegacyPhasorGen.cpp index 0509e14..5040c10 100644 --- a/sundry/streamLegacyPhasorGen.cpp +++ b/sundry/streamLegacyPhasorGen.cpp @@ -173,24 +173,6 @@ int main( int argc, char * argv[] ) std::cout.flush(); } -#if 0 - // Generate Samples - FlyingPhasorElementBufferTypePtr p = pToneSeries.get(); - for ( size_t n = 0; numSamples != n; ++n ) - { - // Legacy Complex Exponential Equivalent - p[n] = exp( j * ( double( n ) * radiansPerSample + phi ) ); - } - - // Write to standard out. It can be redirected. - std::cout << std::scientific; - std::cout.precision(17); - for ( size_t n = 0; numSamples != n; ++n ) - { - std::cout << p[n].real() << " " << p[n].imag() << std::endl; - } -#endif - exit( 0 ); return 0; } From ce54491576d06ea987c2a1785ccc9808dc08dc18 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 15 Nov 2023 15:55:03 -0500 Subject: [PATCH 06/17] Minor Corrections Updated Help Test for Flying Phasor stream utility and fix minor issue with command line parsing. --- sundry/streamFlyingPhasorGen.cpp | 6 +++--- testUtilities/CommandLineParser.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sundry/streamFlyingPhasorGen.cpp b/sundry/streamFlyingPhasorGen.cpp index 9884401..2e48b26 100644 --- a/sundry/streamFlyingPhasorGen.cpp +++ b/sundry/streamFlyingPhasorGen.cpp @@ -25,7 +25,7 @@ void printHelpScreen() std::cout << " Defaults to 0.0 radians if unspecified." << std::endl; std::cout << " --chunkSize=" << std::endl; std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; - std::cout << " Defaults to 4096 radians if unspecified." << std::endl; + std::cout << " Defaults to 4096 samples if unspecified." << std::endl; std::cout << " --numChunks=" << std::endl; std::cout << " The number of chunks to generate. If zero, runs continually up to max uint64 chunks." << std::endl; std::cout << " This maximum value is inclusive of any skipped chunks." << std::endl; @@ -41,7 +41,7 @@ void printHelpScreen() std::cout << " b64 - Outputs data in raw binary 64bit precision (uint64 and double), native endian-ness." << std::endl; std::cout << " Defaults to t64 if unspecified." << std::endl; std::cout << " --includeX" << std::endl; - std::cout << " Include sample count in the output stream. This is useful for gnuplot using any format" << std::endl; + std::cout << " Include sample count in the output stream. This is useful for gnuplot using any format." << std::endl; std::cout << " Defaults to no inclusion if unspecified." << std::endl; std::cout << std::endl; std::cout << "Error Returns:" << std::endl; @@ -85,7 +85,7 @@ int main( int argc, char * argv[] ) numChunks = std::numeric_limits::max() - skipChunks; numChunks += skipChunks; - // If one of the text formats, set output precision appropriately + // Do we have a valid stream output format to use? auto streamFormat = cmdLineParser.getStreamFormat(); if ( CommandLineParser::StreamFormat::Invalid == streamFormat ) { diff --git a/testUtilities/CommandLineParser.cpp b/testUtilities/CommandLineParser.cpp index 8ca3ebd..6be2590 100644 --- a/testUtilities/CommandLineParser.cpp +++ b/testUtilities/CommandLineParser.cpp @@ -23,7 +23,7 @@ int CommandLineParser::parseCommandLine( int argc, char * argv[] ) {"chunkSize", required_argument, nullptr, ChunkSize }, {"numChunks", required_argument, nullptr, NumChunks }, {"skipChunks", required_argument, nullptr, SkipChunks }, - {"streamFormatIn", required_argument, nullptr, StreamFormat }, + {"streamFormat", required_argument, nullptr, StreamFormat }, {"help", no_argument, nullptr, Help }, {"includeX", no_argument, nullptr, IncludeX }, {nullptr, 0, nullptr, 0 } From 6f1fe10f537ec2a840010d74be190fad222837d1 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 11:47:35 -0500 Subject: [PATCH 07/17] Experimenting with README and Images --- README.md | 2 + graphics/figure1.svg | 552 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 554 insertions(+) create mode 100644 graphics/figure1.svg diff --git a/README.md b/README.md index 2a02688..8b91f0f 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ may be "reset", to produce a different phased tone. Doing so, re-initializes all as if the object were just constructed. The amount of state data maintained is fairly small. If numerous tones are simultaneously required, instantiate multiple tone generators. +[Figure 1](graphics/figure1.svg) + # Thread Safety This tone generator is NOT "thread safe". There are no concurrent access mechanisms in place and there is no good reason for addressing this. To the contrary, diff --git a/graphics/figure1.svg b/graphics/figure1.svg new file mode 100644 index 0000000..13cf6a3 --- /dev/null +++ b/graphics/figure1.svg @@ -0,0 +1,552 @@ + + + +Gnuplot +Produced by GNUPLOT 5.2 patchlevel 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -1.5 + + + + + + + + + + + + + -1 + + + + + + + + + + + + + -0.5 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 0.5 + + + + + + + + + + + + + 1 + + + + + + + + + + + + + 1.5 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 128 + + + + + + + + + + + + + 256 + + + + + + + + + + + + + 384 + + + + + + + + + + + + + 512 + + + + + + + + + + + + + 640 + + + + + + + + + + + + + 768 + + + + + + + + + + + + + 896 + + + + + + + + + + + + + + + + + Amplitude + + + + + Sample Number (n) + + + + + FlyingPhasor rads/sample=pi/256 , phase=0.0 + + + real(n) + + + real(n) + + + + + + imag(n) + + + imag(n) + + + + + + + + + + + + + + + + + + From b76b4823a654b4351378e4f77f1a38ee473c4b6e Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 11:56:22 -0500 Subject: [PATCH 08/17] Ensure Image is embedded in page Missing '!' in the markdown. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b91f0f..f0c3b0c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ may be "reset", to produce a different phased tone. Doing so, re-initializes all as if the object were just constructed. The amount of state data maintained is fairly small. If numerous tones are simultaneously required, instantiate multiple tone generators. -[Figure 1](graphics/figure1.svg) +![Figure 1](graphics/figure1.svg) # Thread Safety This tone generator is NOT "thread safe". There are no concurrent access mechanisms From 8f94d094d4ae78912629e2d89127786f2f97c6b8 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 14:27:11 -0500 Subject: [PATCH 09/17] Tweaking Text and Layout --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f0c3b0c..0f82ceb 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ we simply preform a complex multiply in rectangular form, the resultant magnitud of which may drift away from one. Because of this, a re-normalization cycle must be preformed on some periodic basis and this adds to the cost. This tone generator performs this re-normalization every other sample. This was chosen for two reasons. -One, it puts any resultant spur at the Nyquist (edge of bandwidth). +One, it puts any resultant renormalization spur at the Nyquist rate (edge of bandwidth). Two, because at every other sample, the re-normalization required is minimal, -keeping its noise contribution minimum. Additionally, with such small errors, +keeping its noise contribution minimum. Additionally, with such small errors at every other cycle, a simple and inexpensive linear approximation is all that is required to maintain stability. Benchmarking indicates that this tone generator is approximately a factor of 5 times faster -than the traditional method. Accuracy is such that it is "almost" immeasurably worse. +than traditional methods. Accuracy is such that it is "almost" immeasurably worse. You be the judge. Regarding the "state data", this tone generator was designed to generate a single tone per instance. @@ -46,12 +46,15 @@ may be "reset", to produce a different phased tone. Doing so, re-initializes all as if the object were just constructed. The amount of state data maintained is fairly small. If numerous tones are simultaneously required, instantiate multiple tone generators. +Figure 1 - Time Series Data + ![Figure 1](graphics/figure1.svg) + # Thread Safety This tone generator is NOT "thread safe". There are no concurrent access mechanisms in place and there is no good reason for addressing this. To the contrary, -state left by one thread would make no sense to another, never mind the concurrency issues. +state left by one thread would make little sense to another, never mind the concurrency issues. Have threads use their own unique instances. # Acknowledgements From b319935e80509cb5e73547c3eb229f84a916500d Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 14:45:30 -0500 Subject: [PATCH 10/17] Legacy Phasor Help Text Update --- sundry/streamLegacyPhasorGen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sundry/streamLegacyPhasorGen.cpp b/sundry/streamLegacyPhasorGen.cpp index 5040c10..bbacb8f 100644 --- a/sundry/streamLegacyPhasorGen.cpp +++ b/sundry/streamLegacyPhasorGen.cpp @@ -24,7 +24,7 @@ void printHelpScreen() { std::cout << " Defaults to 0.0 radians if unspecified." << std::endl; std::cout << " --chunkSize=" << std::endl; std::cout << " The number of samples to produce per chunk. If zero, no samples are produced." << std::endl; - std::cout << " Defaults to 4096 radians if unspecified." << std::endl; + std::cout << " Defaults to 4096 samples if unspecified." << std::endl; std::cout << " --numChunks=" << std::endl; std::cout << " The number of chunks to generate. If zero, runs continually up to max uint64 chunks." << std::endl; std::cout << " This maximum value is inclusive of any skipped chunks." << std::endl; From eff9c89541e4cfdca6f08c99b5cee78b80a728be Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 15:47:50 -0500 Subject: [PATCH 11/17] Install the Two Streaming Utilities --- CMakeLists.txt | 2 +- sundry/CMakeLists.txt | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c406e0..b94887d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project( ReiserRT_FlyingPhasor - VERSION 2.3.4 + VERSION 2.3.5 DESCRIPTION "ReiserRT Complex Flying Phasor Tone Generator" ) # Set up compiler requirements diff --git a/sundry/CMakeLists.txt b/sundry/CMakeLists.txt index 0f5edfa..374aa54 100644 --- a/sundry/CMakeLists.txt +++ b/sundry/CMakeLists.txt @@ -25,5 +25,33 @@ target_compile_options( twoToneTest PRIVATE $<$>:-Wall -Wextra -Wpedantic -Werror> ) +# What this does is set up a relative path where we expect our custom libraries to be +# It will be used to patch the installation to find libraries relative to the binary. +file( RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX}) +set( _rpath "\$ORIGIN/${_rel}" ) +file( TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" MY_LIBRARY_RPATH ) +message( STATUS "MY_LIBRARY_RPATH: ${MY_LIBRARY_RPATH}" ) + +# Install streamFlyingPhasorGen and streamLegacyPhasorGen with RPATH setting +set_target_properties( streamFlyingPhasorGen + PROPERTIES + SKIP_BUILD_RPATH OFF + BUILD_WITH_INSTALL_RPATH OFF + INSTALL_RPATH "${MY_LIBRARY_RPATH}" + INSTALL_RPATH_USE_LINK_PATH ON +) +set_target_properties( streamLegacyPhasorGen + PROPERTIES + SKIP_BUILD_RPATH OFF + BUILD_WITH_INSTALL_RPATH OFF + INSTALL_RPATH "${MY_LIBRARY_RPATH}" + INSTALL_RPATH_USE_LINK_PATH ON +) + +# Installation of our binary executable component. +install( + TARGETS streamFlyingPhasorGen streamLegacyPhasorGen + RUNTIME DESTINATION ${INSTALL_BINDIR} COMPONENT bin +) # TODO Install the two streaming utilities. From eb0454d8da6913474fe1ac0e523b0bfc529db2ce Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Wed, 22 Nov 2023 15:51:03 -0500 Subject: [PATCH 12/17] Eliminate Done TODO --- sundry/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/sundry/CMakeLists.txt b/sundry/CMakeLists.txt index 374aa54..58369fd 100644 --- a/sundry/CMakeLists.txt +++ b/sundry/CMakeLists.txt @@ -53,5 +53,4 @@ install( TARGETS streamFlyingPhasorGen streamLegacyPhasorGen RUNTIME DESTINATION ${INSTALL_BINDIR} COMPONENT bin ) -# TODO Install the two streaming utilities. From 07822ea7cadc84bb266c9a799506f683052c931b Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Thu, 23 Nov 2023 09:40:17 -0500 Subject: [PATCH 13/17] Adding PlotNotes.txt It seemed important to capture notes on how I generated figures for the README file in case I ever need to work on them. --- graphics/PlotNotes.txt | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100755 graphics/PlotNotes.txt diff --git a/graphics/PlotNotes.txt b/graphics/PlotNotes.txt new file mode 100755 index 0000000..0a9f627 --- /dev/null +++ b/graphics/PlotNotes.txt @@ -0,0 +1,64 @@ +# I wrote this so I can remember how I accomplished the graphics in the README +# incase I ever need to redo them. The project needs to be built and ideally installed +# before hand. + +# To Generate the "Sample Series Plot - Figure1" contained in the README file, I used the below command +# off in some directory outside of the source tree: +streamFlyingPhasorGen --includeX --streamFormat=b32 --chunkSize=1024 > flyingPhasorB32.in + +# The output binary file contains three binary columnms (sampleNum, real, imaginary) for 1024 samples. +# The data is in 32 bit precision and by default has a radians per sample rate of pi/256 +# and a phase of zero. Note that 32 bit precision is more than adequate for a plot. + +# In the same directory where the file 'flyingPhasorB32.in' sits, enter gnuplot. +# You will need gnuplot to be installed obviously to do this. +gnuplot + +# Inside gnuplot +set term svg size 520, 360 font "Helvetica,12" background rgb "grey90" +set output 'figure1.svg' +set xrange [0:1023] +set yrange [-1.5:1.5] +set xtics 128 +set ytics 0.5 +set grid xtics +set grid ytics +set xlabel "Sample Number (n)" +set ylabel "Amplitude" +set title "FlyingPhasor rads/sample=pi/256 , phase=0.0" + +# This is one long line, not two lines. Output goes directly to the file 'figure1.svg'. +plot 'flyingPhasorB32.in' binary format="%uint%float%float" using 1:2 with lines title "real(n)", 'flyingPhasorB32.in' binary format="%uint%float%float" using 1:3 with lines title "imag(n)" + +# You can exit gnuplot now +quit + +# The 'figure1.svg' file was then copied into the projects 'graphics' directory. + + +#################### WORK IN PROGRESS BELOW ####################### + + +# Notes on using 'svg' terminal since 'qt' terminal will not let me set background color. Then must plot in one fell swoop +set term svg size 520, 360 font "Helvetica,12" background rgb "grey90" +set output 'figure1.svg' + + + +# For testing FFT Stream, we need some data. +# Generate FlyingPhasor output in b64, No X data, +~/git/ReiserRT_FlyingPhasor/cmake-build-release/bin/streamFlyingPhasorGen --streamFormat=b64 --chunkSize=1024 > ~/Documents/flyingPhasorB64_NoX.in + +~/git/ReiserRT_FFT_Stream/cmake-build-release/bin/streamFFT --includeX --streamFormat=b32 --chunkSize=1024 < flyingPhasorB64_NoX.in > ~/Documents/flyingPhasorFFT_B32.in + +# Inside gnuplot +set yrange[-400.0:50] +set xrange[-0.5:0.5] +set grid ytics +set grid xtics +set xlabel "Frequency (fraction of sample rate)" +set ylabel "Relative Power (dB)" +plot 'flyingPhasorFFT_B32.in' binary format="%float%float" using 1:2 with lines title "Power Spectrum" +set xrange[-0.05:0.05] +refresh + From dee6f7fbd606a522a669d1071b9e7dea60aaca1b Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Thu, 23 Nov 2023 12:11:38 -0500 Subject: [PATCH 14/17] Added Two More Figures for README Also, updated text in README for better flow. --- README.md | 63 +++++-- graphics/PlotNotes.txt | 75 ++++++-- graphics/figure1.svg | 2 +- graphics/figure2.svg | 407 +++++++++++++++++++++++++++++++++++++++++ graphics/figure3.svg | 407 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 918 insertions(+), 36 deletions(-) create mode 100644 graphics/figure2.svg create mode 100644 graphics/figure3.svg diff --git a/README.md b/README.md index 0f82ceb..fa8ddd5 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # ReiserRT_FlyingPhasor Frank Reiser's C++11 implementation of a fast and accurate, sin/cos waveform pair (I/Q) generator. -This component has been tested to be interface-able with C++20 compiles. Note that the compiled library code -is built using the c++11 standard. ## Overview This tone generator evolved out of a desire to generate complex exponential waveforms (sinusoids) fast and accurate. The traditional way of doing this, involved repeated calls to sin and cos functions with an advancing, radian input argument. This produces accurate results, at least over a limited domain interval. -However, it is anything but fast. +However, it is computationally intensive, not very fast, and subject to domain range issues. If a continual sequence of complex values are what is required for an application. The task of generating this sequence can be accomplished by simply rotating a phasor around the unit circle. @@ -20,39 +18,70 @@ Note that this is not necessarily true for implementations of std::sin and std:: input values may result in instability. ## Details -A little more needs to be said regarding the "loving care" mentioned above. +A little more should be said regarding the "loving care" mentioned above. This tone generator is taking advantage of Euler's mathematics of the unit circle. When you multiply two phasors, you multiply the magnitudes and add the angles. When applied to unit vectors (magnitudes of one), the resultant magnitude stays one, you simply add the angles. Our state data unit vectors are in rectangular form, we simply preform a complex multiply in rectangular form, the resultant magnitude of which may drift away from one. Because of this, a re-normalization cycle must be -preformed on some periodic basis and this adds to the cost. This tone generator performs +preformed on some periodic basis. This tone generator performs this re-normalization every other sample. This was chosen for two reasons. -One, it puts any resultant renormalization spur at the Nyquist rate (edge of bandwidth). -Two, because at every other sample, the re-normalization required is minimal, -keeping its noise contribution minimum. Additionally, with such small errors at every other cycle, +1) It puts any resultant re-normalization spur at the Nyquist rate (edge of bandwidth). +2) Re-normalizing at every other sample, keeps its noise contribution minimal. +Additionally, with such small errors at every other cycle, a simple and inexpensive linear approximation is all that is required to maintain stability. -Benchmarking indicates that this tone generator is approximately a factor of 5 times faster -than traditional methods. Accuracy is such that it is "almost" immeasurably worse. -You be the judge. Regarding the "state data", this tone generator was designed to generate a single tone per instance. An instance is constructed with an initial frequency and phase. When an initial number of samples are requested from an instance, they are delivered from the starting phase angle at a fixed radians per sample, rate. Subsequent sample requests, -are delivered in phase (continuous) with the previous samples delivered. However, an instance -may be "reset", to produce a different phased tone. Doing so, re-initializes all "state data" +are delivered in phase (continuous) with the previous samples delivered. An instance +may be "reset" however, to produce a different phased tone. Resetting re-initializes all "state data" as if the object were just constructed. The amount of state data maintained is fairly small. -If numerous tones are simultaneously required, instantiate multiple tone generators. +If numerous tones are simultaneously required, instantiate multiple tone generators and add the +results. + +# Example Data Characteristics +Here, we present some example data created with the 'streamFlyingPhasorGen' utility program included +with the project. We generated 1024 samples at pi/256 radians per sample with an initial phase of zero. +This data is plotted below: -Figure 1 - Time Series Data +Figure 1 - Example Flying Phasor Sample Series Data ![Figure 1](graphics/figure1.svg) +From the figure, we see what looks like a cosine wave and a sine wave. It looks pretty good but, looks +can be deceiving. What does the spectrum look like? Are there any notable spurs in the frequency domain? +We will take a look at the power spectrum, plotted in decibels for an extended dynamic range view. We +did not apply any window to the sample series data here as our signal is right on a basis function. +Applying a window in this case would distract from our analysis. +This data is plotted below: + +Figure 2 - Example Flying Phasor Power Spectrum Data + +![Figure 2](graphics/figure2.svg) + +As can be seen, we have in excess of 300 dB of spur free dynamic range. This seems phenomenal but, how +does this compare to the legacy method? In order to compare, we use utility 'streamFlyingPhasorGen' +program included with the project, using the same parameters. +This data is plotted below: + +Figure 3 - Example Legacy Generator Power Spectrum Data + +![Figure 3](graphics/figure3.svg) + +As can be seen, we also have in excess of 300 dB of spur free dynamic range. It would appear that the +FlyingPhasor generator performs better. Benchmarking indicates that the FlyingPhasor tone generator is +approximately a factor of 5 times faster than traditional methods and is comparable in spur free dynamic range. +You be the judge. + +# Interface Compatibility +This component has been tested to be interface-able with C++20 compiles. Note that the compiled library code +is built using the c++11 standard. API/ABI stability will be maintained between minor versions of this project. # Thread Safety -This tone generator is NOT "thread safe". There are no concurrent access mechanisms +This tone generator is not "thread safe". There are no concurrent access mechanisms in place and there is no good reason for addressing this. To the contrary, state left by one thread would make little sense to another, never mind the concurrency issues. Have threads use their own unique instances. @@ -61,7 +90,7 @@ Have threads use their own unique instances. I cannot take credit for this algorithm. It is really just high school math. This implementation was derived from something I saw on Stack Exchange. What I have done is to actually utilize std::complex instead of a discrete reimplementation -of complex math, fine honed the re-normalization, and turned it into a reusable object that +of complex math, fine honed the re-normalization period, and turned it into a reusable object that meets my needs. Also, I provide some test harnesses that prove its worthiness. ## Building and Installation diff --git a/graphics/PlotNotes.txt b/graphics/PlotNotes.txt index 0a9f627..a0bbe6a 100755 --- a/graphics/PlotNotes.txt +++ b/graphics/PlotNotes.txt @@ -1,12 +1,13 @@ -# I wrote this so I can remember how I accomplished the graphics in the README -# incase I ever need to redo them. The project needs to be built and ideally installed -# before hand. +# I wrote this, so I can remember how I accomplished the graphics in the README +# in case I ever need to redo them. The project needs to be built and ideally installed +# beforehand. -# To Generate the "Sample Series Plot - Figure1" contained in the README file, I used the below command -# off in some directory outside of the source tree: +################### FIGURE 1 ####################### +# To Generate the "Figure 1 - Example Flying Phasor Sample Series Data" graphic contained in the README file, +# I used the below command off in some directory outside the source tree: streamFlyingPhasorGen --includeX --streamFormat=b32 --chunkSize=1024 > flyingPhasorB32.in -# The output binary file contains three binary columnms (sampleNum, real, imaginary) for 1024 samples. +# The output binary file contains three binary columns (sampleNum, real, imaginary) for 1024 samples. # The data is in 32 bit precision and by default has a radians per sample rate of pi/256 # and a phase of zero. Note that 32 bit precision is more than adequate for a plot. @@ -25,7 +26,7 @@ set grid xtics set grid ytics set xlabel "Sample Number (n)" set ylabel "Amplitude" -set title "FlyingPhasor rads/sample=pi/256 , phase=0.0" +set title "FlyingPhasor rads/sample=pi/256, phase=0.0" # This is one long line, not two lines. Output goes directly to the file 'figure1.svg'. plot 'flyingPhasorB32.in' binary format="%uint%float%float" using 1:2 with lines title "real(n)", 'flyingPhasorB32.in' binary format="%uint%float%float" using 1:3 with lines title "imag(n)" @@ -36,29 +37,67 @@ quit # The 'figure1.svg' file was then copied into the projects 'graphics' directory. -#################### WORK IN PROGRESS BELOW ####################### +################### FIGURE 2 ####################### +# To Generate the "Figure 2 - Example Flying Phasor Power Spectrum Data" graphic contained in the README file, +# I used the below command off in some directory outside the source tree: +streamFlyingPhasorGen --streamFormat=b64 --chunkSize=1024 > flyingPhasorB64_NoX.in -# Notes on using 'svg' terminal since 'qt' terminal will not let me set background color. Then must plot in one fell swoop -set term svg size 520, 360 font "Helvetica,12" background rgb "grey90" -set output 'figure1.svg' - +# The output binary file contains two binary columns (real, imaginary) for 1024 samples. +# The data is in 64 bit precision and by default has a radians per sample rate of pi/256 +# and a phase of zero. Note that 64 bit precision is what we want to take an FFT and +# get a good look at spur free dynamic range. +# Now for the FFT. I used a utility I wrote called 'streamFFT'. I have not published that +# as of 20231123 yet but intend to do so soon. -# For testing FFT Stream, we need some data. -# Generate FlyingPhasor output in b64, No X data, -~/git/ReiserRT_FlyingPhasor/cmake-build-release/bin/streamFlyingPhasorGen --streamFormat=b64 --chunkSize=1024 > ~/Documents/flyingPhasorB64_NoX.in +# The 'streamFFT' utility always expects 64bit I/Q data as input but, we want 32bit out as this is +# adequate for plotting. The output binary two binary columns (fractionOfSampleRate, 10log10(mag^2)) +# for 1024 samples. It is essentially a power spectrum in decibels. Note that we did not specify +# a window for the FFT. This is because we put the signal right on a basis function and using a window +# would distract our visual spur analysis under this scenario. +streamFFT --includeX --streamFormat=b32 --chunkSize=1024 < flyingPhasorB64_NoX.in > flyingPhasorFFT_B32.in -~/git/ReiserRT_FFT_Stream/cmake-build-release/bin/streamFFT --includeX --streamFormat=b32 --chunkSize=1024 < flyingPhasorB64_NoX.in > ~/Documents/flyingPhasorFFT_B32.in +# In the same directory where the file 'flyingPhasorFFT_B32.in' sits, enter gnuplot. +gnuplot # Inside gnuplot +set term svg size 520, 360 font "Helvetica,12" background rgb "grey90" +set output 'figure2.svg' set yrange[-400.0:50] set xrange[-0.5:0.5] set grid ytics set grid xtics set xlabel "Frequency (fraction of sample rate)" set ylabel "Relative Power (dB)" +set title "FlyingPhasor rads/sample=pi/256, phase=0.0" plot 'flyingPhasorFFT_B32.in' binary format="%float%float" using 1:2 with lines title "Power Spectrum" -set xrange[-0.05:0.05] -refresh + +################### FIGURE 3 ####################### +# To Generate the "Figure 3 - Example Legacy Generator Power Spectrum Data" graphic contained in the README file, +# I used the below command off in some directory outside the source tree: +streamLegacyPhasorGen --streamFormat=b64 --chunkSize=1024 > legacyPhasorB64_NoX.in + +# The output binary file contains two binary columns (real, imaginary) for 1024 samples. +# The data is in 64 bit precision and by default has a radians per sample rate of pi/256 +# and a phase of zero. Note that 64 bit precision is what we want to take an FFT and +# get a good look at spur free dynamic range. + +# Take FFT +streamFFT --includeX --streamFormat=b32 --chunkSize=1024 < legacyPhasorB64_NoX.in > legacyPhasorFFT_B32.in + +# In the same directory where the file 'legacyPhasorFFT_B32.in' sits, enter gnuplot. +gnuplot + +# Inside gnuplot +set term svg size 520, 360 font "Helvetica,12" background rgb "grey90" +set output 'figure3.svg' +set yrange[-400.0:50] +set xrange[-0.5:0.5] +set grid ytics +set grid xtics +set xlabel "Frequency (fraction of sample rate)" +set ylabel "Relative Power (dB)" +set title "Legacy Generator rads/sample=pi/256, phase=0.0" +plot 'legacyPhasorFFT_B32.in' binary format="%float%float" using 1:2 with lines title "Power Spectrum" diff --git a/graphics/figure1.svg b/graphics/figure1.svg index 13cf6a3..35f6ce0 100644 --- a/graphics/figure1.svg +++ b/graphics/figure1.svg @@ -260,7 +260,7 @@ - FlyingPhasor rads/sample=pi/256 , phase=0.0 + FlyingPhasor rads/sample=pi/256, phase=0.0 real(n) diff --git a/graphics/figure2.svg b/graphics/figure2.svg new file mode 100644 index 0000000..5d34051 --- /dev/null +++ b/graphics/figure2.svg @@ -0,0 +1,407 @@ + + + +Gnuplot +Produced by GNUPLOT 5.2 patchlevel 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -400 + + + + + + + + + + + + + -350 + + + + + + + + + + + + + -300 + + + + + + + + + + + + + -250 + + + + + + + + + + + + + -200 + + + + + + + + + + + + + -150 + + + + + + + + + + + + + -100 + + + + + + + + + + + + + -50 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 50 + + + + + + + + + + + + + -0.4 + + + + + + + + + + + + + -0.2 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + Relative Power (dB) + + + + + Frequency (fraction of sample rate) + + + + + FlyingPhasor rads/sample=pi/256, phase=0.0 + + + Power Spectrum + + + Power Spectrum + + + + + + + + + + + + + + + + + + diff --git a/graphics/figure3.svg b/graphics/figure3.svg new file mode 100644 index 0000000..1028606 --- /dev/null +++ b/graphics/figure3.svg @@ -0,0 +1,407 @@ + + + +Gnuplot +Produced by GNUPLOT 5.2 patchlevel 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -400 + + + + + + + + + + + + + -350 + + + + + + + + + + + + + -300 + + + + + + + + + + + + + -250 + + + + + + + + + + + + + -200 + + + + + + + + + + + + + -150 + + + + + + + + + + + + + -100 + + + + + + + + + + + + + -50 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 50 + + + + + + + + + + + + + -0.4 + + + + + + + + + + + + + -0.2 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + Relative Power (dB) + + + + + Frequency (fraction of sample rate) + + + + + Legacy Generator rads/sample=pi/256, phase=0.0 + + + Power Spectrum + + + Power Spectrum + + + + + + + + + + + + + + + + + + From bdfcd54b0a34f4f3721c39748e95d8b7d6312a1f Mon Sep 17 00:00:00 2001 From: Frank Reiser <73189110+FrankReiser@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:42:43 -0500 Subject: [PATCH 15/17] Update README.md Tweaked text regarding the comparison of FlyingPhasor to legacy method in the frequency domain. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fa8ddd5..caad926 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,11 @@ Figure 3 - Example Legacy Generator Power Spectrum Data ![Figure 3](graphics/figure3.svg) -As can be seen, we also have in excess of 300 dB of spur free dynamic range. It would appear that the -FlyingPhasor generator performs better. Benchmarking indicates that the FlyingPhasor tone generator is -approximately a factor of 5 times faster than traditional methods and is comparable in spur free dynamic range. +As can be seen, the legacy method has in excess of 300 dB of spur free dynamic range as would be +expected. It also has a slightly tighter skirt than the FlyingPhasor but, this detail is noted +just ablove our noise floor. The FlyingPhasor generator noise floor actually looks better than +the legacy method. Benchmarking indicates that the FlyingPhasor tone generator is +approximately a factor of 5 times faster than the legacy method and is comparable in spur free dynamic range. You be the judge. # Interface Compatibility From ac8a33388d122b4f06d89b129177d706896944b1 Mon Sep 17 00:00:00 2001 From: Frank Reiser <73189110+FrankReiser@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:45:05 -0500 Subject: [PATCH 16/17] Update README.md Fix spelling error. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caad926..7287633 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Figure 3 - Example Legacy Generator Power Spectrum Data As can be seen, the legacy method has in excess of 300 dB of spur free dynamic range as would be expected. It also has a slightly tighter skirt than the FlyingPhasor but, this detail is noted -just ablove our noise floor. The FlyingPhasor generator noise floor actually looks better than +just above our noise floor. The FlyingPhasor generator noise floor actually looks better than the legacy method. Benchmarking indicates that the FlyingPhasor tone generator is approximately a factor of 5 times faster than the legacy method and is comparable in spur free dynamic range. You be the judge. From a092a23ba301d33314ba9111a0bb3c0dc49f46a5 Mon Sep 17 00:00:00 2001 From: Frank Reiser Date: Thu, 23 Nov 2023 20:39:02 -0500 Subject: [PATCH 17/17] Final README Text Tweak for now. This thing is ready to merge. --- README.md | 19 +++++++++++-------- src/FlyingPhasorToneGenerator.cpp | 21 ++++++++++++++------- sundry/streamLegacyPhasorGen.cpp | 2 +- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7287633..08d9d5e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Frank Reiser's C++11 implementation of a fast and accurate, sin/cos waveform pai This tone generator evolved out of a desire to generate complex exponential waveforms (sinusoids) fast and accurate. The traditional way of doing this, involved repeated calls to sin and cos functions with an advancing, radian input argument. This produces accurate results, at least over a limited domain interval. -However, it is computationally intensive, not very fast, and subject to domain range issues. +However, it is computationally intensive (not fast), and subject to domain range issues. If a continual sequence of complex values are what is required for an application. The task of generating this sequence can be accomplished by simply rotating a phasor around the unit circle. @@ -63,7 +63,7 @@ Figure 2 - Example Flying Phasor Power Spectrum Data ![Figure 2](graphics/figure2.svg) As can be seen, we have in excess of 300 dB of spur free dynamic range. This seems phenomenal but, how -does this compare to the legacy method? In order to compare, we use utility 'streamFlyingPhasorGen' +does this compare to the legacy method? In order to compare, we use utility 'streamLegacyPhasorGen' program included with the project, using the same parameters. This data is plotted below: @@ -71,12 +71,13 @@ Figure 3 - Example Legacy Generator Power Spectrum Data ![Figure 3](graphics/figure3.svg) -As can be seen, the legacy method has in excess of 300 dB of spur free dynamic range as would be -expected. It also has a slightly tighter skirt than the FlyingPhasor but, this detail is noted -just above our noise floor. The FlyingPhasor generator noise floor actually looks better than -the legacy method. Benchmarking indicates that the FlyingPhasor tone generator is -approximately a factor of 5 times faster than the legacy method and is comparable in spur free dynamic range. -You be the judge. +The legacy method also has in excess of 300 dB of spur free dynamic range as would be +expected. It has a slightly tighter skirt than the FlyingPhasor but, the FlyingPhasor skirt +is reasonably within the noise floor of the legacy method. The FlyingPhasor generator noise floor actually +looks better than the legacy method. It is more uniformly distributed across the spectrum. +It also lacks the periodic spurs seen in the legacy method. +Benchmarking indicates that the FlyingPhasor tone generator is approximately a factor of 5 times +faster than the legacy method and is comparable in spur free dynamic range. You be the judge. # Interface Compatibility This component has been tested to be interface-able with C++20 compiles. Note that the compiled library code @@ -114,3 +115,5 @@ Roughly as follows: ``` sudo cmake --install . ``` +Please see the "tests" and "sundry" folders for examples on how to use the FlyingPhasor in +your own projects. diff --git a/src/FlyingPhasorToneGenerator.cpp b/src/FlyingPhasorToneGenerator.cpp index cef30ae..7dcdd3a 100644 --- a/src/FlyingPhasorToneGenerator.cpp +++ b/src/FlyingPhasorToneGenerator.cpp @@ -29,7 +29,8 @@ void FlyingPhasorToneGenerator::getSamples( FlyingPhasorElementBufferTypePtr pEl // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -46,7 +47,8 @@ void FlyingPhasorToneGenerator::getSamplesScaled( FlyingPhasorElementBufferTypeP // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -63,7 +65,8 @@ void FlyingPhasorToneGenerator::getSamplesScaled( FlyingPhasorElementBufferTypeP // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -79,7 +82,8 @@ void FlyingPhasorToneGenerator::accumSamples( FlyingPhasorElementBufferTypePtr p // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -96,7 +100,8 @@ void FlyingPhasorToneGenerator::accumSamplesScaled( FlyingPhasorElementBufferTyp // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -113,7 +118,8 @@ void FlyingPhasorToneGenerator::accumSamplesScaled( FlyingPhasorElementBufferTyp // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); } } @@ -134,7 +140,8 @@ FlyingPhasorElementType FlyingPhasorToneGenerator::getSample() // Now advance (rotate) the phasor by our rate (complex multiply) phasor *= rate; - // Perform normalization + // Perform normalization work. This only actually normalized ever other invocation. + // We invoke it to maintain that part of the state machine. normalize(); return retValue; diff --git a/sundry/streamLegacyPhasorGen.cpp b/sundry/streamLegacyPhasorGen.cpp index bbacb8f..b32de2b 100644 --- a/sundry/streamLegacyPhasorGen.cpp +++ b/sundry/streamLegacyPhasorGen.cpp @@ -128,7 +128,7 @@ int main( int argc, char * argv[] ) for ( size_t n = 0; chunkSize != n; ++n ) { // Legacy Complex Exponential Equivalent - p[n] = exp( j * ( double( chunkSize * chunk + n ) * radiansPerSample + phi ) ); + p[n] = std::exp( j * ( double( chunkSize * chunk + n ) * radiansPerSample + phi ) ); } if ( CommandLineParser::StreamFormat::Text32 == streamFormat ||