#include "wrap_mpega.h"
#include "compiler.h"

#include <proto/dos.h>
#include <stdlib.h>
#include <string.h>

#include "audio.h"
#include "stream.h"
#include "frame.h"
#include "synth.h"
#include "resample.h"
#include "equalizer.h"
#include "timer.h"
#include "xing.h"

#ifdef BUILD_WARPUP
#include <powerpc/powerpc.h>
#include <proto/powerpc.h>
#elif defined(BUILD_POWERUP)
#include <powerup/gcclib/powerup_protos.h>

#undef Open
#define Open PPCOpen
#undef Close
#define Close PPCClose
#undef Read
#define Read PPCRead
#undef Seek
#define Seek PPCSeek
#endif

#define DEFAULT_BUFFER_SIZE 8192

typedef struct
{
	BPTR fh;
	UBYTE *buffer;
	ULONG streamsize;
	ULONG streamskip;

	MPEGA_CTRL *ctrl;
	MPEGA_ACCESS *access;

	struct mad_stream stream;
	struct mad_frame frame;
	struct mad_synth synth;

	struct xing xing;
	mad_timer_t timer;

	struct audio_dither left_dither;
	struct audio_dither right_dither;

	mad_fixed_t attenuate;
	struct equalizer equalizer;

	struct resample_state resample[2];
	mad_fixed_t resampled[2][1152];
} DecHandle;


#ifdef __PPC__
#ifdef BUILD_POWERUP
#include <powerup/ppclib/interface.h>
#define CALLHOOK(hook, object, message) \
({ ULONG ret; \
	struct Caos MyCaos; \
	MyCaos.M68kCacheMode = IF_CACHEFLUSHALL; \
	MyCaos.PPCCacheMode = IF_CACHEFLUSHALL; \
	MyCaos.a0 = (ULONG) hook; \
	MyCaos.a2 = (ULONG) object; \
	MyCaos.a1 = (ULONG) message; \
	MyCaos.caos_Un.Function = hook->h_Entry; \
	ret = PPCCallM68k(&MyCaos); \
	ret; \
})
#elif defined(BUILD_WARPUP)
#define CALLHOOK(hook, object, message) \
({ ULONG ret; \
	struct PPCArgs args; \
	args.PP_Regs[PPREG_A0] = (ULONG)hook; \
	args.PP_Regs[PPREG_A2] = (ULONG)object; \
	args.PP_Regs[PPREG_A1] = (ULONG)message; \
	args.PP_Code = (APTR)hook->h_Entry; \
	args.PP_Offset = 0; \
	args.PP_Flags = 0; \
	args.PP_Stack = NULL; \
	args.PP_StackSize = 0; \
	Run68K(&args); \
	ret = args.PP_Regs[PPREG_D0]; \
	ret; \
})
#else
#include <emul/emulregs.h>
#include <emul/emulinterface.h>
#define CALLHOOK(hook, object, message) \
({ ULONG ret; \
	REG_A0 = (ULONG) hook; \
	REG_A2 = (ULONG) object; \
	REG_A1 = (ULONG) message; \
	ret = (*MyEmulHandle->EmulCallDirect68k)(hook->h_Entry); \
	ret; \
})
#endif
#else
#if 0
static ULONG ASM MyCallHook(REG(a0, struct Hook *hook), REG(a2, APTR object), REG(a1, APTR message))
{
	return hook->h_Entry();
}
#define CALLHOOK MyCallHook
#else
#define CALLHOOK(hook, object, message) \
({ register ULONG ret __asm("d0"); \
	register struct Hook *_hook __asm("a0") = hook; \
	register APTR _object __asm("a2") = object; \
	register APTR _message __asm("a1") = message; \
	__asm volatile ("movel a0@(8:W),a3\n\t" "jsr a3@" : "=r" (ret) : "r" (_hook), "r" (_object), "r" (_message) : "d0", "a0", "a1", "a2", "a3", "cc", "memory"); \
	ret; \
})
#endif
#endif


