/* GStreamer ReplayGain analysis
 *
 * Copyright (C) 2006 Rene Stadler <mail@renestadler.de>
 *
 * rganalysis.c: Unit test for the rganalysis element
 *
 * 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
 */

/* Some things to note about the RMS window length of the analysis
 * algorithm and thus the implementation used in the element:
 * Processing divides input data into 50ms windows at some point.
 * Some details about this that normally do not matter:
 *
 *  1. At the end of a stream, the remainder of data that did not fill
 *     up the last 50ms window is simply discarded.
 *
 *  2. If the sample rate changes during a stream, the currently
 *     running window is discarded and the equal loudness filter gets
 *     reset as if a new stream started.
 *
 *  3. For the album gain, it is not entirely correct to think of
 *     obtaining it like "as if all the tracks are analyzed as one
 *     track".  There isn't a separate window being tracked for album
 *     processing, so at stream (track) end, the remaining unfilled
 *     window does not contribute to the album gain either.
 *
 *  4. If a waveform with a result gain G is concatenated to itself
 *     and the result processed as a track, the gain can be different
 *     from G if and only if the duration of the original waveform is
 *     not an integer multiple of 50ms.  If the original waveform gets
 *     processed as a single track and then the same data again as a
 *     subsequent track, the album result gain will always match G
 *     (this is implied by 3.).
 *
 *  5. A stream shorter than 50ms cannot be analyzed.  At 8000 and
 *     48000 Hz, this corresponds to 400 resp. 2400 frames.  If a
 *     stream is shorter than 50ms, the element will not generate tags
 *     at EOS (only if an album finished, but only album tags are
 *     generated then).  This is not an erroneous condition, the
 *     element should behave normally.
 *
 * The limitations outlined in 1.-4. do not apply to the peak values.
 * Every single sample is accounted for when looking for the peak.
 * Thus the album peak is guaranteed to be the maximum value of all
 * track peaks.
 *
 * In normal day-to-day use, these little facts are unlikely to be
 * relevant, but they have to be kept in mind for writing the tests
 * here.
 */

#include <gst/check/gstcheck.h>

GList *buffers = NULL;

/* For ease of programming we use globals to keep refs for our floating
 * src and sink pads we create; otherwise we always have to do get_pad,
 * get_peer, and then remove references in every test function */
static GstPad *mysrcpad, *mysinkpad;

/* Mapping from supported sample rates to the correct result gain for
 * the following test waveform: 20 * 512 samples with a quarter-full
 * amplitude of toggling sign, changing every 48 samples and starting
 * with the positive value.
 *
 * Even if we would generate a wave describing a signal with the same
 * frequency at each sampling rate, the results would vary (slightly).
 * Hence the simple generation method, since we cannot use a constant
 * value as expected result anyways.  For all sample rates, changing
 * the sign every 48 frames gives a sane frequency.  Buffers
 * containing data that forms such a waveform is created using the
 * test_buffer_square_{float,int16}_{mono,stereo} functions below.
 *
 * The results have been checked against what the metaflac and
 * wavegain programs generate for such a stream.  If you want to
 * verify these, be sure that the metaflac program does not produce
 * incorrect results in your environment: I found a strange bug in the
 * (defacto) reference code for the analysis that sometimes leads to
 * incorrect RMS window lengths. */

struct rate_test
{
  guint sample_rate;
  gdouble gain;
};

static const struct rate_test supported_rates[] = {
  8000, -0.91,
  11025, -2.80,
  12000, -3.13,
  16000, -4.26,
  22050, -5.64,
  24000, -5.87,
  32000, -6.03,
  44100, -6.20,
  48000, -6.14
};

/* Lookup the correct gain adjustment result in above array. */

static gdouble
get_expected_gain (guint sample_rate)
{
  gint i;

  for (i = G_N_ELEMENTS (supported_rates); i--;)
    if (supported_rates[i].sample_rate == sample_rate)
      return supported_rates[i].gain;
  g_return_val_if_reached (0.0);
}

#define SILENCE_GAIN 64.82

#define REPLAY_GAIN_CAPS                                \
  "channels = (int) { 1, 2 }, "                         \
  "rate = (int) { 8000, 11025, 12000, 16000, 22050, "   \
  "24000, 32000, 44100, 48000 }"

#define RG_ANALYSIS_CAPS_TEMPLATE_STRING  \
  "audio/x-raw-float, "                   \
  "width = (int) 32, "                    \
  "endianness = (int) BYTE_ORDER, "       \
  REPLAY_GAIN_CAPS                        \
  "; "                                    \
  "audio/x-raw-int, "                     \
  "width = (int) 16, "                    \
  "depth = (int) [ 1, 16 ], "             \
  "signed = (boolean) true, "             \
  "endianness = (int) BYTE_ORDER, "       \
  REPLAY_GAIN_CAPS

static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (RG_ANALYSIS_CAPS_TEMPLATE_STRING)
    );
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (RG_ANALYSIS_CAPS_TEMPLATE_STRING)
    );

