diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-04-13 12:05:12 -0700 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-04-15 07:44:19 -0500 |
commit | d90e8a91fd37e49dad36cf8fc890a08494d979c2 (patch) | |
tree | 64308e12cca712a91c7adfb9d6999b60abb4212c /host/examples | |
parent | dd695fb1670beacef0ac2e5e989720d9727b1ede (diff) | |
download | uhd-d90e8a91fd37e49dad36cf8fc890a08494d979c2.tar.gz uhd-d90e8a91fd37e49dad36cf8fc890a08494d979c2.tar.bz2 uhd-d90e8a91fd37e49dad36cf8fc890a08494d979c2.zip |
examples: wavetable: Modify wave tables to ease power calculations
The existing implementation would create a real signal for any type of
signal (CONST, RAMP, SQUARE, and SINE), and then create the complex
signal by simply delaying the Q value by a 90 degree phase. This had
surprising results for all waveforms:
- CONST waveforms would have a baseband value of ampl + j ampl, thus
increasing the output power by 3 dB vs. what one would expect when
setting an amplitude. It is now ampl + j * 0, and the power is
ampl**2. This now makes the power consistent with SINE, which it was
not, even though a const signal is a sine signal with a frequency of
zero.
- SQUARE waveforms would phase-delay the Q part, thus resulting in three
power output levels (when both phases are zero, when both phases are
ampl, and when one of them is zero and other is ampl). However, the
square signal is useful for watching it in the scope, and there, it
helps if the power is predictably either high or low within the
selected frequency. The Q value is now always zero.
- RAMP waveforms had the same issue and were also resolved by setting
Q to zero.
- SINE signals were fine, although the implementation used sin + j cos
to calculate a complex sine, not cos + j sin according to Euler's
formula.
To make this wavetable more useful with absolute power settings, the
changes mentioned above were implemented. The dBFs power of CONST and
SINE can now be calculated by using ampl**2, SQUARE by using
(ampl**2)/2, and RAMP by solving the integral over a ramp from -1 to 1.
Diffstat (limited to 'host/examples')
-rw-r--r-- | host/examples/wavetable.hpp | 43 |
1 files changed, 24 insertions, 19 deletions
diff --git a/host/examples/wavetable.hpp b/host/examples/wavetable.hpp index 216fe5012..dc2a93c36 100644 --- a/host/examples/wavetable.hpp +++ b/host/examples/wavetable.hpp @@ -1,7 +1,7 @@ // // Copyright 2010-2012,2014 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company -// Copyright 2019 Ettus Research, A National Instruments Brand +// Copyright 2019-2020 Ettus Research, A National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -11,6 +11,7 @@ #include <stdexcept> #include <string> #include <vector> +#include <algorithm> static const size_t wave_table_len = 8192; @@ -18,35 +19,39 @@ class wave_table_class { public: wave_table_class(const std::string& wave_type, const float ampl) - : _wave_table(wave_table_len) + : _wave_table(wave_table_len, {0.0, 0.0}) { - // compute real wave table with 1.0 amplitude - std::vector<float> real_wave_table(wave_table_len); + // Note: CONST, SQUARE, and RAMP only fill the I portion, since they are + // amplitude-modulating signals, not phase-modulating. if (wave_type == "CONST") { - for (size_t i = 0; i < wave_table_len; i++) - real_wave_table[i] = 1.0f; + // Fill with I == ampl, Q == 0 + std::fill( + _wave_table.begin(), _wave_table.end(), std::complex<float>{ampl, 0.0}); } else if (wave_type == "SQUARE") { - for (size_t i = 0; i < wave_table_len; i++) - real_wave_table[i] = (i < wave_table_len / 2) ? 0.0f : 1.0f; + // Fill the second half of the table with ampl, first half with + // zeros + std::fill(_wave_table.begin() + wave_table_len / 2, + _wave_table.end(), + std::complex<float>{ampl, 0.0}); } else if (wave_type == "RAMP") { - for (size_t i = 0; i < wave_table_len; i++) - real_wave_table[i] = 2.0f * i / (wave_table_len - 1) - 1.0f; + // Fill I values with ramp from -1 to 1, Q with zero + for (size_t i = 0; i < wave_table_len; i++) { + _wave_table[i] = {(2.0f * i / (wave_table_len - 1) - 1.0f) * ampl, 0.0}; + } } else if (wave_type == "SINE") { static const double tau = 2 * std::acos(-1.0); + static const std::complex<float> J(0, 1); + // Careful: i is the loop counter, not the imaginary unit for (size_t i = 0; i < wave_table_len; i++) { - real_wave_table[i] = - static_cast<float>(std::sin((tau * i) / wave_table_len)); + // Directly generate complex sinusoid (a*e^{j 2\pi i/N}). We + // create a single rotation. The call site will sub-sample + // appropriately to create a sine wave of it's desired frequency + _wave_table[i] = + ampl * std::exp(J * static_cast<float>(tau * i / wave_table_len)); } } else { throw std::runtime_error("unknown waveform type: " + wave_type); } - - // compute i and q pairs with 90% offset and scale to amplitude - for (size_t i = 0; i < wave_table_len; i++) { - const size_t q = (i + (3 * wave_table_len) / 4) % wave_table_len; - _wave_table[i] = - std::complex<float>(ampl * real_wave_table[i], ampl * real_wave_table[q]); - } } inline std::complex<float> operator()(const size_t index) const |