ref: 8508e91aaf1ffbbd193791c19cfcbe161ef53160
parent: a85496cea4c5c62a2038b1b488f541e9030ff379
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Wed Aug 25 13:24:22 EDT 2021
rewrite autovoicing to reduce pops/clicks even more
--- a/fs.c
+++ b/fs.c
@@ -12,7 +12,6 @@
enum {
Maxobjs = 64,
- Maxvoice = 16,
};
static Aux *objs[Maxobjs];
@@ -28,11 +27,44 @@
static Fs *fs;
+static void
+shutup(State *s, Voice *v)
+{
+ if (s->voice != v) {
+ fs->dsp.free(v->dsp);
+ v->dsp = nil;
+ }
+ v->state = Vsilent;
+}
+
+static Voice *
+newvoice(State *s, Auxdsp *dsp)
+{
+ Voice *v, *f;
+ int i;
+
+ f = s->voice;
+ for (i = 0, v = s->voice; i < nelem(s->voice); i++, v++) {
+ if (v->dsp == nil) {
+ f = v;
+ break;
+ }
+ if (v->state == Vplaying && f->samples < v->samples)
+ f = v;
+ }
+
+ f->dsp = dsp;
+ f->state = Vsilent;
+
+ return f;
+}
+
static Aux *
newobj(char *name)
{
File *f;
Aux *o;
+ Voice *v;
int i, mode;
for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
@@ -62,9 +94,8 @@
uif = f;
o->state = uis = calloc(1, sizeof(State));
- o->state->voice = calloc(1, sizeof(Auxdsp*));
- o->state->voice[o->state->nvoice++] = fs->dsp.new(&o->numin, &o->numout);
- o->state->silent = 1;
+ v = newvoice(o->state, fs->dsp.new(&o->numin, &o->numout));
+ v->state = Vsilent;
closefile(f);
return o;
@@ -80,9 +111,10 @@
if (o->type == Xdsp) {
objs[o->id] = nil;
- for (i = 0; i < o->state->nvoice; i++)
- fs->dsp.free(o->state->voice[i]);
- free(o->state->voice);
+ for (i = 0; i < nelem(o->state->voice); i++) {
+ if (o->state->voice[i].dsp != nil)
+ fs->dsp.free(o->state->voice[i].dsp);
+ }
free(o->state);
}
@@ -113,117 +145,80 @@
respond(r, nil);
}
-#define CROSS(a,b) (a <= 0.0f && a < b)
+#define CROSS(a,b) (a <= 0.0f && a <= b && b >= 0.0f)
static int
-auxreaddsp(Aux *a, float *b, int n)
+auxreaddsp(Aux *a, float *b, int total)
{
+ int i, j, n, cross;
+ Voice *v;
+ float *m;
State *s;
- float *m, level, least;
- int i, j, cross, silent, total, quiet;
s = a->state;
- cross = 0;
- total = n;
- if (!s->silent) {
- silent = 1;
- n = fs->dsp.read(s->voice[0], b, n);
- total = n;
- for (i = 0; i < n; i++) {
- if (b[i] != 0.0f) {
- silent = 0;
- /* not silent, don't care for crossing? leave early */
- if (!s->crossvoice)
- break;
- }
- /* find the cross point */
- if (s->crossvoice && i > 0 && cross < 1 && CROSS(b[i-1], b[i])) {
- cross = i;
- break;
- }
- }
- s->silent = silent;
- }
- /* simple case: error, no data, or just one voice */
- if (n < 1 || s->nvoice < 2)
- return n;
-
- /* otherwise need to mix all the voices together */
+ /* make sure we have a buffer for mixing */
m = s->mixer;
- if (s->mixersz < n) {
- s->mixersz = n;
- if ((m = realloc(m, n*sizeof(float))) == nil) {
- fprint(2, "no memory for mixer, giving up\n");
- return n;
+ if (s->mixersz < total) {
+ if ((m = realloc(m, total*sizeof(float))) != nil) {
+ s->mixer = m;
+ s->mixersz = total;
+ } else {
+ total = s->mixersz;
}
- s->mixer = m;
}
- if (s->silent)
- memset(b, 0, sizeof(*b)*n);
- else if (s->crossvoice && cross < 1) /* no cross point? wait for it */
- return total;
+ /* silence first */
+ memset(b, 0, sizeof(*b)*total);
- /* multiple voices and crossvoicing with a cross point known? shut up the main voice */
- if (cross > 0){
- s->silent = 1;
- memset(b+cross, 0, sizeof(*b)*(n-cross));
- /* start mixing into the crosspoint */
- b += cross;
- m += cross;
- n -= cross;
- }
+ v = s->voice;
+ cross = -1;
+ for (i = 0, n = total; i < nelem(s->voice) && n > 0; i++, v++) {
+ if (v->dsp == nil)
+ continue;
- quiet = -1;
- least = 999.0;
- for (i = 1; i < s->nvoice && n > 1; i++) {
- if (s->crossvoice && i > 1 && cross < 1) /* no cross point? wait for it */
- break;
+ switch (v->state) {
+ case Vsilent: /* nothing to do here, ignore it */
+ continue;
+ case Vstarting: /* should start playing it */
+ if (s->crossvoice && i > 0 && v[-1].state != Vsilent) {
+ /* always start another voice at zero crossing to avoid clicks & pops */
+ if (cross < 0) /* no crossing yet? give up for now */
+ goto done;
+ memset(b+cross, 0, sizeof(*b)*(n - cross)); /* silence the prev voice after zero crossing */
+ cross++; /* leaving one as 0 */
+ n -= cross;
+ b += cross;
+ cross = 0;
+ shutup(s, &v[-1]);
+ }
+ v->state = Vplaying;
+ v->samples = 0;
+ /* slippery floor */
+ case Vplaying:
+ if (n < 1)
+ break;
+ if (fs->dsp.read(v->dsp, m, n) != n)
+ return -1;
+ v->samples += n;
- n = fs->dsp.read(s->voice[i], m, n);
- level = 0.0f;
- cross = 0;
- for (j = 0; j < n; j++) { /* FIXME this could be faster */
- if (s->crossvoice && i+1 < s->nvoice && cross < 1 && j > 0) {
- /* crossvoicing AND we have at least one more on top, see if we can shut *this* one up */
- if (CROSS(m[j-1], m[j])) {
+ for (j = 0; j < n; j++) {
+ b[j] += m[j];
+ if (s->crossvoice && cross < 0 && j > 0 && CROSS(m[j-1], m[j]))
cross = j;
- level = 0.0f; /* shut up forever */
- b += j;
- m += j;
- n -= j;
- break;
- }
}
- level = MAX(level, m[j]);
- b[j] += m[j];
}
-
- if (level <= 0.0f) { /* this one is silent, delete it */
- fs->dsp.free(s->voice[i]);
- s->voice[i] = nil;
- /* freed one, don't try to free more */
- least = -999.0f;
- quiet = -1;
- } else if (level < least) {
- quiet = i;
- least = level;
- }
}
- /* free up a voice if there are too many */
- if (s->nvoice >= Maxvoice && quiet >= 0) {
- fs->dsp.free(s->voice[quiet]);
- s->voice[quiet] = nil;
- }
+done:
/* relocate the voices */
- for (i = j = 1; i < s->nvoice; i++) {
- if (s->voice[i] != nil)
- s->voice[j++] = s->voice[i];
+ for (i = j = 1; i < nelem(s->voice); i++) {
+ s->voice[j] = s->voice[i];
+ if(s->voice[j].dsp != nil)
+ j++;
}
- s->nvoice = j;
+ memset(s->voice+j, 0, sizeof(*s->voice)*(i - j));
return total;
}
@@ -233,8 +228,10 @@
{
int i;
- for (i = 0; i < a->state->nvoice; i++)
- fs->dsp.reset(a->state->voice[i]);
+ for (i = 0; i < nelem(a->state->voice); i++) {
+ if (a->state->voice[i].dsp != nil)
+ fs->dsp.reset(a->state->voice[i].dsp);
+ }
}
static void
@@ -293,9 +290,10 @@
{
State *s;
Auxdsp *clone;
+ Voice *v;
void *so, *sc;
u8int *tmp;
- int r, sz;
+ int i, r, sz, nused;
if (a->ui->writestr == nil) {
werrstr("not implemented");
@@ -305,10 +303,15 @@
s = a->state;
s->crossvoice |= a->ui->crossvoice;
+ for (i = nused = 0; i < nelem(s->voice); i++) {
+ if (s->voice[i].state != Vsilent)
+ nused++;
+ }
+
/* autovoice means every write needs to use (possibly) a new instance */
- if (a->ui->autovoice && (!s->silent || s->nvoice > 1) && fs->dsp.clone != nil && fs->dsp.state != nil) {
+ if (a->ui->autovoice && nused > 0 && fs->dsp.clone != nil && fs->dsp.state != nil) {
/* now do the impossible */
- so = fs->dsp.state(s->voice[0], &sz);
+ so = fs->dsp.state(s->voice[0].dsp, &sz);
tmp = malloc(sz);
memmove(tmp, so, sz); /* save the original state */
/* write to the original and check if a new voice has to be created */
@@ -316,7 +319,7 @@
free(tmp);
return r;
}
- clone = fs->dsp.clone(s->voice[0]); /* clone the original */
+ clone = fs->dsp.clone(s->voice[0].dsp); /* clone the original */
sc = fs->dsp.state(clone, &sz);
memmove(sc, so, sz); /* copy the changed state to the clone */
memmove(so, tmp, sz); /* revert the original state */
@@ -323,13 +326,14 @@
free(tmp);
/* now we have the original dsp intact, with a cloned dsp actually having the changed state */
- s->voice = realloc(s->voice, (s->nvoice+1)*sizeof(*s->voice));
- s->voice[s->nvoice++] = clone;
+ v = newvoice(s, clone);
+ v->state = Vstarting;
+
return r;
}
/* in any other case, just write to the original */
- s->silent = 0;
+ s->voice[0].state = Vstarting;
return a->ui->writestr(a->ui, type, data);
}
--- a/fs.h
+++ b/fs.h
@@ -1,7 +1,16 @@
+enum {
+ Vsilent,
+ Vstarting,
+ Vplaying,
+
+ Maxvoice = 16,
+};
+
typedef struct Aux Aux;
typedef struct Auxdsp Auxdsp;
typedef struct Fs Fs;
typedef struct State State;
+typedef struct Voice Voice;
typedef enum {
Xclone,
@@ -55,15 +64,19 @@
}dsp;
};
+struct Voice {
+ uvlong samples;
+ Auxdsp *dsp;
+ int state;
+};
+
struct State {
float *mixer;
int mixersz;
- Auxdsp **voice;
- int nvoice;
+ Voice voice[Maxvoice];
int crossvoice; /* copied from metadata on ctl write (gate) */
- int silent; /* that's about the first voice only */
};
void fsinit(void *fs);