GstElement *
setup_rganalysis ()
{
  GstElement *analysis;
  GstBus *bus;

  GST_DEBUG ("setup_rganalysis");
  analysis = gst_check_setup_element ("rganalysis");
  mysrcpad = gst_check_setup_src_pad (analysis, &srctemplate, NULL);
  mysinkpad = gst_check_setup_sink_pad (analysis, &sinktemplate, NULL);
  gst_pad_set_active (mysrcpad, TRUE);
  gst_pad_set_active (mysinkpad, TRUE);

  bus = gst_bus_new ();
  gst_element_set_bus (analysis, bus);
  /* gst_element_set_bus does not steal a reference. */
  gst_object_unref (bus);

  return analysis;
}

void
cleanup_rganalysis (GstElement * element)
{
  GST_DEBUG ("cleanup_rganalysis");

  g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (buffers);
  buffers = NULL;

  /* The bus owns references to the element: */
  gst_element_set_bus (element, NULL);

  gst_pad_set_active (mysrcpad, FALSE);
  gst_pad_set_active (mysinkpad, FALSE);
  gst_check_teardown_src_pad (element);
  gst_check_teardown_sink_pad (element);
  gst_check_teardown_element (element);
}

static void
set_playing_state (GstElement * element)
{
  fail_unless (gst_element_set_state (element,
          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
      "Could not set state to PLAYING");
}

static void
send_eos_event (GstElement * element)
{
  GstBus *bus = gst_element_get_bus (element);
  GstPad *pad = gst_element_get_pad (element, "sink");
  GstEvent *event = gst_event_new_eos ();

  fail_unless (gst_pad_send_event (pad, event),
      "Cannot send EOS event: Not handled.");

  /* There is no sink element, so _we_ post the EOS message on the bus
   * here.  Of course we generate any EOS ourselves, but this allows
   * us to poll for the EOS message in poll_eos if we expect the
   * element to _not_ generate a TAG message.  That's better than
   * waiting for a timeout to lapse. */
  fail_unless (gst_bus_post (bus, gst_message_new_eos (NULL)));

  gst_object_unref (bus);
  gst_object_unref (pad);
}

static void
send_tag_event (GstElement * element, GstTagList * tag_list)
{
  GstPad *pad = gst_element_get_pad (element, "sink");
  GstEvent *event = gst_event_new_tag (tag_list);

  fail_unless (gst_pad_send_event (pad, event),
      "Cannot send TAG event: Not handled.");

  gst_object_unref (pad);
}

static void
poll_eos (GstElement * element)
{
  GstBus *bus = gst_element_get_bus (element);
  GstMessage *message;

  message = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_TAG, GST_SECOND);
  fail_unless (message != NULL, "Could not poll for EOS message: Timed out");
  fail_unless (message->type == GST_MESSAGE_EOS,
      "Could not poll for eos message: got message of type %s instead",
      gst_message_type_get_name (message->type));

  gst_message_unref (message);
  gst_object_unref (bus);
}

/* This also polls for EOS since the TAG message comes right before
 * the end of streams. */

static GstTagList *
poll_tags (GstElement * element)
{
  GstBus *bus = gst_element_get_bus (element);
  GstTagList *tag_list;
  GstMessage *message;

  message = gst_bus_poll (bus, GST_MESSAGE_TAG, GST_SECOND);
  fail_unless (message != NULL, "Could not poll for TAG message: Timed out");

  fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (element));

  gst_message_parse_tag (message, &tag_list);
  gst_message_unref (message);
  gst_object_unref (bus);

  poll_eos (element);

  return tag_list;
}

#define MATCH_PEAK(p1, p2) ((p1 < p2 + 1e-6) && (p2 < p1 + 1e-6))
#define MATCH_GAIN(g1, g2) ((g1 < g2 + 1e-13) && (g2 < g1 + 1e-13))

static void
fail_unless_track_gain (const GstTagList * tag_list, gdouble gain)
{
  gdouble result;

  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &result),
      "Tag list contains no track gain value");
  fail_unless (MATCH_GAIN (gain, result),
      "Track gain %+.2f does not match, expected %+.2f", result, gain);
}

static void
fail_unless_track_peak (const GstTagList * tag_list, gdouble peak)
{
  gdouble result;

  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &result),
      "Tag list contains no track peak value");
  fail_unless (MATCH_PEAK (peak, result),
      "Track peak %f does not match, expected %f", result, peak);
}

static void
fail_unless_album_gain (const GstTagList * tag_list, gdouble gain)
{
  gdouble result;

  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &result),
      "Tag list contains no album gain value");
  fail_unless (MATCH_GAIN (result, gain),
      "Album gain %+.2f does not match, expected %+.2f", result, gain);
}

static void
fail_unless_album_peak (const GstTagList * tag_list, gdouble peak)
{
  gdouble result;

  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &result),
      "Tag list contains no album peak value");
  fail_unless (MATCH_PEAK (peak, result),
      "Album peak %f does not match, expected %f", result, peak);
}

static void
fail_if_track_tags (const GstTagList * tag_list)
{
  gdouble result;

  fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &result),
      "Tag list contains track gain value (but should not)");
  fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &result),
      "Tag list contains track peak value (but should not)");
}

static void
fail_if_album_tags (const GstTagList * tag_list)
{
  gdouble result;

  fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &result),
      "Tag list contains album gain value (but should not)");
  fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &result),
      "Tag list contains album peak value (but should not)");
}

