/*-
 * 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.
 *
 * $FreeBSD$
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

#ifdef SND_FEEDER_FULL_MULTIFORMAT
#undef SND_FEEDER_MULTIFORMAT
#define SND_FEEDER_MULTIFORMAT          1
#endif

#include "version.h"
#include "oss_compat.h"
#include "pcm.h"
#include "matrix.h"
#include "matrix_map.h"
#include "g711.h"
#include "intpcm.h"
#include "waveutil.h"
#define SND_USE_FXDIV
#include "snd_fxdiv_gen.h"

#include "feeder_compat.h"

#include "feeder_rate.c"
#include "feeder_stdio.c"
#include "feeder_format.c"
#include "feeder_matrix.c"
#include "feeder_volume.c"
#include "feeder_eq.c"

#if !defined(AFMT_S_NE)
#if defined(Z_16) || (defined(Z_SPEEX) && !defined(FLOATING_POINT)) ||          \
    defined(Z_CG) || defined(Z_ORION)
#define AFMT_S_NE       AFMT_S16_NE
#else
#define AFMT_S_NE       AFMT_S32_NE
#endif
#endif

#ifndef Z_POLYPHASE_MAX
#define Z_POLYPHASE_MAX         0
static int feeder_rate_polyphase_max = 0;
#endif

enum {
        FEEDER_IO_TYPE_UNKNOWN,
        FEEDER_IO_TYPE_RAW,
        FEEDER_IO_TYPE_WAVE,
        FEEDER_IO_TYPE_DSP
};

struct feeder_global_opts {
        uint32_t bufsize;
        uint32_t volume;
        uint32_t afmt_ne;
        int32_t eqpreamp;
        int quality;
        int matrix_swap;
        int verbose;
        int eqtreble, eqbass;
        int bit_perfect;
        int direction;
        int expensive;
};

struct feeder_io_opts {
        struct wave_info waveinfo;
        uint32_t afmt;
        int type, running, exclusive, latency;
        char *file;
};

struct feeder_chain_desc {
        struct {
                uint32_t afmt;
                uint32_t channels;
                uint32_t rate;
                uint32_t maxsize;
                int quality;
        } current;
        struct {
                uint32_t afmt;
                uint32_t channels;
                uint32_t rate;
                uint32_t maxsize;
                int quality;
        } target;
        struct {
                int use;
                int treble, bass;
                int preamp;
        } eq;
        struct pcm_feederdesc desc;
        struct pcm_channel *channel;
        struct pcmchan_matrix m_in, m_out;
        uint32_t matrix_swap;
        uint32_t afmt_ne;
        int use_volume;
        int expensive;
};

static const struct {
        uint32_t afmt;
        const char *sfmt, *alias;
        int prefered;
} afmt_tab[] = {
        {     AFMT_S8,    "s8",   NULL, 0 },
        { AFMT_S16_LE, "s16le",   "16", 1 },
        { AFMT_S24_LE, "s24le",   "24", 1 },
        { AFMT_S32_LE, "s32le",   "32", 1 },
        { AFMT_S16_BE, "s16be",   NULL, 0 },
        { AFMT_S24_BE, "s24be",   NULL, 0 },
        { AFMT_S32_BE, "s32be",   NULL, 0 },
        {     AFMT_U8,    "u8",    "8", 1 },
        { AFMT_U16_LE, "u16le",   NULL, 0 },
        { AFMT_U24_LE, "u24le",   NULL, 0 },
        { AFMT_U32_LE, "u32le",   NULL, 0 },
        { AFMT_U16_BE, "u16be",   NULL, 0 },
        { AFMT_U24_BE, "u24be",   NULL, 0 },
        { AFMT_U32_BE, "u32be",   NULL, 0 },
        {  AFMT_A_LAW,  "alaw",   NULL, 0 },
        { AFMT_MU_LAW, "mulaw", "ulaw", 0 },
        {    AFMT_AC3,   "ac3",   NULL, 0 },
};

#define AFMT_TAB_SIZE           ((int)(sizeof(afmt_tab) / sizeof(afmt_tab[0])))

static void
sfmt_swap_sign(char *sfmt)
{

        if (sfmt[0] == 'u')
                sfmt[0] = 's';
        else if (sfmt[0] == 's')
                sfmt[0] = 'u';
}

static void
sfmt_swap_endian(char *sfmt)
{

        if (strlen(sfmt) == 5) {
                if (sfmt[3] == 'l')
                        sfmt[3] = 'b';
                else if (sfmt[3] == 'b')
                        sfmt[3] = 'l';
        }
}

static uint32_t
sfmt2afmt(const char *sfmt)
{
        int i;

        for (i = 0; i < AFMT_TAB_SIZE; i++) {
                if (strcmp(sfmt, afmt_tab[i].sfmt) == 0 ||
                    (afmt_tab[i].alias != NULL &&
                    strcmp(sfmt, afmt_tab[i].alias) == 0))
                        return (afmt_tab[i].afmt);
        }

        return (0x00000000);
}

static uint32_t
bit2afmt(int bit)
{
        int i;

        for (i = 0; i < AFMT_TAB_SIZE; i++) {
                if (AFMT_BIT(afmt_tab[i].afmt) == bit &&
                    afmt_tab[i].prefered != 0)
                        return (afmt_tab[i].afmt);
        }

        return (0x00000000);
}

static const char *
afmt2sfmt(uint32_t afmt)
{
        int i;

        for (i = 0; i < AFMT_TAB_SIZE; i++) {
                if (AFMT_ENCODING(afmt) == afmt_tab[i].afmt)
                        return (afmt_tab[i].sfmt);
        }

        return (NULL);
}

static uint32_t
afmt_swap_sign(uint32_t afmt)
{
        const char *sfmt;
        char buf[8];

        sfmt = afmt2sfmt(afmt);
        if (sfmt != NULL) {
                strcpy(buf, sfmt);
                sfmt_swap_sign(buf);
                return (sfmt2afmt(buf));
        }

        return (afmt);
}

static uint32_t
afmt_swap_endian(uint32_t afmt)
{
        const char *sfmt;
        char buf[8];

        sfmt = afmt2sfmt(afmt);
        if (sfmt != NULL) {
                strcpy(buf, sfmt);
                sfmt_swap_endian(buf);
                return (sfmt2afmt(buf));
        }

        return (afmt);
}

static struct pcm_channel *pcmchannel = NULL;
static struct feeder_global_opts g_opt;
static struct feeder_io_opts in_opt, out_opt;
static struct timeval tv1, tv2;
static int benchmark = 0;

static void
feeder_benchmark_start(void)
{
        benchmark = 1;
        (void)gettimeofday(&tv1, NULL);
}

static void
feeder_benchmark_stop(void)
{
        double elapsed;

        if (benchmark == 0)
                return;

        (void)gettimeofday(&tv2, NULL);

        benchmark = 0;

        elapsed = (double)(((tv2.tv_sec - tv1.tv_sec) * 1000000) +
            (tv2.tv_usec - tv1.tv_usec)) / 1000000.0;

        fprintf(stderr, "\n  Conversion Time: %.4f sec [%.4f x realtime]",
            elapsed, (double)out_opt.waveinfo.data_size /
            (double)(out_opt.waveinfo.rate * (out_opt.waveinfo.bit >> 3) *
            out_opt.waveinfo.channels) / elapsed);
        fprintf(stderr, "\n    Output Frames: %d",
            out_opt.waveinfo.data_size / ((out_opt.waveinfo.bit >> 3) *
            out_opt.waveinfo.channels));
        fprintf(stderr, "\n        Buffering: bufsize=%u , total=%u bytes\n\n",
            g_opt.bufsize, out_opt.waveinfo.data_size);

        if (pcmchannel != NULL && pcmchannel->feeder != NULL) {
                fprintf(stderr, " Feeders:-\n");
                FEEDER_DUMP(pcmchannel->feeder);
                fprintf(stderr, "\n");
        }
}

static void
sigint_handler(int sig)
{
        fprintf(stderr, "\n\nAborting...\n");

        feeder_benchmark_stop();

        if (out_opt.type == FEEDER_IO_TYPE_WAVE && out_opt.running != 0) {
                out_opt.waveinfo.seekable = 1;
                (void)wave_header_write(&(out_opt.waveinfo));
        }

        if (pcmchannel != NULL)
                chn_destroy(pcmchannel);

        exit(sig);
}

static int
usage(void)
{

        fprintf(stderr, "usage: %s [-b -B bufsize -q quality -n -h "
            "[-d direction] "
            "-e treble:bass -p eq_preamp -F -P -R roundhz -S -v left:right] "
            "[-t type -f format -c channels -r rate -l latency -X] infile "
            "[-t type -f format -c channels -r rate -l latency -X] outfile\n",
            getprogname());

        return (EX_USAGE);
}

static int
parse_options(int *argc, char ***argv,
    struct feeder_global_opts *gopt, struct feeder_io_opts *iopt)
{
        long optl;
        int i, ch;

        optind = 1;
        optreset = 1;

        while ((ch = getopt(*argc, *argv,
            "B:bc:d:e:EFf:l:p:q:Qr:t:v:hnPR:SVX")) != -1) {
                switch (ch) {
                case 'b':
                        gopt->bit_perfect = 1;
                        break;
                case 'B':
                        optl = strtol(optarg, NULL, 10);
                        if (optl > SND_FXDIV_MAX)
                                optl = SND_FXDIV_MAX;
                        else if (optl < 1)
                                optl = 1;
                        gopt->bufsize = (uint32_t)optl;
                        break;
                case 'c':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Supported channels: %d to %d, "
                                    "mono, stereo, swap\n",
                                    SND_CHN_MIN, SND_CHN_MAX);
                                return (EX_USAGE);
                        } else if (strcmp(optarg, "mono") == 0)
                                optl = 1;
                        else if (strcmp(optarg, "stereo") == 0)
                                optl = 2;
                        else if (strcmp(optarg, "swap") == 0) {
                                gopt->matrix_swap = 1;
                                if (iopt->waveinfo.channels != 0)
                                        optl = iopt->waveinfo.channels;
                                else
                                        break;
                        } else
                                optl = strtol(optarg, NULL, 10);
                        if (optl < SND_CHN_MIN || optl > SND_CHN_MAX) {
                                warnx("total channels out of range -- %ld",
                                    optl);
                                return (EINVAL);
                        }
                        iopt->waveinfo.channels = (uint32_t)optl;
                        if (iopt->afmt & AFMT_16BIT)
                                iopt->waveinfo.format =
                                    (iopt->waveinfo.channels > 2) ?
                                    WAVE_FORMAT_EXT : WAVE_FORMAT_PCM;
                        break;
                case 'd':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Direction: play, rec\n");
                                return (EX_USAGE);
                        }
                        if (strcasecmp(optarg, "play") == 0)
                                g_opt.direction = PCMDIR_PLAY;
                        else if (strcasecmp(optarg, "rec") == 0)
                                g_opt.direction = PCMDIR_REC;
                        else {
                                warnx("invalid direction -- %s", optarg);
                                return (EINVAL);
                        }
                        break;
                case 'e':
                {
                        int left, right;

                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Bass/Treble gain: "
                                    "<left 0-100:<right 0-100>\n");
                                return (EX_USAGE);
                        }
                        i = sscanf(optarg, "%d:%d", &left, &right);
                        if ((i == 1 || i == 2) &&
                            (left >= 0 && left <= 100) && (i == 1 ||
                            (right >= 0 && right <= 100))) {
                                gopt->eqtreble = left;
                                gopt->eqbass = (i == 1) ? left : right;
                        } else {
                                warnx("invalid volume -- %s", optarg);
                                return (EINVAL);
                        }
                }
                        break;
                case 'E':
                        g_opt.expensive = 1;
                        break;
                case 'f':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr, "Supported format: ");
                                for (i = 0; i < AFMT_TAB_SIZE; i++) {
                                        fprintf(stderr, "%s%s",
                                            afmt_tab[i].sfmt,
                                            (i < (AFMT_TAB_SIZE - 1)) ?
                                            ", " : "");
                                }
                                fprintf(stderr, "\n");
                                return (EX_USAGE);
                        }
                        iopt->afmt = sfmt2afmt(optarg);
                        if (iopt->afmt == 0) {
                                warnx("unsupported format -- %s", optarg);
                                return (EINVAL);
                        }
                        iopt->waveinfo.bit = AFMT_BIT(iopt->afmt);
                        if (iopt->afmt & AFMT_BIGENDIAN)
                                iopt->waveinfo.endian = WAVE_BIG_ENDIAN;
                        else
                                iopt->waveinfo.endian = WAVE_LITTLE_ENDIAN;
                        if (iopt->afmt == AFMT_MU_LAW)
                                iopt->waveinfo.format = WAVE_FORMAT_ULAW;
                        else if (iopt->afmt == AFMT_A_LAW)
                                iopt->waveinfo.format = WAVE_FORMAT_ALAW;
                        else if (iopt->afmt & (AFMT_24BIT | AFMT_32BIT))
                                iopt->waveinfo.format = WAVE_FORMAT_EXT;
                        else if (iopt->afmt & AFMT_16BIT)
                                iopt->waveinfo.format =
                                    (iopt->waveinfo.channels > 2) ?
                                    WAVE_FORMAT_EXT : WAVE_FORMAT_PCM;
                        break;
                case 'F':
                        feeder_rate_polyphase_max = -1;
                        break;
                case 'l':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Latency (OSS \"Policy\"): 0 - 10\n");
                                return (EX_USAGE);
                        }
                        optl = strtol(optarg, NULL, 10);
                        if (optl < 0 || optl > 10) {
                                warnx("Latency (OSS \"Policy\") out of range"
                                    "-- %s", optarg);
                                return (EX_USAGE);
                        }
                        iopt->latency = (int)optl;
                        break;
                case 'p':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "EQ Preamp: -/+ %d.0dB, %d.%ddB step\n",
                                    FEEDEQ_GAIN_MAX,
                                    FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV,
                                    FEEDEQ_GAIN_STEP -
                                    ((FEEDEQ_GAIN_STEP / FEEDEQ_GAIN_DIV) *
                                    FEEDEQ_GAIN_DIV));
                                return (EX_USAGE);
                        }
                        optl = (int32_t)feed_eq_scan_preamp_arg(optarg);
                        if (optl < FEEDEQ_PREAMP_MIN ||
                            optl > FEEDEQ_PREAMP_MAX) {
                                warnx("EQ Preamp out of range -- %s", optarg);
                                return (EINVAL);
                        }
                        gopt->eqpreamp = (int32_t)optl;
                        break;
                case 'q':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Resampling quality: %d to %d, "
                                    "zoh, linear, sinc\n",
                                    Z_QUALITY_MIN, Z_QUALITY_MAX);
                                return (EX_USAGE);
                        } else if (strcmp(optarg, "zoh") == 0)
                                optl = Z_QUALITY_ZOH;
                        else if (strcmp(optarg, "linear") == 0)
                                optl = Z_QUALITY_LINEAR;
                        else if (strcmp(optarg, "sinc") == 0)
                                optl = Z_QUALITY_SINC;
                        else
                                optl = strtol(optarg, NULL, 10);
                        if (optl < Z_QUALITY_MIN || optl > Z_QUALITY_MAX) {
                                warnx("resampling quality out of range -- %ld",
                                    optl);
                                return (EINVAL);
                        }
                        gopt->quality = (int)optl;
                        break;
                case 'Q':
                        gopt->verbose = 0;
                        break;
                case 'r':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr, "Supported sampling rate: "
                                    "%d to %d Hz\n",
                                    FEEDRATE_RATEMIN, FEEDRATE_RATEMAX);
#ifdef Z_MASK
                                fprintf(stderr, "(Maximum factor: %d)\n",
                                    Z_MASK);
#endif
                                return (EX_USAGE);
                        }
                        optl = strtol(optarg, NULL, 10);
                        if (optl < FEEDRATE_RATEMIN ||
                            optl > FEEDRATE_RATEMAX) {
                                warnx("sampling rate out of range -- %ld",
                                    optl);
                                return (EINVAL);
                        }
                        iopt->waveinfo.rate = (uint32_t)optl;
                        break;
                case 't':
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Supported file type: raw, wav, cd, dsp\n");
                                return (EX_USAGE);
                        } else if (strcmp(optarg, "raw") == 0)
                                iopt->type = FEEDER_IO_TYPE_RAW;
                        else if (strcmp(optarg, "wav") == 0 ||
                            strcmp(optarg, "wave") == 0)
                                iopt->type = FEEDER_IO_TYPE_WAVE;
                        else if (strcmp(optarg, "cd") == 0) {
                                iopt->type = FEEDER_IO_TYPE_RAW;
                                iopt->afmt = AFMT_S16_LE;
                                iopt->waveinfo.bit = 16;
                                iopt->waveinfo.rate = 44100;
                                iopt->waveinfo.channels = 2;
                        } else if (strcmp(optarg, "dsp") == 0 ||
                            strcmp(optarg, "oss") == 0)
                                iopt->type = FEEDER_IO_TYPE_DSP;
                        else {
                                warnx("unknown file type -- %s", optarg);
                                return (EINVAL);
                        }
                        break;
                case 'v':
                {
                        int left, right;

                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "Volume +/-%%: "
                                    "<left 0-100:<right 0-100>\n");
                                return (EX_USAGE);
                        }
                        i = sscanf(optarg, "%d:%d", &left, &right);
                        if ((i == 1 || i == 2) &&
                            (left >= -100 && left <= 100) && (i == 1 ||
                            (right >= -100 && right <= 100))) {
                                gopt->volume = ((left + 100) << 8);
                                gopt->volume |= ((i == 2) ? right : left) +
                                    100;
                        } else {
                                warnx("invalid volume -- %s", optarg);
                                return (EINVAL);
                        }
                }
                        break;
                case 'n':
                        gopt->afmt_ne = 0;
                        break;
                case 'P':
                        feeder_rate_polyphase_max = INT32_MAX >> 2;
                        break;
                case 'R':
#if defined(Z_ROUNDHZ) && defined(Z_ROUNDHZ_MIN) && defined(Z_ROUNDHZ_MAX)
                        if (strcmp(optarg, "help") == 0) {
                                fprintf(stderr,
                                    "ROUNDHZ min=%d <-> max=%d\n",
                                    Z_ROUNDHZ_MIN, Z_ROUNDHZ_MAX);
                                return (EX_USAGE);
                        }
                        optl = strtol(optarg, NULL, 10);
                        if (optl < Z_ROUNDHZ_MIN || optl > Z_ROUNDHZ_MAX) {
                                warnx("roundhz out of range -- %ld", optl);
                                return (EINVAL);
                        }
                        feeder_rate_round = (int)optl;
#else
                        fprintf(stderr, "ROUNDHZ not supported.\n");
                        return (EX_USAGE);
#endif
                        break;
                case 'S':
                        feeder_rate_polyphase_max = 0;
                        break;
                case 'V':
                        fprintf(stderr, "%s version "
                            __XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH"\n",
                            getprogname());
                        return (EX_USAGE);
                case 'X':
                        iopt->exclusive = O_EXCL;
                        break;
                case 'h':
                case '?':
                default:
                        return (usage());
                        break;
                }
        }

        *argc -= optind;
        *argv += optind;

        if (*argc > 0) {
                iopt->file = *(*argv);
                if (iopt->type == FEEDER_IO_TYPE_UNKNOWN &&
                    iopt->file != NULL && strlen(iopt->file) >= 5 &&
                    strcasecmp(iopt->file + strlen(iopt->file) - 4,
                    ".wav") == 0)
                        iopt->type = FEEDER_IO_TYPE_WAVE;
        }

        if (iopt->file == NULL) {
                warnx("require input/output file argument");
                return (usage());
        }

        return (0);
}

static int
feeder_build_stdin(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;

        cdesc->desc.type = FEEDER_STDIO;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->desc.in;

        f = FEEDER_CREATE(feeder_stdin, &(cdesc->desc));
        if (f == NULL) {
                warnx("failed to create feeder_stdin");
                return (EX_IOERR);
        }

        if (cdesc->current.maxsize != 0 &&
            FEEDER_SET(f, FEEDSTDIO_MAXBYTES, cdesc->current.maxsize) != 0) {
                warnx("failed to set maximum bytes on feeder_stdin -- %u",
                    cdesc->current.maxsize);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_stdout(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;

        cdesc->desc.type = FEEDER_STDIO;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->desc.in;

        f = FEEDER_CREATE(feeder_stdout, &(cdesc->desc));
        if (f == NULL) {
            warnx("could not create stdout feeder");
            return (EX_IOERR);
        }

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_format(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;

        cdesc->desc.type = FEEDER_FORMAT;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->target.afmt;

        f = FEEDER_CREATE(feeder_format, &(cdesc->desc));
        if (f == NULL) {
                warnx("failed to create feeder_format");
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDFORMAT_CHANNELS,
            cdesc->current.channels) != 0) {
                warnx("failed to set channels on feeder_format -- %u",
                    cdesc->current.channels);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        cdesc->current.afmt = cdesc->target.afmt;

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_formatne(struct feeder_chain_desc *cdesc, uint32_t nformat)
{
        uint32_t oafmt, ochannels;
        int ret;

        if (nformat == 0)
                nformat = cdesc->afmt_ne;

        if (nformat == 0 || AFMT_ENCODING(cdesc->current.afmt) == nformat)
                return (0);

        oafmt = cdesc->target.afmt;
        ochannels = cdesc->target.channels;
        cdesc->target.afmt = SND_FORMAT(nformat, cdesc->current.channels,
            AFMT_EXTCHANNEL(cdesc->current.afmt));
        ret = feeder_build_format(cdesc);
        if (ret != 0)
                return (ret);
        cdesc->target.afmt = oafmt;
        cdesc->target.channels = ochannels;

        return (0);
}

static int
matrix_swap(struct pcmchan_matrix *src)
{
        struct pcmchan_matrix dst;
        uint32_t i;

        dst = *src;

        dst.id = SND_CHN_MATRIX_MISC;

        for (i = 0; i < dst.channels; i++)
                dst.map[i] = src->map[src->channels - i - 1];

        for (i = 0; i < (sizeof(dst.offset) / sizeof(dst.offset[0])); i++)
                dst.offset[i] = -1;

        for (i = 0; dst.map[i].type != SND_CHN_T_MAX; i++)
                dst.offset[dst.map[i].type] = i;

        *src = dst;

        return (0);
}

static int
feeder_build_matrix(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;
        int ret;

        ret = feeder_build_formatne(cdesc, 0);
        if (ret != 0)
                return (ret);

        cdesc->desc.type = FEEDER_MATRIX;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = SND_FORMAT(cdesc->current.afmt,
            cdesc->target.channels, AFMT_EXTCHANNEL(cdesc->target.afmt));

        f = FEEDER_CREATE(feeder_matrix, &(cdesc->desc));
        if (f == NULL) {
                warnx("failed to create feeder_matrix");
                return (EX_IOERR);
        }

        if (feeder_matrix_setup(f, &cdesc->m_in, &cdesc->m_out) != 0) {
                warnx("feeder_matrix_setup() failed");
                return (EX_IOERR);
        }

        cdesc->current.channels = cdesc->target.channels;
        cdesc->current.afmt = cdesc->desc.out;
        cdesc->m_in = cdesc->m_out;

        cdesc->matrix_swap = 0;

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_rate(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;
        int ret;

        ret = feeder_build_formatne(cdesc, 0);
        if (ret != 0)
                return (ret);

        cdesc->desc.type = FEEDER_RATE;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->desc.in;

        f = FEEDER_CREATE(feeder_rate, &(cdesc->desc));
        if (f == NULL) {
            warnx("failed to create feeder_rate");
            return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDRATE_SRC, cdesc->current.rate) != 0) {
                warnx("failed to set source rate on feeder_rate -- %u",
                    cdesc->current.rate);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDRATE_DST, cdesc->target.rate) != 0) {
                warnx("failed to set destination rate on feeder_rate "
                    "-- %u", cdesc->target.rate);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDRATE_QUALITY, cdesc->target.quality) != 0) {
                warnx("failed to set resampling quality on feeder_rate "
                    "-- %d", cdesc->target.quality);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDRATE_CHANNELS, cdesc->current.channels) != 0) {
                warnx("failed to set channels on feeder_rate -- %u",
                    cdesc->current.channels);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        cdesc->current.rate = cdesc->target.rate;

#ifdef SND_DIAGNOSTIC
        g_opt.verbose = 0;
#endif

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_volume(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;
        int ret;

        ret = feeder_build_formatne(cdesc, 0);
        if (ret != 0)
                return (ret);

        cdesc->desc.type = FEEDER_VOLUME;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->desc.in;

        f = FEEDER_CREATE(feeder_volume, &(cdesc->desc));
        if (f == NULL) {
                warnx("failed to create feeder_volume");
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDVOLUME_CLASS, SND_VOL_C_PCM) != 0) {
                warnx("failed to set volume class on feeder_volume");
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDVOLUME_CHANNELS, cdesc->current.channels) != 0) {
                warnx("failed to set channels on feeder_volume -- %u",
                    cdesc->current.channels);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        ret = feeder_volume_apply_matrix(f, &cdesc->m_in);
        if (ret != 0)
                return (ret);

        cdesc->use_volume = 0;

        return (chn_addfeeder(cdesc->channel, f));
}

static int
feeder_build_eq(struct feeder_chain_desc *cdesc)
{
        struct pcm_feeder *f;
        int ret;

        ret = feeder_build_formatne(cdesc, 0);
        if (ret != 0)
                return (ret);

        cdesc->desc.type = FEEDER_EQ;
        cdesc->desc.in = cdesc->current.afmt;
        cdesc->desc.out = cdesc->desc.in;

        f = FEEDER_CREATE(feeder_eq, &(cdesc->desc));
        if (f == NULL) {
                warnx("failed to create feeder_eq");
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_CHANNELS, cdesc->current.channels) != 0) {
                warnx("failed to set channels on feeder_eq -- %u",
                    cdesc->current.channels);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_RATE, cdesc->current.rate) != 0) {
                warnx("failed to set rate on feeder_eq -- %u",
                    cdesc->current.rate);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_TREBLE, cdesc->eq.treble) != 0) {
                warnx("failed to set treble on feeder_eq -- %u",
                    cdesc->eq.treble);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_BASS, cdesc->eq.bass) != 0) {
                warnx("failed to set bass on feeder_eq -- %u",
                    cdesc->eq.bass);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_PREAMP, cdesc->eq.preamp) != 0) {
                warnx("failed to set preamp on feeder_eq -- %d",
                    cdesc->eq.preamp);
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        if (FEEDER_SET(f, FEEDEQ_STATE, FEEDEQ_ENABLE) != 0) {
                warnx("failed to enable feeder_eq");
                FEEDER_DESTROY(f);
                return (EX_IOERR);
        }

        cdesc->eq.use = 0;

        return (chn_addfeeder(cdesc->channel, f));
}

static uint32_t feeder_fmtopts[] = {
#if defined(SND_FEEDER_FULL_MULTIFORMAT)
        AFMT_S8, AFMT_U8,
#endif
#if defined(SND_FEEDER_MULTIFORMAT) || defined(SND_FEEDER_FULL_MULTIFORMAT)
        AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
        AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
        AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
#else
        AFMT_S16_NE, AFMT_S32_NE,
#endif
        0
};

#define FEEDER_BW(c, t)         ((c)->t.channels * (c)->t.rate)

#define FEEDRATE_UP(c)          ((c)->target.rate > (c)->current.rate)
#define FEEDRATE_DOWN(c)        ((c)->target.rate < (c)->current.rate)
#define FEEDRATE_REQUIRED(c)    (FEEDRATE_UP(c) || FEEDRATE_DOWN(c))

#define FEEDMATRIX_UP(c)        ((c)->target.channels > (c)->current.channels)
#define FEEDMATRIX_DOWN(c)      ((c)->target.channels < (c)->current.channels)
#define FEEDMATRIX_REQUIRED(c)  (FEEDMATRIX_UP(c) ||                    \
                                 FEEDMATRIX_DOWN(c) ||                  \
                                ((c)->matrix_swap != 0 &&           \
                                 (c)->current.channels > 1))

#define FEEDFORMAT_REQUIRED(c)  (AFMT_ENCODING((c)->current.afmt) !=    \
                                 AFMT_ENCODING((c)->target.afmt))

#define FEEDVOLUME_REQUIRED(c)  ((c)->use_volume != 0)

#define FEEDEQ_VALIDRATE(c, t)  (feeder_eq_validrate((c)->t.rate) != 0)
#define FEEDEQ_ECONOMY(c)       (FEEDER_BW(c, current) < FEEDER_BW(c, target))
#define FEEDEQ_REQUIRED(c)      ((c)->eq.use != 0 &&                 \
                                 FEEDEQ_VALIDRATE(c, current))

#if defined(SND_FEEDER_FULL_MULTIFORMAT)
#define FEEDFORMAT_NE_REQUIRED(c)       ((c)->afmt_ne != AFMT_S_NE)
#elif defined(FEEDER_MULTIFORMAT)
#define FEEDFORMAT_NE_REQUIRED(c)       ((c)->afmt_ne != AFMT_S_NE &&   \
                                        ((c)->current.afmt & AFMT_8BIT))
#else
#define FEEDFORMAT_NE_REQUIRED(c)       ((c)->afmt_ne != AFMT_S_NE &&   \
                                        !(cdesc.current.afmt &          \
                                        (AFMT_S16_NE | AFMT_S32_NE |    \
                                        AFMT_S_NE)))
#endif

static int
feeder_build_chain(struct pcm_channel *c)
{
        struct pcmchan_matrix *m;
        struct feeder_chain_desc cdesc;
        int i, left, right, center, ret;

        cdesc.current.afmt      = feeder_matrix_default_format(
                                      SND_FORMAT(in_opt.afmt,
                                      in_opt.waveinfo.channels,
                                      AFMT_EXTCHANNEL(in_opt.afmt)));
        cdesc.current.channels  = in_opt.waveinfo.channels;
        cdesc.current.rate      = in_opt.waveinfo.rate;

        cdesc.current.quality   = g_opt.quality;
        cdesc.current.maxsize   = 0;
        cdesc.target.afmt       = feeder_matrix_default_format(
                                      SND_FORMAT(out_opt.afmt,
                                      out_opt.waveinfo.channels,
                                      AFMT_EXTCHANNEL(out_opt.afmt)));
        cdesc.target.channels   = out_opt.waveinfo.channels;
        cdesc.target.rate       = out_opt.waveinfo.rate;
        cdesc.target.quality    = g_opt.quality;
        cdesc.target.maxsize    = 0;
        cdesc.channel           = c;

        cdesc.use_volume = 0;
        cdesc.afmt_ne = g_opt.afmt_ne;
        cdesc.matrix_swap = g_opt.matrix_swap;
        cdesc.eq.use = (g_opt.eqtreble != -1 && g_opt.eqbass != -1) ? 1 : 0;
        cdesc.eq.treble = g_opt.eqtreble;
        cdesc.eq.bass = g_opt.eqbass;
        cdesc.eq.preamp = g_opt.eqpreamp;

        cdesc.expensive = g_opt.expensive;

#if !defined(Z_MULTIFORMAT) || Z_MULTIFORMAT == 0
        if (FEEDRATE_REQUIRED(&cdesc))
                cdesc.afmt_ne = AFMT_S_NE;
#endif

        /*{
                static uint32_t keke[] = {
                        SND_FORMAT(S16_LE, 1),
                        SND_FORMAT(S16_LE, 2),
                        SND_FORMAT(S16_LE, 4),
                        SND_FORMAT(S32_LE, 1),
                        SND_FORMAT(S32_LE, 2),
                        SND_FORMAT(S32_LE, 4),
                        SND_FORMAT(S32_LE, 6),
                        SND_FORMAT(S32_LE, 8),
                        0
                };
                uint32_t ff, bst;

                ff = SND_FORMAT(U16_BE, 3);
                bst = chn_fmtbest(ff, keke);
                warnx("0x%08x -> 0x%08x", ff, bst);

        }*/

        if (FEEDFORMAT_NE_REQUIRED(&cdesc)) {
                cdesc.afmt_ne = chn_fmtbest(AFMT_ENCODING(cdesc.target.afmt),
                    feeder_fmtopts);
                if (cdesc.afmt_ne == 0) {
                        warnx("chn_fmtbest failed!");
                        cdesc.afmt_ne = (cdesc.target.afmt &
                            (AFMT_24BIT | AFMT_32BIT)) ?
                            AFMT_S32_NE : AFMT_S16_NE;
                }
        }

        left = (g_opt.volume & 0xff00) >> 8;
        if (left > 100) {
                left = ((left - 100) * (SND_VOL_0DB_MAX - SND_VOL_0DB_PCM)) /
                    100;
                left += SND_VOL_0DB_PCM;
        } else if (left < 100)
                left = (left * SND_VOL_0DB_PCM) / 100;
        else
                left = SND_VOL_0DB_PCM;

        right = g_opt.volume & 0xff;
        if (right > 100) {
                right = ((right - 100) * (SND_VOL_0DB_MAX - SND_VOL_0DB_PCM)) /
                    100;
                right += SND_VOL_0DB_PCM;
        } else if (right < 100)
                right = (right * SND_VOL_0DB_PCM) / 100;
        else
                right = SND_VOL_0DB_PCM;

        center = (left + right) >> 1;

        for (i = 0; i != SND_CHN_T_MAX; i++) {
                if ((1 << i) & SND_CHN_LEFT_MASK)
                        CHN_SETVOLUME(c, SND_VOL_C_PCM, i, left);
                else if ((1 << i) & SND_CHN_RIGHT_MASK)
                        CHN_SETVOLUME(c, SND_VOL_C_PCM, i, right);
                else
                        CHN_SETVOLUME(c, SND_VOL_C_PCM, i, center);
        }

        if (left != SND_VOL_0DB_PCM || right != SND_VOL_0DB_PCM)
                cdesc.use_volume = 1;

        bzero(&(cdesc.desc), sizeof(cdesc.desc));

        if (in_opt.type == FEEDER_IO_TYPE_WAVE)
                cdesc.current.maxsize = in_opt.waveinfo.data_size;

        m = feeder_matrix_format_map(cdesc.current.afmt);
        if (m == NULL)
                return (EINVAL);
        cdesc.m_in = *m;
        m = feeder_matrix_format_map(cdesc.target.afmt);
        if (m == NULL)
                return (EINVAL);
        cdesc.m_out = *m;

        /*{
                struct pcmchan_matrix xx;

                xx = cdesc.m_in;
                xx.channels[0].members &= ~SND_CHN_T_MASK_FC;
                warnx("%d", feeder_matrix_compare(&xx, &cdesc.m_in));
        }*/

        if (cdesc.matrix_swap != 0) {
                if (c->direction == PCMDIR_PLAY) {
                        if (cdesc.target.channels > 1)
                                matrix_swap(&cdesc.m_out);
                        else
                                cdesc.matrix_swap = 0;
                } else {
                        if (cdesc.current.channels > 1)
                                matrix_swap(&cdesc.m_in);
                        else
                                cdesc.matrix_swap = 0;
                }
        }

