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
35 #include "statusbar.h"
37 #include "screen_switcher.h"
40 using Global::MainStartY
;
41 using Global::MainHeight
;
43 Visualizer
*myVisualizer
;
51 Visualizer::Visualizer()
52 : Screen(NC::Window(0, MainStartY
, COLS
, MainHeight
, "", Config
.visualizer_color
, NC::Border::None
))
55 m_samples
= 44100/fps
;
56 if (Config
.visualizer_in_stereo
)
59 m_fftw_results
= m_samples
/2+1;
60 m_freq_magnitudes
= new double[m_fftw_results
];
61 m_fftw_input
= static_cast<double *>(fftw_malloc(sizeof(double)*m_samples
));
62 m_fftw_output
= static_cast<fftw_complex
*>(fftw_malloc(sizeof(fftw_complex
)*m_fftw_results
));
63 m_fftw_plan
= fftw_plan_dft_r2c_1d(m_samples
, m_fftw_input
, m_fftw_output
, FFTW_ESTIMATE
);
64 # endif // HAVE_FFTW3_H
67 void Visualizer::switchTo()
69 SwitchTo::execute(this);
72 m_timer
= boost::posix_time::from_time_t(0);
76 void Visualizer::resize()
78 size_t x_offset
, width
;
79 getWindowResizeParams(x_offset
, width
);
80 w
.resize(width
, MainHeight
);
81 w
.moveTo(x_offset
, MainStartY
);
85 std::wstring
Visualizer::title()
87 return L
"Music visualizer";
90 void Visualizer::update()
95 // PCM in format 44100:16:1 (for mono visualization) and 44100:16:2 (for stereo visualization) is supported
96 int16_t buf
[m_samples
];
97 ssize_t data
= read(m_fifo
, buf
, sizeof(buf
));
98 if (data
< 0) // no data available in fifo
101 if (m_output_id
!= -1 && Global::Timer
- m_timer
> Config
.visualizer_sync_interval
)
103 Mpd
.DisableOutput(m_output_id
);
105 Mpd
.EnableOutput(m_output_id
);
106 m_timer
= Global::Timer
;
109 void (Visualizer::*draw
)(int16_t *, ssize_t
, size_t, size_t);
111 if (!Config
.visualizer_use_wave
)
112 draw
= &Visualizer::DrawFrequencySpectrum
;
114 # endif // HAVE_FFTW3_H
115 draw
= &Visualizer::DrawSoundWave
;
117 const ssize_t samples_read
= data
/sizeof(int16_t);
118 std::for_each(buf
, buf
+samples_read
, [](int16_t &sample
) {
119 int32_t tmp
= sample
* Config
.visualizer_sample_multiplier
;
120 if (tmp
< std::numeric_limits
<int16_t>::min())
121 sample
= std::numeric_limits
<int16_t>::min();
122 else if (tmp
> std::numeric_limits
<int16_t>::max())
123 sample
= std::numeric_limits
<int16_t>::max();
129 if (Config
.visualizer_in_stereo
)
131 int16_t buf_left
[samples_read
/2], buf_right
[samples_read
/2];
132 for (ssize_t i
= 0, j
= 0; i
< samples_read
; i
+= 2, ++j
)
134 buf_left
[j
] = buf
[i
];
135 buf_right
[j
] = buf
[i
+1];
137 size_t half_height
= MainHeight
/2;
138 (this->*draw
)(buf_left
, samples_read
/2, 0, half_height
);
139 (this->*draw
)(buf_right
, samples_read
/2, half_height
+(draw
== &Visualizer::DrawSoundWave
? 1 : 0), half_height
+(draw
!= &Visualizer::DrawSoundWave
? 1 : 0));
142 (this->*draw
)(buf
, samples_read
, 0, MainHeight
);
146 int Visualizer::windowTimeout()
148 if (m_fifo
>= 0 && Status::State::player() == MPD::psPlay
)
151 return Screen
<WindowType
>::windowTimeout();
154 void Visualizer::spacePressed()
157 Config
.visualizer_use_wave
= !Config
.visualizer_use_wave
;
158 Statusbar::printf("Visualization type: %1%",
159 Config
.visualizer_use_wave
? "sound wave" : "frequency spectrum"
161 # endif // HAVE_FFTW3_H
164 void Visualizer::DrawSoundWave(int16_t *buf
, ssize_t samples
, size_t y_offset
, size_t height
)
166 const int samples_per_col
= samples
/w
.getWidth();
167 const int half_height
= height
/2;
168 double prev_point_pos
= 0;
169 const size_t win_width
= w
.getWidth();
170 for (size_t i
= 0; i
< win_width
; ++i
)
172 double point_pos
= 0;
173 for (int j
= 0; j
< samples_per_col
; ++j
)
174 point_pos
+= buf
[i
*samples_per_col
+j
];
175 point_pos
/= samples_per_col
;
176 point_pos
/= std::numeric_limits
<int16_t>::max();
177 point_pos
*= half_height
;
178 w
<< NC::XY(i
, y_offset
+half_height
+point_pos
) << Config
.visualizer_chars
[0];
179 if (i
&& abs(prev_point_pos
-point_pos
) > 2)
181 // if gap is too big. intermediate values are needed
182 // since without them all we see are blinking points
183 const int breakpoint
= std::max(prev_point_pos
, point_pos
);
184 const int half
= (prev_point_pos
+point_pos
)/2;
185 for (int k
= std::min(prev_point_pos
, point_pos
)+1; k
< breakpoint
; k
+= 2)
186 w
<< NC::XY(i
-(k
< half
), y_offset
+half_height
+k
) << Config
.visualizer_chars
[0];
188 prev_point_pos
= point_pos
;
193 void Visualizer::DrawFrequencySpectrum(int16_t *buf
, ssize_t samples
, size_t y_offset
, size_t height
)
195 for (unsigned i
= 0, j
= 0; i
< m_samples
; ++i
)
198 m_fftw_input
[i
] = buf
[j
++];
203 fftw_execute(m_fftw_plan
);
205 // count magnitude of each frequency and scale it to fit the screen
206 for (unsigned i
= 0; i
< m_fftw_results
; ++i
)
207 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])/1e5
*height
;
209 const size_t win_width
= w
.getWidth();
210 // cut bandwidth a little to achieve better look
211 const int freqs_per_col
= m_fftw_results
/win_width
* 7/10;
213 size_t bar_real_height
;
214 for (size_t i
= 0; i
< win_width
; ++i
)
217 for (int j
= 0; j
< freqs_per_col
; ++j
)
218 bar_height
+= m_freq_magnitudes
[i
*freqs_per_col
+j
];
219 // buff higher frequencies
220 bar_height
*= log2(2 + i
);
221 // moderately normalize the heights
222 bar_height
= pow(bar_height
, 0.6);
223 bar_real_height
= std::min(size_t(bar_height
/freqs_per_col
), height
);
224 const size_t start_y
= y_offset
> 0 ? y_offset
: height
-bar_real_height
;
225 const size_t stop_y
= std::min(bar_real_height
+start_y
, w
.getHeight());
226 for (size_t j
= start_y
; j
< stop_y
; ++j
)
227 w
<< NC::XY(i
, j
) << Config
.visualizer_chars
[1];
230 #endif // HAVE_FFTW3_H
232 void Visualizer::SetFD()
234 if (m_fifo
< 0 && (m_fifo
= open(Config
.visualizer_fifo_path
.c_str(), O_RDONLY
| O_NONBLOCK
)) < 0)
235 Statusbar::printf("Couldn't open \"%1%\" for reading PCM data: %2%",
236 Config
.visualizer_fifo_path
, strerror(errno
)
240 void Visualizer::ResetFD()
245 void Visualizer::FindOutputID()
248 if (!Config
.visualizer_output_name
.empty())
251 Mpd
.GetOutputs([this, &idx
](MPD::Output output
) {
252 if (output
.name() == Config
.visualizer_output_name
)
256 if (m_output_id
== -1)
257 Statusbar::printf("There is no output named \"%s\"", Config
.visualizer_output_name
);
261 #endif // ENABLE_VISUALIZER