#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <math.h>

#include <pthread.h>
#include <semaphore.h>

#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * WAVDUPLEX -- Synchronous Playback and Record of Alsa-Sound
 *
 * Usage: wavduplex [play.wav ...] rec.wav
 *
 * For recording of multiple voices, do for example:
 * bash-1: wavduplex 1.wav
 * bash-2: wavduplex 1.wav 2.wav
 * bash-3: wavduplex 1.wav 2.wav 3.wav
 * bash-4: ...
 *
 * Program end with Ctrl-C (SIGINT). (German-Keys: Strg-C)
 *
 * -------------------------------------------------------------------------
 *
 * Author: Dr. Joerg Weule
 * Licence: GPL -- Gnu Public Licence
 *
 * weule@7b5.de
 * weule@acm.org
 * historically: weule@cs.uni-duesseldorf.de
 *
 * www.7b5.de
 * www.eulesoft.de
 * www.jazz-events.de
 * www.jazz-schmiede.de 
 *
 */
/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * There are three threads doing th work:
 * The HEAD is reding the input from the files, the BODY is writing the
 * input the the sound card and reads the recorded data. The TAIL is
 * writing the data to a WAV file. The threads are connecte with ring
 * buffers and synchronised by two semaphores per buffer.
 */
/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * BODY
 *
 * PCM data is read for playback and recorded PCM data is
 * written at the same time. The recording will continue
 * even if the input stream has come to an end.
 */
/*---------------------------------------------------------------------------*/
/*
 * First some parameters ...
 *
 */
#define RINGSIZE 1024
#define FRAMES (1024*2)
unsigned int rate = 44100 ;
int channels = 2 ;
snd_pcm_uframes_t latency = 16384;


/*---------------------------------------------------------------------------*/
snd_pcm_t *capture_handle;
snd_pcm_t *playback_handle;

volatile int count_c = 0 ;
volatile int count_p = 0 ;
//const unsigned int max_wav = 0x7fff0000 ;
const unsigned int max_wav = 44100 * 3600 ;

snd_pcm_uframes_t frames = FRAMES ;
int dir = 0 ;

snd_pcm_uframes_t start_threshold=FRAMES*1000;

#define ENSURE(f,s) if((err=f)<0){fprintf(stderr,s " (%s)\n",snd_strerror(err));exit(1);}
#define PENSURE(f,s) if(f<0){fprintf(stderr,s " (%s)\n",snd_strerror(errno));exit(1);}

/*---------------------------------------------------------------------------*/
/*
 * Here is the buffer ...
 *
 */
pthread_t avg_thread ;
pthread_t tail_thread ;
volatile int buf_p_a = 0 ;
volatile int buf_p_b = 0 ;
volatile int buf_c_a = 0 ;
volatile int buf_c_b = 0 ;
sem_t sem_p_free ;
sem_t sem_p_used ;
sem_t sem_c_free ;
sem_t sem_c_used ;
typedef volatile short cshort ;
typedef          short vshort ;
cshort buf_e          [FRAMES*2]; /* silence */
vshort buf_p[RINGSIZE][FRAMES*2];
vshort buf_c[RINGSIZE][FRAMES*2];

/* increase bound */
void roll(volatile int* p){
	int h = *p ; ; h = (h+1) % RINGSIZE ; *p = h ;
}

/*---------------------------------------------------------------------------*/
int max_output = 0 ;
update_max_output(short*buf,int n){
	int i , h ;
	for ( i = 0 ; i < n ; i ++ ) {
		h = buf[i];
		if ( h < 0 ) h = -h ;
		if ( max_output <  h ) max_output = h ;
	}
}
/*---------------------------------------------------------------------------*/
/*
 * please_run is set to zero, if an interrupt is received (trap).
 */
volatile int please_run = 1 ;
volatile int block_input = 0 ;

void trap(int sig){
	if ( please_run == 1 ) please_run = 0 ; else exit(0);
	fprintf(stderr,"Got SIGINT, shutdown of recording now.\n",sig);
}
void stop_playback(int sig){
	block_input = 1 ;
	fprintf(stderr,"Got EOFs,   shutdown of playback  now.\n",sig);
}

/*---------------------------------------------------------------------------*/
/*
 * Print the state of the stream for debugging.
 *
 */
