/*
 * Here there are two distinct implementations of the audio.h interface:
 * 
 * 1. Windows.
 * 
 * 2. Linux.
 * 
 */

#ifdef WINNT

/*
 * Windows implementation.
 * 
 * IMPLEMENTATION NOTES.
 * 
 * 0. A pulse-code modulation (PCM) sound consists in a sequence of frames,
 * each frame contains one sample for each channel in the order (for example,
 * channel 0 is left, channel 1 is right). Frames are played by the audio
 * driver at a given fixed rate. This rate can be changed, but see note 2.
 * The audio driver must be feed with an integral number of frames, that is
 * frames cannot be split. Each sample is assumed to be a little-endian byte
 * ordered integer number, which is exactly the case of the WAV files.
 * 
 * 1. A feeder thread is created for each sound to play, so avoiding any
 * interruption in the execution of the main program. Once the feeder thread
 * has taken control of the sound, the main thread must not change or release
 * the data structures of the sound, with the only exception of some parameters:
 * rate of sampling, start/stop, thread termination request.
 * 
 * 2. The audio driver may or may not support rate changes. Closing the device
 * and opening the device again just to change the rate would cause a brief
 * sound interruption the user would ear as a "click", so it is not suitable.
 * Then if the audio driver does not support rate changes, we must implement
 * our built-in re-sampler at the cost of a bit of CPU load. Resampling is
 * performed in the feeder thread, and the resulting re-sampled audio is then
 * sent to the audio driver which will play these frames at its own current
 * fixed rate.
 * 
 * 3. The feeder thread sends to the audio driver chunks of sampled frames
 * of fixed size. Each chunk is played by the audio driver in a time of
 * 
 *   t = CHUNK_SIZE / (frameLength * nSamplesPerSec)
 * 
 * seconds. Between each frame sent to the audio driver, the feeder thread here
 * checks the status of the sound, including the requested rate and start/stop.
 * By setting a value too large, the program reacts slowly to the changes and
 * a continuous rate pitch change becomes a sequence of tones instead.
 * By setting a value too small, slower machines may fail to feed the audio
 * driver with enough data and the sound becomes intermittent.
 * 
 * For example, by setting a reasonable time of t = 0.1 s and a sound sampled
 * at 8000 Hz, 1 byte/frame, the resulting chunk size is:
 * 
 *   CHUNK_SIZE = t * frameLength * nSamplesPerSec = 800 bytes
 * 
 * Again, still with t = 0.1 s but with a sound sampled at 44100 Hz, 16 bits per
 * sample and stereophonic (frame length of 4 bytes), we have:
 * 
 *   CHUNK_SIZE = 17640 bytes
 * 
 * For our monophonic, 1 byte/frame, 8000 Hz audio samples, 1024 seems to be a
 * good compromise. An improved implementation would require to tune this value
 * based on the actual frame length and frame rate of each sound.
 */

#include <stdio.h>
#include <stdint.h>
#include <assert.h>

#include "error.h"
#include "memory.h"
#include "wav.h"

#define audio_IMPORT
#include "audio.h"

#include <windows.h>
#include <mmsystem.h>

/**
 * Maximum length of the chunks sent to the audio driver. See implementation
 * note 3.
 */
#define CHUNK_SIZE    (1024)

struct audio_Type {
	
	/** Audio device. */
	HWAVEOUT wave_out;
	
	/** Consumed chunks and thread termination requests signaled through this. */
	HANDLE event;
	
	/** Thread that feeds chunks to the driver. See implementation note 1. */
	HANDLE thread;
	
	/** Pause flag. */
	int is_paused;
	
	/** Destructor sets this to ask feeding thread to terminate.
	 * See implementation note 1. */
	int thread_termination_request_pending;
	
	/** Loop execution flag. */
	int loop;
	
	/** New cursor position requested flag; position_request_at is the value. */
	int position_request_pending;
	
	/** Byte offset in the sample data where to set the cursor. */
	int position_request_at;
	
	/** Current sampling rate requested by client. */
	DWORD currentSamplesPerSec;
	
	/** Index to the next byte of sample data to play. */
	int cursor;
	
