///////////////////////////////////////////////////////////////////
// Harvest entropy from audio I/O system.

// In part distantly related to audio-entropyd.c by Damien Miller
// Those parts Copyright 1999 Damien Miller <djm@mindrot.org>
// and licensed under the GNU Public License version 2
// Please see the file COPYING for more details.

using namespace std;		/* needed by some versions of the compiler */

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include "Getopt.h"
#include <unistd.h>
extern "C" {
// This should have been provided by unistd.h but it wasn't;
// I have no idea why not:
       pid_t getpgid(pid_t pid);
};
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

#include <asm/types.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <iostream>	/* needed by some compilers */
#include <string>
#include <vector>
#include "minmax.h"
#include <valarray>
// Two defines needed to prevent bit-rot 
// due to poorly-documented changes to the ALSA API
#define ALSA_PCM_OLD_HW_PARAMS_API
#define ALSA_PCM_OLD_SW_PARAMS_API
#include <alsa/asoundlib.h>
#include "sha1.h"
#include "gauss.h"
#include "probe.h"
#include "dbuffer.h"
#include "stretch.h"
#include "turbid.h"
#include "feed_krng.h"
#include "daemon_fork.h"
#include "alsactl.h"


#ifndef SND_CTL_RDONLY		/* backwards compatibility */
#  warning You may want to use ./excl.patch to implement ALSA exclusive access.
#  define SND_CTL_RDONLY 0
#endif

const double kT(1.38e-23 * (273.15 + 20));
const int FPP(1024);		// frames per period

const double twopi = 2 * pi;

// reference level is saturation minus 15 dB
const double ref_level(INT_MAX * pow(10, -15./20.));
// sine wave RMS is sqrt(2) less than peak:
const double ref_level_2(ref_level/sqrt(2.0));


typedef int32_t datum;		// all internal calculations are 32bit

// a few non-constant global variables:
string pidfile;
int verbose=0;
int d_xrun=0;			// be verbose about underrun and overrun
string progname;

class alsa_dsp{
public:
  snd_pcm_t* phndl;		// pcm handle
  snd_output_t* errlog;
  snd_pcm_hw_params_t *hwparams;
  int nchan;			// samples per frame; 2 ==> stereo
  int rate;			// frames per second
  int sbits;			// significant bits
//  int sizeofsamp;		// bytes per sample
  snd_pcm_format_t format;
  const char* moniker;
  void error(const char*, const int);
  int setup(const string carddevice, 
  	snd_pcm_stream_t stream, const int rate,
	const int lurking);
// Constructor doesn't do much,
// so it is cheap to allocate one that you're not
// necessarily going to use:
  inline alsa_dsp(const char* mm)
	   : errlog(0), moniker(mm) {}
};


class Keeper{
public:
  const alsa_dsp* dsp;
  const int fpp;		// frames per period
  valarray<datum> buf;
  int finbuf;			// frames in buffer at the moment
  int nchan;
  Keeper(const alsa_dsp*, const int fsize);
};

class snark {
public:
  snark();		// constructor
  string card_device;	// principal input, e.g. "0,0"
  string rando_fname;	// fifo: principal output
  string stretch_fname;	// fifo: stretched-orandom  output
  string pid_fname;
  string wcheck_fname;	// place to write raw data (checkfile)
  int nframe;
  int onepass;		// exit after one pass
  int desired_rate;
  int channel_mask;
  double bandwidth;
  double kout;	// output voltage scale, assuming sine-wave
			// ... which has RMS 3dB below ref_level
  double vmeter;	// voltage (from voltmeter)
  double qsig;		// quantum of significance
  double r_in;		// input impedance
  double zref;		// reference resistor
  double calfreq;
  double calival;
  double calamp;
  int daemonflag;
  int lurking;
  int feed_k_flag;
  string mixer_ctlfile;


// not commandline options, but derived therefrom:
  int bufpipe[2];	// for double buffering
  int obs;		// output buffer size (for double buffering)
  int rando_fd;		// principal output
  int stretch_fd;	// streched random output
  int wcheck_fd;
  string justcard;	// the "card" part of that, before the comman
  double sps;		// entropy (bits per sample)
  double snooze;	// time to snooze between actions if not
			//   actually writing to output fifo,
			//   e.g. during calibration.

//// functions
  void usage(const int err);
  void cmdline(int argc, char** argv);
  void open_devices();
  void main_loop(alsa_dsp*, const double snooze);
  int read_stuff(alsa_dsp*, datum*);
  void analyze(alsa_dsp* dsp, valarray<datum>& cap_buffer, const int finbuf);
  void snark::printx(
   const char* name, 
   const double foo_1, 
   const double foo_2,
   double plogp,
   const alsa_dsp* dsp,
   const double vmeter
  );
};

// Forward references:

void calout(const double ofreq, const double calival,
	const double gain, alsa_dsp* phndl, char** argv);
void config_mixer(const string ctlfile, const string justcard);
void write_pid_file(const string fname);
void dummy(int signum);
void trap_exit(int signum);
void trap_detach(int signum);
void drain(const char* draindev);
void drain(const int fd);
int isfile(const char* fname);

// Functions 

