From 7078ed7f5514c7ded27cbd8ac57170d9bbcbfc15 Mon Sep 17 00:00:00 2001 From: Will Huie Date: Wed, 21 Aug 2024 10:12:45 -0500 Subject: [PATCH] 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 Co-authored-by: Andrzej Rybczak --- doc/config | 5 ++ src/screens/visualizer.cpp | 99 ++++++++++++++++++++++++++++++++------ src/screens/visualizer.h | 7 ++- src/settings.cpp | 2 + src/settings.h | 2 + 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/doc/config b/doc/config index 327a0678..cc6d2913 100644 --- a/doc/config +++ b/doc/config @@ -149,6 +149,11 @@ # #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 ##### ## ## ncmpcpp should detect your charset encoding but if it failed to do so, you diff --git a/src/screens/visualizer.cpp b/src/screens/visualizer.cpp index e48decf2..66fc546d 100644 --- a/src/screens/visualizer.cpp +++ b/src/screens/visualizer.cpp @@ -96,7 +96,7 @@ Visualizer::Visualizer() memset(m_fftw_input, 0, sizeof(double)*DFT_TOTAL_SIZE); m_fftw_output = static_cast(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_dft_logspace.reserve(500); + m_dft_freqspace.reserve(500); m_bar_heights.reserve(100); # endif // HAVE_FFTW3_H } @@ -108,7 +108,7 @@ void Visualizer::switchTo() m_reset_output = true; drawHeader(); # ifdef HAVE_FFTW3_H - GenLogspace(); + GenFreqSpace(); m_bar_heights.reserve(w.getWidth()); # endif // HAVE_FFTW3_H } @@ -122,7 +122,7 @@ void Visualizer::resize() hasToBeResized = 0; InitVisualization(); # ifdef HAVE_FFTW3_H - GenLogspace(); + GenFreqSpace(); m_bar_heights.reserve(w.getWidth()); # 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); w << NC::XY(left_half_width + x, top_half_height + y) << c - << Config.visualizer_chars[1] + << Config.visualizer_chars[0] << 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(); 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; 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 size_t count = 0; // 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 - 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]; ++count; @@ -476,8 +476,19 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size // average bins bar_height /= count; - // log scale bar heights - bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE; + // apply scaling to bar heights + 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 bar_height = bar_height > 0 ? bar_height * height : 0; 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; } else { // 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) @@ -549,7 +565,7 @@ void Visualizer::DrawFrequencySpectrumStereo(const int16_t *buf_left, const int1 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 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; } +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) { // 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 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 (size_t i = left_bins; i < m_dft_logspace.size() + left_bins; ++i) { - m_dft_logspace[i - left_bins] = pow(10, i * log_scale); + m_dft_freqspace.resize(win_width); + const double log_scale = log10(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] = 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 diff --git a/src/screens/visualizer.h b/src/screens/visualizer.h index 3b7c9644..3a7a65ba 100644 --- a/src/screens/visualizer.h +++ b/src/screens/visualizer.h @@ -76,8 +76,11 @@ private: void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t); void ApplyWindow(double *, const int16_t *, ssize_t); void GenLogspace(); + void GenLinspace(); + void GenFreqSpace(); 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 void InitDataSource(); @@ -114,7 +117,7 @@ private: const double GAIN; const std::wstring SMOOTH_CHARS; const std::wstring SMOOTH_CHARS_FLIPPED; - std::vector m_dft_logspace; + std::vector m_dft_freqspace; std::vector> m_bar_heights; std::vector m_freq_magnitudes; diff --git a/src/settings.cpp b/src/settings.cpp index 57a9a5bf..8315062e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -278,6 +278,8 @@ bool Configuration::read(const std::vector &config_paths, bool igno lowerBoundCheck(result, Config.visualizer_spectrum_hz_min+1); 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, "blue, cyan, green, yellow, magenta, red", list_of); p.add("system_encoding", &system_encoding, "", [](std::string encoding) { diff --git a/src/settings.h b/src/settings.h index 106163f0..e9a8af08 100644 --- a/src/settings.h +++ b/src/settings.h @@ -91,6 +91,8 @@ struct Configuration double visualizer_spectrum_gain; double visualizer_spectrum_hz_min; double visualizer_spectrum_hz_max; + bool visualizer_spectrum_log_scale_x; + bool visualizer_spectrum_log_scale_y; std::string pattern;