#define FEEDER_BUILD(t) do {                                            \
        ret = feeder_build_##t(&cdesc);                                 \
        if (ret != 0)                                                  \
                return (ret);                                         \
        } while (0)

        FEEDER_BUILD(stdin);

        if (c->direction == PCMDIR_PLAY)
                goto feeder_chain_pcmdir_play;
        else if (c->direction == PCMDIR_REC)
                goto feeder_chain_pcmdir_rec;
        else
                goto feeder_chain_out;

feeder_chain_pcmdir_play:
        /* PCMDIR_PLAY */
        if (FEEDMATRIX_UP(&cdesc)) {
                if (FEEDEQ_REQUIRED(&cdesc) &&
                    (!FEEDEQ_VALIDRATE(&cdesc, target) ||
                    (cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc))))
                        FEEDER_BUILD(eq);
                if (FEEDRATE_REQUIRED(&cdesc))
                        FEEDER_BUILD(rate);
                FEEDER_BUILD(matrix);
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        } else if (FEEDMATRIX_DOWN(&cdesc)) {
                FEEDER_BUILD(matrix);
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                if (FEEDEQ_REQUIRED(&cdesc) &&
                    (!FEEDEQ_VALIDRATE(&cdesc, target) ||
                    FEEDEQ_ECONOMY(&cdesc)))
                        FEEDER_BUILD(eq);
                if (FEEDRATE_REQUIRED(&cdesc))
                        FEEDER_BUILD(rate);
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        } else {
                if (FEEDRATE_DOWN(&cdesc)) {
                        if (FEEDEQ_REQUIRED(&cdesc) &&
                            !FEEDEQ_VALIDRATE(&cdesc, target)) {
                                if (FEEDVOLUME_REQUIRED(&cdesc))
                                        FEEDER_BUILD(volume);
                                FEEDER_BUILD(eq);
                        }
                        FEEDER_BUILD(rate);
                }
                if (FEEDMATRIX_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrix);
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                if (FEEDRATE_UP(&cdesc)) {
                        if (FEEDEQ_REQUIRED(&cdesc) &&
                            !FEEDEQ_VALIDRATE(&cdesc, target))
                                FEEDER_BUILD(eq);
                        FEEDER_BUILD(rate);
                }
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        }

        goto feeder_chain_out;