static void
fail_unless_num_tracks (GstElement * element, guint num_tracks)
{
  guint current;

  g_object_get (element, "num-tracks", &current, NULL);
  fail_unless (current == num_tracks,
      "num-tracks property has incorrect value %u, expected %u",
      current, num_tracks);
}

/* Functions that create buffers with constant sample values, for peak
 * tests. */

static GstBuffer *
test_buffer_const_float_mono (gint sample_rate, gsize n_frames, gfloat value)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat));
  gfloat *data = (gfloat *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;)
    *data++ = value;

  caps = gst_caps_new_simple ("audio/x-raw-float",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "width", G_TYPE_INT, 32, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_const_float_stereo (gint sample_rate, gsize n_frames,
    gfloat value_l, gfloat value_r)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat) * 2);
  gfloat *data = (gfloat *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *data++ = value_l;
    *data++ = value_r;
  }

  caps = gst_caps_new_simple ("audio/x-raw-float",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "width", G_TYPE_INT, 32, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_const_int16_mono (gint sample_rate, gint depth, gsize n_frames,
    gint16 value)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16));
  gint16 *data = (gint16 *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;)
    *data++ = value;

  caps = gst_caps_new_simple ("audio/x-raw-int",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "signed", G_TYPE_BOOLEAN, TRUE,
      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, depth, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_const_int16_stereo (gint sample_rate, gint depth, gsize n_frames,
    gint16 value_l, gint16 value_r)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16) * 2);
  gint16 *data = (gint16 *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *data++ = value_l;
    *data++ = value_r;
  }

  caps = gst_caps_new_simple ("audio/x-raw-int",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "signed", G_TYPE_BOOLEAN, TRUE,
      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, depth, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

/* Functions that create data buffers containing square signal
 * waveforms. */

static GstBuffer *
test_buffer_square_float_mono (gint * accumulator, gint sample_rate,
    gsize n_frames, gfloat value)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat));
  gfloat *data = (gfloat *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

    if (*accumulator < 48)
      *data++ = value;
    else
      *data++ = -value;
  }

  caps = gst_caps_new_simple ("audio/x-raw-float",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "width", G_TYPE_INT, 32, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_square_float_stereo (gint * accumulator, gint sample_rate,
    gsize n_frames, gfloat value_l, gfloat value_r)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat) * 2);
  gfloat *data = (gfloat *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

    if (*accumulator < 48) {
      *data++ = value_l;
      *data++ = value_r;
    } else {
      *data++ = -value_l;
      *data++ = -value_r;
    }
  }

  caps = gst_caps_new_simple ("audio/x-raw-float",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "width", G_TYPE_INT, 32, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_square_int16_mono (gint * accumulator, gint sample_rate,
    gint depth, gsize n_frames, gint16 value)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16));
  gint16 *data = (gint16 *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

    if (*accumulator < 48)
      *data++ = value;
    else
      *data++ = -MAX (value, -32767);
  }

  caps = gst_caps_new_simple ("audio/x-raw-int",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "signed", G_TYPE_BOOLEAN, TRUE,
      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, depth, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static GstBuffer *
test_buffer_square_int16_stereo (gint * accumulator, gint sample_rate,
    gint depth, gsize n_frames, gint16 value_l, gint16 value_r)
{
  GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16) * 2);
  gint16 *data = (gint16 *) GST_BUFFER_DATA (buf);
  GstCaps *caps;
  gint i;

  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

    if (*accumulator < 48) {
      *data++ = value_l;
      *data++ = value_r;
    } else {
      *data++ = -MAX (value_l, -32767);
      *data++ = -MAX (value_r, -32767);
    }
  }

  caps = gst_caps_new_simple ("audio/x-raw-int",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "endianess", G_TYPE_INT, G_BYTE_ORDER, "signed", G_TYPE_BOOLEAN, TRUE,
      "width", G_TYPE_INT, 16, "depth", G_TYPE_INT, depth, NULL);
  gst_buffer_set_caps (buf, caps);
  gst_caps_unref (caps);

  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);

  return buf;
}

static void
push_buffer (GstBuffer * buf)
{
  /* gst_pad_push steals a reference. */
  fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK);
  ASSERT_BUFFER_REFCOUNT (buf, "buf", 1);
}

/*** Start of the tests. ***/

/* This test looks redundant, but early versions of the element
 * crashed when doing, well, nothing: */

