/* libSoX effect: remix   Copyright (c) 2008-9 robs@users.sourceforge.net
 *
 * This library 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 library 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 library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "sox_i.h"

typedef struct {
  enum {semi, automatic, manual} mode;
  sox_bool mix_power;
  unsigned num_out_channels, min_in_channels;
  struct {
    char * str;          /* Command-line argument to parse for this out_spec */
    unsigned num_in_channels;
    struct in_spec {
      unsigned channel_num;
      double   multiplier;
    } * in_specs;
  } * out_specs;
} priv_t;

#define PARSE(SEP, SCAN, VAR, MIN, SEPARATORS) do {\
  end = strpbrk(text, SEPARATORS); \
  if (end == text) \
    SEP = *text++; \
  else { \
    SEP = (SEPARATORS)[strlen(SEPARATORS) - 1]; \
    n = sscanf(text, SCAN"%c", &VAR, &SEP); \
    if (n == 0 || VAR < MIN || (n == 2 && !strchr(SEPARATORS, SEP))) { \
      lsx_fail("cannot parse `%s'", text); \
      return SOX_EOF; \
    } \
    text = end? end + 1 : text + strlen(text); \
  } \
} while (0)

static int parse(sox_effect_t * effp, char * * argv, unsigned channels)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned i, j;
  double mult;

  p->min_in_channels = 0;
  for (i = 0; i < p->num_out_channels; ++i) {
    sox_bool mul_spec = sox_false;
    char * text, * end;
    if (argv) /* 1st parse only */
      p->out_specs[i].str = lsx_strdup(argv[i]);
    for (j = 0, text = p->out_specs[i].str; *text;) {
      static char const separators[] = "-vpi,";
      char sep1, sep2;
      int chan1 = 1, chan2 = channels, n;
      double multiplier = HUGE_VAL;

      PARSE(sep1, "%i", chan1, 0, separators);
      if (!chan1) {
       if (j || *text) {
         /* in-spec 1,0 (j!=0) or 0-something (*text) */
         lsx_fail("the silent channel 0 cannot be part of a range");
         return SOX_EOF;
       }
       continue;
      }
      if (sep1 == '-') {
        PARSE(sep1, "%i", chan2, 0, separators + 1);
        if (!chan2) {
          /* in-spec 1-0 */
          lsx_fail("the silent channel 0 cannot be part of a range");
          return SOX_EOF;
        }
      }
      else chan2 = chan1;
      if (sep1 != ',') {
        multiplier = sep1 == 'v' ? 1 : 0;
        PARSE(sep2, "%lf", multiplier, -HUGE_VAL, separators + 4);
        if (sep1 != 'v')
          multiplier = (sep1 == 'p'? 1 : -1) * dB_to_linear(multiplier);
        mul_spec = sox_true;
      }
      if (chan2 < chan1) {int t = chan1; chan1 = chan2; chan2 = t;}
      p->out_specs[i].in_specs = lsx_realloc(p->out_specs[i].in_specs,
          (j + chan2 - chan1 + 1) * sizeof(*p->out_specs[i].in_specs));
      while (chan1 <= chan2) {
        p->out_specs[i].in_specs[j].channel_num = chan1++ - 1;
        p->out_specs[i].in_specs[j++].multiplier = multiplier;
      }
      p->min_in_channels = max(p->min_in_channels, (unsigned)chan2);
    }
    p->out_specs[i].num_in_channels = j;
    mult = 1. / (p->mix_power? sqrt((double)j) : j);
    for (j = 0; j < p->out_specs[i].num_in_channels; ++j)
      if (p->out_specs[i].in_specs[j].multiplier == HUGE_VAL)
        p->out_specs[i].in_specs[j].multiplier = (p->mode == automatic || (p->mode == semi && !mul_spec)) ? mult : 1;
  }
  effp->out_signal.channels = p->num_out_channels;
  return SOX_SUCCESS;
}