	/** WAV file parameters. */
	wav_Type *wav;
	
	/**
	 * Chunks descriptor data for chunks to send to the driver. When playing,
	 * tries to fill and send to the driver both the chunks to avoid the
	 * annoying "click" sound.
	 */
	WAVEHDR chunks_headers[2];
	
	/** Chunks of frames. */
	char    chunks_data[2][CHUNK_SIZE];
};


/**
 * Whether waveOutSetPitch() works on this device. If that function fails,
 * uses internal emulation. See implementation note 3.
 */
static int pitchChangeSupportedByDevice = 1;


static void CALLBACK audio_waveOutputCallback(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) {
	audio_Type *this = (audio_Type *) dwInstance;

	switch (uMsg) {
	case WOM_OPEN:
		break;

	case WOM_DONE:
		SetEvent(this->event);
		break;

	case WOM_CLOSE:
		break;
		
	default:
		error_internal("unexpected event code: %d", uMsg);
	}
}


/**
 * Software emulation of the pitch changer. Used when hw support is missing,
 * see implementation note 3.
 * Increments this->index by the number of bytes copied or skipped from the
 * source (depending on the pitch skew rate) and returns the number of bytes
 * copied in the destination.
 * @param this
 * @param src Source of the original audio samples.
 * @param src_len Length in bytes of the source buffer of samples.
 * @param dst Destination of the re-sampled audio.
 * @param dst_len Length in bytes of the destination buffer. Must contain at
 * least one sample or a fatal error occurs.
 * @return Number of bytes actually copied in the destination.
 */
static int audio_resample(audio_Type *this,
	char *src, int src_len,
	char *dst, int dst_len)
{
	int src_rate = this->wav->nSamplesPerSec;
	int dst_rate = this->currentSamplesPerSec;
	int sample_len = this->wav->nBlockAlign;
	// Pitch factor maps byte offset of the src into dst:
	double k = (double) dst_rate / (sample_len * src_rate);
	dst_len -= dst_len % sample_len;
	assert(dst_len >= sample_len);
	// For each dst sample, calculate the src sample and copy.
	int src_byte_index = 0;
	int dst_byte_index = 0;
	while( dst_byte_index < dst_len ){
		src_byte_index = sample_len * (int) (dst_byte_index * k + 0.5);
		if( src_byte_index >= src_len )
			break;
		memcpy(dst + dst_byte_index, src + src_byte_index, sample_len);
		dst_byte_index += sample_len;
	}
	this->cursor += src_byte_index + sample_len;
	return dst_byte_index;
}


/**
 * Fill-in and send to the audio driver a chunk of audio data.
 * @param this
 * @param index Chunk to fill, either 0 or 1.
 * @return True if some data available and sent, false if chunk empty.
 */
static int audio_fillAndFeedChunk(audio_Type *this, int index) {
	UINT avail_in_buffer = CHUNK_SIZE;
	WAVEHDR *wavehdr = &this->chunks_headers[index];
	wavehdr->dwFlags &= ~WHDR_DONE;
	int sample_len = this->wav->nBlockAlign;
	
	if( this->position_request_pending ){
		this->position_request_pending = 0;
		this->cursor = this->position_request_at;
	}

	do {
		int avail_in_sample = this->wav->data_len - this->cursor;
		if (avail_in_sample <= 0) {
			if( this->loop ){
				this->cursor = 0;
				avail_in_sample = this->wav->data_len;
			} else {
				break;
			}
		}
		if( ! pitchChangeSupportedByDevice
		&& this->currentSamplesPerSec != this->wav->nSamplesPerSec ){
			// Do pitch change when no hw support available.
			avail_in_buffer -= audio_resample(this,
				this->wav->data + this->cursor, avail_in_sample,
				this->chunks_data[index] + CHUNK_SIZE - avail_in_buffer, avail_in_buffer);
		} else {
			// Basic algo when no pitch change or hw supports pitch change hw.
			int to_copy = avail_in_sample;
			if (to_copy > avail_in_buffer)
				to_copy = avail_in_buffer;
			memcpy(this->chunks_data[index] + CHUNK_SIZE - avail_in_buffer,
				this->wav->data + this->cursor,
				to_copy);
			this->cursor += to_copy;
			avail_in_buffer -= to_copy;
		}
	} while (avail_in_buffer > sample_len);

	wavehdr->lpData = this->chunks_data[index];
	wavehdr->dwFlags = 0;
	wavehdr->dwBufferLength = CHUNK_SIZE - avail_in_buffer;
	if( wavehdr->dwBufferLength == 0 ){
		return 0;
	} else {
		waveOutPrepareHeader(this->wave_out, wavehdr, sizeof(WAVEHDR));
		waveOutWrite(this->wave_out, &this->chunks_headers[index], sizeof(WAVEHDR));
		return 1;
	}
}


