krähemann.com

Chapter 12. Putting all together

So far we have seen the most important objects involved doing an audio processing tree. Now we want to do complete example putting all together. In this example we instantiate AgsAudioThread and AgsChannelThread to play a simple pattern. The sound we use is generated using a sine wave.

In order that the threads are used we provide an appropriate AgsConfig. Further we define an AgsPattern and add the needed recalls to do playback using the AgsFxFactory.

The example creates 2 different AgsAudio objects. One called master which does the actual playback and a second called slave doing the sequencer work. Since the slave is linked to the master, we only have to start slave, which initializes the audio tree for playback.

The slave owns the audio signal and has to provide audio processing threads for it. This is done by AGS_AUDIO_OUTPUT_HAS_RECYCLING flag. We set the ags-fx staging flags and the staging program. We need to do this explicitely in view of reverse compatibility to the deprecated recall engine.

Note, here thread-safety doesn't matter. If you need to do more complex work-flows, you have to care about it. In practice you wouldn't make direct use of any struct fields. Rather use the appropriate getter/setter functions and take care of owner ship.

Usually, you wouldn't call directly void ags_channel_set_link(AgsChannel*, AgsChannel*, GError**), but rather use the AgsLinkChannel task and add it to the AgsTaskLauncher. Else, everything is fine.

Example 12.1. Simple pattern sequencer with master playback

#include <glib.h>
#include <glib-object.h>

#include <ags/libags.h>
#include <ags/libags-audio.h>

void setup_callback(AgsApplicationContext *application_context, gpointer data);

AgsAudio* setup_master(AgsApplicationContext *application_context);
AgsAudio* setup_slave(AgsApplicationContext *application_context);

#define DEFAULT_CONFIG "[generic]\n"			\
  "autosave-thread=false\n"				\
  "simple-file=true\n"					\
  "disable-feature=experimental\n"			\
  "segmentation=4/4\n"					\
  "\n"							\
  "[thread]\n"						\
  "model=super-threaded\n"				\
  "super-threaded-scope=channel\n"			\
  "lock-global=ags-thread\n"				\
  "lock-parent=ags-recycling-thread\n"			\
  "\n"							\
  "[soundcard]\n"					\
  "backend=alsa\n"					\
  "device=default\n"					\
  "samplerate=48000\n"					\
  "buffer-size=1024\n"					\
  "pcm-channels=2\n"					\
  "dsp-channels=2\n"					\
  "format=16\n"						\
  "\n"							\
  "[recall]\n"						\
  "auto-sense=true\n"					\
  "\n"


