Commit Diff


commit - ed992560fbc706d9790080ef4361e6a48790dd69
commit + b232461a63350252e93337c1c0c30e53f2a8cb0a
blob - 32d3db82039f22d66de13c59bd13e50f1cb25cd7
blob + 0d4c17d99a4faa43760f441d3787183fa9d67c9e
--- cw.c
+++ cw.c
@@ -22,6 +22,7 @@ static struct sio_hdl *hdl;
 static struct sio_par par;
 static size_t framesz, bufsz;
 static sample_t *buf;
+static int writ, pos;			/* total frames and current play pos. */
 
 /* envelope parameters */
 static int rise = 4;			/* ramp up (attack) time [ms] */
@@ -185,20 +186,46 @@ sample_out(int f, int x, int n, int total, int nramp)
 	sio_write(hdl, buf, n * framesz);
 }
 
+
+#ifdef DEBUGTIME
+#include <stdio.h>
+#include <sys/time.h>
+
+static struct timespec t0;
+
+static void
+timestamp(const char *str)
+{
+	struct timespec t, dt;
+	double ms;
+
+	clock_gettime(CLOCK_MONOTONIC, &t);
+	timespecsub(&t, &t0, &dt);
+	ms = dt.tv_sec * 1000 + dt.tv_nsec / 1000000.0;
+	fprintf(stderr, "[%7.3f ms] %s", ms, str);
+}
+#else
+#define timestamp(S)
+#endif
+
 /* play a beep of the desired frequency and length */
-void
+int
 beep(int f, int len)
 {
 	int n;		/* samples left to play */
 	int x;		/* sample position */
 	int total, nrise, nfall;
 
-	len -= hightime;		/* audible time in the ramps */
+	len -= hightime;			/* audible time in the ramps */
 
 	total = par.rate * len / 1000;
 	nrise = par.rate * rise / 1000;
 	nfall = par.rate * fall / 1000;
 
+#ifdef DEBUGTIME
+	timestamp("beep");
+	fprintf(stderr, "(%d ms): begin writing, %d frames\n", len, total);
+#endif
 	n = total;
 	x = 0;
 	while (n >= bufsz) {
@@ -208,29 +235,53 @@ beep(int f, int len)
 	}
 	if (n > 0)
 		sample_out(f, x, n, total, nfall);
+	writ += total;
+	timestamp("beep(): done writing\n");
 
 	inbeep = 1;
+	return (writ - pos) * 1000 / par.rate;	/* remaining play time [ms] */
 }
 
 /* play a pause of the desired length */
-void
+int
 silence(int len)
 {
-	int n;
+	int n, total;
 
 	memset(buf, 0, bufsz * framesz);
 
 	if (inbeep)
-		len -= lowtime;		/* inaudible time in the ramps */
-	n = par.rate * len / 1000;
+		len -= lowtime;			/* inaudible time in ramps */
+
+	total = par.rate * len / 1000;
+
+#ifdef DEBUGTIME
+	timestamp("silence");
+	fprintf(stderr, "(%d ms): begin writing, %d frames\n", len, total);
+#endif
+	n = total;
 	while (n >= bufsz) {
 		sio_write(hdl, buf, bufsz * framesz);
 		n -= bufsz;
 	}
 	if (n > 0)
 		sio_write(hdl, buf, n * framesz);
+	writ += total;
+	timestamp("silence(): done writing\n");
 
 	inbeep = 0;
+	return (writ - pos) * 1000 / par.rate;	/* remaining play time [ms] */
+}
+
+static void
+cb_onmove(void *arg, int delta)
+{
+	pos += delta;
+#ifdef DEBUGTIME
+	timestamp("onmove: ");
+	fprintf(stderr, "delta = %d frames (%d ms), pos = %d/%d\n", delta,
+	    delta * 1000 / par.rate, pos, writ);
+#endif
 }
 
 void
@@ -246,6 +297,7 @@ cwinit(void)
 	par.le = SIO_LE_NATIVE;		/* native byte order */
 	par.msb = 0;			/* pad the higher bits */
 	par.pchan = 1;			/* one playback channel */
+	par.xrun = SIO_SYNC;		/* keep exact timing during underrun */
 	if (!sio_setpar(hdl, &par))
 		errx(1, "sio_setpar failed");
 	if (!sio_getpar(hdl, &par))
@@ -265,16 +317,22 @@ cwinit(void)
 		errx(1, "got MSB-aligned samples instead of LSB");
 	sample_max = (1 << (par.bits - 1)) - 1;
 
+#ifdef DEBUGTIME
+	fprintf(stderr, "par.rate	= %6u Hz\n", par.rate);
+	fprintf(stderr, "par.bufsz	= %6u (%3u ms)\n", par.bufsz,
+	    par.bufsz * 1000 / par.rate);
+	fprintf(stderr, "par.appbufsz	= %6u (%3u ms)\n", par.appbufsz,
+	    par.appbufsz * 1000 / par.rate);
+#endif
+
 	/* allocate our buffer */
 	framesz = par.bps * par.pchan;
 	bufsz = (par.appbufsz + par.round - 1) / par.round * par.round;
 	if ((buf = malloc(bufsz * framesz)) == NULL)
 		err(1, "malloc");
