/*
 * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
 *
 * sinesrc.c: An elemnt emitting a sine src in lots of different formats
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "sinesrc.h"
#include <math.h>
#include <string.h>             /* memcpy */

#define SAMPLES_PER_WAVE 200

static GstStaticPadTemplate sinesrc_src_factory =
    GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw-int, "
        "endianness = (int) { LITTLE_ENDIAN, BIG_ENDIAN }, "
        "signed = (boolean) { FALSE, TRUE }, "
        "width = (int) [8, 32], "
        "depth = (int) [8, 32], "
        "rate = (int) [8000, 192000], "
        "channels = (int) [1, 16];"
        "audio/x-raw-float, "
        "endianness = (int) BYTE_ORDER, "
        "width = (int) {32, 64}, "
        "rate = (int) [8000, 192000], " "channels = (int) [1, 16]")
    );

static GstElementClass *parent_class = NULL;

static void sinesrc_init (SineSrc * src);
static void sinesrc_class_init (SineSrcClass * klass);

static GstData *sinesrc_get (GstPad * pad);
static GstStateChangeReturn sinesrc_change_state (GstElement * element,
    GstStateChange transition);


GType
sinesrc_get_type (void)
{
  static GType sinesrc_type = 0;

  if (!sinesrc_type) {
    static const GTypeInfo sinesrc_info = {
      sizeof (SineSrcClass), NULL, NULL,
      (GClassInitFunc) sinesrc_class_init, NULL, NULL,
      sizeof (SineSrc), 0,
      (GInstanceInitFunc) sinesrc_init,
    };

    sinesrc_type = g_type_register_static (GST_TYPE_ELEMENT, "SineSrc",
        &sinesrc_info, 0);
  }
  return sinesrc_type;
}
static void
sinesrc_class_init (SineSrcClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  element_class->change_state = sinesrc_change_state;

  parent_class = g_type_class_peek_parent (klass);
}

static void
sinesrc_init (SineSrc * src)
{
  src->src =
      gst_pad_new_from_template (gst_static_pad_template_get
      (&sinesrc_src_factory), "src");
  gst_element_add_pad (GST_ELEMENT (src), src->src);
  gst_pad_set_get_function (src->src, sinesrc_get);

  src->width = 16;
  src->depth = 16;
  src->sign = TRUE;
  src->endianness = G_BYTE_ORDER;
  src->rate = 44100;
  src->channels = 1;
  src->type = SINE_SRC_INT;
  src->newcaps = TRUE;

  src->pre_get_func = NULL;

  GST_OBJECT (src)->name = "sinesrc";
}

static void
sinesrc_force_caps (SineSrc * src)
{
  GstCaps *caps;

  if (!src->newcaps)
    return;

  src->newcaps = FALSE;

  switch (src->type) {
    case SINE_SRC_INT:
      caps = gst_caps_new_simple ("audio/x-raw-int",
          "signed", G_TYPE_BOOLEAN, src->sign,
          "depth", G_TYPE_INT, src->depth, NULL);
      if (src->width > 8)
        gst_caps_set_simple (caps,
            "endianness", G_TYPE_INT, src->endianness, NULL);
      break;
    case SINE_SRC_FLOAT:
      g_assert (src->width == 32 || src->width == 64);
      caps = gst_caps_new_simple ("audio/x-raw-float",
          "endianness", G_TYPE_INT, src->endianness, NULL);
      break;
    default:
      caps = NULL;
      g_assert_not_reached ();
  }
  gst_caps_set_simple (caps,
      "width", G_TYPE_INT, src->width,
      "rate", G_TYPE_INT, src->rate,
      "channels", G_TYPE_INT, src->channels, NULL);

  if (gst_pad_try_set_caps (src->src, caps) != GST_PAD_LINK_OK)
    g_assert_not_reached ();
}

/* always return 1 wave 
 * there are 200 waves in 1 second, so the frequency is samplerate/200
 */
static guint8
UIDENTITY (guint8 x)
{
  return x;
};
static gint8
IDENTITY (gint8 x)
{
  return x;
};

#define POPULATE(format, be_func, le_func) G_STMT_START {\
  format val = (format) int_value;\
  format *p = data;\
  switch (src->endianness) {\
    case G_LITTLE_ENDIAN:\
      val = le_func (val);\
      break;\
    case G_BIG_ENDIAN:\
      val = be_func (val);\
      break;\
    default: \
      g_assert_not_reached ();\
  };\
  for (j = 0; j < src->channels; j++) {\
    *p = val;\
    p ++;\
  }\
  data = p;\
} G_STMT_END

