1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 "visualizer.h"
23 #ifdef ENABLE_VISUALIZER
25 #include <boost/date_time/posix_time/posix_time.hpp>
36 #include "statusbar.h"
38 #include "screen_switcher.h"
41 using Global::MainStartY
;
42 using Global::MainHeight
;
44 Visualizer
*myVisualizer
;
52 Visualizer::Visualizer()
53 : Screen(NC::Window(0, MainStartY
, COLS
, MainHeight
, "", Config
.visualizer_color
, NC::Border::None
))
56 m_samples
= 44100/fps
;
57 if (Config
.visualizer_in_stereo
)
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);
73 m_timer
= boost::posix_time::from_time_t(0);
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
);
86 std::wstring
Visualizer::title()
88 return L
"Music visualizer";
91 void Visualizer::update()
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
102 if (m_output_id
!= -1 && Global::Timer
- m_timer
> Config
.visualizer_sync_interval
)
104 Mpd
.DisableOutput(m_output_id
);
106 Mpd
.EnableOutput(m_output_id
);
107 m_timer
= Global::Timer
;
110 void (Visualizer::*draw
)(int16_t *, ssize_t
, size_t, size_t);
112 if (!Config
.visualizer_use_wave
)
113 draw
= &Visualizer::DrawFrequencySpectrum
;
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();
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));
143 (this->*draw
)(buf
, samples_read
, 0, MainHeight
);
147 int Visualizer::windowTimeout()
149 if (m_fifo
>= 0 && Status::State::player() == MPD::psPlay
)
152 return Screen
<WindowType
>::windowTimeout();
155 void Visualizer::spacePressed()
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
;
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
)
199 m_fftw_input
[i
] = buf
[j
++];
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;
214 size_t bar_real_height
;
215 for (size_t i
= 0; i
< win_width
; ++i
)
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()
246 void Visualizer::FindOutputID()
249 if (!Config
.visualizer_output_name
.empty())
252 Mpd
.GetOutputs([this, &idx
](MPD::Output output
) {
253 if (output
.name() == Config
.visualizer_output_name
)
257 if (m_output_id
== -1)
258 Statusbar::printf("There is no output named \"%s\"", Config
.visualizer_output_name
);
262 #endif // ENABLE_VISUALIZER