-}
 
-void
-cwstart()
-{
+
+	/* get config from the environment */
 	getenvpct("CW_AMPLITUDE", &amp);
 	getenvnum("CW_ATTACK", "ms", &rise);
 	fall = rise;	/* default */
@@ -294,11 +352,49 @@ cwstart()
 	lowtime = (rise + fall) / 2;
 	hightime = rise + fall - lowtime;
 
-	sio_start(hdl);
+	/* register callback to track our position in the output */
+	sio_onmove(hdl, cb_onmove, NULL);
 }
 
 void
+cwstart()
+{
+#ifdef DEBUGTIME
+	struct timespec t1, delta;
+	double ms;
+
+	clock_gettime(CLOCK_MONOTONIC, &t0);		/* resets the clock */
+	timestamp("cwstart()\n");
+#endif
+	writ = pos = 0;
+	if (!sio_start(hdl))
+		errx(1, "sio_start failed");
+#ifdef DEBUGTIME
+	clock_gettime(CLOCK_MONOTONIC, &t1);
+	timespecsub(&t1, &t0, &delta);
+	ms = delta.tv_sec * 1000 + delta.tv_nsec / 1000000.0;
+	timestamp("sio_start() ");
+	fprintf(stderr, "took %f ms\n", ms);
+#endif
+}
+
+void
 cwstop(void)
 {
-	sio_stop(hdl);
+#ifdef DEBUGTIME
+	struct timespec t0, t1, delta;
+	double ms;
+
+	timestamp("cwstop()\n");
+	clock_gettime(CLOCK_MONOTONIC, &t0);
+#endif
+	if (!sio_stop(hdl))
+		errx(1, "sio_stop failed");
+#ifdef DEBUGTIME
+	clock_gettime(CLOCK_MONOTONIC, &t1);
+	timespecsub(&t1, &t0, &delta);
+	ms = delta.tv_sec * 1000 + delta.tv_nsec / 1000000.0;
+	timestamp("sio_stop() ");
+	fprintf(stderr, "took %f ms\n", ms);
+#endif
 }
blob - 75927a85de175288c360be5e27058fdec27214e9
blob + 613e77647e70605af48b71e73f79cb6e6cb6fcf1
--- cw.h
+++ cw.h
@@ -8,8 +8,8 @@ rampfun_t ramp_lin, ramp_sqr, ramp_sin, ramp_cos, ramp
 void cwinit(void);
 void cwstart(void);
 void cwstop(void);
-void beep(int, int);
-void silence(int);
+int beep(int, int);
+int silence(int);
 
 /* getenv helpers */
 int getenvnum(const char *, const char *, int *);
blob - 7be34dc5fec95edd82a8c8011e032459258be59d
blob + 748852b89b60b9fe5bfaf97e7e733f96d617b36e
--- morse.c
+++ morse.c
@@ -130,7 +130,7 @@ int	lookup(char *);
 void	decode(char *);
 char	*encode(int);
 void	show(char *);
-void	play(char *);
+int	play(char *);
 void	teach(void);		/* from teach.c */
 
 static int sflag = 0;
@@ -206,8 +206,6 @@ main(int argc, char *argv[])
 		if (!tflag)
 			if (pledge("stdio audio", NULL) == -1)
 				err(1, "pledge");
-
-		cwstart();
 	} else {
 		/* no audio, no teacher - drop access to all but stdio */
 		if (pledge("stdio", NULL) == -1)
@@ -239,8 +237,6 @@ main(int argc, char *argv[])
 			morse(ch);
 		show("...-.-");			/* SK */
 	}
-	if (aflag || tflag)
-		cwstop();
 	return 0;
 }
 
@@ -376,34 +372,43 @@ show(char *s)
 			putchar('t');
 	}
 	if (aflag)
-		play(s);
+		play(s);	// XXX wait until sound has finished playing
 	printf("\n");
 }
 
-void
+int
 play(char *s)
 {
+	int pause, rem = 0;
+
 	if (*s == '\0')
-		silence(7 * pditlen - ditlen);	/* inter-word pause */
+		pause = 7 * pditlen - ditlen;	/* inter-word pause */
 	else
-		silence(3 * pditlen - ditlen);	/* intra-word pause */
+		pause = 3 * pditlen - ditlen;	/* intra-word pause */
 
+	cwstart();
+	rem = silence(pause);
 	while (*s) {
 		if (*s == '.')
 			beep(freq, ditlen);
 		else if (*s == '-')
 			beep(freq, 3 * ditlen);
 		s++;
-		silence(ditlen);
+		rem = silence(ditlen);
 	}
+	cwstop();
+
+	return rem;				/* remaining play time [ms] */
 }
 
 /* the audio hook for teacher mode */
