/* GSequencer - Advanced GTK Sequencer
 * Copyright (C) 2005-2026 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/fx/ags_fx_pattern_audio_processor.h>

#include <ags/audio/ags_soundcard_util.h>
#include <ags/audio/ags_channel.h>
#include <ags/audio/ags_recycling.h>
#include <ags/audio/ags_audio_signal.h>
#include <ags/audio/ags_port.h>
#include <ags/audio/ags_pattern.h>
#include <ags/audio/ags_note.h>
#include <ags/audio/ags_recall_id.h>
#include <ags/audio/ags_recycling_context.h>

#include <ags/audio/midi/ags_midi_util.h>

#include <ags/audio/fx/ags_fx_pattern_audio.h>

#include <math.h>

#include <ags/i18n.h>

static void ags_fx_pattern_audio_processor_class_init(AgsFxPatternAudioProcessorClass *fx_pattern_audio_processor);
static void ags_fx_pattern_audio_processor_seekable_interface_init(AgsSeekableInterface *seekable);
static void ags_fx_pattern_audio_processor_countable_interface_init(AgsCountableInterface *countable);
static void ags_fx_pattern_audio_processor_tactable_interface_init(AgsTactableInterface *tactable);
static void ags_fx_pattern_audio_processor_init(AgsFxPatternAudioProcessor *fx_pattern_audio_processor);
static void ags_fx_pattern_audio_processor_dispose(GObject *gobject);
static void ags_fx_pattern_audio_processor_finalize(GObject *gobject);

static void ags_fx_pattern_audio_processor_seek(AgsSeekable *seekable,
						gint64 offset,
						guint whence);

static guint64 ags_fx_pattern_audio_processor_get_sequencer_counter(AgsCountable *countable);

static gdouble ags_fx_pattern_audio_processor_get_bpm(AgsTactable *tactable);
static gdouble ags_fx_pattern_audio_processor_get_tact(AgsTactable *tactable);
static void ags_fx_pattern_audio_processor_change_sequencer_duration(AgsTactable *tactable, guint64 length);
static void ags_fx_pattern_audio_processor_change_bpm(AgsTactable *tactable, gdouble new_bpm, gdouble old_bpm);
static void ags_fx_pattern_audio_processor_change_tact(AgsTactable *tactable, gdouble new_tact, gdouble old_tact);

static void ags_fx_pattern_audio_processor_run_init_pre(AgsRecall *recall);
static void ags_fx_pattern_audio_processor_run_inter(AgsRecall *recall);
static void ags_fx_pattern_audio_processor_done(AgsRecall *recall);

static void ags_fx_pattern_audio_processor_real_key_on(AgsFxPatternAudioProcessor *fx_pattern_audio_processor,
						       AgsNote *note,
						       guint velocity,
						       guint key_mode);

static void ags_fx_pattern_audio_processor_real_play(AgsFxPatternAudioProcessor *fx_pattern_audio_processor);

static void ags_fx_pattern_audio_processor_real_counter_change(AgsFxPatternAudioProcessor *fx_pattern_audio_processor);

/**
 * SECTION:ags_fx_pattern_audio_processor
 * @short_description: fx pattern audio processor
 * @title: AgsFxPatternAudioProcessor
 * @section_id:
 * @include: ags/audio/fx/ags_fx_pattern_audio_processor.h
 *
 * The #AgsFxPatternAudioProcessor class provides ports to the effect processor.
 */

static gpointer ags_fx_pattern_audio_processor_parent_class = NULL;

