/*
** Copyright (C) 1999-2017 Erik de Castro Lopo <erikd@mega-nerd.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation; either version 2.1 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include	"sfconfig.h"

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<limits.h>
#include	<math.h>

#include	"sndfile.h"
#include	"sfendian.h"
#include	"common.h"

#if CPU_IS_LITTLE_ENDIAN
	#define FLOAT32_READ	float32_le_read
	#define FLOAT32_WRITE	float32_le_write
#elif CPU_IS_BIG_ENDIAN
	#define FLOAT32_READ	float32_be_read
	#define FLOAT32_WRITE	float32_be_write
#endif

/*--------------------------------------------------------------------------------------------
**	Processor floating point capabilities. float32_get_capability () returns one of the
**	latter four values.
*/

enum
{	FLOAT_UNKNOWN		= 0x00,
	FLOAT_CAN_RW_LE		= 0x12,
	FLOAT_CAN_RW_BE		= 0x23,
	FLOAT_BROKEN_LE		= 0x34,
	FLOAT_BROKEN_BE		= 0x45
} ;

/*--------------------------------------------------------------------------------------------
**	Prototypes for private functions.
*/

static sf_count_t	host_read_f2s	(SF_PRIVATE *psf, short *ptr, sf_count_t len) ;
static sf_count_t	host_read_f2i	(SF_PRIVATE *psf, int *ptr, sf_count_t len) ;
static sf_count_t	host_read_f	(SF_PRIVATE *psf, float *ptr, sf_count_t len) ;
static sf_count_t	host_read_f2d	(SF_PRIVATE *psf, double *ptr, sf_count_t len) ;

static sf_count_t	host_write_s2f	(SF_PRIVATE *psf, const short *ptr, sf_count_t len) ;
static sf_count_t	host_write_i2f	(SF_PRIVATE *psf, const int *ptr, sf_count_t len) ;
static sf_count_t	host_write_f	(SF_PRIVATE *psf, const float *ptr, sf_count_t len) ;
static sf_count_t	host_write_d2f	(SF_PRIVATE *psf, const double *ptr, sf_count_t len) ;

static void		float32_peak_update	(SF_PRIVATE *psf, const float *buffer, int count, sf_count_t indx) ;

static sf_count_t	replace_read_f2s	(SF_PRIVATE *psf, short *ptr, sf_count_t len) ;
static sf_count_t	replace_read_f2i	(SF_PRIVATE *psf, int *ptr, sf_count_t len) ;
static sf_count_t	replace_read_f	(SF_PRIVATE *psf, float *ptr, sf_count_t len) ;
static sf_count_t	replace_read_f2d	(SF_PRIVATE *psf, double *ptr, sf_count_t len) ;

static sf_count_t	replace_write_s2f	(SF_PRIVATE *psf, const short *ptr, sf_count_t len) ;
static sf_count_t	replace_write_i2f	(SF_PRIVATE *psf, const int *ptr, sf_count_t len) ;
static sf_count_t	replace_write_f	(SF_PRIVATE *psf, const float *ptr, sf_count_t len) ;
static sf_count_t	replace_write_d2f	(SF_PRIVATE *psf, const double *ptr, sf_count_t len) ;

static	void	bf2f_array (float *buffer, int count) ;
static	void	f2bf_array (float *buffer, int count) ;

static int		float32_get_capability	(SF_PRIVATE *psf) ;

/*--------------------------------------------------------------------------------------------
**	Exported functions.
*/