GST_START_TEST (test_no_buffer)
{
  GstElement *element = setup_rganalysis ();

  set_playing_state (element);
  send_eos_event (element);
  poll_eos (element);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_no_buffer_album_1)
{
  GstElement *element = setup_rganalysis ();

  set_playing_state (element);

  /* Single track: */
  send_eos_event (element);
  poll_eos (element);

  /* First album: */
  g_object_set (element, "num-tracks", 3, NULL);

  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 2);

  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 1);

  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  /* Second album: */
  g_object_set (element, "num-tracks", 2, NULL);

  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 1);

  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  /* Single track: */
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_no_buffer_album_2)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "num-tracks", 3, NULL);
  set_playing_state (element);

  /* No buffer for the first track. */

  send_eos_event (element);
  /* No tags should be posted, there was nothing to analyze: */
  poll_eos (element);
  fail_unless_num_tracks (element, 2);

  /* A test waveform with known gain result as second track: */

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_mono (&accumulator, 44100, 512,
            0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, -6.20);
  /* Album is not finished yet: */
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  /* No buffer for the last track. */

  send_eos_event (element);

  tag_list = poll_tags (element);
  fail_unless_album_peak (tag_list, 0.25);
  fail_unless_album_gain (tag_list, -6.20);
  /* No track tags should be posted, as there was no data for it: */
  fail_if_track_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_empty_buffers)
{
  GstElement *element = setup_rganalysis ();

  set_playing_state (element);

  /* Single track: */
  push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);

  /* First album: */
  g_object_set (element, "num-tracks", 2, NULL);

  push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 1);

  push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  /* Second album, with a single track: */
  g_object_set (element, "num-tracks", 1, NULL);
  push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  /* Single track: */
  push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Tests for correctness of the peak values. */

/* Float peak test.  For stereo, one channel has the constant value of
 * -1.369, the other one 0.0.  This tests many things: The result peak
 * value should occur on any channel.  The peak is of course the
 * absolute amplitude, so 1.369 should be the result.  This will also
 * detect if the code uses the absolute value during the comparison.
 * If it is buggy it will return 0.0 since 0.0 > -1.369.  Furthermore,
 * this makes sure that there is no problem with headroom (exceeding
 * 0dBFS).  In the wild you get float samples > 1.0 from stuff like
 * vorbis. */

GST_START_TEST (test_peak_float)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;

  set_playing_state (element);
  push_buffer (test_buffer_const_float_stereo (8000, 512, -1.369, 0.0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.369);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, -1.369));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.369);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_float_mono (8000, 512, -1.369));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.369);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_peak_int16_16)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;

  set_playing_state (element);

  /* Half amplitude. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 1 << 14, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, 1 << 14));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 16, 512, 1 << 14));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Half amplitude, negative variant. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, -1 << 14, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, -1 << 14));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 16, 512, -1 << 14));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);


  /* Now check for correct normalization of the peak value: Sample
   * values of this format range from -32768 to 32767.  So for the
   * highest positive amplitude we do not reach 1.0, only for
   * -32768! */

  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 32767, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 32767. / 32768.);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, 32767));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 32767. / 32768.);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 16, 512, 32767));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 32767. / 32768.);
  gst_tag_list_free (tag_list);


  /* Negative variant, reaching 1.0. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, -32768, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, -32768));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 16, 512, -32768));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Same as the test before, but with 8 bits (packed into 16 bits). */

GST_START_TEST (test_peak_int16_8)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;

  set_playing_state (element);

  /* Half amplitude. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, 1 << 6, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, 0, 1 << 6));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 8, 512, 1 << 6));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);


  /* Half amplitude, negative variant. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, -1 << 6, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, 0, -1 << 6));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 8, 512, -1 << 6));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);


  /* Almost full amplitude (maximum positive value). */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, (1 << 7) - 1, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.9921875);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, 0, (1 << 7) - 1));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.9921875);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 8, 512, (1 << 7) - 1));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.9921875);
  gst_tag_list_free (tag_list);


  /* Full amplitude (maximum negative value). */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, -1 << 7, 0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  /* Swapped channels. */
  push_buffer (test_buffer_const_int16_stereo (8000, 8, 512, 0, -1 << 7));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  /* Mono. */
  push_buffer (test_buffer_const_int16_mono (8000, 8, 512, -1 << 7));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_peak_album)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;

  g_object_set (element, "num-tracks", 2, NULL);
  set_playing_state (element);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 1.0, 0.0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.0, 0.5));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  fail_unless_album_peak (tag_list, 1.0);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  /* Try a second album: */
  g_object_set (element, "num-tracks", 3, NULL);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.4, 0.4));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.4);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 2);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.45, 0.45));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.45);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.2, 0.2));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.2);
  fail_unless_album_peak (tag_list, 0.45);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  /* And now a single track, not in album mode (num-tracks is 0
   * now): */
  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.1, 0.1));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.1);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Switching from track to album mode. */

GST_START_TEST (test_peak_track_album)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  guint num;

  set_playing_state (element);

  push_buffer (test_buffer_const_float_mono (8000, 1024, 1.0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  g_object_set (element, "num-tracks", 1, NULL);
  push_buffer (test_buffer_const_float_mono (8000, 1024, 0.5));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  fail_unless_album_peak (tag_list, 0.5);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Disabling album processing before the end of the album.  Probably a
 * rare edge case and applications should not rely on this to work.
 * They need to send the element to the READY state to clear up after
 * an aborted album anyway since they might need to process another
 * album afterwards. */

GST_START_TEST (test_peak_album_abort_to_track)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  guint num;

  g_object_set (element, "num-tracks", 2, NULL);
  set_playing_state (element);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 1.0, 0.0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 1.0);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  g_object_set (element, "num-tracks", 0, NULL);

  push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.0, 0.5));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_gain_album)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator;
  gint i;

  g_object_set (element, "num-tracks", 3, NULL);
  set_playing_state (element);

  /* The three tracks are constructed such that if any of these is in
   * fact ignored for the album gain, the album gain will differ. */

  accumulator = 0;
  for (i = 8; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.75, 0.75));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.75);
  fail_unless_track_gain (tag_list, -15.70);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  accumulator = 0;
  for (i = 12; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.5, 0.5));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.5);
  fail_unless_track_gain (tag_list, -12.22);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  accumulator = 0;
  for (i = 180; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);

  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, -6.20);
  fail_unless_album_peak (tag_list, 0.75);
  /* Strangely, wavegain reports -12.17 for the album, but the fixed
   * metaflac agrees to us.  Could be a 32767 vs. 32768 issue. */
  fail_unless_album_gain (tag_list, -12.18);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Checks ensuring that the "forced" property works as advertised. */

