
/***********************************************************************/
/* Beginning of AUDIO related code                                     */
/***********************************************************************/


struct sound_clip {
	int active;
	int nsamples;
	int pos;
	double *sample;
} clip[NCLIPS];

struct sound_clip audio_queue[MAX_CONCURRENT_SOUNDS];

int nclips = 0;


int read_clip(int clipnum, char *filename)
{
	SNDFILE *f;
	SF_INFO sfinfo;
	sf_count_t nframes;

	memset(&sfinfo, 0, sizeof(sfinfo));
	f = sf_open(filename, SFM_READ, &sfinfo);
	if (f == NULL) {
		fprintf(stderr, "sf_open('%s') failed.\n", filename);
		return -1;
	}

	printf("Reading sound file: '%s'\n", filename);
	printf("frames = %lld\n", sfinfo.frames);
	printf("samplerate = %d\n", sfinfo.samplerate);
	printf("channels = %d\n", sfinfo.channels);
	printf("format = %d\n", sfinfo.format);
	printf("sections = %d\n", sfinfo.sections);
	printf("seekable = %d\n", sfinfo.seekable);

	clip[clipnum].sample = (double *) 
		malloc(sizeof(double) * sfinfo.channels * sfinfo.frames);
	if (clip[clipnum].sample == NULL) {
		printf("Can't get memory for sound data for %llu frames in %s\n", 
			sfinfo.frames, filename);
		goto error;
	}

	nframes = sf_readf_double(f, clip[clipnum].sample, sfinfo.frames);
	if (nframes != sfinfo.frames) {
		printf("Read only %llu of %llu frames from %s\n", 
			nframes, sfinfo.frames, filename);
	}
	clip[clipnum].nsamples = (int) nframes;
	if (clip[clipnum].nsamples < 0)
		clip[clipnum].nsamples = 0;

	sf_close(f);
	return 0;
error:
	sf_close(f);
	return -1;
}

/* precompute 16 2-second clips of various sine waves */
int init_clips()
{
	int i, j;
	double phase, phaseinc, sawinc, sawval;
	phaseinc = 0.01;
	sawinc = 0.01;

	memset(&audio_queue, 0, sizeof(audio_queue));

	for (i=0;i<NCLIPS;i++) {
		clip[i].nsamples = CLIPLEN;
		clip[i].pos = 0;
		clip[i].active = 0;
		sawval = 0;
		sawinc = (double) 0.01 + (double) i / 1000.0;
		phase = 0.0;
		clip[i].sample = malloc(sizeof(clip[i].sample[0]) * CLIPLEN); 
		if (clip[i].sample == NULL) {
			printf("malloc failed, i=%d\n", i);
			continue;
		}
		for (j=0;j<CLIPLEN;j++) {
			if ((i % 3) != 0) {
				clip[i].sample[j] = (double) (((CLIPLEN) - j) / ((double) 2*j+CLIPLEN)) * sin(phase);
				phase += phaseinc;
				if (phase > TWOPI) 
					phase -= TWOPI;
			} else {
				sawval += sawinc;
				clip[i].sample[j] = sawval * (double) (CLIPLEN -j) / (double) (j+CLIPLEN);
				if (sawval > 0.80 || sawval < -0.80)
					sawinc = -sawinc;
			}
		}
		phaseinc *= 1.4;
		//phaseinc *= 1.02;
	}

	read_clip(PLAYER_LASER_SOUND, "sounds/18385_inferno_laserbeam.wav");
	read_clip(BOMB_IMPACT_SOUND, "sounds/18390_inferno_plascanh.wav");
	read_clip(ROCKET_LAUNCH_SOUND, "sounds/18386_inferno_lightrl.wav");
	read_clip(FLAK_FIRE_SOUND, "sounds/18382_inferno_hvylas.wav");
	read_clip(LARGE_EXPLOSION_SOUND, "sounds/18384_inferno_largex.wav");
	read_clip(ROCKET_EXPLOSION_SOUND, "sounds/9679__dobroide__firecracker.04_modified.wav");
	read_clip(LASER_EXPLOSION_SOUND, "sounds/18399_inferno_stormplas.wav");
	read_clip(GROUND_SMACK_SOUND, "sounds/ground_smack.wav");
	read_clip(INSERT_COIN_SOUND, "sounds/us_quarter.wav");
	read_clip(MUSIC_SOUND, "sounds/lucky13-steve-mono-mix.wav");
	read_clip(SAM_LAUNCH_SOUND, "sounds/18395_inferno_rltx.wav");

	return 0;
}