int
float32_init	(SF_PRIVATE *psf)
{	static int float_caps ;

	if (psf->sf.channels < 1)
	{	psf_log_printf (psf, "float32_init : internal error : channels = %d\n", psf->sf.channels) ;
		return SFE_INTERNAL ;
		} ;

	float_caps = float32_get_capability (psf) ;

	psf->blockwidth = sizeof (float) * psf->sf.channels ;

	if (psf->file.mode == SFM_READ || psf->file.mode == SFM_RDWR)
	{	switch (psf->endian + float_caps)
		{	case (SF_ENDIAN_BIG + FLOAT_CAN_RW_BE) :
					psf->data_endswap = SF_FALSE ;
					psf->read_short		= host_read_f2s ;
					psf->read_int		= host_read_f2i ;
					psf->read_float		= host_read_f ;
					psf->read_double	= host_read_f2d ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_CAN_RW_LE) :
					psf->data_endswap = SF_FALSE ;
					psf->read_short		= host_read_f2s ;
					psf->read_int		= host_read_f2i ;
					psf->read_float		= host_read_f ;
					psf->read_double	= host_read_f2d ;
					break ;

			case (SF_ENDIAN_BIG + FLOAT_CAN_RW_LE) :
					psf->data_endswap = SF_TRUE ;
					psf->read_short		= host_read_f2s ;
					psf->read_int		= host_read_f2i ;
					psf->read_float		= host_read_f ;
					psf->read_double	= host_read_f2d ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_CAN_RW_BE) :
					psf->data_endswap = SF_TRUE ;
					psf->read_short		= host_read_f2s ;
					psf->read_int		= host_read_f2i ;
					psf->read_float		= host_read_f ;
					psf->read_double	= host_read_f2d ;
					break ;

			/* When the CPU is not IEEE compatible. */
			case (SF_ENDIAN_BIG + FLOAT_BROKEN_LE) :
					psf->data_endswap = SF_TRUE ;
					psf->read_short		= replace_read_f2s ;
					psf->read_int		= replace_read_f2i ;
					psf->read_float		= replace_read_f ;
					psf->read_double	= replace_read_f2d ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_BROKEN_LE) :
					psf->data_endswap = SF_FALSE ;
					psf->read_short		= replace_read_f2s ;
					psf->read_int		= replace_read_f2i ;
					psf->read_float		= replace_read_f ;
					psf->read_double	= replace_read_f2d ;
					break ;

			case (SF_ENDIAN_BIG + FLOAT_BROKEN_BE) :
					psf->data_endswap = SF_FALSE ;
					psf->read_short		= replace_read_f2s ;
					psf->read_int		= replace_read_f2i ;
					psf->read_float		= replace_read_f ;
					psf->read_double	= replace_read_f2d ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_BROKEN_BE) :
					psf->data_endswap = SF_TRUE ;
					psf->read_short		= replace_read_f2s ;
					psf->read_int		= replace_read_f2i ;
					psf->read_float		= replace_read_f ;
					psf->read_double	= replace_read_f2d ;
					break ;

			default : break ;
			} ;
		} ;

	if (psf->file.mode == SFM_WRITE || psf->file.mode == SFM_RDWR)
	{	switch (psf->endian + float_caps)
		{	case (SF_ENDIAN_LITTLE + FLOAT_CAN_RW_LE) :
					psf->data_endswap = SF_FALSE ;
					psf->write_short	= host_write_s2f ;
					psf->write_int		= host_write_i2f ;
					psf->write_float	= host_write_f ;
					psf->write_double	= host_write_d2f ;
					break ;

			case (SF_ENDIAN_BIG + FLOAT_CAN_RW_BE) :
					psf->data_endswap = SF_FALSE ;
					psf->write_short	= host_write_s2f ;
					psf->write_int		= host_write_i2f ;
					psf->write_float	= host_write_f ;
					psf->write_double	= host_write_d2f ;
					break ;

			case (SF_ENDIAN_BIG + FLOAT_CAN_RW_LE) :
					psf->data_endswap = SF_TRUE ;
					psf->write_short	= host_write_s2f ;
					psf->write_int		= host_write_i2f ;
					psf->write_float	= host_write_f ;
					psf->write_double	= host_write_d2f ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_CAN_RW_BE) :
					psf->data_endswap = SF_TRUE ;
					psf->write_short	= host_write_s2f ;
					psf->write_int		= host_write_i2f ;
					psf->write_float	= host_write_f ;
					psf->write_double	= host_write_d2f ;
					break ;

			/* When the CPU is not IEEE compatible. */
			case (SF_ENDIAN_BIG + FLOAT_BROKEN_LE) :
					psf->data_endswap = SF_TRUE ;
					psf->write_short	= replace_write_s2f ;
					psf->write_int		= replace_write_i2f ;
					psf->write_float	= replace_write_f ;
					psf->write_double	= replace_write_d2f ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_BROKEN_LE) :
					psf->data_endswap = SF_FALSE ;
					psf->write_short	= replace_write_s2f ;
					psf->write_int		= replace_write_i2f ;
					psf->write_float	= replace_write_f ;
					psf->write_double	= replace_write_d2f ;
					break ;

			case (SF_ENDIAN_BIG + FLOAT_BROKEN_BE) :
					psf->data_endswap = SF_FALSE ;
					psf->write_short	= replace_write_s2f ;
					psf->write_int		= replace_write_i2f ;
					psf->write_float	= replace_write_f ;
					psf->write_double	= replace_write_d2f ;
					break ;

			case (SF_ENDIAN_LITTLE + FLOAT_BROKEN_BE) :
					psf->data_endswap = SF_TRUE ;
					psf->write_short	= replace_write_s2f ;
					psf->write_int		= replace_write_i2f ;
					psf->write_float	= replace_write_f ;
					psf->write_double	= replace_write_d2f ;
					break ;

			default : break ;
			} ;
		} ;

	if (psf->filelength > psf->dataoffset)
	{	psf->datalength = (psf->dataend > 0) ? psf->dataend - psf->dataoffset :
							psf->filelength - psf->dataoffset ;
		}
	else
		psf->datalength = 0 ;

	psf->sf.frames = psf->blockwidth > 0 ? psf->datalength / psf->blockwidth : 0 ;

	return 0 ;
} /* float32_init */

