/*
 * tcaudio.c - audio processing library for transcode
 * Written by Andrew Church <achurch@achurch.org>
 *
 * This file is part of transcode, a video stream processing tool.
 * transcode is free software, distributable under the terms of the GNU
 * General Public License (version 2 or later).  See the file COPYING
 * for details.
 */

#include "tcaudio.h"

#include "src/transcode.h"
#include <math.h>

/*************************************************************************/

/* Internal data structure to hold various state information.  The
 * TCAHandle returned by tca_init() and passed by the caller to other
 * functions is a pointer to this structure. */

struct tcahandle_ {
    AudioFormat format;            /* Sample format */
    int bits, issigned, msbfirst;  /* Information about sample format */
};

/*************************************************************************/

/* Internal-use functions (defined at the bottom of the file). */

static int tca_get_format_info(AudioFormat format, int *bits_ret,
                               int *issigned_ret, int *msbfirst_ret);
static int tca_convert(const char *funcname, TCAHandle handle, void *buf,
                       int len, AudioFormat srcfmt, AudioFormat destfmt);

/*************************************************************************/
/*************************************************************************/

/* External interface functions. */

/*************************************************************************/

/**
 * tca_init:  Create and return a handle for use in other tcaudio
 * functions.  The handle should be freed with tca_free() when no longer
 * needed.
 *
 * Parameters: format: Audio sample format to use with tcaudio functions.
 * Return value: A handle to be passed to other tcaudio functions, or 0 on
 *               error.
 * Preconditions: None.
 * Postconditions: None.
 */

TCAHandle tca_init(AudioFormat format)
{
    TCAHandle handle;
    int bits, issigned, msbfirst;

    if (!tca_get_format_info(format, &bits, &issigned, &msbfirst)) {
        tc_log_error("libtcaudio", "tca_init: invalid audio format (%d)!",
                     format);
        return NULL;
    }
    handle = malloc(sizeof(*handle));
    if (!handle)
        return NULL;
    handle->format   = format;
    handle->bits     = bits;
    handle->issigned = issigned;
    handle->msbfirst = msbfirst;
    return handle;
}

/*************************************************************************/

/**
 * tca_free:  Free resources allocated for the given handle.
 *
 * Parameters: handle: tcaudio handle.
 * Return value: None.
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

void tca_free(TCAHandle handle)
{
    free(handle);
}

/*************************************************************************/

/**
 * tca_convert_from:  Convert the given audio buffer from another sample
 * format to the format given in tca_init().
 *
 * Parameters: handle: tcaudio handle.
 *                buf: Audio data buffer.
 *                len: Audio data length, in samples.
 *             srcfmt: Format of source audio samples.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

int tca_convert_from(TCAHandle handle, void *buf, int len, AudioFormat srcfmt)
{
    return tca_convert("tca_convert_from", handle, buf, len, srcfmt,
                       handle->format);
}

/*************************************************************************/

/**
 * tca_convert_to:  Convert the given audio buffer from the format given in
 * tca_init() to another sample format.
 *
 * Parameters:  handle: tcaudio handle.
 *                 buf: Audio data buffer.
 *                 len: Audio data length, in samples.
 *             destfmt: Format to convert audio samples into.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

int tca_convert_to(TCAHandle handle, void *buf, int len, AudioFormat destfmt)
{
    return tca_convert("tca_convert_to", handle, buf, len, handle->format,
                       destfmt);
}

/*************************************************************************/

