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