Tweaks to frequency spectrum and sound ellipse in visualizer (#464)

* use visualizer_spectrum_log_scale_{x,y} to control log scaling

* change sound ellipse character to the circle instead of the bar

* oops, should be determined by x-axis scaling

* rebase to upstream

* use values from Config directly

* fix that weird first-column screen-wrapping issue when drawing

* rebase to upstream

---------

Co-authored-by: whooie <wchooie@gmail.com>
Co-authored-by: Andrzej Rybczak <arybczak@users.noreply.github.com>
This commit is contained in:
Will Huie
2024-08-21 10:12:45 -05:00
committed by GitHub
parent f51968b98b
commit 7078ed7f55
5 changed files with 98 additions and 17 deletions

View File

@@ -149,6 +149,11 @@
# #
#visualizer_spectrum_hz_max = 20000 #visualizer_spectrum_hz_max = 20000
# #
## Use log scaling for the frequency spectrum axes
#
#visualizer_spectrum_log_scale_x = yes
#visualizer_spectrum_log_scale_y = yes
#
##### system encoding ##### ##### system encoding #####
## ##
## ncmpcpp should detect your charset encoding but if it failed to do so, you ## ncmpcpp should detect your charset encoding but if it failed to do so, you

View File

@@ -96,7 +96,7 @@ Visualizer::Visualizer()
memset(m_fftw_input, 0, 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_output = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*m_fftw_results));
m_fftw_plan = fftw_plan_dft_r2c_1d(DFT_TOTAL_SIZE, 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_dft_freqspace.reserve(500);
m_bar_heights.reserve(100); m_bar_heights.reserve(100);
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
@@ -108,7 +108,7 @@ void Visualizer::switchTo()
m_reset_output = true; m_reset_output = true;
drawHeader(); drawHeader();
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
GenLogspace(); GenFreqSpace();
m_bar_heights.reserve(w.getWidth()); m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
@@ -122,7 +122,7 @@ void Visualizer::resize()
hasToBeResized = 0; hasToBeResized = 0;
InitVisualization(); InitVisualization();
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
GenLogspace(); GenFreqSpace();
m_bar_heights.reserve(w.getWidth()); m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
@@ -421,7 +421,7 @@ void Visualizer::DrawSoundEllipseStereo(const int16_t *buf_left, const int16_t *
auto c = toColor(sqrt(x*x + 4*y*y), radius, true); auto c = toColor(sqrt(x*x + 4*y*y), radius, true);
w << NC::XY(left_half_width + x, top_half_height + y) w << NC::XY(left_half_width + x, top_half_height + y)
<< c << c
<< Config.visualizer_chars[1] << Config.visualizer_chars[0]
<< NC::FormattedColor::End<>(c); << NC::FormattedColor::End<>(c);
} }
} }
@@ -450,7 +450,7 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
const size_t win_width = w.getWidth(); const size_t win_width = w.getWidth();
size_t cur_bin = 0; size_t cur_bin = 0;
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[0]) while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_freqspace[0])
++cur_bin; ++cur_bin;
for (size_t x = 0; x < win_width; ++x) for (size_t x = 0; x < win_width; ++x)
{ {
@@ -459,10 +459,10 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// accumulate bins // accumulate bins
size_t count = 0; size_t count = 0;
// check right bound // check right bound
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[x]) while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_freqspace[x])
{ {
// check left bound if not first index // check left bound if not first index
if (x == 0 || Bin2Hz(cur_bin) >= m_dft_logspace[x-1]) if (x == 0 || Bin2Hz(cur_bin) >= m_dft_freqspace[x-1])
{ {
bar_height += m_freq_magnitudes[cur_bin]; bar_height += m_freq_magnitudes[cur_bin];
++count; ++count;
@@ -476,8 +476,19 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// average bins // average bins
bar_height /= count; bar_height /= count;
// log scale bar heights // apply scaling to bar heights
bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE; if (Config.visualizer_spectrum_log_scale_y) {
bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE;
} else {
// apply gain
bar_height *= pow(10, 1.8 + GAIN / 20);
// buff higher frequencies
bar_height *= log2(2 + x) * 80.0/win_width;
// moderately normalize the heights
bar_height = pow(bar_height, 0.65);
//bar_height = pow(10, 1 + GAIN / 20) * bar_height;
}
// Scale bar height between 0 and height // Scale bar height between 0 and height
bar_height = bar_height > 0 ? bar_height * height : 0; bar_height = bar_height > 0 ? bar_height * height : 0;
bar_height = bar_height > height ? height : bar_height; bar_height = bar_height > height ? height : bar_height;
@@ -499,7 +510,12 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
++h_idx; ++h_idx;
} else { } else {
// data point does not exist, need to interpolate // data point does not exist, need to interpolate
h = Interpolate(x, h_idx); if (Config.visualizer_spectrum_log_scale_x) {
h = InterpolateCubic(x, h_idx);
} else {
h = std::min(InterpolateLinear(x, h_idx), height / 1.0);
//h = 0;
}
} }
for (size_t j = 0; j < h; ++j) for (size_t j = 0; j < h; ++j)
@@ -549,7 +565,7 @@ void Visualizer::DrawFrequencySpectrumStereo(const int16_t *buf_left, const int1
DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height); DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height);
} }
double Visualizer::Interpolate(size_t x, size_t h_idx) double Visualizer::InterpolateCubic(size_t x, size_t h_idx)
{ {
const double x_next = m_bar_heights[h_idx].first; const double x_next = m_bar_heights[h_idx].first;
const double h_next = m_bar_heights[h_idx].second; const double h_next = m_bar_heights[h_idx].second;
@@ -594,6 +610,33 @@ double Visualizer::Interpolate(size_t x, size_t h_idx)
return h_next; return h_next;
} }
double Visualizer::InterpolateLinear(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 the left, linear extrapolation
if (h_idx < m_bar_heights.size()) {
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 < m_bar_heights.size()) {
// simple linear interpolation
const double x_prev = m_bar_heights[h_idx-1].first;
const double h_prev = m_bar_heights[h_idx-1].second;
const double m = (h_next - h_prev) / (x_next - x_prev);
return h_prev + m * (x - x_prev);
}
// no data points on the right: don't interpolate
return h_next;
}
void Visualizer::ApplyWindow(double *output, const int16_t *input, ssize_t samples) void Visualizer::ApplyWindow(double *output, const int16_t *input, ssize_t samples)
{ {
// Use Blackman window for low sidelobes and fast sidelobe rolloff // Use Blackman window for low sidelobes and fast sidelobe rolloff
@@ -622,10 +665,36 @@ void Visualizer::GenLogspace()
const size_t win_width = w.getWidth(); 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)); const size_t left_bins = (log10(HZ_MIN) - win_width*log10(HZ_MIN)) / (log10(HZ_MIN) - log10(HZ_MAX));
// Generate logspaced frequencies // Generate logspaced frequencies
m_dft_logspace.resize(win_width); m_dft_freqspace.resize(win_width);
const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_logspace.size() - 1); const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_freqspace.size() - 1);
for (size_t i = left_bins; i < m_dft_logspace.size() + left_bins; ++i) { for (size_t i = left_bins; i < m_dft_freqspace.size() + left_bins; ++i) {
m_dft_logspace[i - left_bins] = pow(10, i * log_scale); m_dft_freqspace[i - left_bins] = pow(10, i * log_scale);
}
}
// Generate vector of linearly-spaced frequencies from HZ_MIN to HZ_MAX
void Visualizer::GenLinspace()
{
// Calculate number of extra bins needed between 0 HZ and HZ_MIN
const size_t win_width = w.getWidth();
const size_t left_bins = (HZ_MIN - win_width * HZ_MIN) / (HZ_MIN - HZ_MAX);
// Generate linspaced frequencies
m_dft_freqspace.resize(win_width);
const double lin_scale = HZ_MAX / (left_bins + m_dft_freqspace.size() - 1);
for (size_t i = left_bins; i < m_dft_freqspace.size() + left_bins; ++i) {
m_dft_freqspace[i - left_bins] = i * lin_scale;
}
}
// Generate vector of spectrum frequencies from HZ_MIN to HZ_MAX
// Frequencies are (not) log-scaled depending on
// Config.visualizer_spectrum_log_scale_x
void Visualizer::GenFreqSpace()
{
if (Config.visualizer_spectrum_log_scale_x) {
GenLogspace();
} else {
GenLinspace();
} }
} }
#endif // HAVE_FFTW3_H #endif // HAVE_FFTW3_H

