/* 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>
#include <gst/audio/audio.h>

/* 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;
static GstBus *mybus;

/* 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, "                             \
  "format = (string) "GST_AUDIO_NE (F32) ", " \
  "layout = (string) interleaved, "           \
  REPLAY_GAIN_CAPS                            \
  "; "                                        \
  "audio/x-raw, "                             \
  "format = (string) "GST_AUDIO_NE (S16) ", " \
  "layout = (string) interleaved, "           \
  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)
    );

static gboolean
mysink_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
{
  GST_LOG_OBJECT (pad, "%s event: %" GST_PTR_FORMAT,
      GST_EVENT_TYPE_NAME (event), event);

  /* a sink would post tag events as messages, so do the same here,
   * esp. since we're polling on the bus waiting for TAG messages.. */
  if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) {
    GstTagList *taglist;

    gst_event_parse_tag (event, &taglist);

    gst_bus_post (mybus, gst_message_new_tag (GST_OBJECT (mysinkpad),
            gst_tag_list_copy (taglist)));
  }

  gst_event_unref (event);
  return TRUE;
}

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

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

  bus = gst_bus_new ();
  gst_element_set_bus (analysis, bus);

  mybus = bus;                  /* keep ref */

  return analysis;
}

static 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;

  gst_object_unref (mybus);
  mybus = 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_flush_events (GstElement * element)
{
  gboolean res;
  GstPad *pad;

  pad = gst_element_get_static_pad (element, "sink");
  res = gst_pad_send_event (pad, gst_event_new_flush_start ());
  fail_unless (res, "flush-start even not handledt");
  res = gst_pad_send_event (pad, gst_event_new_flush_stop (TRUE));
  fail_unless (res, "flush-stop event not handled");
  gst_object_unref (pad);
}

static void
send_segment_event (GstElement * element)
{
  GstSegment segment;
  gboolean res;
  GstPad *pad;

  pad = gst_element_get_static_pad (element, "sink");
  gst_segment_init (&segment, GST_FORMAT_TIME);
  res = gst_pad_send_event (pad, gst_event_new_segment (&segment));
  fail_unless (res, "SEGMENT event not handled");
  gst_object_unref (pad);
}

