/*
	volume: libsyn123 audio volume handling

	copyright 2018 by the mpg123 project
	licensed under the terms of the LGPL 2.1
	see COPYING and AUTHORS files in distribution or http://mpg123.org

	initially written by Thomas Orgis
*/

#define NO_GROW_BUF
#define NO_SMAX
#include "syn123_int.h"
#include "debug.h"
// Avoid conflict with pragmas in isnan() and friends.
#undef warning

static const double db_min = -SYN123_DB_LIMIT;
static const double db_max =  SYN123_DB_LIMIT;

double attribute_align_arg syn123_db2lin(double db)
{
	if(isnan(db) || isless(db, db_min))
		db = db_min;
	else if(isgreater(db, db_max))
		db = db_max;
	return pow(10, db/20);
}

double attribute_align_arg syn123_lin2db(double volume)
{
	double db;
	if(isnan(volume) || islessequal(volume, 0.))
		db = db_min;
	else
	{
		db = 20*log10(volume);
		if(isgreater(db, db_min))
		{
			if(isgreater(db, db_max))
				db = db_max;
		}
		else
			db = db_min;
	}
	return db;
}


int attribute_align_arg
syn123_amp( void* buf, int encoding, size_t samples
,	double volume, double offset, size_t *clipped, syn123_handle *sh )
{
	size_t clips = 0;
	int err = 0;
	if(!buf)
	{
		err = SYN123_BAD_BUF;
		goto amp_end;
	}
	switch(encoding)
	{
		// This is close to FMA, but only that. It's FAM.
		#define AMP_LOOP(type) \
			for(size_t i=0; i<samples; ++i) \
				((type*)buf)[i] = (type)volume * (((type*)buf)[i] + (type)offset);
		case MPG123_ENC_FLOAT_32:
			AMP_LOOP(float)
			goto amp_end;
		case MPG123_ENC_FLOAT_64:
			AMP_LOOP(double)
			goto amp_end;
		#undef AMP_LOOP
	}
	if(!sh)
	{
		err = SYN123_BAD_ENC;
		goto amp_end;
	}
	else
	{
		char *cbuf = buf;
		int mixenc = syn123_mixenc(encoding, encoding);
		int mixframe = MPG123_SAMPLESIZE(mixenc);
		int inframe = MPG123_SAMPLESIZE(encoding);
		if(!mixenc || !mixframe || !inframe)
		{
			err = SYN123_BAD_CONV;
			goto amp_end;
		}
		// Use the whole workbuf, both halves.
		int mbufblock = 2*bufblock*sizeof(double)/mixframe;
		mdebug("mbufblock=%i (enc %i)", mbufblock, mixenc);
		while(samples)
		{
			int block = (int)smin(samples, mbufblock);
			int err = syn123_conv(
				sh->workbuf, mixenc, sizeof(sh->workbuf)
			,	cbuf, encoding, inframe*block
			,	NULL, NULL, NULL );
			if(!err)
			{
				err = syn123_amp( sh->workbuf, mixenc, block
				,	volume, offset, NULL, NULL );
				if(err)
					return err;
				size_t clips_block = 0;
				err = syn123_conv(
					cbuf, encoding, inframe*block
				,	sh->workbuf, mixenc, mixframe*block
				,	NULL, &clips_block, NULL );
				clips += clips_block;
			}
			if(err)
			{
				mdebug("conv error: %i", err);
				err = SYN123_BAD_CONV; // SYN123_WEIRD also an option
				goto amp_end;
			}
			cbuf += block*inframe;
			samples -= block;
		}
	}
amp_end:
	if(clipped)
		*clipped = clips;
	return err;
}