static int show(priv_t *p)
{
  unsigned i, j;

  for (j = 0; j < p->num_out_channels; j++) {
    lsx_debug("%i: ", j);
    for (i = 0; i < p->out_specs[j].num_in_channels; i++)
      lsx_debug("\t%i %g", p->out_specs[j].in_specs[i].channel_num, p->out_specs[j].in_specs[i].multiplier);
  }
  return SOX_SUCCESS;
}

static int create_remix(sox_effect_t * effp, int argc, char * * argv)
{
  priv_t * p = (priv_t *)effp->priv;
  --argc, ++argv;
  if (argc && !strcmp(*argv, "-m")) p->mode = manual   , ++argv, --argc;
  if (argc && !strcmp(*argv, "-a")) p->mode = automatic, ++argv, --argc;
  if (argc && !strcmp(*argv, "-p")) p->mix_power = sox_true, ++argv, --argc;
  if (!argc) {
    lsx_fail("must specify at least one output channel");
    return SOX_EOF;
  }
  p->num_out_channels = argc;
  lsx_vcalloc(p->out_specs, p->num_out_channels);
  return parse(effp, argv, 1); /* No channels yet; parse with dummy */
}

static int start_remix(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  double max_sum = 0;
  unsigned i, j;
  int non_integer = 0;

  parse(effp, NULL, effp->in_signal.channels);
  if (effp->in_signal.channels < p->min_in_channels) {
    lsx_fail("too few input channels");
    return SOX_EOF;
  }

  for (j = 0; j < effp->out_signal.channels; j++) {
    double sum = 0;
    for (i = 0; i < p->out_specs[j].num_in_channels; i++) {
      double mult = p->out_specs[j].in_specs[i].multiplier;
      sum += fabs(mult);
      non_integer += floor(mult) != mult;
    }
    max_sum = max(max_sum, sum);
  }
  if (effp->in_signal.mult && max_sum > 1)
    *effp->in_signal.mult /= max_sum;
  if (!non_integer)
    effp->out_signal.precision = effp->in_signal.precision;
  else
    effp->out_signal.precision = SOX_SAMPLE_PRECISION;
  show(p);
  return SOX_SUCCESS;
}

static int flow_remix(sox_effect_t * effp, const sox_sample_t * ibuf,
    sox_sample_t * obuf, size_t * isamp, size_t * osamp)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned i, j, len;
  len =  min(*isamp / effp->in_signal.channels, *osamp / effp->out_signal.channels);
  *isamp = len * effp->in_signal.channels;
  *osamp = len * effp->out_signal.channels;

  for (; len--; ibuf += effp->in_signal.channels) for (j = 0; j < effp->out_signal.channels; j++) {
    double out = 0;
    for (i = 0; i < p->out_specs[j].num_in_channels; i++)
      out += ibuf[p->out_specs[j].in_specs[i].channel_num] * p->out_specs[j].in_specs[i].multiplier;
    *obuf++ = SOX_ROUND_CLIP_COUNT(out, effp->clips);
  }
  return SOX_SUCCESS;
}

static int kill_remix(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned i;
  for (i = 0; i < p->num_out_channels; ++i) {
    free(p->out_specs[i].str);
    free(p->out_specs[i].in_specs);
  }
  free(p->out_specs);
  return SOX_SUCCESS;
}

sox_effect_handler_t const * lsx_remix_effect_fn(void)
{
  static char const usage[] =
    "[-a|-m] [-p] <0|in-chan[(v|p|i)[volume]]{,in-chan[(v|p|i)[volume]]}>";
  static char const * const extra_usage[] = {
    "-a  Automatically adjust channel levels",
    "-m  Disable all automatic level adjustments",
    "-p  Use power (1/sqrt(n)) scaling",
    "0   A silent channel",
    "p   Power adjustment in dB (0 is no change)",
    "i   Power adjustment in dB, inverted",
    "v   Voltage multiplier (1 is no change)",
    NULL
  };
  static sox_effect_handler_t handler = {
    "remix", usage, extra_usage,
    SOX_EFF_MCHAN | SOX_EFF_CHAN | SOX_EFF_GAIN | SOX_EFF_PREC,
    create_remix, start_remix, flow_remix, NULL, NULL, kill_remix,
    sizeof(priv_t)
  };
  return &handler;
}

