/*
 *  PCM-Recorder / 16bit signed Dateien auf Ice1712 Pro mit ALSA/Linux/x86
 *  Aufruf [Programm] [Dateiname]            ("liest" die Datei mit mmap!)
 *
 *  (Compilieren fuer Mono oder Stereo und auf eine feste Samplingrate zw.
 *   8000 und 48000Hz, hat bisher keine eigene Erkennung fuer WAV-Header!)
 *  Benoetigte Library: asound
 *
 *  Frei kopierbarer  haesslicher  "proof of concept"  Code  ohne jegliche
 *  Garantien, nicht mal die, die oben versprochene Funktion zu erfuellen.
 *
 *  Geschrieben von Eric Auer, Tipps von Dan Hollis und Christian Dressler
 *  Achtung: Der ALSA Treiber fuer diese Karte erlaubt nur,  ALLE  Kanaele
 *  gleichzeitig zu oeffnen, daher funktioniert dafuer auch keine OSS-Emu.
 *  Fuer Ausgabe sind das exakt 10, fuer Eingabe exakt 12 Monokanaele.
 *
 *  Ausserdem ist 32bit signed little endian das einzig moegliche Format,
 *  wobei nur die  24 MSB  vom Wandler verarbeitet werden.  Das  ALSA PCM 
 *  Plugin System bietet aber bei Bedarf Formatanpassungen per Software.
 *
 *  30.05.2000
 */
 
#define WHICH_CHN 0
// Kanal der ausgelesen werden soll, 0..7.., normal 0.
#define snd_HZ 22050 /* z.B. 22050 */ 
/* Ice1712pro/Linux kann nur S32_LE, aber 8-48kHz Samplingrate... */

/* ***************************************************************** */

#include <sys/asoundlib.h>
#include <linux/asound.h>
#include <stdio.h>
#include <string.h> // strncpy
#include <sys/types.h>
#include <errno.h> // ...
#include <fcntl.h> // open
#include <sys/stat.h> // stat
#include <stdlib.h> // malloc
#include <unistd.h> // usleep
#include <sys/uio.h> // iovec

/* ***************************************************************** */

// #define DEBUG
#define USLEEP
#define RBUF 4096 /* ReadBufferSize in Samples */
#define nCHN 12
// Channels, muss 12 sein bei der Karte

#define snd_inBITS 16
/* *** Achtung, zur Zeit werden nur 16 Bit signed verwendet!!! *** */
#define snd_SHL (31-snd_inBITS) /* 24 MSB of 32 used bei ice1712 */
// Ich habe ein MSB weggelassen, besser analog auf volles Volume bringen...
// 386: __BYTE_ORDER == __LITTLE_ENDIAN

// ice1712pro kann nur genau 10 voices playback und 12 voices capture
// sehr witzig!
// ausserdem geht im stream mode nur write/interleaved richtig!?

/* ***************************************************************** */

void help(void) { printf("Usage: Give me a filename and I will write,\n"
             "signed %dbit "
             "mono"
             " PCM %dHz data to it.\n",
             snd_inBITS,snd_HZ); 
 _exit(1); return; };

/* ***************************************************************** */

int open_sound(snd_pcm_t ** handle /* ptr auf ptr */)
{
int retval;
snd_pcm_channel_params_t params;

if ((retval = snd_pcm_open(handle, 0 /* card */, 0 /* device */, 
                           SND_PCM_OPEN_CAPTURE)))
   { fprintf(stderr,"Error with snd_pcm_open: %s\n",snd_strerror(retval));
     return 1; };
   // auch mgl.: snd_pcm_open_subdevice(h,c,d, s ,m) ...
   // mode: | SND_PCM_OPEN_NONBLOCK ist mgl.

params.channel=0; 
params.format.format=SND_PCM_SFMT_S32_LE;
   // Ice1712pro/Linux does only this, and without the use of plugins,
   // ALSA offers no conversion.
   // btw. the hardware only uses the 24 MSBs.
params.format.interleave = 1;
   // interleaved vs. blocked data format
   // for vector based i/o, interleave sucks.
params.format.rate = snd_HZ;
params.format.voices = nCHN;
   // Ice1712pro/Linux driver supports only opening all voices at once!
   // for capture, voices *must* be 12, for playback, it *must* be 10.
params.channel = SND_PCM_CHANNEL_CAPTURE; // or ... CAPTURE
params.mode = SND_PCM_MODE_STREAM; // or ... BLOCK
  // not set: params.digital.*, params.format.reserved, 
  // params.reserved, params.format.special
params.start_mode = SND_PCM_START_DATA; // or ... FULL or ... GO
params.stop_mode = SND_PCM_STOP_ROLLOVER; 
  // ... STOP or ... ERASE or ... ROLLOVER
  // STOP klappt, aber ROLLOVER muss nach einem Buffer
  // overrun/underrun nicht neu angeworfen werden.
params.time = 0; // set to get gettimeofday time of beginning of transfer
                 // -> sys/time.h ... struct timeval -> tv_sec ...
params.ust_time = 0; // set to get time in UST format
  
   params.buf.stream.queue_size = (snd_HZ >> 1) * 4 * nCHN;
                 // muss ein Vielfaches von 4*nCHN sein, min 1/2k...
                 // 1/16 Sekunde mit (snd_HZ >> 4) * 40 funktioniert z.B. ok.
                 // Groessere Queue -> mehr Latency aber weniger kritisches
                 // Timing bei Multitasking. Also sinnvoll einstellen!
   params.buf.stream.fill = SND_PCM_FILL_SILENCE_WHOLE; 
                 // or ... NONE or ... SILENCE
   params.buf.stream.max_fill = 480;
                 // not really used if fill!=...SILENCE
/* 
 * params.buf.block.frag_size=40960;
 * params.buf.block.frags_min=1;
 * params.buf.block.frags_max=10;
 */
if ((retval = snd_pcm_channel_params(*handle,&params)))
   { fprintf(stderr,"Format not supported by soundcard: "); 
     fprintf(stderr,"%s\n",snd_strerror(retval)); return 1; };

if ((retval = snd_pcm_capture_prepare(*handle)))
   { fprintf(stderr,"Error preparing capture: "); 
     fprintf(stderr,"%s\n",snd_strerror(retval)); return 1; };

// je nach START setting fehlt nun noch snd_pcm_capture_go(),
// ansonsten passiert der START automatisch.
return 0;
};