static __inline void SetStreamValues(MPEGA_STREAM *mpega_stream, struct mad_header *header)
{
	switch (header->mode)
	{
		case MAD_MODE_SINGLE_CHANNEL:
			mpega_stream->mode = MPEGA_MODE_MONO;
			break;

		case MAD_MODE_DUAL_CHANNEL:
			mpega_stream->mode = MPEGA_MODE_DUAL;
			break;

		case MAD_MODE_JOINT_STEREO:
			mpega_stream->mode = MPEGA_MODE_J_STEREO;
			break;

		case MAD_MODE_STEREO:
			mpega_stream->mode = MPEGA_MODE_STEREO;
			break;

		default:
			mpega_stream->mode = -1;
			break;
	}

	mpega_stream->norm = ((header->flags & MAD_FLAG_LSF_EXT) ? 2 : 1);
	mpega_stream->layer = header->layer;
	mpega_stream->bitrate = header->bitrate / 1000;
	mpega_stream->frequency = header->samplerate;
	mpega_stream->channels = ((header->mode == MAD_MODE_SINGLE_CHANNEL) ? 1 : 2);

	mpega_stream->private_bit = ((header->private_bits & MAD_PRIVATE_HEADER) ? 1 : 0);
	mpega_stream->copyright = ((header->flags & MAD_FLAG_COPYRIGHT) ? 1 : 0);
	mpega_stream->original = ((header->flags & MAD_FLAG_ORIGINAL) ? 1 : 0);
}

static __inline void ClearStreamValues(MPEGA_STREAM *mpega_stream)
{
	/* Dummy values */
	mpega_stream->mode = MPEGA_MODE_J_STEREO;
	mpega_stream->norm = 1;
	mpega_stream->layer = 3;
	mpega_stream->bitrate = 128;
	mpega_stream->frequency = 44100;
	mpega_stream->channels = 2;

	mpega_stream->private_bit = 0;
	mpega_stream->copyright = 0;
	mpega_stream->original = 0;

	mpega_stream->ms_duration = 1000;

	mpega_stream->dec_channels = 2;
	mpega_stream->dec_quality = 2;
	mpega_stream->dec_frequency = 44100;
}

static __inline void SetStreamOptions(MPEGA_STREAM *mpega_stream, MPEGA_CTRL *ctrl, DecHandle *dechandle)
{
	MPEGA_LAYER *layer = ((mpega_stream->layer == 3) ? (&ctrl->layer_3) : (&ctrl->layer_1_2));
	MPEGA_OUTPUT *output;
	LONG freq;

	if (layer->force_mono || mpega_stream->channels == 1)
	{
		output = &layer->mono;
		mpega_stream->dec_channels = 1;
	} else {
		output = &layer->stereo;
		mpega_stream->dec_channels = 2;
	}

	if (output->quality == 0)
	{
		switch (output->freq_div)
		{
			case 4:
				if ((freq = mpega_stream->frequency / 4) > 8000)
				{
					mad_stream_options(&dechandle->stream, MAD_OPTION_QUARTSAMPLERATE);
					mpega_stream->dec_frequency = freq;
					break;
				}
				/* Fall through */

			case 2:
				if ((freq = mpega_stream->frequency / 2) > 8000)
				{
					mad_stream_options(&dechandle->stream, MAD_OPTION_HALFSAMPLERATE);
					mpega_stream->dec_frequency = freq;
					break;
				}
				/* Fall through */

			case 0:
				if ((freq = mpega_stream->frequency) > output->freq_max)
				{
					LONG freqdiv = freq / output->freq_max;

					switch (freqdiv)
					{
						case 6:
						case 5:
						case 4:
							freq /= 4;
							mad_stream_options(&dechandle->stream, MAD_OPTION_QUARTSAMPLERATE);

							resample_init(&dechandle->resample[0], freq, output->freq_max);
							if (mpega_stream->dec_channels == 2)
								resample_init(&dechandle->resample[1], freq, output->freq_max);

							mpega_stream->dec_frequency = output->freq_max;
							break;

						case 3:
						case 2:
							freq /= 2;
							mad_stream_options(&dechandle->stream, MAD_OPTION_HALFSAMPLERATE);

							resample_init(&dechandle->resample[0], freq, output->freq_max);
							if (mpega_stream->dec_channels == 2)
								resample_init(&dechandle->resample[1], freq, output->freq_max);

							mpega_stream->dec_frequency = output->freq_max;
							break;

						default:
							mad_stream_options(&dechandle->stream, 0);

							resample_init(&dechandle->resample[0], mpega_stream->frequency, freq);
							if (mpega_stream->dec_channels == 2)
								resample_init(&dechandle->resample[1], mpega_stream->frequency, freq);

							mpega_stream->dec_frequency = freq;
							break;
					}

					break;
				}
				/* Fall through */

			default:
				mad_stream_options(&dechandle->stream, 0);
				mpega_stream->dec_frequency = mpega_stream->frequency;
				break;
		}
	} else {
		switch (output->freq_div)
		{
			case 4:
				if ((freq = mpega_stream->frequency / 4) > 8000)
				{
					resample_init(&dechandle->resample[0], mpega_stream->frequency, freq);
					if (mpega_stream->dec_channels == 2)
						resample_init(&dechandle->resample[1], mpega_stream->frequency, freq);

					mpega_stream->dec_frequency = freq;
					break;
				}
				/* Fall through */

			case 2:
				if ((freq = mpega_stream->frequency / 2) > 8000)
				{
					resample_init(&dechandle->resample[0], mpega_stream->frequency, freq);
					if (mpega_stream->dec_channels == 2)
						resample_init(&dechandle->resample[1], mpega_stream->frequency, freq);

					mpega_stream->dec_frequency = freq;
					break;
				}
				/* Fall through */

			case 0:
				if ((freq = mpega_stream->frequency) > output->freq_max)
				{
					resample_init(&dechandle->resample[0], freq, output->freq_max);
					if (mpega_stream->dec_channels == 2)
						resample_init(&dechandle->resample[1], freq, output->freq_max);

					mpega_stream->dec_frequency = output->freq_max;
					break;
				}
				/* Fall through */

			default:
				mpega_stream->dec_frequency = mpega_stream->frequency;
				break;
		}
	}

	if ((mpega_stream->dec_quality = output->quality) < 2) dechandle->stream.options |= MAD_OPTION_IGNORECRC;
}

