/**
 * @file tapcleaner.c
 * Reduce noise in low-level tape images
 * @author Marko Mäkelä (msmakela@nic.funet.fi)
 */

/* #define VERBOSE */ /* to be really verbose */

/* to do: compute the frequencies of the quantized pulses? */

/* Copyright © 2003 Marko Mäkelä.

   This file is part of TAPCLEAN, a program for processing symmetric
   pulse streams on computer data tapes.

   TAPCLEAN is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   TAPCLEAN is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
   License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

/** Program version */
#define VERSION "1.0"
/** Release date (day.month.year) */
#define DATE "1.2.2003"

#include <stdio.h>
#include <string.h>

/** Histogram of pulse widths */
static unsigned hist[256];

/** the tape image header */
static char tapheader[20];

/** pulse translation buffer */
static unsigned char buf[256];

int
main (int argc, char** argv)
{
  int i;
  if (argc < 2) {
    fputs ("usage: tapcleaner file1.tap file2.tap ....\n", stderr);
    return 0;
  }
  for (i = 1; i < argc; i++) {
    int c;
    FILE* f = fopen (argv[i], "rb+");
    if (!f) {
      fputs (argv[i], stderr);
      perror (": fopen");
      return 1;
    }
    if (sizeof tapheader != fread (tapheader, 1, sizeof tapheader, f) ||
	memcmp (tapheader + 3, "-TAPE-RAW", 9)) {
      fputs (argv[i], stderr);
      fputs (": unrecognised tape image header\n", stderr);
      fclose (f);
      return 1;
    }
    fputs (argv[i], stderr);
    fputs (": ", stderr);
    fwrite (tapheader, 1, 12, stderr);
    fprintf (stderr, " version %u.%u\n",
	     (unsigned char) tapheader[12],
	     (unsigned char) tapheader[13]);
    do {
      /** start of the block */
      fpos_t start;
      /** number of pulses in the block */
      unsigned long num;
    next:
      num = 0;
      /* clear the pulse histogram */
      memset (hist, 0, sizeof hist);
      if (fgetpos (f, &start)) {
      fgetpos_fail:
	fputs (argv[i], stderr);
	perror (": fgetpos");
	fclose (f);
	return 2;
      }
      while ((c = fgetc (f)) != EOF) {
	if (c) {
	  if (!++hist[c])
	    hist[c]--; /* truncate the histogram on overflow */
	  num++;
	}
	else {
	  /** number of recognised pulses */
	  unsigned pcnt = 0;
	  /** median pulse widths (indexed 0..3) */
	  unsigned char median[4];
#ifdef VERBOSE
	  for (c = 0; c < 256; c++)
	    if (hist[c])
	      fprintf (stderr, "%d: %u\n", c, hist[c]);
#endif /* VERBOSE */
	  /* quantize the pulse widths */
	  memset (median, 0, sizeof median);
	  for (c = 0; c < 254; c++) {
	    if (!hist[c]) {
	      /*
	       * require a gap of at least 3 units
	       * between adjacent pulse widths
	       */
	      if (!hist[c + 1] && !hist[c + 2] &&
		  pcnt < sizeof median && median[pcnt])
		pcnt++;
	    }
	    else if (pcnt >= sizeof median) {
	      fputs (*argv, stderr);
	      fputs (": too many pulses; ignoring the longest ones\n", stderr);
	      break;
	    }
	    else if (!median[pcnt] || hist[c] > hist[median[pcnt]])
	      median[pcnt] = c;
	  }
	  fputs ("median:", stderr);
	  for (c = 0; c < pcnt; c++)
	    fprintf (stderr, " %u", median[c]);
	  putc ('\n', stderr);

	  /* transform the histogram to a quantization table */
	  for (c = pcnt; c--; ) {
	    const unsigned m = median[c];
	    unsigned low, high;
	    low = c > 1 ? ((m + median[c - 1]) >> 1) - 1 : 0;
	    high = c < pcnt - 1 ? (m + median[c + 1]) >> 1 : 256;
	    while (high-- > low)
	      hist[high] = m;
	  }
	  /* rewind to the beginning of the block */
	  if (fsetpos (f, &start)) {
	  fsetpos_fail:
	    fputs (argv[i], stderr);
	    perror (": fsetpos");
	    fclose (f);
	    return 2;
	  }
	  /* translate the block */
	  while (num) {
	    fpos_t fpos;
	    unsigned n = num < sizeof buf ? num : sizeof buf;
	    num -= n;
	    if (fgetpos (f, &fpos))
	      goto fgetpos_fail;
	    if (n != fread (buf, 1, n, f)) {
	      fputs (argv[i], stderr);
	      perror (": fread");
	      fclose (f);
	      return 2;
	    }
	    for (c = n; c--; )
	      buf[c] = hist[buf[c]];
	    if (fsetpos (f, &fpos))
	      goto fsetpos_fail;
	    if (n != fwrite (buf, 1, n, f)) {
	      fputs (argv[i], stderr);
	      perror (": fwrite");
	      fclose (f);
	      return 2;
	    }
	  }
	  c = fgetc (f);
	  if (c) {
	    fputs (argv[i], stderr);
	    fputs (": inconsistency detected, aborting\n", stderr);
	    fclose (f);
	    return 2;
	  }
	  if (tapheader[12] != 1 || tapheader[13]) {
	    /* read and skip the 24-bit pause counter */
	    if (fgetc (f) == EOF ||
		fgetc (f) == EOF ||
		fgetc (f) == EOF)
	      break;
	  }
	  goto next;
	}
      }
    }
    while (0);
    fclose (f);
  }
  return 0;
}