/* This routine will be called by the PortAudio engine when audio is needed.
** It may called at interrupt level on some machines so don't do anything
** that could mess up the system like calling malloc() or free().
*/
static int patestCallback(const void *inputBuffer, void *outputBuffer,
	unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
	PaStreamCallbackFlags statusFlags, __attribute__ ((unused)) void *userData )
{
	// void *data = userData; /* Prevent unused variable warning. */
	float *out = (float*)outputBuffer;
	int i, j, sample, count = 0;
	(void) inputBuffer; /* Prevent unused variable warning. */
	float output = 0.0;

	for (i=0; i<framesPerBuffer; i++) {
		output = 0.0;
		count = 0;
		for (j=0; j<NCLIPS; j++) {
			if (!audio_queue[j].active || 
				audio_queue[j].sample == NULL)
				continue;
			sample = i + audio_queue[j].pos;
			count++;
			if (sample >= audio_queue[j].nsamples) {
				audio_queue[j].active = 0;
				continue;
			}
			output += audio_queue[j].sample[sample];
		}
		*out++ = (float) output / 2; /* (output / count); */
        }
	for (i=0;i<NCLIPS;i++) {
		if (!audio_queue[i].active)
			continue;
		audio_queue[i].pos += framesPerBuffer;
		if (audio_queue[i].pos >= audio_queue[i].nsamples)
			audio_queue[i].active = 0;
	}
	return 0; /* we're never finished */
}

static PaStream *stream = NULL;

void decode_paerror(PaError rc)
{
	if (rc == paNoError)
		return;
	fprintf(stderr, "An error occured while using the portaudio stream\n");
	fprintf(stderr, "Error number: %d\n", rc);
	fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(rc));
}

void terminate_portaudio(PaError rc)
{
	Pa_Terminate();
	decode_paerror(rc);
}

int initialize_portaudio()
{
	PaStreamParameters outparams;
	PaError rc;

	init_clips();

	rc = Pa_Initialize();
	if (rc != paNoError)
		goto error;
    
	outparams.device = Pa_GetDefaultOutputDevice();  /* default output device */
	outparams.channelCount = 1;                      /* mono output */
	outparams.sampleFormat = paFloat32;              /* 32 bit floating point output */
	outparams.suggestedLatency = 
		Pa_GetDeviceInfo(outparams.device)->defaultLowOutputLatency;
	outparams.hostApiSpecificStreamInfo = NULL;

	rc = Pa_OpenStream(&stream,
		NULL,         /* no input */
		&outparams, SAMPLE_RATE, FRAMES_PER_BUFFER,
		paNoFlag, /* paClipOff, */   /* we won't output out of range samples so don't bother clipping them */
		patestCallback, NULL /* cookie */);    
	if (rc != paNoError)
		goto error;
	if ((rc = Pa_StartStream(stream)) != paNoError);
		goto error;
#if 0
	for (i=0;i<20;i++) {
		for (j=0;j<NCLIPS;j++) {
			// printf("clip[%d].pos = %d, active = %d\n", j, clip[j].pos, clip[j].active);
			Pa_Sleep( 250 );
			if (clip[j].active == 0) {
				clip[j].nsamples = CLIPLEN;
				clip[j].pos = 0;
				clip[j].active = 1;
			}
		}
		Pa_Sleep( 1500 );
	}
#endif
	return rc;
error:
	terminate_portaudio(rc);
	return rc;
}


void stop_portaudio()
{
	int rc;

	if ((rc = Pa_StopStream(stream)) != paNoError)
		goto error;
	rc = Pa_CloseStream(stream);
error:
	terminate_portaudio(rc);
	return;
}

int add_sound(int which_sound, int which_slot)
{
	int i;

	if (which_slot != ANY_SLOT) {
		if (audio_queue[which_slot].active)
			audio_queue[which_slot].active = 0;
		audio_queue[which_slot].pos = 0;
		audio_queue[which_slot].nsamples = 0;
		/* would like to put a memory barrier here. */
		audio_queue[which_slot].sample = clip[which_sound].sample;
		audio_queue[which_slot].nsamples = clip[which_sound].nsamples;
		/* would like to put a memory barrier here. */
		audio_queue[which_slot].active = 1;
		return which_slot;
	}
	for (i=1;i<MAX_CONCURRENT_SOUNDS;i++) {
		if (audio_queue[i].active == 0) {
			audio_queue[i].nsamples = clip[which_sound].nsamples;
			audio_queue[i].pos = 0;
			audio_queue[i].sample = clip[which_sound].sample;
			audio_queue[i].active = 1;
			break;
		}
	}
	return (i >= MAX_CONCURRENT_SOUNDS) ? -1 : i;
}

void cancel_sound(int queue_entry)
{
	audio_queue[queue_entry].active = 0;
}

void cancel_all_sounds()
{
	int i;
	for (i=0;i<MAX_CONCURRENT_SOUNDS;i++)
		audio_queue[i].active = 0;
}

/***********************************************************************/
/* End of AUDIO related code                                     */
/***********************************************************************/