float
float32_be_read (const unsigned char *cptr)
{	int		exponent, mantissa, negative ;
	float	fvalue ;

	negative = cptr [0] & 0x80 ;
	exponent = ((cptr [0] & 0x7F) << 1) | ((cptr [1] & 0x80) ? 1 : 0) ;
	mantissa = ((cptr [1] & 0x7F) << 16) | (cptr [2] << 8) | (cptr [3]) ;

	if (! (exponent || mantissa))
		return 0.0 ;

	mantissa |= 0x800000 ;
	exponent = exponent ? exponent - 127 : 0 ;

	fvalue = mantissa ? ((float) mantissa) / ((float) 0x800000) : 0.0 ;

	if (negative)
		fvalue *= -1 ;

	if (exponent > 0)
		fvalue *= pow (2.0, exponent) ;
	else if (exponent < 0)
		fvalue /= pow (2.0, abs (exponent)) ;

	return fvalue ;
} /* float32_be_read */

float
float32_le_read (const unsigned char *cptr)
{	int		exponent, mantissa, negative ;
	float	fvalue ;

	negative = cptr [3] & 0x80 ;
	exponent = ((cptr [3] & 0x7F) << 1) | ((cptr [2] & 0x80) ? 1 : 0) ;
	mantissa = ((cptr [2] & 0x7F) << 16) | (cptr [1] << 8) | (cptr [0]) ;

	if (! (exponent || mantissa))
		return 0.0 ;

	mantissa |= 0x800000 ;
	exponent = exponent ? exponent - 127 : 0 ;

	fvalue = mantissa ? ((float) mantissa) / ((float) 0x800000) : 0.0 ;

	if (negative)
		fvalue *= -1 ;

	if (exponent > 0)
		fvalue *= pow (2.0, exponent) ;
	else if (exponent < 0)
		fvalue /= pow (2.0, abs (exponent)) ;

	return fvalue ;
} /* float32_le_read */

void
float32_le_write (float in, unsigned char *out)
{	int		exponent, mantissa, negative = 0 ;

	memset (out, 0, sizeof (int)) ;

	if (fabs (in) < 1e-30)
		return ;

	if (in < 0.0)
	{	in *= -1.0 ;
		negative = 1 ;
		} ;

	in = frexp (in, &exponent) ;

	exponent += 126 ;

	in *= (float) 0x1000000 ;
	mantissa = (((int) in) & 0x7FFFFF) ;

	if (negative)
		out [3] |= 0x80 ;

	if (exponent & 0x01)
		out [2] |= 0x80 ;

	out [0] = mantissa & 0xFF ;
	out [1] = (mantissa >> 8) & 0xFF ;
	out [2] |= (mantissa >> 16) & 0x7F ;
	out [3] |= (exponent >> 1) & 0x7F ;

	return ;
} /* float32_le_write */

