#include <alsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

int dobreak = 0;
void *brkhndl = NULL;

void handlesigint(int sig)
{
 dobreak++;
 if (brkhndl)
 {
   snd_pcm_t *apcm = (snd_pcm_t *) brkhndl;
   snd_pcm_drop(apcm);
 }
}

int main(int argc, char **argv)
{
 //PCM handle
 snd_pcm_t *pcm_handle;
 //playback stream
 snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
 //Struct for Info about hardware & config for stream
 snd_pcm_hw_params_t *hwparams;

 //PCM Device Name, eg "plughw:0,0" or "hw:0,0".  Can also use "default"?
 char *pcm_name;

 //Assignments & Allocations
 if (argc>1) pcm_name = strdup(argv[1]); else pcm_name = strdup("default");
 snd_pcm_hw_params_alloca(&hwparams);

 //Open PCM
 if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0 /* Mode. 0=blocking. Can also use SND_PCM_NONBLOCK, which returns read/write access immediately, or SND_PCM_ASYNC, which sends a SIGIO back to the program whenever a period has been processed. */) < 0)
 {
   fprintf(stderr, "Error opening PCM device %s\n", pcm_name);
   snd_pcm_close(pcm_handle);
   return -1;
 }

 //Before writing PCM data, we specify access type, sample format, # of channels, # of periods and period size.
 //Init the hwparams with full config space:
 if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
 {
   fprintf(stderr, "Cannot configure this PCM device!\n");
   snd_pcm_close(pcm_handle);
   return -2;
 }

 /* Can get info on configurations with:
    snd_pcm_hw_params_can_<capability>,
    snd_pcm_hw_params_is_<property>,
    snd_pcm_hw_params_get_<parameter>.

    Can test parameters (access type, buffer size, # of channels,
                         sample format, sample rate & # of periods)
    with:
    snd_pcm_hw_params_test_<parameter>

    All of this is especially important with "hw" interfaces. The
    configuration can be restricted with:
    snd_pcm_hw_params_set_<parameter>
 */

 //Let's assume 16BitLE data sampled at 44.1KHz:
 int rate = 44100;
 int exact_rate; //Sample rate returned by snd_pcm_hw_params_set_rate_near()
 int dir; //if exact_rate==rate, dir=0. if exact_rate<rate, dir=-1, else dir=+1.
 int periods = 2; //Number of periods
 snd_pcm_uframes_t periodsize = 8192; //Period size (bytes) = 8kb

 /* Interleaved data is stored as the words (for 16bLE, bytes for 8b, etc)
    for each channel stored consecutively, then the next period, etc.
    Noninterleaved data is stored as each period as a chunk of words for
    the first channel, then the nexrt channel, etc, before going to the
    next period.  Basically, interleaved subdivides the period into the
    channels, whereas non-interleaved goes a period at a time.
 */

 //Set access type to Interleaved:
 if (snd_pcm_hw_params_set_access(pcm_handle,hwparams,SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
 {
   fprintf(stderr, "Error setting access to Interleaved!\n");
   snd_pcm_close(pcm_handle);
   return -3;
 }

 //Set sample format to 16bLE (16 bit Little Endian)
 if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0)
 {
   fprintf(stderr,"Error setting format to 16bLE.\n");
   snd_pcm_close(pcm_handle);
   return -4;
 }

 //Set the sample rate - use the nearest to the desired rate that is supported by the hardware!
 exact_rate = rate;
 if (snd_pcm_hw_params_set_rate_near(pcm_handle,hwparams,&exact_rate, 0) < 0)
 {
   fprintf(stderr, "Error setting rate to %d Hz.\n",rate);
   snd_pcm_close(pcm_handle);
   return -5;
 }
 if (rate != exact_rate)
 {
   fprintf(stderr, "Warning: The rate %d Hz is not supported by your hardware.\n  Using %d Hz instead.\n", rate, exact_rate);
 }

 //Set number of channels
 if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2 /* Stereo */) < 0)
 {
   fprintf(stderr, "Error setting channels to stereo.\n");
   snd_pcm_close(pcm_handle);
   return -6;
 }

 //Set number of periods (AKA Fragments)
 if (snd_pcm_hw_params_set_periods(pcm_handle,hwparams,periods,0) < 0)
 {
   fprintf(stderr, "Error setting periods (fragments) to %d.\n", periods);
   snd_pcm_close(pcm_handle);
   return -6;
 }

 /* Unit of Buffersize is sometimes bytes and sometimes frames.  1 frame =
    num bytes in data * num channels (16bLE*stereo=4 bytes here)

    If buffersize of 2^n is not supported, use
    snd_pcm_hw_params_set_buffer_size_near(), which has a format similar to
    snd_pcm_hw_params_set_rate_near().
 */

 snd_pcm_uframes_t bsize;

 //Set buffer size in frames.
 //Latency = (periodsize * periods) / (rate * bytesperframe) = BufferSize / rate
 bsize = (periodsize*periods)>>2;
 if (snd_pcm_hw_params_set_buffer_size(pcm_handle,hwparams,bsize) < 0)
 {
   fprintf(stderr, "Error setting buffer size.  Attempting to set near %d.\n", bsize);
   if (snd_pcm_hw_params_set_buffer_size_near(pcm_handle,hwparams,&bsize) < 0)
   {
     fprintf(stderr, "Error setting buffer size.\n");
     snd_pcm_close(pcm_handle);
     return -7;
   }
 }
 printf("Buffer is %d.\n",bsize);

 //Apply the configuration:
 if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
 {
   fprintf(stderr, "Error setting HW params!\n");
   snd_pcm_close(pcm_handle);
   return -8;
 }

 /* Once the device is configured, PCM data can be written.  Playback starts
    at the first write!

    For Interleaved access we use:
      snd_pcm_sframes_t snd_pcm_writei(pcm_handle, data, numframes);
    which writes numframes frames from buffer data to PCM device at
    pcm_handle, (frame by frame?) returning the number of frames actually
    written.

    For Noninterleaved access we use:
      snd_pcm_frames_t snd_pcm_writen(pcm_handle, data, numframes);
    which writes numframes frames from buffer data to the PCM device at
    pcm_handle (period by period?) returning the number of frames actually
    written.
 */

 if (signal(SIGINT, handlesigint) == SIG_ERR)
 {
   fprintf(stderr,"Cannot register SIGINT handler!\n");
 }

 unsigned char *adata;
 int pcmreturn, l1, l2, wavelen1, wavelen2, seclen;
 short s1, s2;
 int frames;
 int fudge;

 brkhndl = (void *) pcm_handle;

 adata = (unsigned char *) malloc(periodsize);
 if (!adata)
 {
   snd_pcm_close(pcm_handle);
   return -9;
 }
 wavelen1 = 128;
 wavelen2 = 512;
 //Wavelength = Wavelen / Rate, Frequency = Rate / Wavelen
 //Therefore, required Wavelen for Frequency = Rate / Frequency

 if (argc>=4)
 {
   wavelen1 = (int) (exact_rate / atoi(argv[2]));
   wavelen2 = (int) (exact_rate / atoi(argv[3]));
 }
 else if (argc == 3)
 {
   wavelen1 = (int) (exact_rate / atoi(argv[2]));
   wavelen2 = wavelen1;
 }

 //Fudge factor = time for noise (seconds) * rate * size of frame / size of period
 if (argc >=5)
 {
   seclen = atoi(argv[4]);
 }
 else seclen = 5; // 5 seconds

 fudge = (int) ((seclen * exact_rate * 4) / periodsize);

 frames = periodsize >> 2; //4 bytes per frame

 for (l1 = 0; l1 < fudge; l1++)
 {
   if (dobreak) break;
   for (l2 = 0; l2 < frames; l2++)
   {
     if (dobreak) break;
     s1 = (l2 % wavelen1) * 100 - 5000;
     s2 = (l2 % wavelen2) * 100 - 5000;
     adata[4*l2] = (unsigned char) (s1 & 255);
     adata[4*l2+1] = (unsigned char) ((s1 >> 8) & 255);
     adata[4*l2+2] = (unsigned char) (s2 & 255);
     adata[4*l2+3] = (unsigned char) ((s2 >> 8) & 255);

   }
   while ((pcmreturn = snd_pcm_writei(pcm_handle,adata, frames)) < 0)
   {
     if (dobreak) break;
     snd_pcm_prepare(pcm_handle);
     fprintf(stderr, "<<<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>>>>>>>> \n");
   }
   if (dobreak) break;
 }
 if (dobreak) printf("Cancelled\n");

 brkhndl = NULL;
 snd_pcm_close(pcm_handle);
 if (signal(SIGINT, SIG_DFL) == SIG_ERR)
 {
   fprintf(stderr,"Cannot deregister SIGINT handler!\n");
 }
 free(adata);
 return 0;
}