static __inline ULONG CheckID3(UBYTE *buffer)
{
	ULONG size = 0;

	if (buffer[0]=='I' && buffer[1]=='D' && buffer[2]=='3' && buffer[3]<0xFF && buffer[4]<0xFF && buffer[6]<0x80 && buffer[7]<0x80 && buffer[8]<0x80 && buffer[9]<0x80)
	{
		size = 10 + (((buffer[6] & 0x7F) << 21) | ((buffer[7] & 0x7F) << 14) | ((buffer[8] & 0x7F) << 7) | buffer[9] & 0x7F);

		if (buffer[5] & 0x10)
			size += 10;
	}

	return size;
}

static __inline LONG ReadFunc(DecHandle *dechandle, UBYTE *buffer, LONG len)
{
	MPEGA_CTRL *ctrl = dechandle->ctrl;
	LONG bytes;

	if (ctrl->bs_access == NULL)
	{
		bytes = Read(dechandle->fh, buffer, len);
	} else {
		MPEGA_ACCESS *access = dechandle->access;

		access->func = MPEGA_BSFUNC_READ;
		access->data.read.buffer = buffer;
		access->data.read.num_bytes = len;

		bytes = CALLHOOK(ctrl->bs_access, (APTR)dechandle->fh, access);
	}

	return bytes;
}

