• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * GStreamer
3  * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-audiodynamic
23  * @title: audiodynamic
24  *
25  * This element can act as a compressor or expander. A compressor changes the
26  * amplitude of all samples above a specific threshold with a specific ratio,
27  * a expander does the same for all samples below a specific threshold. If
28  * soft-knee mode is selected the ratio is applied smoothly.
29  *
30  * ## Example launch line
31  * |[
32  * gst-launch-1.0 audiotestsrc wave=saw ! audiodynamic characteristics=soft-knee mode=compressor threshold=0.5 ratio=0.5 ! alsasink
33  * gst-launch-1.0 filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiodynamic characteristics=hard-knee mode=expander threshold=0.2 ratio=4.0 ! alsasink
34  * gst-launch-1.0 audiotestsrc wave=saw ! audioconvert ! audiodynamic ! audioconvert ! alsasink
35  * ]|
36  *
37  */
38 
39 /* TODO: Implement attack and release parameters */
40 
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44 
45 #include <gst/gst.h>
46 #include <gst/base/gstbasetransform.h>
47 #include <gst/audio/audio.h>
48 #include <gst/audio/gstaudiofilter.h>
49 
50 #include "audiodynamic.h"
51 
52 #define GST_CAT_DEFAULT gst_audio_dynamic_debug
53 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
54 
55 /* Filter signals and args */
56 enum
57 {
58   /* FILL ME */
59   LAST_SIGNAL
60 };
61 
62 enum
63 {
64   PROP_0,
65   PROP_CHARACTERISTICS,
66   PROP_MODE,
67   PROP_THRESHOLD,
68   PROP_RATIO
69 };
70 
71 #define ALLOWED_CAPS \
72     "audio/x-raw,"                                                \
73     " format=(string) {"GST_AUDIO_NE(S16)","GST_AUDIO_NE(F32)"}," \
74     " rate=(int)[1,MAX],"                                         \
75     " channels=(int)[1,MAX],"                                     \
76     " layout=(string) {interleaved, non-interleaved}"
77 
78 G_DEFINE_TYPE (GstAudioDynamic, gst_audio_dynamic, GST_TYPE_AUDIO_FILTER);
79 GST_ELEMENT_REGISTER_DEFINE (audiodynamic, "audiodynamic",
80     GST_RANK_NONE, GST_TYPE_AUDIO_DYNAMIC);
81 
82 static void gst_audio_dynamic_set_property (GObject * object, guint prop_id,
83     const GValue * value, GParamSpec * pspec);
84 static void gst_audio_dynamic_get_property (GObject * object, guint prop_id,
85     GValue * value, GParamSpec * pspec);
86 
87 static gboolean gst_audio_dynamic_setup (GstAudioFilter * filter,
88     const GstAudioInfo * info);
89 static GstFlowReturn gst_audio_dynamic_transform_ip (GstBaseTransform * base,
90     GstBuffer * buf);
91 
92 static void
93 gst_audio_dynamic_transform_hard_knee_compressor_int (GstAudioDynamic * filter,
94     gint16 * data, guint num_samples);
95 static void
96 gst_audio_dynamic_transform_hard_knee_compressor_float (GstAudioDynamic *
97     filter, gfloat * data, guint num_samples);
98 static void
99 gst_audio_dynamic_transform_soft_knee_compressor_int (GstAudioDynamic * filter,
100     gint16 * data, guint num_samples);
101 static void
102 gst_audio_dynamic_transform_soft_knee_compressor_float (GstAudioDynamic *
103     filter, gfloat * data, guint num_samples);
104 static void gst_audio_dynamic_transform_hard_knee_expander_int (GstAudioDynamic
105     * filter, gint16 * data, guint num_samples);
106 static void
107 gst_audio_dynamic_transform_hard_knee_expander_float (GstAudioDynamic * filter,
108     gfloat * data, guint num_samples);
109 static void gst_audio_dynamic_transform_soft_knee_expander_int (GstAudioDynamic
110     * filter, gint16 * data, guint num_samples);
111 static void
112 gst_audio_dynamic_transform_soft_knee_expander_float (GstAudioDynamic * filter,
113     gfloat * data, guint num_samples);
114 
115 static const GstAudioDynamicProcessFunc process_functions[] = {
116   (GstAudioDynamicProcessFunc)
117       gst_audio_dynamic_transform_hard_knee_compressor_int,
118   (GstAudioDynamicProcessFunc)
119       gst_audio_dynamic_transform_hard_knee_compressor_float,
120   (GstAudioDynamicProcessFunc)
121       gst_audio_dynamic_transform_soft_knee_compressor_int,
122   (GstAudioDynamicProcessFunc)
123       gst_audio_dynamic_transform_soft_knee_compressor_float,
124   (GstAudioDynamicProcessFunc)
125       gst_audio_dynamic_transform_hard_knee_expander_int,
126   (GstAudioDynamicProcessFunc)
127       gst_audio_dynamic_transform_hard_knee_expander_float,
128   (GstAudioDynamicProcessFunc)
129       gst_audio_dynamic_transform_soft_knee_expander_int,
130   (GstAudioDynamicProcessFunc)
131   gst_audio_dynamic_transform_soft_knee_expander_float
132 };
133 
134 enum
135 {
136   CHARACTERISTICS_HARD_KNEE = 0,
137   CHARACTERISTICS_SOFT_KNEE
138 };
139 
140 #define GST_TYPE_AUDIO_DYNAMIC_CHARACTERISTICS (gst_audio_dynamic_characteristics_get_type ())
141 static GType
gst_audio_dynamic_characteristics_get_type(void)142 gst_audio_dynamic_characteristics_get_type (void)
143 {
144   static GType gtype = 0;
145 
146   if (gtype == 0) {
147     static const GEnumValue values[] = {
148       {CHARACTERISTICS_HARD_KNEE, "Hard Knee (default)",
149           "hard-knee"},
150       {CHARACTERISTICS_SOFT_KNEE, "Soft Knee (smooth)",
151           "soft-knee"},
152       {0, NULL, NULL}
153     };
154 
155     gtype = g_enum_register_static ("GstAudioDynamicCharacteristics", values);
156   }
157   return gtype;
158 }
159 
160 enum
161 {
162   MODE_COMPRESSOR = 0,
163   MODE_EXPANDER
164 };
165 
166 #define GST_TYPE_AUDIO_DYNAMIC_MODE (gst_audio_dynamic_mode_get_type ())
167 static GType
gst_audio_dynamic_mode_get_type(void)168 gst_audio_dynamic_mode_get_type (void)
169 {
170   static GType gtype = 0;
171 
172   if (gtype == 0) {
173     static const GEnumValue values[] = {
174       {MODE_COMPRESSOR, "Compressor (default)",
175           "compressor"},
176       {MODE_EXPANDER, "Expander", "expander"},
177       {0, NULL, NULL}
178     };
179 
180     gtype = g_enum_register_static ("GstAudioDynamicMode", values);
181   }
182   return gtype;
183 }
184 
185 static void
gst_audio_dynamic_set_process_function(GstAudioDynamic * filter,const GstAudioInfo * info)186 gst_audio_dynamic_set_process_function (GstAudioDynamic * filter,
187     const GstAudioInfo * info)
188 {
189   gint func_index;
190 
191   func_index = (filter->mode == MODE_COMPRESSOR) ? 0 : 4;
192   func_index += (filter->characteristics == CHARACTERISTICS_HARD_KNEE) ? 0 : 2;
193   func_index += (GST_AUDIO_INFO_FORMAT (info) == GST_AUDIO_FORMAT_F32) ? 1 : 0;
194 
195   g_assert (func_index >= 0 && func_index < G_N_ELEMENTS (process_functions));
196 
197   filter->process = process_functions[func_index];
198 }
199 
200 /* GObject vmethod implementations */
201 
202 static void
gst_audio_dynamic_class_init(GstAudioDynamicClass * klass)203 gst_audio_dynamic_class_init (GstAudioDynamicClass * klass)
204 {
205   GObjectClass *gobject_class;
206   GstElementClass *gstelement_class;
207   GstCaps *caps;
208 
209   GST_DEBUG_CATEGORY_INIT (gst_audio_dynamic_debug, "audiodynamic", 0,
210       "audiodynamic element");
211 
212   gobject_class = (GObjectClass *) klass;
213   gstelement_class = (GstElementClass *) klass;
214 
215   gobject_class->set_property = gst_audio_dynamic_set_property;
216   gobject_class->get_property = gst_audio_dynamic_get_property;
217 
218   g_object_class_install_property (gobject_class, PROP_CHARACTERISTICS,
219       g_param_spec_enum ("characteristics", "Characteristics",
220           "Selects whether the ratio should be applied smooth (soft-knee) "
221           "or hard (hard-knee).",
222           GST_TYPE_AUDIO_DYNAMIC_CHARACTERISTICS, CHARACTERISTICS_HARD_KNEE,
223           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
224 
225   g_object_class_install_property (gobject_class, PROP_MODE,
226       g_param_spec_enum ("mode", "Mode",
227           "Selects whether the filter should work on loud samples (compressor) or"
228           "quiet samples (expander).",
229           GST_TYPE_AUDIO_DYNAMIC_MODE, MODE_COMPRESSOR,
230           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
231 
232   g_object_class_install_property (gobject_class, PROP_THRESHOLD,
233       g_param_spec_float ("threshold", "Threshold",
234           "Threshold until the filter is activated", 0.0, 1.0,
235           0.0,
236           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
237 
238   g_object_class_install_property (gobject_class, PROP_RATIO,
239       g_param_spec_float ("ratio", "Ratio",
240           "Ratio that should be applied", 0.0, G_MAXFLOAT,
241           1.0,
242           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
243 
244   gst_element_class_set_static_metadata (gstelement_class,
245       "Dynamic range controller", "Filter/Effect/Audio",
246       "Compressor and Expander", "Sebastian Dröge <slomo@circular-chaos.org>");
247 
248   caps = gst_caps_from_string (ALLOWED_CAPS);
249   gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
250       caps);
251   gst_caps_unref (caps);
252 
253   GST_AUDIO_FILTER_CLASS (klass)->setup =
254       GST_DEBUG_FUNCPTR (gst_audio_dynamic_setup);
255 
256   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
257       GST_DEBUG_FUNCPTR (gst_audio_dynamic_transform_ip);
258   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip_on_passthrough = FALSE;
259 
260   gst_type_mark_as_plugin_api (GST_TYPE_AUDIO_DYNAMIC_CHARACTERISTICS, 0);
261   gst_type_mark_as_plugin_api (GST_TYPE_AUDIO_DYNAMIC_MODE, 0);
262 }
263 
264 static void
gst_audio_dynamic_init(GstAudioDynamic * filter)265 gst_audio_dynamic_init (GstAudioDynamic * filter)
266 {
267   filter->ratio = 1.0;
268   filter->threshold = 0.0;
269   filter->characteristics = CHARACTERISTICS_HARD_KNEE;
270   filter->mode = MODE_COMPRESSOR;
271   gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE);
272   gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE);
273 }
274 
275 static void
gst_audio_dynamic_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)276 gst_audio_dynamic_set_property (GObject * object, guint prop_id,
277     const GValue * value, GParamSpec * pspec)
278 {
279   GstAudioDynamic *filter = GST_AUDIO_DYNAMIC (object);
280 
281   switch (prop_id) {
282     case PROP_CHARACTERISTICS:
283       filter->characteristics = g_value_get_enum (value);
284       gst_audio_dynamic_set_process_function (filter,
285           GST_AUDIO_FILTER_INFO (filter));
286       break;
287     case PROP_MODE:
288       filter->mode = g_value_get_enum (value);
289       gst_audio_dynamic_set_process_function (filter,
290           GST_AUDIO_FILTER_INFO (filter));
291       break;
292     case PROP_THRESHOLD:
293       filter->threshold = g_value_get_float (value);
294       break;
295     case PROP_RATIO:
296       filter->ratio = g_value_get_float (value);
297       break;
298     default:
299       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300       break;
301   }
302 }
303 
304 static void
gst_audio_dynamic_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)305 gst_audio_dynamic_get_property (GObject * object, guint prop_id,
306     GValue * value, GParamSpec * pspec)
307 {
308   GstAudioDynamic *filter = GST_AUDIO_DYNAMIC (object);
309 
310   switch (prop_id) {
311     case PROP_CHARACTERISTICS:
312       g_value_set_enum (value, filter->characteristics);
313       break;
314     case PROP_MODE:
315       g_value_set_enum (value, filter->mode);
316       break;
317     case PROP_THRESHOLD:
318       g_value_set_float (value, filter->threshold);
319       break;
320     case PROP_RATIO:
321       g_value_set_float (value, filter->ratio);
322       break;
323     default:
324       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
325       break;
326   }
327 }
328 
329 /* GstAudioFilter vmethod implementations */
330 
331 static gboolean
gst_audio_dynamic_setup(GstAudioFilter * base,const GstAudioInfo * info)332 gst_audio_dynamic_setup (GstAudioFilter * base, const GstAudioInfo * info)
333 {
334   GstAudioDynamic *filter = GST_AUDIO_DYNAMIC (base);
335 
336   gst_audio_dynamic_set_process_function (filter, info);
337   return TRUE;
338 }
339 
340 static void
gst_audio_dynamic_transform_hard_knee_compressor_int(GstAudioDynamic * filter,gint16 * data,guint num_samples)341 gst_audio_dynamic_transform_hard_knee_compressor_int (GstAudioDynamic * filter,
342     gint16 * data, guint num_samples)
343 {
344   glong val;
345   glong thr_p = filter->threshold * G_MAXINT16;
346   glong thr_n = filter->threshold * G_MININT16;
347 
348   /* Nothing to do for us if ratio is 1.0 or if the threshold
349    * equals 1.0. */
350   if (filter->threshold == 1.0 || filter->ratio == 1.0)
351     return;
352 
353   for (; num_samples; num_samples--) {
354     val = *data;
355 
356     if (val > thr_p) {
357       val = thr_p + (val - thr_p) * filter->ratio;
358     } else if (val < thr_n) {
359       val = thr_n + (val - thr_n) * filter->ratio;
360     }
361     *data++ = (gint16) CLAMP (val, G_MININT16, G_MAXINT16);
362   }
363 }
364 
365 static void
gst_audio_dynamic_transform_hard_knee_compressor_float(GstAudioDynamic * filter,gfloat * data,guint num_samples)366 gst_audio_dynamic_transform_hard_knee_compressor_float (GstAudioDynamic *
367     filter, gfloat * data, guint num_samples)
368 {
369   gdouble val, threshold = filter->threshold;
370 
371   /* Nothing to do for us if ratio == 1.0.
372    * As float values can be above 1.0 we have to do something
373    * if threshold is greater than 1.0. */
374   if (filter->ratio == 1.0)
375     return;
376 
377   for (; num_samples; num_samples--) {
378     val = *data;
379 
380     if (val > threshold) {
381       val = threshold + (val - threshold) * filter->ratio;
382     } else if (val < -threshold) {
383       val = -threshold + (val + threshold) * filter->ratio;
384     }
385     *data++ = (gfloat) val;
386   }
387 }
388 
389 static void
gst_audio_dynamic_transform_soft_knee_compressor_int(GstAudioDynamic * filter,gint16 * data,guint num_samples)390 gst_audio_dynamic_transform_soft_knee_compressor_int (GstAudioDynamic * filter,
391     gint16 * data, guint num_samples)
392 {
393   glong val;
394   glong thr_p = filter->threshold * G_MAXINT16;
395   glong thr_n = filter->threshold * G_MININT16;
396   gdouble a_p, b_p, c_p;
397   gdouble a_n, b_n, c_n;
398 
399   /* Nothing to do for us if ratio is 1.0 or if the threshold
400    * equals 1.0. */
401   if (filter->threshold == 1.0 || filter->ratio == 1.0)
402     return;
403 
404   /* We build a 2nd degree polynomial here for
405    * values greater than threshold or small than
406    * -threshold with:
407    * f(t) = t, f'(t) = 1, f'(m) = r
408    * =>
409    * a = (1-r)/(2*(t-m))
410    * b = (r*t - m)/(t-m)
411    * c = t * (1 - b - a*t)
412    * f(x) = ax^2 + bx + c
413    */
414 
415   /* shouldn't happen because this would only be the case
416    * for threshold == 1.0 which we catch above */
417   g_assert (thr_p - G_MAXINT16 != 0);
418   g_assert (thr_n - G_MININT != 0);
419 
420   a_p = (1 - filter->ratio) / (2 * (thr_p - G_MAXINT16));
421   b_p = (filter->ratio * thr_p - G_MAXINT16) / (thr_p - G_MAXINT16);
422   c_p = thr_p * (1 - b_p - a_p * thr_p);
423   a_n = (1 - filter->ratio) / (2 * (thr_n - G_MININT16));
424   b_n = (filter->ratio * thr_n - G_MININT16) / (thr_n - G_MININT16);
425   c_n = thr_n * (1 - b_n - a_n * thr_n);
426 
427   for (; num_samples; num_samples--) {
428     val = *data;
429 
430     if (val > thr_p) {
431       val = a_p * val * val + b_p * val + c_p;
432     } else if (val < thr_n) {
433       val = a_n * val * val + b_n * val + c_n;
434     }
435     *data++ = (gint16) CLAMP (val, G_MININT16, G_MAXINT16);
436   }
437 }
438 
439 static void
gst_audio_dynamic_transform_soft_knee_compressor_float(GstAudioDynamic * filter,gfloat * data,guint num_samples)440 gst_audio_dynamic_transform_soft_knee_compressor_float (GstAudioDynamic *
441     filter, gfloat * data, guint num_samples)
442 {
443   gdouble val;
444   gdouble threshold = filter->threshold;
445   gdouble a_p, b_p, c_p;
446   gdouble a_n, b_n, c_n;
447 
448   /* Nothing to do for us if ratio == 1.0.
449    * As float values can be above 1.0 we have to do something
450    * if threshold is greater than 1.0. */
451   if (filter->ratio == 1.0)
452     return;
453 
454   /* We build a 2nd degree polynomial here for
455    * values greater than threshold or small than
456    * -threshold with:
457    * f(t) = t, f'(t) = 1, f'(m) = r
458    * =>
459    * a = (1-r)/(2*(t-m))
460    * b = (r*t - m)/(t-m)
461    * c = t * (1 - b - a*t)
462    * f(x) = ax^2 + bx + c
463    */
464 
465   /* FIXME: If threshold is the same as the maximum
466    * we need to raise it a bit to prevent
467    * division by zero. */
468   if (threshold == 1.0)
469     threshold = 1.0 + 0.00001;
470 
471   a_p = (1.0 - filter->ratio) / (2.0 * (threshold - 1.0));
472   b_p = (filter->ratio * threshold - 1.0) / (threshold - 1.0);
473   c_p = threshold * (1.0 - b_p - a_p * threshold);
474   a_n = (1.0 - filter->ratio) / (2.0 * (-threshold + 1.0));
475   b_n = (-filter->ratio * threshold + 1.0) / (-threshold + 1.0);
476   c_n = -threshold * (1.0 - b_n + a_n * threshold);
477 
478   for (; num_samples; num_samples--) {
479     val = *data;
480 
481     if (val > 1.0) {
482       val = 1.0 + (val - 1.0) * filter->ratio;
483     } else if (val > threshold) {
484       val = a_p * val * val + b_p * val + c_p;
485     } else if (val < -1.0) {
486       val = -1.0 + (val + 1.0) * filter->ratio;
487     } else if (val < -threshold) {
488       val = a_n * val * val + b_n * val + c_n;
489     }
490     *data++ = (gfloat) val;
491   }
492 }
493 
494 static void
gst_audio_dynamic_transform_hard_knee_expander_int(GstAudioDynamic * filter,gint16 * data,guint num_samples)495 gst_audio_dynamic_transform_hard_knee_expander_int (GstAudioDynamic * filter,
496     gint16 * data, guint num_samples)
497 {
498   glong val;
499   glong thr_p = filter->threshold * G_MAXINT16;
500   glong thr_n = filter->threshold * G_MININT16;
501   gdouble zero_p, zero_n;
502 
503   /* Nothing to do for us here if threshold equals 0.0
504    * or ratio equals 1.0 */
505   if (filter->threshold == 0.0 || filter->ratio == 1.0)
506     return;
507 
508   /* zero crossing of our function */
509   if (filter->ratio != 0.0) {
510     zero_p = thr_p - thr_p / filter->ratio;
511     zero_n = thr_n - thr_n / filter->ratio;
512   } else {
513     zero_p = zero_n = 0.0;
514   }
515 
516   if (zero_p < 0.0)
517     zero_p = 0.0;
518   if (zero_n > 0.0)
519     zero_n = 0.0;
520 
521   for (; num_samples; num_samples--) {
522     val = *data;
523 
524     if (val < thr_p && val > zero_p) {
525       val = filter->ratio * val + thr_p * (1 - filter->ratio);
526     } else if ((val <= zero_p && val > 0) || (val >= zero_n && val < 0)) {
527       val = 0;
528     } else if (val > thr_n && val < zero_n) {
529       val = filter->ratio * val + thr_n * (1 - filter->ratio);
530     }
531     *data++ = (gint16) CLAMP (val, G_MININT16, G_MAXINT16);
532   }
533 }
534 
535 static void
gst_audio_dynamic_transform_hard_knee_expander_float(GstAudioDynamic * filter,gfloat * data,guint num_samples)536 gst_audio_dynamic_transform_hard_knee_expander_float (GstAudioDynamic * filter,
537     gfloat * data, guint num_samples)
538 {
539   gdouble val, threshold = filter->threshold, zero;
540 
541   /* Nothing to do for us here if threshold equals 0.0
542    * or ratio equals 1.0 */
543   if (filter->threshold == 0.0 || filter->ratio == 1.0)
544     return;
545 
546   /* zero crossing of our function */
547   if (filter->ratio != 0.0)
548     zero = threshold - threshold / filter->ratio;
549   else
550     zero = 0.0;
551 
552   if (zero < 0.0)
553     zero = 0.0;
554 
555   for (; num_samples; num_samples--) {
556     val = *data;
557 
558     if (val < threshold && val > zero) {
559       val = filter->ratio * val + threshold * (1.0 - filter->ratio);
560     } else if ((val <= zero && val > 0.0) || (val >= -zero && val < 0.0)) {
561       val = 0.0;
562     } else if (val > -threshold && val < -zero) {
563       val = filter->ratio * val - threshold * (1.0 - filter->ratio);
564     }
565     *data++ = (gfloat) val;
566   }
567 }
568 
569 static void
gst_audio_dynamic_transform_soft_knee_expander_int(GstAudioDynamic * filter,gint16 * data,guint num_samples)570 gst_audio_dynamic_transform_soft_knee_expander_int (GstAudioDynamic * filter,
571     gint16 * data, guint num_samples)
572 {
573   glong val;
574   glong thr_p = filter->threshold * G_MAXINT16;
575   glong thr_n = filter->threshold * G_MININT16;
576   gdouble zero_p, zero_n;
577   gdouble a_p, b_p, c_p;
578   gdouble a_n, b_n, c_n;
579   gdouble r2;
580 
581   /* Nothing to do for us here if threshold equals 0.0
582    * or ratio equals 1.0 */
583   if (filter->threshold == 0.0 || filter->ratio == 1.0)
584     return;
585 
586   /* zero crossing of our function */
587   zero_p = (thr_p * (filter->ratio - 1.0)) / (1.0 + filter->ratio);
588   zero_n = (thr_n * (filter->ratio - 1.0)) / (1.0 + filter->ratio);
589 
590   if (zero_p < 0.0)
591     zero_p = 0.0;
592   if (zero_n > 0.0)
593     zero_n = 0.0;
594 
595   /* shouldn't happen as this would only happen
596    * with threshold == 0.0 */
597   g_assert (thr_p != 0);
598   g_assert (thr_n != 0);
599 
600   /* We build a 2n degree polynomial here for values between
601    * 0 and threshold or 0 and -threshold with:
602    * f(t) = t, f'(t) = 1, f(z) = 0, f'(z) = r
603    * z between 0 and t
604    * =>
605    * a = (1 - r^2) / (4 * t)
606    * b = (1 + r^2) / 2
607    * c = t * (1.0 - b - a*t)
608    * f(x) = ax^2 + bx + c */
609   r2 = filter->ratio * filter->ratio;
610   a_p = (1.0 - r2) / (4.0 * thr_p);
611   b_p = (1.0 + r2) / 2.0;
612   c_p = thr_p * (1.0 - b_p - a_p * thr_p);
613   a_n = (1.0 - r2) / (4.0 * thr_n);
614   b_n = (1.0 + r2) / 2.0;
615   c_n = thr_n * (1.0 - b_n - a_n * thr_n);
616 
617   for (; num_samples; num_samples--) {
618     val = *data;
619 
620     if (val < thr_p && val > zero_p) {
621       val = a_p * val * val + b_p * val + c_p;
622     } else if ((val <= zero_p && val > 0) || (val >= zero_n && val < 0)) {
623       val = 0;
624     } else if (val > thr_n && val < zero_n) {
625       val = a_n * val * val + b_n * val + c_n;
626     }
627     *data++ = (gint16) CLAMP (val, G_MININT16, G_MAXINT16);
628   }
629 }
630 
631 static void
gst_audio_dynamic_transform_soft_knee_expander_float(GstAudioDynamic * filter,gfloat * data,guint num_samples)632 gst_audio_dynamic_transform_soft_knee_expander_float (GstAudioDynamic * filter,
633     gfloat * data, guint num_samples)
634 {
635   gdouble val;
636   gdouble threshold = filter->threshold;
637   gdouble zero;
638   gdouble a_p, b_p, c_p;
639   gdouble a_n, b_n, c_n;
640   gdouble r2;
641 
642   /* Nothing to do for us here if threshold equals 0.0
643    * or ratio equals 1.0 */
644   if (filter->threshold == 0.0 || filter->ratio == 1.0)
645     return;
646 
647   /* zero crossing of our function */
648   zero = (threshold * (filter->ratio - 1.0)) / (1.0 + filter->ratio);
649 
650   if (zero < 0.0)
651     zero = 0.0;
652 
653   /* shouldn't happen as this only happens with
654    * threshold == 0.0 */
655   g_assert (threshold != 0.0);
656 
657   /* We build a 2n degree polynomial here for values between
658    * 0 and threshold or 0 and -threshold with:
659    * f(t) = t, f'(t) = 1, f(z) = 0, f'(z) = r
660    * z between 0 and t
661    * =>
662    * a = (1 - r^2) / (4 * t)
663    * b = (1 + r^2) / 2
664    * c = t * (1.0 - b - a*t)
665    * f(x) = ax^2 + bx + c */
666   r2 = filter->ratio * filter->ratio;
667   a_p = (1.0 - r2) / (4.0 * threshold);
668   b_p = (1.0 + r2) / 2.0;
669   c_p = threshold * (1.0 - b_p - a_p * threshold);
670   a_n = (1.0 - r2) / (-4.0 * threshold);
671   b_n = (1.0 + r2) / 2.0;
672   c_n = -threshold * (1.0 - b_n + a_n * threshold);
673 
674   for (; num_samples; num_samples--) {
675     val = *data;
676 
677     if (val < threshold && val > zero) {
678       val = a_p * val * val + b_p * val + c_p;
679     } else if ((val <= zero && val > 0.0) || (val >= -zero && val < 0.0)) {
680       val = 0.0;
681     } else if (val > -threshold && val < -zero) {
682       val = a_n * val * val + b_n * val + c_n;
683     }
684     *data++ = (gfloat) val;
685   }
686 }
687 
688 /* GstBaseTransform vmethod implementations */
689 static GstFlowReturn
gst_audio_dynamic_transform_ip(GstBaseTransform * base,GstBuffer * buf)690 gst_audio_dynamic_transform_ip (GstBaseTransform * base, GstBuffer * buf)
691 {
692   GstAudioDynamic *filter = GST_AUDIO_DYNAMIC (base);
693   guint num_samples;
694   GstClockTime timestamp, stream_time;
695   GstMapInfo map;
696 
697   timestamp = GST_BUFFER_TIMESTAMP (buf);
698   stream_time =
699       gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
700 
701   GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
702       GST_TIME_ARGS (timestamp));
703 
704   if (GST_CLOCK_TIME_IS_VALID (stream_time))
705     gst_object_sync_values (GST_OBJECT (filter), stream_time);
706 
707   if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)))
708     return GST_FLOW_OK;
709 
710   gst_buffer_map (buf, &map, GST_MAP_READWRITE);
711   num_samples = map.size / GST_AUDIO_FILTER_BPS (filter);
712 
713   filter->process (filter, map.data, num_samples);
714 
715   gst_buffer_unmap (buf, &map);
716 
717   return GST_FLOW_OK;
718 }
719