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:
+84
-15
@@ -96,7 +96,7 @@ Visualizer::Visualizer()
|
||||
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(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
|
||||
|
||||
@@ -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<double> m_dft_logspace;
|
||||
std::vector<double> m_dft_freqspace;
|
||||
std::vector<std::pair<size_t, double>> m_bar_heights;
|
||||
|
||||
std::vector<double> m_freq_magnitudes;
|
||||
|
||||
@@ -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);
|
||||
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<NC::FormattedColor>);
|
||||
p.add("system_encoding", &system_encoding, "", [](std::string encoding) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user