static LONG InitDecoder(MPEGA_STREAM *mpega_stream, MPEGA_CTRL *ctrl)
{
	DecHandle *dechandle = (DecHandle *) mpega_stream->handle;
	LONG bytes;

	mad_stream_init(&dechandle->stream);
	mad_frame_init(&dechandle->frame);
	mad_synth_init(&dechandle->synth);

	xing_init(&dechandle->xing);
	resample_init(&dechandle->resample[0], 44100, 44100);
	resample_init(&dechandle->resample[1], 44100, 44100);
	mad_timer_reset(&dechandle->timer);
	dechandle->attenuate = MAD_F_ONE;

	bytes = ReadFunc(dechandle, dechandle->buffer, ctrl->stream_buffer_size);

	if (bytes > 0)
	{
		ULONG skip;

		ClearStreamValues(mpega_stream);
		dechandle->streamskip = skip = CheckID3(dechandle->buffer);
		if (dechandle->streamsize == 0) dechandle->streamsize = ((bytes > skip) ? (bytes) : (skip));

		/* Ensure that we have something to decode */
		if (skip > (bytes - 2000))
		{
			LONG len = 0, size = bytes;

			if (skip < size)
			{
				bytes = len = size - skip;
				memmove(dechandle->buffer, dechandle->buffer + (size - len), len);
				skip = size;
			}

			while (skip >= size)
			{
				skip -= size;
				size = ReadFunc(dechandle, dechandle->buffer + len, ctrl->stream_buffer_size - len);

				if (size <= 0) return 0;
			}

			if (skip)
			{
				bytes = len = size - skip;
				memmove(dechandle->buffer, dechandle->buffer + (size - len), len);
				size = ReadFunc(dechandle, dechandle->buffer + len, ctrl->stream_buffer_size - len);
			}

			if (size > 0) bytes += size;
		}

		bzero(dechandle->buffer + bytes, MAD_BUFFER_GUARD);
		mad_stream_buffer(&dechandle->stream, dechandle->buffer, bytes);

		dechandle->stream.sync = 0;
		mad_stream_options(&dechandle->stream, MAD_OPTION_IGNORECRC);

		if (mad_frame_decode(&dechandle->frame, &dechandle->stream) == -1)
		{
			while (MAD_RECOVERABLE(dechandle->stream.error))
			{
				dechandle->stream.error = 0;
				if (mad_frame_decode(&dechandle->frame, &dechandle->stream) == 0) break;
			}

			if (dechandle->stream.error)
			{
				mad_stream_options(&dechandle->stream, 0);
				if (ctrl->check_mpeg && dechandle->streamsize > bytes) return 0;

				return 1;
			}
		}

		mad_stream_options(&dechandle->stream, 0);
		SetStreamValues(mpega_stream, &dechandle->frame.header);

		if (xing_parse(&dechandle->xing, dechandle->stream.anc_ptr, dechandle->stream.anc_bitlen) == 0)
		{
			skip = dechandle->stream.next_frame - dechandle->buffer;
			dechandle->streamskip += skip;

			if (dechandle->xing.flags & XING_FRAMES)
			{
				ULONG seconds;
				mad_timer_t total = dechandle->frame.header.duration;

				mad_timer_multiply(&total, dechandle->xing.frames);
				mpega_stream->ms_duration = mad_timer_count(total, MAD_UNITS_MILLISECONDS);
				seconds = ((mpega_stream->ms_duration > 1000) ? (mpega_stream->ms_duration / 1000) : (1));

				if (dechandle->xing.flags & XING_BYTES)
				{
					mpega_stream->bitrate = ((dechandle->xing.bytes / 125) / seconds);
				} else {
					mpega_stream->bitrate = (((dechandle->streamsize - dechandle->streamskip) / 125) / seconds);
				}
			}
		} else {
			skip = dechandle->stream.this_frame - dechandle->buffer;
			dechandle->streamskip += skip;

			if (dechandle->streamsize)
			{
#if 0
				mad_timer_t total = dechandle->frame.header.duration;

				mad_timer_multiply(&total, ((dechandle->streamsize - dechandle->streamskip) / (dechandle->stream.next_frame - dechandle->stream.this_frame)));
				mpega_stream->ms_duration = mad_timer_count(total, MAD_UNITS_MILLISECONDS);
#else
				/* This will overflow on files over 500MB, but is more accurate than the above */
				mpega_stream->ms_duration = ((dechandle->streamsize - dechandle->streamskip) * 8) / ((mpega_stream->bitrate) ? (mpega_stream->bitrate) : (8));
#endif
			}

			mad_stream_buffer(&dechandle->stream, dechandle->buffer + skip, bytes - skip);
			mad_frame_mute(&dechandle->frame);
		}

		SetStreamOptions(mpega_stream, ctrl, dechandle);

		return 1;
	}

	return 0;
}

static void FinishDecoder(DecHandle *dechandle)
{
	resample_finish(&dechandle->resample[0]);
	resample_finish(&dechandle->resample[1]);
	xing_finish(&dechandle->xing);
	mad_synth_finish(&dechandle->synth);
	mad_frame_finish(&dechandle->frame);
	mad_stream_finish(&dechandle->stream);
}

