/* Copyright (C) 2003 Marcus Overhagen
 * Released under terms of the MIT license.
 *
 * A simple resampling class for the audio mixer.
 * You pick the conversation function on object creation,
 * and then call the Resample() function, specifying data pointer,
 * offset (in bytes) to the next sample, and count of samples for
 * both source and destination.
 *
 */

#include <MediaDefs.h>
#include "Resampler.h"
#include "debug.h"

Resampler::Resampler(uint32 src_format, uint32 dst_format)
 :	fFunc(0)
{
	if (dst_format == media_raw_audio_format::B_AUDIO_FLOAT) {
		switch (src_format) {
			case media_raw_audio_format::B_AUDIO_FLOAT:
				fFunc = &Resampler::float_to_float;
				return;
			case media_raw_audio_format::B_AUDIO_INT:
				fFunc = &Resampler::int32_to_float;
				return;
			case media_raw_audio_format::B_AUDIO_SHORT:
				fFunc = &Resampler::int16_to_float;
				return;
			case media_raw_audio_format::B_AUDIO_CHAR:
				fFunc = &Resampler::int8_to_float;
				return;
			case media_raw_audio_format::B_AUDIO_UCHAR:
				fFunc = &Resampler::uint8_to_float;
				return;
			default:
				ERROR("Resampler::Resampler: unknown source format 0x%x\n", src_format);
				return;
		}
	}

	if (src_format == media_raw_audio_format::B_AUDIO_FLOAT) {
		switch (dst_format) {
			// float=>float already handled above
			case media_raw_audio_format::B_AUDIO_INT:
				fFunc = &Resampler::float_to_int32;
				return;
			case media_raw_audio_format::B_AUDIO_SHORT:
				fFunc = &Resampler::float_to_int16;
				return;
			case media_raw_audio_format::B_AUDIO_CHAR:
				fFunc = &Resampler::float_to_int8;
				return;
			case media_raw_audio_format::B_AUDIO_UCHAR:
				fFunc = &Resampler::float_to_uint8;
				return;
			default:
				ERROR("Resampler::Resampler: unknown destination format 0x%x\n", dst_format);
				return;
		}
	}

	ERROR("Resampler::Resampler: source or destination format must be B_AUDIO_FLOAT\n");
}

Resampler::~Resampler()
{
}

status_t
Resampler::InitCheck()
{
	return (fFunc != 0) ? B_OK : B_ERROR;
}

void
Resampler::float_to_float(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			*(float *)dst = *(const float *)src * gain;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			*(float *)dst = *(const float *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			*(float *)dst = *(const float *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::int32_to_float(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain / 2147483647.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			*(float *)dst = *(const int32 *)src * gain;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			*(float *)dst = *(const int32 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			*(float *)dst = *(const int32 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::int16_to_float(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain / 32767.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			*(float *)dst = *(const int16 *)src * gain;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			*(float *)dst = *(const int16 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			*(float *)dst = *(const int16 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::int8_to_float(const void *_src, int32 src_sample_offset, int32 src_sample_count,
				  		 void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain / 127.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			*(float *)dst = *(const int8 *)src * gain;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			*(float *)dst = *(const int8 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			*(float *)dst = *(const int8 *)src * gain;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::uint8_to_float(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain / 127.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			*(float *)dst = (((int32) *(const uint8 *)src) - 128) * gain;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			*(float *)dst = (((int32) *(const uint8 *)src) - 128) * gain;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			*(float *)dst = (((int32) *(const uint8 *)src) - 128) * gain;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::float_to_int32(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain * 2147483647.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 2147483647.0f)
				*(int32 *)dst = 2147483647L;
			else if (sample < -2147483647.0f)
				*(int32 *)dst = -2147483647L;
			else
				*(int32 *)dst = (int32)sample;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 2147483647.0f)
				*(int32 *)dst = 2147483647L;
			else if (sample < -2147483647.0f)
				*(int32 *)dst = -2147483647L;
			else
				*(int32 *)dst = (int32)sample;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 2147483647.0f)
				*(int32 *)dst = 2147483647L;
			else if (sample < -2147483647.0f)
				*(int32 *)dst = -2147483647L;
			else
				*(int32 *)dst = (int32)sample;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::float_to_int16(const void *_src, int32 src_sample_offset, int32 src_sample_count,
				  		  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain * 32767.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 32767.0f)
				*(int16 *)dst = 32767;
			else if (sample < -32767.0f)
				*(int16 *)dst = -32767;
			else
				*(int16 *)dst = (int16)sample;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 32767.0f)
				*(int16 *)dst = 32767;
			else if (sample < -32767.0f)
				*(int16 *)dst = -32767;
			else
				*(int16 *)dst = (int16)sample;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 32767.0f)
				*(int16 *)dst = 32767;
			else if (sample < -32767.0f)
				*(int16 *)dst = -32767;
			else
				*(int16 *)dst = (int16)sample;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::float_to_int8(const void *_src, int32 src_sample_offset, int32 src_sample_count,
			 			 void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain * 127.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 127.0f)
				*(int8 *)dst = 127;
			else if (sample < -127.0f)
				*(int8 *)dst = -127;
			else
				*(int8 *)dst = (int8)sample;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 127.0f)
				*(int8 *)dst = 127;
			else if (sample < -127.0f)
				*(int8 *)dst = -127;
			else
				*(int8 *)dst = (int8)sample;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			register float sample = *(const float *)src * gain;
			if (sample > 127.0f)
				*(int8 *)dst = 127;
			else if (sample < -127.0f)
				*(int8 *)dst = -127;
			else
				*(int8 *)dst = (int8)sample;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}

void
Resampler::float_to_uint8(const void *_src, int32 src_sample_offset, int32 src_sample_count,
						  void *_dst, int32 dst_sample_offset, int32 dst_sample_count, float _gain)
{
	register const char * src = (const char *) _src;
	register char * dst = (char *) _dst;
	register int32 count = dst_sample_count;
	register float gain = _gain * 127.0;
	
	if (src_sample_count == dst_sample_count) {
		// optimized case for no resampling
		while (count--) {
			register float sample = 128.0f + *(const float *)src * gain;
			if (sample > 255.0f)
				*(uint8 *)dst = 255;
			else if (sample < 1.0f)
				*(uint8 *)dst = 1;
			else
				*(uint8 *)dst = (uint8)sample;
			src += src_sample_offset;
			dst += dst_sample_offset;
		}
		return;
	}

	register float delta = float(src_sample_count) / float(dst_sample_count);
	register float current = 0.0f;

	if (delta < 1.0) {
		// upsample
		while (count--) {
			register float sample = 128.0f + *(const float *)src * gain;
			if (sample > 255.0f)
				*(uint8 *)dst = 255;
			else if (sample < 1.0f)
				*(uint8 *)dst = 1;
			else
				*(uint8 *)dst = (uint8)sample;
			dst += dst_sample_offset;
			current += delta;
			if (current >= 1.0f) {
				current -= 1.0f;
				src += src_sample_offset;
			}
		}
	} else {
		// downsample
		while (count--) {
			register float sample = 128.0f + *(const float *)src * gain;
			if (sample > 255.0f)
				*(uint8 *)dst = 255;
			else if (sample < 1.0f)
				*(uint8 *)dst = 1;
			else
				*(uint8 *)dst = (uint8)sample;
			dst += dst_sample_offset;
			current += delta;
			register int32 skipcount = (int32)current;
			current -= skipcount;
			src += skipcount * src_sample_offset;
		}
	}
}
