--- a
+++ b/mit-bih-arrhythmia-database-1.0.0/mitdbdir/src/dbnotes.c
@@ -0,0 +1,936 @@
+/* file: dbnotes.c	G. Moody	25 August 1988
+			Last revised:	6 December 1990
+Create database notes file (tbl/troff input format)
+
+Copyright (C) Massachusetts Institute of Technology 1990. All rights reserved.
+*/
+
+#include <stdio.h>
+#include <ecg/db.h>
+#include <ecg/ecgmap.h>
+
+/* Rhythm types.  These may be rearranged arbitrarily;  the order determines
+   the order in which rhythms are printed in the rhythm table.  Add a call to
+   `setrtab' and adjust N_RTYPES whenever another rhythm is added. */
+#define R_UNDEF	0
+#define NSR	1
+#define SBR	2
+#define BII	3
+#define B22	4
+#define B3	5
+#define SAB	6
+#define PREX	7
+#define AB	8
+#define EAR	9
+#define SVTA	10
+#define AFL	11
+#define AFIB	12
+#define PR	13
+#define NOD	14
+#define VBG	15
+#define VTG	16
+#define IVR	17
+#define VT	18
+#define VFL	19
+
+#define N_RTYPES	20	/* number of rhythm types, including R_UNDEF */
+
+/* ST and T change types. */
+#define STP	0	/* increase in ST level */
+#define STN	1	/* decrease in ST level */
+#define TP	2	/* increase in T amplitude */
+#define TXP	3	/* extreme increase in T amplitude */
+#define TN	4	/* decrease in T amplitude */
+#define TXN	5	/* extreme decrease in T amplitude */
+#define TBP	6	/* biphasic T-wave */
+
+#define N_STYPES	7	/* number of ST and T change types */
+
+
+#define PLUS	1	/* positive change */
+#define MINUS	2	/* negative change */
+#define XPLUS	3	/* extreme positive change */
+#define XMINUS	4	/* extreme negative change */
+#define BIPHASIC 5	/* change to biphasic form */
+
+#define STCH0	1	/* beginning of ST change episode */
+#define STCH1	2	/* extremum of ST change */
+#define STCH2	3	/* end of ST change episode */
+
+#define TCH0	4	/* beginning of T change episode */
+#define TCH1	5	/* extremum of T change */
+#define TCH2	6	/* end of T change episode */
+
+
+/* Noise type bit masks. */
+#define C0		0x00	/* signal 0 clean */
+#define C1		0x00	/* signal 1 clean */
+#define C2		0x00	/* signal 2 clean */
+#define C3		0x00	/* signal 3 clean */
+#define N0	     	0x01	/* signal 0 noisy */
+#define N1	     	0x02	/* signal 1 noisy */
+#define N2	     	0x04	/* signal 2 noisy */
+#define N3	     	0x08	/* signal 3 noisy */
+#define U0	     	0x11	/* signal 0 unreadable */
+#define U1	     	0x22	/* signal 1 unreadable */
+#define U2		0x44	/* signal 2 unreadable */
+#define U3	   	0x88	/* signal 3 unreadable */
+#define UNREADABLE   	0xff	/* all signals unreadable */
+
+#define N_NTYPES	256	/* number of noise types */
+#define N_RLEN	20		/* number of distinct run lengths */
+
+/* Values for info_type. */
+#define NO_INFO	0
+#define MITDB	1
+#define EDB	2
+#define VALEDB	3
+
+struct btabstruct {
+    long lcount;	/* number of beats of this type in learning period */
+    long tcount;	/* number of beats of this type in test period */
+} btab[ACMAX+1];
+
+struct rltabstruct {
+    long len;		/* number of beats in run */
+    long nruns;		/* number of runs of `len' beats */
+} artab[N_RLEN], vrtab[N_RLEN];
+
+struct rtabstruct {
+    long episodes;	/* number of episodes of this rhythm */
+    long duration;	/* duration of this rhythm (in sample intervals) */
+    char *rstring;	/* rhythm string as it appears in annotation file */
+    char *rslong;	/* rhythm name as it is printed by this program */
+    int min3r, max3r;	/* minimum/maximum length of 3 R-R intervals */
+} rtab[N_RTYPES];
+
+struct sttabstruct {
+    long episodes;	/* number of episodes of ST or T change of this type */
+    long duration;	/* duration of episodes (in sample intervals) */
+    char *ststring;	/* description of change as printed by this program */
+    int extremum;	/* extreme value of change */
+} sttab[N_STYPES][MAXSIG];
+
+struct ntabstruct {
+    long episodes;	/* number of episodes of this noise type */
+    long duration;	/* duration of this noise type (in sample intervals) */
+    char *nstring;	/* noise type as it is printed by this program */
+} ntab[N_NTYPES];
+
+long lsum, tsum;	/* numbers of beats of all types in the learning and
+			   test periods (tallied by `bstats') */
+long ttest;		/* time of the end of the test period */
+
+main(argc, argv)
+int argc;
+char *argv[];
+{
+    double t3min;
+    int a=0, age, c, i, info_type, magnitude, maxrate, minrate, noise = 0,
+	nsig, rhythm = R_UNDEF, rlen = -1, rr3, sign, st_calibrated = 0,
+	st_data = 0, t_data = 0, type, v=0;
+    static long n0, r0, tq0, tq1, tq2, tq3, s0[N_STYPES][MAXSIG];
+    struct siginfo si[MAXSIG];
+    struct anninfo ai;
+    struct ann annot;
+    static char buf[256], *infop, *p, *record, sex[10], umask;
+
+    /* Check command-line arguments. */
+    if (argc < 3) {
+	fprintf(stderr, "usage: %s annotator record\n", argv[0]);
+	exit(1);
+    }
+
+    /* Open the database files. */
+    ai.name = argv[1]; ai.stat = READ; record = argv[2];
+    if (annopen(record, &ai, 1) < 0 ||
+	(nsig = isigopen(record, si, -MAXSIG)) < 0)
+	exit(2);
+
+    /* Initialize variables. */
+    t3min = strtim("3:0");
+    ttest = strtim("5:0");
+
+    setrtab(R_UNDEF, NULL, "Unspecified rhythm");
+    setrtab(NSR, "N", "Normal sinus rhythm");
+    setrtab(SBR, "SBR", "Sinus bradycardia");
+    setrtab(BII, "BII", "2\\(de heart block");
+    setrtab(B22, "B22", "2\\(de heart block (Mobitz type II)");
+    setrtab(B3, "B3", "3\\(de heart block");
+    setrtab(SAB, "SAB", "Sino-atrial block");
+    setrtab(PREX, "PREX", "Pre-excitation (WPW)");
+    setrtab(AB, "AB", "Atrial bigeminy");
+    setrtab(EAR, "EAR", "Ectopic atrial rhythm");
+    setrtab(SVTA, "SVTA", "SVTA");
+    setrtab(AFL, "AFL", "Atrial flutter");
+    setrtab(AFIB, "AFIB", "Atrial fibrillation");
+    setrtab(PR, "P", "Paced rhythm");
+    setrtab(NOD, "NOD", "Nodal (junctional) rhythm");
+    setrtab(VBG, "B", "Ventricular bigeminy");
+    setrtab(VTG, "T", "Ventricular trigeminy");
+    setrtab(IVR, "IVR", "Idioventricular rhythm");
+    setrtab(VT, "VT", "Ventricular tachycardia");
+    setrtab(VFL, "VFL", "Ventricular flutter");
+
+    for (c = 0; c < nsig; c++) {
+	setstab(STP, c, "Positive ST dev.");
+	setstab(STN, c, "Negative ST dev.");
+	setstab(TP, c, "Positive T dev.");
+	setstab(TXP, c, "T dev. \\(>= 400 \\(*mV");
+	setstab(TN, c, "Negative T dev.");
+	setstab(TXN, c, "T dev. \\(<= \\(mi400 \\(*mV");
+	setstab(TBP, c, "Biphasic T");
+    }
+
+    switch (nsig) {
+      case 1:
+	setntab(C0, "Clean");
+	setntab(N0, "Noisy");
+	setntab(U0, "Unreadable");
+	setntab(UNREADABLE, "Unreadable");
+	umask = U0;
+	break;		/* so far, so good ... */
+      case 2:
+	setntab(C0|C1, "Both clean");
+	setntab(C0|N1, "Lower noisy");
+	setntab(C0|U1, "Lower unreadable");
+	setntab(N0|C1, "Upper noisy");
+	setntab(N0|N1, "Both noisy");
+	setntab(N0|U1, "Upper noisy, lower unreadable");
+	setntab(U0|C1, "Upper unreadable");
+	setntab(U0|N1, "Upper unreadable, lower noisy");
+	setntab(U0|U1, "Both unreadable");
+	setntab(UNREADABLE, "Unreadable");
+	umask = U0|U1;
+	break;		/* yikes! */
+      case 3:
+	setntab(C0|C1|C2, "All clean");
+	setntab(C0|C1|N2, "Lower noisy");
+	setntab(C0|C1|U2, "Lower unreadable");
+	setntab(C0|N1|C2, "Middle noisy");
+	setntab(C0|N1|N2, "Middle and lower noisy");
+	setntab(C0|N1|U2, "Middle noisy, lower unreadable");
+	setntab(C0|U1|C2, "Middle unreadable");
+	setntab(C0|U1|N2, "Middle unreadable, lower noisy");
+	setntab(C0|U1|U2, "Middle and lower unreadable");
+	setntab(N0|C1|C2, "Upper noisy");
+	setntab(N0|C1|N2, "Upper and lower noisy");
+	setntab(N0|C1|U2, "Upper noisy, lower unreadable");
+	setntab(N0|N1|C2, "Upper and middle noisy");
+	setntab(N0|N1|N2, "All noisy");
+	setntab(N0|N1|U2, "Lower unreadable, others noisy");
+	setntab(N0|U1|C2, "Upper noisy, middle unreadable");
+	setntab(N0|U1|N2, "Middle unreadable, others noisy");
+	setntab(N0|U1|U2, "Upper noisy, others unreadable");
+	setntab(U0|C1|C2, "Upper unreadable");
+	setntab(U0|C1|N2, "Upper unreadable, lower noisy");
+	setntab(U0|C1|U2, "Upper and lower unreadable");
+	setntab(U0|N1|C2, "Upper unreadable, middle noisy");
+	setntab(U0|N1|N2, "Upper unreadable, others noisy");
+	setntab(U0|N1|U2, "Middle noisy, others unreadable");
+	setntab(U0|U1|C2, "Upper and middle unreadable");
+	setntab(U0|U1|N2, "Lower noisy, others unreadable");
+	setntab(U0|U1|U2, "All unreadable");
+	setntab(UNREADABLE, "Unreadable");
+	umask = U0|U1|U2;
+	break;		/* arggggh! */
+/*    case 4:		   nonononononononononono!!!!!!! */
+      default:
+	umask = UNREADABLE;
+	break;
+    }
+
+    /* Read the annotation file. */
+    while (getann(0, &annot) >= 0) {
+
+    /* Count the annotation in the test or learning period as appropriate. */
+	if (annot.time >= ttest) btab[annot.anntyp].tcount++;
+	else btab[annot.anntyp].lcount++;
+
+	if (isqrs(annot.anntyp)) {
+
+	    /* Update the ectopic activity tables if appropriate. */
+	    switch (map2(annot.anntyp)) {
+		case NORMAL:	if (a > 0) { aupdate(a); a = 0; }
+				if (v > 0) { vupdate(v); v = 0; }
+				break;
+		case SVPB:	if (v > 0) { vupdate(v); v = 0; }
+				a++;
+				break;
+		case PVC:
+		case FUSION:	if (a > 0) { aupdate(a); a = 0; }
+				v++;
+				break;
+	    }
+
+	    /* Keep track of the times of the last 4 beats. */
+	    tq0 = tq1; tq1 = tq2; tq2 = tq3; tq3 = annot.time;
+
+	    /* Update the count of consecutive beats in the current rhythm. */
+	    rlen++;
+
+	    /* Check various conditions which might make a heart rate
+	       measurement invalid. */
+	    switch (rhythm) {
+	      case NSR:
+	      case SBR:
+	      case BII:
+	      case B22:
+	      case B3:
+	      case SAB:
+	      case PREX: 
+		if (map2(annot.anntyp) != NORMAL) rlen = -1; break;
+	      case AB:
+	      case NOD:
+		if (map1(annot.anntyp) != NORMAL) rlen = -1; break;
+	      case EAR:
+	      case SVTA:
+		if (map2(annot.anntyp) != SVPB) rlen = -1; break;
+	      case PR:
+		if (annot.anntyp != PACE) rlen = -1; break;
+	      case IVR:
+	      case VT:
+		if (map1(annot.anntyp) == NORMAL) rlen = -1; break;
+	      case VFL:
+		if (annot.anntyp != FLWAV) rlen = -1; break;
+	      default:
+		break;
+	    }	
+
+	    /* If a valid heart rate can be measured, update the records of
+	       maximum and minimum rate for the current rhythm.  Note that
+	       these can be determined from the minimum and maximum R-R
+	       intervals;  what is actually used here is the sum of three
+	       consecutive intervals. */
+	    if (rlen >= 3) {
+		rr3 = tq3 - tq0;
+ 		if (rr3 > rtab[rhythm].max3r) rtab[rhythm].max3r = rr3;
+		if (rr3 < rtab[rhythm].min3r) rtab[rhythm].min3r = rr3;
+	    }
+	}
+	else if (annot.anntyp == RHYTHM) {
+	    /* Don't count the interval from the beginning of the record to
+	       the first rhythm label as `unspecified rhythm' unless the first
+	       beat label occurs before the first rhythm label. */
+	    if (tq3 > 0L) {
+		rtab[rhythm].episodes++;
+		rtab[rhythm].duration += annot.time - r0;
+		r0 = annot.time;
+	    }
+	    /* Identify the new rhythm. */
+	    for (i = 1, rhythm = R_UNDEF; i < N_RTYPES; i++)
+		if (strcmp(annot.aux+2, rtab[i].rstring) == 0) {
+		    rhythm = i;
+		    break;
+		}
+	    if (rhythm == SVTA || rhythm == VT) rlen = 0;
+	    else rlen = -1;
+	}
+	else if (annot.anntyp == STCH || annot.anntyp == TCH) {
+	    /* Identify the nature of the ST or T change from the aux field.
+	       The syntax of the aux field is one of:
+	         "(" <type> [<signal designation>] <sign>
+		 "A" <type> [<signal designation>] <sign> [<magnitude>]
+		 <type> [<signal designation>] <sign> ")"
+	       where <type> is "ST" or "T", <signal designation> is 1 or 2
+	       decimal digits, <sign> is "+", "++", "-", "--", or "BP", and
+	       <magnitude> is 2-4 decimal digits.  ("++" and "--" appear only
+	       in T-wave change annotations;  they designate extreme (>= 0.4 mV
+	       changes in T-wave amplitude.)  The signal designation is
+	       the signal number (e.g., signal 0 is designated by "0",
+	       etc.);  in records which contain only one signal (such as those
+	       in the VALE DB), the signal designation is omitted.  The
+	       magnitude is given in microvolts, and may also be omitted (as
+	       in the VALE DB).
+	     */
+	    if (annot.aux == NULL)
+		fprintf(stderr,
+			"warning: %s annotation without text field at %s\n",
+			ecgstr(annot.anntyp), timstr(annot.time));
+	    else {
+		p = annot.aux+1;
+		switch (*p) {
+		  case '(':
+		    if (annot.anntyp == STCH) {
+			type = STCH0;
+			p += 3;		/* skip `(ST' */
+		    }
+		    else {
+			type = TCH0;
+			p += 2;		/* skip `(T' */
+		    }
+		    break;
+		  case 'A':
+		    if (annot.anntyp == STCH) {
+			type = STCH1;
+			p += 3;		/* skip `(ST' */
+		    }
+		    else {
+			type = TCH1;
+			p += 2;		/* skip `(T' */
+		    }
+		    break;
+		  case 'S':
+		    type = STCH2;
+		    p += 2;		/* skip `ST' */
+		    break;
+		  case 'T':
+		    type = TCH2;
+		    p++;		/* skip `T' */
+		    break;
+		  default:
+		    fprintf(stderr,
+			"warning: %s annotation with text field %s at %s\n",
+			ecgstr(annot.anntyp), annot.aux, timstr(annot.time));
+		    type = 0;
+		    break;
+		}
+		if (nsig > 1) {
+		    if ((c = atoi(p)) > 9) {
+			p++;
+		    }
+		    p++;
+		}
+		else c = 0;
+		switch (*p++) {
+		  case '+':
+		    if (*p == '+') {
+			sign = XPLUS;
+			p++;
+		    }
+		    else
+			sign = PLUS;
+		    break;
+		  case '-':
+		    if (*p == '-') {
+			sign = XMINUS;
+			p++;
+		    }
+		    else
+			sign = MINUS;
+		    break;
+		  case 'B':
+		    if (*p == 'P') {
+			sign = BIPHASIC;
+			p++;
+		    }
+		    break;
+		  default:
+		    fprintf(stderr,
+			"warning: %s annotation with text field %s at %s\n",
+			ecgstr(annot.anntyp), annot.aux, timstr(annot.time));
+		    sign = 0;
+		    break;
+		}
+		if (*p && *p != ')') {
+		    if ((magnitude = atoi(p)) > 0)
+			st_calibrated = 1;
+		}
+		else magnitude = 0;
+		if (0 <= c && c < MAXSIG) switch (type) {
+		  case STCH0:	/* beginning of ST change episode */
+		    if (sign == PLUS) {
+			st_data = 1;
+			s0[STP][c] = annot.time;
+		    }
+		    else if (sign == MINUS) {
+			st_data = 1;
+			s0[STN][c] = annot.time;
+		    }
+		    break;
+		  case STCH1:	/* extremum of ST change */
+		    if (sign == PLUS && magnitude > sttab[STP][c].extremum)
+			sttab[STP][c].extremum = magnitude;
+		    else if (sign == MINUS &&
+			     magnitude > sttab[STN][c].extremum)
+			sttab[STN][c].extremum = magnitude;
+		    break;
+		  case STCH2:	/* end of ST change episode */
+		    if (sign == PLUS) {
+			sttab[STP][c].episodes++;
+			sttab[STP][c].duration += annot.time - s0[STP][c];
+			s0[STP][c] = 0L;
+		    }
+		    else if (sign == MINUS) {
+			sttab[STN][c].episodes++;
+			sttab[STN][c].duration += annot.time - s0[STN][c];
+			s0[STN][c] = 0L;
+		    }
+		    break;
+		  case TCH0:	/* beginning of T change episode */
+		    if (sign == PLUS) {
+			t_data = 1;
+			s0[TP][c] = annot.time;
+		    }
+		    else if (sign == MINUS) {
+			t_data = 1;
+			s0[TN][c] = annot.time;
+		    }
+		    else if (sign == XPLUS) {
+			t_data = 1;
+			s0[TXP][c] = annot.time;
+		    }
+		    else if (sign == XMINUS) {
+			t_data = 1;
+			s0[TXN][c] = annot.time;
+		    }
+		    else if (sign == BIPHASIC) {
+			t_data = 1;
+			s0[TBP][c] = annot.time;
+		    }
+		    break;
+		  case TCH1:	/* extremum of T change */
+		    if ((sign == PLUS || sign == XPLUS) &&
+			magnitude > sttab[TP][c].extremum)
+			sttab[TP][c].extremum = magnitude;
+		    else if ((sign == MINUS || sign == XMINUS) &&
+			     magnitude > sttab[TN][c].extremum)
+			sttab[TN][c].extremum = magnitude;
+		    break;
+		  case TCH2:	/* end of T change episode */
+		    if (sign == PLUS) {
+			sttab[TP][c].episodes++;
+			sttab[TP][c].duration += annot.time - s0[TP][c];
+			s0[TP][c] = 0L;
+		    }
+		    else if (sign == MINUS) {
+			sttab[TN][c].episodes++;
+			sttab[TN][c].duration += annot.time - s0[TN][c];
+			s0[TN][c] = 0L;
+		    }
+		    else if (sign == XPLUS) {
+			sttab[TXP][c].episodes++;
+			sttab[TXP][c].duration += annot.time - s0[TXP][c];
+			s0[TXP][c] = 0L;
+		    }
+		    else if (sign == XMINUS) {
+			sttab[TXN][c].episodes++;
+			sttab[TXN][c].duration += annot.time - s0[TXN][c];
+			s0[TXN][c] = 0L;
+		    }
+		    else if (sign == BIPHASIC) {
+			sttab[TBP][c].episodes++;
+			sttab[TBP][c].duration += annot.time - s0[TBP][c];
+			s0[TBP][c] = 0L;
+		    }
+		    break;
+		  default:
+		    break;
+		}
+	    }
+	}
+	else if (annot.anntyp == NOISE) {
+	    /* Don't count the interval from the beginning of the record to
+	       the first signal quality label as `clean' unless the first beat
+	       label occurs before the first signal quality label. */
+	    if (tq3 > 0L) {
+		ntab[noise].episodes++;
+		ntab[noise].duration += annot.time - n0;
+		n0 = annot.time;
+	    }
+	    noise = annot.subtyp & 0xff;
+
+	    /* Unreadable segments invalidate rate measurements. */
+	    if ((noise & umask) == umask) rlen = -1;
+	}
+    }
+
+    /* At the end of the annotation file, count any run in progress and adjust
+       the counters for the current rhythm and noise type, and ST and T states.
+    */
+    if (a) aupdate(a);
+    if (v) aupdate(v);
+    if (strtim("e") > annot.time) annot.time = strtim("e");
+    rtab[rhythm].episodes++;
+    rtab[rhythm].duration += annot.time - r0;
+    for (c = 0; c < nsig; c++) {
+	for (i = 0; i < N_STYPES; i++)
+	    if (s0[i][c] > 0L) {
+		sttab[i][c].episodes++;
+		sttab[i][c].duration += annot.time - s0[i][c];
+	    }
+	if (sttab[TXP][c].episodes > 0)
+	    sttab[TXP][c].extremum = sttab[TP][c].extremum;
+	if (sttab[TXN][c].episodes > 0)
+	    sttab[TXN][c].extremum = sttab[TN][c].extremum;
+    }
+    ntab[noise].episodes++;
+    ntab[noise].duration += annot.time - n0;
+
+    /* Read the first line of info, if any. */
+    infop = getinfo(record);
+    if (infop == NULL)
+	info_type = NO_INFO;
+    else {
+	while (*infop == ' ' || *infop == '\t')
+	    infop++;
+	if (*infop == 'A') {	/* `Age: ...' as in European ST-T DB */
+	    sscanf(infop, "Age: %d Sex: %s", &age, sex);
+	    info_type = EDB;
+	}
+	else if (*infop == 'R') {	/* `Reference: ...' as in VALE DB */
+	    age = -1; sex[0] = '?';
+	    info_type = VALEDB;
+	}
+	else {			/* fixed format as in MIT DB */
+	    sscanf(infop, "%d%s", &age, sex);
+	    info_type = MITDB;
+	}
+    }
+
+    /* Start a "keep" (to force troff to print the following section in a
+       single column), and print the section header line. */
+    printf(".KS\n.SH\n");
+
+    printf("Record %s  (", record);
+    for (i = 0; i < nsig; i++) {
+	if (i > 0) fputs(", ", stdout);
+	fputs(si[i].desc, stdout);
+    }
+    fputs("; ", stdout);
+    switch (sex[0]) {
+      case 'f':
+      case 'F':	fputs("female, age ", stdout); break;
+      case 'm':
+      case 'M': fputs("male, age ", stdout); break;
+      default:	fputs("age ", stdout); break;
+    }
+    if (age > 0) printf("%d)\n", age);
+    else fputs("not recorded)\n", stdout);
+
+    switch (info_type) {
+      case MITDB:
+	/* Print the medications section (the second line of info). */
+	if (infop = getinfo(NULL)) {
+	    while (*infop == ' ' || *infop == '\t')
+		infop++;
+	    printf(".LP\n\\fIMedications:\\fR %s\n", infop);
+	}
+	break;
+      default:
+	break;
+    }
+
+    /* Print the beat table. */
+    printf(".TS\nexpand;\nlw(1i) c c c\nl r r r.\n");
+    printf("\\fIBeats\tBefore 5:00\tAfter 5:00\tTotal\\fR\n");
+    bstats(NORMAL, "Normal");
+    bstats(LBBB, "Left BBB");
+    bstats(RBBB, "Right BBB");
+    bstats(APC, "APC");
+    bstats(ABERR, "Aberrated APC");
+    bstats(NPC, "Junctional premature");
+    bstats(SVPB, "SVPC");
+    bstats(PVC, "PVC");
+    bstats(RONT, "R-on-T PVC");
+    bstats(FUSION, "Fusion PVC");
+    bstats(FLWAV, "Ventricular flutter wave");
+    bstats(AESC, "Atrial escape");
+    bstats(NESC, "Junctional escape");
+    bstats(SVESC, "Supraventricular escape");
+    bstats(VESC, "Ventricular escape");
+    bstats(PACE, "Paced");
+    bstats(PFUS, "Pacemaker fusion");
+    bstats(NAPC, "Blocked APC");
+    bstats(UNKNOWN, "Unclassifiable");
+    printf("\\fITotal\t%ld\t%ld\t%ld\\fR\n", lsum, tsum, lsum+tsum);
+    printf(".TE\n.KE\n");
+
+    /* Print the supraventricular ectopy table, if necessary. */
+    if (artab[0].len > 1 || artab[0].nruns > 1 || artab[1].len > 0) {
+	int i, j, k, l;
+
+	printf(".KS\n.TS\nlw(1.125i) l.\n");
+	printf("\\fISupraventricular ectopy:\\fR");
+        for (i = 0; i < N_RLEN; i++) {
+	    for (j = 0, l = 9999; j < N_RLEN; j++)
+		if (0 < artab[j].len && artab[j].len < l) l = artab[k = j].len;
+	    if (l == 9999) break;
+	    switch (l) {
+	      case 1:
+		printf("\t%d isolated beat%s\n",
+		       artab[k].nruns, (artab[k].nruns > 1) ? "s" : "");
+		break;
+	      case 2:
+		printf("\t%d couplet%s\n",
+		       artab[k].nruns, (artab[k].nruns > 1) ? "s" : "");
+		break;
+	      default:
+		printf("\t%d run%s of %d beats\n",
+		       artab[k].nruns, (artab[k].nruns > 1) ? "s" : "",
+		       artab[k].len);
+		break;
+	    }
+	    artab[k].len = 0;
+	}
+	printf(".TE\n.KE\n");
+    }
+
+    /* Print the ventricular ectopy table, if necessary. */
+    if (vrtab[0].len > 1 || vrtab[0].nruns > 1 || vrtab[1].len > 0) {
+	int i, j, k, l;
+
+	printf(".KS\n.TS\nlw(1.125i) l.\n");
+	printf("\\fIVentricular ectopy:\\fR");
+        for (i = 0; i < N_RLEN; i++) {
+	    for (j = 0, l = 9999; j < N_RLEN; j++)
+		if (0 < vrtab[j].len && vrtab[j].len < l) l = vrtab[k = j].len;
+	    if (l == 9999) break;
+	    switch (l) {
+	      case 1:
+		printf("\t%d isolated beat%s\n",
+		       vrtab[k].nruns, (vrtab[k].nruns > 1) ? "s" : "");
+		break;
+	      case 2:
+		printf("\t%d couplet%s\n",
+		       vrtab[k].nruns, (vrtab[k].nruns > 1) ? "s" : "");
+		break;
+	      default:
+		printf("\t%d run%s of %d beats\n",
+		       vrtab[k].nruns, (vrtab[k].nruns > 1) ? "s" : "",
+		       vrtab[k].len);
+		break;
+	    }
+	    vrtab[k].len = 0;
+	}
+	printf(".TE\n.KE\n");
+    }
+
+    /* Print the rhythm table. */
+    printf(".KS\n.TS\nexpand;\nlw c rw(0.4i) rw(0.4i)\nl c n r.\n");
+    printf("\\fIRhythm\tRate\tEpisodes\tDuration\\fR\n");
+    for (i = 0; i < N_RTYPES; i++)
+	if (rtab[i].episodes != 0L) {
+	    printf("%s\t", rtab[i].rslong);
+	    if (rtab[i].max3r > 0)
+		printf("%d", minrate = (int)(t3min/rtab[i].max3r + 0.5));
+	    else
+		minrate = 0;
+	    if (rtab[i].min3r != 0xffff) {
+		maxrate = (int)(t3min/rtab[i].min3r + 0.5);
+		if (maxrate > minrate) printf("\\-%d", maxrate);
+	    }
+	    else if (minrate == 0)
+		printf("\\-");
+	    printf("\t%ld\t%s\n", rtab[i].episodes, timstr(rtab[i].duration));
+	}
+    printf(".TE\n.KE\n");
+
+    /* Print the ST and T-change table, if available. */
+    if (st_data || t_data) {
+	printf(".KS\n.TS\nexpand;\nl");
+	if (st_calibrated) printf(" r");
+	printf(" rw(0.4i) rw(0.4i)\nl");
+	if (st_calibrated) printf(" n");
+	printf(" n r.\n\\fIST\\-T state");
+	if (st_calibrated) printf("\tExtremum");
+	printf("\tEpisodes\tDuration\\fR\n");
+	for (c = 0; c < nsig; c++) {
+	    int header_printed = 0;
+
+	    for (i = 0; i < N_STYPES; i++) {
+		if (sttab[i][c].episodes != 0L) {
+		    if (header_printed == 0) {
+			switch (nsig) {
+			  case 1:
+			    break;
+			  case 2:
+			    if (c == 0) printf("\\fI(upper signal)\\fR\n");
+			    else printf("\\fI(lower signal)\\fR\n");
+			    break;
+			  case 3:
+			    if (c == 0) printf("\\fI(upper signal)\\fR\n");
+			    else if (c == 1)
+				printf("\\fI(middle signal)\\fR\n");
+			    else printf("\\fI(lower signal)\\fR\n");
+			    break;
+			  default:
+			    printf("(signal %d)\n", c);
+			    break;
+			}
+			header_printed = 1;
+		    }
+		    printf("%s\t", sttab[i][c].ststring);
+		    if (st_calibrated) {
+			if (sttab[i][c].extremum > 0) {
+			    switch (i) {
+			      case STP:
+			      case TP:
+			      case TXP:	printf("\\(pl"); break;
+			      case STN:
+			      case TN:
+			      case TXN: printf("\\(mi"); break;
+			    }
+			    printf("%d\t", sttab[i][c].extremum);
+			}
+			else
+			    printf("\\-\t");
+		    }
+		    printf("%ld\t%s\n", sttab[i][c].episodes,
+			   timstr(sttab[i][c].duration));
+		}
+	    }
+	}
+	printf(".TE\n.KE\n");
+    }
+
+    /* Print the signal quality table. */
+    printf(".KS\n.TS\nexpand;\nl rw(0.4i) rw(0.4i)\nl n r.\n");
+    printf("\\fISignal quality\tEpisodes\tDuration\\fR\n");
+    for (i = 0; i < N_NTYPES; i++)
+	if (ntab[i].episodes != 0L) {
+	    if (ntab[i].nstring == NULL) {
+		static char ns[40];
+
+		sprintf(ns, "Signal quality code %d", i);
+		ntab[i].nstring = ns;
+	    }
+	    printf("%s\t%ld\t%s\n", ntab[i].nstring, ntab[i].episodes,
+		   timstr(ntab[i].duration));
+	}
+    printf(".TE\n");	/* (the "keep" ends after the "notes" section) */
+
+    /* Print a "notes" section if there is any additional info available. */
+    switch (info_type) {
+      case EDB:
+	if (infop = getinfo(NULL)) {
+	    while (*infop == ' ' || *infop == '\t')
+		infop++;
+	    if ('a' <= *infop && *infop <= 'z') *infop += 'A' - 'a';
+	    printf(".IP \\fINotes:\\fR .375i\n%s\n", infop);
+	    while (infop = getinfo(NULL)) {
+		while (*infop == ' ' || *infop == '\t')
+		    infop++;
+		printf(".br\n%s\n", infop);
+	    }
+	}
+	else
+	    printf(".LP\n");
+	break;
+      case MITDB:
+	if (infop = getinfo(NULL)) {
+	    while (*infop == ' ' || *infop == '\t')
+		infop++;
+	    printf(".IP \\fINotes:\\fR .375i\n%s\n", infop);
+	    while (infop = getinfo(NULL)) {
+		while (*infop == ' ' || *infop == '\t')
+		    infop++;
+		printf("%s\n", infop);
+	    }
+	}
+	else
+	    printf(".LP\n");
+	break;
+      case VALEDB:
+	if (infop = getinfo(NULL)) {
+	    while (*infop == ' ' || *infop == '\t')
+		infop++;
+	    if (strncmp(infop, "Reference:", 10) == 0)
+		infop = getinfo(NULL);
+	}
+	if (infop) {
+	    while (*infop == ' ' || *infop == '\t')
+		infop++;
+	    printf(".IP \\fINotes:\\fR .375i\n%s\n", infop);
+	    while (infop = getinfo(NULL)) {
+		while (*infop == ' ' || *infop == '\t')
+		    infop++;
+		printf(".br\n%s\n", infop);
+	    }
+	}
+	else
+	    printf(".LP\n");
+	break;
+      default:
+	printf(".LP\n");
+	break;
+    }
+    printf(".KE\n");
+
+/* Close the database files and quit. */
+    dbquit();
+    exit(0);
+}
+
+setrtab(i, rst, rsl)
+int i;
+char *rst, *rsl;
+{
+    if (0 <= i && i < N_RTYPES) {
+	rtab[i].episodes = 0L;
+	rtab[i].duration = strtim("0.5");
+	rtab[i].rstring = rst;
+	rtab[i].rslong = rsl;
+	rtab[i].min3r = 0xffff;
+	rtab[i].max3r = 0;
+    }
+}
+
+setstab(i, signal, sst)
+int i, signal;
+char *sst;
+{
+    if (0 <= i && i < N_STYPES && 0 <= signal && signal < MAXSIG) {
+	sttab[i][signal].episodes = 0L;
+	sttab[i][signal].duration = strtim("0.5");
+	sttab[i][signal].ststring = sst;
+	sttab[i][signal].extremum = 0;
+    }
+}
+
+setntab(i, nst)
+int i;
+char *nst;
+{
+    if (0 <= i && i < N_NTYPES) {
+	ntab[i].episodes = 0L;
+	ntab[i].duration = strtim("0.5");
+	ntab[i].nstring = nst;
+    }
+}
+	
+bstats(i, s)  /* print a line of the beat table if beats of type i were seen */
+int i;
+char *s;		/* English description of beat type i */
+{
+    if (btab[i].tcount > 0L || btab[i].lcount > 0L) {
+	printf("%s\t", s);
+	if (btab[i].lcount > 0L) {
+	    printf("%ld\t", btab[i].lcount);
+	    lsum += btab[i].lcount;
+	}
+	else
+	    printf("\\-\t");
+	if (btab[i].tcount > 0L) {
+	    printf("%ld\t", btab[i].tcount);
+	    tsum += btab[i].tcount;
+	}
+	else
+	    printf("\\-\t");
+	printf("%ld\n", btab[i].lcount + btab[i].tcount);
+    }
+}
+
+aupdate(i)	/* Record that an i-beat SV run was observed */
+int i;
+{
+    int j;
+
+    for (j = 0; j < N_RLEN-1; j++) {
+	if (artab[j].len == i) break;
+	else if (artab[j].len == 0) { artab[j].len = i; break; }
+    }
+    artab[j].nruns++;
+}
+
+vupdate(i)	/* Record that an i-beat V run was observed */
+int i;
+{
+    int j;
+
+    for (j = 0; j < N_RLEN-1; j++) {
+	if (vrtab[j].len == i) break;
+	else if (vrtab[j].len == 0) { vrtab[j].len = i; break; }
+    }
+    vrtab[j].nruns++;
+}