static __inline LONG ValidateFrequency(LONG freq)
{
	if (freq <= 0) return 48000;
	if (freq <= 8000) return 8000;
	if (freq <= 11025) return 11025;
	if (freq <= 12000) return 12000;
	if (freq <= 16000) return 16000;
	if (freq <= 22050) return 22050;
	if (freq <= 24000) return 24000;
	if (freq <= 32000) return 32000;
	if (freq <= 44100) return 44100;

	return 48000;
}

static __inline void ValidateCtrl(MPEGA_CTRL *ctrl)
{
	if (ctrl->stream_buffer_size < DEFAULT_BUFFER_SIZE)
		ctrl->stream_buffer_size = DEFAULT_BUFFER_SIZE;

	if (ctrl->layer_1_2.mono.freq_div < 0)
		ctrl->layer_1_2.mono.freq_div = 0;

	if (ctrl->layer_1_2.mono.freq_div > 2)
		ctrl->layer_1_2.mono.freq_div = 4;

	if (ctrl->layer_1_2.mono.quality < 0)
		ctrl->layer_1_2.mono.quality = 0;

	if (ctrl->layer_1_2.mono.quality > 2)
		ctrl->layer_1_2.mono.quality = 2;

	if (ctrl->layer_1_2.stereo.freq_div < 0)
		ctrl->layer_1_2.stereo.freq_div = 0;

	if (ctrl->layer_1_2.stereo.freq_div > 2)
		ctrl->layer_1_2.stereo.freq_div = 4;

	if (ctrl->layer_1_2.stereo.quality < 0)
		ctrl->layer_1_2.stereo.quality = 0;

	if (ctrl->layer_1_2.stereo.quality > 2)
		ctrl->layer_1_2.stereo.quality = 2;

	ctrl->layer_1_2.mono.freq_max = ValidateFrequency(ctrl->layer_1_2.mono.freq_max);
	ctrl->layer_1_2.stereo.freq_max = ValidateFrequency(ctrl->layer_1_2.stereo.freq_max);

	if (ctrl->layer_3.mono.freq_div < 0)
		ctrl->layer_3.mono.freq_div = 0;

	if (ctrl->layer_3.mono.freq_div > 2)
		ctrl->layer_3.mono.freq_div = 4;

	if (ctrl->layer_3.mono.quality < 0)
		ctrl->layer_3.mono.quality = 0;

	if (ctrl->layer_3.mono.quality > 2)
		ctrl->layer_3.mono.quality = 2;

	if (ctrl->layer_3.stereo.freq_div < 0)
		ctrl->layer_3.stereo.freq_div = 0;

	if (ctrl->layer_3.stereo.freq_div > 2)
		ctrl->layer_3.stereo.freq_div = 4;

	if (ctrl->layer_3.stereo.quality < 0)
		ctrl->layer_3.stereo.quality = 0;

	if (ctrl->layer_3.stereo.quality > 2)
		ctrl->layer_3.stereo.quality = 2;

	ctrl->layer_3.mono.freq_max = ValidateFrequency(ctrl->layer_3.mono.freq_max);
	ctrl->layer_3.stereo.freq_max = ValidateFrequency(ctrl->layer_3.stereo.freq_max);
}

static __inline LONG OutputPCM(MPEGA_STREAM *mpega_stream, DecHandle *dechandle, WORD *pcm[MPEGA_MAX_CHANNELS])
{
	WORD *outleft = pcm[0];
	WORD *outright = pcm[1];
	mad_fixed_t const *left = dechandle->synth.pcm.samples[0];
	mad_fixed_t const *right = dechandle->synth.pcm.samples[1];
	ULONG len = dechandle->synth.pcm.length;
	LONG nsamples, sample0, sample1;

	nsamples = len;

	if (mpega_stream->dec_channels == 1 || !outright)
	{
		if (dechandle->resample[0].ratio != MAD_F_ONE)
		{
			nsamples = resample_block(&dechandle->resample[0], len, left, dechandle->resampled[0]);
			left = dechandle->resampled[0];
		}

		if (mpega_stream->dec_quality < 2)
		{
			while (len--)
			{
				sample0 = audio_linear_round(16, *left++);

				*outleft++ = sample0 & 0x0000FFFF;
			}
		} else {
			while (len--)
			{
				sample0 = audio_linear_dither(16, *left++, &dechandle->left_dither);

				*outleft++ = sample0 & 0x0000FFFF;
			}
		}
	} else {	/* Stereo */
		if (dechandle->resample[0].ratio != MAD_F_ONE)
		{
			nsamples = resample_block(&dechandle->resample[0], len, left, dechandle->resampled[0]);
			resample_block(&dechandle->resample[1], len, right, dechandle->resampled[1]);
			left = dechandle->resampled[0];
			right = dechandle->resampled[1];
		}

		if (mpega_stream->dec_quality < 2)
		{
			while (len--)
			{
				sample0 = audio_linear_round(16, *left++);
				sample1 = audio_linear_round(16, *right++);

				*outleft++ = sample0 & 0x0000FFFF;
				*outright++ = sample1 & 0x0000FFFF;
			}
		} else {
			while (len--)
			{
				sample0 = audio_linear_dither(16, *left++, &dechandle->left_dither);
				sample1 = audio_linear_dither(16, *right++, &dechandle->right_dither);

				*outleft++ = sample0 & 0x0000FFFF;
				*outright++ = sample1 & 0x0000FFFF;
			}
		}
	}

	return nsamples;
}


