9308c573f6312784e644a1937610ad8e8426db06
[platform/upstream/gstreamer.git] / gst / audiovisualizers / gstsynaescope.c
1 /* GStreamer
2  * Copyright (C) <2011> Stefan Kost <ensonic@users.sf.net>
3  *
4  * gstsynaescope.c: frequency spectrum scope
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 /**
21  * SECTION:element-synaescope
22  * @see_also: goom
23  *
24  * Synaescope is an audio visualisation element. It analyzes frequencies and
25  * out-of phase properties of audio and draws this as clouds of stars.
26  *
27  * <refsect2>
28  * <title>Example launch line</title>
29  * |[
30  * gst-launch audiotestsrc ! audioconvert ! synaescope ! ximagesink
31  * ]|
32  * </refsect2>
33  */
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 #include "gstsynaescope.h"
39
40 static GstStaticPadTemplate gst_synae_scope_src_template =
41 GST_STATIC_PAD_TEMPLATE ("src",
42     GST_PAD_SRC,
43     GST_PAD_ALWAYS,
44 #if G_BYTE_ORDER == G_BIG_ENDIAN
45     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("xRGB"))
46 #else
47     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGRx"))
48 #endif
49     );
50
51 static GstStaticPadTemplate gst_synae_scope_sink_template =
52 GST_STATIC_PAD_TEMPLATE ("sink",
53     GST_PAD_SINK,
54     GST_PAD_ALWAYS,
55     GST_STATIC_CAPS ("audio/x-raw, "
56         "format = (string) " GST_AUDIO_NE (S16) ", "
57         "layout = (string) interleaved, "
58         "rate = (int) [ 8000, 96000 ], "
59         "channels = (int) 2, " "channel-mask = (bitmask) 0x3")
60     );
61
62
63 GST_DEBUG_CATEGORY_STATIC (synae_scope_debug);
64 #define GST_CAT_DEFAULT synae_scope_debug
65
66 static void gst_synae_scope_finalize (GObject * object);
67
68 static gboolean gst_synae_scope_setup (GstAudioVisualizer * scope);
69 static gboolean gst_synae_scope_render (GstAudioVisualizer * scope,
70     GstBuffer * audio, GstBuffer * video);
71
72
73 G_DEFINE_TYPE (GstSynaeScope, gst_synae_scope, GST_TYPE_AUDIO_VISUALIZER);
74
75 static void
76 gst_synae_scope_class_init (GstSynaeScopeClass * g_class)
77 {
78   GObjectClass *gobject_class = (GObjectClass *) g_class;
79   GstElementClass *element_class = (GstElementClass *) g_class;
80   GstAudioVisualizerClass *scope_class = (GstAudioVisualizerClass *) g_class;
81
82   gobject_class->finalize = gst_synae_scope_finalize;
83
84   gst_element_class_set_details_simple (element_class, "Synaescope",
85       "Visualization",
86       "Creates video visualizations of audio input, using stereo and pitch information",
87       "Stefan Kost <ensonic@users.sf.net>");
88
89   gst_element_class_add_pad_template (element_class,
90       gst_static_pad_template_get (&gst_synae_scope_src_template));
91   gst_element_class_add_pad_template (element_class,
92       gst_static_pad_template_get (&gst_synae_scope_sink_template));
93
94   scope_class->setup = GST_DEBUG_FUNCPTR (gst_synae_scope_setup);
95   scope_class->render = GST_DEBUG_FUNCPTR (gst_synae_scope_render);
96 }
97
98 static void
99 gst_synae_scope_init (GstSynaeScope * scope)
100 {
101   guint32 *colors = scope->colors;
102   guint *shade = scope->shade;
103   guint i, r, g, b;
104
105 #define BOUND(x) ((x) > 255 ? 255 : (x))
106 #define PEAKIFY(x) BOUND((x) - (x)*(255-(x))/255/2)
107
108   for (i = 0; i < 256; i++) {
109     r = PEAKIFY ((i & 15 * 16));
110     g = PEAKIFY ((i & 15) * 16 + (i & 15 * 16) / 4);
111     b = PEAKIFY ((i & 15) * 16);
112
113     colors[i] = (r << 16) | (g << 8) | b;
114   }
115 #undef BOUND
116 #undef PEAKIFY
117
118   for (i = 0; i < 256; i++)
119     shade[i] = i * 200 >> 8;
120 }
121
122 static void
123 gst_synae_scope_finalize (GObject * object)
124 {
125   GstSynaeScope *scope = GST_SYNAE_SCOPE (object);
126
127   if (scope->fft_ctx) {
128     gst_fft_s16_free (scope->fft_ctx);
129     scope->fft_ctx = NULL;
130   }
131   if (scope->freq_data_l) {
132     g_free (scope->freq_data_l);
133     scope->freq_data_l = NULL;
134   }
135   if (scope->freq_data_r) {
136     g_free (scope->freq_data_r);
137     scope->freq_data_r = NULL;
138   }
139   if (scope->adata_l) {
140     g_free (scope->adata_l);
141     scope->adata_l = NULL;
142   }
143   if (scope->adata_r) {
144     g_free (scope->adata_r);
145     scope->adata_r = NULL;
146   }
147
148   G_OBJECT_CLASS (gst_synae_scope_parent_class)->finalize (object);
149 }
150
151 static gboolean
152 gst_synae_scope_setup (GstAudioVisualizer * bscope)
153 {
154   GstSynaeScope *scope = GST_SYNAE_SCOPE (bscope);
155   guint num_freq = bscope->height + 1;
156
157   if (scope->fft_ctx)
158     gst_fft_s16_free (scope->fft_ctx);
159   g_free (scope->freq_data_l);
160   g_free (scope->freq_data_r);
161   g_free (scope->adata_l);
162   g_free (scope->adata_r);
163
164   /* FIXME: we could have horizontal or vertical layout */
165
166   /* we'd need this amount of samples per render() call */
167   bscope->req_spf = num_freq * 2 - 2;
168   scope->fft_ctx = gst_fft_s16_new (bscope->req_spf, FALSE);
169   scope->freq_data_l = g_new (GstFFTS16Complex, num_freq);
170   scope->freq_data_r = g_new (GstFFTS16Complex, num_freq);
171
172   scope->adata_l = g_new (gint16, bscope->req_spf);
173   scope->adata_r = g_new (gint16, bscope->req_spf);
174
175   return TRUE;
176 }
177
178 static inline void
179 add_pixel (guint32 * _p, guint32 _c)
180 {
181   guint8 *p = (guint8 *) _p;
182   guint8 *c = (guint8 *) & _c;
183
184   if (p[0] < 255 - c[0])
185     p[0] += c[0];
186   else
187     p[0] = 255;
188   if (p[1] < 255 - c[1])
189     p[1] += c[1];
190   else
191     p[1] = 255;
192   if (p[2] < 255 - c[2])
193     p[2] += c[2];
194   else
195     p[2] = 255;
196   if (p[3] < 255 - c[3])
197     p[3] += c[3];
198   else
199     p[3] = 255;
200 }
201
202 static gboolean
203 gst_synae_scope_render (GstAudioVisualizer * bscope, GstBuffer * audio,
204     GstBuffer * video)
205 {
206   GstSynaeScope *scope = GST_SYNAE_SCOPE (bscope);
207   GstMapInfo amap, vmap;
208   guint32 *vdata;
209   gint16 *adata;
210   gint16 *adata_l = scope->adata_l;
211   gint16 *adata_r = scope->adata_r;
212   GstFFTS16Complex *fdata_l = scope->freq_data_l;
213   GstFFTS16Complex *fdata_r = scope->freq_data_r;
214   gint x, y;
215   guint off;
216   guint w = bscope->width;
217   guint h = bscope->height;
218   guint32 *colors = scope->colors, c;
219   guint *shade = scope->shade;
220   //guint w2 = w /2;
221   guint ch = GST_AUDIO_INFO_CHANNELS (&bscope->ainfo);
222   guint num_samples;
223   gint i, j, b;
224   gint br, br1, br2;
225   gint clarity;
226   gdouble fc, r, l, rr, ll;
227   gdouble frl, fil, frr, fir;
228   const guint sl = 30;
229
230   gst_buffer_map (video, &vmap, GST_MAP_WRITE);
231   gst_buffer_map (audio, &amap, GST_MAP_READ);
232
233   vdata = (guint32 *) vmap.data;
234   adata = (gint16 *) amap.data;
235
236   num_samples = amap.size / (ch * sizeof (gint16));
237
238   /* deinterleave */
239   for (i = 0, j = 0; i < num_samples; i++) {
240     adata_l[i] = adata[j++];
241     adata_r[i] = adata[j++];
242   }
243
244   /* run fft */
245   /*gst_fft_s16_window (scope->fft_ctx, adata_l, GST_FFT_WINDOW_HAMMING); */
246   gst_fft_s16_fft (scope->fft_ctx, adata_l, fdata_l);
247   /*gst_fft_s16_window (scope->fft_ctx, adata_r, GST_FFT_WINDOW_HAMMING); */
248   gst_fft_s16_fft (scope->fft_ctx, adata_r, fdata_r);
249
250   /* draw stars */
251   for (y = 0; y < h; y++) {
252     b = h - y;
253     frl = (gdouble) fdata_l[b].r;
254     fil = (gdouble) fdata_l[b].i;
255     frr = (gdouble) fdata_r[b].r;
256     fir = (gdouble) fdata_r[b].i;
257
258     ll = (frl + fil) * (frl + fil) + (frr - fir) * (frr - fir);
259     l = sqrt (ll);
260     rr = (frl - fil) * (frl - fil) + (frr + fir) * (frr + fir);
261     r = sqrt (rr);
262     /* out-of-phase'ness for this frequency component */
263     clarity = (gint) (
264         ((frl + fil) * (frl - fil) + (frr + fir) * (frr - fir)) /
265         (ll + rr) * 256);
266     fc = r + l;
267
268     x = (guint) (r * w / fc);
269     /* the brighness scaling factor was picked by experimenting */
270     br = b * fc * 0.01;
271
272     br1 = br * (clarity + 128) >> 8;
273     br2 = br * (128 - clarity) >> 8;
274     br1 = CLAMP (br1, 0, 255);
275     br2 = CLAMP (br2, 0, 255);
276
277     GST_DEBUG ("y %3d fc %10.6f clarity %d br %d br1 %d br2 %d", y, fc, clarity,
278         br, br1, br2);
279
280     /* draw a star */
281     off = (y * w) + x;
282     c = colors[(br1 >> 4) | (br2 & 0xf0)];
283     add_pixel (&vdata[off], c);
284     if ((x > (sl - 1)) && (x < (w - sl)) && (y > (sl - 1)) && (y < (h - sl))) {
285       for (i = 1; br1 || br2; i++, br1 = shade[br1], br2 = shade[br2]) {
286         c = colors[(br1 >> 4) + (br2 & 0xf0)];
287         add_pixel (&vdata[off - i], c);
288         add_pixel (&vdata[off + i], c);
289         add_pixel (&vdata[off - i * w], c);
290         add_pixel (&vdata[off + i * w], c);
291       }
292     } else {
293       for (i = 1; br1 || br2; i++, br1 = shade[br1], br2 = shade[br2]) {
294         c = colors[(br1 >> 4) | (br2 & 0xf0)];
295         if (x - i > 0)
296           add_pixel (&vdata[off - i], c);
297         if (x + i < (w - 1))
298           add_pixel (&vdata[off + i], c);
299         if (y - i > 0)
300           add_pixel (&vdata[off - i * w], c);
301         if (y + i < (h - 1))
302           add_pixel (&vdata[off + i * w], c);
303       }
304     }
305   }
306   gst_buffer_unmap (video, &vmap);
307   gst_buffer_unmap (audio, &amap);
308
309   return TRUE;
310 }
311
312 gboolean
313 gst_synae_scope_plugin_init (GstPlugin * plugin)
314 {
315   GST_DEBUG_CATEGORY_INIT (synae_scope_debug, "synaescope", 0, "synaescope");
316
317   return gst_element_register (plugin, "synaescope", GST_RANK_NONE,
318       GST_TYPE_SYNAE_SCOPE);
319 }