actions: make JumpToPlayingSong work also if player is stopped
[ncmpcpp.git] / src / visualizer.cpp
blob1621ddcac651330698df63f909bbbb5fcb3bb1bb
1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 "visualizer.h"
23 #ifdef ENABLE_VISUALIZER
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <cerrno>
27 #include <cmath>
28 #include <cstring>
29 #include <fstream>
30 #include <limits>
31 #include <fcntl.h>
33 #include "global.h"
34 #include "settings.h"
35 #include "status.h"
36 #include "statusbar.h"
37 #include "title.h"
38 #include "screen_switcher.h"
39 #include "status.h"
41 using Global::MainStartY;
42 using Global::MainHeight;
44 Visualizer *myVisualizer;
46 namespace {
48 const int fps = 25;
52 Visualizer::Visualizer()
53 : Screen(NC::Window(0, MainStartY, COLS, MainHeight, "", Config.visualizer_color, NC::Border::None))
55 ResetFD();
56 m_samples = 44100/fps;
57 if (Config.visualizer_in_stereo)
58 m_samples *= 2;
59 # ifdef HAVE_FFTW3_H
60 m_fftw_results = m_samples/2+1;
61 m_freq_magnitudes = new double[m_fftw_results];
62 m_fftw_input = static_cast<double *>(fftw_malloc(sizeof(double)*m_samples));
63 m_fftw_output = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*m_fftw_results));
64 m_fftw_plan = fftw_plan_dft_r2c_1d(m_samples, m_fftw_input, m_fftw_output, FFTW_ESTIMATE);
65 # endif // HAVE_FFTW3_H
68 void Visualizer::switchTo()
70 SwitchTo::execute(this);
71 w.clear();
72 SetFD();
73 m_timer = boost::posix_time::from_time_t(0);
74 drawHeader();
77 void Visualizer::resize()
79 size_t x_offset, width;
80 getWindowResizeParams(x_offset, width);
81 w.resize(width, MainHeight);
82 w.moveTo(x_offset, MainStartY);
83 hasToBeResized = 0;
86 std::wstring Visualizer::title()
88 return L"Music visualizer";
91 void Visualizer::update()
93 if (m_fifo < 0)
94 return;
96 // PCM in format 44100:16:1 (for mono visualization) and 44100:16:2 (for stereo visualization) is supported
97 int16_t buf[m_samples];
98 ssize_t data = read(m_fifo, buf, sizeof(buf));
99 if (data < 0) // no data available in fifo
100 return;
102 if (m_output_id != -1 && Global::Timer - m_timer > Config.visualizer_sync_interval)
104 Mpd.DisableOutput(m_output_id);
105 usleep(50000);
106 Mpd.EnableOutput(m_output_id);
107 m_timer = Global::Timer;
110 void (Visualizer::*draw)(int16_t *, ssize_t, size_t, size_t);
111 # ifdef HAVE_FFTW3_H
112 if (!Config.visualizer_use_wave)
113 draw = &Visualizer::DrawFrequencySpectrum;
114 else
115 # endif // HAVE_FFTW3_H
116 draw = &Visualizer::DrawSoundWave;
118 const ssize_t samples_read = data/sizeof(int16_t);
119 std::for_each(buf, buf+samples_read, [](int16_t &sample) {
120 int32_t tmp = sample * Config.visualizer_sample_multiplier;
121 if (tmp < std::numeric_limits<int16_t>::min())
122 sample = std::numeric_limits<int16_t>::min();
123 else if (tmp > std::numeric_limits<int16_t>::max())
124 sample = std::numeric_limits<int16_t>::max();
125 else
126 sample = tmp;
129 w.clear();
130 if (Config.visualizer_in_stereo)
132 int16_t buf_left[samples_read/2], buf_right[samples_read/2];
133 for (ssize_t i = 0, j = 0; i < samples_read; i += 2, ++j)
135 buf_left[j] = buf[i];
136 buf_right[j] = buf[i+1];
138 size_t half_height = MainHeight/2;
139 (this->*draw)(buf_left, samples_read/2, 0, half_height);
140 (this->*draw)(buf_right, samples_read/2, half_height+(draw == &Visualizer::DrawSoundWave ? 1 : 0), half_height+(draw != &Visualizer::DrawSoundWave ? 1 : 0));
142 else
143 (this->*draw)(buf, samples_read, 0, MainHeight);
144 w.refresh();
147 int Visualizer::windowTimeout()
149 if (m_fifo >= 0 && Status::State::player() == MPD::psPlay)
150 return 1000/fps;
151 else
152 return Screen<WindowType>::windowTimeout();
155 void Visualizer::spacePressed()
157 # ifdef HAVE_FFTW3_H
158 Config.visualizer_use_wave = !Config.visualizer_use_wave;
159 Statusbar::printf("Visualization type: %1%",
160 Config.visualizer_use_wave ? "sound wave" : "frequency spectrum"
162 # endif // HAVE_FFTW3_H
165 void Visualizer::DrawSoundWave(int16_t *buf, ssize_t samples, size_t y_offset, size_t height)
167 const int samples_per_col = samples/w.getWidth();
168 const int half_height = height/2;
169 double prev_point_pos = 0;
170 const size_t win_width = w.getWidth();
171 for (size_t i = 0; i < win_width; ++i)
173 double point_pos = 0;
174 for (int j = 0; j < samples_per_col; ++j)
175 point_pos += buf[i*samples_per_col+j];
176 point_pos /= samples_per_col;
177 point_pos /= std::numeric_limits<int16_t>::max();
178 point_pos *= half_height;
179 w << NC::XY(i, y_offset+half_height+point_pos) << Config.visualizer_chars[0];
180 if (i && abs(prev_point_pos-point_pos) > 2)
182 // if gap is too big. intermediate values are needed
183 // since without them all we see are blinking points
184 const int breakpoint = std::max(prev_point_pos, point_pos);
185 const int half = (prev_point_pos+point_pos)/2;
186 for (int k = std::min(prev_point_pos, point_pos)+1; k < breakpoint; k += 2)
187 w << NC::XY(i-(k < half), y_offset+half_height+k) << Config.visualizer_chars[0];
189 prev_point_pos = point_pos;
193 #ifdef HAVE_FFTW3_H
194 void Visualizer::DrawFrequencySpectrum(int16_t *buf, ssize_t samples, size_t y_offset, size_t height)
196 for (unsigned i = 0, j = 0; i < m_samples; ++i)
198 if (j < samples)
199 m_fftw_input[i] = buf[j++];
200 else
201 m_fftw_input[i] = 0;
204 fftw_execute(m_fftw_plan);
206 // count magnitude of each frequency and scale it to fit the screen
207 for (unsigned i = 0; i < m_fftw_results; ++i)
208 m_freq_magnitudes[i] = sqrt(m_fftw_output[i][0]*m_fftw_output[i][0] + m_fftw_output[i][1]*m_fftw_output[i][1])/2e4*height;
210 const size_t win_width = w.getWidth();
211 // cut bandwidth a little to achieve better look
212 const int freqs_per_col = m_fftw_results/win_width * 7/10;
213 double bar_height;
214 size_t bar_real_height;
215 for (size_t i = 0; i < win_width; ++i)
217 bar_height = 0;
218 for (int j = 0; j < freqs_per_col; ++j)
219 bar_height += m_freq_magnitudes[i*freqs_per_col+j];
220 // buff higher frequencies
221 bar_height *= log2(2 + i);
222 // moderately normalize the heights
223 bar_height = pow(bar_height, 0.5);
224 bar_real_height = std::min(size_t(bar_height/freqs_per_col), height);
225 const size_t start_y = y_offset > 0 ? y_offset : height-bar_real_height;
226 const size_t stop_y = std::min(bar_real_height+start_y, w.getHeight());
227 for (size_t j = start_y; j < stop_y; ++j)
228 w << NC::XY(i, j) << Config.visualizer_chars[1];
231 #endif // HAVE_FFTW3_H
233 void Visualizer::SetFD()
235 if (m_fifo < 0 && (m_fifo = open(Config.visualizer_fifo_path.c_str(), O_RDONLY | O_NONBLOCK)) < 0)
236 Statusbar::printf("Couldn't open \"%1%\" for reading PCM data: %2%",
237 Config.visualizer_fifo_path, strerror(errno)
241 void Visualizer::ResetFD()
243 m_fifo = -1;
246 void Visualizer::FindOutputID()
248 m_output_id = -1;
249 if (!Config.visualizer_output_name.empty())
251 size_t idx = 0;
252 Mpd.GetOutputs([this, &idx](MPD::Output output) {
253 if (output.name() == Config.visualizer_output_name)
254 m_output_id = idx;
255 ++idx;
257 if (m_output_id == -1)
258 Statusbar::printf("There is no output named \"%s\"", Config.visualizer_output_name);
262 #endif // ENABLE_VISUALIZER