void print_state(void){
	snd_pcm_state_t s;
	s = snd_pcm_state(capture_handle);
	if ( s == SND_PCM_STATE_RUNNING )
		fprintf(stderr,"CAPTURE is running\n");
	else
	if ( s == SND_PCM_STATE_PREPARED )
		fprintf(stderr,"CAPTURE is prepared\n");
	else
	if ( s == SND_PCM_STATE_XRUN )
		fprintf(stderr,"CAPTURE is xrun\n");
	else
	fprintf(stderr,
		"CAPTURE is in unknown state: %d\n",s);

	s = snd_pcm_state(playback_handle);
	if ( s == SND_PCM_STATE_RUNNING )
		fprintf(stderr,"PALYBACK is running\n");
	else
	if ( s == SND_PCM_STATE_PREPARED )
		fprintf(stderr,"PALYBACK is prepared\n");
	else
	if ( s == SND_PCM_STATE_XRUN )
		fprintf(stderr,"PALYBACK is xrun\n");
	else
	fprintf(stderr,
		"PALYBACK is in unknown state: %d\n",s);

	fprintf(stderr,
		"REC=%d PLAY=%d\n",count_c,count_p);
}

/*---------------------------------------------------------------------------*/
/*
 * Play to line-out record what comes from line-in.
 *
 */
int play(void){
	int n = 0 ;
	snd_pcm_uframes_t err = 0;

 	/* block_input is set to 1 when the end of input is detected
	 * by either end of file or a SIGUSER1 in caught.
	 * Silence should be played in this case. */
	if ( block_input == 0 ) {
		PENSURE(sem_wait(&sem_p_used),"Can not sem_wait")
		if ( buf_p_a == buf_p_b ) {
			stop_playback(30);
		}
	}
	if ( block_input == 0 ) {
		err = snd_pcm_writei(playback_handle,buf_p[buf_p_a],frames);
		roll(&buf_p_a);
		if ( buf_p_a == buf_p_b && count_p > 44100 ) fprintf(stderr,
			"Warning: p-puf emtpy: sec=%d\n",count_p/44100);
		PENSURE(sem_post(&sem_p_free),"Can not sem_post")
	}
	else    err = snd_pcm_writei(playback_handle,(void*)buf_e,frames);

	if (err == FRAMES) { /* OK */
		count_p += err ;
	} else
	if (err == -EPIPE ) {
		fprintf (stderr,
			"PLAY: underrun (%s)\n",
			snd_strerror (err));	
		return 0 ;
	} else
	if (err < 0) {
		fprintf (stderr,
			"PLAY: write failed (%s)\n",
			snd_strerror (err));	
		return please_run ;
	} else /* 0 <= err < FRAMES */
	{
		fprintf (stderr, "PLAY: written: %d of %d\n",err,n);
	        fprintf (stderr, "PLAY: playback failed\n");
		return 0 ;
	}

	return please_run ;
}

int rec(void){
	int n = 0 ;
	snd_pcm_uframes_t err = 0 ;

	PENSURE(sem_wait(&sem_c_free),"Can not sem_wait")
	err = snd_pcm_readi (capture_handle, buf_c[buf_c_b], frames);
	roll(&buf_c_b);
	PENSURE(sem_post(&sem_c_used),"Can not sem_post")
	if ( err > 0 ) {
		count_c += err ;
	} else 
	if ( err != -EAGAIN && err != -11 ) {
		fprintf (stderr, "REC: snd_pcm_readi returned %d\n",err);
		snd_pcm_prepare (capture_handle);
		perror("--");
		fprintf(stderr,
			"REC: read from audio interface failed (%s)\n",
			snd_strerror(err));
		return 0 ;
	}

	return please_run ;
}

/*---------------------------------------------------------------------------*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
 * This function will do the following steps:
 * 1. Set up interrupt handlers.
 * 2. Set up the streams for playback and recording.
 * 3. Fill the playback buffer with the beginning of the playback data.
 * 4. Start both channels.
 * 5. Continously play and record a constant amount of data.
 * 6. Close the streams.
 *
 */
