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:
Evan Chang
2020-12-13 08:13:45 -05:00
committed by GitHub
parent 750e7ff59d
commit 47b3baf93c
6 changed files with 338 additions and 59 deletions

View File

@@ -94,6 +94,31 @@
## ##
#visualizer_color = 41, 83, 119, 155, 185, 215, 209, 203, 197, 161 #visualizer_color = 41, 83, 119, 155, 185, 215, 209, 203, 197, 161
# #
##
## Note: The next few visualization options apply to the spectrum visualizer
##
#
## Use unicode block characters for a smoother, more continuous look.
## This will override the visualizer_look option. With transparent terminals
## and visualizer_in_stereo set, artifacts may be visible on the bottom half of
## the visualization.
#
#visualizer_spectrum_smooth_look = yes
#
## A value between 0 and 5 inclusive. Specifying a larger value makes the
## visualizer look at a larger slice of time, which results less jumpy
## visualizer output.
#
#visualizer_spectrum_dft_size = 2
#
## Left-most frequency of visualizer in Hz, must be less than HZ MAX
#
#visualizer_spectrum_hz_min = 20
#
## Right-most frequency of visualizer in Hz, must be greater than HZ MIN
#
#visualizer_spectrum_hz_max = 20000
#
##### 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

@@ -99,6 +99,21 @@ Defines visualizer's look (string has to be exactly 2 characters long: first one
.B visualizer_color = COLORS .B visualizer_color = COLORS
Comma separated list of colors to be used in music visualization. Comma separated list of colors to be used in music visualization.
.TP .TP
.B visualizer_autoscale = yes/no
Automatically scale visualizer size.
.TP
.B visualizer_spectrum_smooth_look = yes/no
For spectrum visualizer, use unicode block characters for a smoother, more continuous look. This will override the visualizer_look option. With transparent terminals and visualizer_in_stereo set, artifacts may be visible on the bottom half of the visualization.
.TP
.B visualizer_spectrum_dft_size = NUMBER
For spectrum visualizer, a value between 0 and 5 inclusive. Specifying a larger value makes the visualizer look at a larger slice of time, which results less jumpy visualizer output.
.TP
.B visualizer_spectrum_hz_min = Hz
For spectrum visualizer, left-most frequency of visualizer, must be less than HZ MAX.
.TP
.B visualizer_spectrum_hz_max = Hz
For spectrum visualizer, right-most frequency of visualizer, must be greater than HZ MIN.
.TP
.B system_encoding = ENCODING .B system_encoding = ENCODING
If you use encoding other than utf8, set it in order to handle utf8 encoded strings properly. If you use encoding other than utf8, set it in order to handle utf8 encoded strings properly.
.TP .TP

View File