void snark::usage(const int err) {
  (err ? cerr : cout) << 
"Harvest entropy from the audio I/O system.\n"
"Usage: " << progname << " [options]\n\n"
"Options:\n"
"--buffer-size,	-b []  Number of frames to read at a time. [= "
	<< nframe << "].\n"
"--card,  	-c []  Sound card, subdevice to use. [= " 
	<< card_device << "].\n"
"--detach,  	-d     Detach from terminal (daemonize).\n"
"--mixer-ctl,	-m []  Mixer set-up from alsactl file. "
	"[= " << mixer_ctlfile << "];\n"
"		-m \"\" ==> don't set up; just use inherited settings.\n"
"--output-fifo	-o []  Output fifo [= " << rando_fname << "].\n" 
"--stretch-fifo	-s []  Stretched-random fifo [= " << stretch_fname << "].\n" 
"--pidfile-dir	-P []  File to contain our PID [= " << pid_fname << "].\n"
"--Amplitude, 	-A []  Calibrator amplitude / dB "
			"[= " << calamp << "] relative to -15dBFS.\n"
"--Bandwidth, 	-B []  Bandwidth / Hz\n"
"--Channel-mask	-C []  1==>left 2==>right 3==> stereo etc.\n"
"--Frequency, 	-F []  Calibrator frequency / Hz (useful= 440).\n"
"--Interval, 	-I []  Calibrator 2nd chan freq relative to 1st"
			" [= " << calival << "].\n"
"--frame-rate,	-N []  Audio measurement rate, frames per second;\n"
"		-N 0 ==> default ==> max rate supported by the hardware.\n"
"--Vmeter,	-V []  Voltmeter reading, for calibration.\n"
"--Quantum-sig,	-Q []  Quantum of significance, from calibration.\n"
"--Rin,		-R []  Input resistance, from calibration.\n"
"--feed-kernel,	-f     Feed entropy to /dev/random\n"
"--Zref,  	-Z []  External reference resistor / Ohms "
			"[= " << zref << "]\n"
"--verbose,  	-v     Print extra debugging info (-vv ==> even more).\n"
"--lurk,  	-l     Lurk (instead of exiting) if device busy.\n"
"--write-check,	-w []  Write checkfile containing one buffer of audio.\n"
/////"		-w \"\" ==> default ==> don't bother.\n"
"--1pass,	-1     Exit after one pass through main loop.\n"
<< endl;
}

// Just like snd_pcm_readi(), 
// except the buff is always of the type of datum (int32_t),
// no matter what type the raw hardware uses.
// Return value:
//   nonnegative: # of frames read
//   negative: error code
int readi(const alsa_dsp* dsp, datum* buff, const int nframe){
  if (dsp->format == SND_PCM_FORMAT_S32_LE) {
    return snd_pcm_readi(dsp->phndl, buff, nframe);
  } else if (dsp->format == SND_PCM_FORMAT_S16_LE) {
    valarray<int16_t> tmp(nframe * dsp->nchan);
    int rslt = snd_pcm_readi(dsp->phndl, &tmp[0], nframe);
    if (rslt <= 0) return rslt;
    int16_t* from (&tmp[0]);
    datum* to (buff);
    int fudge(1<<16);
    for (int ii = 0; ii < rslt * dsp->nchan; ii++){
      *to++ = *from++ * fudge;
    }
    return rslt;
  } else {
    cerr << "Don't know how to convert dsp data from format "
         <<   snd_pcm_format_name(dsp->format) << endl;
    exeunt(1);
  }
  return 0;		// defeat stupid compiler warning
}

// Just like snd_pcm_writei(), 
// except the buff is always of the type ofdatum (int32_t),
// no matter what type the raw hardware uses.
// Return value:
//   nonnegative: # of frames written
//   negative: error code
int writei(const alsa_dsp* dsp, const datum* buff, const int nframe){
  if (dsp->format == SND_PCM_FORMAT_S32_LE) {
    return snd_pcm_writei(dsp->phndl, buff, nframe);
  } else if (dsp->format == SND_PCM_FORMAT_S16_LE) {
    valarray<int16_t> tmp(nframe * dsp->nchan);
    int16_t* to (&tmp[0]);
    const datum* from (buff);
    const int fudge(1<<16);
    for (int ii = 0; ii < nframe * dsp->nchan; ii++){
      *to++ = *from++ / fudge;
    }
    return snd_pcm_writei(dsp->phndl, &tmp[0], nframe);
  } else if (dsp->format == SND_PCM_FORMAT_S8) {
    valarray<char> tmp(nframe * dsp->nchan);
    char* to (&tmp[0]);
    const datum* from (buff);
    const int fudge(1<<24);
    for (int ii = 0; ii < nframe * dsp->nchan; ii++){
      *to++ = *from++ / fudge;
    }
    return snd_pcm_writei(dsp->phndl, &tmp[0], nframe);
  } else {
    cerr << "Don't know how to convert dsp data to format "
          <<   snd_pcm_format_name(dsp->format) << endl;
    exeunt(1);
  }
  return 0;		// defeat stupid compiler warning
}

// Just like the library routine of the same name, except:
// 1) The first arg is a "keeper",
//   which means that we will accept writes that are
//   not an integer multiple of the period.
// 2) The second arg is a datum* (not void*).
//   We call writei() to convert things to the sample-size 
//   of the actual device.
snd_pcm_sframes_t snd_pcm_writei(Keeper& kk, const datum* buffer,
	snd_pcm_uframes_t nframes){
  int rslt;
  int todo(nframes);
  int head(0);		// frames taken from head of buffer
  const datum* mybuf(buffer);
  datum* start = &kk.buf[0];
  if (kk.finbuf) {
    head = kk.fpp - kk.finbuf;
    memcpy(start + kk.nchan*kk.finbuf, buffer, kk.nchan*sizeof(datum)*head);
    rslt = writei(kk.dsp, start, kk.fpp);
    if (rslt <= 0) return rslt;		// no change in kk    
    if (rslt != kk.fpp) return -98;
    todo -= head;
    kk.finbuf = 0;
  }
  mybuf += kk.nchan*head;
  rslt = writei(kk.dsp, mybuf, todo);
  if (rslt < 0) return rslt;	// some error, let caller deal with it
  if (rslt == 0) return head;		// head got written, nothing more
// OK, at this point rslt must be positive.
  int left(todo - rslt);		// number of frames left to do....
  if (!left) return nframes;		// Nothing?  Great!
  if (left < kk.fpp) {		// can we keep it for next time?
    kk.finbuf = left;
    memcpy(start, mybuf + kk.nchan*rslt, kk.nchan*sizeof(datum)*left);
    return nframes;
  }
// here with a short write that doesn't fit in our keep-buffer:
  kk.finbuf = 0;
  return head + rslt;
}


// Constructor
Keeper::Keeper(const alsa_dsp* _dsp, const int _fpp)
  : dsp(_dsp),			// remember the args
    fpp(_fpp), 	
    buf(dsp->nchan * _fpp), 	// allocate the buffer
    finbuf(0),
    nchan(_dsp->nchan)
{} // no code, just initializers (above)