static GstData *
sinesrc_get (GstPad * pad)
{
  GstBuffer *buf;
  SineSrc *src;

  void *data;
  gint i, j;
  gdouble value;

  g_return_val_if_fail (pad != NULL, NULL);
  src = SINESRC (gst_pad_get_parent (pad));

  if (src->pre_get_func)
    src->pre_get_func (src);

  buf = gst_buffer_new_and_alloc ((src->width / 8) * src->channels *
      SAMPLES_PER_WAVE);
  g_assert (buf);
  data = GST_BUFFER_DATA (buf);
  g_assert (data);

  for (i = 0; i < SAMPLES_PER_WAVE; i++) {
    value = sin (i * 2 * M_PI / SAMPLES_PER_WAVE);
    switch (src->type) {
      case SINE_SRC_INT:{
        gint64 int_value =
            (value + (src->sign ? 0 : 1)) * (((guint64) 1) << (src->depth - 1));
        if (int_value ==
            (1 + (src->sign ? 0 : 1)) * (((guint64) 1) << (src->depth - 1)))
          int_value--;
        switch (src->width) {
          case 8:
            if (src->sign)
              POPULATE (gint8, IDENTITY, IDENTITY);
            else
              POPULATE (guint8, UIDENTITY, UIDENTITY);
            break;
          case 16:
            if (src->sign)
              POPULATE (gint16, GINT16_TO_BE, GINT16_TO_LE);
            else
              POPULATE (guint16, GUINT16_TO_BE, GUINT16_TO_LE);
            break;
          case 24:
            if (src->sign) {
              gpointer p;
              gint32 val = (gint32) int_value;

              switch (src->endianness) {
                case G_LITTLE_ENDIAN:
                  val = GINT32_TO_LE (val);
                  break;
                case G_BIG_ENDIAN:
                  val = GINT32_TO_BE (val);
                  break;
                default:
                  g_assert_not_reached ();
              };
              p = &val;
              if (src->endianness == G_BIG_ENDIAN)
                p++;
              for (j = 0; j < src->channels; j++) {
                memcpy (data, p, 3);
                data += 3;
              }
            } else {
              gpointer p;
              guint32 val = (guint32) int_value;

              switch (src->endianness) {
                case G_LITTLE_ENDIAN:
                  val = GUINT32_TO_LE (val);
                  break;
                case G_BIG_ENDIAN:
                  val = GUINT32_TO_BE (val);
                  break;
                default:
                  g_assert_not_reached ();
              };
              p = &val;
              if (src->endianness == G_BIG_ENDIAN)
                p++;
              for (j = 0; j < src->channels; j++) {
                memcpy (data, p, 3);
                data += 3;
              }
            }
            break;
          case 32:
            if (src->sign)
              POPULATE (gint32, GINT32_TO_BE, GINT32_TO_LE);
            else
              POPULATE (guint32, GUINT32_TO_BE, GUINT32_TO_LE);
            break;
          default:
            g_assert_not_reached ();
        }
        break;
      }
      case SINE_SRC_FLOAT:
        if (src->width == 32) {
          gfloat *p = (gfloat *) data;
          gfloat fval = (gfloat) value;

          for (j = 0; j < src->channels; j++) {
            *p = fval;
            p++;
          }
          data = p;
          break;
        }
        if (src->width == 64) {
          gdouble *p = (gdouble *) data;

          for (j = 0; j < src->channels; j++) {
            *p = value;
            p++;
          }
          data = p;
          break;
        }
        g_assert_not_reached ();
      default:
        g_assert_not_reached ();
    }
  }

  if (src->newcaps) {
    sinesrc_force_caps (src);
  }
  return GST_DATA (buf);
}

GstElement *
sinesrc_new (void)
{
  return GST_ELEMENT (g_object_new (TYPE_SINESRC, NULL));
}

void
sinesrc_set_pre_get_func (SineSrc * src, PreGetFunc func)
{
  src->pre_get_func = func;
}

static GstStateChangeReturn
sinesrc_change_state (GstElement * element, GstStateChange transition)
{
  SineSrc *sinesrc;

  g_return_val_if_fail (element != NULL, FALSE);
  sinesrc = SINESRC (element);

  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
    case GST_STATE_CHANGE_READY_TO_PAUSED:
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      sinesrc->newcaps = TRUE;
      break;
    case GST_STATE_CHANGE_READY_TO_NULL:
      break;
    default:
      g_assert_not_reached ();
  }

  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  return GST_STATE_CHANGE_SUCCESS;
}