MPEGA_STREAM * LIBPPC WRAP_MPEGA_open(char *stream_name, MPEGA_CTRL *ctrl)
{
	if (ctrl)
	{
		MPEGA_STREAM *mpega_stream;

		if ((mpega_stream = malloc(sizeof(MPEGA_STREAM))))
		{
			DecHandle *dechandle;

			if ((dechandle = calloc(1, sizeof(DecHandle))))
			{
				mpega_stream->handle = dechandle;

				dechandle->fh = NULL;
				dechandle->access = NULL;

				ValidateCtrl(ctrl);

				if ((dechandle->ctrl = malloc(sizeof(MPEGA_CTRL))))
				{
					bcopy(ctrl, dechandle->ctrl, sizeof(MPEGA_CTRL));

					if ((dechandle->buffer = malloc(ctrl->stream_buffer_size + MAD_BUFFER_GUARD)))
					{
						if (ctrl->bs_access == NULL)
						{
							if (stream_name)
							{
								if ((dechandle->fh = Open(stream_name, MODE_OLDFILE)))
								{
									struct FileInfoBlock *fib;

									dechandle->streamsize = 0;

									if ((fib = malloc(sizeof(struct FileInfoBlock))))
									{
										if (ExamineFH(dechandle->fh, fib))
										{
											dechandle->streamsize = fib->fib_Size;
										}

										free(fib);
									}

									if (InitDecoder(mpega_stream, ctrl) != 0)
									{
										return mpega_stream;
									}

									Close(dechandle->fh);
								}
							}
						} else {
							MPEGA_ACCESS *access;

							if ((access = malloc(sizeof(MPEGA_ACCESS))))
							{
								dechandle->access = access;

								access->func = MPEGA_BSFUNC_OPEN;
								access->data.open.stream_name = stream_name;
								access->data.open.buffer_size = ctrl->stream_buffer_size;
								access->data.open.stream_size = 0;

								if ((dechandle->fh = CALLHOOK(ctrl->bs_access, NULL, access)))
								{
									dechandle->streamsize = access->data.open.stream_size;

									if (InitDecoder(mpega_stream, ctrl) != 0)
									{
										return mpega_stream;
									}

									access->func = MPEGA_BSFUNC_CLOSE;
									CALLHOOK(ctrl->bs_access, (APTR)dechandle->fh, access);
								}

								free(access);
							}
						}

						free(dechandle->buffer);
					}

					free(dechandle->ctrl);
				}

				free(dechandle);
			}

			free(mpega_stream);
		}
	}

	return NULL;
}

void LIBPPC WRAP_MPEGA_close(MPEGA_STREAM *mpega_stream)
{
	if (mpega_stream)
	{
		DecHandle *dechandle = (DecHandle *) mpega_stream->handle;

		if (dechandle)
		{
			MPEGA_CTRL *ctrl = dechandle->ctrl;

			FinishDecoder(dechandle);

			if (ctrl->bs_access == NULL)
			{
				if (dechandle->fh) Close(dechandle->fh);
			} else {
				MPEGA_ACCESS *access = dechandle->access;

				access->func = MPEGA_BSFUNC_CLOSE;

				CALLHOOK(ctrl->bs_access, (APTR)dechandle->fh, access);
			}

			if (dechandle->ctrl) free(dechandle->ctrl);
			if (dechandle->buffer) free(dechandle->buffer);
			if (dechandle->access) free(dechandle->access);

			free(dechandle);
		}

		free(mpega_stream);
	}
}