// Constructor
snark::snark() : 
  card_device("0,0"),
  rando_fname("/dev/hrandom"),
  stretch_fname("/dev/srandom"),
  pid_fname("/var/run/" + progname + ".pid"),
  wcheck_fname(""),
  nframe(2048),
  onepass(0),
  desired_rate(0),
  channel_mask(-1),
  bandwidth(0),
  kout(0),
  vmeter(0),
  qsig(0),
  r_in(0),
  zref(1e6),			// assume reference = 1 megohm 
  calfreq(0),			// default is no calibrator
  calival(4./3.),
  calamp(0.),			// default amplitude (dB relative to -15dBFS)
  daemonflag(0),
  lurking(0),
  feed_k_flag(0),
  mixer_ctlfile(""),
  obs(512),	// there are two buffers, so actual storage is twice this
  rando_fd(-1),
  stretch_fd(-1),
  wcheck_fd(-1),
  sps(0),
  snooze(.5)
{}	// no code, just the initializers (above)

void alsa_dsp::error(const char* msg, const int val) {

  if (val != -999) {
    cerr << "Device won't accept parameter '" << msg << "'"
    	 << " value '" << val << "'" << endl;
  }
  else {
    cerr << msg << " for " << moniker << endl;
  }
  snd_pcm_hw_params_dump(hwparams, errlog);
  int sbits = snd_pcm_hw_params_get_sbits(hwparams);
  if (sbits > 0) {
    cerr << "Allegedly " << sbits << " significant bits per sample." << endl;
  } else {
// Can't determine sigbits if the card is capable of
// multiple sample-sizes and we haven't chosen one yet.
    cerr << "Can't (yet) determine sigbits per sample: " 
    	<< snd_strerror(sbits) << endl;
    sbits = 0;
  }

  snd_pcm_close(phndl);
  snd_output_close(errlog);
  exeunt(1);
}

int alsa_dsp::setup(const string carddev, 
	snd_pcm_stream_t stream, 
	const int desired_rate,
	const int lurking  ){
  int err;

// Connect error reporting to stderr
  if (!errlog) snd_output_stdio_attach(&errlog, stderr, 0);  

  int mode = lurking ? 0 : SND_PCM_NONBLOCK;
  err = snd_pcm_open(&phndl, carddev.c_str(), stream, mode);
  if (err < 0){
    fprintf(stderr, "Error opening soundcard %s for %s: %s\n", 
	carddev.c_str(), moniker, snd_strerror(err));
    snd_output_close(errlog);
    return 1;
  }
  
// now that we've grabbed the device, without blocking
// if it was busy, we now switch to blocking mode for all I/O:
  err = snd_pcm_nonblock(phndl, 0);	

// Allocate a hwparams struct...
  snd_pcm_hw_params_alloca(&hwparams);
// ... and initialize it to the "wide open" ranges
// allowed by phndl:
  err = snd_pcm_hw_params_any(phndl, hwparams);
  if (err < 0) error("Initialization error", -999);

// User can force a dump of parameters,
// here at a nice early stage:
  if (desired_rate == -1) error("Possibilities are:", -999);

// Access method, interleaved or non-interleaved
  err = snd_pcm_hw_params_set_access(phndl, hwparams,
      SND_PCM_ACCESS_RW_INTERLEAVED);
  if (err < 0) error("access method", SND_PCM_ACCESS_RW_INTERLEAVED);

// Try various sample formats:
  do {			// so I can use break instead of goto.
    format = SND_PCM_FORMAT_S32_LE;
    err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
    if (err >= 0) break;

    format = SND_PCM_FORMAT_S16_LE;
    err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
    if (err >= 0) break;

    format = SND_PCM_FORMAT_S8;
    err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
    if (err >= 0) break;

    error("DSP error: format not S32_LE nor S16_LE nor S8", -999);
  }  while (0);		// execute the block once

// Sample rate
  rate = desired_rate;
  if (!rate) rate = snd_pcm_hw_params_get_rate_max(hwparams, 0);

  err = snd_pcm_hw_params_set_rate(phndl, hwparams, rate, 0);
  if (err < 0) error("rate", rate);

// Number of channels we want, stereo = 2
  nchan = snd_pcm_hw_params_get_channels_max(hwparams);
  err = snd_pcm_hw_params_set_channels(phndl, hwparams, nchan);
  if (err < 0) error("channels", nchan);

// The period size. For all practical purposes this is synonymous 
// to OSS/Free's fragment size.
// Note that this in frames (frame = nr_channels * sample_width)
// For example, a setup using FPP=1024, stereo, 16-bit format
// gives a period size of 4096 bytes (1024 x 2 x 2 bytes).
  if (1) {
    err = snd_pcm_hw_params_set_period_size(phndl, hwparams, FPP, 0);
    if (err < 0) error("period-size", FPP);
  }

// The number of periods we want to allocate, 4 is reasonable
  err = snd_pcm_hw_params_set_periods(phndl, hwparams, 4, 0);
  if (err < 0) error("#-of-periods", 4);

// Finally set up our hardware with the selected values
  err = snd_pcm_hw_params(phndl, hwparams);
  if (err < 0) {
    fprintf(stderr, "Unable to set hardware parameter:\n");
    snd_pcm_hw_params_dump(hwparams, errlog);
    return 2;
  }  

  err = sbits = snd_pcm_hw_params_get_sbits(hwparams);
  if (sbits <= 0 || sbits > 32) {
// if driver won't tell us how many are significant,
// assume they all are:
    sbits = snd_pcm_samples_to_bytes(phndl, 8);
    fprintf(stderr, "Trouble %d determining sig bits; assuming %d\n",
    	err, sbits);
  }


  if (verbose >= 2) {
    fprintf(stderr, ">>>>>> After setup (%s)\n", moniker);
    snd_pcm_hw_params_dump(hwparams, errlog);
  }

// At this point you can start sending PCM data to the device  
  return 0;
}

string crtRed   ("\033[1;31m");
string crtNormal("\033[0;39m");

vector<int> clean_me;

void trapme(){
  signal(SIGHUP, trap_exit);		// hangup
  signal(SIGINT, trap_exit);		// interrupt
  signal(SIGTERM, trap_exit);		// terminate
  signal(SIGPIPE, trap_exit);		// broken pipe
  signal(SIGUSR1, trap_detach);		// daemon_fork kills parent
  signal(SIGCHLD, SIG_DFL);
}


double rms(const int* ptr, const int nn){
  if (!nn) return 0;
  double val;
  double sum(0);
  for (int ii = 0; ii < nn; ii++){
    val = ptr[ii];
    sum += val*val;
  }
  sum /= nn;
  return sqrt(sum);
}