GST_START_TEST (test_forced)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, NULL);
  set_playing_state (element);

  tag_list = gst_tag_list_new ();
  /* Provided values are totally arbitrary. */
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL);
  send_tag_event (element, tag_list);

  for (i = 20; i--;)
    push_buffer (test_buffer_const_float_stereo (44100, 512, 0.5, 0.5));
  send_eos_event (element);
  /* This fails if a tag message is generated: */
  poll_eos (element);

  /* Now back to a track without tags. */

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100));
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Sending track gain and peak in separate tag lists. */

GST_START_TEST (test_forced_separate)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, NULL);
  set_playing_state (element);

  tag_list = gst_tag_list_new ();
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, GST_TAG_TRACK_GAIN, 2.21,
      NULL);
  send_tag_event (element, tag_list);

  tag_list = gst_tag_list_new ();
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, GST_TAG_TRACK_PEAK, 1.0,
      NULL);
  send_tag_event (element, tag_list);

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.5, 0.5));
  send_eos_event (element);
  /* This fails if a tag message is generated: */
  poll_eos (element);

  /* Now a track without tags. */

  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100));
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* A TAG event is sent _after_ data has already been processed.  In
 * real pipelines, this could happen if there is more than one
 * rganalysis element (by accident).  While it would have analyzed all
 * the data prior to receiving the event, I expect it to not post its
 * results if not forced.  This test is almost equivalent to
 * test_forced. */

GST_START_TEST (test_forced_after_data)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, NULL);
  set_playing_state (element);

  for (i = 20; i--;)
    push_buffer (test_buffer_const_float_stereo (8000, 512, 0.5, 0.5));

  tag_list = gst_tag_list_new ();
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL);
  send_tag_event (element, tag_list);

  send_eos_event (element);
  poll_eos (element);

  /* Now back to a normal track, this one has no tags: */
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25,
            0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (8000));
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Like test_forced, but *analyze* an album afterwards.  The two tests
 * following this one check the *skipping* of albums. */

GST_START_TEST (test_forced_album)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator;
  gint i;

  g_object_set (element, "forced", FALSE, NULL);
  set_playing_state (element);

  tag_list = gst_tag_list_new ();
  /* Provided values are totally arbitrary. */
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL);
  send_tag_event (element, tag_list);

  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.5, 0.5));
  send_eos_event (element);
  /* This fails if a tag message is generated: */
  poll_eos (element);

  /* Now an album without tags. */
  g_object_set (element, "num-tracks", 2, NULL);

  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100));
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100));
  fail_unless_album_peak (tag_list, 0.25);
  fail_unless_album_gain (tag_list, get_expected_gain (44100));
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_forced_album_skip)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL);
  set_playing_state (element);

  tag_list = gst_tag_list_new ();
  /* Provided values are totally arbitrary. */
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21,
      GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL);
  send_tag_event (element, tag_list);

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25,
            0.25));
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 1);

  /* This track has no tags, but needs to be skipped anyways since we
   * are in album processing mode. */
  for (i = 20; i--;)
    push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);
  fail_unless_num_tracks (element, 0);

  /* Normal track after the album.  Of course not to be skipped. */
  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25,
            0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (8000));
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_forced_album_no_skip)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL);
  set_playing_state (element);

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25,
            0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (8000));
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  /* The second track has indeed full tags, but although being not
   * forced, this one has to be processed because album processing is
   * on. */
  tag_list = gst_tag_list_new ();
  /* Provided values are totally arbitrary. */
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21,
      GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL);
  send_tag_event (element, tag_list);
  for (i = 20; i--;)
    push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.0);
  fail_unless_track_gain (tag_list, SILENCE_GAIN);
  /* Second track was just silence so the album peak equals the first
   * track's peak. */
  fail_unless_album_peak (tag_list, 0.25);
  /* Statistical processing leads to the second track being
   * ignored for the gain (because it is so short): */
  fail_unless_album_gain (tag_list, get_expected_gain (8000));
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 0);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_forced_abort_album_no_skip)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL);
  set_playing_state (element);

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25,
            0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (8000));
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);
  fail_unless_num_tracks (element, 1);

  /* Disabling album processing before end of album: */
  g_object_set (element, "num-tracks", 0, NULL);

  /* Processing a track that has to be skipped. */
  tag_list = gst_tag_list_new ();
  /* Provided values are totally arbitrary. */
  gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND,
      GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21,
      GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL);
  send_tag_event (element, tag_list);
  for (i = 20; i--;)
    push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0));
  send_eos_event (element);
  poll_eos (element);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_reference_level)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i;

  g_object_set (element, "reference-level", 83., "num-tracks", 2, NULL);
  set_playing_state (element);

  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100) - 6.);
  fail_if_album_tags (tag_list);
  gst_tag_list_free (tag_list);

  accumulator = 0;
  for (i = 20; i--;)
    push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512,
            0.25, 0.25));
  send_eos_event (element);
  tag_list = poll_tags (element);
  fail_unless_track_peak (tag_list, 0.25);
  fail_unless_track_gain (tag_list, get_expected_gain (44100) - 6.);
  fail_unless_album_peak (tag_list, 0.25);
  /* We provided the same waveform twice, with a reset separating
   * them.  Therefore, the album gain matches the track gain. */
  fail_unless_album_gain (tag_list, get_expected_gain (44100) - 6.);
  gst_tag_list_free (tag_list);

  cleanup_rganalysis (element);
}

