]> git.tue.mpg.de Git - paraslash.git/commitdiff
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 1fe29d460bdb5056523f2b4b47ec09b203c6d682..a2e6a423dd6624c4538954637feca67bbe9dbc44 100644 (file)
@@ -264,6 +264,8 @@ para_play \
        $(samplerate_ldflags) \
        -lm
 
+para_mixer: LDFLAGS += -lm
+
 para_write \
 para_play \
 para_audiod \
index 9a56566031aca9373aad7c3cca18fdd9027e80b1..37ecbd8172035d63c4b8a4f47d11eb9a914525ec 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 6d1b5f5cad2d344c3f1eb6b2b441e7ef62e5d79c..d33fa831c9c3ba2cdc29c1ab3609795ab49bba16 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);