GType
ags_fx_pattern_audio_processor_get_type()
{
  static gsize g_define_type_id__static = 0;

  if(g_once_init_enter(&g_define_type_id__static)){
    GType ags_type_fx_pattern_audio_processor = 0;

    static const GTypeInfo ags_fx_pattern_audio_processor_info = {
      sizeof (AgsFxPatternAudioProcessorClass),
      NULL, /* base_init */
      NULL, /* base_finalize */
      (GClassInitFunc) ags_fx_pattern_audio_processor_class_init,
      NULL, /* class_finalize */
      NULL, /* class_audio_processor */
      sizeof (AgsFxPatternAudioProcessor),
      0,    /* n_preallocs */
      (GInstanceInitFunc) ags_fx_pattern_audio_processor_init,
    };

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

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

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

    ags_type_fx_pattern_audio_processor = g_type_register_static(AGS_TYPE_RECALL_AUDIO_RUN,
								 "AgsFxPatternAudioProcessor",
								 &ags_fx_pattern_audio_processor_info,
								 0);

    g_type_add_interface_static(ags_type_fx_pattern_audio_processor,
				AGS_TYPE_COUNTABLE,
				&ags_countable_interface_info);

    g_type_add_interface_static(ags_type_fx_pattern_audio_processor,
				AGS_TYPE_SEEKABLE,
				&ags_seekable_interface_info);

    g_type_add_interface_static(ags_type_fx_pattern_audio_processor,
				AGS_TYPE_TACTABLE,
				&ags_tactable_interface_info);

    g_once_init_leave(&g_define_type_id__static, ags_type_fx_pattern_audio_processor);
  }

  return(g_define_type_id__static);
}

void
ags_fx_pattern_audio_processor_class_init(AgsFxPatternAudioProcessorClass *fx_pattern_audio_processor)
{
  GObjectClass *gobject;
  AgsRecallClass *recall;
  
  ags_fx_pattern_audio_processor_parent_class = g_type_class_peek_parent(fx_pattern_audio_processor);

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

  gobject->dispose = ags_fx_pattern_audio_processor_dispose;
  gobject->finalize = ags_fx_pattern_audio_processor_finalize;

  /* AgsRecallClass */
  recall = (AgsRecallClass *) fx_pattern_audio_processor;
  
  recall->run_init_pre = ags_fx_pattern_audio_processor_run_init_pre;
  recall->run_inter = ags_fx_pattern_audio_processor_run_inter;
  recall->done = ags_fx_pattern_audio_processor_done;
  
  /* AgsFxPatternAudioProcessorClass */
  fx_pattern_audio_processor->key_on = ags_fx_pattern_audio_processor_real_key_on;

  fx_pattern_audio_processor->play = ags_fx_pattern_audio_processor_real_play;

  fx_pattern_audio_processor->counter_change = ags_fx_pattern_audio_processor_real_counter_change;
}

void
ags_fx_pattern_audio_processor_seekable_interface_init(AgsSeekableInterface *seekable)
{
  seekable->seek = ags_fx_pattern_audio_processor_seek;
}

void
ags_fx_pattern_audio_processor_countable_interface_init(AgsCountableInterface *countable)
{
  countable->get_sequencer_counter = ags_fx_pattern_audio_processor_get_sequencer_counter;
  countable->get_notation_counter = NULL;
  countable->get_wave_counter = NULL;
  countable->get_midi_counter = NULL;
}

void
ags_fx_pattern_audio_processor_tactable_interface_init(AgsTactableInterface *tactable)
{
  tactable->get_bpm = ags_fx_pattern_audio_processor_get_bpm;
  tactable->get_tact = ags_fx_pattern_audio_processor_get_tact;

  tactable->get_sequencer_duration = NULL;
  tactable->get_notation_duration = NULL;
  tactable->get_wave_duration = NULL;
  tactable->get_midi_duration = NULL;

  tactable->change_sequencer_duration = ags_fx_pattern_audio_processor_change_sequencer_duration;
  tactable->change_notation_duration = NULL;
  tactable->change_wave_duration = NULL;
  tactable->change_midi_duration = NULL;

  tactable->change_bpm = ags_fx_pattern_audio_processor_change_bpm;
  tactable->change_tact = ags_fx_pattern_audio_processor_change_tact;
}

void
ags_fx_pattern_audio_processor_init(AgsFxPatternAudioProcessor *fx_pattern_audio_processor)
{
  AGS_RECALL(fx_pattern_audio_processor)->name = "ags-fx-pattern";
  AGS_RECALL(fx_pattern_audio_processor)->version = AGS_RECALL_DEFAULT_VERSION;
  AGS_RECALL(fx_pattern_audio_processor)->build_id = AGS_RECALL_DEFAULT_BUILD_ID;
  AGS_RECALL(fx_pattern_audio_processor)->xml_type = "ags-fx-pattern-audio-processor";

  /* frame clock */
  fx_pattern_audio_processor->frame_clock = ags_frame_clock_new();

  /* 256th */
  fx_pattern_audio_processor->note_256th = NULL;
}