GST_END_TEST;

GST_START_TEST (test_all_formats)
{
  GstElement *element = setup_rganalysis ();
  GstTagList *tag_list;
  gint accumulator = 0;
  gint i, j;

  set_playing_state (element);
  for (i = G_N_ELEMENTS (supported_rates); i--;) {
    accumulator = 0;
    for (j = 0; j < 4; j++)
      push_buffer (test_buffer_square_float_stereo (&accumulator,
              supported_rates[i].sample_rate, 512, 0.25, 0.25));
    for (j = 0; j < 3; j++)
      push_buffer (test_buffer_square_float_mono (&accumulator,
              supported_rates[i].sample_rate, 512, 0.25));
    for (j = 0; j < 4; j++)
      push_buffer (test_buffer_square_int16_stereo (&accumulator,
              supported_rates[i].sample_rate, 16, 512, 1 << 13, 1 << 13));
    for (j = 0; j < 3; j++)
      push_buffer (test_buffer_square_int16_mono (&accumulator,
              supported_rates[i].sample_rate, 16, 512, 1 << 13));
    for (j = 0; j < 3; j++)
      push_buffer (test_buffer_square_int16_stereo (&accumulator,
              supported_rates[i].sample_rate, 8, 512, 1 << 5, 1 << 5));
    for (j = 0; j < 3; j++)
      push_buffer (test_buffer_square_int16_mono (&accumulator,
              supported_rates[i].sample_rate, 8, 512, 1 << 5));
    send_eos_event (element);
    tag_list = poll_tags (element);
    fail_unless_track_peak (tag_list, 0.25);
    fail_unless_track_gain (tag_list, supported_rates[i].gain);
    gst_tag_list_free (tag_list);
  }

  cleanup_rganalysis (element);
}

GST_END_TEST;

/* Checks ensuring all advertised supported sample rates are really
 * accepted, for integer and float, mono and stereo.  This also
 * verifies that the correct gain is computed for all formats (except
 * odd bit depths). */

