new feature: support for stereo visualization

This commit is contained in:
Andrzej Rybczak
2011-11-09 17:46:20 +01:00
parent 78cd46b756
commit 0e6306849c
6 changed files with 68 additions and 41 deletions

View File

@@ -23,17 +23,24 @@
## ##
## Note: In order to make music visualizer work you'll ## Note: In order to make music visualizer work you'll
## need to use mpd fifo output, whose format parameter ## need to use mpd fifo output, whose format parameter
## has to be set to 44100:16:1. Example configuration: ## has to be set to 44100:16:1 for mono visualization
## (it has to be put into mpd.conf) ## or 44100:16:2 for stereo visualization. Example
## configuration (it has to be put into mpd.conf):
## ##
## audio_output { ## audio_output {
## type "fifo" ## type "fifo"
## name "My FIFO" ## name "My FIFO"
## path "/tmp/mpd.fifo" ## path "/tmp/mpd.fifo"
## format "44100:16:1" ## format "44100:16:2"
## } ## }
## ##
# #
##
## If you set format to 44100:16:2, make it 'yes'.
##
#
#visualizer_in_stereo = "no"
#
#visualizer_fifo_path = "" #visualizer_fifo_path = ""
# #
## ##

View File

@@ -75,8 +75,11 @@ Default number of seconds to crossfade, if enabled by ncmpcpp.
.B mpd_communication_mode = MODE .B mpd_communication_mode = MODE
If set to 'polling', ncmpcpp will constantly poll mpd for its status. If set to 'notifications', ncmppcp will make use of 'idle' command and wait for events. This is more efficient and responsive, but may cause some trouble with <mpd-0.15, so if you run such version and encounter strange bugs (e.g. current track time not being updated), you will either have to use 'polling' or upgrade your mpd. If set to 'polling', ncmpcpp will constantly poll mpd for its status. If set to 'notifications', ncmppcp will make use of 'idle' command and wait for events. This is more efficient and responsive, but may cause some trouble with <mpd-0.15, so if you run such version and encounter strange bugs (e.g. current track time not being updated), you will either have to use 'polling' or upgrade your mpd.
.TP .TP
.B visualizer_in_stereo = yes/no
Should be set to 'yes', if fifo output's format was set to 44100:16:2.
.TP
.B visualizer_fifo_path = PATH .B visualizer_fifo_path = PATH
Path to mpd fifo output. This is needed to make music visualizer work (note that output sound format of this fifo has to be 44100:16:1) Path to mpd fifo output. This is needed to make music visualizer work (note that output sound format of this fifo has to be either 44100:16:1 or 44100:16:2, depending on whether you want mono or stereo visualization)
.TP .TP
.B visualizer_output_name = NAME .B visualizer_output_name = NAME
Name of output that provides data for visualizer. Needed to keep sound and visualization in sync. Name of output that provides data for visualizer. Needed to keep sound and visualization in sync.

View File