LONG LIBPPC WRAP_MPEGA_decode_frame(MPEGA_STREAM *mpega_stream, WORD *pcm[MPEGA_MAX_CHANNELS])
{
	if (mpega_stream && pcm && pcm[0])
	{
		DecHandle *dechandle = (DecHandle *) mpega_stream->handle;
		MPEGA_CTRL *ctrl = dechandle->ctrl;

		if (mad_frame_decode(&dechandle->frame, &dechandle->stream) == -1)
		{
			LONG error = dechandle->stream.error;

			if (error == MAD_ERROR_BUFLEN)
			{
				LONG bytes = ctrl->stream_buffer_size, len = 0;

				if (dechandle->stream.next_frame)
				{
					memmove(dechandle->buffer, dechandle->stream.next_frame, len = &dechandle->buffer[bytes] - dechandle->stream.next_frame);
				}

				bytes = ReadFunc(dechandle, dechandle->buffer + len, bytes - len);

				if (bytes == 0) return MPEGA_ERR_EOF;
				if (bytes < 0) return MPEGA_ERR_BADVALUE;

				bzero(dechandle->buffer + bytes + len, MAD_BUFFER_GUARD);
				mad_stream_buffer(&dechandle->stream, dechandle->buffer, bytes + len);

				if (mad_frame_decode(&dechandle->frame, &dechandle->stream) == -1) error = dechandle->stream.error;
				else error = 0;
			}

			if (error)
			{
				switch (error)
				{
					case MAD_ERROR_BUFPTR:
						return MPEGA_ERR_BADVALUE;
						break;

					case MAD_ERROR_NOMEM:
						return MPEGA_ERR_MEM;
						break;

					case MAD_ERROR_LOSTSYNC:
						return MPEGA_ERR_NO_SYNC;
						break;

					case MAD_ERROR_BADLAYER:
					case MAD_ERROR_BADBITRATE:
					case MAD_ERROR_BADSAMPLERATE:
					case MAD_ERROR_BADEMPHASIS:
					case MAD_ERROR_BADCRC:
					case MAD_ERROR_BADBITALLOC:
					case MAD_ERROR_BADSCALEFACTOR:
					case MAD_ERROR_BADFRAMELEN:
					case MAD_ERROR_BADBIGVALUES:
					case MAD_ERROR_BADBLOCKTYPE:
					case MAD_ERROR_BADSCFSI:
					case MAD_ERROR_BADDATAPTR:
					case MAD_ERROR_BADPART3LEN:
					case MAD_ERROR_BADHUFFTABLE:
					case MAD_ERROR_BADHUFFDATA:
					case MAD_ERROR_BADSTEREO:
						return MPEGA_ERR_BADFRAME;
						break;

					default:
						return -error;
						break;
				}
			}
		}

		mad_timer_add(&dechandle->timer, dechandle->frame.header.duration);

		SetStreamValues(mpega_stream, &dechandle->frame.header);
		SetStreamOptions(mpega_stream, ctrl, dechandle);

		if (dechandle->attenuate != MAD_F_ONE)
			equalizer_filter(&dechandle->equalizer, &dechandle->frame);

		mad_synth_frame(&dechandle->synth, &dechandle->frame);

		return OutputPCM(mpega_stream, dechandle, pcm);
	}

	return MPEGA_ERR_BADVALUE;
}