#define MAKE_GAIN_TEST_FLOAT_MONO(sample_rate)                        \
  GST_START_TEST (test_gain_float_mono_##sample_rate)                 \
{                                                                     \
  GstElement *element = setup_rganalysis ();                          \
  GstTagList *tag_list;                                               \
  gint accumulator = 0;                                               \
  gint i;                                                             \
                                                                      \
  set_playing_state (element);                                        \
                                                                      \
  for (i = 0; i < 20; i++)                                            \
    push_buffer (test_buffer_square_float_mono (&accumulator,         \
            sample_rate, 512, 0.25));                                 \
  send_eos_event (element);                                           \
  tag_list = poll_tags (element);                                     \
  fail_unless_track_peak (tag_list, 0.25);                            \
  fail_unless_track_gain (tag_list,                                   \
      get_expected_gain (sample_rate));                               \
  gst_tag_list_free (tag_list);                                       \
                                                                      \
  cleanup_rganalysis (element);                                       \
}                                                                     \
                                                                      \
GST_END_TEST;

#define MAKE_GAIN_TEST_FLOAT_STEREO(sample_rate)                      \
  GST_START_TEST (test_gain_float_stereo_##sample_rate)               \
{                                                                     \
  GstElement *element = setup_rganalysis ();                          \
  GstTagList *tag_list;                                               \
  gint accumulator = 0;                                               \
  gint i;                                                             \
                                                                      \
  set_playing_state (element);                                        \
                                                                      \
  for (i = 0; i < 20; i++)                                            \
    push_buffer (test_buffer_square_float_stereo (&accumulator,       \
            sample_rate, 512, 0.25, 0.25));                           \
  send_eos_event (element);                                           \
  tag_list = poll_tags (element);                                     \
  fail_unless_track_peak (tag_list, 0.25);                            \
  fail_unless_track_gain (tag_list,                                   \
      get_expected_gain (sample_rate));                               \
  gst_tag_list_free (tag_list);                                       \
                                                                      \
  cleanup_rganalysis (element);                                       \
}                                                                     \
                                                                      \
GST_END_TEST;

#define MAKE_GAIN_TEST_INT16_MONO(sample_rate, depth)                 \
  GST_START_TEST (test_gain_int16_##depth##_mono_##sample_rate)       \
{                                                                     \
  GstElement *element = setup_rganalysis ();                          \
  GstTagList *tag_list;                                               \
  gint accumulator = 0;                                               \
  gint i;                                                             \
                                                                      \
  set_playing_state (element);                                        \
                                                                      \
  for (i = 0; i < 20; i++)                                            \
    push_buffer (test_buffer_square_int16_mono (&accumulator,         \
            sample_rate, depth, 512, 1 << (13 + depth - 16)));        \
                                                                      \
  send_eos_event (element);                                           \
  tag_list = poll_tags (element);                                     \
  fail_unless_track_peak (tag_list, 0.25);                            \
  fail_unless_track_gain (tag_list,                                   \
      get_expected_gain (sample_rate));                               \
  gst_tag_list_free (tag_list);                                       \
                                                                      \
  cleanup_rganalysis (element);                                       \
}                                                                     \
                                                                      \
GST_END_TEST;

#define MAKE_GAIN_TEST_INT16_STEREO(sample_rate, depth)               \
  GST_START_TEST (test_gain_int16_##depth##_stereo_##sample_rate)     \
{                                                                     \
  GstElement *element = setup_rganalysis ();                          \
  GstTagList *tag_list;                                               \
  gint accumulator = 0;                                               \
  gint i;                                                             \
                                                                      \
  set_playing_state (element);                                        \
                                                                      \
  for (i = 0; i < 20; i++)                                            \
    push_buffer (test_buffer_square_int16_stereo (&accumulator,       \
            sample_rate, depth, 512, 1 << (13 + depth - 16),          \
            1 << (13 + depth - 16)));                                 \
  send_eos_event (element);                                           \
  tag_list = poll_tags (element);                                     \
  fail_unless_track_peak (tag_list, 0.25);                            \
  fail_unless_track_gain (tag_list,                                   \
      get_expected_gain (sample_rate));                               \
  gst_tag_list_free (tag_list);                                       \
                                                                      \
  cleanup_rganalysis (element);                                       \
}                                                                     \
                                                                      \
GST_END_TEST;

MAKE_GAIN_TEST_FLOAT_MONO (8000);
MAKE_GAIN_TEST_FLOAT_MONO (11025);
MAKE_GAIN_TEST_FLOAT_MONO (12000);
MAKE_GAIN_TEST_FLOAT_MONO (16000);
MAKE_GAIN_TEST_FLOAT_MONO (22050);
MAKE_GAIN_TEST_FLOAT_MONO (24000);
MAKE_GAIN_TEST_FLOAT_MONO (32000);
MAKE_GAIN_TEST_FLOAT_MONO (44100);
MAKE_GAIN_TEST_FLOAT_MONO (48000);

MAKE_GAIN_TEST_FLOAT_STEREO (8000);
MAKE_GAIN_TEST_FLOAT_STEREO (11025);
MAKE_GAIN_TEST_FLOAT_STEREO (12000);
MAKE_GAIN_TEST_FLOAT_STEREO (16000);
MAKE_GAIN_TEST_FLOAT_STEREO (22050);
MAKE_GAIN_TEST_FLOAT_STEREO (24000);
MAKE_GAIN_TEST_FLOAT_STEREO (32000);
MAKE_GAIN_TEST_FLOAT_STEREO (44100);
MAKE_GAIN_TEST_FLOAT_STEREO (48000);

MAKE_GAIN_TEST_INT16_MONO (8000, 16);
MAKE_GAIN_TEST_INT16_MONO (11025, 16);
MAKE_GAIN_TEST_INT16_MONO (12000, 16);
MAKE_GAIN_TEST_INT16_MONO (16000, 16);
MAKE_GAIN_TEST_INT16_MONO (22050, 16);
MAKE_GAIN_TEST_INT16_MONO (24000, 16);
MAKE_GAIN_TEST_INT16_MONO (32000, 16);
MAKE_GAIN_TEST_INT16_MONO (44100, 16);
MAKE_GAIN_TEST_INT16_MONO (48000, 16);

MAKE_GAIN_TEST_INT16_STEREO (8000, 16);
MAKE_GAIN_TEST_INT16_STEREO (11025, 16);
MAKE_GAIN_TEST_INT16_STEREO (12000, 16);
MAKE_GAIN_TEST_INT16_STEREO (16000, 16);
MAKE_GAIN_TEST_INT16_STEREO (22050, 16);
MAKE_GAIN_TEST_INT16_STEREO (24000, 16);
MAKE_GAIN_TEST_INT16_STEREO (32000, 16);
MAKE_GAIN_TEST_INT16_STEREO (44100, 16);
MAKE_GAIN_TEST_INT16_STEREO (48000, 16);

MAKE_GAIN_TEST_INT16_MONO (8000, 8);
MAKE_GAIN_TEST_INT16_MONO (11025, 8);
MAKE_GAIN_TEST_INT16_MONO (12000, 8);
MAKE_GAIN_TEST_INT16_MONO (16000, 8);
MAKE_GAIN_TEST_INT16_MONO (22050, 8);
MAKE_GAIN_TEST_INT16_MONO (24000, 8);
MAKE_GAIN_TEST_INT16_MONO (32000, 8);
MAKE_GAIN_TEST_INT16_MONO (44100, 8);
MAKE_GAIN_TEST_INT16_MONO (48000, 8);

MAKE_GAIN_TEST_INT16_STEREO (8000, 8);
MAKE_GAIN_TEST_INT16_STEREO (11025, 8);
MAKE_GAIN_TEST_INT16_STEREO (12000, 8);
MAKE_GAIN_TEST_INT16_STEREO (16000, 8);
MAKE_GAIN_TEST_INT16_STEREO (22050, 8);
MAKE_GAIN_TEST_INT16_STEREO (24000, 8);
MAKE_GAIN_TEST_INT16_STEREO (32000, 8);
MAKE_GAIN_TEST_INT16_STEREO (44100, 8);
MAKE_GAIN_TEST_INT16_STEREO (48000, 8);

Suite *
rganalysis_suite (void)
{
  Suite *s = suite_create ("rganalysis");
  TCase *tc_chain = tcase_create ("general");

  suite_add_tcase (s, tc_chain);

  tcase_add_test (tc_chain, test_no_buffer);
  tcase_add_test (tc_chain, test_no_buffer_album_1);
  tcase_add_test (tc_chain, test_no_buffer_album_2);
  tcase_add_test (tc_chain, test_empty_buffers);

  tcase_add_test (tc_chain, test_peak_float);
  tcase_add_test (tc_chain, test_peak_int16_16);
  tcase_add_test (tc_chain, test_peak_int16_8);

  tcase_add_test (tc_chain, test_peak_album);
  tcase_add_test (tc_chain, test_peak_track_album);
  tcase_add_test (tc_chain, test_peak_album_abort_to_track);

  tcase_add_test (tc_chain, test_gain_album);

  tcase_add_test (tc_chain, test_forced);
  tcase_add_test (tc_chain, test_forced_separate);
  tcase_add_test (tc_chain, test_forced_after_data);
  tcase_add_test (tc_chain, test_forced_album);
  tcase_add_test (tc_chain, test_forced_album_skip);
  tcase_add_test (tc_chain, test_forced_album_no_skip);
  tcase_add_test (tc_chain, test_forced_abort_album_no_skip);

  tcase_add_test (tc_chain, test_reference_level);

  tcase_add_test (tc_chain, test_all_formats);

  tcase_add_test (tc_chain, test_gain_float_mono_8000);
  tcase_add_test (tc_chain, test_gain_float_mono_11025);
  tcase_add_test (tc_chain, test_gain_float_mono_12000);
  tcase_add_test (tc_chain, test_gain_float_mono_16000);
  tcase_add_test (tc_chain, test_gain_float_mono_22050);
  tcase_add_test (tc_chain, test_gain_float_mono_24000);
  tcase_add_test (tc_chain, test_gain_float_mono_32000);
  tcase_add_test (tc_chain, test_gain_float_mono_44100);
  tcase_add_test (tc_chain, test_gain_float_mono_48000);

  tcase_add_test (tc_chain, test_gain_float_stereo_8000);
  tcase_add_test (tc_chain, test_gain_float_stereo_11025);
  tcase_add_test (tc_chain, test_gain_float_stereo_12000);
  tcase_add_test (tc_chain, test_gain_float_stereo_16000);
  tcase_add_test (tc_chain, test_gain_float_stereo_22050);
  tcase_add_test (tc_chain, test_gain_float_stereo_24000);
  tcase_add_test (tc_chain, test_gain_float_stereo_32000);
  tcase_add_test (tc_chain, test_gain_float_stereo_44100);
  tcase_add_test (tc_chain, test_gain_float_stereo_48000);

  tcase_add_test (tc_chain, test_gain_int16_16_mono_8000);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_11025);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_12000);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_16000);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_22050);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_24000);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_32000);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_44100);
  tcase_add_test (tc_chain, test_gain_int16_16_mono_48000);

  tcase_add_test (tc_chain, test_gain_int16_16_stereo_8000);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_11025);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_12000);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_16000);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_22050);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_24000);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_32000);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_44100);
  tcase_add_test (tc_chain, test_gain_int16_16_stereo_48000);

  tcase_add_test (tc_chain, test_gain_int16_8_mono_8000);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_11025);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_12000);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_16000);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_22050);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_24000);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_32000);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_44100);
  tcase_add_test (tc_chain, test_gain_int16_8_mono_48000);

  tcase_add_test (tc_chain, test_gain_int16_8_stereo_8000);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_11025);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_12000);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_16000);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_22050);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_24000);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_32000);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_44100);
  tcase_add_test (tc_chain, test_gain_int16_8_stereo_48000);

  return s;
}

int
main (int argc, char **argv)
{
  gint nf;

  Suite *s = rganalysis_suite ();
  SRunner *sr = srunner_create (s);

  gst_check_init (&argc, &argv);

  srunner_run_all (sr, CK_ENV);
  nf = srunner_ntests_failed (sr);
  srunner_free (sr);

  return nf;
}