Make 'update_environment' action also update local mpd status
[ncmpcpp.git] / src / screens / visualizer.cpp
blobaddb0cf7f2ae83fbea9e7e41b603f35aa165c02c
1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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>
27 #include <cerrno>
28 #include <cmath>
29 #include <cstring>
30 #include <fstream>
31 #include <limits>
32 #include <fcntl.h>
34 #include "global.h"
35 #include "settings.h"
36 #include "status.h"
37 #include "statusbar.h"
38 #include "title.h"
39 #include "screens/screen_switcher.h"
40 #include "status.h"
41 #include "enums.h"
43 using Samples = std::vector<int16_t>;
45 using Global::MainStartY;
46 using Global::MainHeight;
48 Visualizer *myVisualizer;
50 namespace {
52 const int fps = 25;
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()))
71 ResetFD();
72 m_samples = 44100/fps;
73 if (Config.visualizer_in_stereo)
74 m_samples *= 2;
75 # ifdef HAVE_FFTW3_H
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);
87 w.clear();
88 SetFD();
89 m_timer = boost::posix_time::from_time_t(0);
90 drawHeader();
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);
99 hasToBeResized = 0;
102 std::wstring Visualizer::title()
104 return L"Music visualizer";
107 void Visualizer::update()
109 if (m_fifo < 0)
110 return;
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
118 return;
120 if (m_output_id != -1 && Global::Timer - m_timer > Config.visualizer_sync_interval)
122 Mpd.DisableOutput(m_output_id);
123 usleep(50000);
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);
130 # ifdef HAVE_FFTW3_H
131 if (Config.visualizer_type == VisualizerType::Spectrum)
133 draw = &Visualizer::DrawFrequencySpectrum;
134 drawStereo = &Visualizer::DrawFrequencySpectrumStereo;
136 else
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;
148 else
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();
159 scale /= sample;
160 scale = fabs(scale);
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();
173 else
174 sample = tmp;
177 w.clear();
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);
191 else
193 (this->*draw)(samples.data(), samples_read, 0, w.getHeight());
195 w.refresh();
198 int Visualizer::windowTimeout()
200 if (m_fifo >= 0 && Status::State::player() == MPD::psPlay)
201 return 1000/fps;
202 else
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)
217 return;
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)
222 << c
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)
230 point_y = 0;
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);
250 else
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
271 // channel.
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)
281 return;
283 int32_t point_y;
284 for (size_t x = 0; x < win_width; ++x)
286 point_y = 0;
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;
299 w << NC::XY(x, y)
300 << c
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;
324 int32_t x, y;
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]);
335 radius /= 32768.0;
337 // Appropriately scale the position.
338 x *= radius;
339 y *= radius;
341 auto c = toColor(sqrt(x*x + y*y), max_radius, false);
342 w << NC::XY(half_width + x, half_height + y)
343 << c
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();
369 int32_t x, y;
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
379 // circle.
380 auto c = toColor(sqrt(x*x + 4*y*y), radius);
381 w << NC::XY(left_half_width + x, top_half_height + y)
382 << c
383 << Config.visualizer_chars[1]
384 << NC::FormattedColor::End(c);
388 /**********************************************************************/
390 #ifdef HAVE_FFTW3_H
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]
406 )/2e4*height;
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;
411 double bar_height;
412 size_t bar_bound_height;
413 for (size_t x = 0; x < win_width; ++x)
415 bar_height = 0;
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);
428 w << NC::XY(x, y)
429 << c
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;
451 break;
452 case VisualizerType::WaveFilled:
453 # ifdef HAVE_FFTW3_H
454 Config.visualizer_type = VisualizerType::Spectrum;
455 # else
456 Config.visualizer_type = VisualizerType::Ellipse;
457 # endif // HAVE_FFTW3_H
458 break;
459 # ifdef HAVE_FFTW3_H
460 case VisualizerType::Spectrum:
461 Config.visualizer_type = VisualizerType::Ellipse;
462 break;
463 # endif // HAVE_FFTW3_H
464 case VisualizerType::Ellipse:
465 Config.visualizer_type = VisualizerType::Wave;
466 break;
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()
481 m_fifo = -1;
484 void Visualizer::FindOutputID()
486 m_output_id = -1;
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();
494 break;
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