void
float32_be_write (float in, unsigned char *out)
{	int		exponent, mantissa, negative = 0 ;

	memset (out, 0, sizeof (int)) ;

	if (fabs (in) < 1e-30)
		return ;

	if (in < 0.0)
	{	in *= -1.0 ;
		negative = 1 ;
		} ;

	in = frexp (in, &exponent) ;

	exponent += 126 ;

	in *= (float) 0x1000000 ;
	mantissa = (((int) in) & 0x7FFFFF) ;

	if (negative)
		out [0] |= 0x80 ;

	if (exponent & 0x01)
		out [1] |= 0x80 ;

	out [3] = mantissa & 0xFF ;
	out [2] = (mantissa >> 8) & 0xFF ;
	out [1] |= (mantissa >> 16) & 0x7F ;
	out [0] |= (exponent >> 1) & 0x7F ;

	return ;
} /* float32_be_write */

/*==============================================================================================
**	Private functions.
*/

static void
float32_peak_update	(SF_PRIVATE *psf, const float *buffer, int count, sf_count_t indx)
{	int 	chan ;
	int		k, position ;
	float	fmaxval ;

	for (chan = 0 ; chan < psf->sf.channels ; chan++)
	{	fmaxval = fabs (buffer [chan]) ;
		position = 0 ;
		for (k = chan ; k < count ; k += psf->sf.channels)
			if (fmaxval < fabs (buffer [k]))
			{	fmaxval = fabs (buffer [k]) ;
				position = k ;
				} ;

		if (fmaxval > psf->peak_info->peaks [chan].value)
		{	psf->peak_info->peaks [chan].value = fmaxval ;
			psf->peak_info->peaks [chan].position = psf->write_current + indx + (position / psf->sf.channels) ;
			} ;
		} ;

	return ;
} /* float32_peak_update */

static int
float32_get_capability	(SF_PRIVATE *psf)
{	union
	{	float			f ;
		int				i ;
		unsigned char	c [4] ;
	} data ;

	data.f = (float) 1.23456789 ; /* Some arbitrary value. */

	if (! psf->ieee_replace)
	{	/* If this test is true ints and floats are compatible and little endian. */
		if (data.c [0] == 0x52 && data.c [1] == 0x06 && data.c [2] == 0x9e && data.c [3] == 0x3f)
			return FLOAT_CAN_RW_LE ;

		/* If this test is true ints and floats are compatible and big endian. */
		if (data.c [3] == 0x52 && data.c [2] == 0x06 && data.c [1] == 0x9e && data.c [0] == 0x3f)
			return FLOAT_CAN_RW_BE ;
		} ;

	/* Floats are broken. Don't expect reading or writing to be fast. */
	psf_log_printf (psf, "Using IEEE replacement code for float.\n") ;

	return (CPU_IS_LITTLE_ENDIAN) ? FLOAT_BROKEN_LE : FLOAT_BROKEN_BE ;
} /* float32_get_capability */

/*=======================================================================================
*/

static void
f2s_array (const float *src, int count, short *dest, float scale)
{
	for (int i = 0 ; i < count ; i++)
	{	dest [i] = psf_lrintf (scale * src [i]) ;
		} ;
} /* f2s_array */

static void
f2s_clip_array (const float *src, int count, short *dest, float scale)
{	for (int i = 0 ; i < count ; i++)
	{	float tmp = scale * src [i] ;

		if (tmp > 32767.0)
			dest [i] = SHRT_MAX ;
		else if (tmp < -32768.0)
			dest [i] = SHRT_MIN ;
		else
			dest [i] = psf_lrintf (tmp) ;
		} ;
} /* f2s_clip_array */

static inline void
f2i_array (const float *src, int count, int *dest, float scale)
{	for (int i = 0 ; i < count ; i++)
	{	dest [i] = psf_lrintf (scale * src [i]) ;
		} ;
} /* f2i_array */

static inline void
f2i_clip_array (const float *src, int count, int *dest, float scale)
{	for (int i = 0 ; i < count ; i++)
	{	float tmp = scale * src [i] ;

		if (tmp > (1.0 * INT_MAX))
			dest [i] = INT_MAX ;
		else if (tmp < (-1.0 * INT_MAX))
			dest [i] = INT_MIN ;
		else
			dest [i] = psf_lrintf (tmp) ;
		} ;
} /* f2i_clip_array */