int duplex (int argc, char *argv[])
{
	snd_pcm_hw_params_t *hw_params;
	snd_pcm_sw_params_t *sw_params;
	snd_pcm_state_t state;

	int err;
	time_t start_time;

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 1 INTERRUPTS === */
	signal(1,trap);
	signal(2,trap);
	signal(3,trap);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 2 SETUP === */
#define DEV "default"
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/*
	 * PLAYBACK
	 */
	ENSURE( snd_pcm_open
		(&playback_handle, DEV, SND_PCM_STREAM_PLAYBACK, 0),
		"cannot open audio device" DEV)

	ENSURE( snd_pcm_hw_params_malloc
		(&hw_params),
		"cannot allocate hardware parameter structure")
	ENSURE( snd_pcm_hw_params_any
		(playback_handle, hw_params),
		"cannot initialize hardware parameter structure")
	ENSURE( snd_pcm_hw_params_set_access
		(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED),
		"cannot set access type")
	ENSURE( snd_pcm_hw_params_set_format
		(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE),
		"cannot set sample format")
	ENSURE( snd_pcm_hw_params_set_rate_near
		(playback_handle, hw_params, &rate, &dir),
		"cannot set sample rate")
	ENSURE( snd_pcm_hw_params_set_channels
		(playback_handle, hw_params, channels),
		"cannot set channel count")
		frames = FRAMES ;
	ENSURE( snd_pcm_hw_params_set_period_size_near
		(playback_handle, hw_params, &frames, &dir),
		"cannot set period size")
		frames = FRAMES ;
	ENSURE( snd_pcm_hw_params_set_buffer_size_near
		(playback_handle, hw_params, &latency),
		"cannot set buffer size")
	ENSURE( snd_pcm_hw_params
		(playback_handle, hw_params),
		"cannot set parameters") 
	snd_pcm_hw_params_free (hw_params);

	ENSURE(snd_pcm_sw_params_malloc
		(&sw_params),
		"cannot allocate software parameters structure")
	ENSURE(snd_pcm_sw_params_current
		(playback_handle, sw_params),
		"cannot initialize software parameters structure")
	ENSURE(snd_pcm_sw_params_set_avail_min
		(playback_handle, sw_params, 2),
		"cannot set minimum available count")
	ENSURE(snd_pcm_sw_params_set_start_threshold
		(playback_handle, sw_params, start_threshold),
		"cannot set start mode")
	ENSURE(snd_pcm_sw_params
		(playback_handle, sw_params),
		"cannot set software parameters")
	snd_pcm_sw_params_free (sw_params);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/*
	 * CAPTURE
	 */
 	ENSURE( snd_pcm_open
		(&capture_handle, DEV, SND_PCM_STREAM_CAPTURE , 0),
		"cannot open audio device" DEV)

	ENSURE(snd_pcm_hw_params_malloc
		(&hw_params),
		"cannot allocate hardware parameter structure")
	ENSURE(snd_pcm_hw_params_any
		(capture_handle, hw_params),
		"cannot initialize hardware parameter structure")
	ENSURE(snd_pcm_hw_params_set_access
		(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED),
		"cannot set access type")
	ENSURE(snd_pcm_hw_params_set_format
		(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE),
		"cannot set sample format")
	ENSURE(snd_pcm_hw_params_set_rate_near
		(capture_handle, hw_params, &rate, &dir),
		"cannot set sample rate")
	ENSURE(snd_pcm_hw_params_set_channels
		(capture_handle, hw_params, channels),
		"cannot set channel count")
		frames = FRAMES ;
	ENSURE(snd_pcm_hw_params_set_period_size_near
		(capture_handle, hw_params, &frames, &dir),
		"cannot set period size")
		frames = FRAMES ;
	ENSURE(snd_pcm_hw_params_set_buffer_size_near
		(capture_handle, hw_params, &latency),
		"cannot set buffer size")
	ENSURE(snd_pcm_hw_params
		(capture_handle, hw_params),
		"cannot set parameters")
	snd_pcm_hw_params_free (hw_params);

	ENSURE(snd_pcm_sw_params_malloc
		(&sw_params),
		"cannot allocate software parameters structure")
	ENSURE(snd_pcm_sw_params_current
		(capture_handle, sw_params),
		"cannot initialize software parameters structure")
	ENSURE(snd_pcm_sw_params_set_avail_min
		(capture_handle, sw_params, 1),
		"cannot set minimum available count")
	ENSURE(snd_pcm_sw_params_set_start_threshold
		(capture_handle, sw_params, start_threshold),
		"cannot set start mode")
	ENSURE(snd_pcm_sw_params
		(capture_handle, sw_params),
		"cannot set software parameters")
	snd_pcm_sw_params_free (sw_params);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/*
	 * LINK the streams and prepare ...
	 */
	ENSURE(snd_pcm_link
		(playback_handle,capture_handle),
		"cannot sync the streams")
	ENSURE(snd_pcm_prepare
		(playback_handle),
		"cannot prepare audio interface for use")
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 3 FILL BUFFER === */
	while ( count_p < (1024*16-1*FRAMES) && play() );
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 4 START === */

	{ int i = 5 ; while ( i-- ) {sleep(1);fprintf(stderr,"-%d-\n",i);} }
	snd_pcm_start(capture_handle);
	start_time = time(NULL);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 5 PLAYBACK AND RECORD */
	fprintf(stderr,"---START---\n");
	while ( play() && rec() && please_run == 1 && max_wav > count_c );
	PENSURE(sem_post(&sem_c_used),"Can not sem_post")
	fprintf(stderr,"----END----\n");
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* === STEP 6 FINAL PROCESSING */
	fprintf(stderr,"Duration: %5.0fsec\n",difftime(time(NULL),start_time));
	
	snd_pcm_drain (playback_handle);
	snd_pcm_close (playback_handle);
	fprintf(stderr,"Count_p = %d\n",count_p);

	snd_pcm_close (capture_handle);
	fprintf(stderr,"Count_c = %d\n",count_c );

	fprintf(stderr,"Maximal output value recorded = %d (%4.2f%%)\n",
		max_output,max_output/(1024.0*32.0) );
	return 0;
}

