
/** @file mp_thread_client.c
 *
 * @brief This simple client demonstrates the use of "jack_thread_wait" function in a multi-threaded context.
 
  A set of threads (the jack process thread + n helper threads) are used to work on a global queue of tasks.
  The last finishing thread gives control back to libjack using the "jack_thread_wait" function. Other threads suspend
  on a condition variable and are resumed next cycle by the libjack suspended thread.
*/
 
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
 
#define __SMP__ 1

#include <jack/jack.h>
#include <jack/thread.h>
#include <math.h>
 
#include "JackAtomic.h"
 
jack_port_t *input_port;
jack_port_t *output_port;
jack_client_t *client;
int buffer_size;
 
#define WORK_AT_EACH_CYCLE   1000
#define WORK_AT_EACH_LOOP  15
static SInt32 cycle_work_count = 0;
 
pthread_cond_t	cond;
pthread_mutex_t	mutex;
 
jack_nframes_t last_time = 0;
static int print_count = 50;
int result = 0;

typedef struct thread_context 
{
	pthread_t thread;
	int num;
};
 
// Simulate workload
static int fib(int n)
{
	if (n < 2)
		return n;
	else 
		return fib(n - 2) + fib(n - 1);
}

static void do_some_work(void *arg)
{
	result = fib(WORK_AT_EACH_LOOP);
}
 
static void resume_all_threads(void *arg) 
{
	thread_context* context = (thread_context*)arg;
	
	jack_nframes_t cur_time = jack_frame_time(client);
	if (--print_count == 0) {
		printf("resume_all_threads from thread = %ld jack_frame_time = %u jack_cpu_load = %f\n", context->num, (cur_time - last_time), jack_cpu_load(client));
		print_count = 50;
	}
	pthread_mutex_lock(&mutex);		// Hum...
	pthread_cond_broadcast(&cond); 
	pthread_mutex_unlock(&mutex);	// Hum...
	cycle_work_count = WORK_AT_EACH_CYCLE;
	last_time = cur_time;
}
 
static void suspend_jack_thread(void *arg) 
{
	jack_thread_wait(client, 0);
	resume_all_threads(arg);
}
 
static void suspend_worker_thread(void *arg) 
{
	pthread_mutex_lock(&mutex);		// Hum...
	pthread_cond_wait(&cond, &mutex); 
	pthread_mutex_unlock(&mutex);	// Hum...
}
 
static void * worker_aux_thread(void *arg)
{
	while (1) {
 
		int val = DEC_ATOMIC(&cycle_work_count);
 
		if (val == 1) {  // Last thread
			suspend_jack_thread(arg);
		} else if (val < 1) {	
			suspend_worker_thread(arg);
		} else {
			do_some_work(arg);
		}
	}
 
	return 0;
}
 
static void * worker_thread(void *arg)
{
	suspend_worker_thread(arg);		// Start in "suspended" state
	worker_aux_thread(arg);
	return 0;
}
 
// Example of audio process
int process(jack_nframes_t nframes, void *arg)
{
	resume_all_threads(arg);
	worker_aux_thread(arg);
	return 0;
}
 
/**
 * JACK calls this shutdown_callback if the server ever shuts down or
 * decides to disconnect the client.
 */
void jack_shutdown (void *arg)
{
	exit(1);
}
 
int main (int argc, char *argv[])
{
	thread_context* worker_threads;
	int n, nthreads = 0;
	
	if (argc == 2) 
		nthreads = atoi(argv[1]);
	
	worker_threads = (thread_context *) malloc (sizeof (thread_context) * nthreads);
	
	/* open a client connection to the JACK server */
	if ((client = jack_client_open("mp_thread_test", JackNoStartServer, NULL)) == NULL) {
		fprintf(stderr, "Cannot open client\n");
		exit(1);
	}
 
	buffer_size = jack_get_buffer_size(client);
 
	/* tell the JACK server to call the 'callback' function
	*/
	worker_threads[0].num = 0;
	jack_set_process_callback(client, process, &worker_threads[0]);
 
	/* tell the JACK server to call `jack_shutdown()' if
	   it ever shuts down, either entirely, or if it
	   just decides to stop calling us.
	*/
	jack_on_shutdown(client, jack_shutdown, 0);
 
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);
 
	input_port = jack_port_register(client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
	output_port = jack_port_register(client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
 
	if ((input_port == NULL) || (output_port == NULL)) {
		fprintf(stderr, "no more JACK ports available\n");
		exit(1);
	}
 	
	fprintf(stderr, "Creating %d threads\n", nthreads);
 
	for (n = 1; n <= nthreads; ++n) {
		worker_threads[n].num = n;
		if (jack_client_create_thread(client, &worker_threads[n].thread, 90, 1, worker_thread, &worker_threads[n]) < 0) 
			exit(1);
		jack_acquire_real_time_scheduling (worker_threads[n].thread, 90);
	}
 
	/* Tell the JACK server that we are ready to roll.  Our
	 * process() callback will start running now. */
 
	if (jack_activate(client)) {
		fprintf(stderr, "cannot activate client");
		exit(1);
	}
 
	while (1) {
	#ifdef WIN32 
		Sleep(1000);
	#else
		sleep(1);
	#endif
	}
 
	jack_client_close(client);
 
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
 
	exit(0);
}
