From bd374f0948c77dd91287f73f7573bf59f3fb5666 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Fri, 30 Dec 2016 23:25:11 +0100 Subject: [PATCH] mixer: Implement non-linear time scale for fading. 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 and link with -lm. --- Makefile.real | 2 + m4/lls/mixer.suite.m4 | 19 ++++++++ mixer.c | 101 +++++++++++++++++++++++++++++------------- 3 files changed, 92 insertions(+), 30 deletions(-) diff --git a/Makefile.real b/Makefile.real index 1fe29d46..a2e6a423 100644 --- a/Makefile.real +++ b/Makefile.real @@ -264,6 +264,8 @@ para_play \ $(samplerate_ldflags) \ -lm +para_mixer: LDFLAGS += -lm + para_write \ para_play \ para_audiod \ diff --git a/m4/lls/mixer.suite.m4 b/m4/lls/mixer.suite.m4 index 9a565660..37ecbd81 100644 --- a/m4/lls/mixer.suite.m4 +++ b/m4/lls/mixer.suite.m4 @@ -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 = [] diff --git a/mixer.c b/mixer.c index 6d1b5f5c..d33fa831 100644 --- a/mixer.c +++ b/mixer.c @@ -8,6 +8,7 @@ #include #include +#include #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); -- 2.39.2