/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * The WAVE Header
 */
typedef struct wav_s {
	char chunk_id[4] ; /* "RIFF" */
	unsigned int chunk_size; /* n*4 + 36 */
	char format[4]; /* "WAVE" */
	char sub_chunk_id[4]; /* "fmt " */
	unsigned int sub_chunk_size; /* 16 */
	unsigned short audio_format; /* 1 */
	unsigned short num_channels; /* 2 */
	unsigned int sample_rate; /* 44100 */
	unsigned int byte_rate; /* 4*44100 */
	unsigned short block_alg; /* 4 */
	unsigned short bps; /* 16 */
	unsigned char sub_chunk2_id[4]; /* "data" */
	unsigned int sub_chunk2_size; /* n*4 */
} wav_t ;

/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * HEAD: Read the input files and combine them to one stream of music.
 *
 * 1. Open the files.
 * 2. Read all files and calculate the maximum valume.
 * 3. Calculate the divisor to limit the volume to 1<<15 .
 * 4. Go back just behind each WAV header.
 * 5. Read the files and write the combination to the ring buffer.
 * 6. Send an extra sem_post to indicate the end of playback_data.
 * 7. Close the streams.
 *
 */
#define BUFSIZE (FRAMES*2)
int snd[BUFSIZE];

int wavc; char**wavv;
void* avg(void*data){
	int i , k ;
	int m = 0 ;
	int n , err ;
	int count = 0 ;
	vshort*buf = buf_p[0] ;

	FILE**f=calloc(wavc,sizeof(FILE*));
	double d = 0.0 ;

	/* open the files */
	for ( i = 0 ; i < wavc ; i++ ){
		if ( NULL == (f[i] = fopen(wavv[i],"r+"))){
			fprintf(stderr,"Can not open %s\n",wavv[i]);
			exit(1);
		} else {
			fprintf(stderr,"Using %s\n",wavv[i]);
		}
	}
	/* skip the header */
	for ( i = 0 ; i < wavc ; i++ ){
		wav_t head ;
		fread(&head,1,sizeof(wav_t),f[i]);
	}

	/* check the maximum of the combination */
	n = BUFSIZE ;
	while ( n ) {
		memset(snd,0,BUFSIZE*sizeof(int));
		for ( i = 0 ; i < wavc ; i++ ){
			n = fread(buf,sizeof(short),n,f[i]);
			for ( k = 0 ; k < n ; k++ ) snd[k] += buf[k] ;
		}
		for ( k = 0 ; k < n ; k++ ) if (snd[k] < 0) snd[k] = -snd[k] ;
		for ( k = 0 ; k < n ; k++ ) if (m < snd[k]) m = snd[k] ;
	}

	/* skip back to the beginning of the music */
	for ( i = 0 ; i < wavc ; i++ ){
		fseek(f[i],44,SEEK_SET);
	}

	/* calculate a good level */
	/* d = 1.0 / wavc ; */
	d = 32000.0 / m ;
	fprintf(stderr,"MAX_AMP=%d, MULT=%f\n",m,d);

	/* write out the new combined music */ 
	n = BUFSIZE ;
	while ( 1 ) {
		int m = 0 ;
		PENSURE(sem_wait(&sem_p_free),"Can not sem_wait")
		buf = buf_p[buf_p_b];
		memset(snd,0,BUFSIZE*sizeof(int));
		memset(buf,0,BUFSIZE*sizeof(short));
		for ( i = 0 ; i < wavc ; i++ ){
			n = fread(buf,sizeof(short),BUFSIZE,f[i]);
			for ( k = 0 ; k < n ; k++ ) snd[k] += buf[k] ;
			if ( m < n ) m = n ;
		}
		n = m ;

		if ( d != 0.0 ) for ( k=0 ; k<n ; k++ ) buf[k] = d * snd[k] ;
		else            for ( k=0 ; k<n ; k++ ) buf[k] =     snd[k] ;

		if ( n <= 0 ) {
			break ;
		}
		roll(&buf_p_b);
		PENSURE(sem_post(&sem_p_used),"Can not sem_post")
		count += n ;
	}
	PENSURE(sem_post(&sem_p_used),"Can not sem_post")

	/*
	 * close the files
	 */
	for ( i = 0 ; i < wavc ; i++ ) fclose(f[i]);

	return NULL ;
}