View File

@@ -76,8 +76,11 @@ private:
void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t); void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t);
void ApplyWindow(double *, const int16_t *, ssize_t); void ApplyWindow(double *, const int16_t *, ssize_t);
void GenLogspace(); void GenLogspace();
void GenLinspace();
void GenFreqSpace();
double Bin2Hz(size_t); double Bin2Hz(size_t);
double Interpolate(size_t, size_t); double InterpolateCubic(size_t, size_t);
double InterpolateLinear(size_t, size_t);
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
void InitDataSource(); void InitDataSource();
@@ -114,7 +117,7 @@ private:
const double GAIN; const double GAIN;
const std::wstring SMOOTH_CHARS; const std::wstring SMOOTH_CHARS;
const std::wstring SMOOTH_CHARS_FLIPPED; const std::wstring SMOOTH_CHARS_FLIPPED;
std::vector<double> m_dft_logspace; std::vector<double> m_dft_freqspace;
std::vector<std::pair<size_t, double>> m_bar_heights; std::vector<std::pair<size_t, double>> m_bar_heights;
std::vector<double> m_freq_magnitudes; std::vector<double> m_freq_magnitudes;

View File

@@ -278,6 +278,8 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
lowerBoundCheck<double>(result, Config.visualizer_spectrum_hz_min+1); lowerBoundCheck<double>(result, Config.visualizer_spectrum_hz_min+1);
return result; return result;
}); });
p.add("visualizer_spectrum_log_scale_x", &visualizer_spectrum_log_scale_x, "yes", yes_no);
p.add("visualizer_spectrum_log_scale_y", &visualizer_spectrum_log_scale_y, "yes", yes_no);
p.add("visualizer_color", &visualizer_colors, p.add("visualizer_color", &visualizer_colors,
"blue, cyan, green, yellow, magenta, red", list_of<NC::FormattedColor>); "blue, cyan, green, yellow, magenta, red", list_of<NC::FormattedColor>);
p.add("system_encoding", &system_encoding, "", [](std::string encoding) { p.add("system_encoding", &system_encoding, "", [](std::string encoding) {

View File

@@ -91,6 +91,8 @@ struct Configuration
double visualizer_spectrum_gain; double visualizer_spectrum_gain;
double visualizer_spectrum_hz_min; double visualizer_spectrum_hz_min;
double visualizer_spectrum_hz_max; double visualizer_spectrum_hz_max;
bool visualizer_spectrum_log_scale_x;
bool visualizer_spectrum_log_scale_y;
std::string pattern; std::string pattern;