-void
+int
 sound(char c)
 {
 	char *s;
 
 	if ((s = encode(c)) != NULL)
-		play(s);
+		return play(s);
+	else
+		return 0;
 }
blob - b1f2eaa242c81607932d3d6d87a5f9b6bd2a56ed
blob + 33debd4fe6388ec3a1e1a5a79545781ccafd84fb
--- morseplay.c
+++ morseplay.c
@@ -35,6 +35,7 @@ main(int argc, char **argv)
 	int nl = 1;	/* new line of input? */
 	int ws = 0;	/* whitespace seen? */
 	int dd = 0;	/* dot or dash seen? */
+	int pl = 0;	/* playback started (cwstart called)? */
 
 	setvbuf(stdout, NULL, _IONBF, 0);	/* disable output buffering */
 
@@ -64,37 +65,48 @@ main(int argc, char **argv)
 	cwinit();
 
 	/* main loop */
-	cwstart();
 	while ((ch = getchar()) != EOF) {
 		switch(ch) {
 		case '.':
+			if (!pl)
+				cwstart();
 			if (dd)
 				silence(len);
 			beep(freq, len);
-			dd = 1;
+			pl = dd = 1;
 			nl = ws = 0;
 			break;
 		case '-':
+			if (!pl)
+				cwstart();
 			if (dd)
 				silence(len);
 			beep(freq, 3 * len);
-			dd = 1;
+			pl = dd = 1;
 			nl = ws = 0;
 			break;
 		case ' ':
 		case '\t':
-			if (!ws)
+			if (!ws) {
+				if (!pl)
+					cwstart();
 				silence(3 * plen);
+				pl = 1;
+			}
 			ws = 1;
 			dd = 0;
 			break;
 		case '\n':
+			if (!pl)
+				cwstart();
 			if (nl)
 				silence(7 * plen);
 			else if (!ws)
 				silence(3 * plen);
+			cwstop();
+			// XXX wait until sound has finished playing
 			nl = ws = 1;
-			dd = 0;
+			pl = dd = 0;
 			break;
 		default:
 			continue;	/* do not echo */
@@ -103,7 +115,6 @@ main(int argc, char **argv)
 		if (echo)
 			putchar(ch);
 	}
-	cwstop();
 
 	return 0;
 }
blob - e554dc61f8bb868917fd0a06b18d214ef45cd015
blob + 8987ab4d704c00e4f4d165076a2480df5dc50380
--- morseteach.c
+++ morseteach.c
@@ -10,11 +10,12 @@ void teach(void);	/* from teach.c */
 FILE *fout, *fin;	/* our pipes to 'morse -a' */
 int Dflag = 0;		/* level of diagnostic output */
 
-void
+int
 sound(char c)
 {
 	fputc(c, fout);
 	while (fgetc(fin) != '\n') ;		/* wait for sound to finish */
+	return 0;
 }
 
 /*
blob - 577086ff4ac12eb079c791252762ac8c120c4d48
blob + c135883985cb742fd1e51598dafc674b826609ab
--- teach.c
+++ teach.c
@@ -61,7 +61,7 @@ struct timespec tpause;	/* time spent paused */
 
 /* externally supplied */
 extern int Dflag;	/* level of diagnostic output */
-void sound(char);	/* sound output routine */
+int sound(char);	/* sound output routine */
 
 double
 square(double x)
@@ -164,6 +164,7 @@ trial(int i, int ch)
 	double result;
 	int input;
 	int tries;
+	int latency;				/* return latency of sound() */
 
 	/*
 	 * Emphasize new/unknown symbols by a short pause and automatically
@@ -171,7 +172,8 @@ trial(int i, int ch)
 	 */
 	if (failprob[i] > 0.9)
 		sound(' ');			/* inter-word pause */
-	sound(ch);
+	latency = sound(ch);
+	//assert(latency == 0);		// XXX
 	clock_gettime(CLOCK_MONOTONIC, &t0);
 	if (failprob[i] > 0.9)
 		printf("%c\b", ch);
@@ -189,6 +191,7 @@ trial(int i, int ch)
 		clock_gettime(CLOCK_MONOTONIC, &t1);
 		timespecsub(&t1, &t0, &delta);
 		taken = delta.tv_sec + delta.tv_nsec / 1000000000.0;	/* s */
+		//taken += latency / 1000.0;
 		sigma = sqrt(vartime);
 
 		/* if return pressed, pause and show scores */
@@ -197,7 +200,7 @@ trial(int i, int ch)
 				return -1;
 
 			/* otherwise resound, reset timer, and restart */
-			sound(ch);
+			latency = sound(ch);
 			clock_gettime(CLOCK_MONOTONIC, &t0);
 			continue;
 		}
@@ -211,6 +214,10 @@ trial(int i, int ch)
 			return -1;
 		}
 
+		/* ignore premature answers */
+		if (taken < 0)
+			continue;
+
 		tries++;
 
 		/* if space pressed, resound and show hint */