static inline void
f2d_array (const float *src, int count, double *dest)
{	for (int i = 0 ; i < count ; i++)
	{	dest [i] = src [i] ;
		} ;
} /* f2d_array */

static inline void
s2f_array (const short *src, float *dest, int count, float scale)
{	for (int i = 0 ; i < count ; i++)
	{	dest [i] = scale * src [i] ;
		} ;
} /* s2f_array */

static inline void
i2f_array (const int *src, float *dest, int count, float scale)
{	for (int i = 0 ; i < count ; i++)
	{	dest [i] = scale * src [i] ;
		} ;
} /* i2f_array */

static inline void
d2f_array (const double *src, float *dest, int count)
{	for (int i = 0 ; i < count ; i++)
	{	dest [i] = src [i] ;
		} ;
} /* d2f_array */

/*----------------------------------------------------------------------------------------------
*/

static sf_count_t
host_read_f2s	(SF_PRIVATE *psf, short *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	void		(*convert) (const float *, int, short *, float) ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;
	float		scale ;

	convert = (psf->add_clipping) ? f2s_clip_array : f2s_array ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;
	scale = (psf->float_int_mult == 0) ? 1.0 : 0x7FFF / psf->float_max ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

/* Fix me : Need lef2s_array */
		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, readcount) ;

		convert (ubuf.fbuf, readcount, ptr + total, scale) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* host_read_f2s */

