/*-
 * Copyright (c) 2009 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 <audio_grc3.h>

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

#define Z_QUALITY_MIN           0
#define Z_QUALITY_MAX           6
#define Z_QUALITY_ZOH           Z_QUALITY_MIN
#define Z_QUALITY_LINEAR        Z_QUALITY_MIN
#define Z_QUALITY_SINC          3

#define Z_GRC3_BUFSZ            8192
#define Z_GRC3_MAXCHAN          8

#define Z_MULTIFORMAT           0

#define AFMT_S_NE               AFMT_S32_NE

struct feeder_grc3_info {
        uint32_t src;
        uint32_t dst;
        uint32_t channels;
        uint32_t align;
        uint32_t maxfeed;
        int quality;
        grc3state_t grc[Z_GRC3_MAXCHAN];
        int32_t buffer[Z_GRC3_BUFSZ];
};

static int
feeder_grc3_reset(struct feeder_grc3_info *info)
{
        uint32_t i;

        info->align = PCM_32_BPS * info->channels;
        info->maxfeed = SND_FXDIV(sizeof(info->buffer), info->align);

        for (i = 0; i < info->channels; i++) {
                grc3_reset(&(info->grc[i]));
                grc3_setup(&(info->grc[i]), info->src, info->dst);
        }

        return (0);
}

static int
feeder_grc3_get(struct pcm_feeder *f, int what)
{
        struct feeder_grc3_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_grc3_set(struct pcm_feeder *f, int what, int32_t value)
{
        struct feeder_grc3_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 (value < Z_QUALITY_MIN || value > Z_QUALITY_MAX)
                        return (EINVAL);
                if (info->quality == value)
                        return (0);
                info->quality = value;
                break;
        case FEEDRATE_CHANNELS:
                if (value > Z_GRC3_MAXCHAN)
                        return (E2BIG);
                if (info->channels == (uint32_t)value)
                        return (0);
                info->channels = (uint32_t)value;
                break;
        default:
                return (EINVAL);
                break;
        }

        return (feeder_grc3_reset(info));
}

static int
feeder_grc3_init(struct pcm_feeder *f)
{
        struct feeder_grc3_info *info;
        int error;

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

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

        info->src = 48000;
        info->dst = 48000;
        info->channels = AFMT_CHANNEL(f->desc->in);
        info->quality = Z_QUALITY_SINC;

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

        f->data = info;

        return (error);
}

static int
feeder_grc3_free(struct pcm_feeder *f)
{
        struct feeder_grc3_info *info;

        info = f->data;
        if (info != NULL)
                free(info, M_DEVBUF);

        return (0);
}

static int
feeder_grc3_feed(struct pcm_feeder *f, struct pcm_channel *c,
    uint8_t *b, uint32_t count, void *source)
{
        struct feeder_grc3_info *info;
        uint32_t i, 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);

#if 0
        /* 32bit -> 24bit */
        for (i = 0; i < (isz * info->channels); i++)
                info->buffer[i] >>= 8;
#endif

        for (i = 0; i < info->channels; i++)
                grc3_convert(&(info->grc[i]), info->quality,
                    info->buffer, b, isz, osz, info->channels, i);

#if 0
        /* 24bit -> 32bit */
        for (i = 0; i < (info->grc[0].outsz * info->channels); i++)
                ((int32_t *)b)[i] <<= 8;
#endif

        return (info->grc[0].outsz * info->align);
}

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

static kobj_method_t feeder_rate_methods[] = {
        KOBJMETHOD(feeder_init,         feeder_grc3_init),
        KOBJMETHOD(feeder_free,         feeder_grc3_free),
        KOBJMETHOD(feeder_set,          feeder_grc3_set),
        KOBJMETHOD(feeder_get,          feeder_grc3_get),
        KOBJMETHOD(feeder_feed,         feeder_grc3_feed),
        KOBJMETHOD_END
};

FEEDER_DECLARE(feeder_rate, NULL);