void
ags_fx_pattern_audio_processor_dispose(GObject *gobject)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(gobject);
  
  /* call parent */
  G_OBJECT_CLASS(ags_fx_pattern_audio_processor_parent_class)->dispose(gobject);
}

void
ags_fx_pattern_audio_processor_finalize(GObject *gobject)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(gobject);  

  g_object_unref(fx_pattern_audio_processor->frame_clock);
  
  /* call parent */
  G_OBJECT_CLASS(ags_fx_pattern_audio_processor_parent_class)->finalize(gobject);
}

void
ags_fx_pattern_audio_processor_seek(AgsSeekable *seekable,
				    gint64 offset,
				    guint whence)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;

  guint64 note_offset;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(seekable);
  
  note_offset = ags_frame_clock_get_note_offset(fx_pattern_audio_processor->frame_clock);

  switch(whence){
  case AGS_SEEK_CUR:
    {
      ags_frame_clock_set_note_offset(fx_pattern_audio_processor->frame_clock,
				      note_offset + offset);
    }
    break;
  case AGS_SEEK_SET:
    {
      ags_frame_clock_set_note_offset(fx_pattern_audio_processor->frame_clock,
				      (guint64) offset);
    }
    break;
  case AGS_SEEK_END:
    {
      ags_frame_clock_set_note_offset(fx_pattern_audio_processor->frame_clock,
				      AGS_NOTATION_DEFAULT_END + offset);
    }
    break;
  }
}

guint64
ags_fx_pattern_audio_processor_get_sequencer_counter(AgsCountable *countable)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  
  guint64 sequencer_counter;

  GRecMutex *recall_mutex;

  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(countable);
  
  /* get recall mutex */
  recall_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  /* bpm */
  g_rec_mutex_lock(recall_mutex);

  sequencer_counter = ags_frame_clock_get_note_offset(fx_pattern_audio_processor->frame_clock);
  
  g_rec_mutex_unlock(recall_mutex);
  
  return(sequencer_counter);
}

gdouble
ags_fx_pattern_audio_processor_get_bpm(AgsTactable *tactable)
{
  AgsFxPatternAudio *fx_pattern_audio;
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  AgsPort *port;
  
  gdouble bpm;
  
  GValue value = {0,};
  
  fx_pattern_audio  = NULL;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(tactable);

  port = NULL;
  
  bpm = AGS_SOUNDCARD_DEFAULT_BPM;

  g_object_get(fx_pattern_audio_processor,
	       "recall-audio", &fx_pattern_audio,
	       NULL);
  
  if(fx_pattern_audio != NULL){
    g_object_get(fx_pattern_audio,
		 "bpm", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value, G_TYPE_DOUBLE);

      ags_port_safe_read(port, &value);

      bpm = g_value_get_double(&value);

      g_value_unset(&value);
      
      g_object_unref(port);
    }
  }
  
  if(fx_pattern_audio != NULL){
    g_object_unref(fx_pattern_audio);
  }
  
  return(bpm);
}

gdouble
ags_fx_pattern_audio_processor_get_tact(AgsTactable *tactable)
{
  gdouble tact;
  
  tact = AGS_SOUNDCARD_DEFAULT_DELAY_FACTOR;
  
  return(tact);
}

void
ags_fx_pattern_audio_processor_change_sequencer_duration(AgsTactable *tactable, guint64 length)
{
  AgsFxPatternAudio *fx_pattern_audio;
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  AgsPort *port;

  GValue value = {0,};

  fx_pattern_audio  = NULL;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(tactable);

  port = NULL;
  
  g_object_get(fx_pattern_audio_processor,
	       "recall-audio", &fx_pattern_audio,
	       NULL);
  
  /* duration */
  if(fx_pattern_audio != NULL){
    g_object_get(fx_pattern_audio,
		 "loop-end", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value, G_TYPE_UINT64);
      g_value_set_uint64(&value, length);

      ags_port_safe_write(port, &value);

      g_value_unset(&value);
      
      g_object_unref(port);
    }
  }
  
  if(fx_pattern_audio != NULL){
    g_object_unref(fx_pattern_audio);
  }
}

void
ags_fx_pattern_audio_processor_change_bpm(AgsTactable *tactable, gdouble new_bpm, gdouble old_bpm)
{
  AgsFxPatternAudio *fx_pattern_audio;
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  AgsPort *port;

  GObject *output_soundcard;

  long double absolute_delay;
  
  GValue value = {0,};

  GRecMutex *fx_pattern_audio_processor_mutex;
  GRecMutex *frame_clock_mutex;
  
  output_soundcard = NULL;
  
  fx_pattern_audio  = NULL;
  
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(tactable);

  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);
  
  /* get frame clock mutex */
  frame_clock_mutex = AGS_FRAME_CLOCK_GET_OBJ_MUTEX(fx_pattern_audio_processor->frame_clock);
  
  port = NULL;
  
  g_object_get(fx_pattern_audio_processor,
	       "output-soundcard", &output_soundcard,
	       "recall-audio", &fx_pattern_audio,
	       NULL);

  /* delay */
  g_rec_mutex_lock(frame_clock_mutex);
  
  absolute_delay = fx_pattern_audio_processor->frame_clock->absolute_delay;
  
  g_rec_mutex_unlock(frame_clock_mutex);
  
  if(fx_pattern_audio != NULL){
    port = NULL;
    
    g_object_get(fx_pattern_audio,
		 "delay", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value, G_TYPE_DOUBLE);

      g_value_set_double(&value,
			 (gdouble) absolute_delay);

      ags_port_safe_write(port, &value);

      g_value_unset(&value);

      g_object_unref(port);
    }
  }
  
  /* bpm */
  if(fx_pattern_audio != NULL){
    port = NULL;
    
    g_object_get(fx_pattern_audio,
		 "bpm", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value, G_TYPE_DOUBLE);

      g_value_set_double(&value, new_bpm);

      ags_port_safe_write(port, &value);

      g_value_unset(&value);
      
      g_object_unref(port);
    }
  }

  ags_frame_clock_set_bpm(fx_pattern_audio_processor->frame_clock,
			  new_bpm);
  
  if(output_soundcard != NULL){
    g_object_unref(output_soundcard);
  }
  
  if(fx_pattern_audio != NULL){
    g_object_unref(fx_pattern_audio);
  }
}

void
ags_fx_pattern_audio_processor_change_tact(AgsTactable *tactable, gdouble new_tact, gdouble old_tact)
{
  g_warning("defaults to 4/4");
}

void
ags_fx_pattern_audio_processor_run_init_pre(AgsRecall *recall)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;

  GRecMutex *fx_pattern_audio_processor_mutex;

  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(recall);
  
  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  /* frame clock */
  ags_frame_clock_set_start_note_offset(fx_pattern_audio_processor->frame_clock,
					ags_soundcard_get_start_note_offset(AGS_SOUNDCARD(AGS_RECALL(fx_pattern_audio_processor)->output_soundcard)));
  
  ags_frame_clock_start(fx_pattern_audio_processor->frame_clock);

  /* call parent */
  AGS_RECALL_CLASS(ags_fx_pattern_audio_processor_parent_class)->run_init_pre(recall);
}

void
ags_fx_pattern_audio_processor_run_inter(AgsRecall *recall)
{
  AgsFxPatternAudio *fx_pattern_audio;
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  AgsRecallID *recall_id;
  AgsRecyclingContext *parent_recycling_context, *recycling_context;
  
  GObject *output_soundcard;
  
  gint sound_scope;
  gdouble delay_counter;
  gboolean do_loop;
  guint64 loop_start, loop_end;
  guint64 note_offset;

  GValue value = {0,};
   
  GRecMutex *fx_pattern_audio_processor_mutex;
 
  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(recall);
  
  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  recall_id = NULL;

  sound_scope = ags_recall_get_sound_scope(recall);

  g_object_get(recall,
	       "recall-id", &recall_id,
	       NULL);

  if(!ags_recall_id_check_sound_scope(recall_id, sound_scope)){
    if(recall_id != NULL){
      g_object_unref(recall_id);
    }

    AGS_RECALL_CLASS(ags_fx_pattern_audio_processor_parent_class)->run_inter(recall);
    
    return;
  }

  output_soundcard = NULL;
  
  fx_pattern_audio = NULL;
  
  recycling_context = NULL;
  parent_recycling_context = NULL;
  
  g_object_get(recall,
	       "output-soundcard", &output_soundcard,
	       "recall-audio", &fx_pattern_audio,
	       NULL);
  
  g_object_get(recall_id,
	       "recycling-context", &recycling_context,
	       NULL);

  g_object_get(recycling_context,
	       "parent", &parent_recycling_context,
	       NULL);
  
  /* loop */
  do_loop = FALSE;
  
  loop_start = AGS_FX_PATTERN_AUDIO_DEFAULT_LOOP_START;
  loop_end = AGS_FX_PATTERN_AUDIO_DEFAULT_LOOP_END;
  
  if(fx_pattern_audio != NULL){
    AgsPort *port;

    /* loop */
    g_object_get(fx_pattern_audio,
		 "loop", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_BOOLEAN);
    
      ags_port_safe_read(port,
			 &value);

      do_loop = g_value_get_boolean(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }

    /* loop-start */
    g_object_get(fx_pattern_audio,
		 "loop-start", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_UINT64);
    
      ags_port_safe_read(port,
			 &value);

      loop_start = g_value_get_uint64(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }

    /* loop-end */
    g_object_get(fx_pattern_audio,
		 "loop-end", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_UINT64);
    
      ags_port_safe_read(port,
			 &value);

      loop_end = g_value_get_uint64(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }
  }
  
  /* frame clock */
  ags_frame_clock_copy_time(fx_pattern_audio_processor->frame_clock,
			    (AgsFrameClock *) ags_soundcard_get_frame_clock(AGS_SOUNDCARD(output_soundcard)));

  note_offset = ags_frame_clock_get_note_offset(fx_pattern_audio_processor->frame_clock);
  
  if(do_loop &&
     note_offset >= loop_end){
    ags_frame_clock_set_note_offset(fx_pattern_audio_processor->frame_clock,
				    loop_start + ((note_offset - loop_end) % (loop_end - loop_start)));
  }

  /* run */
  if(ags_recall_id_check_sound_scope(recall_id, AGS_SOUND_SCOPE_SEQUENCER)){
    if(parent_recycling_context == NULL){
      ags_fx_pattern_audio_processor_play(fx_pattern_audio_processor);
    }
  }
  
  /* counter change */
  ags_fx_pattern_audio_processor_counter_change(fx_pattern_audio_processor);

  if(output_soundcard != NULL){
    g_object_unref(output_soundcard);
  }
  
  if(recall_id != NULL){
    g_object_unref(recall_id);
  }

  if(recycling_context != NULL){
    g_object_unref(recycling_context);
  }

  if(parent_recycling_context != NULL){
    g_object_unref(parent_recycling_context);
  }
  
  /* call parent */
  AGS_RECALL_CLASS(ags_fx_pattern_audio_processor_parent_class)->run_inter(recall);
}

void
ags_fx_pattern_audio_processor_done(AgsRecall *recall)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;
  
  GRecMutex *fx_pattern_audio_processor_mutex;

  fx_pattern_audio_processor = AGS_FX_PATTERN_AUDIO_PROCESSOR(recall);
  
  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  ags_frame_clock_stop(fx_pattern_audio_processor->frame_clock);
  
  /* call parent */
  AGS_RECALL_CLASS(ags_fx_pattern_audio_processor_parent_class)->done(recall);
}

void
ags_fx_pattern_audio_processor_real_key_on(AgsFxPatternAudioProcessor *fx_pattern_audio_processor,
					   AgsNote *note,
					   guint velocity,
					   guint key_mode)
{
  AgsAudio *audio;
  AgsChannel *start_input;
  AgsChannel *input, *selected_input;
  AgsRecallID *recall_id;
  AgsRecyclingContext *recycling_context;
  AgsFxPatternAudio *fx_pattern_audio;
  AgsPort *port;

  gdouble delay;
  guint input_pads;
  guint audio_channel;
  guint y;
  
  GValue value = {0,};

  GRecMutex *fx_pattern_audio_processor_mutex;

  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  audio = NULL;

  start_input = NULL;

  audio_channel = 0;
  
  recall_id = NULL;

  fx_pattern_audio = NULL;
  
  g_object_get(fx_pattern_audio_processor,
	       "audio", &audio,
	       "recall-id", &recall_id,
	       "recall-audio", &fx_pattern_audio,
	       "audio-channel", &audio_channel,
	       NULL);

  g_object_get(audio,
    	       "input", &start_input,
	       NULL);
  
  g_object_get(recall_id,
	       "recycling-context", &recycling_context,
	       NULL);

  /* get delay */
  delay = AGS_SOUNDCARD_DEFAULT_DELAY;
  
  if(fx_pattern_audio != NULL){        
    g_object_get(fx_pattern_audio,
		 "delay", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_DOUBLE);
    
      ags_port_safe_read(port,
			 &value);

      delay = g_value_get_double(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }
  }

  g_object_get(audio,
	       "input-pads", &input_pads,
	       NULL);
  
  g_object_get(note,
	       "y", &y,
	       NULL);
  
  input = ags_channel_nth(start_input,
			  audio_channel);

#if 0
  if(ags_audio_test_behaviour_flags(audio, AGS_SOUND_BEHAVIOUR_REVERSE_MAPPING)){
    selected_input = ags_channel_pad_nth(input,
					 input_pads - y - 1);
  }else{
    selected_input = ags_channel_pad_nth(input,
					 y);
  }
#else
  selected_input = ags_channel_pad_nth(input,
				       y);
#endif
  
  if(selected_input != NULL){
    AgsRecycling *first_recycling, *last_recycling;
    AgsRecycling *recycling, *next_recycling;
    AgsRecycling *end_recycling;
    AgsRecallID *child_recall_id;
    
    GObject *output_soundcard;

    GList *start_list, *list;

    guint attack;

    output_soundcard = NULL;

    first_recycling = NULL;
    last_recycling = NULL;
    
    g_object_get(selected_input,
		 "output-soundcard", &output_soundcard,
		 "first-recycling", &first_recycling,
		 "last-recycling", &last_recycling,
		 NULL);

    attack = ags_frame_clock_get_note_frame_offset(fx_pattern_audio_processor->frame_clock) % (guint64) ags_frame_clock_get_buffer_size(fx_pattern_audio_processor->frame_clock);
    
    end_recycling = ags_recycling_next(last_recycling);

    /* get child recall id */
    g_object_get(selected_input,
		 "recall-id", &start_list,
		 NULL);

    list = start_list;
    child_recall_id = NULL;

    while(child_recall_id == NULL &&
	  list != NULL){
      AgsRecyclingContext *current_recycling_context, *current_parent_recycling_context;

      g_object_get(list->data,
		   "recycling-context", &current_recycling_context,
		   NULL);

      g_object_get(current_recycling_context,
		   "parent", &current_parent_recycling_context,
		   NULL);
	  
      if(current_parent_recycling_context == recycling_context){
	child_recall_id = (AgsRecallID *) list->data;
	g_object_ref(child_recall_id);
      }

      if(current_recycling_context != NULL){
	g_object_unref(current_recycling_context);
      }

      if(current_parent_recycling_context != NULL){
	g_object_unref(current_parent_recycling_context);
      }
      
      /* iterate */
      list = list->next;
    }

    recycling = first_recycling;
    g_object_ref(recycling);

//    g_message(" - audio processor");
    
    while(recycling != end_recycling){
      AgsAudioSignal *template, *audio_signal;

      GRecMutex *recycling_mutex;

      recycling_mutex = AGS_RECYCLING_GET_OBJ_MUTEX(recycling);

      g_rec_mutex_lock(recycling_mutex);

      template = ags_audio_signal_get_default_template(recycling->audio_signal);
      
      g_rec_mutex_unlock(recycling_mutex);
      
      /* create audio signal */
      audio_signal = ags_audio_signal_new((GObject *) output_soundcard,
					  (GObject *) recycling,
					  (GObject *) child_recall_id);
      ags_audio_signal_set_flags(audio_signal, (AGS_AUDIO_SIGNAL_STREAM |
						AGS_AUDIO_SIGNAL_SLICE_ALLOC));
      g_object_set(audio_signal,
		   "default-template", template,
		   "note", note,
		   "attack", attack,
		   NULL);

      ags_audio_signal_stream_resize(audio_signal,
				     2);

      audio_signal->stream_current = audio_signal->stream;
      
      ags_connectable_connect(AGS_CONNECTABLE(audio_signal));
      ags_recycling_add_audio_signal(recycling,
				     audio_signal);
//      g_message(" `- added");
    
      /* iterate */
      next_recycling = ags_recycling_next(recycling);

      g_object_unref(recycling);

      recycling = next_recycling;
    }

    if(output_soundcard != NULL){
      g_object_unref(output_soundcard);
    }

    if(first_recycling != NULL){
      g_object_unref(first_recycling);
    }

    if(last_recycling != NULL){
      g_object_unref(last_recycling);
    }

    if(recycling != NULL){
      g_object_unref(recycling);
    }
    
    if(end_recycling != NULL){
      g_object_unref(end_recycling);
    }

    g_list_free_full(start_list,
		     g_object_unref);

    if(child_recall_id != NULL){
      g_object_unref(child_recall_id);
    }
  }

  if(audio != NULL){
    g_object_unref(audio);
  }
  
  if(start_input != NULL){
    g_object_unref(start_input);
  }

  if(input != NULL){
    g_object_unref(input);
  }

  if(selected_input != NULL){
    g_object_unref(selected_input);
  }

  if(recall_id != NULL){
    g_object_unref(recall_id);
  }

  if(recycling_context != NULL){
    g_object_unref(recycling_context);
  }
  
  if(fx_pattern_audio != NULL){
    g_object_unref(fx_pattern_audio);
  }
}

void
ags_fx_pattern_audio_processor_key_on(AgsFxPatternAudioProcessor *fx_pattern_audio_processor,
				      AgsNote *note,
				      guint velocity,
				      guint key_mode)
{
  g_return_if_fail(AGS_IS_FX_PATTERN_AUDIO_PROCESSOR(fx_pattern_audio_processor));

  g_object_ref(fx_pattern_audio_processor);
  g_object_ref(note);
  
  if(AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->key_on != NULL){
    AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->key_on(fx_pattern_audio_processor,
										 note,
										 velocity,
										 key_mode);
  }
  
  g_object_unref(fx_pattern_audio_processor);
  g_object_unref(note);
}

void
ags_fx_pattern_audio_processor_real_play(AgsFxPatternAudioProcessor *fx_pattern_audio_processor)
{
  AgsAudio *audio;
  AgsChannel *start_input, *input;
  AgsChannel *channel, *next_pad;
  AgsNote *note;
  
  AgsFxPatternAudio *fx_pattern_audio;
  
  GObject *output_soundcard;
  
  guint64 note_offset_absolute;
  guint64 note_offset_absolute_start;
  guint64 offset_counter;
  guint64 loop_end;
  guint audio_channel;
  gfloat bank_index_0;
  gfloat bank_index_1;
  gboolean has_16th_pulse;
  
  GValue value = {0,};
  
  GRecMutex *fx_pattern_audio_mutex;
  GRecMutex *fx_pattern_audio_processor_mutex;

  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  audio = NULL;

  start_input = NULL;

  output_soundcard = NULL;
  
  fx_pattern_audio = NULL;

  audio_channel = 0;
  
  g_object_get(fx_pattern_audio_processor,
	       "audio", &audio,
	       "output-soundcard", &output_soundcard,
	       "recall-audio", &fx_pattern_audio,
	       "audio-channel", &audio_channel,
	       NULL);

  g_object_get(audio,
	       "input", &start_input,
	       NULL);

  g_rec_mutex_lock(fx_pattern_audio_processor_mutex);
    
  offset_counter = ags_frame_clock_get_note_offset(fx_pattern_audio_processor->frame_clock);

  has_16th_pulse = ags_frame_clock_get_has_16th_pulse(fx_pattern_audio_processor->frame_clock);

  note_offset_absolute_start = ags_frame_clock_get_start_note_offset(fx_pattern_audio_processor->frame_clock);
  
  g_rec_mutex_unlock(fx_pattern_audio_processor_mutex);

  if(!has_16th_pulse){
    return;
  }

  note = NULL;

  bank_index_0 = 0.0;
  bank_index_1 = 0.0;

  loop_end = 16;
  
  if(fx_pattern_audio != NULL){
    AgsPort *port;

    /* bank index 0 */
    g_object_get(fx_pattern_audio,
		 "bank-index-0", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_FLOAT);
    
      ags_port_safe_read(port,
			 &value);

      bank_index_0 = g_value_get_float(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }

    /* bank index 1 */
    g_object_get(fx_pattern_audio,
		 "bank-index-1", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_FLOAT);
    
      ags_port_safe_read(port,
			 &value);

      bank_index_1 = g_value_get_float(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }

    /* loop-end */
    g_object_get(fx_pattern_audio,
		 "loop-end", &port,
		 NULL);

    if(port != NULL){
      g_value_init(&value,
		   G_TYPE_UINT64);
    
      ags_port_safe_read(port,
			 &value);

      loop_end = g_value_get_uint64(&value);
      g_value_unset(&value);

      g_object_unref(port);
    }
  }

  note_offset_absolute = ags_frame_clock_get_absolute_note_offset(fx_pattern_audio_processor->frame_clock);
  
  /* get input */
  input = ags_channel_nth(start_input,
			  audio_channel);

  channel = input;
  g_object_ref(channel);
  
  while(channel != NULL){
    AgsPattern *current_pattern;
    
    GList *start_pattern;

    guint pad;
    
    current_pattern = NULL;

    pad = 0;
    
    g_object_get(channel,
		 "pattern", &start_pattern,
		 "pad", &pad,
		 NULL);

    if(start_pattern != NULL){
      current_pattern = start_pattern->data;
    }
    
    if(ags_pattern_get_bit(current_pattern,
			   (guint) bank_index_0,
			   (guint) bank_index_1,
			   ((note_offset_absolute - note_offset_absolute_start) % loop_end))){
      note = ags_pattern_get_note(current_pattern,
				  ((note_offset_absolute - note_offset_absolute_start) % loop_end));

      ags_fx_pattern_audio_processor_key_on(fx_pattern_audio_processor,
					    note,
					    AGS_FX_PATTERN_AUDIO_PROCESSOR_DEFAULT_KEY_ON_VELOCITY,
					    0);

      if(note != NULL){
	g_object_unref(note);
      }
    }
    
    g_list_free_full(start_pattern,
		     (GDestroyNotify) g_object_unref);

    /* iterate */
    next_pad = ags_channel_next_pad(channel);

    g_object_unref(channel);

    channel = next_pad;
  }
  
  if(start_input != NULL){
    g_object_unref(start_input);
  }

  if(input != NULL){
    g_object_unref(input);
  }

  if(fx_pattern_audio != NULL){
    g_object_unref(fx_pattern_audio);
  }

  if(audio != NULL){
    g_object_unref(audio);
  }
}

void
ags_fx_pattern_audio_processor_play(AgsFxPatternAudioProcessor *fx_pattern_audio_processor)
{
  g_return_if_fail(AGS_IS_FX_PATTERN_AUDIO_PROCESSOR(fx_pattern_audio_processor));

  g_object_ref(fx_pattern_audio_processor);

  if(AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->play != NULL){
    AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->play(fx_pattern_audio_processor);
  }
  
  g_object_unref(fx_pattern_audio_processor);
}

void
ags_fx_pattern_audio_processor_real_counter_change(AgsFxPatternAudioProcessor *fx_pattern_audio_processor)
{
  GRecMutex *fx_pattern_audio_processor_mutex;
  
  fx_pattern_audio_processor_mutex = AGS_RECALL_GET_OBJ_MUTEX(fx_pattern_audio_processor);

  //  ags_frame_clock_increment_counter(fx_pattern_audio_processor->frame_clock);
}

void
ags_fx_pattern_audio_processor_counter_change(AgsFxPatternAudioProcessor *fx_pattern_audio_processor)
{
  g_return_if_fail(AGS_IS_FX_PATTERN_AUDIO_PROCESSOR(fx_pattern_audio_processor));

  g_object_ref(fx_pattern_audio_processor);
  
  if(AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->counter_change != NULL){
    AGS_FX_PATTERN_AUDIO_PROCESSOR_GET_CLASS(fx_pattern_audio_processor)->counter_change(fx_pattern_audio_processor);
  }
  
  g_object_unref(fx_pattern_audio_processor);
}

/**
 * ags_fx_pattern_audio_processor_new:
 * @audio: the #AgsAudio
 *
 * Create a new instance of #AgsFxPatternAudioProcessor
 *
 * Returns: the new #AgsFxPatternAudioProcessor
 *
 * Since: 3.3.0
 */
AgsFxPatternAudioProcessor*
ags_fx_pattern_audio_processor_new(AgsAudio *audio)
{
  AgsFxPatternAudioProcessor *fx_pattern_audio_processor;

  fx_pattern_audio_processor = (AgsFxPatternAudioProcessor *) g_object_new(AGS_TYPE_FX_PATTERN_AUDIO_PROCESSOR,
									   "audio", audio,
									   NULL);

  return(fx_pattern_audio_processor);
}