static void
send_eos_event (GstElement * element)
{
  GstBus *bus = gst_element_get_bus (element);
  GstPad *pad = gst_element_get_static_pad (element, "sink");
  gboolean res;

  res = gst_pad_send_event (pad, gst_event_new_eos ());
  fail_unless (res, "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_static_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");

  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));
  GstMapInfo map;
  gfloat *data;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gfloat *) map.data;
  for (i = n_frames; i--;)
    *data++ = value;
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1,
      "layout", G_TYPE_STRING, "interleaved", NULL);
  gst_pad_set_caps (mysrcpad, 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);
  GstMapInfo map;
  gfloat *data;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gfloat *) map.data;
  for (i = n_frames; i--;) {
    *data++ = value_l;
    *data++ = value_r;
  }
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate,
      "channels", G_TYPE_INT, 2,
      "channel-mask", GST_TYPE_BITMASK, (gint64) 0x3, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gint16 *) map.data;
  for (i = n_frames; i--;)
    *data++ = value;
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gint16 *) map.data;
  for (i = n_frames; i--;) {
    *data++ = value_l;
    *data++ = value_r;
  }
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "channel-mask", GST_TYPE_BITMASK, (gint64) 0x3, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gfloat *) map.data;
  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

    if (*accumulator < 48)
      *data++ = value;
    else
      *data++ = -value;
  }
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gfloat *) map.data;
  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;
    }
  }
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "channel-mask", GST_TYPE_BITMASK, (gint64) 0x3, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gint16 *) map.data;
  for (i = n_frames; i--;) {
    *accumulator += 1;
    *accumulator %= 96;

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

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 1, NULL);
  gst_pad_set_caps (mysrcpad, 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;
  GstMapInfo map;
  GstCaps *caps;
  gint i;

  gst_buffer_map (buf, &map, GST_MAP_WRITE);
  data = (gint16 *) map.data;
  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);
    }
  }
  gst_buffer_unmap (buf, &map);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
      "layout", G_TYPE_STRING, "interleaved",
      "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, 2,
      "channel-mask", GST_TYPE_BITMASK, (gint64) 0x3, NULL);
  gst_pad_set_caps (mysrcpad, 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_segment_event (element);
  send_eos_event (element);
  poll_eos (element);

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

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

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

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

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

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

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

  /* Single track: */
  send_flush_events (element);
  send_segment_event (element);
  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_segment_event (element);
  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: */

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

  /* No buffer for the last track. */

  send_flush_events (element);
  send_segment_event (element);
  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_unref (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: */
  send_segment_event (element);
  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);

  send_flush_events (element);
  send_segment_event (element);
  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);

  send_flush_events (element);
  send_segment_event (element);
  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);
  send_flush_events (element);
  send_segment_event (element);
  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: */
  send_flush_events (element);
  send_segment_event (element);
  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);
  send_segment_event (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_unref (tag_list);

  /* Swapped channels. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Mono. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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. */
  send_segment_event (element);
  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_unref (tag_list);

  /* Swapped channels. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Mono. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Half amplitude, negative variant. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Swapped channels. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Mono. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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! */

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Swapped channels. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Mono. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);


  /* Negative variant, reaching 1.0. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Swapped channels. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  /* Mono. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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);

  send_segment_event (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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 0);

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

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 2);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 0);

  /* And now a single track, not in album mode (num-tracks is 0
   * now): */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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;

  set_playing_state (element);

  send_segment_event (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_unref (tag_list);

  g_object_set (element, "num-tracks", 1, NULL);
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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;

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

  send_segment_event (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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

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

  send_flush_events (element);
  send_segment_event (element);
  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_unref (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. */
  send_segment_event (element);
  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_unref (tag_list);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (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);

  send_segment_event (element);
  tag_list = gst_tag_list_new_empty ();
  /* 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. */
  send_flush_events (element);
  send_segment_event (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));
  gst_tag_list_unref (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);

  send_segment_event (element);
  tag_list = gst_tag_list_new_empty ();
  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_empty ();
  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. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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);

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

  tag_list = gst_tag_list_new_empty ();
  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);

  send_flush_events (element);
  send_segment_event (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_unref (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);

  send_segment_event (element);
  tag_list = gst_tag_list_new_empty ();
  /* 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);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

  send_flush_events (element);
  send_segment_event (element);
  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_unref (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);

  send_segment_event (element);
  tag_list = gst_tag_list_new_empty ();
  /* 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. */
  send_flush_events (element);
  send_segment_event (element);
  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. */
  send_flush_events (element);
  send_segment_event (element);
  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_unref (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);

  send_segment_event (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_unref (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. */
  send_flush_events (element);
  send_segment_event (element);
  tag_list = gst_tag_list_new_empty ();
  /* 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_unref (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);

  send_segment_event (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_unref (tag_list);
  fail_unless_num_tracks (element, 1);

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

  send_flush_events (element);
  send_segment_event (element);

  /* Processing a track that has to be skipped. */
  tag_list = gst_tag_list_new_empty ();
  /* 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;
  gdouble ref_level;
  gint accumulator = 0;
  gint i;

  set_playing_state (element);

  send_segment_event (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));
  fail_if_album_tags (tag_list);
  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL,
          &ref_level) && MATCH_GAIN (ref_level, 89.),
      "Incorrect reference level tag");
  gst_tag_list_unref (tag_list);

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

  send_flush_events (element);
  send_segment_event (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);
  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL,
          &ref_level) && MATCH_GAIN (ref_level, 83.),
      "Incorrect reference level tag");
  gst_tag_list_unref (tag_list);

  send_flush_events (element);
  send_segment_event (element);
  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.);
  fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL,
          &ref_level) && MATCH_GAIN (ref_level, 83.),
      "Incorrect reference level tag");
  gst_tag_list_unref (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--;) {
    send_flush_events (element);
    send_segment_event (element);
    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));
    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_unref (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_unref (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_unref (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_unref (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_unref (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);

static 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_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_skip_broken_test (tc_chain, test_forced);
  tcase_skip_broken_test (tc_chain, test_forced_separate);
  tcase_skip_broken_test (tc_chain, test_forced_after_data);
  tcase_skip_broken_test (tc_chain, test_forced_album);
  tcase_skip_broken_test (tc_chain, test_forced_album_skip);
  tcase_skip_broken_test (tc_chain, test_forced_album_no_skip);
  tcase_skip_broken_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);

  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;
}
