/*
 * portaudio_lite.c -- minimal interface to the portaudio library
 *
 * Made for use with Visualworks Smalltalk DLLCC
 * Stephen Pope -- stp@create.ucsb.edu -- 2003.07.16
 */

#include "userprim.h"
#include "oeAPI.h"
#include <stdio.h>
#include <math.h>
#include "portaudio.h"
#include "portaudio_lite.h"

PaStream * s_pa_stream;			// the PortAudio stream we play out/get data from

// These are all global; perhaps they should be put in the PortAudio 
// callback data structure, but why bother?

short * in_buf, * out_buf;	// I/O buffer pointers given to us by the Smalltalk VM
int pa_is_inited = 0,		// driver status flags
	pa_is_open = 0, 
	pa_is_running = 0;
OEint io_sem;				// the registry slot of the I/O semaphore
unsigned in_ch;				// the # of I/O channels
unsigned out_ch;

#define NUM_CLIENTS 4

typedef struct {			// structure to represent a playing sound (low-level version)
	unsigned short * data;
	unsigned totalFrames;
	unsigned currentFrame;
} PA_CLIENT;

PA_CLIENT clients[NUM_CLIENTS];		// the array of active clients (playing sounds)

// the PA callback function for use with the semaphore to Smalltalk

static int pa_callback_sem (void * inputBuffer, void * outputBuffer, unsigned long frames, 
				PaTimestamp outTime, void * userData) {
//	printf("PA callback\n");
	
	if (out_ch > 0) { 		// copy output buffers
		unsigned osize = frames * out_ch * sizeof(short);
		memcpy(outputBuffer, out_buf, osize);
		bzero(out_buf, osize);
	}
	if (in_ch > 0) {		// and/or copy input buffers
		memcpy(in_buf, inputBuffer, frames * in_ch * sizeof(short));
	}							// signal i/o semaphore
	OEoop sem = oeRegisteredHandleAt(io_sem);
	oeSignalSemaphore(sem);
	return 0;
}

// The callback for use with client playing (no callback to Smalltalk)

static int pa_callback_nosem (void * inputBuffer, void * outputBuffer, unsigned long frames, 
				PaTimestamp outTime, void * userData) {
	short * samples = (short *) outputBuffer;
	unsigned i, j;
	for (i = 0; i < NUM_CLIENTS; i++) {
		if (clients[i].totalFrames > 0) {		// if this client is playing
			PA_CLIENT cli = clients[i];
			unsigned cFrame = cli.currentFrame;
			unsigned tFrame = cli.totalFrames;
			unsigned short * cData = cli.data + (cFrame * out_ch);
			unsigned toCopy = frames < (tFrame - cFrame) ? frames : (tFrame - cFrame);
			for (j = 0; j < (toCopy * out_ch); j++) 	// copy data
				*samples++ = (short) (*cData++ - 32767);
			cli.currentFrame += toCopy;
			if (cli.currentFrame >= cli.totalFrames)
				cli.totalFrames = 0;
		}
	}
	return 0;
}

// PA error handler -- print to stdout

void pa_handle_error(PaError err) {
	pa_is_open = 0;
	pa_is_running = 0;
	printf("\n\tPortAudio error: %d\n", err);
	printf("\t\tMessage: %s\n", Pa_GetErrorText(err));	
	if (pa_is_inited)
		Pa_Terminate();
}

// initialize PA

int pa_initialize(void) {
	PaError err;
	if (pa_is_inited)			// return if already initialized
		return 0;
	printf("PA_init\n");
	err = Pa_Initialize();
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_inited = 1;
	pa_is_open = 0;
	pa_is_running = 0;
	return 0;
}

// open PA -- use the default device for now, use a callback semaphore to signal Smalltalk

int pa_open_s(int device, unsigned in_c, unsigned out_c, 
		unsigned long format, unsigned rate, unsigned blockSize,
		_oop sem, unsigned short * in_buffer, unsigned short * out_buffer) {
	PaError err;
	if ( ! pa_is_inited)		// lazy initialization OK
		pa_initialize();
	if (pa_is_open)				// return if already open
		return 0;
	printf("PA_open (%d i %d o sr %d bs %d)\n", in_ch, out_ch, rate, blockSize);
					// set up the callback data structure	
	io_sem = oeAllocRegistrySlot();
	oeRegisteredHandleAtPut(io_sem, sem);
	out_ch = out_c;
	in_ch = in_c;
	in_buf = in_buffer;	// store I/O buffer pointers
	out_buf = out_buffer;
					// now open default PA stream
	err = Pa_OpenDefaultStream(&s_pa_stream, in_ch, out_ch, 
				format, rate, blockSize, 0,
				pa_callback_sem, NULL);
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_open = 1;
	bzero(in_buf, blockSize * in_ch * 2);
	bzero(out_buf, blockSize * out_ch * 2);
	oeSignalSemaphore(sem);		// request output once to get the first buffer
	return 0;
}

// Simpler version (does not use call-backs to Smalltalk)

int pa_open_p(int device, unsigned out_c, 
		unsigned long format, unsigned rate, unsigned blockSize) {
	PaError err;
	if ( ! pa_is_inited)		// lazy initialization OK
		pa_initialize();
	if (pa_is_open)				// return if already open
		return 0;
	printf("PA_open (%d o sr %d bs %d)\n", out_c, rate, blockSize);
					// set up the callback data structure	
	out_ch = out_c;
	in_ch = 0;
					// zero out the playing clients
	unsigned  i;
	for (i = 0; i < NUM_CLIENTS; i++)
		clients[i].totalFrames = 0;
					// now open default PA stream
	err = Pa_OpenDefaultStream(&s_pa_stream, 0, out_ch, 
				format, rate, blockSize, 0,
				pa_callback_nosem, NULL);
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_open = 1;
	return 0;
}

// start PA playing

int pa_start(void) {
	PaError err;
	if ( ! pa_is_open) {		// no lazy opening allowed
		printf("Error: cannot start closed portaudio interface\n");
		oeFail(-1);
	}
	if (pa_is_running)			// return if already running
		return 0;
	printf("PA_start\n");
	err = Pa_StartStream(s_pa_stream);
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_running = 1;
	return 0;
}

// stop PA playing

int pa_stop(void) {
	PaError err;
	if ( ! pa_is_running)
		return 0;
	printf("PA_stop\n");
	err = Pa_StopStream(s_pa_stream);
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_running = 0;
	return 0;
}

// close PA stream

int pa_close(void) {
	PaError err;
	if ( ! pa_is_open)
		return 0;
	printf("PA_close\n");
	err = Pa_CloseStream(s_pa_stream);
	if (err != paNoError) {
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_open = 0;
	return 0;
}

// terminate PA altogether

int pa_terminate(void) {
	PaError err;
	if ( ! pa_is_inited)
		return 0;
	printf("PA_terminate\n");
	err = Pa_Terminate();
	if (err != paNoError) {
		pa_is_inited = 0;
		pa_handle_error(err);
		oeFail(err);
	}
	pa_is_inited = 0;
	return 0;
}

// Play a sound buffer -- plug the given data into the client list

int pa_play(unsigned short * out_buffer, unsigned numFrames) {
	unsigned i;
	for (i = 0; i < NUM_CLIENTS; i++) {
		if (clients[i].totalFrames == 0) {
			printf("PA_play %d (%d)\n", numFrames, i);
			clients[i].data = out_buffer;
			clients[i].totalFrames = numFrames;
			clients[i].currentFrame = 0;
			return 0;
		}
	}
}

