aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-04-13 12:05:12 -0700
committerAaron Rossetto <aaron.rossetto@ni.com>2020-04-15 07:44:19 -0500
commitd90e8a91fd37e49dad36cf8fc890a08494d979c2 (patch)
tree64308e12cca712a91c7adfb9d6999b60abb4212c /host
parentdd695fb1670beacef0ac2e5e989720d9727b1ede (diff)
downloaduhd-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')
-rw-r--r--host/examples/wavetable.hpp43
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