static sf_count_t
host_read_f2i	(SF_PRIVATE *psf, int *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	void		(*convert) (const float *, int, int *, float) ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;
	float		scale ;

	convert = (psf->add_clipping) ? f2i_clip_array : f2i_array ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;
	scale = (psf->float_int_mult == 0) ? 1.0 : 2147483648.0f / psf->float_max ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		convert (ubuf.fbuf, readcount, ptr + total, scale) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* host_read_f2i */

static sf_count_t
host_read_f	(SF_PRIVATE *psf, float *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;

	if (psf->data_endswap != SF_TRUE)
		return psf_fread (ptr, sizeof (float), len, psf) ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		endswap_int_copy ((int*) (ptr + total), ubuf.ibuf, readcount) ;

		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* host_read_f */

static sf_count_t
host_read_f2d	(SF_PRIVATE *psf, double *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

/* Fix me : Need lef2d_array */
		f2d_array (ubuf.fbuf, readcount, ptr + total) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* host_read_f2d */

static sf_count_t
host_write_s2f	(SF_PRIVATE *psf, const short *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;
	float		scale ;

/* Erik */
	scale = (psf->scale_int_float == 0) ? 1.0 : 1.0 / 0x8000 ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		s2f_array (ptr + total, ubuf.fbuf, bufferlen, scale) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* host_write_s2f */

static sf_count_t
host_write_i2f	(SF_PRIVATE *psf, const int *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;
	float		scale ;

	scale = (psf->scale_int_float == 0) ? 1.0 : 1.0 / (8.0 * 0x10000000) ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		i2f_array (ptr + total, ubuf.fbuf, bufferlen, scale) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float) , bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* host_write_i2f */

static sf_count_t
host_write_f	(SF_PRIVATE *psf, const float *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;

	if (psf->peak_info)
		float32_peak_update (psf, ptr, len, 0) ;

	if (psf->data_endswap != SF_TRUE)
		return psf_fwrite (ptr, sizeof (float), len, psf) ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;

		endswap_int_copy (ubuf.ibuf, (const int*) (ptr + total), bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* host_write_f */

static sf_count_t
host_write_d2f	(SF_PRIVATE *psf, const double *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;

		d2f_array (ptr + total, ubuf.fbuf, bufferlen) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* host_write_d2f */

/*=======================================================================================
*/

static sf_count_t
replace_read_f2s	(SF_PRIVATE *psf, short *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;
	float		scale ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;
	scale = (psf->float_int_mult == 0) ? 1.0 : 0x7FFF / psf->float_max ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		bf2f_array (ubuf.fbuf, bufferlen) ;

		f2s_array (ubuf.fbuf, readcount, ptr + total, scale) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* replace_read_f2s */

static sf_count_t
replace_read_f2i	(SF_PRIVATE *psf, int *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;
	float		scale ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;
	scale = (psf->float_int_mult == 0) ? 1.0 : 2147483648.0f / psf->float_max ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		bf2f_array (ubuf.fbuf, bufferlen) ;

		f2i_array (ubuf.fbuf, readcount, ptr + total, scale) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* replace_read_f2i */

static sf_count_t
replace_read_f	(SF_PRIVATE *psf, float *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;

	/* FIX THIS */

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		bf2f_array (ubuf.fbuf, bufferlen) ;

		memcpy (ptr + total, ubuf.fbuf, bufferlen * sizeof (float)) ;

		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* replace_read_f */

static sf_count_t
replace_read_f2d	(SF_PRIVATE *psf, double *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, readcount ;
	sf_count_t	total = 0 ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		readcount = (int) psf_fread (ubuf.fbuf, sizeof (float), bufferlen, psf) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		bf2f_array (ubuf.fbuf, bufferlen) ;

		f2d_array (ubuf.fbuf, readcount, ptr + total) ;
		total += readcount ;
		if (readcount < bufferlen)
			break ;
		len -= readcount ;
		} ;

	return total ;
} /* replace_read_f2d */

static sf_count_t
replace_write_s2f	(SF_PRIVATE *psf, const short *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;
	float		scale ;

	scale = (psf->scale_int_float == 0) ? 1.0 : 1.0 / 0x8000 ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		s2f_array (ptr + total, ubuf.fbuf, bufferlen, scale) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		f2bf_array (ubuf.fbuf, bufferlen) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* replace_write_s2f */

static sf_count_t
replace_write_i2f	(SF_PRIVATE *psf, const int *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;
	float		scale ;

	scale = (psf->scale_int_float == 0) ? 1.0 : 1.0 / (8.0 * 0x10000000) ;
	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		i2f_array (ptr + total, ubuf.fbuf, bufferlen, scale) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		f2bf_array (ubuf.fbuf, bufferlen) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* replace_write_i2f */

static sf_count_t
replace_write_f	(SF_PRIVATE *psf, const float *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;

	/* FIX THIS */
	if (psf->peak_info)
		float32_peak_update (psf, ptr, len, 0) ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;

		memcpy (ubuf.fbuf, ptr + total, bufferlen * sizeof (float)) ;

		f2bf_array (ubuf.fbuf, bufferlen) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float) , bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* replace_write_f */

static sf_count_t
replace_write_d2f	(SF_PRIVATE *psf, const double *ptr, sf_count_t len)
{	BUF_UNION	ubuf ;
	int			bufferlen, writecount ;
	sf_count_t	total = 0 ;

	bufferlen = ARRAY_LEN (ubuf.fbuf) ;

	while (len > 0)
	{	if (len < bufferlen)
			bufferlen = (int) len ;
		d2f_array (ptr + total, ubuf.fbuf, bufferlen) ;

		if (psf->peak_info)
			float32_peak_update (psf, ubuf.fbuf, bufferlen, total / psf->sf.channels) ;

		f2bf_array (ubuf.fbuf, bufferlen) ;

		if (psf->data_endswap == SF_TRUE)
			endswap_int_array (ubuf.ibuf, bufferlen) ;

		writecount = (int) psf_fwrite (ubuf.fbuf, sizeof (float), bufferlen, psf) ;
		total += writecount ;
		if (writecount < bufferlen)
			break ;
		len -= writecount ;
		} ;

	return total ;
} /* replace_write_d2f */

/*----------------------------------------------------------------------------------------------
*/

static void
bf2f_array (float *buffer, int count)
{	for (int i = 0 ; i < count ; i++)
	{	buffer [i] = FLOAT32_READ ((unsigned char *) &buffer [i]) ;
		} ;
} /* bf2f_array */

static void
f2bf_array (float *buffer, int count)
{	for (int i = 0 ; i < count ; i++)
	{	FLOAT32_WRITE (buffer [i], (unsigned char*) &buffer [i]) ;
		} ;
} /* f2bf_array */