/*----------------------- The `channels' effect alias ------------------------*/

static int create_channels(sox_effect_t * effp, int argc, char * * argv)
{
  priv_t * p = (priv_t *)effp->priv;
  char dummy;     /* To check for extraneous chars. */

  if (argc == 2) {
    if (sscanf(argv[1], "%d %c", (int *)&p->num_out_channels, &dummy) != 1) {
      lsx_fail("invalid number of channels `%s'", argv[1]);
      return SOX_EOF;
    }
    if ((int)p->num_out_channels <= 0) {
      lsx_fail("number of channels must be 1 or more");
      return SOX_EOF;
    }

    effp->out_signal.channels = p->num_out_channels;
  }
  else if (argc != 1)
    return lsx_usage(effp);
  return SOX_SUCCESS;
}

static int start_channels(sox_effect_t * effp)
{
  priv_t * p = (priv_t *)effp->priv;
  unsigned num_out_channels = p->num_out_channels != 0 ?
      p->num_out_channels : effp->out_signal.channels;
  unsigned i, j;

  lsx_vcalloc(p->out_specs, num_out_channels);
  if (effp->in_signal.channels == num_out_channels)
    return SOX_EFF_NULL;

  if (effp->in_signal.channels > num_out_channels) {
    for (j = 0; j < num_out_channels; j++) {
      unsigned in_per_out = (effp->in_signal.channels +
          num_out_channels - 1 - j) / num_out_channels;
      lsx_valloc(p->out_specs[j].in_specs, in_per_out);
      p->out_specs[j].num_in_channels = in_per_out;
      for (i = 0; i < in_per_out; ++i) {
        p->out_specs[j].in_specs[i].channel_num = i * num_out_channels + j;
        p->out_specs[j].in_specs[i].multiplier = 1. / in_per_out;
      }
    }
  }
  else for (j = 0; j < num_out_channels; j++) {
    lsx_valloc(p->out_specs[j].in_specs, 1);
    p->out_specs[j].num_in_channels = 1;
    p->out_specs[j].in_specs[0].channel_num = j % effp->in_signal.channels;
    p->out_specs[j].in_specs[0].multiplier = 1;
  }
  effp->out_signal.channels = p->num_out_channels = num_out_channels;
  effp->out_signal.precision = (effp->in_signal.channels > num_out_channels) ?
    SOX_SAMPLE_PRECISION : effp->in_signal.precision;
  show(p);
  return SOX_SUCCESS;
}

sox_effect_handler_t const * lsx_channels_effect_fn(void)
{
  static sox_effect_handler_t handler;
  handler = *lsx_remix_effect_fn();
  handler.name = "channels";
  handler.usage = "number";
  handler.extra_usage = NULL;
  handler.flags &= ~SOX_EFF_GAIN;
  handler.getopts = create_channels;
  handler.start = start_channels;
  return &handler;
}

/*------------------------- The `oops' effect alias --------------------------*/

static int oops_getopts(sox_effect_t *effp, int argc, char **argv)
{
  char *args[] = {0, "1,2i", "1,2i"};
  args[0] = argv[0];
  return --argc? lsx_usage(effp) : create_remix(effp, 3, args);
}

sox_effect_handler_t const * lsx_oops_effect_fn(void)
{
  static sox_effect_handler_t handler;
  handler = *lsx_remix_effect_fn();
  handler.name = "oops";
  handler.usage = NULL;
  handler.extra_usage = NULL;
  handler.getopts = oops_getopts;
  return &handler;
}
