1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "screens/visualizer.h"
23 #ifdef ENABLE_VISUALIZER
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/math/constants/constants.hpp>
37 #include "statusbar.h"
39 #include "screens/screen_switcher.h"
43 using Samples
= std::vector
<int16_t>;
45 using Global::MainStartY
;
46 using Global::MainHeight
;
48 Visualizer
*myVisualizer
;
54 // toColor: a scaling function for coloring. For numbers 0 to max this function
55 // returns a coloring from the lowest color to the highest, and colors will not
56 // loop from 0 to max.
57 const NC::FormattedColor
&toColor(size_t number
, size_t max
, bool wrap
= true)
59 const auto colors_size
= Config
.visualizer_colors
.size();
60 const auto index
= (number
* colors_size
) / max
;
61 return Config
.visualizer_colors
[
62 wrap
? index
% colors_size
: std::min(index
, colors_size
-1)
68 Visualizer::Visualizer()
69 : Screen(NC::Window(0, MainStartY
, COLS
, MainHeight
, "", NC::Color::Default
, NC::Border()))
72 m_samples
= 44100/fps
;
73 if (Config
.visualizer_in_stereo
)
76 m_fftw_results
= m_samples
/2+1;
77 m_freq_magnitudes
.resize(m_fftw_results
);
78 m_fftw_input
= static_cast<double *>(fftw_malloc(sizeof(double)*m_samples
));
79 m_fftw_output
= static_cast<fftw_complex
*>(fftw_malloc(sizeof(fftw_complex
)*m_fftw_results
));
80 m_fftw_plan
= fftw_plan_dft_r2c_1d(m_samples
, m_fftw_input
, m_fftw_output
, FFTW_ESTIMATE
);
81 # endif // HAVE_FFTW3_H
84 void Visualizer::switchTo()
86 SwitchTo::execute(this);
89 m_timer
= boost::posix_time::from_time_t(0);
93 void Visualizer::resize()
95 size_t x_offset
, width
;
96 getWindowResizeParams(x_offset
, width
);
97 w
.resize(width
, MainHeight
);
98 w
.moveTo(x_offset
, MainStartY
);
102 std::wstring
Visualizer::title()
104 return L
"Music visualizer";
107 void Visualizer::update()
112 // PCM in format 44100:16:1 (for mono visualization) and
113 // 44100:16:2 (for stereo visualization) is supported.
114 Samples
samples(m_samples
);
115 ssize_t data
= read(m_fifo
, samples
.data(),
116 samples
.size() * sizeof(Samples::value_type
));
117 if (data
< 0) // no data available in fifo
120 if (m_output_id
!= -1 && Global::Timer
- m_timer
> Config
.visualizer_sync_interval
)
122 Mpd
.DisableOutput(m_output_id
);
124 Mpd
.EnableOutput(m_output_id
);
125 m_timer
= Global::Timer
;
128 void (Visualizer::*draw
)(int16_t *, ssize_t
, size_t, size_t);
129 void (Visualizer::*drawStereo
)(int16_t *, int16_t *, ssize_t
, size_t);
131 if (Config
.visualizer_type
== VisualizerType::Spectrum
)
133 draw
= &Visualizer::DrawFrequencySpectrum
;
134 drawStereo
= &Visualizer::DrawFrequencySpectrumStereo
;
137 # endif // HAVE_FFTW3_H
138 if (Config
.visualizer_type
== VisualizerType::WaveFilled
)
140 draw
= &Visualizer::DrawSoundWaveFill
;
141 drawStereo
= &Visualizer::DrawSoundWaveFillStereo
;
143 else if (Config
.visualizer_type
== VisualizerType::Ellipse
)
145 draw
= &Visualizer::DrawSoundEllipse
;
146 drawStereo
= &Visualizer::DrawSoundEllipseStereo
;
150 draw
= &Visualizer::DrawSoundWave
;
151 drawStereo
= &Visualizer::DrawSoundWaveStereo
;
154 const ssize_t samples_read
= data
/sizeof(int16_t);
155 m_auto_scale_multiplier
+= 1.0/fps
;
156 for (auto &sample
: samples
)
158 double scale
= std::numeric_limits
<int16_t>::min();
161 if (scale
< m_auto_scale_multiplier
)
162 m_auto_scale_multiplier
= scale
;
164 for (auto &sample
: samples
)
166 int32_t tmp
= sample
;
167 if (m_auto_scale_multiplier
<= 50.0) // limit the auto scale
168 tmp
*= m_auto_scale_multiplier
;
169 if (tmp
< std::numeric_limits
<int16_t>::min())
170 sample
= std::numeric_limits
<int16_t>::min();
171 else if (tmp
> std::numeric_limits
<int16_t>::max())
172 sample
= std::numeric_limits
<int16_t>::max();
178 if (Config
.visualizer_in_stereo
)
180 auto chan_samples
= samples_read
/2;
181 int16_t buf_left
[chan_samples
], buf_right
[chan_samples
];
182 for (ssize_t i
= 0, j
= 0; i
< samples_read
; i
+= 2, ++j
)
184 buf_left
[j
] = samples
[i
];
185 buf_right
[j
] = samples
[i
+1];
187 size_t half_height
= w
.getHeight()/2;
189 (this->*drawStereo
)(buf_left
, buf_right
, chan_samples
, half_height
);
193 (this->*draw
)(samples
.data(), samples_read
, 0, w
.getHeight());
198 int Visualizer::windowTimeout()
200 if (m_fifo
>= 0 && Status::State::player() == MPD::psPlay
)
203 return Screen
<WindowType
>::windowTimeout();
206 /**********************************************************************/
208 void Visualizer::DrawSoundWave(int16_t *buf
, ssize_t samples
, size_t y_offset
, size_t height
)
210 const size_t half_height
= height
/2;
211 const size_t base_y
= y_offset
+half_height
;
212 const size_t win_width
= w
.getWidth();
213 const int samples_per_column
= samples
/win_width
;
215 // too little samples
216 if (samples_per_column
== 0)
219 auto draw_point
= [&](size_t x
, int32_t y
) {
220 auto c
= toColor(std::abs(y
), half_height
, false);
221 w
<< NC::XY(x
, base_y
+y
)
223 << Config
.visualizer_chars
[0]
224 << NC::FormattedColor::End(c
);
227 int32_t point_y
, prev_point_y
= 0;
228 for (size_t x
= 0; x
< win_width
; ++x
)
231 // calculate mean from the relevant points
232 for (int j
= 0; j
< samples_per_column
; ++j
)
233 point_y
+= buf
[x
*samples_per_column
+j
];
234 point_y
/= samples_per_column
;
235 // normalize it to fit the screen
236 point_y
*= height
/ 65536.0;
238 draw_point(x
, point_y
);
240 // if the gap between two consecutive points is too big,
241 // intermediate values are needed for the wave to be watchable.
242 if (x
> 0 && std::abs(prev_point_y
-point_y
) > 1)
244 const int32_t half
= (prev_point_y
+point_y
)/2;
245 if (prev_point_y
< point_y
)
247 for (auto y
= prev_point_y
; y
< point_y
; ++y
)
248 draw_point(x
-(y
< half
), y
);
252 for (auto y
= prev_point_y
; y
> point_y
; --y
)
253 draw_point(x
-(y
> half
), y
);
256 prev_point_y
= point_y
;
260 void Visualizer::DrawSoundWaveStereo(int16_t *buf_left
, int16_t *buf_right
, ssize_t samples
, size_t height
)
262 DrawSoundWave(buf_left
, samples
, 0, height
);
263 DrawSoundWave(buf_right
, samples
, height
, w
.getHeight() - height
);
266 /**********************************************************************/
268 // DrawSoundWaveFill: This visualizer is very similar to DrawSoundWave, but
269 // instead of a single line the entire height is filled. In stereo mode, the top
270 // half of the screen is dedicated to the right channel, the bottom the left
272 void Visualizer::DrawSoundWaveFill(int16_t *buf
, ssize_t samples
, size_t y_offset
, size_t height
)
274 // if right channel is drawn, bars descend from the top to the bottom
275 const bool flipped
= y_offset
> 0;
276 const size_t win_width
= w
.getWidth();
277 const int samples_per_column
= samples
/win_width
;
279 // too little samples
280 if (samples_per_column
== 0)
284 for (size_t x
= 0; x
< win_width
; ++x
)
287 // calculate mean from the relevant points
288 for (int j
= 0; j
< samples_per_column
; ++j
)
289 point_y
+= buf
[x
*samples_per_column
+j
];
290 point_y
/= samples_per_column
;
291 // normalize it to fit the screen
292 point_y
= std::abs(point_y
);
293 point_y
*= height
/ 32768.0;
295 for (int32_t j
= 0; j
< point_y
; ++j
)
297 auto c
= toColor(j
, height
);
298 size_t y
= flipped
? y_offset
+j
: y_offset
+height
-j
-1;
301 << Config
.visualizer_chars
[1]
302 << NC::FormattedColor::End(c
);
307 void Visualizer::DrawSoundWaveFillStereo(int16_t *buf_left
, int16_t *buf_right
, ssize_t samples
, size_t height
)
309 DrawSoundWaveFill(buf_left
, samples
, 0, height
);
310 DrawSoundWaveFill(buf_right
, samples
, height
, w
.getHeight() - height
);
313 /**********************************************************************/
315 // Draws the sound wave as an ellipse with origin in the center of the screen.
316 void Visualizer::DrawSoundEllipse(int16_t *buf
, ssize_t samples
, size_t, size_t height
)
318 const size_t half_width
= w
.getWidth()/2;
319 const size_t half_height
= height
/2;
321 // Make it so that the loop goes around the ellipse exactly once.
322 const double deg_multiplier
= 2*boost::math::constants::pi
<double>()/samples
;
325 double radius
, max_radius
;
326 for (ssize_t i
= 0; i
< samples
; ++i
)
328 x
= half_width
* std::cos(i
*deg_multiplier
);
329 y
= half_height
* std::sin(i
*deg_multiplier
);
330 max_radius
= sqrt(x
*x
+ y
*y
);
332 // Calculate the distance of the sample from the center, where 0 is the
333 // center of the ellipse and 1 is its border.
334 radius
= std::abs(buf
[i
]);
337 // Appropriately scale the position.
341 auto c
= toColor(sqrt(x
*x
+ y
*y
), max_radius
, false);
342 w
<< NC::XY(half_width
+ x
, half_height
+ y
)
344 << Config
.visualizer_chars
[0]
345 << NC::FormattedColor::End(c
);
349 // DrawSoundEllipseStereo: This visualizer only works in stereo. The colors form
350 // concentric rings originating from the center (width/2, height/2). For any
351 // given point, the width is scaled with the left channel and height is scaled
352 // with the right channel. For example, if a song is entirely in the right
353 // channel, then it would just be a vertical line.
355 // Since every font/terminal is different, the visualizer is never a perfect
356 // circle. This visualizer assume the font height is twice the length of the
357 // font's width. If the font is skinner or wider than this, instead of a circle
358 // it will be an ellipse.
359 void Visualizer::DrawSoundEllipseStereo(int16_t *buf_left
, int16_t *buf_right
, ssize_t samples
, size_t half_height
)
361 const size_t width
= w
.getWidth();
362 const size_t left_half_width
= width
/2;
363 const size_t right_half_width
= width
- left_half_width
;
364 const size_t top_half_height
= half_height
;
365 const size_t bottom_half_height
= w
.getHeight() - half_height
;
367 // Makes the radius of each ring be approximately 2 cells wide.
368 const int32_t radius
= 2*Config
.visualizer_colors
.size();
370 for (ssize_t i
= 0; i
< samples
; ++i
)
372 x
= buf_left
[i
]/32768.0 * (buf_left
[i
] < 0 ? left_half_width
: right_half_width
);
373 y
= buf_right
[i
]/32768.0 * (buf_right
[i
] < 0 ? top_half_height
: bottom_half_height
);
375 // The arguments to the toColor function roughly follow a circle equation
376 // where the center is not centered around (0,0). For example (x - w)^2 +
377 // (y-h)+2 = r^2 centers the circle around the point (w,h). Because fonts
378 // are not all the same size, this will not always generate a perfect
380 auto c
= toColor(sqrt(x
*x
+ 4*y
*y
), radius
);
381 w
<< NC::XY(left_half_width
+ x
, top_half_height
+ y
)
383 << Config
.visualizer_chars
[1]
384 << NC::FormattedColor::End(c
);
388 /**********************************************************************/
391 void Visualizer::DrawFrequencySpectrum(int16_t *buf
, ssize_t samples
, size_t y_offset
, size_t height
)
393 // If right channel is drawn, bars descend from the top to the bottom.
394 const bool flipped
= y_offset
> 0;
396 // copy samples to fftw input array
397 for (unsigned i
= 0; i
< m_samples
; ++i
)
398 m_fftw_input
[i
] = i
< samples
? buf
[i
] : 0;
399 fftw_execute(m_fftw_plan
);
401 // Count magnitude of each frequency and scale it to fit the screen.
402 for (size_t i
= 0; i
< m_fftw_results
; ++i
)
403 m_freq_magnitudes
[i
] = sqrt(
404 m_fftw_output
[i
][0]*m_fftw_output
[i
][0]
405 + m_fftw_output
[i
][1]*m_fftw_output
[i
][1]
408 const size_t win_width
= w
.getWidth();
409 // Cut bandwidth a little to achieve better look.
410 const double bins_per_bar
= m_fftw_results
/win_width
* 7/10;
412 size_t bar_bound_height
;
413 for (size_t x
= 0; x
< win_width
; ++x
)
416 for (int j
= 0; j
< bins_per_bar
; ++j
)
417 bar_height
+= m_freq_magnitudes
[x
*bins_per_bar
+j
];
418 // Buff higher frequencies.
419 bar_height
*= log2(2 + x
) * 100.0/win_width
;
420 // Moderately normalize the heights.
421 bar_height
= pow(bar_height
, 0.5);
423 bar_bound_height
= std::min(std::size_t(bar_height
/bins_per_bar
), height
);
424 for (size_t j
= 0; j
< bar_bound_height
; ++j
)
426 size_t y
= flipped
? y_offset
+j
: y_offset
+height
-j
-1;
427 auto c
= toColor(j
, height
);
430 << Config
.visualizer_chars
[1]
431 << NC::FormattedColor::End(c
);
436 void Visualizer::DrawFrequencySpectrumStereo(int16_t *buf_left
, int16_t *buf_right
, ssize_t samples
, size_t height
)
438 DrawFrequencySpectrum(buf_left
, samples
, 0, height
);
439 DrawFrequencySpectrum(buf_right
, samples
, height
, w
.getHeight() - height
);
441 #endif // HAVE_FFTW3_H
443 /**********************************************************************/
445 void Visualizer::ToggleVisualizationType()
447 switch (Config
.visualizer_type
)
449 case VisualizerType::Wave
:
450 Config
.visualizer_type
= VisualizerType::WaveFilled
;
452 case VisualizerType::WaveFilled
:
454 Config
.visualizer_type
= VisualizerType::Spectrum
;
456 Config
.visualizer_type
= VisualizerType::Ellipse
;
457 # endif // HAVE_FFTW3_H
460 case VisualizerType::Spectrum
:
461 Config
.visualizer_type
= VisualizerType::Ellipse
;
463 # endif // HAVE_FFTW3_H
464 case VisualizerType::Ellipse
:
465 Config
.visualizer_type
= VisualizerType::Wave
;
468 Statusbar::printf("Visualization type: %1%", Config
.visualizer_type
);
471 void Visualizer::SetFD()
473 if (m_fifo
< 0 && (m_fifo
= open(Config
.visualizer_fifo_path
.c_str(), O_RDONLY
| O_NONBLOCK
)) < 0)
474 Statusbar::printf("Couldn't open \"%1%\" for reading PCM data: %2%",
475 Config
.visualizer_fifo_path
, strerror(errno
)
479 void Visualizer::ResetFD()
484 void Visualizer::FindOutputID()
487 if (!Config
.visualizer_output_name
.empty())
489 for (MPD::OutputIterator out
= Mpd
.GetOutputs(), end
; out
!= end
; ++out
)
491 if (out
->name() == Config
.visualizer_output_name
)
493 m_output_id
= out
->id();
497 if (m_output_id
== -1)
498 Statusbar::printf("There is no output named \"%s\"", Config
.visualizer_output_name
);
502 void Visualizer::ResetAutoScaleMultiplier()
504 m_auto_scale_multiplier
= std::numeric_limits
<double>::infinity();
507 #endif // ENABLE_VISUALIZER