@@ -22,6 +22,7 @@
#ifdef ENABLE_VISUALIZER #ifdef ENABLE_VISUALIZER
#include <algorithm>
#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/math/constants/constants.hpp> #include <boost/math/constants/constants.hpp>
#include <cerrno> #include <cerrno>
@@ -30,6 +31,7 @@
#include <fstream> #include <fstream>
#include <limits> #include <limits>
#include <fcntl.h> #include <fcntl.h>
#include <cassert>
#include "global.h" #include "global.h"
#include "settings.h" #include "settings.h"
@@ -39,6 +41,7 @@
#include "screens/screen_switcher.h" #include "screens/screen_switcher.h"
#include "status.h" #include "status.h"
#include "enums.h" #include "enums.h"
#include "utility/wide_string.h"
using Samples = std::vector<int16_t>; using Samples = std::vector<int16_t>;
@@ -50,6 +53,7 @@ Visualizer *myVisualizer;
namespace { namespace {
const int fps = 25; const int fps = 25;
const uint32_t MIN_DFT_SIZE = 14;
// toColor: a scaling function for coloring. For numbers 0 to max this function // 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 // 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() Visualizer::Visualizer()
: Screen(NC::Window(0, MainStartY, COLS, MainHeight, "", NC::Color::Default, NC::Border())) : 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(); ResetFD();
m_samples = 44100/fps;
if (Config.visualizer_in_stereo)
m_samples *= 2;
# ifdef HAVE_FFTW3_H # 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_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_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 # endif // HAVE_FFTW3_H
} }
@@ -88,6 +114,11 @@ void Visualizer::switchTo()
SetFD(); SetFD();
m_timer = boost::posix_time::from_time_t(0); m_timer = boost::posix_time::from_time_t(0);
drawHeader(); 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() void Visualizer::resize()
@@ -97,6 +128,10 @@ void Visualizer::resize()
w.resize(width, MainHeight); w.resize(width, MainHeight);
w.moveTo(x_offset, MainStartY); w.moveTo(x_offset, MainStartY);
hasToBeResized = 0; hasToBeResized = 0;
# ifdef HAVE_FFTW3_H
GenLogspace();
m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H
} }
std::wstring Visualizer::title() std::wstring Visualizer::title()
@@ -111,12 +146,20 @@ void Visualizer::update()
// PCM in format 44100:16:1 (for mono visualization) and // PCM in format 44100:16:1 (for mono visualization) and
// 44100:16:2 (for stereo visualization) is supported. // 44100:16:2 (for stereo visualization) is supported.
Samples samples(m_samples); const int buf_size = sizeof(int16_t)*m_read_samples;
ssize_t data = read(m_fifo, samples.data(), ssize_t data = read(m_fifo, m_temp_sample_buffer.data(), buf_size);
samples.size() * sizeof(Samples::value_type)); if (data <= 0) // no data available in fifo
if (data < 0) // no data available in fifo
return; 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) if (m_output_id != -1 && Global::Timer - m_timer > Config.visualizer_sync_interval)
{ {
Mpd.DisableOutput(m_output_id); Mpd.DisableOutput(m_output_id);
@@ -130,6 +173,7 @@ void Visualizer::update()
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
if (Config.visualizer_type == VisualizerType::Spectrum) if (Config.visualizer_type == VisualizerType::Spectrum)
{ {
m_read_samples = DFT_NONZERO_SIZE;
draw = &Visualizer::DrawFrequencySpectrum; draw = &Visualizer::DrawFrequencySpectrum;
drawStereo = &Visualizer::DrawFrequencySpectrumStereo; drawStereo = &Visualizer::DrawFrequencySpectrumStereo;
} }
@@ -137,52 +181,58 @@ void Visualizer::update()
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
if (Config.visualizer_type == VisualizerType::WaveFilled) if (Config.visualizer_type == VisualizerType::WaveFilled)
{ {
m_read_samples = 44100 / fps;
draw = &Visualizer::DrawSoundWaveFill; draw = &Visualizer::DrawSoundWaveFill;
drawStereo = &Visualizer::DrawSoundWaveFillStereo; drawStereo = &Visualizer::DrawSoundWaveFillStereo;
} }
else if (Config.visualizer_type == VisualizerType::Ellipse) else if (Config.visualizer_type == VisualizerType::Ellipse)
{ {
m_read_samples = 44100 / fps;
draw = &Visualizer::DrawSoundEllipse; draw = &Visualizer::DrawSoundEllipse;
drawStereo = &Visualizer::DrawSoundEllipseStereo; drawStereo = &Visualizer::DrawSoundEllipseStereo;
} }
else else
{ {
m_read_samples = 44100 / fps;
draw = &Visualizer::DrawSoundWave; draw = &Visualizer::DrawSoundWave;
drawStereo = &Visualizer::DrawSoundWaveStereo; 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); if (Config.visualizer_autoscale) {
m_auto_scale_multiplier += 1.0/fps; m_auto_scale_multiplier += 1.0/fps;
for (auto &sample : samples) for (auto &sample : m_sample_buffer)
{ {
double scale = std::numeric_limits<int16_t>::min(); double scale = std::numeric_limits<int16_t>::min();
scale /= sample; scale /= sample;
scale = fabs(scale); scale = fabs(scale);
if (scale < m_auto_scale_multiplier) if (scale < m_auto_scale_multiplier)
m_auto_scale_multiplier = scale; m_auto_scale_multiplier = scale;
} }
for (auto &sample : samples) for (auto &sample : m_sample_buffer)
{ {
int32_t tmp = sample; int32_t tmp = sample;
if (m_auto_scale_multiplier <= 50.0) // limit the auto scale if (m_auto_scale_multiplier <= 50.0) // limit the auto scale
tmp *= m_auto_scale_multiplier; tmp *= m_auto_scale_multiplier;
if (tmp < std::numeric_limits<int16_t>::min()) if (tmp < std::numeric_limits<int16_t>::min())
sample = std::numeric_limits<int16_t>::min(); sample = std::numeric_limits<int16_t>::min();
else if (tmp > std::numeric_limits<int16_t>::max()) else if (tmp > std::numeric_limits<int16_t>::max())
sample = std::numeric_limits<int16_t>::max(); sample = std::numeric_limits<int16_t>::max();
else else
sample = tmp; sample = tmp;
}
} }
w.clear(); w.clear();
if (Config.visualizer_in_stereo) 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]; 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_left[j] = m_sample_buffer[i];
buf_right[j] = samples[i+1]; buf_right[j] = m_sample_buffer[i+1];
} }
size_t half_height = w.getHeight()/2; size_t half_height = w.getHeight()/2;
@@ -190,7 +240,7 @@ void Visualizer::update()
} }
else else
{ {
(this->*draw)(samples.data(), samples_read, 0, w.getHeight()); (this->*draw)(m_sample_buffer.data(), m_read_samples, 0, w.getHeight());
} }
w.refresh(); 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. // If right channel is drawn, bars descend from the top to the bottom.
const bool flipped = y_offset > 0; const bool flipped = y_offset > 0;
// copy samples to fftw input array // copy samples to fftw input array and apply Hamming window
for (unsigned i = 0; i < m_samples; ++i) ApplyWindow(m_fftw_input, buf, samples);
m_fftw_input[i] = i < samples ? buf[i] : 0;
fftw_execute(m_fftw_plan); 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) for (size_t i = 0; i < m_fftw_results; ++i)
m_freq_magnitudes[i] = sqrt( m_freq_magnitudes[i] = sqrt(
m_fftw_output[i][0]*m_fftw_output[i][0] m_fftw_output[i][0]*m_fftw_output[i][0]
+ m_fftw_output[i][1]*m_fftw_output[i][1] + m_fftw_output[i][1]*m_fftw_output[i][1]
)/2e4*height; ) / (DFT_NONZERO_SIZE);
const size_t win_width = w.getWidth(); 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 prev_bar_height = 0;
double bar_height; size_t cur_bin = 0;
size_t bar_bound_height; 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) for (size_t x = 0; x < win_width; ++x)
{ {
bar_height = 0; double 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);
bar_bound_height = std::min(std::size_t(bar_height/bins_per_bar), height); // accumulate bins
for (size_t j = 0; j < bar_bound_height; ++j) 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; 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) w << NC::XY(x, y)
<< c << color
<< Config.visualizer_chars[1] << ch
<< NC::FormattedColor::End<>(c); << 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_left, samples, 0, height);
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)
{
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 #endif // HAVE_FFTW3_H
/**********************************************************************/ /**********************************************************************/

View File

@@ -71,19 +71,34 @@ private:
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
void DrawFrequencySpectrum(int16_t *, ssize_t, size_t, size_t); void DrawFrequencySpectrum(int16_t *, ssize_t, size_t, size_t);
void DrawFrequencySpectrumStereo(int16_t *, int16_t *, ssize_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 # endif // HAVE_FFTW3_H
int m_output_id; int m_output_id;
boost::posix_time::ptime m_timer; boost::posix_time::ptime m_timer;
int m_fifo; 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; double m_auto_scale_multiplier;
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
size_t m_fftw_results; size_t m_fftw_results;
double *m_fftw_input; double *m_fftw_input;
fftw_complex *m_fftw_output; fftw_complex *m_fftw_output;
fftw_plan m_fftw_plan; 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; std::vector<double> m_freq_magnitudes;
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H

View File

@@ -291,6 +291,26 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
boundsCheck<std::wstring::size_type>(result.size(), 2, 2); boundsCheck<std::wstring::size_type>(result.size(), 2, 2);
return result; return result;
}); });
p.add("visualizer_autoscale", &visualizer_autoscale, "yes", yes_no);
p.add("visualizer_spectrum_smooth_look", &visualizer_spectrum_smooth_look, "yes", yes_no);
p.add("visualizer_spectrum_dft_size", &visualizer_spectrum_dft_size,
"12", [](std::string v) {
uint32_t result = verbose_lexical_cast<uint32_t>(v);
boundsCheck<uint32_t>(result, 0, 5);
return result + 12;
});
p.add("visualizer_spectrum_hz_min", &visualizer_spectrum_hz_min,
"20", [](std::string v) {
auto result = verbose_lexical_cast<double>(v);
lowerBoundCheck<double>(result, 1);
return result;
});
p.add("visualizer_spectrum_hz_max", &visualizer_spectrum_hz_max,
"20000", [](std::string v) {
auto result = verbose_lexical_cast<double>(v);
lowerBoundCheck<double>(result, Config.visualizer_spectrum_hz_min+1);
return result;
});
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

@@ -82,6 +82,11 @@ struct Configuration
std::string lastfm_preferred_language; std::string lastfm_preferred_language;
std::wstring progressbar; std::wstring progressbar;
std::wstring visualizer_chars; std::wstring visualizer_chars;
bool visualizer_autoscale;
bool visualizer_spectrum_smooth_look;
uint32_t visualizer_spectrum_dft_size;
double visualizer_spectrum_hz_min;
double visualizer_spectrum_hz_max;
std::string pattern; std::string pattern;