/*-
 * Copyright (c) 2008 Ariff Abdullah <ariff@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <speex/speex_resampler.h>

#ifndef SND_USE_FXDIV
#define SND_USE_FXDIV
#include "snd_fxdiv_gen.h"
#endif

#ifdef FLOATING_POINT
#include <math.h>
#define AFMT_SPEEX_NE   AFMT_S32_NE
#else
#define AFMT_SPEEX_NE   AFMT_S16_NE
#endif

#define AFMT_S_NE       AFMT_SPEEX_NE

#define Z_QUALITY_MIN           SPEEX_RESAMPLER_QUALITY_MIN
#define Z_QUALITY_MAX           SPEEX_RESAMPLER_QUALITY_MAX
#define Z_QUALITY_ZOH           Z_QUALITY_MIN
#define Z_QUALITY_LINEAR        Z_QUALITY_MIN
#define Z_QUALITY_SINC          SPEEX_RESAMPLER_QUALITY_DEFAULT

#define Z_SPEEX_BUFSZ           8192

struct feeder_speex_info {
        SpeexResamplerState *spx;
        uint32_t src;
        uint32_t dst;
        uint32_t channels;
        uint32_t align;
        uint32_t maxfeed;
        int quality;
#ifdef FLOATING_POINT
        float buffer[Z_SPEEX_BUFSZ];
#else
        spx_int16_t buffer[Z_SPEEX_BUFSZ];
#endif
};

static int
feeder_speex_resampler_reset(struct feeder_speex_info *info)
{

        if (info->spx != NULL) {
                speex_resampler_destroy(info->spx);
                info->spx = NULL;
        }

        info->spx = speex_resampler_init(info->channels, info->src, info->dst,
            info->quality, NULL);

        if (info->spx == NULL)
                return (ENOMEM);

        info->align = sizeof(info->buffer[0]) * info->channels;
        info->maxfeed = SND_FXDIV(sizeof(info->buffer) /
            sizeof(info->buffer[0]), info->channels);

        return (0);
}

static int
feeder_speex_resampler_get(struct pcm_feeder *f, int what)
{
        struct feeder_speex_info *info;
        int ret;

        info = f->data;

        switch (what) {
        case FEEDRATE_SRC:
                ret = info->src;
                break;
        case FEEDRATE_DST:
                ret = info->dst;
                break;
        case FEEDRATE_QUALITY:
                ret = info->quality;
                break;
        case FEEDRATE_CHANNELS:
                ret = info->channels;
                break;
        default:
                return (-1);
                break;
        }

        return (ret);
}

static int
feeder_speex_resampler_set(struct pcm_feeder *f, int what, int32_t value)
{
        struct feeder_speex_info *info;

        info = f->data;

        switch (what) {
        case FEEDRATE_SRC:
                if (info->src == (uint32_t)value)
                        return (0);
                info->src = (uint32_t)value;
                break;
        case FEEDRATE_DST:
                if (info->dst == (uint32_t)value)
                        return (0);
                info->dst = (uint32_t)value;
                break;
        case FEEDRATE_QUALITY:
                if (info->quality == value)
                        return (0);
                info->quality = value;
                break;
        case FEEDRATE_CHANNELS:
                if (info->channels == (uint32_t)value)
                        return (0);
                info->channels = (uint32_t)value;
                break;
        default:
                return (EINVAL);
                break;
        }

        return (feeder_speex_resampler_reset(info));
}

static int
feeder_speex_resampler_init(struct pcm_feeder *f)
{
        struct feeder_speex_info *info;
        int error;

        if (f->desc->in != f->desc->out ||
            AFMT_ENCODING(f->desc->in) != AFMT_SPEEX_NE)
                return (EINVAL);

        info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
        if (info == NULL)
                return (ENOMEM);

        info->spx = NULL;
        info->src = 44100;
        info->dst = 48000;
        info->channels = AFMT_CHANNEL(f->desc->in);
        info->align = sizeof(info->buffer[0]) * info->channels;
        info->quality = SPEEX_RESAMPLER_QUALITY_DEFAULT;

        error = feeder_speex_resampler_reset(info);
        if (error != 0) {
                free(info, M_DEVBUF);
                return (error);
        }

        f->data = info;

        return (error);
}

static int
feeder_speex_resampler_free(struct pcm_feeder *f)
{
        struct feeder_speex_info *info;

        info = f->data;
        if (info != NULL) {
                if (info->spx != NULL) {
                        speex_resampler_destroy(info->spx);
                        info->spx = NULL;
                }
                free(info, M_DEVBUF);
        }

        return (0);
}

static int
feeder_speex_resampler_feed(struct pcm_feeder *f, struct pcm_channel *c,
    uint8_t *b, uint32_t count, void *source)
{
        struct feeder_speex_info *info;
        uint32_t osz, isz;

        info = f->data;
        osz = SND_FXDIV(count, info->align);
        isz = ((uint64_t)info->src * osz) / info->dst;
        if (isz < 1)
                return (0);

        if (isz > info->maxfeed)
                isz = info->maxfeed;

        isz = SND_FXDIV(FEEDER_FEED(f->source, c, (uint8_t *)info->buffer,
            isz * info->align, source), info->align);

#ifdef FLOATING_POINT
        u_int i;

        for (i = 0; i < isz * info->channels; i++)
                info->buffer[i] = (double)(((spx_int32_t *)info->buffer)[i]) /
                    (1.0 + INT32_MAX);

        speex_resampler_process_interleaved_float(info->spx, info->buffer,
            &isz, (float *)b, &osz);

        for (i = 0; i < osz * info->channels; i++) {
                int64_t v;

                v = llrint(((double)((float *)b)[i] * (1.0 + INT32_MAX)) +
                    0.5);
                ((spx_int32_t *)b)[i] = (v > INT32_MAX) ? INT32_MAX :
                    ((v < INT32_MIN) ? INT32_MIN : v);
        }
#else
        speex_resampler_process_interleaved_int(info->spx, info->buffer,
            &isz, (spx_int16_t *)b, &osz);
#endif

        return (osz * info->align);
}

static struct pcm_feederdesc feeder_rate_desc[] = {
        { FEEDER_RATE, AFMT_SPEEX_NE, AFMT_SPEEX_NE, 0, 0 },
        { 0, 0, 0, 0, 0 },
};

static kobj_method_t feeder_rate_methods[] = {
        KOBJMETHOD(feeder_init,         feeder_speex_resampler_init),
        KOBJMETHOD(feeder_free,         feeder_speex_resampler_free),
        KOBJMETHOD(feeder_set,          feeder_speex_resampler_set),
        KOBJMETHOD(feeder_get,          feeder_speex_resampler_get),
        KOBJMETHOD(feeder_feed,         feeder_speex_resampler_feed),
        KOBJMETHOD_END
};

FEEDER_DECLARE(feeder_rate, NULL);