/**
 * Driver buffer feeder thread. Continuously feeds the driver with 2 chunks if
 * possible. See implementation note 1.
 * Suspends if end of the audio data reached and no loop enabled, but restarts
 * whenever loop enabled or current position moved back.
 * Note that the pause feature is already built-in in the driver so does not
 * need to be replicated here.
 * This function returns when this->thread_termination_request_pending is set.
 * @param pDataInput
 * @return 
 */
static DWORD WINAPI audio_feeder(PVOID pDataInput)
{
	audio_Type *this = (audio_Type *) pDataInput;
	
	// Marks all chunks as available.
	this->chunks_headers[0].dwFlags = 0;
	this->chunks_headers[1].dwFlags = 0;

	while (!this->thread_termination_request_pending) {
		
		if( ! this->is_paused ){
			// Try to fill and send to the driver as many chunks as possible.
			if ( this->chunks_headers[0].dwFlags == 0 )
				audio_fillAndFeedChunk(this, 0);
			if ( this->chunks_headers[1].dwFlags == 0 )
				audio_fillAndFeedChunk(this, 1);
		}
		
		// Wait for either chunk consumed or timeout or more data to play
		// available because loop enabled or playing resumed, or thread
		// termination request from destructor:
		int waitStatus = WaitForSingleObject(this->event, 123);
		switch(waitStatus){
		case WAIT_OBJECT_0:
			// Some chunk consumed by driver. Make it available again.
			if ( this->chunks_headers[0].dwFlags & WHDR_DONE )
				this->chunks_headers[0].dwFlags = 0;
			if ( this->chunks_headers[1].dwFlags & WHDR_DONE )
				this->chunks_headers[1].dwFlags = 0;
			break;
		case WAIT_TIMEOUT:
			// Still no chunk consumed, or no data to send (wav ended or pause).
			break;
		default:
			error_internal("WaitForSingleObject() returned code %d", waitStatus);
		}
	}

	waveOutReset(this->wave_out);

	return 0;
}


static void audio_destruct(void *p)
{
	audio_Type *this = (audio_Type *) p;

	if (this->thread) {
		this->thread_termination_request_pending = 1;
		SetEvent(this->event);
		WaitForSingleObject(this->thread, INFINITE);
		CloseHandle(this->event);
		CloseHandle(this->thread);
	}
	
	waveOutClose(this->wave_out);
	memory_dispose(this->wav);
}


audio_Type * audio_new(char * wav_filename)
{
	audio_Type *this = memory_allocate(sizeof(audio_Type), audio_destruct);
	memory_zero(this);
	this->wav = wav_fromFile(wav_filename);
	this->is_paused = 1;
	this->currentSamplesPerSec = this->wav->nSamplesPerSec;
	WAVEFORMATEX wf;
	wf.wFormatTag      = this->wav->wFormatTag;
	wf.nChannels       = this->wav->nChannels;
	wf.nSamplesPerSec  = this->wav->nSamplesPerSec;
	wf.nAvgBytesPerSec = this->wav->nAvgBytesPerSec;
	wf.nBlockAlign     = this->wav->nBlockAlign;
	wf.wBitsPerSample  = this->wav->wBitsPerSample;
	wf.cbSize = sizeof(wf);
	MMRESULT err = waveOutOpen(&this->wave_out, WAVE_MAPPER, &wf,
		(ULONG) audio_waveOutputCallback, (ULONG) this, CALLBACK_FUNCTION);
	if( err != MMSYSERR_NOERROR)
		error_internal("waveOutOpen() failed: %d", err);
	DWORD dwThreadId;
	this->event = CreateEvent(NULL, FALSE, FALSE, NULL);
	if( this->event == NULL )
		error_internal("CreateEvent() failed: %d", GetLastError());
	this->thread = CreateThread(NULL, 0, audio_feeder, this, 0, &dwThreadId);
	if( this->thread == NULL )
		error_internal("CreateThread() failed: %d", GetLastError())
	//waveOutPause(this->wave_out);
	return this;
}

