mixer: Implement non-linear time scale for fading.
authorAndre Noll <maan@tuebingen.mpg.de>
Fri, 30 Dec 2016 22:25:11 +0000 (23:25 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Sat, 1 Apr 2017 10:48:50 +0000 (12:48 +0200)
For some sound hardware, changing the volume channel has little
effect for small values but even an increment by one is audible for
high values. For such hardware it is desirable to scale the volume
in a non-linear way.

This patch implements this feature. The new --fade-exponent receives
an argument to control the level of non-linearity. The default value
of zero results in old behavior: linear scaling.

For the implementation we need to compute (the inverse of) t ->
t^alpha, where alpha is between zero and one. This requires the use
of exp(3) and log(3), so we need to include <math.h> and link with -lm.

Makefile.real
m4/lls/mixer.suite.m4
mixer.c

index 1fe29d4..a2e6a42 100644 (file)
@@ -264,6 +264,8 @@ para_play \
        $(samplerate_ldflags) \
        -lm
 
+para_mixer: LDFLAGS += -lm
+
 para_write \
 para_play \
 para_audiod \
index 9a56566..37ecbd8 100644 (file)
@@ -62,6 +62,25 @@ caption = List of subcommands
                        'reclev', 'igain', 'ogain'. However, not all listed channels might be
                        supported on any particular hardware. The default channel is 'volume'.
                [/help]
+       [option fade-exponent]
+               summary = set non-linear time scale for fading
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = value
+               default_val = 0
+               [help]
+                       This option affects the fade, snooze and sleep subcommands. It is
+                       ignored in set mode.
+
+                       The argument must be a number between 0 and 100. The default value
+                       0 corresponds to linear scaling. That is, the value of the mixer
+                       channel is increased or decreased in fixed time intervals until the
+                       destination value is reached. Exponents between 1 and 99 cause low
+                       channel values to be increased more quickly than high channel values.
+                       Large exponents cause greater differences. The special value 100 sets
+                       the destination value immediately. The command then sleeps for the
+                       configured fade time.
+               [/help]
 [subcommand help]
        purpose = print subcommand help
        non-opts-name = [<subcommand>]
diff --git a/mixer.c b/mixer.c
index 6d1b5f5..d33fa83 100644 (file)
--- a/mixer.c
+++ b/mixer.c
@@ -8,6 +8,7 @@
 
 #include <regex.h>
 #include <lopsub.h>
+#include <math.h>
 
 #include "mixer.lsg.h"
 #include "para.h"
@@ -66,47 +67,87 @@ static int set_channel(struct mixer *m, struct mixer_handle *h, const char *chan
        return m->set_channel(h, channel);
 }
 
+static void millisleep(int ms)
+{
+       struct timespec ts;
+
+       PARA_INFO_LOG("sleeping %dms\n", ms);
+       if (ms < 0)
+               return;
+       ts.tv_sec = ms / 1000,
+       ts.tv_nsec = (ms % 1000) * 1000 * 1000;
+       nanosleep(&ts, NULL);
+}
+
+/*
+ * This implements the inverse function of t -> t^alpha, scaled to the time
+ * interval [0,T] and the range given by old_vol and new_vol. It returns the
+ * amount of milliseconds until the given volume is reached.
+ */
+static unsigned volume_time(double vol, double old_vol, double new_vol,
+               double T, double alpha)
+{
+       double c, d, x;
+
+       if (old_vol < new_vol) {
+               c = old_vol;
+               d = new_vol;
+       } else {
+               c = new_vol;
+               d = old_vol;
+       }
+
+       x = T * exp(log(((vol - c) / (d - c))) / alpha);
+       assert(x <= T);
+       if (old_vol < new_vol)
+               return x;
+       else
+               return T - x;
+}
+
 /* Fade to new volume in fade_time seconds. */
 static int fade(struct mixer *m, struct mixer_handle *h, uint32_t new_vol,
                uint32_t fade_time)
 {
-       int vol, diff, incr, ret;
-       unsigned secs;
-       struct timespec ts;
-       unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
+       int i, T, old_vol, ret, slept, incr;
+       double ms, alpha;
+       uint32_t fe = OPT_UINT32_VAL(PARA_MIXER, FADE_EXPONENT);
 
-       if (fade_time <= 0)
-               return m->set(h, new_vol);
-       secs = fade_time;
+       if (fade_time <= 0 || fe >= 100) {
+               ret = m->set(h, new_vol);
+               if (ret < 0)
+                       return ret;
+               goto sleep;
+       }
+       alpha = (100 - fe) / 100.0;
        ret = m->get(h);
        if (ret < 0)
-               goto out;
-       vol = ret;
+               return ret;
+       old_vol = ret;
+       if (old_vol == new_vol)
+               goto sleep;
        PARA_NOTICE_LOG("fading %s from %d to %u in %u seconds\n",
-               OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), vol, new_vol, secs);
-       diff = new_vol - vol;
-       if (!diff) {
-               sleep(secs);
-               goto out;
-       }
-       incr = diff > 0? 1: -1;
-       diff = diff > 0? diff: -diff;
-       tmp = secs * 1000 / diff;
-       tmp2 = tmp % 1000;
-       while ((new_vol - vol) * incr > 0) {
-               ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
-               ts.tv_sec = tmp / 1000; /* really nec ?*/
-               //printf("ts.tv_sec: %i\n", ts.tv_nsec);
-               vol += incr;
-               ret = m->set(h, vol);
+               OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), old_vol,
+               new_vol, fade_time);
+       incr = old_vol < new_vol? 1 : -1;
+       T = fade_time * 1000;
+       i = old_vol;
+       slept = 0;
+       do {
+               ms = volume_time(i + incr, old_vol, new_vol, T, alpha);
+               millisleep(ms - slept);
+               slept = ms;
+               i += incr;
+               ret = m->set(h, i);
                if (ret < 0)
-                       goto out;
-               //printf("vol = %i\n", vol);
-               nanosleep(&ts, NULL);
-       }
-out:
+                       return ret;
+       } while (i != new_vol);
+       return 1;
+sleep:
+       sleep(fade_time);
        return ret;
 }
+
 static int com_fade(struct mixer *m, struct mixer_handle *h)
 {
        uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL);