/* GSequencer - Advanced GTK Sequencer
 * Copyright (C) 2005-2015 Joël Krähemann
 *
 * This file is part of GSequencer.
 *
 * GSequencer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GSequencer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GSequencer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ags/audio/recall/ags_delay_audio.h>
#include <ags/audio/recall/ags_delay_audio_run.h>

#include <ags/object/ags_config.h>
#include <ags/object/ags_tactable.h>
#include <ags/object/ags_plugin.h>
#include <ags/object/ags_soundcard.h>

#include <math.h>

#include <ags/i18n.h>

void ags_delay_audio_class_init(AgsDelayAudioClass *delay_audio);
void ags_delay_audio_tactable_interface_init(AgsTactableInterface *tactable);
void ags_delay_audio_plugin_interface_init(AgsPluginInterface *plugin);
void ags_delay_audio_init(AgsDelayAudio *delay_audio);
void ags_delay_audio_set_property(GObject *gobject,
				  guint prop_id,
				  const GValue *value,
				  GParamSpec *param_spec);
void ags_delay_audio_get_property(GObject *gobject,
				  guint prop_id,
				  GValue *value,
				  GParamSpec *param_spec);
void ags_delay_audio_set_ports(AgsPlugin *plugin, GList *port);
void ags_delay_audio_dispose(GObject *gobject);
void ags_delay_audio_finalize(GObject *gobject);

void ags_delay_audio_notify_audio_callback(GObject *gobject,
					   GParamSpec *pspec,
					   gpointer user_data);
void ags_delay_audio_notify_soundcard_callback(GObject *gobject,
					       GParamSpec *pspec,
					       gpointer user_data);

void ags_delay_audio_notify_samplerate_callback(GObject *gobject,
						GParamSpec *pspec,
						gpointer user_data);
void ags_delay_audio_notify_buffer_size_callback(GObject *gobject,
						 GParamSpec *pspec,
						 gpointer user_data);

gdouble ags_delay_audio_get_bpm(AgsTactable *tactable);
gdouble ags_delay_audio_get_tact(AgsTactable *tactable);
gdouble ags_delay_audio_get_sequencer_duration(AgsTactable *tactable);
gdouble ags_delay_audio_get_notation_duration(AgsTactable *tactable);
void ags_delay_audio_change_bpm(AgsTactable *tactable, gdouble new_bpm, gdouble old_bpm);
void ags_delay_audio_change_tact(AgsTactable *tactable, gdouble new_tact, gdouble old_bpm);
void ags_delay_audio_change_sequencer_duration(AgsTactable *tactable, gdouble duration);
void ags_delay_audio_change_notation_duration(AgsTactable *tactable, gdouble duration);

void ags_delay_audio_refresh_delay(AgsDelayAudio *delay_audio);

/**
 * SECTION:ags_delay_audio
 * @short_description: delay audio 
 * @title: AgsDelayAudio
 * @section_id:
 * @include: ags/audio/recall/ags_delay_audio.h
 *
 * The #AgsDelayAudio class provides ports to the effect processor.
 */

enum{
  NOTATION_DURATION_CHANGED,
  SEQUENCER_DURATION_CHANGED,
  LAST_SIGNAL,
};

enum{
  PROP_0,
  PROP_NOTATION_DELAY,
  PROP_SEQUENCER_DELAY,
  PROP_TACT,
  PROP_BPM,
  PROP_SEQUENCER_DURATION,
  PROP_NOTATION_DURATION,
};

static gpointer ags_delay_audio_parent_class = NULL;

static guint delay_audio_signals[LAST_SIGNAL];

static const gchar *ags_delay_audio_plugin_name = "ags-delay";
static const gchar *ags_delay_audio_specifier[] = {
  "./bpm[0]",
  "./tact[0]",
  "./sequencer_delay[0]",
  "./notation_delay[0]",
  "./sequencer_duration[0]",
  "./notation_duration[0]"
};
static const gchar *ags_delay_audio_control_port[] = {
  "1/6",
  "2/6",
  "3/6",
  "4/6",
  "5/6",
  "6/6",
};

GType
ags_delay_audio_get_type()
{
  static GType ags_type_delay_audio = 0;

  if(!ags_type_delay_audio){
    static const GTypeInfo ags_delay_audio_info = {
      sizeof (AgsDelayAudioClass),
      NULL, /* base_init */
      NULL, /* base_finalize */
      (GClassInitFunc) ags_delay_audio_class_init,
      NULL, /* class_finalize */
      NULL, /* class_audio */
      sizeof (AgsDelayAudio),
      0,    /* n_preallocs */
      (GInstanceInitFunc) ags_delay_audio_init,
    };

    static const GInterfaceInfo ags_tactable_interface_info = {
      (GInterfaceInitFunc) ags_delay_audio_tactable_interface_init,
      NULL, /* interface_finalize */
      NULL, /* interface_data */
    };

    static const GInterfaceInfo ags_plugin_interface_info = {
      (GInterfaceInitFunc) ags_delay_audio_plugin_interface_init,
      NULL, /* interface_finalize */
      NULL, /* interface_data */
    };

    ags_type_delay_audio = g_type_register_static(AGS_TYPE_RECALL_AUDIO,
						  "AgsDelayAudio",
						  &ags_delay_audio_info,
						  0);

    g_type_add_interface_static(ags_type_delay_audio,
				AGS_TYPE_TACTABLE,
				&ags_tactable_interface_info);

    g_type_add_interface_static(ags_type_delay_audio,
				AGS_TYPE_PLUGIN,
				&ags_plugin_interface_info);
  }

  return(ags_type_delay_audio);
}

void
ags_delay_audio_class_init(AgsDelayAudioClass *delay_audio)
{
  GObjectClass *gobject;
  GParamSpec *param_spec;

  ags_delay_audio_parent_class = g_type_class_peek_parent(delay_audio);

  /* GObjectClass */
  gobject = (GObjectClass *) delay_audio;

  gobject->set_property = ags_delay_audio_set_property;
  gobject->get_property = ags_delay_audio_get_property;

  gobject->dispose = ags_delay_audio_dispose;
  gobject->finalize = ags_delay_audio_finalize;

  /* properties */
  /**
   * AgsDelayAudio:bpm:
   *
   * The beats per minute.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("bpm",
				   i18n_pspec("bpm of recall"),
				   i18n_pspec("The recall's bpm"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_BPM,
				  param_spec);

  /**
   * AgsDelayAudio:tact:
   *
   * The tact segmentation.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("tact",
				   i18n_pspec("tact of recall"),
				   i18n_pspec("The recall's tact"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_TACT,
				  param_spec);

  /**
   * AgsDelayAudio:sequencer-delay:
   *
   * The sequencer's delay.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("sequencer-delay",
				   i18n_pspec("sequencer-delay of recall"),
				   i18n_pspec("The delay of the sequencer"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_SEQUENCER_DELAY,
				  param_spec);

  /**
   * AgsDelayAudio:notation-delay:
   *
   * The notation's delay.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("notation-delay",
				   i18n_pspec("notation-delay of recall"),
				   i18n_pspec("The delay of the notation"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_NOTATION_DELAY,
				  param_spec);

  /**
   * AgsDelayAudio:sequencer-duration:
   *
   * The sequencer's duration.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("sequencer-duration",
				   i18n_pspec("sequencer-duration of recall"),
				   i18n_pspec("The duration of the sequencer"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_SEQUENCER_DURATION,
				  param_spec);

  /**
   * AgsDelayAudio:notation-duration:
   *
   * The notation's duration.
   * 
   * Since: 1.0.0
   */
  param_spec = g_param_spec_object("notation-duration",
				   i18n_pspec("notation-duration of recall"),
				   i18n_pspec("The duration of the notation"),
				   AGS_TYPE_PORT,
				   G_PARAM_READABLE | G_PARAM_WRITABLE);
  g_object_class_install_property(gobject,
				  PROP_NOTATION_DURATION,
				  param_spec);


  /* signals */
  /**
   * AgsDelayAudio::notation-duration-changed:
   * @delay_audio: the object to resolve
   *
   * The ::notation-duration-changed signal notifies about changed duration
   * of notation.
   */
  delay_audio_signals[NOTATION_DURATION_CHANGED] = 
    g_signal_new("notation-duration-changed",
		 G_TYPE_FROM_CLASS(delay_audio),
		 G_SIGNAL_RUN_LAST,
		 G_STRUCT_OFFSET(AgsDelayAudioClass, notation_duration_changed),
		 NULL, NULL,
		 g_cclosure_marshal_VOID__VOID,
		 G_TYPE_NONE, 0);

  /**
   * AgsDelayAudio::sequencer-duration-changed:
   * @delay_audio: the object to resolve
   *
   * The ::sequencer-duration-changed signal notifies about changed duration
   * of sequencer.
   */
  delay_audio_signals[SEQUENCER_DURATION_CHANGED] = 
    g_signal_new("sequencer-duration-changed",
		 G_TYPE_FROM_CLASS(delay_audio),
		 G_SIGNAL_RUN_LAST,
		 G_STRUCT_OFFSET(AgsDelayAudioClass, sequencer_duration_changed),
		 NULL, NULL,
		 g_cclosure_marshal_VOID__VOID,
		 G_TYPE_NONE, 0);
}

void
ags_delay_audio_tactable_interface_init(AgsTactableInterface *tactable)
{
  tactable->get_sequencer_duration = ags_delay_audio_get_sequencer_duration;
  tactable->get_notation_duration = ags_delay_audio_get_notation_duration;
  tactable->get_bpm = ags_delay_audio_get_bpm;
  tactable->get_tact = ags_delay_audio_get_tact;

  tactable->change_sequencer_duration = ags_delay_audio_change_sequencer_duration;
  tactable->change_notation_duration = ags_delay_audio_change_notation_duration;
  tactable->change_bpm = ags_delay_audio_change_bpm;
  tactable->change_tact = ags_delay_audio_change_tact;
}

void
ags_delay_audio_plugin_interface_init(AgsPluginInterface *plugin)
{
  plugin->set_ports = ags_delay_audio_set_ports;
}

void
ags_delay_audio_init(AgsDelayAudio *delay_audio)
{
  GList *port;

  gdouble bpm;
  gdouble delay;

  AGS_RECALL(delay_audio)->name = "ags-delay";
  AGS_RECALL(delay_audio)->version = AGS_RECALL_DEFAULT_VERSION;
  AGS_RECALL(delay_audio)->build_id = AGS_RECALL_DEFAULT_BUILD_ID;
  AGS_RECALL(delay_audio)->xml_type = "ags-delay-audio";

  port = NULL;

  bpm = AGS_SOUNDCARD_DEFAULT_BPM;
  delay = AGS_SOUNDCARD_DEFAULT_DELAY;

  /* bpm */
  delay_audio->bpm = g_object_new(AGS_TYPE_PORT,
				  "plugin-name", ags_delay_audio_plugin_name,
				  "specifier", ags_delay_audio_specifier[0],
				  "control-port", ags_delay_audio_control_port[0],
				  "port-value-is-pointer", FALSE,
				  "port-value-type", G_TYPE_DOUBLE,
				  "port-value-size", sizeof(gdouble),
				  "port-value-length", 1,
				  NULL);
  g_object_ref(delay_audio->bpm);
  
  delay_audio->bpm->port_value.ags_port_double = bpm;

  /* add port */
  port = g_list_prepend(port, delay_audio->bpm);
  g_object_ref(delay_audio->bpm);
  
  /* tact */
  delay_audio->tact = g_object_new(AGS_TYPE_PORT,
				   "plugin-name", ags_delay_audio_plugin_name,
				   "specifier", ags_delay_audio_specifier[1],
				   "control-port", ags_delay_audio_control_port[1],
				   "port-value-is-pointer", FALSE,
				   "port-value-type", G_TYPE_DOUBLE,
				   "port-value-size", sizeof(gdouble),
				   "port-value-length", 1,
				   NULL);
  g_object_ref(delay_audio->tact);
  
  delay_audio->tact->port_value.ags_port_double = AGS_SOUNDCARD_DEFAULT_TACT;

  /* add port */
  port = g_list_prepend(port, delay_audio->tact);
  g_object_ref(delay_audio->tact);
  
  /* sequencer delay */
  delay_audio->sequencer_delay = g_object_new(AGS_TYPE_PORT,
					      "plugin-name", ags_delay_audio_plugin_name,
					      "specifier", ags_delay_audio_specifier[2],
					      "control-port", ags_delay_audio_control_port[2],
					      "port-value-is-pointer", FALSE,
					      "port-value-type", G_TYPE_DOUBLE,
					      "port-value-size", sizeof(gdouble),
					      "port-value-length", 1,
					      NULL);
  g_object_ref(delay_audio->sequencer_delay);
  
  delay_audio->sequencer_delay->port_value.ags_port_double = delay;

  /* add port */
  port = g_list_prepend(port, delay_audio->sequencer_delay);
  g_object_ref(delay_audio->sequencer_delay);
  
  /* notation delay */
  delay_audio->notation_delay = g_object_new(AGS_TYPE_PORT,
					     "plugin-name", ags_delay_audio_plugin_name,
					     "specifier", ags_delay_audio_specifier[3],
					     "control-port", ags_delay_audio_control_port[3],
					     "port-value-is-pointer", FALSE,
					     "port-value-type", G_TYPE_DOUBLE,
					     "port-value-size", sizeof(gdouble),
					     "port-value-length", 1,
					     NULL);
  g_object_ref(delay_audio->notation_delay);
  
  delay_audio->notation_delay->port_value.ags_port_double = delay;

  /* add port */
  port = g_list_prepend(port, delay_audio->notation_delay);
  g_object_ref(delay_audio->notation_delay);
  
  /* sequencer duration */
  delay_audio->sequencer_duration = g_object_new(AGS_TYPE_PORT,
						 "plugin-name", ags_delay_audio_plugin_name,
						 "specifier", ags_delay_audio_specifier[4],
						 "control-port", ags_delay_audio_control_port[4],
						 "port-value-is-pointer", FALSE,
						 "port-value-type", G_TYPE_DOUBLE,
						 "port-value-size", sizeof(gdouble),
						 "port-value-length", 1,
						 NULL);
  g_object_ref(delay_audio->sequencer_duration);
  
  delay_audio->sequencer_duration->port_value.ags_port_double = ceil(16.0 * delay);

  /* add port */
  port = g_list_prepend(port, delay_audio->sequencer_duration);
  g_object_ref(delay_audio->sequencer_duration);
  
  /* notation duration */
  delay_audio->notation_duration = g_object_new(AGS_TYPE_PORT,
						"plugin-name", ags_delay_audio_plugin_name,
						"specifier", ags_delay_audio_specifier[5],
						"control-port", ags_delay_audio_control_port[5],
						"port-value-is-pointer", FALSE,
						"port-value-type", G_TYPE_DOUBLE,
						"port-value-size", sizeof(gdouble),
						"port-value-length", 1,
						NULL);
  g_object_ref(delay_audio->notation_duration);
  
  delay_audio->notation_duration->port_value.ags_port_double = ceil(AGS_NOTATION_DEFAULT_DURATION * delay);

  /* add port */
  port = g_list_prepend(port, delay_audio->notation_duration);
  g_object_ref(delay_audio->notation_duration);
  
  /* set port */
  AGS_RECALL(delay_audio)->port = port;
  
  /* notify some properties to do final configuration */
  g_signal_connect_after(delay_audio, "notify::audio",
			 G_CALLBACK(ags_delay_audio_notify_audio_callback), NULL);

  g_signal_connect_after(delay_audio, "notify::soundcard",
			 G_CALLBACK(ags_delay_audio_notify_soundcard_callback), NULL);
}

void
ags_delay_audio_set_property(GObject *gobject,
			     guint prop_id,
			     const GValue *value,
			     GParamSpec *param_spec)
{
  AgsDelayAudio *delay_audio;

  delay_audio = AGS_DELAY_AUDIO(gobject);

  switch(prop_id){
  case PROP_BPM:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->bpm){
	return;
      }

      if(delay_audio->bpm != NULL){
	g_object_unref(G_OBJECT(delay_audio->bpm));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->bpm = port;
    }
    break;
  case PROP_TACT:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->tact){
	return;
      }

      if(delay_audio->tact != NULL){
	g_object_unref(G_OBJECT(delay_audio->tact));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->tact = port;
    }
    break;
  case PROP_NOTATION_DELAY:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->notation_delay){
	return;
      }

      if(delay_audio->notation_delay != NULL){
	g_object_unref(G_OBJECT(delay_audio->notation_delay));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->notation_delay = port;
    }
    break;
  case PROP_SEQUENCER_DELAY:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->sequencer_delay){
	return;
      }

      if(delay_audio->sequencer_delay != NULL){
	g_object_unref(G_OBJECT(delay_audio->sequencer_delay));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->sequencer_delay = port;
    }
    break;
  case PROP_NOTATION_DURATION:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->notation_duration){
	return;
      }

      if(delay_audio->notation_duration != NULL){
	g_object_unref(G_OBJECT(delay_audio->notation_duration));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->notation_duration = port;
    }
    break;
  case PROP_SEQUENCER_DURATION:
    {
      AgsPort *port;

      port = (AgsPort *) g_value_get_object(value);

      if(port == delay_audio->sequencer_duration){
	return;
      }

      if(delay_audio->sequencer_duration != NULL){
	g_object_unref(G_OBJECT(delay_audio->sequencer_duration));
      }
      
      if(port != NULL){
	g_object_ref(G_OBJECT(port));
      }

      delay_audio->sequencer_duration = port;
    }
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
    break;
  }
}

void
ags_delay_audio_get_property(GObject *gobject,
			     guint prop_id,
			     GValue *value,
			     GParamSpec *param_spec)
{
  AgsDelayAudio *delay_audio;

  delay_audio = AGS_DELAY_AUDIO(gobject);

  switch(prop_id){
  case PROP_BPM:
    g_value_set_object(value, delay_audio->bpm);
    break;
  case PROP_TACT:
    g_value_set_object(value, delay_audio->tact);
    break;
  case PROP_NOTATION_DELAY:
    g_value_set_object(value, delay_audio->notation_delay);
    break;
  case PROP_SEQUENCER_DELAY:
    g_value_set_object(value, delay_audio->sequencer_delay);
    break;
  case PROP_NOTATION_DURATION:
    g_value_set_object(value, delay_audio->notation_duration);
    break;
  case PROP_SEQUENCER_DURATION:
    g_value_set_object(value, delay_audio->sequencer_duration);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
    break;
  }
}

void
ags_delay_audio_set_ports(AgsPlugin *plugin, GList *port)
{
  while(port != NULL){
    if(!strncmp(AGS_PORT(port->data)->specifier,
		"./bpm[0]",
		8)){
      g_object_set(G_OBJECT(plugin),
		   "bpm", AGS_PORT(port->data),
		   NULL);
    }else if(!strncmp(AGS_PORT(port->data)->specifier,
		      "./tact[0]",
		      9)){
      g_object_set(G_OBJECT(plugin),
		   "tact", AGS_PORT(port->data),
		   NULL);
    }else if(!strncmp(AGS_PORT(port->data)->specifier,
		      "./notation-delay[0]",
		      18)){
      g_object_set(G_OBJECT(plugin),
		   "notation-delay", AGS_PORT(port->data),
		   NULL);
    }else if(!strncmp(AGS_PORT(port->data)->specifier,
		      "./sequencer-delay[0]",
		      19)){
      g_object_set(G_OBJECT(plugin),
		   "sequencer-delay", AGS_PORT(port->data),
		   NULL);
    }else if(!strncmp(AGS_PORT(port->data)->specifier,
		      "./notation-duration[0]",
		      21)){
      g_object_set(G_OBJECT(plugin),
		   "notation-duration", AGS_PORT(port->data),
		   NULL);
    }else if(!strncmp(AGS_PORT(port->data)->specifier,
		      "./sequencer-duration[0]",
		      22)){
      g_object_set(G_OBJECT(plugin),
		   "sequencer-duration", AGS_PORT(port->data),
		   NULL);
    }

    port = port->next;
  }
}

void
ags_delay_audio_dispose(GObject *gobject)
{
  AgsDelayAudio *delay_audio;

  delay_audio = AGS_DELAY_AUDIO(gobject);

  /* bpm and tact */
  if(delay_audio->bpm != NULL){
    g_object_unref(G_OBJECT(delay_audio->bpm));

    delay_audio->bpm = NULL;
  }

  if(delay_audio->tact != NULL){
    g_object_unref(G_OBJECT(delay_audio->tact));

    delay_audio->tact = NULL;
  }

  /* delay */
  if(delay_audio->notation_delay != NULL){
    g_object_unref(G_OBJECT(delay_audio->notation_delay));

    delay_audio->notation_delay = NULL;
  }

  if(delay_audio->sequencer_delay != NULL){
    g_object_unref(G_OBJECT(delay_audio->sequencer_delay));

    delay_audio->sequencer_delay = NULL;
  }

  /* duration */
  if(delay_audio->notation_duration != NULL){
    g_object_unref(G_OBJECT(delay_audio->notation_duration));

    delay_audio->notation_duration = NULL;
  }

  if(delay_audio->sequencer_duration != NULL){
    g_object_unref(G_OBJECT(delay_audio->sequencer_duration));

    delay_audio->sequencer_duration = NULL;
  }

  /* call parent */
  G_OBJECT_CLASS(ags_delay_audio_parent_class)->dispose(gobject);
}

void
ags_delay_audio_finalize(GObject *gobject)
{
  AgsDelayAudio *delay_audio;

  delay_audio = AGS_DELAY_AUDIO(gobject);

  if(delay_audio->bpm != NULL){
    g_object_unref(G_OBJECT(delay_audio->bpm));
  }

  if(delay_audio->tact != NULL){
    g_object_unref(G_OBJECT(delay_audio->tact));
  }

  if(delay_audio->notation_delay != NULL){
    g_object_unref(G_OBJECT(delay_audio->notation_delay));
  }

  if(delay_audio->sequencer_delay != NULL){
    g_object_unref(G_OBJECT(delay_audio->sequencer_delay));
  }

  if(delay_audio->notation_duration != NULL){
    g_object_unref(G_OBJECT(delay_audio->notation_duration));
  }

  if(delay_audio->sequencer_duration != NULL){
    g_object_unref(G_OBJECT(delay_audio->sequencer_duration));
  }

  /* call parent */
  G_OBJECT_CLASS(ags_delay_audio_parent_class)->finalize(gobject);
}

void
ags_delay_audio_notify_audio_callback(GObject *gobject,
				      GParamSpec *pspec,
				      gpointer user_data)
{
  AgsDelayAudio *delay_audio;
  
  AgsAudio *audio;
  
  delay_audio = AGS_DELAY_AUDIO(gobject);

  audio = AGS_RECALL_AUDIO(delay_audio)->audio;

  if(audio == NULL){
    return;
  }

  g_signal_connect_after(audio, "notify::samplerate",
			 G_CALLBACK(ags_delay_audio_notify_samplerate_callback), delay_audio);

  g_signal_connect_after(audio, "notify::buffer-size",
			 G_CALLBACK(ags_delay_audio_notify_buffer_size_callback), delay_audio);
}

void
ags_delay_audio_notify_soundcard_callback(GObject *gobject,
					  GParamSpec *pspec,
					  gpointer user_data)
{
  AgsDelayAudio *delay_audio;

  GObject *soundcard;
  gdouble bpm;
  gdouble delay;
  gchar *str;
  
  delay_audio = AGS_DELAY_AUDIO(gobject);

  soundcard = AGS_RECALL(delay_audio)->soundcard;

  if(soundcard == NULL){
    return;
  }
  
  /*  */
  bpm = ags_soundcard_get_bpm(AGS_SOUNDCARD(soundcard));
  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));

  /* bpm */
  delay_audio->bpm->port_value.ags_port_double = bpm;

  /* tact */
  delay_audio->tact->port_value.ags_port_double = AGS_SOUNDCARD_DEFAULT_TACT;

  /* sequencer delay */
  delay_audio->sequencer_delay->port_value.ags_port_double = delay;
  
  /* notation delay */
  delay_audio->notation_delay->port_value.ags_port_double = delay;

  /* sequencer duration */
  delay_audio->sequencer_duration->port_value.ags_port_double = ceil(16.0 * delay);

  /* notation duration */  
  delay_audio->notation_duration->port_value.ags_port_double = ceil(AGS_NOTATION_DEFAULT_DURATION * delay);
}

void
ags_delay_audio_notify_samplerate_callback(GObject *gobject,
					   GParamSpec *pspec,
					   gpointer user_data)
{
  AgsDelayAudio *delay_audio;
  
  delay_audio = AGS_DELAY_AUDIO(user_data);

  ags_delay_audio_refresh_delay(delay_audio);
}

void
ags_delay_audio_notify_buffer_size_callback(GObject *gobject,
					    GParamSpec *pspec,
					    gpointer user_data)
{
  AgsDelayAudio *delay_audio;
  
  delay_audio = AGS_DELAY_AUDIO(user_data);

  ags_delay_audio_refresh_delay(delay_audio);
}

gdouble
ags_delay_audio_get_bpm(AgsTactable *tactable)
{
  AgsDelayAudio *delay_audio;
  
  gdouble bpm;

  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);
  
  /* retrieve bpm */
  g_value_init(&value, G_TYPE_DOUBLE);

  ags_port_safe_read(delay_audio->bpm, &value);
  bpm = g_value_get_double(&value);
  g_value_unset(&value);
  
  return(bpm);
}

gdouble
ags_delay_audio_get_tact(AgsTactable *tactable)
{
  AgsDelayAudio *delay_audio;
  
  gdouble tact;
  
  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);
  
  /* retrieve tact */
  g_value_init(&value, G_TYPE_DOUBLE);

  ags_port_safe_read(delay_audio->tact, &value);
  tact = g_value_get_double(&value);
  g_value_unset(&value);
  
  return(tact);
}

gdouble
ags_delay_audio_get_sequencer_duration(AgsTactable *tactable)
{
  AgsDelayAudio *delay_audio;
  
  gdouble sequencer_duration;

  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);
  
  /* retrieve sequencer_duration */
  g_value_init(&value, G_TYPE_DOUBLE);

  ags_port_safe_read(delay_audio->sequencer_duration, &value);
  sequencer_duration = g_value_get_double(&value);

  return(sequencer_duration);
}

gdouble
ags_delay_audio_get_notation_duration(AgsTactable *tactable)
{
  AgsDelayAudio *delay_audio;
  
  gdouble notation_duration;

  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);
  
  /* retrieve notation_duration */
  g_value_init(&value, G_TYPE_DOUBLE);

  ags_port_safe_read(delay_audio->notation_duration, &value);
  notation_duration = g_value_get_double(&value);

  return(notation_duration);
}

void
ags_delay_audio_change_bpm(AgsTactable *tactable, gdouble new_bpm, gdouble old_bpm)
{
  AgsDelayAudio *delay_audio;

  GObject *soundcard;

  gdouble delay;

  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);

  soundcard = AGS_RECALL(delay_audio)->soundcard;

  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));
  g_value_init(&value, G_TYPE_DOUBLE);
  
  /* -- start adjust -- */
  /* notation-delay */
  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->notation_delay, &value);
  
  /* sequencer-delay */
  g_value_reset(&value);

  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->sequencer_delay, &value);

  /**/
  g_value_reset(&value);

  g_value_set_double(&value, new_bpm);
  ags_port_safe_write(delay_audio->bpm, &value);

  /* notation-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(AGS_NOTATION_DEFAULT_DURATION * delay));
  ags_port_safe_write(delay_audio->notation_duration, &value);

  /* sequencer-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(16.0 * delay));
  ags_port_safe_write(delay_audio->sequencer_duration, &value);

  /* -- finish adjust -- */

  /* emit changed */
  ags_delay_audio_sequencer_duration_changed(delay_audio);
}

void
ags_delay_audio_change_tact(AgsTactable *tactable, gdouble new_tact, gdouble old_tact)
{
  AgsDelayAudio *delay_audio;

  GObject *soundcard;

  gdouble delay;
  
  GValue value = {0,};
  
  delay_audio = AGS_DELAY_AUDIO(tactable);

  soundcard = AGS_RECALL(delay_audio)->soundcard;

  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));
  g_value_init(&value, G_TYPE_DOUBLE);

  /* -- start adjust -- */
  /* notation-delay */
  g_value_reset(&value);

  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->notation_delay, &value);

  //  g_message("notation delay = %f", notation_delay * (old_tact / new_tact));
  
  /* sequencer-delay */
  g_value_reset(&value);

  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->sequencer_delay, &value);

  //  g_message("sequencer delay = %f", sequencer_delay * (old_tact / new_tact));

  /**/
  g_value_reset(&value);

  g_value_set_double(&value, new_tact);
  ags_port_safe_write(delay_audio->tact, &value);

  /* notation-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(AGS_NOTATION_DEFAULT_DURATION * delay));
  ags_port_safe_write(delay_audio->notation_duration, &value);

  /* sequencer-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(16.0 * delay));
  ags_port_safe_write(delay_audio->sequencer_duration, &value);

  /* -- finish adjust -- */

  /* emit changed */
  ags_delay_audio_sequencer_duration_changed(delay_audio);
}

void
ags_delay_audio_refresh_delay(AgsDelayAudio *delay_audio)
{
  GObject *soundcard;

  gdouble delay;
  
  GValue value = {0,};
  
  soundcard = AGS_RECALL(delay_audio)->soundcard;

  if(soundcard == NULL){
    return;
  }
  
  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));
  g_value_init(&value, G_TYPE_DOUBLE);

  /* -- start adjust -- */
  /* notation-delay */
  g_value_reset(&value);

  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->notation_delay, &value);

  //  g_message("notation delay = %f", notation_delay * (old_tact / new_tact));
  
  /* sequencer-delay */
  g_value_reset(&value);

  g_value_set_double(&value, delay);
  ags_port_safe_write(delay_audio->sequencer_delay, &value);

  //  g_message("sequencer delay = %f", sequencer_delay * (old_tact / new_tact));

  /* notation-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(AGS_NOTATION_DEFAULT_DURATION * delay));
  ags_port_safe_write(delay_audio->notation_duration, &value);

  /* sequencer-duration */
  g_value_reset(&value);

  g_value_set_double(&value, ceil(16.0 * delay));
  ags_port_safe_write(delay_audio->sequencer_duration, &value);

  /* -- finish adjust -- */

  /* emit changed */
  ags_delay_audio_sequencer_duration_changed(delay_audio);
}

void
ags_delay_audio_change_sequencer_duration(AgsTactable *tactable, gdouble duration)
{
  AgsDelayAudio *delay_audio;

  GObject *soundcard;
  
  gdouble delay;
  GValue value = {0,};

  delay_audio = AGS_DELAY_AUDIO(tactable);

  soundcard = AGS_RECALL(delay_audio)->soundcard;

  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));

  g_value_init(&value, G_TYPE_DOUBLE);
  g_value_set_double(&value, duration * delay * AGS_SOUNDCARD_DEFAULT_SCALE);
  ags_port_safe_write(delay_audio->sequencer_duration,
		      &value);
}

void
ags_delay_audio_change_notation_duration(AgsTactable *tactable, gdouble duration)
{
  AgsDelayAudio *delay_audio;

  GObject *soundcard;

  gdouble delay;
  gchar *str;
  
  GValue value = {0,};
  
  delay_audio = AGS_DELAY_AUDIO(tactable);

  soundcard = AGS_RECALL(delay_audio)->soundcard;
  
  delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));
  
  g_value_init(&value, G_TYPE_DOUBLE);
  g_value_set_double(&value, duration * delay * AGS_SOUNDCARD_DEFAULT_SCALE);
  ags_port_safe_write(delay_audio->notation_duration,
		      &value);
}

/**
 * ags_delay_audio_new:
 * @delay_audio: the #AgsDelayAudio
 *
 * Notation duration changed of #AgsDelayAudio
 *
 * Since: 1.0.0
 */
void
ags_delay_audio_notation_duration_changed(AgsDelayAudio *delay_audio)
{
  g_return_if_fail(AGS_IS_DELAY_AUDIO(delay_audio));

  g_object_ref((GObject *) delay_audio);
  g_signal_emit(G_OBJECT(delay_audio),
		delay_audio_signals[NOTATION_DURATION_CHANGED], 0);
  g_object_unref((GObject *) delay_audio);
}

/**
 * ags_delay_audio_new:
 * @delay_audio: the #AgsDelayAudio
 *
 * Sequencer duration changed of #AgsDelayAudio
 *
 * Since: 1.0.0
 */
void
ags_delay_audio_sequencer_duration_changed(AgsDelayAudio *delay_audio)
{
  g_return_if_fail(AGS_IS_DELAY_AUDIO(delay_audio));

  g_object_ref((GObject *) delay_audio);
  g_signal_emit(G_OBJECT(delay_audio),
		delay_audio_signals[SEQUENCER_DURATION_CHANGED], 0);
  g_object_unref((GObject *) delay_audio);
}

/**
 * ags_delay_audio_new:
 *
 * Creates an #AgsDelayAudio
 *
 * Returns: a new #AgsDelayAudio
 *
 * Since: 1.0.0
 */
AgsDelayAudio*
ags_delay_audio_new()
{
  AgsDelayAudio *delay_audio;

  delay_audio = (AgsDelayAudio *) g_object_new(AGS_TYPE_DELAY_AUDIO,
					       NULL);

  return(delay_audio);
}