void audio_play(audio_Type *this)
{
	if( this->is_paused ){
		this->is_paused = 0;
		//waveOutRestart(this->wave_out);
	}
}

void audio_pause(audio_Type *this)
{
	if( ! this->is_paused ){
		this->is_paused = 1;
		//waveOutPause(this->wave_out);
	}
}

int audio_isPaused(audio_Type *this)
{
	return ! this->is_paused;
}

void audio_loop(audio_Type *this, int enable)
{
	this->loop = enable;
}

int audio_isLooping(audio_Type *this)
{
	return this->loop;
}

int audio_isPlaying(audio_Type *this)
{
	return ! this->is_paused && this->cursor < this->wav->data_len;
}

int  audio_getOriginalLengthMilliseconds(audio_Type *this)
{
	return 1000 * this->wav->data_len
		/ (this->wav->nSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

int  audio_getCurrentLengthMilliseconds(audio_Type *this)
{
	return 1000 * this->wav->data_len
		/ (this->currentSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

int  audio_getCurrentPositionMilliseconds(audio_Type *this)
{
	return 1000 * this->cursor
		/ (this->currentSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

void audio_setCurrentPositionMilliseconds(audio_Type *this, int position)
{
	int index = position
		* this->currentSamplesPerSec
		* this->wav->nChannels
		* this->wav->nBlockAlign
		/ 1000;
	if( index < 0 )
		index = 0;
	else if( index > this->wav->data_len )
		index = this->wav->data_len;
	this->position_request_at = index;
	this->position_request_pending = 1;
}

int  audio_getOriginalSamplesPerSecond(audio_Type *this)
{
	return this->wav->nSamplesPerSec;
}

int  audio_getCurrentSamplesPerSecond(audio_Type *this)
{
	return this->currentSamplesPerSec;
}


void audio_setCurrentSamplesPerSecond(audio_Type *this, int rate)
{
	if( rate < 100 )
		rate = 100;
	else if( rate > 15000 )
		rate = 15000;
	if( pitchChangeSupportedByDevice ){
		double pitchFactor = rate / this->wav->nSamplesPerSec;
		MMRESULT err = waveOutSetPitch(this->wave_out, pitchFactor * UINT32_MAX);
		if( err == MMSYSERR_NOTSUPPORTED ){
			pitchChangeSupportedByDevice = 0; // enable sw emulation instead
		} else if( err != MMSYSERR_NOERROR ){
			printf("waveOutSetPitch() unexpected exit code is %d\n", err);
			pitchChangeSupportedByDevice = 0;
		}
	}
	this->currentSamplesPerSec = rate;
}



#else


/*
 * 
 *      Linux implementation with Alsa audio library.
 * 
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
#include <alsa/asoundlib.h>

#include "error.h"
#include "memory.h"
#include "wav.h"

#define audio_IMPORT
#include "audio.h"


#define audio_ALSA_DEVICE "default"


struct audio_Type {
	
	/** Audio device. */
	snd_pcm_t *wave_out;
	
	/** Thread that feeds chunks to the driver. */
	pthread_t thread;
	
	/** Pause flag. */
	int is_paused;
	
	/** Destructor sets this to ask feeding thread to terminate. */
	int thread_termination_request_pending;
	
	/** Loop execution flag. */
	int loop;
	
	/** New cursor position requested flag; position_request_at is the value. */
	int position_request_pending;
	
	/** Byte offset in the sample data where to set the cursor. */
	int position_request_at;
	
	/** Current sampling rate requested by client. */
	uint32_t currentSamplesPerSec;
	
	/** Client signals here to the feeder thread he changed the current samples rate. */
	int samplesPerSecRequestPending;
	
	/** Index to the next byte of the sample data to play. */
	int cursor;
	
	/** WAV file parameters. */
	wav_Type *wav;
};


static void audio_alsaSetParameters(audio_Type *this)
{
	/*
	 * Maps our WAV file parameters into an ALSA compliant format specifier.
	 * FIXME: check WAV to ALSA format mapping; this is only by trial and error!
	 */
	snd_pcm_format_t format;
	switch (this->wav->wBitsPerSample) {
		case  8: format = SND_PCM_FORMAT_U8;     break;
		case 16: format = SND_PCM_FORMAT_S16_LE; break;
		case 24: format = SND_PCM_FORMAT_S24_LE; break;
		case 32: format = SND_PCM_FORMAT_S32_LE; break;
		default: error_internal("%s: unsupported number of bits per sample: %d",
				this->wav->filename, this->wav->wBitsPerSample);
	}
	
	int err = snd_pcm_set_params(this->wave_out,
		format,
		SND_PCM_ACCESS_RW_INTERLEAVED,
		this->wav->nChannels,
		this->currentSamplesPerSec,
		1, // allows alsa-lib resampling // FIXME: ???????
		100000 // required overall latency in us // FIXME: ????????
	);
	if( err != 0 )
		error_internal("%s: setting parameters for audio device '%s': %s",
				this->wav->filename, audio_ALSA_DEVICE, snd_strerror(err));
	
/*
	err = snd_pcm_prepare(this->wave_out);
	if( err != 0 )
		error_internal("%s: preparing audio device '%s': %s",
				this->wav->filename, audio_ALSA_DEVICE, snd_strerror(err));
*/
}


/**
 * Software emulation of the pitch changer.
 * Increments this->cursor by the number of bytes copied or skipped from the
 * source (depending on the pitch skew rate) and returns the number of bytes
 * copied in the destination. It always consumes at least one frame of the
 * source and always copies at least one frame into the destination to prevent
 * locks in the caller.
 * @param this
 * @param src Source buffer of the original audio frames.
 * @param src_len Length in bytes of the source buffer of frames. Must contain
 * at least one frame or a fatal error occurs.
 * @param dst Destination buffer of the re-sampled audio.
 * @param dst_len Length in bytes of the destination buffer. Must contain at
 * least one frame or a fatal error occurs.
 * @return Number of bytes actually copied in the destination. It is always
 * positive.
 */
static int audio_resample(audio_Type *this,
	char *src, int src_len,
	char *dst, int dst_len)
{
	int frame_len = this->wav->frameLength;
	// Destination must contain an integral number of frames for faster
	// end detection:
	dst_len -= dst_len % frame_len;
	assert(src_len >= frame_len);
	assert(dst_len >= frame_len);
	// Pitch factor maps frame offset of the src into dst:
	double k = (double) this->currentSamplesPerSec / this->wav->nSamplesPerSec;
	// For each dst sample, calculate the src frame and copy.
	int src_byte_index = 0;
	int dst_byte_index = 0;
	int dst_frame_index = 0;
	do {
		src_byte_index = frame_len * (int) (dst_frame_index * k + 0.5);
		if( src_byte_index >= src_len )
			break;
		memcpy(dst + dst_byte_index, src + src_byte_index, frame_len);
		dst_byte_index += frame_len;
		dst_frame_index++;
	} while( dst_byte_index < dst_len );
	if( src_byte_index < frame_len )
		src_byte_index = frame_len;
	else if( src_byte_index > src_len )
		src_byte_index = src_len;
	this->cursor += src_byte_index;
	return dst_byte_index;
}


static int32_t audio_write(audio_Type *this, void *frames, int number_of_frames)
{
	int32_t wrote_frames = snd_pcm_writei(this->wave_out, frames, number_of_frames);

	// If an error, try to recover from it
	if (wrote_frames < 0) {
		int err = snd_pcm_recover(this->wave_out, wrote_frames, 1);
		if (err < 0){
			error_internal(
			"ALSA library while playing %s:\n"
			"       snd_pcm_writei() failed: %s\n"
			"       snd_pcm_recover() failed: %s",
			this->wav->filename,
			snd_strerror(wrote_frames), snd_strerror(err));
		}
		wrote_frames = 0;
	}
	return wrote_frames;
}


/**
 * Feeder thread. Continously submits sound frames to the audio driver,
 * checking for clients request changes (loop or one-shot mode, rewind and
 * positioning, thread termination request).
 * @param p Pointer to audio_Type.
 * @return Always returns NULL.
 */
static void * audio_feeder(void *p)
{
	audio_Type *this = p;
	int paused = 0; // paused
	int draining = 0; // paused after one-shot play finished
	char *resample_buffer = NULL;
	int resample_buffer_capacity;
	int resample_length;
	int resample_cursor;

	while( ! this->thread_termination_request_pending ) {
		
		int do_sleep = 1;
		
		if( this->position_request_pending ){
			this->cursor = this->position_request_at;
			if( this->cursor < 0 )
				this->cursor = 0;
			else if( this->cursor > this->wav->data_len )
				this->cursor = this->wav->data_len;
			this->cursor -= this->cursor % this->wav->frameLength;
			this->position_request_pending = 0;
		}
		
		assert(0 <= this->cursor && this->cursor <= this->wav->data_len);
		
		// Check pause request.
		if( this->is_paused ){
			if( paused ){
				// ok
			} else {
				snd_pcm_pause(this->wave_out, 1);
				paused = 1;
			}
			
		} else {
			if( paused ){
				snd_pcm_pause(this->wave_out, 0);
				paused = 0;
			} else {
				// ok
			}
		}
		
		if( ! paused ){
			
			if( this->cursor >= this->wav->data_len && this->loop ){
				this->cursor = 0;
			}
			
			if( this->cursor < this->wav->data_len ){
				
				if( this->samplesPerSecRequestPending ){
					this->samplesPerSecRequestPending = 0;
					draining = 0;
					if( resample_buffer == NULL ){
						// Create a resample buffer that may contain a quite short sample
						// so to react faster to commanded frequencies changes.
						// 1/8 s seems good.
						resample_buffer_capacity = (this->wav->frameLength * this->wav->nSamplesPerSec) >> 3;
						resample_buffer_capacity -= resample_buffer_capacity % this->wav->frameLength;
						resample_buffer = memory_allocate(resample_buffer_capacity, NULL);
						resample_cursor = 0;
						resample_length = 0;
					}
				} else if( draining ){
					draining = 0;
					int err = snd_pcm_prepare(this->wave_out);
					if( err != 0 )
						error_internal("preparing audio device '%s': %s",
								audio_ALSA_DEVICE, snd_strerror(err));
				}

				if( resample_buffer == NULL ){
					uint32_t avail_frames = (this->wav->data_len - this->cursor) / this->wav->frameLength;
					// Send max 1/4 s of audio per turn.
					if( avail_frames > (this->currentSamplesPerSec >> 2) )
						avail_frames = (this->currentSamplesPerSec >> 2);
					int32_t wrote_frames = audio_write(this, this->wav->data + this->cursor, avail_frames);
					if( wrote_frames > 0 ){
						this->cursor += wrote_frames * this->wav->frameLength;
						do_sleep = 0;
					}
					
				} else {
					
					// Fill resample buffer with frames from WAV:
					if( resample_length < resample_buffer_capacity ){
						resample_length += audio_resample(this,
							this->wav->data + this->cursor,
							this->wav->data_len - this->cursor,
							resample_buffer + resample_length,
							resample_buffer_capacity - resample_length);
					}
					
					// Send resampled frames to the driver:
					uint32_t avail_frames = (resample_length - resample_cursor) / this->wav->frameLength;
					if( avail_frames > 0 ){
						int32_t wrote_frames = audio_write(this, resample_buffer + resample_cursor, avail_frames);

						if( wrote_frames > 0 ){
							resample_cursor += wrote_frames * this->wav->frameLength;
							if( resample_cursor >= resample_length ){
								resample_cursor = 0;
								resample_length = 0;
							}
							do_sleep = 0;
						}
					}
					
				}
				
			} else if( this->cursor >= this->wav->data_len ){
				// Single shot mode, end of the sample, .
				// Wait for playback to completely finish and release the
				// driver's playing pipeline, otherwise ALSA would send to
				// stderr the boring "ALSA lib pcm.c:7963:(snd_pcm_recover)
				// underrun occurred" message!
				snd_pcm_drain(this->wave_out);
				draining = 1;
			}
			
		}
		
		if( do_sleep )
			usleep(100000);

	}
	
	memory_dispose(resample_buffer);
	return NULL;
}


static void audio_destruct(void *p)
{
	audio_Type *this = (audio_Type *) p;
	this->thread_termination_request_pending = 1;
	pthread_join(this->thread, NULL);
	snd_pcm_close(this->wave_out);
	memory_dispose(this->wav);
}


audio_Type * audio_new(char * wav_filename)
{
	audio_Type *this = memory_allocate(sizeof(audio_Type), audio_destruct);
	memory_zero(this);
	
	this->wav = wav_fromFile(wav_filename);
	
	if( this->wav->wFormatTag != 1 )
		error_internal("%s: sorry, only WAV format tag 1 (PCM) supported, found: %d",
				wav_filename, this->wav->wFormatTag);
	
	int err = snd_pcm_open(&this->wave_out, audio_ALSA_DEVICE,
			SND_PCM_STREAM_PLAYBACK, 0);
	if( err != 0 )
		error_internal("opening audio device '%s': %s",
				audio_ALSA_DEVICE, snd_strerror(err));
	
	this->is_paused = 1;
	this->currentSamplesPerSec = this->wav->nSamplesPerSec;
	audio_alsaSetParameters(this);
	
	err = pthread_create(&this->thread, NULL, audio_feeder, this);
	if( err != 0 )
		error_external("pthread_create() return code %d: cannot create audio feeder thread to play %s", err, wav_filename);
	
	return this;
}

void audio_play(audio_Type *this)
{
	if( this->is_paused ){
		this->is_paused = 0;
	}
}

int  audio_isPlaying(audio_Type *this)
{
	return ! this->is_paused && this->cursor < this->wav->data_len;
}

void audio_pause(audio_Type *this)
{
	if( ! this->is_paused ){
		this->is_paused = 1;
	}
}

int  audio_isPaused(audio_Type *this)
{
	return ! this->is_paused;
}

void audio_loop(audio_Type *this, int enable)
{
	this->loop = enable;
}

int  audio_isLooping(audio_Type *this)
{
	return this->loop;
}

int  audio_getOriginalLengthMilliseconds(audio_Type *this)
{
	return 1000 * this->wav->data_len
		/ (this->wav->nSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

int  audio_getCurrentLengthMilliseconds(audio_Type *this)
{
	return 1000 * this->wav->data_len
		/ (this->currentSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

int  audio_getCurrentPositionMilliseconds(audio_Type *this)
{
	return 1000 * this->cursor
		/ (this->currentSamplesPerSec
			* this->wav->nChannels
			* this->wav->nBlockAlign);
}

void audio_setCurrentPositionMilliseconds(audio_Type *this, int position)
{
	int index = position
		* this->currentSamplesPerSec
		* this->wav->nChannels
		* this->wav->nBlockAlign
		/ 1000;
	if( index < 0 )
		index = 0;
	else if( index > this->wav->data_len )
		index = this->wav->data_len;
	this->position_request_at = index;
	this->position_request_pending = 1;
}

int  audio_getOriginalSamplesPerSecond(audio_Type *this)
{
	return this->wav->nSamplesPerSec;
}

int  audio_getCurrentSamplesPerSecond(audio_Type *this)
{
	return this->currentSamplesPerSec;
}

void audio_setCurrentSamplesPerSecond(audio_Type *this, int rate)
{
	if( rate < 100 )
		rate = 100;
	else if( rate > 15000 )
		rate = 15000;
	if( rate == this->currentSamplesPerSec )
		return;
	this->currentSamplesPerSec = rate;
	this->samplesPerSecRequestPending = 1;
}

#endif