/* ***************************************************************** */

int read_sound(snd_pcm_t * handle, FILE * o_stream)
{
int left, written, cnt, cnt2, which_chn=WHICH_CHN;
// struct iovec bufv[nCHN];
signed int mybuf[RBUF*nCHN]; // nCHN wg nCHN voices
signed short int mywrbuf[RBUF];

// for (left=0;left<nCHN;left++) 
//   { bufv[left].iov_base=NULL; bufv[left].iov_len=0; };
// bufv[0].iov_base=buf; bufv[0].iov_len=left;

if ((cnt = snd_pcm_capture_go(handle))) 
   { fprintf(stderr,"Error with snd_pcm_capture_go: %s\n",
             snd_strerror(cnt)); return 1; 
   };
#ifdef USLEEP
    usleep(1000);
#endif

left = sizeof(mybuf);

while (/* left>0 */ 42==42)
  { 
    left = snd_pcm_read(handle,mybuf, sizeof(mybuf));
    // snd_pcm_readv(handle, bufv, nCHN);
    left = (left>0) ? left / (nCHN * sizeof(mybuf[0])) : left;
    if (left<=0)
       { fprintf(stderr,"Error with snd_pcm_read: %s\n",
                 snd_strerror(left));
#ifdef DEBUG
         left = snd_pcm_capture_go(handle);
         fprintf(stderr,"Tried to restart capture: %s\n",
                 snd_strerror(left));
#endif
         left = 0;
         fprintf(stderr,"Trying to continue\n");
       };
    if ((left<0) && (left!= -EAGAIN)) 
       { fprintf(stderr,"Error with snd_pcm_read: %s\n",
                 snd_strerror(left));
         return 1;
       };
    if (left>0)
    { /* SKIP IF BUFFER EMPTY */
      for (cnt=0,cnt2=which_chn; cnt<left; cnt++)
        { 
          mywrbuf[cnt] = mybuf[cnt2] >> snd_SHL; 
          cnt2+=nCHN;
        };
      written = fwrite((void *)mywrbuf, sizeof(mywrbuf[0]), left, o_stream);
      if (written < left)
         { fprintf(stderr,"Did not expect file write to block. Bad luck.\n"
                          "%d of %d samples written.\n",written,left);
           // return 1;
         };
    }; /* SKIPPED IF BUFFER EMPTY */
#ifdef DEBUG
    if (left>0) { fprintf(stderr,"%d samples fetched\n",left); };
#endif
#ifdef USLEEP
    usleep(1000);
#endif
#ifdef DEBUG
    left = 1;
    // endlos lesen
#endif
  };
return 0;
};

/* ***************************************************************** */

int main(int argc, char ** argv)
{
int retval;
snd_pcm_t * soundhandle=NULL;
FILE * pcmdata;

if ((argc!=2) || (!argv)) help(); if (!argv[1]) help();

(void)umask(022);

if (! (pcmdata = fopen(argv[1],"a"))) 
   { fprintf(stderr,"%s:",argv[1]); perror("Error opening file"); return 1; };

if (open_sound(&soundhandle)) return 1;

if (read_sound(soundhandle,pcmdata))
   { fprintf(stderr, "Error reading from PCM or writing to file\n"); };
   // but hang on...

fprintf(stderr,"Closing sound and file\n");
if ((retval = snd_pcm_close(soundhandle))) 
   { fprintf(stderr,"Error closing sound: "); 
     fprintf(stderr,"%s\n",snd_strerror(retval)); };
   // hang on if an error occured, do not return 1.

if (fclose(pcmdata)) 
   { perror("Could not close file (write error?)"); return 1; };

return 0;
};