/**
 * tca_amplify:  Amplify the given audio buffer by the given scale factor.
 * When increasing amplitude (scale factor greater than one), samples are
 * automatically clipped to the sample format's amplitude range; if
 * `nclip_ret' is not NULL, the number of clipped samples is stored there
 * (unmodified on error).
 *
 * Parameters:    handle: tcaudio handle.
 *                   buf: Audio data buffer.
 *                   len: Audio data length, in samples.
 *                 scale: Factor by which to scale audio data.
 *             nclip_ret: Variable to store number of clipped samples in,
 *                        or NULL if this value is not required.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

int tca_amplify(TCAHandle handle, void *buf, int len, double scale,
                int *nclip_ret)
{
    int nclip;

    if (!handle || !buf || len < 0) {
        tc_log_error("libtcaudio", "tca_amplify: invalid parameters!");
        return 0;
    }
    nclip = 0;
    if (handle->bits == 8) {
        uint8_t *ptr = buf;
        int bias = handle->issigned ? 0 : 0x80;
        int i;
        for (i = 0; i < len; i++) {
            int32_t v = floor(((ptr[i]-bias) * scale) + 0.5);
            if (v > 0x7F) {
                v = 0x7F;
                nclip++;
            }
            if (v < -0x80) {
                v = -0x80;
                nclip++;
            }
            ptr[i] = v;
        }
    } else if (handle->bits == 16) {
        int8_t *ptr1 = buf + (handle->msbfirst ? 0 : 1);
        uint8_t *ptr2 = buf + (handle->msbfirst ? 1 : 0);
        int bias = handle->issigned ? 0 : 0x8000;
        int i;
        for (i = 0; i < len; i++) {
            int32_t v =
                floor((((ptr1[i*2]<<8 | ptr2[i*2]) - bias) * scale) + 0.5);
            if (v > 0x7FFF) {
                v = 0x7FFF;
                nclip++;
            }
            if (v < -0x8000) {
                v = -0x8000;
                nclip++;
            }
            ptr1[i*2] = v >> 8;
            ptr2[i*2] = v & 0xFF;
        }
    } else {
        tc_log_error("libtcaudio", "tca_amplify: %d-bit samples not supported",
                     handle->bits);
        return 0;
    }
    if (nclip_ret)
        *nclip_ret = nclip;
    return 1;
}

/*************************************************************************/

/**
 * tca_mono_to_stereo:  Convert monaural audio data to stereo by
 * duplicating the data into both stereo channels.
 *
 * Parameters: handle: tcaudio handle.
 *                buf: Audio data buffer.
 *                len: Audio data length, in stereo samples.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

int tca_mono_to_stereo(TCAHandle handle, void *buf, int len)
{

    if (!handle || !buf || len < 0) {
        tc_log_error("libtcaudio", "tca_mono_to_stereo: invalid parameters!");
        return 0;
    }
    /* Be careful--we have to go backwards or we overwrite unconverted data */
    if (handle->bits == 8) {
        uint8_t *ptr = buf;
        int i;
        for (i = len-1; i >= 0; i--) {
            ptr[i*2] = ptr[i];
            ptr[i*2+1] = ptr[i];
        }
    } else if (handle->bits == 16) {
        uint16_t *ptr = buf;
        int i;
        for (i = len-1; i >= 0; i--) {
            ptr[i*2] = ptr[i];
            ptr[i*2+1] = ptr[i];
        }
    } else {
        tc_log_error("libtcaudio", "tca_mono_to_stereo: %d-bit samples not"
                     " supported", handle->bits);
        return 0;
    }
    return 1;
}

/*************************************************************************/

/**
 * tca_stereo_to_mono:  Convert stereo audio data to monaural by mixing the
 * two stereo channels.
 *
 * Parameters: handle: tcaudio handle.
 *                buf: Audio data buffer.
 *                len: Audio data length, in stereo samples.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

int tca_stereo_to_mono(TCAHandle handle, void *buf, int len)
{
    if (!handle || !buf || len < 0) {
        tc_log_error("libtcaudio", "tca_stereo_to_mono: invalid parameters!");
        return 0;
    }
    if (handle->bits == 8) {
        uint8_t *ptr = buf;
        int i;
        for (i = 0; i < len; i++) {
            ptr[i] = ((int)ptr[i*2] + (int)ptr[i*2+1] + 1) / 2;
        }
    } else if (handle->bits == 16) {
        int8_t *ptr1 = buf + (handle->msbfirst ? 0 : 1);
        uint8_t *ptr2 = buf + (handle->msbfirst ? 1 : 0);
        int i;
        for (i = 0; i < len; i++) {
            int32_t v = ((ptr1[i*4]<<8 | ptr2[i*4])
                         + (ptr1[i*4+2]<<8 | ptr2[i*4+2])
                         + 1
                        ) / 2;
            ptr1[i*2] = v >> 8;
            ptr2[i*2] = v & 0xFF;
        }
    } else {
        tc_log_error("libtcaudio", "tca_stereo_to_mono: %d-bit samples not"
                     " supported", handle->bits);
        return 0;
    }
    return 1;
}

/*************************************************************************/
/*************************************************************************/

/* Internal-use helper functions. */

/*************************************************************************/

