Skip to content

Commit

Permalink
Merge branch 'nextRelease'
Browse files Browse the repository at this point in the history
  • Loading branch information
FrankReiser committed Nov 24, 2023
2 parents 616318c + a22b2fc commit d94469a
Show file tree
Hide file tree
Showing 13 changed files with 1,976 additions and 147 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 56 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 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.
Expand All @@ -20,43 +18,82 @@ 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 spur at the Nyquist (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,
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 the traditional method. 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 - 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 'streamLegacyPhasorGen'
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)

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
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 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
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
Expand All @@ -78,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.
103 changes: 103 additions & 0 deletions graphics/PlotNotes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 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.

################### 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 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.

# 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.



################### 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

# 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.

# 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

# 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"


################### 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"
Loading

0 comments on commit d94469a

Please sign in to comment.