/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * TAIL: read the PCM data and write a WAV file.
 *
 * 1. Open the file.
 * 2. Write the WAV header.
 * 3. Write the PCM data.
 * 4. Go back to the beginning of the file.
 * 5. Update the header with the correct length information.
 * 6. Close the file.
 */
char * recfilename ;
void* tail(void*data){
	wav_t head = {
		{'R','I','F','F'}
		,(1<<28)+36,
		{'W','A','V','E'},
		{'f','m','t',' '},
		16,1,2,44100,4*44100,4,16,
		{'d','a','t','a'},
		1<<30
	};
	int n , count = 0 , err ;
	char b[4096];
	FILE*f = fopen(recfilename,"w+");
	if ( f == NULL ) {
		fprintf(stderr,"Can not open %s\n",recfilename);
		exit(1);
	}

	ENSURE(fwrite(&head,sizeof(wav_t),1,f),"Can not write to recfile")
	while ( 1 ){
		PENSURE(sem_wait(&sem_c_used),"Can not sem_wait")
		if ( buf_c_a == buf_c_b ) break ;
		short* buf = buf_c[buf_c_a];
		ENSURE(fwrite(buf,4,FRAMES,f),"Can not write to file")
		roll(&buf_c_a);
		if ( ((buf_c_b - buf_c_a) % RINGSIZE) > (RINGSIZE-10) )
			fprintf(stderr,
			"Warning: c-puf full: sec=%d\n",count_c/44100);
		PENSURE(sem_post(&sem_c_free),"Can not sem_post")
	}
	ENSURE(fseek(f,0,SEEK_SET),"Can not fseek")
	head.sub_chunk2_size = count ;
	head.chunk_size = count + 36 ;
	ENSURE(fwrite(&head,sizeof(wav_t),1,f),"Can not write to recfile")
	ENSURE(fclose(f),"Can not close recfile")
	return NULL ;
}
/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
/*
 * Here is the part to organize the processing:
 *
 * There are three thread: The HEAD is reading the input and producing
 * the sound to play. The BODY is playing and recording using the alsa
 * library. The TAIL will write the recorded music to a file.
 *
 * The process will first set the schedule priority to -5 if its called
 * with root setuid bit. The the calling usedid will be esstablished.
 *
 * The organization is making real use of UNIX and POSIX system calls.
 *
 * 1. Set scheduling priority.
 * 2. Initialize semaphores.
 * 3. Start HEAD and TAIL threads.
 * 4. Wait for completion of the recorded data.
 */
void back_to_user_id(){
	int err;
	if ( getuid() != geteuid() ) ENSURE(seteuid(getuid()),"Setuid failed")
}

/*---------------------------------------------------------------------------*/
int main(int argc,char*argv[]){
	signal(SIGCHLD,SIG_IGN);

	if ( geteuid() == 0 ) nice(-5);
	back_to_user_id();

	recfilename = argv[argc-1];
	argv[argc-1]=NULL;
	wavv = argv + 1 ;
	wavc = argc - 2 ;

	PENSURE(sem_init(&sem_p_used,0,0),"Can not sem_init")
	PENSURE(sem_init(&sem_p_free,0,RINGSIZE-2),"Can not sem_init")
	PENSURE(sem_init(&sem_c_used,0,0),"Can not sem_init")
	PENSURE(sem_init(&sem_c_free,0,RINGSIZE-2),"Can not sem_init")

	if ( argc > 2 ) {
		PENSURE(pthread_create(&avg_thread,NULL,avg,NULL),
			"Unable to establish pthread HEAD")
	} else {
		block_input = 1 ;
	}

	PENSURE(pthread_create(&tail_thread,NULL,tail,NULL),
		"Unable to establish pthread TAIL")

	duplex(argc,argv);
	PENSURE(pthread_join(tail_thread,NULL),"Unable to join TAIL")
	return 0 ;
}
/*---------------------------------------------------------------------------*/
/*===========================================================================*/
/*---------------------------------------------------------------------------*/