feeder_chain_pcmdir_rec:
        /* PCMDIR_REC */
        if (FEEDMATRIX_UP(&cdesc)) {
                if (FEEDEQ_REQUIRED(&cdesc) &&
                    (!FEEDEQ_VALIDRATE(&cdesc, target) ||
                    (cdesc.expensive == 0 && FEEDEQ_ECONOMY(&cdesc))))
                        FEEDER_BUILD(eq);
                if (FEEDRATE_REQUIRED(&cdesc))
                        FEEDER_BUILD(rate);
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                FEEDER_BUILD(matrix);
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        } else if (FEEDMATRIX_DOWN(&cdesc)) {
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                FEEDER_BUILD(matrix);
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                if (FEEDEQ_REQUIRED(&cdesc) &&
                    (!FEEDEQ_VALIDRATE(&cdesc, target) ||
                    FEEDEQ_ECONOMY(&cdesc)))
                        FEEDER_BUILD(eq);
                if (FEEDRATE_REQUIRED(&cdesc))
                        FEEDER_BUILD(rate);
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        } else {
                if (FEEDRATE_DOWN(&cdesc)) {
                        if (FEEDEQ_REQUIRED(&cdesc) &&
                            !FEEDEQ_VALIDRATE(&cdesc, target))
                                FEEDER_BUILD(eq);
                        FEEDER_BUILD(rate);
                }
                if (FEEDMATRIX_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrix);
                /*if (FEEDMATRIX_SWAP_REQUIRED(&cdesc))
                        FEEDER_BUILD(matrixswap);*/
                if (FEEDVOLUME_REQUIRED(&cdesc))
                        FEEDER_BUILD(volume);
                if (FEEDRATE_UP(&cdesc)) {
                        if (FEEDEQ_REQUIRED(&cdesc) &&
                            !FEEDEQ_VALIDRATE(&cdesc, target))
                                FEEDER_BUILD(eq);
                        FEEDER_BUILD(rate);
                }
                if (FEEDEQ_REQUIRED(&cdesc))
                        FEEDER_BUILD(eq);
        }

