1 /*
2  * GStreamer
3  * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
4  * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-audioinvert
24  *
25  * Swaps upper and lower half of audio samples. Mixing an inverted sample on top of
26  * the original with a slight delay can produce effects that sound like resonance.
27  * Creating a stereo sample from a mono source, with one channel inverted produces wide-stereo sounds.
28  *
29  * <refsect2>
30  * <title>Example launch line</title>
31  * |[
32  * gst-launch-1.0 audiotestsrc wave=saw ! audioinvert degree=0.4 ! alsasink
33  * gst-launch-1.0 filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audioinvert degree=0.4 ! alsasink
34  * gst-launch-1.0 audiotestsrc wave=saw ! audioconvert ! audioinvert degree=0.4 ! audioconvert ! alsasink
35  * ]|
36  * </refsect2>
37  */
38 
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42 
43 #include <gst/gst.h>
44 #include <gst/base/gstbasetransform.h>
45 #include <gst/audio/audio.h>
46 #include <gst/audio/gstaudiofilter.h>
47 
48 #include "audioinvert.h"
49 
50 #define GST_CAT_DEFAULT gst_audio_invert_debug
51 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
52 
53 /* Filter signals and args */
54 enum
55 {
56   /* FILL ME */
57   LAST_SIGNAL
58 };
59 
60 enum
61 {
62   PROP_0,
63   PROP_DEGREE
64 };
65 
66 #define ALLOWED_CAPS \
67     "audio/x-raw,"                                                     \
68     " format=(string) {"GST_AUDIO_NE(S16)","GST_AUDIO_NE(F32)"},"      \
69     " rate=(int)[1,MAX],"                                              \
70     " channels=(int)[1,MAX],"                                          \
71     " layout=(string) {interleaved, non-interleaved}"
72 
73 G_DEFINE_TYPE (GstAudioInvert, gst_audio_invert, GST_TYPE_AUDIO_FILTER);
74 
75 static void gst_audio_invert_set_property (GObject * object, guint prop_id,
76     const GValue * value, GParamSpec * pspec);
77 static void gst_audio_invert_get_property (GObject * object, guint prop_id,
78     GValue * value, GParamSpec * pspec);
79 
80 static gboolean gst_audio_invert_setup (GstAudioFilter * filter,
81     const GstAudioInfo * info);
82 static GstFlowReturn gst_audio_invert_transform_ip (GstBaseTransform * base,
83     GstBuffer * buf);
84 
85 static void gst_audio_invert_transform_int (GstAudioInvert * filter,
86     gint16 * data, guint num_samples);
87 static void gst_audio_invert_transform_float (GstAudioInvert * filter,
88     gfloat * data, guint num_samples);
89 
90 /* GObject vmethod implementations */
91 
92 static void
gst_audio_invert_class_init(GstAudioInvertClass * klass)93 gst_audio_invert_class_init (GstAudioInvertClass * klass)
94 {
95   GObjectClass *gobject_class;
96   GstElementClass *gstelement_class;
97   GstCaps *caps;
98 
99   GST_DEBUG_CATEGORY_INIT (gst_audio_invert_debug, "audioinvert", 0,
100       "audioinvert element");
101 
102   gobject_class = (GObjectClass *) klass;
103   gstelement_class = (GstElementClass *) klass;
104 
105   gobject_class->set_property = gst_audio_invert_set_property;
106   gobject_class->get_property = gst_audio_invert_get_property;
107 
108   g_object_class_install_property (gobject_class, PROP_DEGREE,
109       g_param_spec_float ("degree", "Degree",
110           "Degree of inversion", 0.0, 1.0,
111           0.0,
112           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
113 
114   gst_element_class_set_static_metadata (gstelement_class, "Audio inversion",
115       "Filter/Effect/Audio",
116       "Swaps upper and lower half of audio samples",
117       "Sebastian Dröge <slomo@circular-chaos.org>");
118 
119   caps = gst_caps_from_string (ALLOWED_CAPS);
120   gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
121       caps);
122   gst_caps_unref (caps);
123 
124   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
125       GST_DEBUG_FUNCPTR (gst_audio_invert_transform_ip);
126   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip_on_passthrough = FALSE;
127 
128   GST_AUDIO_FILTER_CLASS (klass)->setup =
129       GST_DEBUG_FUNCPTR (gst_audio_invert_setup);
130 }
131 
132 static void
gst_audio_invert_init(GstAudioInvert * filter)133 gst_audio_invert_init (GstAudioInvert * filter)
134 {
135   filter->degree = 0.0;
136   gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE);
137   gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE);
138 }
139 
140 static void
gst_audio_invert_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)141 gst_audio_invert_set_property (GObject * object, guint prop_id,
142     const GValue * value, GParamSpec * pspec)
143 {
144   GstAudioInvert *filter = GST_AUDIO_INVERT (object);
145 
146   switch (prop_id) {
147     case PROP_DEGREE:
148       filter->degree = g_value_get_float (value);
149       gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (filter),
150           filter->degree == 0.0);
151       break;
152     default:
153       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
154       break;
155   }
156 }
157 
158 static void
gst_audio_invert_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)159 gst_audio_invert_get_property (GObject * object, guint prop_id,
160     GValue * value, GParamSpec * pspec)
161 {
162   GstAudioInvert *filter = GST_AUDIO_INVERT (object);
163 
164   switch (prop_id) {
165     case PROP_DEGREE:
166       g_value_set_float (value, filter->degree);
167       break;
168     default:
169       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
170       break;
171   }
172 }
173 
174 /* GstAudioFilter vmethod implementations */
175 
176 static gboolean
gst_audio_invert_setup(GstAudioFilter * base,const GstAudioInfo * info)177 gst_audio_invert_setup (GstAudioFilter * base, const GstAudioInfo * info)
178 {
179   GstAudioInvert *filter = GST_AUDIO_INVERT (base);
180   gboolean ret = TRUE;
181 
182   switch (GST_AUDIO_INFO_FORMAT (info)) {
183     case GST_AUDIO_FORMAT_S16:
184       filter->process = (GstAudioInvertProcessFunc)
185           gst_audio_invert_transform_int;
186       break;
187     case GST_AUDIO_FORMAT_F32:
188       filter->process = (GstAudioInvertProcessFunc)
189           gst_audio_invert_transform_float;
190       break;
191     default:
192       ret = FALSE;
193       break;
194   }
195   return ret;
196 }
197 
198 static void
gst_audio_invert_transform_int(GstAudioInvert * filter,gint16 * data,guint num_samples)199 gst_audio_invert_transform_int (GstAudioInvert * filter,
200     gint16 * data, guint num_samples)
201 {
202   gint i;
203   gfloat dry = 1.0 - filter->degree;
204   glong val;
205 
206   for (i = 0; i < num_samples; i++) {
207     val = (*data) * dry + (-1 - (*data)) * filter->degree;
208     *data++ = (gint16) CLAMP (val, G_MININT16, G_MAXINT16);
209   }
210 }
211 
212 static void
gst_audio_invert_transform_float(GstAudioInvert * filter,gfloat * data,guint num_samples)213 gst_audio_invert_transform_float (GstAudioInvert * filter,
214     gfloat * data, guint num_samples)
215 {
216   gint i;
217   gfloat dry = 1.0 - filter->degree;
218   glong val;
219 
220   for (i = 0; i < num_samples; i++) {
221     val = (*data) * dry - (*data) * filter->degree;
222     *data++ = val;
223   }
224 }
225 
226 /* GstBaseTransform vmethod implementations */
227 static GstFlowReturn
gst_audio_invert_transform_ip(GstBaseTransform * base,GstBuffer * buf)228 gst_audio_invert_transform_ip (GstBaseTransform * base, GstBuffer * buf)
229 {
230   GstAudioInvert *filter = GST_AUDIO_INVERT (base);
231   guint num_samples;
232   GstClockTime timestamp, stream_time;
233   GstMapInfo map;
234 
235   timestamp = GST_BUFFER_TIMESTAMP (buf);
236   stream_time =
237       gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
238 
239   GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
240       GST_TIME_ARGS (timestamp));
241 
242   if (GST_CLOCK_TIME_IS_VALID (stream_time))
243     gst_object_sync_values (GST_OBJECT (filter), stream_time);
244 
245   if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)))
246     return GST_FLOW_OK;
247 
248   gst_buffer_map (buf, &map, GST_MAP_READWRITE);
249   num_samples = map.size / GST_AUDIO_FILTER_BPS (filter);
250 
251   filter->process (filter, map.data, num_samples);
252 
253   gst_buffer_unmap (buf, &map);
254 
255   return GST_FLOW_OK;
256 }
257