LONG LIBPPC WRAP_MPEGA_seek(MPEGA_STREAM *mpega_stream, ULONG ms_time_position)
{
	if (mpega_stream)
	{
		DecHandle *dechandle = (DecHandle *) mpega_stream->handle;
		MPEGA_CTRL *ctrl = dechandle->ctrl;
		LONG pos, error;

		if (dechandle->xing.flags & XING_TOC)
		{
			int step, i = 99;

			if (dechandle->xing.flags & XING_BYTES)
			{
				step = dechandle->xing.bytes / 256;
			} else {
				step = (dechandle->streamsize - dechandle->streamskip) / 256;
			}

			if (ms_time_position < mpega_stream->ms_duration)
			{
				i = ((mpega_stream->ms_duration > 100) ? (ms_time_position / (mpega_stream->ms_duration / 100)) : (0));
				if (i > 99) i = 99;
			}

			pos = (step * dechandle->xing.toc[i]) + dechandle->streamskip;
		} else {
			pos = ((((ULONG)mpega_stream->bitrate * 125UL) * (ms_time_position / 1000)) + dechandle->streamskip);
		}

		if (ctrl->bs_access == NULL)
		{
			error = Seek(dechandle->fh, pos, OFFSET_BEGINNING);

			if (error > 0) error = 0;
		} else {
			MPEGA_ACCESS *access = dechandle->access;

			access->func = MPEGA_BSFUNC_SEEK;
			access->data.seek.abs_byte_seek_pos = pos;

			error = CALLHOOK(ctrl->bs_access, (APTR)dechandle->fh, access);
		}

		if (error == 0)
		{
			LONG bytes;

			bytes = ReadFunc(dechandle, dechandle->buffer, ctrl->stream_buffer_size);

			if (bytes == 0) return MPEGA_ERR_EOF;
			if (bytes < 0) return MPEGA_ERR_BADVALUE;

			bzero(dechandle->buffer + bytes, MAD_BUFFER_GUARD);
			mad_stream_buffer(&dechandle->stream, dechandle->buffer, bytes);

			mad_frame_mute(&dechandle->frame);
			mad_synth_mute(&dechandle->synth);
			dechandle->stream.sync = 0;

			/* If we seek somewhere other than the beginning we need to flush out junk audio */
			if (pos > dechandle->streamskip)
			{
				mad_frame_decode(&dechandle->frame, &dechandle->stream);
				mad_frame_decode(&dechandle->frame, &dechandle->stream);
				mad_synth_frame(&dechandle->synth, &dechandle->frame);
			}

//			mad_timer_set(&dechandle->timer, 0, pos - dechandle->streamskip, (ULONG)mpega_stream->bitrate * 125);
			mad_timer_set(&dechandle->timer, 0, ms_time_position, 1000);

			return 0;
		}
	}

	return MPEGA_ERR_BADVALUE;
}

LONG LIBPPC WRAP_MPEGA_time(MPEGA_STREAM *mpega_stream, ULONG *ms_time_position)
{
	if (mpega_stream && ms_time_position)
	{
		DecHandle *dechandle = (DecHandle *) mpega_stream->handle;

		*ms_time_position = mad_timer_count(dechandle->timer, MAD_UNITS_MILLISECONDS);
		return 0;
	}

	return MPEGA_ERR_BADVALUE;
}

LONG LIBPPC WRAP_MPEGA_find_sync(UBYTE *buffer, LONG buffer_size)
{
	if (buffer && buffer_size > 75)
	{
		LONG i = 0;

		while (i < buffer_size - 1)
		{
			if ((buffer[i] == 0xFF) && ((buffer[i+1] & 0xE0) == 0xE0)) return i;
			++i;
		}

		return MPEGA_ERR_NO_SYNC;
	}

	return MPEGA_ERR_BADVALUE;
}

LONG LIBPPC WRAP_MPEGA_scale(MPEGA_STREAM *mpega_stream, LONG scale_percent)
{
	if (mpega_stream)
	{
		DecHandle *dechandle = (DecHandle *) mpega_stream->handle;
		mad_fixed_t attenuate;

		if (scale_percent >= 800) attenuate = MAD_F_MAX;
		else if (scale_percent <= 0) attenuate = ((mad_fixed_t)0x00000001L);
		else attenuate = (MAD_F_ONE / 100) * scale_percent;

		equalizer_init(&dechandle->equalizer, attenuate);
		dechandle->attenuate = attenuate;

		return 0;
	}

	return MPEGA_ERR_BADVALUE;
}

#ifdef BUILD_POWERUP
APTR __LibFuncTable[] =
{
	WRAP_MPEGA_open,
	WRAP_MPEGA_close,
	WRAP_MPEGA_decode_frame,
	WRAP_MPEGA_seek,
	WRAP_MPEGA_time,
	WRAP_MPEGA_find_sync,
	WRAP_MPEGA_scale,
	NULL
};
#endif
