shithub: neindaw

Download patch

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);