int main(int argc, char** argv) {
  double probe_level(rms(probe, nprobe));
  if (abs(probe_level/ref_level - 1) > 1e-9) {
    cerr << "Probe_level / ref_level mismatch " 
    	<< probe_level/ref_level - 1 << endl;
// should never happen, unless bogus probe signal is
// compiled in.
    exit(1);
  }

  progname = argv[0];
  unsigned int where = progname.rfind('/');	// find final slash
  if (where != progname.npos) {
    progname = progname.substr(1+where);
  }
  snark foo;				// holder for cmdline options ...
  foo.cmdline(argc, argv);		// ... process them
  trapme();				// install trap handlers
  daemon_fork myfork;
  if (foo.daemonflag) myfork.setup();	// so we know the right pid
  write_pid_file(foo.pid_fname);	// write it to file

  {
    int logflags = LOG_CONS | LOG_PID;
    if (!foo.daemonflag) logflags |= LOG_PERROR;
    openlog(progname.c_str(), logflags, LOG_DAEMON);
  }
  
// Lurkers should detach early, so we don't hang the
// init.d/turbid startup script.
// Non-lurkers should not detach early, to increase the
// chance of detecting errors and exiting with a meaningful
// error status.

  if (foo.lurking) myfork.detach();

// be sure to setup the capture handler first, so 
// on a soundblaster16 capture gets the 16-bit DMA
// channel and calout gets the 8-bit channel
  alsa_dsp in("capture");
  int err;
  err = in.setup("hw:" + foo.card_device, 
  	SND_PCM_STREAM_CAPTURE, foo.desired_rate, foo.lurking);
  if (err) exit(1); 		// msgs have already been printed

  alsa_dsp out("calout");
  if (foo.calamp < -200) foo.calfreq = 0;  // too far down ==> off
  if (foo.calfreq) {		// off ==> don't even initialize
    err = out.setup("hw:" + foo.card_device,
	  SND_PCM_STREAM_PLAYBACK, foo.desired_rate, foo.lurking);
    if (err) exit(1); 		// msgs have already been printed
#ifdef disgusting
    usleep(100000);  		// let hardware settle down ...
	  // (SB16 has nasty transients right after initialization)
#endif
  }

// Be sure to configure mixer  _after_ opening the pcm handlers,
// because we want to hold exclusive access to the mixer (control 
// device) but snd_pcm_open wants to temporarily open the control
// device for its own reasons.
  config_mixer(foo.mixer_ctlfile, foo.justcard);

  foo.open_devices();		// random-output, raw-output, etc.

  if (verbose) syslog(LOG_NOTICE, "starting %s", progname.c_str());
  myfork.detach();
// Now that detach has done the setsid, we can fork our subprocesses:
  if (foo.sps && foo.rando_fd >= 0) 
    clean_me.push_back(dbuffer(foo.bufpipe, foo.rando_fd, foo.obs));
  if (foo.sps && foo.stretch_fd >= 0) 
    clean_me.push_back(stretch(foo.bufpipe, foo.stretch_fd, foo.obs));

  if (foo.calfreq) calout(foo.calfreq, foo.calival,
		foo.calamp, &out, argv);

  if (foo.feed_k_flag) feed_krng(foo.bufpipe, verbose, argv);
// do the following close() after all consumers (dbuffer _and_
// feed_krng) have been started.  It means the main program
// will be killed via SIGPIPE if/when the last consumer dies.
// Whether it is wise to keep running if dbuffer has died
// is an unanswered question.
  close(foo.bufpipe[0]);	
  foo.main_loop(&in, foo.snooze);
  exeunt(0);
}

string purify(const string foo) {
  string rslt;
  for (unsigned int ii = 0; ii < foo.length(); ii++) {
    char ch = foo[ii];
    if (isupper(ch)) ch += 'a' - 'A';
    if (isalnum(ch)) rslt += ch;
  }
  return rslt;
}

// return a string like "sb16.ctl"
// based on ALSA's notion of the driver name
string invent_ctlfile(const string justcard){
    string driver("unknown");
    snd_ctl_t* chndl;		// control handle
    string xxx = "hw:" + justcard;
    int err = snd_ctl_open(&chndl, xxx.c_str(), SND_CTL_RDONLY);
    if (err < 0) {
      chndl = 0;
      cerr << "Error opening control channel to card " 
	  << justcard << endl;
      // proceed with driver = "unknown" 
    } else {
      snd_ctl_card_info_t *info;
      snd_ctl_card_info_alloca(&info);
      err = snd_ctl_card_info(chndl, info);
      if (err < 0) {
	cerr << "Can't get card ID info: " << err << endl;
      } else {
#if 0
  // example: hw:0
	cerr << "Name:      " << snd_ctl_name(chndl) << endl;
  // example: card0 (but could be changed in modules.conf):
	cerr << "Card ID:   " << snd_ctl_card_info_get_id(info) << endl;
  // example:  M Audio Delta 1010 at 0x1400, irq 22
	cerr << "Longname:  " << snd_ctl_card_info_get_longname(info) << endl;
  // example: ICE1712
	cerr << "Driver:    " << snd_ctl_card_info_get_driver(info) << endl;
	cerr << "Mixername: " << snd_ctl_card_info_get_mixername(info) << endl;
#endif
	driver = snd_ctl_card_info_get_driver(info);
      }
    }
    return purify(driver) + ".ctl";
}

static string digits = "0123456789";

void dump_io() {
  for (int ii = 0; ii<getdtablesize(); ii++){
    int rslt = fcntl(ii, F_GETFL); 
    if (rslt != -1) {
      cout << "FD " << ii << " flags " << rslt << endl;
    } else if (0) {
      cout << "FD " << ii << " errno " << errno
		  << " == " << strerror(errno) << endl;
    }
  }
}