/**
 * tca_get_format_info:  Return information about the given audio format.
 *
 * Parameters:       format: Audio format to get information about.
 *                 bits_ret: Set to the number of bits in a sample.
 *             issigned_ret: Set to 1 if samples are signed, else 0.
 *             msbfirst_ret: Set to 1 if samples have the most significant
 *                           byte stored first, else 0.
 * Return value: Nonzero if the format is recognized, zero otherwise.
 * Preconditions: bits_ret != NULL
 *                issigned_ret != NULL
 *                msbfirst_ret != NULL
 * Postconditions: None.
 */

static int tca_get_format_info(AudioFormat format, int *bits_ret,
                               int *issigned_ret, int *msbfirst_ret)
{
    switch (format) {
      case TCA_S8:
        *bits_ret = 8;
        *issigned_ret = 1;
        *msbfirst_ret = 0;
        return 1;
      case TCA_U8:
        *bits_ret = 8;
        *issigned_ret = 0;
        *msbfirst_ret = 0;
        return 1;
      case TCA_S16BE:
        *bits_ret = 16;
        *issigned_ret = 1;
        *msbfirst_ret = 1;
        return 1;
      case TCA_S16LE:
        *bits_ret = 16;
        *issigned_ret = 1;
        *msbfirst_ret = 0;
        return 1;
      case TCA_U16BE:
        *bits_ret = 16;
        *issigned_ret = 0;
        *msbfirst_ret = 1;
        return 1;
      case TCA_U16LE:
        *bits_ret = 16;
        *issigned_ret = 0;
        *msbfirst_ret = 0;
        return 1;
    }
    return 0;
}

/*************************************************************************/

/**
 * tca_convert:  Convert from one audio sample format to another.
 * Implements tca_convert_from() and tca_convert_to().
 *
 * Parameters:  handle: tcaudio handle.
 *                 buf: Audio data buffer.
 *                 len: Audio data length, in samples.
 *              srcfmt: Format of source audio samples.
 *             destfmt: Format to convert audio samples into.
 * Return value: Nonzero on success, zero on error (invalid parameters).
 * Preconditions: handle != 0: handle was returned by tca_init()
 * Postconditions: None.
 */

static int tca_convert(const char *funcname, TCAHandle handle, void *buf,
                       int len, AudioFormat srcfmt, AudioFormat destfmt)
{
    int src_bits = -1, src_issigned = -1, src_msbfirst = -1,
        dest_bits = -1, dest_issigned = -1, dest_msbfirst = -1;

    /* Parameter checks */
    if (!handle || !buf || len < 0
     || !tca_get_format_info(srcfmt,&src_bits,&src_issigned,&src_msbfirst)
     || !tca_get_format_info(destfmt,&dest_bits,&dest_issigned,&dest_msbfirst)
    ) {
        tc_log_error("libtcaudio", "%s: invalid parameters!", funcname);
        return 0;
    }

    /* Convert sample sizes and byte orders */
    if (src_bits == 8 && dest_bits == 16) {
        /* 8 bit -> 16 bit */
        const uint8_t *src8 = buf;
        uint8_t *dest8 = buf + (dest_msbfirst ? 0 : 1);
        int i;
        /* Go backwards so we don't overwrite unconverted data */
        for (i = len-1; i >= 0; i--)
            dest8[i*2] = src8[i];
    } else if (src_bits == 16 && dest_bits == 8) {
        /* 16 bit -> 8 bit */
        const uint8_t *src8 = buf + (src_msbfirst ? 0 : 1);
        uint8_t *dest8 = buf;
        int i;
        for (i = 0; i < len; i++)
            dest8[i] = src8[i*2];
    } else if (src_msbfirst != dest_msbfirst) {
        /* 16 bit -> 16 bit, endian swap */
        uint16_t *buf16 = buf;
        int i;
        for (i = 0; i < len; i++)
            buf16[i] = buf16[i]<<8 | buf16[i]>>8;
    } else {
        /* N bit -> N bit, no change */
    }

    /* Convert signed/unsigned */
    if (src_issigned != dest_issigned) {
        int sampsize = dest_bits / 8;
        uint8_t *buf8 = buf + (dest_msbfirst ? 0 : sampsize - 1);
        int i;
        for (i = 0; i < len * sampsize; i += sampsize)
            buf8[i] ^= 0x80;
    }

    /* All done */
    return 1;
}

/*************************************************************************/
/*************************************************************************/

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