feeder_chain_out:
        if (FEEDFORMAT_REQUIRED(&cdesc))
                FEEDER_BUILD(format);

        FEEDER_BUILD(stdout);

        return (0);
}

int
main(int argc, char **argv)
{
        uint32_t sec1, sec8;
        int ret;

        if (0)
                afmt_swap_sign(0x00000000);

        bzero(&g_opt, sizeof(g_opt));
        bzero(&in_opt, sizeof(in_opt));
        bzero(&out_opt, sizeof(out_opt));

        g_opt.afmt_ne = AFMT_S_NE;
        g_opt.bufsize = FEEDSTDIO_BUFSIZE;
        g_opt.quality = Z_QUALITY_SINC;
        g_opt.matrix_swap = 0;
        g_opt.verbose = 1;
        g_opt.volume = (100 << 8) | 100;
        g_opt.eqtreble = -1;
        g_opt.eqbass = -1;
        g_opt.eqpreamp = FEEDEQ_PREAMP_DEFAULT;
        g_opt.direction = PCMDIR_PLAY;
        g_opt.expensive = 0;

#define IO_FILENO(x)    fileno((x).waveinfo.fp)

        in_opt.waveinfo.fp = stdin;
        in_opt.latency = 5;
        out_opt.waveinfo.fp = stdout;
        out_opt.latency = 5;

        feeder_rate_polyphase_max = 0;

        ret = parse_options(&argc, &argv, &g_opt, &in_opt);
        if (ret != 0)
                return (ret);

        ret = parse_options(&argc, &argv, &g_opt, &out_opt);
        if (ret != 0)
                return (ret);
        
        if (argc != 1)
                return (usage());

        if (strcmp(in_opt.file, "-") != 0) {
                int fd;

                fd = open(in_opt.file, O_RDONLY | in_opt.exclusive);
                if (fd == -1) {
                        warn("open() file=%s", in_opt.file);
                        return (EX_IOERR);
                }
                in_opt.waveinfo.fp = fdopen(fd, "r");
                if (in_opt.waveinfo.fp == NULL) {
                        warn("fdopen()");
                        close(fd);
                        return (EX_IOERR);
                }
        }

#define FIXUP_IO(o)     do {                                                \
        if ((o).type != FEEDER_IO_TYPE_DSP) {                          \
                struct stat st;                                               \
                int dummy = 0;                                                \
                if (fstat(IO_FILENO(o), &st) == 0 &&                  \
                    S_ISCHR(st.st_mode) != 0 &&                               \
                    ioctl(IO_FILENO(o), SNDCTL_DSP_CHANNELS, &dummy) != \
                    -1)                                                       \
                        (o).type = FEEDER_IO_TYPE_DSP;                  \
        }                                                               \
} while (0)

        FIXUP_IO(in_opt);

        if (in_opt.type == FEEDER_IO_TYPE_UNKNOWN)
                in_opt.type = FEEDER_IO_TYPE_RAW;

        if (in_opt.type == FEEDER_IO_TYPE_RAW &&
            (in_opt.afmt == 0 || in_opt.waveinfo.channels == 0 ||
            in_opt.waveinfo.rate == 0)) {
                warnx("raw io input require format, channels and "
                    "sampling rate arguments");
                if (in_opt.waveinfo.fp != stdin)
                        fclose(in_opt.waveinfo.fp);
                return (usage());
        }

        switch (in_opt.type) {
        case FEEDER_IO_TYPE_WAVE:
                ret = wave_header_read(&(in_opt.waveinfo));
                if (ret != 0) {
                        if (in_opt.waveinfo.fp != stdin)
                                fclose(in_opt.waveinfo.fp);
                        return (ret);
                }
                switch (in_opt.waveinfo.format) {
                case WAVE_FORMAT_PCM:
                case WAVE_FORMAT_EXT:
                        in_opt.afmt = bit2afmt(in_opt.waveinfo.bit);
                        if (in_opt.waveinfo.endian == WAVE_BIG_ENDIAN)
                                in_opt.afmt = afmt_swap_endian(in_opt.afmt);
                        break;
                case WAVE_FORMAT_ALAW:
                        in_opt.afmt = AFMT_A_LAW;
                        break;
                case WAVE_FORMAT_ULAW:
                        in_opt.afmt = AFMT_MU_LAW;
                        break;
                default:
                        warnx("unsupported format -- 0x%04x",
                            in_opt.waveinfo.format);
                        if (in_opt.waveinfo.fp != stdin)
                                fclose(in_opt.waveinfo.fp);
                        return (EFTYPE);
                        break;
                }

                if (in_opt.afmt == 0 ||
                    in_opt.waveinfo.channels < SND_CHN_MIN ||
                    in_opt.waveinfo.channels > SND_CHN_MAX ||
                    in_opt.waveinfo.rate < FEEDRATE_RATEMIN ||
                    in_opt.waveinfo.rate > FEEDRATE_RATEMAX) {
                        warnx("Don't know how to handle wave "
                            "afmt=0x%08x channels=%u rate=%u",
                            in_opt.afmt, in_opt.waveinfo.channels,
                            in_opt.waveinfo.rate);
                        if (in_opt.waveinfo.fp != stdin)
                                fclose(in_opt.waveinfo.fp);
                        return (EFTYPE);
                }

                break;
        case FEEDER_IO_TYPE_DSP:
                if (in_opt.afmt == 0)
                        in_opt.afmt = AFMT_S16_LE;
                if (in_opt.waveinfo.channels == 0)
                        in_opt.waveinfo.channels = 2;
                if (in_opt.waveinfo.rate == 0)
                        in_opt.waveinfo.rate = 44100;
                in_opt.waveinfo.bit = AFMT_BIT(in_opt.afmt);
                if ((in_opt.afmt & (AFMT_24BIT | AFMT_32BIT)) ||
                    (in_opt.waveinfo.channels > 2))
                        in_opt.waveinfo.format = WAVE_FORMAT_EXT;
                else
                        in_opt.waveinfo.format = WAVE_FORMAT_PCM;
                if (ioctl(IO_FILENO(in_opt), SNDCTL_DSP_SETFMT,
                    &(in_opt.afmt)) == -1 ||
                    ioctl(IO_FILENO(in_opt), SNDCTL_DSP_CHANNELS,
                    &(in_opt.waveinfo.channels)) == -1 ||
                    ioctl(IO_FILENO(in_opt), SNDCTL_DSP_SPEED,
                    &(in_opt.waveinfo.rate)) == -1 ||
                    ioctl(IO_FILENO(in_opt), SNDCTL_DSP_POLICY,
                    &(in_opt.latency)) == -1) {
                        warn("DSP ioctl()");
                        fclose(in_opt.waveinfo.fp);
                        return (EX_IOERR);
                }
                break;
        default:
                break;
        }

        if (out_opt.type == FEEDER_IO_TYPE_UNKNOWN)
                out_opt.type = in_opt.type;
        if (out_opt.afmt == 0)
                out_opt.afmt = in_opt.afmt;
        if (out_opt.waveinfo.format == 0)
                out_opt.waveinfo.format = in_opt.waveinfo.format;
        if (out_opt.waveinfo.bit == 0)
                out_opt.waveinfo.bit = in_opt.waveinfo.bit;
        if (out_opt.waveinfo.channels == 0)
                out_opt.waveinfo.channels = in_opt.waveinfo.channels;
        if (out_opt.waveinfo.rate == 0)
                out_opt.waveinfo.rate = in_opt.waveinfo.rate;
        if (out_opt.waveinfo.endian == 0)
                out_opt.waveinfo.endian = in_opt.waveinfo.endian;

        if (g_opt.bufsize < WAVE_BLOCK_ALIGN(&(out_opt.waveinfo)))
                g_opt.bufsize = WAVE_BLOCK_ALIGN(&(out_opt.waveinfo));
        g_opt.bufsize -= g_opt.bufsize % WAVE_BLOCK_ALIGN(&(out_opt.waveinfo));
        pcmchannel = chn_create(g_opt.direction, g_opt.bufsize);
        if (pcmchannel == NULL) {
                warn("chn_create()");
                if (in_opt.waveinfo.fp != stdin)
                        fclose(in_opt.waveinfo.fp);
                return (ENOMEM);
        }
        pcmchannel->in_fp = in_opt.waveinfo.fp;
        ret = feeder_build_chain(pcmchannel);
        if (ret != 0) {
                chn_destroy(pcmchannel);
                return (ret);
        }

        if (strcmp(out_opt.file, "-") != 0) {
                struct stat st;
                int fd;

                if (stat(out_opt.file, &st) == 0 && S_ISCHR(st.st_mode))
                        fd = open(out_opt.file, O_WRONLY | out_opt.exclusive);
                else
                        fd = open(out_opt.file, O_WRONLY | O_CREAT | O_TRUNC);
                if (fd == -1) {
                        warn("open() file=%s", out_opt.file);
                        chn_destroy(pcmchannel);
                        return (EX_IOERR);
                }
                out_opt.waveinfo.fp = fdopen(fd, "w");
                if (out_opt.waveinfo.fp == NULL) {
                        warn("fdopen()");
                        close(fd);
                        chn_destroy(pcmchannel);
                        return (EX_IOERR);
                }
        }
        pcmchannel->out_fp = out_opt.waveinfo.fp;

        signal(SIGINT, sigint_handler);

        FIXUP_IO(out_opt);

        if (out_opt.type == FEEDER_IO_TYPE_DSP) {
                g_opt.bit_perfect ^= 1;
                (void)ioctl(IO_FILENO(out_opt), SNDCTL_DSP_COOKEDMODE,
                    &(g_opt.bit_perfect));
                if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_SETFMT,
                    &(out_opt.afmt)) == -1)
                        warn("DSP ioctl(SETFMT)");
                if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_CHANNELS,
                    &(out_opt.waveinfo.channels)) == -1)
                        warn("DSP ioctl(CHANNELS)");
                if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_SPEED,
                    &(out_opt.waveinfo.rate)) == -1)
                        warn("DSP ioctl(SPEED)");
                if (ioctl(IO_FILENO(out_opt), SNDCTL_DSP_POLICY,
                    &(out_opt.latency)) == -1)
                        warn("DSP ioctl(POLICY)");
                out_opt.type = FEEDER_IO_TYPE_RAW;
                if (0) {
                        unsigned long long chnorder;
                        int xret;

                        chnorder = 0x0000000000000000ULL;
                        xret = ioctl(IO_FILENO(out_opt),
                            SNDCTL_DSP_GET_CHNORDER, &chnorder);
                        warnx("Channel order: %d 0x%016llx", xret, chnorder);
                        /*chnorder = 0x0000000087654321ULL;
                        xret = ioctl(IO_FILENO(out_opt),
                            SNDCTL_DSP_SET_CHNORDER, &chnorder);
                        warnx("Channel order: %d 0x%016llx", xret, chnorder);*/
                }
        }

        if (out_opt.type == FEEDER_IO_TYPE_WAVE) {
                out_opt.waveinfo.seekable = 0;
                out_opt.waveinfo.data_size = WAVE_DATA_SIZE_BIG;
                (void)wave_header_write(&(out_opt.waveinfo));
                out_opt.waveinfo.data_size = 0;
                if (ftell(out_opt.waveinfo.fp) == -1) {
                        warn("impossible to fix wave header");
                        out_opt.type = FEEDER_IO_TYPE_RAW;
                } else
                        out_opt.running = 1;
        }

        sec1 = out_opt.waveinfo.rate * out_opt.waveinfo.channels *
            (out_opt.waveinfo.bit >> 3);
        if (sec1 < 1)
                sec1 = 1;
        sec8 = sec1 >> 3;
        if (sec8 < 1)
                sec8 = 1;

        if (g_opt.verbose != 0) {
#define VOL_LEFT()      ((g_opt.volume >> 8) & 0xff)
#define VOL_RIGHT()     (g_opt.volume & 0xff)

                fprintf(stderr, "\n            Files: %s -> %s\n",
                    in_opt.file, out_opt.file);
                fprintf(stderr, "       Conversion: %s (%u Hz) -> %s (%u Hz)\n",
                    afmt2sfmt(in_opt.afmt), in_opt.waveinfo.rate,
                    afmt2sfmt(out_opt.afmt), out_opt.waveinfo.rate);
                fprintf(stderr, "         Channels: %u -> %u\n",
                    in_opt.waveinfo.channels, out_opt.waveinfo.channels);
                if (VOL_LEFT() != 100 || VOL_RIGHT() != 100)
                        fprintf(stderr, " Gain/Attenuation: L %d%% / R %d%%\n",
                            VOL_LEFT() - 100, VOL_RIGHT() - 100);
                if (g_opt.eqtreble != -1 || g_opt.eqbass != -1) {
                        fprintf(stderr, "      Treble/Bass: %d%% / %d%%\n",
                            g_opt.eqtreble, g_opt.eqbass);
                        if (g_opt.eqpreamp != FEEDEQ_PREAMP_DEFAULT)
                                fprintf(stderr,
                                    "        EQ Preamp: %c%d.%ddB\n",
                                    FEEDEQ_PREAMP_SIGNMARK(g_opt.eqpreamp),
                                    FEEDEQ_PREAMP_IPART(g_opt.eqpreamp),
                                    FEEDEQ_PREAMP_FPART(g_opt.eqpreamp));
                }
                if (in_opt.waveinfo.rate != out_opt.waveinfo.rate)
                        fprintf(stderr, " Resample Quality: %d (%s)\n",
                            g_opt.quality, (g_opt.quality == 0) ?
                            "zero-order hold" : ((g_opt.quality < 2) ?
                            "linear interpolation" : "sinc interpolation"));
                fprintf(stderr, "\n");
        }
                
        feeder_benchmark_start();

        while ((ret = chn_feed(pcmchannel)) != 0) {
                if (g_opt.verbose != 0) {
                        static const char throbber[4] = "-\\|/";
                        static uint32_t idx = 0, cnt1 = 0, cnt2 = 0;
                        static uint32_t sc = 0;

#define print_progress()        fprintf(stderr,                                \
                                    "         Progress: [%10u Bytes] "      \
                                    "%c [Time: %02u:%02u:%02u]\r",  \
                                    out_opt.waveinfo.data_size,         \
                                    throbber[idx],                      \
                                    sc / (60 * 60), (sc / 60) % 60, \
                                    sc % 60)

                        if (out_opt.waveinfo.data_size == 0)
                                print_progress();

                        cnt1 += ret;
                        cnt2 += ret;

                        if (cnt1 >= sec1) {
                                cnt1 %= sec1;
                                sc++;
                                print_progress();
                        }

                        if (cnt2 >= sec8) {
                                cnt2 %= sec8;
                                idx++;
                                idx &= 3;
                                print_progress();
                        }
                }
                out_opt.waveinfo.data_size += ret;
        }

        if (g_opt.verbose != 0)
                fprintf(stderr, "\n");

        feeder_benchmark_stop();

        if (out_opt.type == FEEDER_IO_TYPE_WAVE) {
                out_opt.waveinfo.seekable = 1;
                (void)wave_header_write(&(out_opt.waveinfo));
                out_opt.running = 0;
        }

        chn_destroy(pcmchannel);
        pcmchannel = NULL;

        return (EX_OK);
}