// Process command-line switches, et cetera
void snark::cmdline(int argc, char **argv) {

// Note basic default values were set up in constructor.
// Now process options:
  int ch;
  time_t mytime;
  struct tm* mytm;
  const int ALT(128);
  static struct option long_options[] = {
    {"buffer-size",	1, NULL, 'b'},
    {"card",		1, NULL, 'c'},
    {"detach",		0, NULL, 'd'},
    {"help",		0, NULL, 'h'},
    {"mixer-ctl",	1, NULL, 'm'},
    {"output-fifo",	1, NULL, 'o'},
    {"stretch-fifo",	1, NULL, 's'},
    {"pid-file",	1, NULL, 'P'},
    {"PID-file",	1, NULL, 'P'},
    {"verbose",		0, NULL, 'v'},
    {"write",		1, NULL, 'w'},
    {"amplitude",	1, NULL, 'A'},
    {"Amplitude",	1, NULL, 'A'},
    {"bandwidth",	1, NULL, 'B'}, 
    {"Bandwidth",	1, NULL, 'B'}, 
    {"channel-mask",	1, NULL, 'C'}, 
    {"Channel-mask",	1, NULL, 'C'}, 
    {"frequency",	1, NULL, 'F'},
    {"Frequency",	1, NULL, 'F'},
    {"interval",	1, NULL, 'I'},
    {"Interval",	1, NULL, 'I'},
    {"kout",		1, NULL, 'K'}, 
    {"Kout",		1, NULL, 'K'}, 
    {"lurk",		0, NULL, 'l'},
    {"frame-rate",	1, NULL, 'N'}, 
    {"vmeter",		1, NULL, 'V'}, 
    {"Vmeter",		1, NULL, 'V'}, 
    {"quantum-sig",	1, NULL, 'Q'},
    {"Quantum-sig",	1, NULL, 'Q'},
    {"rin",		1, NULL, 'R'},
    {"Rin",		1, NULL, 'R'},
    {"feed-krng",	0, NULL, 'f'},
    {"zref",		1, NULL, 'Z'},
    {"Zref",		1, NULL, 'Z'},
    {"1pass",		0, NULL, '1'},
    {"d-xrun",		0, NULL, ALT|'x'},
    {"hack",		0, NULL, ALT|'h'},
    {"snooze",		1, NULL, ALT|'s'},
    {NULL,		0, NULL, 0}
  };

//////////////////////////////////////////////////////////////////////

  mytime = time(0);
  srand(mytime);
  mixer_ctlfile = "<>";		// fill in default, later
  mytm = localtime (&mytime);
  int helpme(0);
  char* homish = getenv("HOME");
  if (!homish) homish = "/root";
  string home(homish);
  if (home != "/root") {
    pid_fname = home + pid_fname;
    rando_fname = home + rando_fname;
    stretch_fname = home + stretch_fname;
  }
  
// Process commandline options 
  while(1) {
//    int getopt_long(int argc, char * const argv[],
//                  const struct option *longopts, int *longindex);

    ch = getopt_long (argc, argv, long_options, NULL);
    if (ch == -1)
      break;
    
    if (optarg) if (*optarg == ':' || *optarg == '=') optarg++;
    switch(ch) {
      case '1':
	onepass = 1;
	break;
      case 'b':
	nframe = atoi(optarg);
	break;
      case 'c':
	card_device = optarg;
	break;
      case 'd':
	daemonflag++;
	break;
      case 'm':
	mixer_ctlfile = optarg;
	break;
      case 'o':
	rando_fname = optarg;
	break;
      case 'p':
	stretch_fname = optarg;
	break;
      case 'P':
	pid_fname = optarg;
	break;
      case 'w':
	wcheck_fname = optarg;
	break;
      case 'N':
	desired_rate = atoi(optarg);
	break;
      case 'B':
	bandwidth = atof(optarg);
	break;
      case 'C':
	channel_mask = atoi(optarg);
	break;
      case 'K':
	kout = atof(optarg);
	break;
      case 'V':
	vmeter = atof(optarg);
	break;
      case 'R':
	r_in = atof(optarg);
	break;
      case 'f':
	feed_k_flag++;
	break;
      case 'Q':
	qsig = atof(optarg);
	break;
      case 'Z':
	zref = atof(optarg);
	break;
      case 'F':
	calfreq = atof(optarg);
	break;
      case 'A':
	calamp = atof(optarg);
	break;
      case 'I':
	calival = atof(optarg);
	break;
      case 'v':
	verbose ++;
	break;
      case 'h':
        helpme++;
	goto helper;
      case 'l':
        lurking++;
	break;
      case ALT|'x':
        d_xrun++;
	break;
      case ALT|'h':
        dump_io();
	exit(0);
        break;
      case ALT|'s':
	snooze = atof(optarg);
	break;
      case '?':		// optarg() uses this for any unrecognized 
	usage(1);	//   option, and has already complained about it.
	exit(1);
      default:
        int chx(ch&~ALT);
	fprintf(stderr, "Sorry, option %s'%c' not implemented.\n", 
		ch!=chx? "ALT+" : "", chx);
	exit(1);
    }
  }
helper:;;;;

  if (!helpme) if (optind < argc) {
    printf ("Extraneous verbiage: ");
    while (optind < argc) cerr << argv[optind++] << " " ;
    cerr << endl;
    exit(1);
  }
  
  unsigned int where = card_device.find_first_not_of(digits);
  if (where == card_device.npos) justcard = card_device;
  else justcard = card_device.substr(0, where);

  if (mixer_ctlfile == "<>") {
    mixer_ctlfile = invent_ctlfile(justcard);
  }// else cmdline has explict ctlfile, use that
  if (mixer_ctlfile.length()) {
    char mx = mixer_ctlfile[0];
    if (mx != '.' && mx != '/') {
      mixer_ctlfile = "/etc/" + progname + "/" + mixer_ctlfile;
      if (home != "/root") {
	mixer_ctlfile = home + mixer_ctlfile;
      }
    } // else . or / means use it verbatim
  }

  if(helpme) {
    usage(0);
    exit(0);		// no need to exeunt()
  }

  if (verbose > 1) {
    cerr << "Bandwidth: " << bandwidth << endl;
    cerr << "R_in: " << r_in << endl;
    cerr << "Qsig: " << qsig << endl;
  }
  if (qsig && r_in && bandwidth) {
     double sigmaJ(sqrt(4 * kT * r_in * bandwidth));
     sps = normEnt(0, 0, sigmaJ/qsig);
     if (verbose) cerr << "SigmaJ: " << sigmaJ 
     	<< " Bits per sample: " << sps << endl;
  }

}// cmdline