@@ -426,6 +426,7 @@ void NcmpcppConfig::SetDefaults()
mouse_list_scroll_whole_page = true; mouse_list_scroll_whole_page = true;
new_design = false; new_design = false;
visualizer_use_wave = true; visualizer_use_wave = true;
visualizer_in_stereo = false;
browser_sort_by_mtime = false; browser_sort_by_mtime = false;
tag_editor_extended_numeration = false; tag_editor_extended_numeration = false;
media_library_display_date = true; media_library_display_date = true;
@@ -1056,6 +1057,10 @@ void NcmpcppConfig::Read()
{ {
visualizer_use_wave = v == "wave"; visualizer_use_wave = v == "wave";
} }
else if (cl.find("visualizer_in_stereo") != std::string::npos)
{
visualizer_in_stereo = v == "yes";
}
else if (cl.find("mouse_support") != std::string::npos) else if (cl.find("mouse_support") != std::string::npos)
{ {
mouse_support = v == "yes"; mouse_support = v == "yes";

View File

@@ -248,6 +248,7 @@ struct NcmpcppConfig
bool mouse_list_scroll_whole_page; bool mouse_list_scroll_whole_page;
bool new_design; bool new_design;
bool visualizer_use_wave; bool visualizer_use_wave;
bool visualizer_in_stereo;
bool browser_sort_by_mtime; bool browser_sort_by_mtime;
bool tag_editor_extended_numeration; bool tag_editor_extended_numeration;
bool media_library_display_date; bool media_library_display_date;

View File

@@ -37,23 +37,20 @@ using Global::MainHeight;
Visualizer *myVisualizer = new Visualizer; Visualizer *myVisualizer = new Visualizer;
const unsigned Visualizer::Samples = 2048; const int Visualizer::WindowTimeout = 1000/25; /* 25 fps */
#ifdef HAVE_FFTW3_H
const unsigned Visualizer::FFTResults = Samples/2+1;
#endif // HAVE_FFTW3_H
int Visualizer::WindowTimeout = 1000/25; /* 25 fps */
void Visualizer::Init() void Visualizer::Init()
{ {
w = new Window(0, MainStartY, COLS, MainHeight, "", Config.visualizer_color, brNone); w = new Window(0, MainStartY, COLS, MainHeight, "", Config.visualizer_color, brNone);
ResetFD(); ResetFD();
itsSamples = Config.visualizer_in_stereo ? 4096 : 2048;
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
itsFreqsMagnitude = new unsigned[FFTResults]; itsFFTResults = itsSamples/2+1;
itsInput = static_cast<double *>(fftw_malloc(sizeof(double)*Samples)); itsFreqsMagnitude = new unsigned[itsFFTResults];
itsOutput = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*FFTResults)); itsInput = static_cast<double *>(fftw_malloc(sizeof(double)*itsSamples));
itsPlan = fftw_plan_dft_r2c_1d(Samples, itsInput, itsOutput, FFTW_ESTIMATE); itsOutput = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*itsFFTResults));
itsPlan = fftw_plan_dft_r2c_1d(itsSamples, itsInput, itsOutput, FFTW_ESTIMATE);
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
FindOutputID(); FindOutputID();
@@ -106,8 +103,8 @@ void Visualizer::Update()
if (itsFifo < 0) if (itsFifo < 0)
return; return;
// it supports only PCM in format 44100:16:1 // PCM in format 44100:16:1 (for mono visualization) and 44100:16:2 (for stereo visualization) is supported
static int16_t buf[Samples]; int16_t buf[itsSamples];
ssize_t data = read(itsFifo, buf, sizeof(buf)); ssize_t data = read(itsFifo, buf, sizeof(buf));
if (data < 0) // no data available in fifo if (data < 0) // no data available in fifo
return; return;
@@ -120,12 +117,30 @@ void Visualizer::Update()
gettimeofday(&itsTimer, 0); gettimeofday(&itsTimer, 0);
} }
w->Clear(); void (Visualizer::*draw)(int16_t *, ssize_t, size_t, size_t);
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
Config.visualizer_use_wave ? DrawSoundWave(buf, data) : DrawFrequencySpectrum(buf, data); if (!Config.visualizer_use_wave)
# else draw = &Visualizer::DrawFrequencySpectrum;
DrawSoundWave(buf, data); else
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
draw = &Visualizer::DrawSoundWave;
w->Clear();
if (Config.visualizer_in_stereo)
{
ssize_t bytes_read = data/sizeof(int16_t);
int16_t buf_left[bytes_read/2], buf_right[bytes_read/2];
for (ssize_t i = 0, j = 0; i < bytes_read; i += 2, ++j)
{
buf_left[j] = buf[i];
buf_right[j] = buf[i+1];
}
size_t half_height = MainHeight/2;
(this->*draw)(buf_left, data/2, 0, half_height);
(this->*draw)(buf_right, data/2, half_height+(draw == &Visualizer::DrawSoundWave ? 1 : 0), half_height+(draw != &Visualizer::DrawSoundWave ? 1 : 0));
}
else
(this->*draw)(buf, data, 0, MainHeight);
w->Refresh(); w->Refresh();
} }
@@ -137,10 +152,10 @@ void Visualizer::SpacePressed()
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
void Visualizer::DrawSoundWave(int16_t *buf, ssize_t data) void Visualizer::DrawSoundWave(int16_t *buf, ssize_t data, size_t y_offset, size_t height)
{ {
const int samples_per_col = data/sizeof(int16_t)/COLS; const int samples_per_col = data/sizeof(int16_t)/COLS;
const int half_height = MainHeight/2; const int half_height = height/2;
*w << fmtAltCharset; *w << fmtAltCharset;
double prev_point_pos = 0; double prev_point_pos = 0;
for (int i = 0; i < COLS; ++i) for (int i = 0; i < COLS; ++i)
@@ -151,7 +166,7 @@ void Visualizer::DrawSoundWave(int16_t *buf, ssize_t data)
point_pos /= samples_per_col; point_pos /= samples_per_col;
point_pos /= std::numeric_limits<int16_t>::max(); point_pos /= std::numeric_limits<int16_t>::max();
point_pos *= half_height; point_pos *= half_height;
*w << XY(i, half_height+point_pos) << '`'; *w << XY(i, y_offset+half_height+point_pos) << '`';
if (i && abs(prev_point_pos-point_pos) > 2) if (i && abs(prev_point_pos-point_pos) > 2)
{ {
// if gap is too big. intermediate values are needed // if gap is too big. intermediate values are needed
@@ -159,7 +174,7 @@ void Visualizer::DrawSoundWave(int16_t *buf, ssize_t data)
const int breakpoint = std::max(prev_point_pos, point_pos); const int breakpoint = std::max(prev_point_pos, point_pos);
const int half = (prev_point_pos+point_pos)/2; const int half = (prev_point_pos+point_pos)/2;
for (int k = std::min(prev_point_pos, point_pos)+1; k < breakpoint; k += 2) for (int k = std::min(prev_point_pos, point_pos)+1; k < breakpoint; k += 2)
*w << XY(i-(k < half), half_height+k) << '`'; *w << XY(i-(k < half), y_offset+half_height+k) << '`';
} }
prev_point_pos = point_pos; prev_point_pos = point_pos;
} }
@@ -167,27 +182,27 @@ void Visualizer::DrawSoundWave(int16_t *buf, ssize_t data)
} }
#ifdef HAVE_FFTW3_H #ifdef HAVE_FFTW3_H
void Visualizer::DrawFrequencySpectrum(int16_t *buf, ssize_t data) void Visualizer::DrawFrequencySpectrum(int16_t *buf, ssize_t data, size_t y_offset, size_t height)
{ {
// zero old values // zero old values
std::fill(buf+data/sizeof(int16_t), buf+Samples, 0); std::fill(buf+data/sizeof(int16_t), buf+data/2, 0);
for (unsigned i = 0; i < Samples; ++i) for (unsigned i = 0; i < data/2; ++i)
itsInput[i] = buf[i]; itsInput[i] = buf[i];
fftw_execute(itsPlan); fftw_execute(itsPlan);
// count magnitude of each frequency and scale it to fit the screen // count magnitude of each frequency and scale it to fit the screen
for (unsigned i = 0; i < FFTResults; ++i) for (unsigned i = 0; i < itsFFTResults; ++i)
itsFreqsMagnitude[i] = sqrt(itsOutput[i][0]*itsOutput[i][0] + itsOutput[i][1]*itsOutput[i][1])/1e5*LINES/5; itsFreqsMagnitude[i] = sqrt(itsOutput[i][0]*itsOutput[i][0] + itsOutput[i][1]*itsOutput[i][1])/1e5*height/5;
const int freqs_per_col = FFTResults/COLS /* cut bandwidth a little to achieve better look */ * 4/5; const int freqs_per_col = itsFFTResults/COLS /* cut bandwidth a little to achieve better look */ * 4/5;
for (int i = 0; i < COLS; ++i) for (int i = 0; i < COLS; ++i)
{ {
size_t bar_height = 0; size_t bar_height = 0;
for (int j = 0; j < freqs_per_col; ++j) for (int j = 0; j < freqs_per_col; ++j)
bar_height += itsFreqsMagnitude[i*freqs_per_col+j]; bar_height += itsFreqsMagnitude[i*freqs_per_col+j];
bar_height = std::min(bar_height/freqs_per_col, MainHeight); bar_height = std::min(bar_height/freqs_per_col, height);
mvwvline(w->Raw(), MainHeight-bar_height, i, 0, bar_height); mvwvline(w->Raw(), y_offset > 0 ? y_offset : height-bar_height, i, 0, bar_height);
} }
} }
#endif // HAVE_FFTW3_H #endif // HAVE_FFTW3_H

View File

@@ -58,33 +58,29 @@ class Visualizer : public Screen<Window>
void ResetFD(); void ResetFD();
void FindOutputID(); void FindOutputID();
static int WindowTimeout; static const int WindowTimeout;
protected: protected:
virtual void Init(); virtual void Init();
private: private:
void DrawSoundWave(int16_t *, ssize_t); void DrawSoundWave(int16_t *, ssize_t, size_t, size_t);
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
void DrawFrequencySpectrum(int16_t *, ssize_t); void DrawFrequencySpectrum(int16_t *, ssize_t, size_t, size_t);
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
int itsOutputID; int itsOutputID;
timeval itsTimer; timeval itsTimer;
int itsFifo; int itsFifo;
unsigned itsSamples;
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
unsigned itsFFTResults;
unsigned *itsFreqsMagnitude; unsigned *itsFreqsMagnitude;
double *itsInput; double *itsInput;
fftw_complex *itsOutput; fftw_complex *itsOutput;
fftw_plan itsPlan; fftw_plan itsPlan;
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
static const unsigned Samples;
# ifdef HAVE_FFTW3_H
static const unsigned FFTResults;
# endif // HAVE_FFTW3_H
}; };
extern Visualizer *myVisualizer; extern Visualizer *myVisualizer;