void setup_callback(AgsApplicationContext *application_context, gpointer data)
{
  AgsAudio *master, *slave;
  AgsChannel *start_output, *output;
  AgsChannel *start_input, *input;

  AgsStartAudio *start_audio;
  
  AgsThread *main_loop;
  AgsTaskLauncher *task_launcher;
  
  GError *error;

  task_launcher = ags_concurrency_provider_get_task_launcher(AGS_CONCURRENCY_PROVIDER(application_context));
  
  /* main loop */
  main_loop = ags_concurrency_provider_get_main_loop(AGS_CONCURRENCY_PROVIDER(application_context));

  /* setup audio tree */
  master = setup_master(application_context);
  slave = setup_slave(application_context);

  /* set link */
  start_input = NULL;
  start_output = NULL;
  
  g_object_get(master,
	       "input", &start_input,
	       NULL);

  g_object_get(slave,
	       "input", &start_output,
	       NULL);
  
  input = start_input;

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

  output = start_output;

  if(output != NULL){
    g_object_ref(output);
  }  

  while(input != NULL &&
        output != NULL){
    AgsChannel *next;
    
    error = NULL;
    ags_channel_set_link(input,
                         output,
                         &error);

    if(error != NULL){
      g_message("%s", error->message);
    }

    /* iterate output */
    next = ags_channel_next(output);

    g_object_unref(output);
    
    output = next;

    /* iterate input */
    next = ags_channel_next(input);

    g_object_unref(input);
    
    input = next;
  }

  start_audio = ags_start_audio_new(slave,
				    AGS_SOUND_SCOPE_SEQUENCER);

  /* launch task */
  ags_task_launcher_add_task(task_launcher,
			     start_audio);

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

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

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

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

AgsAudio*
setup_master(AgsApplicationContext *application_context)
{
  AgsAudio *audio;
  AgsChannel *channel;
  AgsChannel *start_output;
  AgsRecallContainer *playback_play_container;
  AgsRecallContainer *playback_recall_container;
  
  GObject *soundcard;

  GList *start_list;
  GList *start_recall;

  guint n_audio_channels, n_output_pads, n_input_pads;
  gint position;
  
  /* get soundcard */
  start_list = ags_sound_provider_get_soundcard(AGS_SOUND_PROVIDER(application_context));

  soundcard = start_list->data;

  /* create master playback */
  audio = ags_audio_new(soundcard);

  n_audio_channels = 2;

  n_output_pads = 1;
  n_input_pads = 1;
  
  ags_audio_set_audio_channels(audio,
                               n_audio_channels);
  
  ags_audio_set_pads(audio,
                     AGS_TYPE_OUTPUT,
                     n_output_pads);
  ags_audio_set_pads(audio,
                     AGS_TYPE_INPUT,
                     n_input_pads);

  /* create recall container */
  position = 0;
  
  playback_play_container = ags_recall_container_new();
  playback_recall_container = ags_recall_container_new();
  
  start_recall = ags_fx_factory_create(audio,
				       playback_play_container, playback_recall_container,
				       "ags-fx-playback",
				       NULL,
				       NULL,
				       0, n_audio_channels,
				       0, n_output_pads,
				       position,
				       (AGS_FX_FACTORY_ADD |
					AGS_FX_FACTORY_INPUT),
				       0);

  g_list_free_full(start_recall,
		   (GDestroyNotify) g_object_unref);
  
  /* set output soundcard channel on ags-fx-playback */
  start_output = NULL;
  
  g_object_get(audio,
	       "output", &start_output,
	       NULL);
  
  channel = start_output;

  if(channel != NULL){
    g_object_ref(channel);
  }
  
  while(channel != NULL){
    AgsChannel *next;

    GList *start_play, *play;

    start_play = NULL;
    
    g_object_get(channel,
		 "play", &start_play,
		 NULL);
    
    play = start_play;

    while((play = ags_play_template_find_type(play,
					      AGS_TYPE_FX_PLAYBACK_CHANNEL)) != NULL){
      g_object_set(play->data,
		   "output-soundcard-channel", channel->audio_channel,
		   NULL);

      /* iterate */
      play = play->next;
    }

    g_list_free_full(start_play,
		     (GDestroyNotify) g_object_unref);
  

    /* iterate */
    next = ags_channel_next(channel);

    g_object_unref(channel);
    
    channel = next;
  }

  /* unref */
  g_list_free_full(start_list,
		   (GDestroyNotify) g_object_unref);

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

  return(audio);
}

AgsAudio*
setup_slave(AgsApplicationContext *application_context)
{
  AgsAudio *audio;
  AgsPlaybackDomain *playback_domain;
  AgsChannel *channel;
  AgsChannel *start_input;
  AgsAudioSignal *audio_signal;
  AgsRecallContainer *pattern_play_container;
  AgsRecallContainer *pattern_recall_container;  
  AgsRecallContainer *buffer_play_container;
  AgsRecallContainer *buffer_recall_container;

  AgsDelayAudioRun *play_delay_audio_run;
  AgsCountBeatsAudioRun *play_count_beats_audio_run;

  GObject *soundcard;

  GList *start_list;
  GList *start_pattern;
  GList *start_recall, *recall;

  guint n_audio_channels, n_output_pads, n_input_pads;
  gint position;
  gdouble volume;
  guint current_phase, prev_phase;
  guint i, j, k;
  
  GValue value;
  
  static const guint staging_program[] = {
    (AGS_SOUND_STAGING_AUTOMATE | AGS_SOUND_STAGING_RUN_INTER | AGS_SOUND_STAGING_FX),
  };

  /* get soundcard */
  start_list = ags_sound_provider_get_soundcard(AGS_SOUND_PROVIDER(application_context));

  soundcard = start_list->data;

  /* create master playback */
  audio = ags_audio_new(soundcard);
  ags_audio_set_flags(audio,
		      (AGS_AUDIO_OUTPUT_HAS_RECYCLING |
		       AGS_AUDIO_INPUT_HAS_RECYCLING));
  ags_audio_set_ability_flags(audio, (AGS_SOUND_ABILITY_SEQUENCER));
  ags_audio_set_behaviour_flags(audio, (AGS_SOUND_BEHAVIOUR_PATTERN_MODE |
					AGS_SOUND_BEHAVIOUR_REVERSE_MAPPING |
					AGS_SOUND_BEHAVIOUR_DEFAULTS_TO_INPUT));

  /* set ags-fx staging */
  playback_domain = NULL;
  
  g_object_get(audio,
	       "playback-domain", &playback_domain,
	       NULL);

  if(playback_domain != NULL){
    for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
      AgsThread *audio_thread;
	  
      audio_thread = ags_playback_domain_get_audio_thread(playback_domain,
							  i);

      if(audio_thread != NULL){
	ags_audio_thread_set_do_fx_staging(audio_thread, TRUE);
	ags_audio_thread_set_staging_program(audio_thread,
					     staging_program,
					     1);
	    
	g_object_unref(audio_thread);
      }
    }
    
    g_object_unref(playback_domain);
  }
  
  n_audio_channels = 2;

  n_output_pads = 1;
  n_input_pads = 1;
  
  ags_audio_set_audio_channels(audio,
                               n_audio_channels);
  
  ags_audio_set_pads(audio,
                     AGS_TYPE_OUTPUT,
                     n_output_pads);
  ags_audio_set_pads(audio,
                     AGS_TYPE_INPUT,
                     n_input_pads);

  /* set sequencer ability */
  channel = audio->output;

  while(channel != NULL){
    ags_channel_set_ability_flags(channel, (AGS_SOUND_ABILITY_SEQUENCER));

    channel = channel->next;
  }
  
  /* add pattern and generate sound */
  start_input = NULL;

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

  if(channel != NULL){
    g_object_ref(channel);
  }  
  
  for(i = 0; i < n_input_pads; i++){
    for(j = 0; j < n_audio_channels; j++){
      AgsChannel *next;
      
      /* pattern */
      start_pattern = NULL;
      
      g_object_get(channel,
		   "pattern", &start_pattern,
		   NULL);

      for(k = 0; k < 16;){
        ags_pattern_toggle_bit(start_pattern->data,
                               0,
                               0,
                               k);

	/* iterate */
        k += 4;
      }

      g_list_free_full(start_pattern,
		       (GDestroyNotify) g_object_unref);

      /* sound */
      audio_signal = ags_audio_signal_new();
      ags_audio_signal_set_flags(audio_signal,
				 AGS_AUDIO_SIGNAL_TEMPLATE);
      ags_audio_signal_stream_resize(audio_signal,
                                     5);
      
      stream = audio_signal->stream;

      current_phase = 0;
      volume = 1.0;

      k = 0;
      
      while(stream != NULL){
        ags_synth_sin(soundcard, (signed short *) stream->data,
                      0, 440.0, current_phase, audio_signal->buffer_size,
                      volume);

        prev_phase = current_phase;
        current_phase = (prev_phase + (audio_signal->buffer_size) + k * audio_signal->buffer_size) % 440.0;

	/* iterate */
        stream = stream->next;
        k++;
      }

      ags_recycling_add_audio_signal(channel->first_recycling,
				     audio_signal);

      /* iterate */
      next = ags_channel_next(channel);

      g_object_unref(channel);
      
      channel = next;
    }
  }
  
  /* create recall container */
  position = 0;

  pattern_play_container = ags_recall_container_new();
  pattern_recall_container = ags_recall_container_new();

  buffer_play_container = ags_recall_container_new();
  buffer_recall_container = ags_recall_container_new();

  /* ags-fx-pattern */
  start_recall = ags_fx_factory_create(audio,
				       pattern_play_container, pattern_recall_container,
				       "ags-fx-pattern",
				       NULL,
				       NULL,
				       0, n_audio_channels,
				       0, n_input_pads,
				       position,
				       (AGS_FX_FACTORY_ADD | AGS_FX_FACTORY_INPUT),
				       0);

  g_list_free_full(start_recall,
		   (GDestroyNotify) g_object_unref);

  /* ags-fx-buffer */
  start_recall = ags_fx_factory_create(audio,
				       buffer_play_container, buffer_recall_container,
				       "ags-fx-buffer",
				       NULL,
				       NULL,
				       0, n_audio_channels,
				       0, n_input_pads,
				       position,
				       (AGS_FX_FACTORY_ADD | AGS_FX_FACTORY_INPUT),
				       0);

  g_list_free_full(start_recall,
		   (GDestroyNotify) g_object_unref);

  /* unref */
  g_list_free_full(start_list,
		   (GDestroyNotify) g_object_unref);

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

  return(audio);
}

int
main(int argc, char **argv)
{
  AgsApplicationContext *application_context;
  AgsConfig *config;

  config = ags_config_get_instance();
  ags_config_load_from_data(config,
			    DEFAULT_CONFIG,
			    strlen(DEFAULT_CONFIG));

  /* create application context */
  application_context = ags_audio_application_context_new();
  g_object_ref(application_context);
  
  g_signal_connect_after(application_context, "setup",
			 G_CALLBACK(setup_callback), NULL);
  
  ags_application_context_prepare(application_context);
  ags_application_context_setup(application_context);
  
  /* main loop run */
  g_main_loop_run(g_main_loop_new(g_main_context_default(),
				  TRUE));

  return(0);
}