void config_mixer(const string ctlfilestr, const string justcard){
  if (!ctlfilestr.length()) return;

  const char* ctlfile = ctlfilestr.c_str();
  int rslt = open(ctlfile, O_RDONLY);
  if (rslt < 0) {
    syslog(LOG_WARNING, "Could not open alsactl file '%s': %m "
    	"... will use inherited mixer settings.", ctlfile);
    return;
  }
  close(rslt);
  
  rslt = load_state(ctlfile, justcard.c_str(), 1, 1);

  if (rslt) {
    syslog(LOG_ERR, "error in load_state");
    exit(1);
  }
}


int open_fifo(const string fname) {
  if (!fname.length()) return -1;

  int rslt = mkfifo(fname.c_str(), 0644);
  if (rslt < 0) {
    if (errno != EEXIST) {
      syslog(LOG_ERR, "Couldn't create output fifo (%s): %m", 
	fname.c_str());
      exit(1);
    } else {		// already exists

      struct stat statbuf;
      rslt = stat(fname.c_str(), &statbuf);
      if (rslt < 0) {
	syslog(LOG_ERR, "Couldn't stat output fifo (%s): %m",
		fname.c_str());
	exit(1);
      }
      if (!S_ISFIFO(statbuf.st_mode)){
	syslog(LOG_WARNING, "Output (%s) exists but isn't a fifo,"
		" hope you know what you're doing.",
		fname.c_str());
	// proceed
      }
    }
  }
  // here with named fifo existing

// We (main process) quasi-open it for reading.
// Otherwise the open (for writing) would block for lack
// of a listener, which would be bad.
// Note that we never actually do any reading, and this
// doesn't interfere with other processes (e.g. independent 
// customers) who do the real reading.
  int junk = open(fname.c_str(), O_RDONLY | O_NONBLOCK);
  if (junk < 0) {
    syslog(LOG_WARNING, "Stretch-open failed '%s' -- %m", 
	fname.c_str());
    // don't exit -- maybe not fatal -- but weird.
  }

  int fd = open(fname.c_str(), O_WRONLY);
  if (fd < 0) {
      syslog(LOG_WARNING, "Couldn't open fifo (%s) for writing: %m",
	      fname.c_str());
      syslog(LOG_WARNING, "Proceeding in 'test' mode....");
  }
  return fd;
}

