define __STDC_*_MACROS in vlc_fixups.h for pre C++11 hosts
[vlc.git] / modules / visualization / glspectrum.c
1 /*****************************************************************************
2  * glspectrum.c: spectrum visualization module based on OpenGL
3  *****************************************************************************
4  * Copyright © 2009-2013 VLC authors and VideoLAN
5  *
6  * Authors: Adrien Maglo <magsoft@videolan.org>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <assert.h>
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_aout.h>
32 #include <vlc_vout_window.h>
33 #include <vlc_opengl.h>
34 #include <vlc_filter.h>
35 #include <vlc_rand.h>
36
37 #include <GL/gl.h>
38
39 #include <math.h>
40
41 #include "visual/fft.h"
42 #include "visual/window.h"
43
44
45 /*****************************************************************************
46  * Module descriptor
47  *****************************************************************************/
48 static int Open(vlc_object_t *);
49 static void Close(vlc_object_t *);
50
51 #define WIDTH_TEXT N_("Video width")
52 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
53
54 #define HEIGHT_TEXT N_("Video height")
55 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
56
57 vlc_module_begin()
58     set_shortname(N_("glSpectrum"))
59     set_description(N_("3D OpenGL spectrum visualization"))
60     set_capability("visualization", 0)
61     set_category(CAT_AUDIO)
62     set_subcategory(SUBCAT_AUDIO_VISUAL)
63
64     add_integer("glspectrum-width", 400, WIDTH_TEXT, WIDTH_LONGTEXT, false)
65     add_integer("glspectrum-height", 300, HEIGHT_TEXT, HEIGHT_LONGTEXT, false)
66
67     add_shortcut("glspectrum")
68     set_callbacks(Open, Close)
69 vlc_module_end()
70
71
72 /*****************************************************************************
73  * Local prototypes
74  *****************************************************************************/
75 struct filter_sys_t
76 {
77     vlc_thread_t thread;
78
79     /* Audio data */
80     unsigned i_channels;
81     block_fifo_t    *fifo;
82     unsigned i_prev_nb_samples;
83     int16_t *p_prev_s16_buff;
84
85     /* Opengl */
86     vlc_gl_t *gl;
87
88     float f_rotationAngle;
89     float f_rotationIncrement;
90
91     /* FFT window parameters */
92     window_param wind_param;
93 };
94
95
96 static block_t *DoWork(filter_t *, block_t *);
97 static void *Thread(void *);
98
99 #define SPECTRUM_WIDTH 4.f
100 #define NB_BANDS 20
101 #define ROTATION_INCREMENT .1f
102 #define BAR_DECREMENT .075f
103 #define ROTATION_MAX 20
104
105 const GLfloat lightZeroColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
106 const GLfloat lightZeroPosition[] = {0.0f, 3.0f, 10.0f, 0.0f};
107
108 /**
109  * Open the module.
110  * @param p_this: the filter object
111  * @return VLC_SUCCESS or vlc error codes
112  */
113 static int Open(vlc_object_t * p_this)
114 {
115     filter_t *p_filter = (filter_t *)p_this;
116     filter_sys_t *p_sys;
117
118     p_sys = p_filter->p_sys = (filter_sys_t*)malloc(sizeof(*p_sys));
119     if (p_sys == NULL)
120         return VLC_ENOMEM;
121
122     /* Create the object for the thread */
123     p_sys->i_channels = aout_FormatNbChannels(&p_filter->fmt_in.audio);
124     p_sys->i_prev_nb_samples = 0;
125     p_sys->p_prev_s16_buff = NULL;
126
127     p_sys->f_rotationAngle = 0;
128     p_sys->f_rotationIncrement = ROTATION_INCREMENT;
129
130     /* Fetch the FFT window parameters */
131     window_get_param( VLC_OBJECT( p_filter ), &p_sys->wind_param );
132
133     /* Create the FIFO for the audio data. */
134     p_sys->fifo = block_FifoNew();
135     if (p_sys->fifo == NULL)
136         goto error;
137
138     /* Create the openGL provider */
139     vout_window_cfg_t cfg = {
140         .width = var_InheritInteger(p_filter, "glspectrum-width"),
141         .height = var_InheritInteger(p_filter, "glspectrum-height"),
142     };
143
144     p_sys->gl = vlc_gl_surface_Create(p_this, &cfg, NULL);
145     if (p_sys->gl == NULL)
146     {
147         block_FifoRelease(p_sys->fifo);
148         goto error;
149     }
150
151     /* Create the thread */
152     if (vlc_clone(&p_sys->thread, Thread, p_filter,
153                   VLC_THREAD_PRIORITY_VIDEO))
154         goto error;
155
156     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
157     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
158     p_filter->pf_audio_filter = DoWork;
159
160     return VLC_SUCCESS;
161
162 error:
163     free(p_sys);
164     return VLC_EGENERIC;
165 }
166
167
168 /**
169  * Close the module.
170  * @param p_this: the filter object
171  */
172 static void Close(vlc_object_t *p_this)
173 {
174     filter_t *p_filter = (filter_t *)p_this;
175     filter_sys_t *p_sys = p_filter->p_sys;
176
177     /* Terminate the thread. */
178     vlc_cancel(p_sys->thread);
179     vlc_join(p_sys->thread, NULL);
180
181     /* Free the ressources */
182     vlc_gl_surface_Destroy(p_sys->gl);
183     block_FifoRelease(p_sys->fifo);
184     free(p_sys->p_prev_s16_buff);
185     free(p_sys);
186 }
187
188
189 /**
190  * Do the actual work with the new sample.
191  * @param p_filter: filter object
192  * @param p_in_buf: input buffer
193  */
194 static block_t *DoWork(filter_t *p_filter, block_t *p_in_buf)
195 {
196     block_t *block = block_Duplicate(p_in_buf);
197     if (likely(block != NULL))
198         block_FifoPut(p_filter->p_sys->fifo, block);
199     return p_in_buf;
200 }
201
202
203 /**
204   * Init the OpenGL scene.
205   **/
206 static void initOpenGLScene(void)
207 {
208     glEnable(GL_CULL_FACE);
209     glEnable(GL_DEPTH_TEST);
210     glDepthMask(GL_TRUE);
211
212     glMatrixMode(GL_PROJECTION);
213     glFrustum(-1.0f, 1.0f, -1.0f, 1.0f, 0.5f, 10.0f);
214
215     glMatrixMode(GL_MODELVIEW);
216     glTranslatef(0.0, -2.0, -2.0);
217
218     // Init the light.
219     glEnable(GL_LIGHTING);
220
221     glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
222     glEnable(GL_COLOR_MATERIAL);
223
224     glEnable(GL_LIGHT0);
225     glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
226     glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
227
228     glShadeModel(GL_SMOOTH);
229
230     glEnable(GL_BLEND);
231     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
232 }
233
234
235 /**
236  * Draw one bar of the Spectrum.
237  */
238 static void drawBar(void)
239 {
240     const float w = SPECTRUM_WIDTH / NB_BANDS - 0.05f;
241
242     const GLfloat vertexCoords[] = {
243         0.f, 0.f, 0.f,     w, 0.f, 0.f,   0.f, 1.f, 0.f,
244         0.f, 1.f, 0.f,     w, 0.f, 0.f,     w, 1.f, 0.f,
245
246         0.f, 0.f, -w,    0.f, 0.f, 0.f,   0.f, 1.f,  -w,
247         0.f, 1.f, -w,    0.f, 0.f, 0.f,   0.f, 1.f, 0.f,
248
249           w, 0.f, 0.f,     w, 0.f,  -w,     w, 1.f, 0.f,
250           w, 1.f, 0.f,     w, 0.f,  -w,     w, 1.f,  -w,
251
252           w, 0.f,  -w,   0.f, 0.f,  -w,   0.f, 1.f,  -w,
253         0.f, 1.f,  -w,     w, 1.f,  -w,     w, 0.f,  -w,
254
255         0.f, 1.f, 0.f,     w, 1.f, 0.f,     w, 1.f,  -w,
256         0.f, 1.f, 0.f,     w, 1.f,  -w,   0.f, 1.f,  -w,
257     };
258
259     const GLfloat normals[] = {
260         0.f, 0.f, 1.f,   0.f, 0.f, 1.f,   0.f, 0.f, 1.f,
261         0.f, 0.f, 1.f,   0.f, 0.f, 1.f,   0.f, 0.f, 1.f,
262
263         -1.f, 0.f, 0.f,   -1.f, 0.f, 0.f,   -1.f, 0.f, 0.f,
264         -1.f, 0.f, 0.f,   -1.f, 0.f, 0.f,   -1.f, 0.f, 0.f,
265
266         1.f, 0.f, 0.f,   1.f, 0.f, 0.f,   1.f, 0.f, 0.f,
267         1.f, 0.f, 0.f,   1.f, 0.f, 0.f,   1.f, 0.f, 0.f,
268
269         0.f, 0.f, -1.f,   0.f, 0.f, -1.f,   0.f, 0.f, -1.f,
270         0.f, 0.f, -1.f,   0.f, 0.f, -1.f,   0.f, 0.f, -1.f,
271
272         0.f, 1.f, 0.f,   0.f, 1.f, 0.f,   0.f, 1.f, 0.f,
273         0.f, 1.f, 0.f,   0.f, 1.f, 0.f,   0.f, 1.f, 0.f,
274     };
275
276     glVertexPointer(3, GL_FLOAT, 0, vertexCoords);
277     glNormalPointer(GL_FLOAT, 0, normals);
278     glDrawArrays(GL_TRIANGLES, 0, 6 * 5);
279 }
280
281
282 /**
283  * Set the color of one bar of the spectrum.
284  * @param f_height the height of the bar.
285  */
286 static void setBarColor(float f_height)
287 {
288     float r, b;
289
290 #define BAR_MAX_HEIGHT 4.2f
291     r = -1.f + 2 / BAR_MAX_HEIGHT * f_height;
292     b = 2.f - 2 / BAR_MAX_HEIGHT * f_height;
293 #undef BAR_MAX_HEIGHT
294
295     /* Test the ranges. */
296     r = r > 1.f ? 1.f : r;
297     b = b > 1.f ? 1.f : b;
298
299     r = r < 0.f ? 0.f : r;
300     b = b < 0.f ? 0.f : b;
301
302     /* Set the bar color. */
303     glColor4f(r, 0.f, b, 1.f);
304 }
305
306
307 /**
308  * Draw all the bars of the spectrum.
309  * @param heights the heights of all the bars.
310  */
311 static void drawBars(float heights[])
312 {
313     glPushMatrix();
314     glTranslatef(-2.f, 0.f, 0.f);
315
316     glEnableClientState(GL_VERTEX_ARRAY);
317     glEnableClientState(GL_NORMAL_ARRAY);
318
319     float w = SPECTRUM_WIDTH / NB_BANDS;
320     for (unsigned i = 0; i < NB_BANDS; ++i)
321     {
322         glPushMatrix();
323         glScalef(1.f, heights[i], 1.f);
324         setBarColor(heights[i]);
325         drawBar();
326         glPopMatrix();
327
328         glTranslatef(w, 0.f, 0.f);
329     }
330
331     glDisableClientState(GL_VERTEX_ARRAY);
332     glDisableClientState(GL_NORMAL_ARRAY);
333
334     glPopMatrix();
335 }
336
337
338 /**
339  * Update thread which do the rendering
340  * @param p_this: the p_thread object
341  */
342 static void *Thread( void *p_data )
343 {
344     filter_t  *p_filter = (filter_t*)p_data;
345     filter_sys_t *p_sys = p_filter->p_sys;
346     vlc_gl_t *gl = p_sys->gl;
347
348     vlc_gl_MakeCurrent(gl);
349     initOpenGLScene();
350     vlc_gl_ReleaseCurrent(gl);
351
352     float height[NB_BANDS] = {0};
353
354     while (1)
355     {
356         block_t *block = block_FifoGet(p_sys->fifo);
357
358         int canc = vlc_savecancel();
359         unsigned win_width, win_height;
360
361         vlc_gl_MakeCurrent(gl);
362         if (vlc_gl_surface_CheckSize(gl, &win_width, &win_height))
363             glViewport(0, 0, win_width, win_height);
364
365         /* Horizontal scale for 20-band equalizer */
366         const unsigned xscale[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
367                                    36,47,62,82,107,141,184,255};
368
369         fft_state *p_state = NULL; /* internal FFT data */
370         DEFINE_WIND_CONTEXT(wind_ctx); /* internal window data */
371
372         unsigned i, j;
373         float p_output[FFT_BUFFER_SIZE];           /* Raw FFT Result  */
374         int16_t p_buffer1[FFT_BUFFER_SIZE];        /* Buffer on which we perform
375                                                       the FFT (first channel) */
376         int16_t p_dest[FFT_BUFFER_SIZE];           /* Adapted FFT result */
377         float *p_buffl = (float*)block->p_buffer;  /* Original buffer */
378
379         int16_t  *p_buffs;                         /* int16_t converted buffer */
380         int16_t  *p_s16_buff;                      /* int16_t converted buffer */
381
382         if (!block->i_nb_samples) {
383             msg_Err(p_filter, "no samples yet");
384             goto release;
385         }
386
387         /* Allocate the buffer only if the number of samples change */
388         if (block->i_nb_samples != p_sys->i_prev_nb_samples)
389         {
390             free(p_sys->p_prev_s16_buff);
391             p_sys->p_prev_s16_buff = malloc(block->i_nb_samples *
392                                             p_sys->i_channels *
393                                             sizeof(int16_t));
394             if (!p_sys->p_prev_s16_buff)
395                 goto release;
396             p_sys->i_prev_nb_samples = block->i_nb_samples;
397         }
398         p_buffs = p_s16_buff = p_sys->p_prev_s16_buff;
399
400         /* Convert the buffer to int16_t
401            Pasted from float32tos16.c */
402         for (i = block->i_nb_samples * p_sys->i_channels; i--;)
403         {
404             union {float f; int32_t i;} u;
405
406             u.f = *p_buffl + 384.f;
407             if (u.i > 0x43c07fff)
408                 *p_buffs = 32767;
409             else if (u.i < 0x43bf8000)
410                 *p_buffs = -32768;
411             else
412                 *p_buffs = u.i - 0x43c00000;
413
414             p_buffl++; p_buffs++;
415         }
416         p_state = visual_fft_init();
417         if (!p_state)
418         {
419             msg_Err(p_filter,"unable to initialize FFT transform");
420             goto release;
421         }
422         if (!window_init(FFT_BUFFER_SIZE, &p_sys->wind_param, &wind_ctx))
423         {
424             msg_Err(p_filter,"unable to initialize FFT window");
425             goto release;
426         }
427         p_buffs = p_s16_buff;
428         for (i = 0 ; i < FFT_BUFFER_SIZE; i++)
429         {
430             p_output[i] = 0;
431             p_buffer1[i] = *p_buffs;
432
433             p_buffs += p_sys->i_channels;
434             if (p_buffs >= &p_s16_buff[block->i_nb_samples * p_sys->i_channels])
435                 p_buffs = p_s16_buff;
436         }
437         window_scale_in_place (p_buffer1, &wind_ctx);
438         fft_perform (p_buffer1, p_output, p_state);
439
440         for (i = 0; i< FFT_BUFFER_SIZE; ++i)
441             p_dest[i] = p_output[i] *  (2 ^ 16)
442                         / ((FFT_BUFFER_SIZE / 2 * 32768) ^ 2);
443
444         for (i = 0 ; i < NB_BANDS; i++)
445         {
446             /* Decrease the previous size of the bar. */
447             height[i] -= BAR_DECREMENT;
448             if (height[i] < 0)
449                 height[i] = 0;
450
451             int y = 0;
452             /* We search the maximum on one scale
453                to determine the current size of the bar. */
454             for (j = xscale[i]; j < xscale[i + 1]; j++)
455             {
456                 if (p_dest[j] > y)
457                      y = p_dest[j];
458             }
459             /* Calculate the height of the bar */
460             float new_height = y != 0 ? logf(y) * 0.4f : 0;
461             height[i] = new_height > height[i]
462                         ? new_height : height[i];
463         }
464
465         /* Determine the camera rotation angle. */
466         p_sys->f_rotationAngle += p_sys->f_rotationIncrement;
467         if (p_sys->f_rotationAngle <= -ROTATION_MAX)
468             p_sys->f_rotationIncrement = ROTATION_INCREMENT;
469         else if (p_sys->f_rotationAngle >= ROTATION_MAX)
470             p_sys->f_rotationIncrement = -ROTATION_INCREMENT;
471
472         /* Render the frame. */
473         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
474
475         glPushMatrix();
476             glRotatef(p_sys->f_rotationAngle, 0, 1, 0);
477             drawBars(height);
478         glPopMatrix();
479
480         /* Wait to swapp the frame on time. */
481         mwait(block->i_pts + (block->i_length / 2));
482         if (!vlc_gl_Lock(gl))
483         {
484             vlc_gl_Swap(gl);
485             vlc_gl_Unlock(gl);
486         }
487
488 release:
489         window_close(&wind_ctx);
490         fft_close(p_state);
491         vlc_gl_ReleaseCurrent(gl);
492         block_Release(block);
493         vlc_restorecancel(canc);
494     }
495
496     vlc_assert_unreachable();
497 }