/* CSL - Common Sound Layer
 * Copyright (C) 2001 Jeff Tranter
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#define _REENTRANT
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <csl/csl.h>

/* 

This illustrates using CSL in a multithreaded environment. It requires
the pthreads library to compile.

Background:
===========

If an application is multithreaded and you arbitrarily call the CSL
functions from multiple threads simultaneously, the internal CSL data
structures and/or PCM streams could be corrupted. This can be solved
by locking critical sections of code with mutexes.

Since locking is dependent on the threading library used by the host
sysytem and application, you need to tell CSL what locking primitives
to use. To do this you need to:

1. Create a variable of type CslMutex
2. Set the lock,unlock, and destroy fields of the CslMutex to point to
   the appropriate functions using your threading primitives.
3. Create a mutex using your threading library.
4. Set the user_data field of the CslMutex to point to your mutex.
5. Use csl_init_mutex when initializing the library.

CSL will then call the locking primitives when necessary during it's
internal processing.

If you are using a POSIX threads implementation such as pthreads, it
is very easy since the the lock/unlock/destroy functions map directly
into POSIX thread primitives.

*/


CslDriver *driver;

/*
 * Function run by each thread: opens a PCM output stream, writes some
 * data, and cleans up. If arg is 0 we write to the left audio
 * channel, otherwise to the right. We also add some delays to ensure
 * that more locking will be going on.
 */

static void thread(int arg)
{
	CslOptions options;
	CslErrorType err;
	CslPcmStream *stream;
	const int size = 1024;
	short buffer[size];
	int i, j;

    /* hard code the options to simply the example */
	options.rate = 44100;
	options.n_channels = 2;
	options.pcm_format = CSL_PCM_FORMAT_S16_LE;

	printf("Thread %d: Calling csl_pcm_open_output\n", getpid());
	err = csl_pcm_open_output (driver, "cslmutex", options.rate, options.n_channels, options.pcm_format, &stream);
	if (err)
		csl_error ("failed to open output device: %s", csl_strerror(err));

	sleep(1);

	memset(buffer, 0, size);
	
	for (i=0; i < 500; i++)
	{
		for (j=0; j<size; j++)
		{
			if (arg == 0)
			{
				buffer[j] = random();
				buffer[j+1] = 0;
			} else {
				buffer[j] = 0;
				buffer[j+1] = random();
			}
				j++;
		}
		printf("Thread %d: Calling csl_pcm_write\n", getpid());
		csl_pcm_write(stream, size, buffer);
	}

	sleep(1);

	printf("Thread %d: Calling csl_pcm_close\n", getpid());
	csl_pcm_close (stream);

	sleep(1);
}

int main()
{
	CslErrorType err;
	CslMutex mutex; /* CSL mutex */
	pthread_mutex_t pmutex = PTHREAD_MUTEX_INITIALIZER; /* pthread mutex */
	pthread_t thr1, thr2; /* threads */

	printf("Main: Starting\n");

	mutex.lock = (CslMutexLock) &pthread_mutex_lock;
	mutex.unlock = (CslMutexUnlock) &pthread_mutex_unlock;
	mutex.destroy = (CslMutexDestroy) &pthread_mutex_destroy;
	mutex.user_data = &pmutex;

	printf("Main: Calling csl_driver_init_mutex\n");
	err = csl_driver_init_mutex(NULL, CSL_DRIVER_CAP_PCM, &mutex, &driver);
	if (err)
		printf("failed, error was \"%s\" (%d)\n", csl_strerror(err), err);

    /* create two threads and start them */
	printf("Main: Creating 2 threads\n");
	pthread_create(&thr1, NULL, (void*)thread, (void*)0);
	pthread_create(&thr2, NULL, (void*)thread, (void*)1);

    /* wait for threads to complete */
	printf("Main: Waiting for threads to complete\n");
	pthread_join(thr1, NULL);
	pthread_join(thr2, NULL);

	/* clean up */
	printf("Main: Calling csl_driver_shutdown\n");
	csl_driver_shutdown(driver);

	printf("Main: Done\n");
	return 0;
}
