I'm sitting at work, listening to music, debugging a web-application with JavaScript alert()s. Each time an alert window pops up, the browser plays a sound. For a brief moment, the volume drops twicefold then goes back to normal. This is annoying and doesn't make sense.
In real life, if you are surrounded by multiple sound sources, their sound volumes will not be divided by the total amount of sound sources. Their sounds will add up until they blur and you can't distinguish anything anymore. Other operating systems, such as Macrohard Doors, do mixing by modeling this real world behaviour. In this sense, aucat violates the principle of least surprise. I'm used to how sound interacts in real world and then aucat steps in and introduces it's own laws of physics. To remedy this, aucat has an option -v, which lets you pre-divide the volume of inputs. This results in loss of dynamic range (quiet sounds might disappear and the maximum volume that you can set decreases). And also, if during usage the count of inputs raises above of what I predicted, the volume starts to jump up and down again. Experimentally, I've found that if you do a saturating addition between inputs, it sounds very much how it might have sounded in real world and how Macrohard Doors, among others, sounds like when playing multiple sounds. So, why is what I'm proposing better than what currently exists: * Resembles how sound behaves in real world more closely; * Doesn't violate the principle of least surprise; * No more annoying volume jumps up and down; * No need to use the -v option anymore / less stuff to remember / "it just works"; * No more choosing between being annoyed by volume jumps or loosing dynamic range. (This doesn't affect the -v option, it remains fully functional.) Tested on i386 and amd64 with 16 bits and 24 bits. Index: abuf.h =================================================================== RCS file: /OpenBSD/src/usr.bin/aucat/abuf.h,v retrieving revision 1.23 diff -u -r1.23 abuf.h --- abuf.h 21 Oct 2010 18:57:42 -0000 1.23 +++ abuf.h 10 May 2011 22:58:18 -0000 @@ -46,7 +46,6 @@ union { struct { int weight; /* dynamic range */ - int maxweight; /* max dynamic range allowed */ unsigned vol; /* volume within the dynamic range */ unsigned done; /* frames ready */ unsigned xrun; /* underrun policy */ Index: aparams.h =================================================================== RCS file: /OpenBSD/src/usr.bin/aucat/aparams.h,v retrieving revision 1.11 diff -u -r1.11 aparams.h --- aparams.h 5 Nov 2010 16:42:17 -0000 1.11 +++ aparams.h 10 May 2011 22:58:18 -0000 @@ -19,6 +19,8 @@ #include <sys/param.h> +#include <limits.h> + #define NCHAN_MAX 16 /* max channel in a stream */ #define RATE_MIN 4000 /* min sample rate */ #define RATE_MAX 192000 /* max sample rate */ @@ -64,6 +66,9 @@ typedef short adata_t; +#define ADATA_MIN SHRT_MIN +#define ADATA_MAX SHRT_MAX + #define ADATA_MUL(x,y) (((int)(x) * (int)(y)) >> (ADATA_BITS - 1)) #define ADATA_MULDIV(x,y,z) ((int)(x) * (int)(y) / (int)(z)) @@ -71,6 +76,9 @@ typedef int adata_t; +#define ADATA_MIN (-0xffffff / 2) +#define ADATA_MAX (0xffffff / 2) + #if defined(__i386__) && defined(__GNUC__) static inline int @@ -119,6 +127,28 @@ #else #error "only 16-bit and 24-bit precisions are supported" #endif + +/* saturating addition */ +static inline adata_t +adata_sadd(register adata_t x, register adata_t y) +{ +#if ADATA_BITS <= 16 + register int sum; +#else + register long long sum; +#endif + + sum = x; + sum += y; + + if (sum < ADATA_MIN) + sum = ADATA_MIN; + else if (sum > ADATA_MAX) + sum = ADATA_MAX; + + return (adata_t) sum; +} +#define ADATA_SADD(x,y) adata_sadd(x,y) #define MIDI_MAXCTL 127 #define MIDI_TO_ADATA(m) (aparams_ctltovol[m] << (ADATA_BITS - 16)) Index: aproc.c =================================================================== RCS file: /OpenBSD/src/usr.bin/aucat/aproc.c,v retrieving revision 1.64 diff -u -r1.64 aproc.c --- aproc.c 28 Apr 2011 07:20:03 -0000 1.64 +++ aproc.c 10 May 2011 22:58:19 -0000 @@ -617,6 +617,7 @@ unsigned i, j, cc, istart, inext, onext, ostart; unsigned scount, icount, ocount; int vol; + adata_t sample; #ifdef DEBUG if (debug_level >= 4) { @@ -673,7 +674,8 @@ idata += istart; for (i = scount; i > 0; i--) { for (j = cc; j > 0; j--) { - *odata += ADATA_MUL(*idata, vol); + sample = ADATA_MUL(*idata, vol); + *odata = ADATA_SADD(*odata, sample); idata++; odata++; } @@ -914,8 +916,6 @@ struct abuf *i, *obuf = LIST_FIRST(&p->outs); unsigned odone; - mix_setmaster(p); - if (!aproc_inuse(p)) { #ifdef DEBUG if (debug_level >= 3) { @@ -962,7 +962,6 @@ ibuf->r.mix.done = 0; ibuf->r.mix.vol = ADATA_UNIT; ibuf->r.mix.weight = ADATA_UNIT; - ibuf->r.mix.maxweight = ADATA_UNIT; ibuf->r.mix.xrun = XRUN_IGNORE; ibuf->r.mix.drop = 0; } @@ -1028,57 +1027,6 @@ p->u.mix.ctl = NULL; p->u.mix.mon = NULL; return p; -} - -/* - * Normalize input levels. - */ -void -mix_setmaster(struct aproc *p) -{ - unsigned n; - struct abuf *i, *j; - int weight; - - /* - * count the number of inputs. If a set of inputs - * uses channels that have no intersection, they are - * counted only once because they don't need to - * share their volume - * - * XXX: this is wrong, this is not optimal if we have two - * buckets of N and N' clients, in which case we should - * get 1/N and 1/N' respectively - */ - n = 0; - LIST_FOREACH(i, &p->ins, ient) { - j = LIST_NEXT(i, ient); - for (;;) { - if (j == NULL) { - n++; - break; - } - if (i->cmin > j->cmax || i->cmax < j->cmin) - break; - j = LIST_NEXT(j, ient); - } - } - LIST_FOREACH(i, &p->ins, ient) { - weight = ADATA_UNIT / n; - if (weight > i->r.mix.maxweight) - weight = i->r.mix.maxweight; - i->r.mix.weight = weight; -#ifdef DEBUG - if (debug_level >= 3) { - abuf_dbg(i); - dbg_puts(": setmaster: "); - dbg_puti(i->r.mix.weight); - dbg_puts("/"); - dbg_puti(i->r.mix.maxweight); - dbg_puts("\n"); - } -#endif - } } void Index: dev.c =================================================================== RCS file: /OpenBSD/src/usr.bin/aucat/dev.c,v retrieving revision 1.64 diff -u -r1.64 dev.c --- dev.c 21 Oct 2010 18:57:42 -0000 1.64 +++ dev.c 10 May 2011 22:58:19 -0000 @@ -998,8 +998,7 @@ } aproc_setin(d->mix, ibuf); ibuf->r.mix.xrun = xrun; - ibuf->r.mix.maxweight = vol; - mix_setmaster(d->mix); + ibuf->r.mix.weight = vol; } if (mode & MODE_REC) { opar = *sopar;