// Open miscellaneous i/o devices:
void snark::open_devices(){
  if (wcheck_fname.length()) {
    wcheck_fd = open(wcheck_fname.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (wcheck_fd == -1) {
      syslog(LOG_ERR, "Couldn't open checkfile for writing: %m");
      exit(1);
    }
  }

  if (rando_fname.length()) {
   rando_fd = open_fifo(rando_fname);
// Also open the double-buffering pipe:
    int rslt = pipe(bufpipe);
    if (rslt < 0) {
      syslog(LOG_WARNING, "Can't open pipe for double-buffering: %m");
      exit(1);
    }
  }
  
  if (stretch_fname.length()) {
    stretch_fd = open_fifo(stretch_fname);
  }

}


datum smash(double foo) {
  if (foo > INT_MAX) return INT_MAX;
  if (foo < -INT_MAX) return -INT_MAX;
  return datum(foo);
}

void calout(const double ofreq, const double calival,
	const double calamp, alsa_dsp* dsp, char**  argv){
  if (calamp < -200.) return;	// no amplitude: don't even bother.
  if (!ofreq) return;
  int parent(getpid());
  int cal_pid=fork();
  clean_me.push_back(cal_pid);
  if (cal_pid) return;
  progname = "turbid-calibrator";
  argv[0] = "turbid calibrator";
  if (verbose) cerr << "Calout starts, ofreq = " << ofreq << endl;

  Keeper keep(dsp, FPP);

  double caltime = .2;		//  # of seconds in output buffer
  int calframe = int(dsp->rate * caltime);	// # of frames
  double phpf0 = twopi * ofreq / dsp->rate;	// phase per frame
  double phpf1 = phpf0 * calival;

  int bufsize(calframe * dsp->nchan);
  valarray<datum> calout_buffer(bufsize);

  datum* ptr;
  datum foo[2];
  int flip;
  double gfactor(pow(10, calamp/20));
  double amplitude(ref_level * gfactor);

  int phase(0);

// main loop
  for (;;) {		// loop over all buffers
    if ( getpgid  (parent) < 0) {
      syslog(LOG_ERR, "Calibrator exiting due to abandonment");
      exit(1);
    }
// loop over all frames in this buffer:
    for (int ff = 0; ff < calframe; ff++) {
      if (ofreq < 0) {
        foo[0] = foo[1] = smash(gfactor * probe[phase]);
	phase++;
	if (phase >= nprobe) phase = 0;
      } else {
	foo[0] = smash(amplitude * sin(phase * phpf0));
	foo[1] = smash(amplitude * sin(phase * phpf1));
	phase++;
      }
// loop over all channels in frame:
      ptr = &calout_buffer[ff * dsp->nchan];
      for (int jj = 0; jj < dsp->nchan; jj++) {
	flip = jj & 1;
	*ptr++ = foo[flip];
      }
    }
    datum* doit(&calout_buffer[0]);
    int todo = calframe;

    while (todo) {		// keep trying to get this buffer out
      int rslt = snd_pcm_writei(keep, doit, todo);
      if (rslt == todo) break;		// good!
      if (rslt > 0) {			// short write
        todo -= rslt;
	doit += rslt;
	continue;
      }
      if (rslt == -EPIPE) {		// explicit underrrun
        if (d_xrun) syslog(LOG_WARNING, 
		"Calout underrun: wrote %d got %d", todo, rslt);
        snd_pcm_prepare(dsp->phndl);	// recover from underruns
	continue;
      }
      // some error we don't understand
      syslog(LOG_WARNING, "Calout wrote %d got %d", todo, rslt);
      snd_pcm_prepare(dsp->phndl);	// try to recover
    }
  }
}

// inrange set to 1 iff no clipping occurs
//
double histo_step(const datum* buf, const int bufsize, const int slots,
	 const datum mean, const int stride, const int loss,
	int& inrange){
  inrange = 0;
  int histo[slots];
  for (int jj = 0; jj < slots; jj++){
    histo[jj] = 0;
  }
  datum xx;
  const datum* from = buf;
  for (int ii = 0; ii < bufsize; ii++){
    xx = *from;
    from += stride;
    xx /= loss;
    xx += slots/2;	// the middle slot corresponds to zero voltage
    if (xx < 0) xx = 0;
    if (xx >= slots) xx = slots-1;
    histo[xx] += 1;
  }

// OK, the histogram is built;  now evalute p log p.

  double sumlog(0);
  int nj;
// Note:
// - sum[p log p] = - sum[(n/d) log (n/d)] where d = sum[n]
// =        (-1/d) (sum[n log n] - sum[n log d])
// = log d - (1/d)  sum[n log n]
  for (int jj = 0; jj < slots; jj++){
    nj = histo[jj];
    if(nj) {
      sumlog += nj * log2(nj);
    }
  }
  if (verbose >= 3) 
    printf("log2(bufsize) %g, sumlog %g, sumlog/bufsize %g, loss %d\n",
	log2(bufsize), sumlog, sumlog / bufsize, loss);
  if (histo[0] == 0 && histo[slots-1] == 0) inrange = 1;
  return log2(bufsize) - sumlog / bufsize;
}

// Calculate histogram.
// This higher-level routine iterates checking for stuck bits.
// 
double histogram(const datum* buf, const int bufsize, const int slots,
	 const datum mean, const int stride, int sigbits){
  double max_ent = -1.;
  double ent;
  int inrange;
  int loss(1);
  if (sigbits > 0) {
    loss = 1 << (bPB*sizeof(datum) - sigbits);
  }
  do {
    ent = histo_step(buf, bufsize, slots, mean, stride, loss, inrange);
    if (ent > max_ent) max_ent = ent;
    if (0 && verbose >= 3) 
      fprintf(stderr, "Loss %d returns %g max %g\n", 
    	loss, ent, max_ent); 
    loss *= 2;
  } while (!inrange);
  return max_ent;
}


void snark::printx(
 const char* name, 
 const double foo_1, 
 const double foo_2,
 double plogp,
 const alsa_dsp* dsp,
 const double vmeter
) {

  double stdev = sqrt(foo_2 - foo_1*foo_1);

  double dbdev(-999);
  if (stdev != 0.0) dbdev = 20 * log10(stdev / ref_level_2);
  printf("%6s: 1st %9.3g, 2nd %9.3g, stdev %9.3g "
	  "= %7.3fdB, plp %6.3f\n",
	name, foo_1, foo_2, stdev, dbdev, plogp); 
  if (dsp) {
// for 24 bits of significance in a 32-bit representation,
// rsig will equal 256
    int rsig(1);
    if (dsp->sbits > 0) rsig = 1 << (bPB*sizeof(datum) - dsp->sbits);

    if (!qsig) {	// no -Q flag on command line
      if (vmeter) cout << "--Q " << (vmeter * rsig) / stdev 
	  << " stdev " << stdev << endl;
    } else {
      double vin = stdev * qsig / rsig;
      double vratio = vin / vmeter;
      double rval(-1);
      if (vratio > 0 && vratio < 0.9) {
	rval = zref * vratio/(1-vratio);
      }
      int printit(0);
      if (!r_in) {
	cout << "--Rin " << rval;
	printit++;
      }
      if (vmeter) {
	cout <<  "   Vin/Vmeter " << vratio ;
	printit++;
      }

      double vindiv(vin);
      if (r_in) {
	vindiv = vin * (r_in + zref) / r_in;
	if (vmeter) {
	  cout << " Vin/Vmeter/divider " << vindiv/vmeter;
	  printit++;
	}
	if (printit) cout << endl;
	double gfactor(pow(10, calamp/20));
  // Kout could have been determined earlier, but we don't
  // reveal it until now, because we want to encourage the
  // user to put Zref in series before calibrating Kout.
	if (!kout) {
	  if (vmeter) cout << "--Kout " << vmeter/gfactor << endl;
	} else {
  // probe RMS is at ref_level, 3dB higher than sine wave
	  double koutprime(kout);
	  if (calfreq < 0) koutprime *= sqrt(2.0);
	  double vgain(vindiv/gfactor/koutprime);
	  if (vmeter) cout << " Vmeter/Vout " << vmeter/gfactor/koutprime;
	  cout << " Vin/Vout/divider " << vgain ;
	  double bw(vgain*vgain*dsp->rate / 2);
	  cout << endl;
	  if (calfreq < 0) {
	    if (!bandwidth) {
	      cout << "--Bandwidth " << bw << endl;
	    } else if (r_in) {
	      cout << "  Bw meas/arg " << bw/bandwidth;
	      double sigmaJsq = 4 * kT * r_in * bandwidth;
	      cout <<  "   sigmaJ " << sqrt(sigmaJsq)
	        << "  Entropy " << sps << " bits/sample" << endl;
	    }
	  }
	}// if kout
      } else {  // not r_in
        if (printit) cout << endl;
      }
    }
  }
}

void snark::analyze(alsa_dsp* dsp, valarray<datum>& cap_buffer, 
	const int finbuf){
  int left, right, sdiff;
  valarray<datum> sdiff_buffer(nframe);

  int left_idx = 0;
  int right_idx = 1;

/// Kludge test:  left_idx = 10; right_idx = 11;
// Be careful to index outside array boundaries:
  int topchan = dsp->nchan - 1;
  if (left_idx  > topchan) left_idx  = topchan;
  if (right_idx > topchan) right_idx = topchan;

  double left_1st(0), left_2nd(0), left_s(0);
  double right_1st(0), right_2nd(0), right_s(0);
  double sdiff_1st(0), sdiff_2nd(0), sdiff_s(0);

  for(int frame = 0; frame < nframe; frame++) {
    left  = cap_buffer[frame * dsp->nchan + left_idx ];
    right = cap_buffer[frame * dsp->nchan + right_idx];

    sdiff = left - right;
    sdiff_buffer[frame] = sdiff;

    if (0) if (verbose) if (frame < 4) {
      printf(" >>> %5d %5d %5d\n", left, right, sdiff);
    }

    left_1st += left;      left_2nd += ((double)left)*left;
    right_1st += right;    right_2nd += ((double)right)*right;
    sdiff_1st += sdiff;      sdiff_2nd += ((double)sdiff)*sdiff;
  }
// Do the normalization denominators:
  left_1st /= nframe;		left_2nd /= nframe;
  right_1st /= nframe;	right_2nd /= nframe;
  sdiff_1st /= nframe;	sdiff_2nd /= nframe;
  int histoslots(512);
  left_s = histogram(&cap_buffer[0], nframe, histoslots, 
		      int(left_1st), dsp->nchan, dsp->sbits);
  right_s = histogram(&cap_buffer[1], nframe, histoslots, 
		      int(right_1st), dsp->nchan, dsp->sbits);
  sdiff_s = histogram(&sdiff_buffer[0], nframe, histoslots, 
		      int(sdiff_1st), 1, dsp->sbits);

  if (verbose) {
    printx("left", left_1st, left_2nd, left_s, dsp, vmeter);
  }
  if (verbose) {
    cout << crtRed;
    printx("right", right_1st, right_2nd, right_s, dsp, vmeter);
    cout << crtNormal;
  }
  if (verbose) printx("sdiff", sdiff_1st, sdiff_2nd, sdiff_s, 0, 0);

}

////////////////////////////////////////
// Read some data and maybe write checkfile
int snark::read_stuff(alsa_dsp* dsp, datum* cap_buffer){

  int sts;
  int frames_read;

  for (;;){		// keep trying to read
    frames_read = readi(dsp, &cap_buffer[0], nframe);
    if (frames_read == nframe) break;	// good!
    if (frames_read > 0 || 
	frames_read == -EPIPE) {	// recover from overruns
      if (d_xrun) syslog(LOG_WARNING, 
// Note that these underruns are normal.  They
// happen whenever we transition from being busy to 
// being happily blocked due to output pipeline full.
      		"Read_stuff overrun: requested %i got %i: %s",
	        nframe, frames_read, snd_strerror(frames_read));
      snd_pcm_prepare(dsp->phndl);
    } else {				// unrecoverable error
      syslog(LOG_WARNING, "Read_stuff requested %i got %i: %s",
	      nframe, frames_read, snd_strerror(frames_read));
      exeunt(1);
    }
  } 

  int gotbytes = snd_pcm_frames_to_bytes(dsp->phndl, frames_read);

  if (wcheck_fd >= 0) {
    sts = write(wcheck_fd, &cap_buffer[0], gotbytes);
    if (sts != gotbytes) {
      syslog(LOG_ERR, "Write error on raw output file (%i): %m", wcheck_fd);
      exeunt(1);
    }
    close(wcheck_fd);
    wcheck_fd = -1;
  }
  
  return frames_read;
}

#include "bufwrite.h"

void snark::main_loop(alsa_dsp* dsp, const double snooze){

  valarray<datum> cap_buf(nframe * dsp->nchan);
  valarray<datum> chosen_buf(dsp->nchan);
  bufwrite chunky(bufpipe[1], obs);
  
  SHA1_CTX shacon;		// the SHA-1 "context"
  unsigned char digest[20];
  SHA1_Init(&shacon);
  double sincon(0);		// entropy in shacon

// 10 megabytes in 26 seconds
//  time dd if=~/dev/hrandom of=foo.dat bs=20 count=500k

  int do_out(sps && channel_mask && (rando_fd >= 0));

  while (1) {
    int finbuf = read_stuff(dsp, &cap_buf[0]);
    datum* cap_ptr(&cap_buf[0]);
    if (verbose) analyze(dsp, cap_buf, finbuf);
    if (do_out) while (finbuf) {
      int mymask(channel_mask);
      int chosench(0);
      for (int ii = 0; ii < dsp->nchan; ii++){
	if ((mymask & 1)) chosen_buf[chosench++] = cap_ptr[ii];
	mymask = mymask >> 1;
      }

      SHA1_Update(&shacon, (unsigned char*) (&chosen_buf[0]), 
      			chosench * sizeof(datum));
      cap_ptr += dsp->nchan;		// use a new frame next time
      finbuf--;				// fewer frames remaining
      sincon += chosench * sps;		// more entropy in shacon
      if (sincon > 170){
	SHA1_Final(digest, &shacon);
	chunky.Write(digest, sizeof(digest));
#if 0
	int sts = write(rando_fd, digest, sizeof(digest));
	if (sts != sizeof(digest)){
	  syslog(LOG_ERR, "Write error on fifo (principal output): %m");
	  sleep(10);		// or exit ???
	}
#endif
	SHA1_Init(&shacon);	// get ready for next time
	sincon = 0;
      }
    }
    if (onepass) exeunt(0);
    const double microsec(1e-6);
    if (!do_out) usleep(int(snooze/microsec));
  }// while (1)
}
  

////////////////////
// The whole idea of a pidfile is a crock.
// It is inconsistent with the idea that you might
// reasonably run multiple copies of turbid.
void write_pid_file(const string fname) {
  pidfile = "";			// assume the worst, for now

  if (!fname.length()) return;				
  FILE* junk = fopen(fname.c_str(), "r");
  if (junk) {
// ???!!! todo:  be smart if pidfile exists but points
// to extinct process ......
    syslog(LOG_WARNING, "PID file \"%s\" already exists, not overwriting",
    	fname.c_str());
    fclose(junk);
    return;
  }

  FILE* p = fopen(fname.c_str(), "w");
  if (!p) {
    syslog(LOG_WARNING, "Couldn't open PID file \"%s\" for writing: %m.",
    	 fname.c_str());
  } else {
    int kidpid = getpid();
    fprintf(p, "Kidpid: %i\n", kidpid);
    fclose(p);
    pidfile = fname;	// remember fname in global (pidfile)
			// for later use during cleanup
  }
}

void cleanup(int sts){
  for (unsigned int ii = 0;  ii < clean_me.size(); ii++) {
    kill(clean_me[ii], SIGQUIT);
  }
  if (pidfile.length()) unlink(pidfile.c_str());
  cout << crtNormal;
  exit(sts);
}

void trap_exit(int signum) {
  syslog(LOG_NOTICE, "%s stopping on signal %i", progname.c_str(), signum);
  cleanup(0);
}

void trap_detach(int signum) {
  syslog(LOG_NOTICE, "%s detaching on signal %i", progname.c_str(), signum);
  exit(0);
}

void dummy(int signum) {
  syslog(LOG_NOTICE, "Dummy caught signal %i", signum);
}

void exeunt(int sts){
  syslog(LOG_NOTICE, "%s exiting, status %i", progname.c_str(), sts);
  cleanup(sts);
}
