Use log scale for visualizer (#397)
* Visualizer: use log scale - log scale frequency and gain - Hamming windowing - improve reading from fifo * Fix Visualizer memory leaks * Visualizer: use Blackman window * Visualizer: support DFT zero padding * Visualizer: support fractional height bars, fix fifo read * Revert "Fix Visualizer memory leaks" This reverts commit 5c6509d2b8ed985a4928f681217dc8616d053ace. * Visualizer: fix fifo read again * Visualizer: add cubic interpolation option * Visualizer: Expose more config options, add docs for config options * Visualizer: Use reverse video text for stereo visualizer smooth look * Visualizer: use FormattedColor to for reverse-video * Visualizer: change some config options for spectrum * Fix build fftw disabled * Visualizer: use [0,5] interval for dft_size config option
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
#ifdef ENABLE_VISUALIZER
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/math/constants/constants.hpp>
|
||||
#include <cerrno>
|
||||
@@ -30,6 +31,7 @@
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <fcntl.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "global.h"
|
||||
#include "settings.h"
|
||||
@@ -39,6 +41,7 @@
|
||||
#include "screens/screen_switcher.h"
|
||||
#include "status.h"
|
||||
#include "enums.h"
|
||||
#include "utility/wide_string.h"
|
||||
|
||||
using Samples = std::vector<int16_t>;
|
||||
|
||||
@@ -50,6 +53,7 @@ Visualizer *myVisualizer;
|
||||
namespace {
|
||||
|
||||
const int fps = 25;
|
||||
const uint32_t MIN_DFT_SIZE = 14;
|
||||
|
||||
// toColor: a scaling function for coloring. For numbers 0 to max this function
|
||||
// returns a coloring from the lowest color to the highest, and colors will not
|
||||
@@ -67,17 +71,39 @@ const NC::FormattedColor &toColor(size_t number, size_t max, bool wrap = true)
|
||||
|
||||
Visualizer::Visualizer()
|
||||
: Screen(NC::Window(0, MainStartY, COLS, MainHeight, "", NC::Color::Default, NC::Border()))
|
||||
# ifdef HAVE_FFTW3_H
|
||||
,
|
||||
DFT_NONZERO_SIZE(1 << Config.visualizer_spectrum_dft_size),
|
||||
DFT_TOTAL_SIZE(Config.visualizer_spectrum_dft_size >= MIN_DFT_SIZE ? 1 << (Config.visualizer_spectrum_dft_size) : 1<<MIN_DFT_SIZE),
|
||||
DYNAMIC_RANGE(100),
|
||||
HZ_MIN(Config.visualizer_spectrum_hz_min),
|
||||
HZ_MAX(Config.visualizer_spectrum_hz_max),
|
||||
GAIN(0),
|
||||
SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█"))
|
||||
#endif
|
||||
{
|
||||
ResetFD();
|
||||
m_samples = 44100/fps;
|
||||
if (Config.visualizer_in_stereo)
|
||||
m_samples *= 2;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
m_fftw_results = m_samples/2+1;
|
||||
m_read_samples = DFT_NONZERO_SIZE;
|
||||
# else
|
||||
m_read_samples = 44100 / fps;
|
||||
# endif // HAVE_FFTW3_H
|
||||
if (Config.visualizer_in_stereo)
|
||||
m_read_samples *= 2;
|
||||
m_sample_buffer.resize(m_read_samples);
|
||||
m_temp_sample_buffer.resize(m_read_samples);
|
||||
memset(m_sample_buffer.data(), 0, m_sample_buffer.size()*sizeof(int16_t));
|
||||
memset(m_temp_sample_buffer.data(), 0, m_sample_buffer.size()*sizeof(int16_t));
|
||||
|
||||
# ifdef HAVE_FFTW3_H
|
||||
m_fftw_results = DFT_TOTAL_SIZE/2+1;
|
||||
m_freq_magnitudes.resize(m_fftw_results);
|
||||
m_fftw_input = static_cast<double *>(fftw_malloc(sizeof(double)*m_samples));
|
||||
m_fftw_input = static_cast<double *>(fftw_malloc(sizeof(double)*DFT_TOTAL_SIZE));
|
||||
memset(m_fftw_input, 0, sizeof(double)*DFT_TOTAL_SIZE);
|
||||
m_fftw_output = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*m_fftw_results));
|
||||
m_fftw_plan = fftw_plan_dft_r2c_1d(m_samples, m_fftw_input, m_fftw_output, FFTW_ESTIMATE);
|
||||
m_fftw_plan = fftw_plan_dft_r2c_1d(DFT_TOTAL_SIZE, m_fftw_input, m_fftw_output, FFTW_ESTIMATE);
|
||||
m_dft_logspace.reserve(500);
|
||||
m_bar_heights.reserve(100);
|
||||
# endif // HAVE_FFTW3_H
|
||||
}
|
||||
|
||||
@@ -88,6 +114,11 @@ void Visualizer::switchTo()
|
||||
SetFD();
|
||||
m_timer = boost::posix_time::from_time_t(0);
|
||||
drawHeader();
|
||||
memset(m_sample_buffer.data(), 0, m_sample_buffer.size()*sizeof(int16_t));
|
||||
# ifdef HAVE_FFTW3_H
|
||||
GenLogspace();
|
||||
m_bar_heights.reserve(w.getWidth());
|
||||
# endif // HAVE_FFTW3_H
|
||||
}
|
||||
|
||||
void Visualizer::resize()
|
||||
@@ -97,6 +128,10 @@ void Visualizer::resize()
|
||||
w.resize(width, MainHeight);
|
||||
w.moveTo(x_offset, MainStartY);
|
||||
hasToBeResized = 0;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
GenLogspace();
|
||||
m_bar_heights.reserve(w.getWidth());
|
||||
# endif // HAVE_FFTW3_H
|
||||
}
|
||||
|
||||
std::wstring Visualizer::title()
|
||||
@@ -111,12 +146,20 @@ void Visualizer::update()
|
||||
|
||||
// PCM in format 44100:16:1 (for mono visualization) and
|
||||
// 44100:16:2 (for stereo visualization) is supported.
|
||||
Samples samples(m_samples);
|
||||
ssize_t data = read(m_fifo, samples.data(),
|
||||
samples.size() * sizeof(Samples::value_type));
|
||||
if (data < 0) // no data available in fifo
|
||||
const int buf_size = sizeof(int16_t)*m_read_samples;
|
||||
ssize_t data = read(m_fifo, m_temp_sample_buffer.data(), buf_size);
|
||||
if (data <= 0) // no data available in fifo
|
||||
return;
|
||||
|
||||
{
|
||||
// create int8_t pointers for arithmetic
|
||||
int8_t *const sdata = (int8_t *)m_sample_buffer.data();
|
||||
int8_t *const temp_sdata = (int8_t *)m_temp_sample_buffer.data();
|
||||
int8_t *const sdata_end = sdata + buf_size;
|
||||
memmove(sdata, sdata + data, buf_size - data);
|
||||
memcpy(sdata_end - data, temp_sdata, data);
|
||||
}
|
||||
|
||||
if (m_output_id != -1 && Global::Timer - m_timer > Config.visualizer_sync_interval)
|
||||
{
|
||||
Mpd.DisableOutput(m_output_id);
|
||||
@@ -130,6 +173,7 @@ void Visualizer::update()
|
||||
# ifdef HAVE_FFTW3_H
|
||||
if (Config.visualizer_type == VisualizerType::Spectrum)
|
||||
{
|
||||
m_read_samples = DFT_NONZERO_SIZE;
|
||||
draw = &Visualizer::DrawFrequencySpectrum;
|
||||
drawStereo = &Visualizer::DrawFrequencySpectrumStereo;
|
||||
}
|
||||
@@ -137,52 +181,58 @@ void Visualizer::update()
|
||||
# endif // HAVE_FFTW3_H
|
||||
if (Config.visualizer_type == VisualizerType::WaveFilled)
|
||||
{
|
||||
m_read_samples = 44100 / fps;
|
||||
draw = &Visualizer::DrawSoundWaveFill;
|
||||
drawStereo = &Visualizer::DrawSoundWaveFillStereo;
|
||||
}
|
||||
else if (Config.visualizer_type == VisualizerType::Ellipse)
|
||||
{
|
||||
m_read_samples = 44100 / fps;
|
||||
draw = &Visualizer::DrawSoundEllipse;
|
||||
drawStereo = &Visualizer::DrawSoundEllipseStereo;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_read_samples = 44100 / fps;
|
||||
draw = &Visualizer::DrawSoundWave;
|
||||
drawStereo = &Visualizer::DrawSoundWaveStereo;
|
||||
}
|
||||
m_sample_buffer.resize(m_read_samples);
|
||||
m_temp_sample_buffer.resize(m_read_samples);
|
||||
|
||||
const ssize_t samples_read = data/sizeof(int16_t);
|
||||
m_auto_scale_multiplier += 1.0/fps;
|
||||
for (auto &sample : samples)
|
||||
{
|
||||
double scale = std::numeric_limits<int16_t>::min();
|
||||
scale /= sample;
|
||||
scale = fabs(scale);
|
||||
if (scale < m_auto_scale_multiplier)
|
||||
m_auto_scale_multiplier = scale;
|
||||
}
|
||||
for (auto &sample : samples)
|
||||
{
|
||||
int32_t tmp = sample;
|
||||
if (m_auto_scale_multiplier <= 50.0) // limit the auto scale
|
||||
tmp *= m_auto_scale_multiplier;
|
||||
if (tmp < std::numeric_limits<int16_t>::min())
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
else if (tmp > std::numeric_limits<int16_t>::max())
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
else
|
||||
sample = tmp;
|
||||
if (Config.visualizer_autoscale) {
|
||||
m_auto_scale_multiplier += 1.0/fps;
|
||||
for (auto &sample : m_sample_buffer)
|
||||
{
|
||||
double scale = std::numeric_limits<int16_t>::min();
|
||||
scale /= sample;
|
||||
scale = fabs(scale);
|
||||
if (scale < m_auto_scale_multiplier)
|
||||
m_auto_scale_multiplier = scale;
|
||||
}
|
||||
for (auto &sample : m_sample_buffer)
|
||||
{
|
||||
int32_t tmp = sample;
|
||||
if (m_auto_scale_multiplier <= 50.0) // limit the auto scale
|
||||
tmp *= m_auto_scale_multiplier;
|
||||
if (tmp < std::numeric_limits<int16_t>::min())
|
||||
sample = std::numeric_limits<int16_t>::min();
|
||||
else if (tmp > std::numeric_limits<int16_t>::max())
|
||||
sample = std::numeric_limits<int16_t>::max();
|
||||
else
|
||||
sample = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
w.clear();
|
||||
if (Config.visualizer_in_stereo)
|
||||
{
|
||||
auto chan_samples = samples_read/2;
|
||||
auto chan_samples = m_read_samples/2;
|
||||
int16_t buf_left[chan_samples], buf_right[chan_samples];
|
||||
for (ssize_t i = 0, j = 0; i < samples_read; i += 2, ++j)
|
||||
for (ssize_t i = 0, j = 0; i < m_read_samples; i += 2, ++j)
|
||||
{
|
||||
buf_left[j] = samples[i];
|
||||
buf_right[j] = samples[i+1];
|
||||
buf_left[j] = m_sample_buffer[i];
|
||||
buf_right[j] = m_sample_buffer[i+1];
|
||||
}
|
||||
size_t half_height = w.getHeight()/2;
|
||||
|
||||
@@ -190,7 +240,7 @@ void Visualizer::update()
|
||||
}
|
||||
else
|
||||
{
|
||||
(this->*draw)(samples.data(), samples_read, 0, w.getHeight());
|
||||
(this->*draw)(m_sample_buffer.data(), m_read_samples, 0, w.getHeight());
|
||||
}
|
||||
w.refresh();
|
||||
}
|
||||
@@ -393,42 +443,111 @@ void Visualizer::DrawFrequencySpectrum(int16_t *buf, ssize_t samples, size_t y_o
|
||||
// If right channel is drawn, bars descend from the top to the bottom.
|
||||
const bool flipped = y_offset > 0;
|
||||
|
||||
// copy samples to fftw input array
|
||||
for (unsigned i = 0; i < m_samples; ++i)
|
||||
m_fftw_input[i] = i < samples ? buf[i] : 0;
|
||||
// copy samples to fftw input array and apply Hamming window
|
||||
ApplyWindow(m_fftw_input, buf, samples);
|
||||
fftw_execute(m_fftw_plan);
|
||||
|
||||
// Count magnitude of each frequency and scale it to fit the screen.
|
||||
// Count magnitude of each frequency and normalize
|
||||
for (size_t i = 0; i < m_fftw_results; ++i)
|
||||
m_freq_magnitudes[i] = sqrt(
|
||||
m_fftw_output[i][0]*m_fftw_output[i][0]
|
||||
+ m_fftw_output[i][1]*m_fftw_output[i][1]
|
||||
)/2e4*height;
|
||||
) / (DFT_NONZERO_SIZE);
|
||||
|
||||
const size_t win_width = w.getWidth();
|
||||
// Cut bandwidth a little to achieve better look.
|
||||
const double bins_per_bar = m_fftw_results/win_width * 7/10;
|
||||
double bar_height;
|
||||
size_t bar_bound_height;
|
||||
|
||||
double prev_bar_height = 0;
|
||||
size_t cur_bin = 0;
|
||||
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[0])
|
||||
{
|
||||
++cur_bin;
|
||||
}
|
||||
m_bar_heights.clear();
|
||||
for (size_t x = 0; x < win_width; ++x)
|
||||
{
|
||||
bar_height = 0;
|
||||
for (int j = 0; j < bins_per_bar; ++j)
|
||||
bar_height += m_freq_magnitudes[x*bins_per_bar+j];
|
||||
// Buff higher frequencies.
|
||||
bar_height *= log2(2 + x) * 100.0/win_width;
|
||||
// Moderately normalize the heights.
|
||||
bar_height = pow(bar_height, 0.5);
|
||||
double bar_height = 0;
|
||||
|
||||
bar_bound_height = std::min(std::size_t(bar_height/bins_per_bar), height);
|
||||
for (size_t j = 0; j < bar_bound_height; ++j)
|
||||
// accumulate bins
|
||||
size_t count = 0;
|
||||
// check right bound
|
||||
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[x])
|
||||
{
|
||||
// check left bound if not first index
|
||||
if (x == 0 || Bin2Hz(cur_bin) >= m_dft_logspace[x-1])
|
||||
{
|
||||
bar_height += m_freq_magnitudes[cur_bin];
|
||||
++count;
|
||||
}
|
||||
++cur_bin;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
continue;
|
||||
|
||||
// average bins
|
||||
bar_height /= count;
|
||||
prev_bar_height = bar_height;
|
||||
|
||||
// log scale bar heights
|
||||
bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE;
|
||||
// Scale bar height between 0 and height
|
||||
bar_height = bar_height > 0 ? bar_height * height : 0;
|
||||
bar_height = bar_height > height ? height : bar_height;
|
||||
|
||||
m_bar_heights.emplace_back(std::make_pair(x, bar_height));
|
||||
}
|
||||
|
||||
size_t h_idx = 0;
|
||||
for (size_t x = 0; x < win_width; ++x)
|
||||
{
|
||||
const size_t &i = m_bar_heights[h_idx].first;
|
||||
const double &bar_height = m_bar_heights[h_idx].second;
|
||||
double h = 0;
|
||||
|
||||
if (x == i) {
|
||||
// this data point exists
|
||||
h = bar_height;
|
||||
if (h_idx < m_bar_heights.size()-1)
|
||||
++h_idx;
|
||||
} else {
|
||||
// data point does not exist, need to interpolate
|
||||
h = Interpolate(x, h_idx);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < h; ++j)
|
||||
{
|
||||
size_t y = flipped ? y_offset+j : y_offset+height-j-1;
|
||||
auto c = toColor(j, height);
|
||||
auto color = toColor(j, height);
|
||||
std::wstring ch;
|
||||
bool reverse = false;
|
||||
|
||||
// select character to draw
|
||||
if (Config.visualizer_spectrum_smooth_look) {
|
||||
// smooth
|
||||
const size_t &size = SMOOTH_CHARS.size();
|
||||
const int idx = static_cast<int>(size*h) % size;
|
||||
if (j < h-1 || idx == size-1) {
|
||||
// full height
|
||||
ch = SMOOTH_CHARS[size-1];
|
||||
} else {
|
||||
// fractional height
|
||||
if (flipped) {
|
||||
ch = SMOOTH_CHARS[size-idx-2];
|
||||
color = NC::FormattedColor(color.color(), {NC::Format::Reverse});
|
||||
} else {
|
||||
ch = SMOOTH_CHARS[idx];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// default, non-smooth
|
||||
ch = Config.visualizer_chars[1];
|
||||
}
|
||||
|
||||
// draw character on screen
|
||||
w << NC::XY(x, y)
|
||||
<< c
|
||||
<< Config.visualizer_chars[1]
|
||||
<< NC::FormattedColor::End<>(c);
|
||||
<< color
|
||||
<< ch
|
||||
<< NC::FormattedColor::End<>(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,6 +557,86 @@ void Visualizer::DrawFrequencySpectrumStereo(int16_t *buf_left, int16_t *buf_rig
|
||||
DrawFrequencySpectrum(buf_left, samples, 0, height);
|
||||
DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height);
|
||||
}
|
||||
|
||||
double Visualizer::Interpolate(size_t x, size_t h_idx)
|
||||
{
|
||||
const double &x_next = m_bar_heights[h_idx].first;
|
||||
const double &h_next = m_bar_heights[h_idx].second;
|
||||
|
||||
double dh = 0;
|
||||
if (h_idx == 0) {
|
||||
// no data points on left, linear extrap
|
||||
if (h_idx < m_bar_heights.size()-1) {
|
||||
const double &x_next2 = m_bar_heights[h_idx+1].first;
|
||||
const double &h_next2 = m_bar_heights[h_idx+1].second;
|
||||
dh = (h_next2 - h_next) / (x_next2 - x_next);
|
||||
}
|
||||
return h_next - dh*(x_next-x);
|
||||
} else if (h_idx == 1) {
|
||||
// one data point on left, linear interp
|
||||
const double &x_prev = m_bar_heights[h_idx-1].first;
|
||||
const double &h_prev = m_bar_heights[h_idx-1].second;
|
||||
dh = (h_next - h_prev) / (x_next - x_prev);
|
||||
return h_next - dh*(x_next-x);
|
||||
} else if (h_idx < m_bar_heights.size()-1) {
|
||||
// two data points on both sides, cubic interp
|
||||
// see https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Interpolation_on_an_arbitrary_interval
|
||||
const double &x_prev2 = m_bar_heights[h_idx-2].first;
|
||||
const double &h_prev2 = m_bar_heights[h_idx-2].second;
|
||||
const double &x_prev = m_bar_heights[h_idx-1].first;
|
||||
const double &h_prev = m_bar_heights[h_idx-1].second;
|
||||
const double &x_next2 = m_bar_heights[h_idx+1].first;
|
||||
const double &h_next2 = m_bar_heights[h_idx+1].second;
|
||||
|
||||
const double m0 = (h_prev - h_prev2) / (x_prev - x_prev2);
|
||||
const double m1 = (h_next2 - h_next) / (x_next2 - x_next);
|
||||
const double t = (x - x_prev) / (x_next - x_prev);
|
||||
const double h00 = 2*t*t*t - 3*t*t + 1;
|
||||
const double h10 = t*t*t - 2*t*t + t;
|
||||
const double h01 = -2*t*t*t + 3*t*t;
|
||||
const double h11 = t*t*t - t*t;
|
||||
|
||||
return h00*h_prev + h10*(x_next-x_prev)*m0 + h01*h_next + h11*(x_next-x_prev)*m1;
|
||||
}
|
||||
|
||||
// less than two data points on right, no interp, should never happen unless VERY low DFT size
|
||||
return h_next;
|
||||
}
|
||||
|
||||
void Visualizer::ApplyWindow(double *output, int16_t *input, ssize_t samples)
|
||||
{
|
||||
// Use Blackman window for low sidelobes and fast sidelobe rolloff
|
||||
// don't care too much about mainlobe width
|
||||
const double alpha = 0.16;
|
||||
const double a0 = (1 - alpha) / 2;
|
||||
const double a1 = 0.5;
|
||||
const double a2 = alpha / 2;
|
||||
const double pi = boost::math::constants::pi<double>();
|
||||
for (unsigned i = 0; i < samples; ++i)
|
||||
{
|
||||
double window = a0 - a1*cos(2*pi*i/(DFT_NONZERO_SIZE-1)) + a2*cos(4*pi*i/(DFT_NONZERO_SIZE-1));
|
||||
output[i] = window * input[i] / INT16_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
double Visualizer::Bin2Hz(size_t bin)
|
||||
{
|
||||
return bin*44100/DFT_TOTAL_SIZE;
|
||||
}
|
||||
|
||||
// Generate log-scaled vector of frequencies from HZ_MIN to HZ_MAX
|
||||
void Visualizer::GenLogspace()
|
||||
{
|
||||
// Calculate number of extra bins needed between 0 HZ and HZ_MIN
|
||||
const size_t win_width = w.getWidth();
|
||||
const size_t left_bins = (log10(HZ_MIN) - win_width*log10(HZ_MIN)) / (log10(HZ_MIN) - log10(HZ_MAX));
|
||||
// Generate logspaced frequencies
|
||||
m_dft_logspace.resize(win_width);
|
||||
const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_logspace.size() - 1);
|
||||
for (int i = left_bins; i < m_dft_logspace.size() + left_bins; ++i) {
|
||||
m_dft_logspace[i - left_bins] = pow(10, i * log_scale);
|
||||
}
|
||||
}
|
||||
#endif // HAVE_FFTW3_H
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
@@ -71,19 +71,34 @@ private:
|
||||
# ifdef HAVE_FFTW3_H
|
||||
void DrawFrequencySpectrum(int16_t *, ssize_t, size_t, size_t);
|
||||
void DrawFrequencySpectrumStereo(int16_t *, int16_t *, ssize_t, size_t);
|
||||
void ApplyWindow(double *, int16_t *, ssize_t);
|
||||
void GenLogspace();
|
||||
double Bin2Hz(size_t);
|
||||
double Interpolate(size_t, size_t);
|
||||
# endif // HAVE_FFTW3_H
|
||||
|
||||
int m_output_id;
|
||||
boost::posix_time::ptime m_timer;
|
||||
|
||||
int m_fifo;
|
||||
size_t m_samples;
|
||||
size_t m_read_samples;
|
||||
std::vector<int16_t> m_sample_buffer;
|
||||
std::vector<int16_t> m_temp_sample_buffer;
|
||||
double m_auto_scale_multiplier;
|
||||
# ifdef HAVE_FFTW3_H
|
||||
size_t m_fftw_results;
|
||||
double *m_fftw_input;
|
||||
fftw_complex *m_fftw_output;
|
||||
fftw_plan m_fftw_plan;
|
||||
const uint32_t DFT_NONZERO_SIZE;
|
||||
const uint32_t DFT_TOTAL_SIZE;
|
||||
const double DYNAMIC_RANGE;
|
||||
const double HZ_MIN;
|
||||
const double HZ_MAX;
|
||||
const double GAIN;
|
||||
const std::wstring SMOOTH_CHARS;
|
||||
std::vector<double> m_dft_logspace;
|
||||
std::vector<std::pair<size_t, double>> m_bar_heights;
|
||||
|
||||
std::vector<double> m_freq_magnitudes;
|
||||
# endif // HAVE_FFTW3_H
|
||||
|
||||
Reference in New Issue
Block a user