// ------------------------------------------------------------------------
// eca-object-factory.cpp: Abstract factory for creating libecasound 
//                         objects.
// Copyright (C) 2000-2005,2007,2008 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3
//
// This program 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 2 of the License, or
// (at your option) any later version.
// 
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <algorithm> /* ANSI-C++: find() */
#include <list>
#include <map>
#include <string>
#include <pthread.h>

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#ifdef ECA_COMPILE_JACK
#include <jack/jack.h>
#endif

#include <kvu_dbc.h>
#include "kvu_locks.h"
#include <kvu_numtostr.h>
#include <kvu_message_item.h>

#include "audioio.h"
#include "audioio-loop.h"
#include "midiio.h"
#include "audiofx_ladspa.h"
#include "audiofx_lv2.h"
#include "generic-controller.h"
#include "eca-static-object-maps.h"
#include "eca-object-map.h"
#include "eca-preset-map.h"
#include "eca-object-factory.h"
#include "eca-resources.h"
#include "eca-logger.h"

/**
 * Import std namespace.
 */
using std::list;
using std::map;
using std::string;

/**
 * Initialize static member variables.
 */

ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::audio_io_rt_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::audio_io_nonrt_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::chain_operator_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::lv2_plugin_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::ladspa_plugin_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::ladspa_plugin_id_map_repp = 0;
ECA_PRESET_MAP* ECA_OBJECT_FACTORY::preset_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::controller_map_repp = 0;
ECA_OBJECT_MAP* ECA_OBJECT_FACTORY::midi_device_map_repp = 0;

pthread_mutex_t ECA_OBJECT_FACTORY::lock_rep = PTHREAD_MUTEX_INITIALIZER;

/**
 * Definitions for static member functions.
 */

/**
 * Returns an object map containing all registered
 * realtime audio i/o object types.
 *
 * All stored objects are derived from AUDIO_IO_DEVICE.
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::audio_io_rt_map(void) 
{
  //
  // Note! Below we use the Double-Checked Locking Pattern
  //       to protect against concurrent access

  if (audio_io_rt_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (audio_io_rt_map_repp == 0) {
      audio_io_rt_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_audio_io_rt_objects(audio_io_rt_map_repp);
    }
  }
  return *audio_io_rt_map_repp;
}

/**
 * Returns an object map containing all registered
 * non-realtime audio i/o object types.
 *
 * All stored objects are derived from AUDIO_IO.
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::audio_io_nonrt_map(void) 
{
  if (audio_io_nonrt_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (audio_io_nonrt_map_repp == 0) {
      audio_io_nonrt_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_audio_io_nonrt_objects(audio_io_nonrt_map_repp);
    }
  }
  return *audio_io_nonrt_map_repp;
}

/**
 * Returns an object map containing all registered
 * chain operator object types.
 *
 * All stored objects are derived from CHAIN_OPERATOR.
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::chain_operator_map(void) 
{
  if (chain_operator_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (chain_operator_map_repp == 0) {
      chain_operator_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_chain_operator_objects(chain_operator_map_repp);
    }
  }
  return *chain_operator_map_repp;
}

/**
 * Returns an object map containing all registered
 * LV2 plugin types.
 *
 * All stored objects are derived from EFFECT_LV2.
 *
 * @see ladspa_plugin_id_map()
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::lv2_plugin_map(void) 
{
  if (lv2_plugin_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (lv2_plugin_map_repp == 0) {
      lv2_plugin_map_repp = new ECA_OBJECT_MAP();
      DBC_CHECK(lv2_plugin_map_repp != 0);

      /* note: matching LADSPA unique names must be case sensitive */
      lv2_plugin_map_repp->toggle_case_sensitive_expressions(true);
      ECA_STATIC_OBJECT_MAPS::register_lv2_plugin_objects(lv2_plugin_map_repp);
    }
  }
  return *lv2_plugin_map_repp;
}

/**
 * Returns an object map containing all registered
 * LADSPA plugin types.
 * 
 *
 * All stored objects are derived from EFFECT_LADSPA.
 *
 * @see ladspa_plugin_id_map()
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::ladspa_plugin_map(void) 
{
  if (ladspa_plugin_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (ladspa_plugin_map_repp == 0) {
      ladspa_plugin_map_repp = new ECA_OBJECT_MAP();
      DBC_CHECK(ladspa_plugin_map_repp != 0);

      /* note: matching LADSPA unique names must be case sensitive */
      ladspa_plugin_map_repp->toggle_case_sensitive_expressions(true);

      ECA_STATIC_OBJECT_MAPS::register_ladspa_plugin_objects(ladspa_plugin_map_repp);
    }
  }
  return *ladspa_plugin_map_repp;
}

/**
 * Returns an object map containing all registered
 * LADSPA plugin types. Plugins are identified using
 * their unique LADSPA id number.
 * 
 * All stored objects are derived from EFFECT_LADSPA.
 *
 * @see ladspa_plugin_map()
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::ladspa_plugin_id_map(void) 
{
  if (ladspa_plugin_id_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (ladspa_plugin_id_map_repp == 0) {
      ladspa_plugin_id_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_ladspa_plugin_id_objects(ladspa_plugin_id_map_repp);
    }
  }
  return *ladspa_plugin_id_map_repp;
}

/**
 * Returns an object map containing all registered
 * chain operator preset object types.
 *
 * All stored objects are derived from PRESET.
 */
ECA_PRESET_MAP& ECA_OBJECT_FACTORY::preset_map(void) 
{
  if (preset_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (preset_map_repp == 0) {
      preset_map_repp = new ECA_PRESET_MAP();
      ECA_STATIC_OBJECT_MAPS::register_preset_objects(preset_map_repp);
    }
  }
  return *preset_map_repp;
}

/**
 * Returns an object map containing all registered
 * controller object types.
 *
 * All stored objects are derived from GENERIC_CONTROLLER.
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::controller_map(void) 
{
  if (controller_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (controller_map_repp == 0) {
      controller_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_controller_objects(controller_map_repp);
    }
  }
  return *controller_map_repp;
}

/**
 * Returns an object map containing all registered
 * MIDI-device types.
 *
 *
 * All stored objects are derived from MIDI_IO.
 */
ECA_OBJECT_MAP& ECA_OBJECT_FACTORY::midi_device_map(void) 
{
  if (midi_device_map_repp == 0) {
    KVU_GUARD_LOCK guard(&ECA_OBJECT_FACTORY::lock_rep);
    if (midi_device_map_repp == 0) {
      midi_device_map_repp = new ECA_OBJECT_MAP();
      ECA_STATIC_OBJECT_MAPS::register_midi_device_objects(midi_device_map_repp);
    }
  }
  return *midi_device_map_repp;
}

/**
 * Create a new audio object based on the formatted argument string.
 *
 * @param arg a formatted string describing an audio object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre arg.empty() != true
 */
AUDIO_IO* ECA_OBJECT_FACTORY::create_audio_object(const string& arg)
{
  // --
  DBC_REQUIRE(arg.empty() != true);
  // --

  int args_given = kvu_get_number_of_arguments(arg);
 
  string fname = kvu_get_argument_number(1, arg);
  const AUDIO_IO* main_file = 0;
  main_file = dynamic_cast<const AUDIO_IO*>(ECA_OBJECT_FACTORY::audio_io_rt_map().object_expr(fname));
  if (main_file == 0) {
    main_file = dynamic_cast<const AUDIO_IO*>(ECA_OBJECT_FACTORY::audio_io_nonrt_map().object_expr(fname));
  }

  AUDIO_IO* new_file = 0;
  if (main_file != 0) {
    new_file = main_file->new_expr();

    ECA_LOG_MSG(ECA_LOGGER::user_objects,
		"Object \"" + arg + "\" created, type \"" + new_file->name() + 
		"\". Has " + kvu_numtostr(new_file->number_of_params()) + 
		" parameter(s) (variable: " + (new_file->variable_params() == true ? string("yes).") : string("no).")));

    /* if more params are given and the object supports 
     * variable number of args, pass them all to the object */
    int params = new_file->number_of_params();
    if (new_file->variable_params() &&
	args_given > params)
      params = args_given;
      
    for(int n = 0; n < params; n++) {
      new_file->set_parameter(n + 1, kvu_get_argument_number(n + 1, arg));
    }
  }
  return new_file;
}

/**
 * Create a new MIDI-device object based on the formatted argument string.
 *
 * @param arg a formatted string describing a MIDI-device object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * require:
 *  arg.empty() != true
 */
MIDI_IO* ECA_OBJECT_FACTORY::create_midi_device(const string& arg)
{
  // --------
  DBC_REQUIRE(arg.empty() != true);
  // --------
 
  string fname = kvu_get_argument_number(1, arg);

  const MIDI_IO* device = 0;
  device = dynamic_cast<const MIDI_IO*>(ECA_OBJECT_FACTORY::midi_device_map().object_expr(fname));

  MIDI_IO* new_device = 0;
  if (device != 0) {
    new_device = device->new_expr();
    ECA_LOG_MSG(ECA_LOGGER::user_objects, "Object \"" + arg + "\" created, type \"" + new_device->name() + "\". Has " + kvu_numtostr(new_device->number_of_params()) + " parameter(s).");
    for(int n = 0; n < new_device->number_of_params(); n++) {
      new_device->set_parameter(n + 1, kvu_get_argument_number(n + 1, arg));
    }
  }
  return new_device;
}

/**
 * Create a new loop input object.
 *
 * @param arg a formatted string describing an loop object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.empty() != true
 */
AUDIO_IO* ECA_OBJECT_FACTORY::create_loop_input(const string& argu,
						map<string,LOOP_DEVICE*>* loop_map)
{
  // --------
  DBC_REQUIRE(argu.empty() != true);
  // --------

  LOOP_DEVICE* p = 0;
  string tname = kvu_get_argument_number(1, argu);
  if (tname == "loop") {
    string id = kvu_get_argument_number(2, argu);
    p = new LOOP_DEVICE(id);
    if (loop_map->find(id) == loop_map->end()) { 
      (*loop_map)[id] = p;
    }
    else
      p = (*loop_map)[id];

    p->register_input();
  }
  
  return p;
}

/**
 * Create a new loop output object.
 *
 * @param arg a formatted string describing an loop object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.empty() != true
 */
AUDIO_IO* ECA_OBJECT_FACTORY::create_loop_output(const string& argu,
						 map<string,LOOP_DEVICE*>* loop_map)
{
  // --------
  DBC_REQUIRE(argu.empty() != true);
  // --------

  LOOP_DEVICE* p = 0;
  string tname = kvu_get_argument_number(1, argu);
  if (tname == "loop") {
    string id = kvu_get_argument_number(2, argu);
    p = new LOOP_DEVICE(id);
    if (loop_map->find(id) == loop_map->end()) { 
      (*loop_map)[id] = p;
    }
    else
      p = (*loop_map)[id];

    p->register_output();
  }
  
  return p;
}

/**
 * Creates a new LADSPA plugin.
 *
 * @param arg a formatted string describing an LADSPA object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.size() > 0
 * @pre argu[0] == '-'
 */
CHAIN_OPERATOR* ECA_OBJECT_FACTORY::create_ladspa_plugin (const string& argu)
{
  // --------
  DBC_REQUIRE(argu.size() > 0);
  DBC_REQUIRE(argu[0] == '-');
  // --------

  MESSAGE_ITEM otemp;
  otemp.setprecision(3);
  const CHAIN_OPERATOR* cop = 0;
  string prefix = kvu_get_argument_prefix(argu);
  if (prefix == "el" || prefix == "eli") {
    string unique = kvu_get_argument_number(1, argu);
    if (prefix == "el") 
      cop = dynamic_cast<const CHAIN_OPERATOR*>(ECA_OBJECT_FACTORY::ladspa_plugin_map().object(unique));
    else 
      cop = dynamic_cast<const CHAIN_OPERATOR*>(ECA_OBJECT_FACTORY::ladspa_plugin_id_map().object(unique));

    CHAIN_OPERATOR* new_cop = 0;
    if (cop != 0) {
      new_cop = dynamic_cast<CHAIN_OPERATOR*>(cop->new_expr());

      ECA_LOG_MSG(ECA_LOGGER::user_objects, 
		  "Creating LADSPA-plugin \"" + new_cop->name() + "\"");

      otemp << "Setting parameters: ";
      for(int n = 0; n < new_cop->number_of_params(); n++) {
	new_cop->set_parameter(n + 1, atof(kvu_get_argument_number(n + 2, argu).c_str()));
	otemp << new_cop->get_parameter_name(n + 1) << " = ";
	otemp << new_cop->get_parameter(n + 1);
	if (n + 1 < new_cop->number_of_params()) otemp << ", ";
      }
      ECA_LOG_MSG(ECA_LOGGER::user_objects, otemp.to_string());
    }
    else {
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "ERROR: Unable to find LADSPA plugin \"" + unique + "\"");

    }
    return new_cop;
  }
  return 0;
}


/**
 * Creates a new LV2 plugin.
 *
 * @param arg a formatted string describing an LV2 object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.size() > 0
 * @pre argu[0] == '-'
 */
CHAIN_OPERATOR* ECA_OBJECT_FACTORY::create_lv2_plugin (const string& argu)
{
  // --------
  DBC_REQUIRE(argu.size() > 0);
  DBC_REQUIRE(argu[0] == '-');
  // --------

  MESSAGE_ITEM otemp;
  otemp.setprecision(3);
  const CHAIN_OPERATOR* cop = 0;
  string prefix = kvu_get_argument_prefix(argu);
  if (prefix == "elv2") {
    string unique = kvu_get_argument_number(1, argu);
	cop = dynamic_cast<const CHAIN_OPERATOR*>(ECA_OBJECT_FACTORY::lv2_plugin_map().object(unique));
    CHAIN_OPERATOR* new_cop = 0;
    if (cop != 0) {
      new_cop = dynamic_cast<CHAIN_OPERATOR*>(cop->new_expr());

      ECA_LOG_MSG(ECA_LOGGER::user_objects, 
		  "Creating LV2-plugin \"" + new_cop->name() + "\"");

      otemp << "Setting parameters: ";
      for(int n = 0; n < new_cop->number_of_params(); n++) {
	new_cop->set_parameter(n + 1, atof(kvu_get_argument_number(n + 2, argu).c_str()));
	otemp << new_cop->get_parameter_name(n + 1) << " = ";
	otemp << new_cop->get_parameter(n + 1);
	if (n + 1 < new_cop->number_of_params()) otemp << ", ";
      }
      ECA_LOG_MSG(ECA_LOGGER::user_objects, otemp.to_string());
    }
    else {
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "ERROR: Unable to find LV2 plugin \"" + unique + "\"");

    }
    return new_cop;
  }
  return 0;
}


/**
 * VST not currently actively supported due to licensing
 * issues.
 */
#if 0
/**
 * Creates a new VST1.0/2.0 plugin.
 *
 * Notes: VST support is currently not used 
 *        because of licensing problems 
 *        (distribution of VST-headers is not
 *        allowed).
 */
CHAIN_OPERATOR* ECA_OBJECT_FACTORY::create_vst_plugin (const string& argu)
{
  // --------
  DBC_REQUIRE(argu.size() > 0);
  DBC_REQUIRE(argu[0] == '-');
  // --------

  MESSAGE_ITEM otemp;
  otemp.setprecision(3);
  const CHAIN_OPERATOR* cop = 0;
  string prefix = kvu_get_argument_prefix(argu);

  cop = dynamic_cast<const CHAIN_OPERATOR*>(ECA_STATIC_OBJECT_MAPS::vst_plugin_map().object(prefix));
  CHAIN_OPERATOR* new_cop = 0;
  if (cop != 0) {
    
    ECA_LOG_MSG(ECA_LOGGER::user_objects, "Creating VST-plugin \"" + new_cop->name() + "\"");
    otemp << "Setting parameters: ";
    for(int n = 0; n < new_cop->number_of_params(); n++) {
      new_cop->set_parameter(n + 1, atof(kvu_get_argument_number(n + 1, argu).c_str()));
      otemp << new_cop->get_parameter_name(n + 1) << " = ";
      otemp << new_cop->get_parameter(n + 1);
      if (n + 1 < new_cop->number_of_params()) otemp << ", ";
    }
    ECA_LOG_MSG(ECA_LOGGER::user_objects, otemp.to_string());
  }
  return new_cop;
}
#endif /* VST ifdef 0 */

/**
 * Creates a new chain operator object.
 *
 * @param arg a formatted string describing an chain operator object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.size() > 0
 * @pre argu[0] == '-'
 */
CHAIN_OPERATOR* ECA_OBJECT_FACTORY::create_chain_operator (const string& argu)
{
  // --------
  DBC_REQUIRE(argu.size() > 0);
  DBC_REQUIRE(argu[0] == '-');
  // --------

  string prefix = kvu_get_argument_prefix(argu);
  int args_given = kvu_get_number_of_arguments(argu);

  MESSAGE_ITEM otemp;
  otemp.setprecision(3);
  const CHAIN_OPERATOR* cop = 
    dynamic_cast<const CHAIN_OPERATOR*>(ECA_OBJECT_FACTORY::chain_operator_map().object(prefix));
  CHAIN_OPERATOR* new_cop = 0;
  if (cop != 0) {
    new_cop = dynamic_cast<CHAIN_OPERATOR*>(cop->new_expr());

    ECA_LOG_MSG(ECA_LOGGER::user_objects, "Creating chain operator \"" +
		  new_cop->name() + "\"");
    //    otemp << "(eca-chainsetup) Adding effect " << new_cop->name();
    otemp << "Setting parameters: ";

    int params = new_cop->number_of_params();
    if (new_cop->variable_params() &&
	args_given > params)
      params = args_given;

    for(int n = 0; n < params; n++) {
      new_cop->set_parameter(n + 1, atof(kvu_get_argument_number(n + 1, argu).c_str()));
      otemp << new_cop->get_parameter_name(n + 1) << " = ";
      otemp << new_cop->get_parameter(n +1);
      if (n + 1 < new_cop->number_of_params()) otemp << ", ";
    }
    ECA_LOG_MSG(ECA_LOGGER::user_objects, otemp.to_string());
    return new_cop;
  }
  return 0;
}

/**
 * Creates a new generic controller object.
 *
 * @param arg a formatted string describing an generic controller object, see ecasound 
 *            manuals for detailed info
 * @return the created object or 0 if an invalid format string was given 
 *         as the argument
 *
 * @pre argu.size() > 0
 * @pre argu[0] == '-'
 */
GENERIC_CONTROLLER* ECA_OBJECT_FACTORY::create_controller (const string& argu)
{
  // --------
  DBC_REQUIRE(argu.size() > 0);
  DBC_REQUIRE(argu[0] == '-');
  // --------

  if (argu.size() > 0 && argu[0] != '-') return 0;
  string prefix = kvu_get_argument_prefix(argu);

  const GENERIC_CONTROLLER* gcontroller = 
    dynamic_cast<const GENERIC_CONTROLLER*>(ECA_OBJECT_FACTORY::controller_map().object(prefix));
  GENERIC_CONTROLLER* new_gcontroller = 0;
  if (gcontroller != 0) {
    new_gcontroller = gcontroller->new_expr();
    if (new_gcontroller != 0) {
      const CONTROLLER_SOURCE* csource = gcontroller->source_pointer();
      CONTROLLER_SOURCE* new_csource = 0;
      if (csource != 0) {
	new_csource = csource->new_expr();
      }
      new_gcontroller->assign_source(new_csource);

      ECA_LOG_MSG(ECA_LOGGER::user_objects, "Creating controller source \"" +  new_gcontroller->name() + "\"");

      MESSAGE_ITEM otemp;
      otemp.setprecision(3);
      otemp << "Setting parameters: ";
      int numparams = new_gcontroller->number_of_params();
      for(int n = 0; n < numparams; n++) {
	new_gcontroller->set_parameter(n + 1, atof(kvu_get_argument_number(n + 1, argu).c_str()));
	otemp << new_gcontroller->get_parameter_name(n + 1) << " = ";
	otemp << new_gcontroller->get_parameter(n +1);
	if (new_gcontroller->variable_params())
	  numparams = new_gcontroller->number_of_params(); // in case 'n_o_p()' varies
	if (n + 1 < numparams) otemp << ", ";
      }
      ECA_LOG_MSG(ECA_LOGGER::user_objects, otemp.to_string());

      return new_gcontroller;
    }
  }
  return 0;
}

/**
 * Returns a EOS-compatible string describing the default 
 * output device. This device is determined based on 
 * ecasoundrc settings, available resources, 
 * compile-time options, etc.
 */
std::string ECA_OBJECT_FACTORY::probe_default_output_device(void)
{
    ECA_RESOURCES ecaresources;
    const char *output_autodetect = "autodetect";
    string default_output = output_autodetect;
    bool output_selected = true;

    if (ecaresources.has("default-output") == true)
      default_output =
	ecaresources.resource("default-output");

    if (default_output == output_autodetect) {
      
      output_selected = false;

#ifdef ECA_COMPILE_JACK
      /* phase 1: check for JACK */

      int pid = getpid();
      string cname = "ecasound-autodetect-" + kvu_numtostr(pid);

      jack_client_t *client = jack_client_open(cname.c_str(), JackNoStartServer, NULL);
      if (client != 0) {
	jack_client_close(client);
	
	default_output = "jack_alsa";
	output_selected = true;
      }
     
#endif
      
      /* phase 2: check for ALSA support */
      if (output_selected != true) {
	const ECA_OBJECT *obj = ECA_OBJECT_FACTORY::audio_io_rt_map().object("alsa");
	if (obj != 0) {
	  default_output = "alsa,default";
	  output_selected = true;
	}
      }
      
      /* phase 3: check for OSS support */
      if (output_selected != true) {
	const ECA_OBJECT *obj = ECA_OBJECT_FACTORY::audio_io_rt_map().object("/dev/dsp");
	if (obj != 0) {
	  default_output = "/dev/dsp";
	  output_selected = true;
	}
      }
      
      /* phase 4: fallback to rtnull */
      if (output_selected != true) {
	ECA_LOG_MSG(ECA_LOGGER::info,
		    "WARNING: No default output available. Using 'rtnull' as a fallback.");
	  default_output = "rtnull";
      }
    }

    return default_output;
}

/**
 * Makes an Ecasound Option Syntax (EOS) compatible string
 * describing the current state of chain operator 'gctrl'.
 */
string ECA_OBJECT_FACTORY::chain_operator_to_eos(const CHAIN_OPERATOR* chainop)
{
  MESSAGE_ITEM t;
  
  // >--
  // special handling for LADSPA-plugins
#ifndef ECA_DISABLE_EFFECTS

  const EFFECT_LADSPA* ladspa = dynamic_cast<const EFFECT_LADSPA*>(chainop);
  const CHAIN_OPERATOR *lv2_cop = 0;
  string lv2_arg;

#ifdef ECA_USE_LIBLILV
  const EFFECT_LV2* lv2 = dynamic_cast<const EFFECT_LV2*>(chainop);
  if (lv2 != 0) {
    lv2_cop = static_cast<const EFFECT_LV2*>(lv2);
    lv2_arg = lv2->unique();
  }
#endif

  if (lv2_cop != 0) {
    t << "-elv2:" << lv2_arg;
    if (chainop->number_of_params() > 0) t << ",";
  }
  else if (ladspa != 0) {
    t << "-eli:" << ladspa->unique_number();
    if (chainop->number_of_params() > 0) t << ",";
  }
  else {
    ECA_OBJECT_MAP& copmap = ECA_OBJECT_FACTORY::chain_operator_map();
    ECA_PRESET_MAP& presetmap = ECA_OBJECT_FACTORY::preset_map();
    
    string idstring = copmap.object_identifier(chainop);
    if (idstring.size() == 0) {
      idstring = presetmap.object_identifier(chainop);
    }
    if (idstring.size() == 0) {
      ECA_LOG_MSG(ECA_LOGGER::errors,
		  "Unable to save chain operator \"" +
		  chainop->name() + "\".");
      return t.to_string();
    }
     
    t << "-" << idstring;
    if (chainop->number_of_params() > 0) t << ":";
  }
#endif
  // --<

  t << ECA_OBJECT_FACTORY::operator_parameters_to_eos(chainop);

  return t.to_string();
}

/**
 * Makes an Ecasound Option Syntax (EOS) compatible string
 * describing the current state of controller object 'gctrl'.
 */
string ECA_OBJECT_FACTORY::controller_to_eos(const GENERIC_CONTROLLER* gctrl)
{
  MESSAGE_ITEM t;
  ECA_OBJECT_MAP& ctrlmap = ECA_OBJECT_FACTORY::controller_map();
  string idstring = ctrlmap.object_identifier(gctrl);

  if (idstring.size() == 0) {
    ECA_LOG_MSG(ECA_LOGGER::errors, 
		"Unable to save controller \"" +
		gctrl->name() + "\".");
    return t.to_string();
  }

  t << "-" 
    << idstring 
    << ":"
    << ECA_OBJECT_FACTORY::operator_parameters_to_eos(gctrl);

  return t.to_string();
}

/**
 * Makes an Ecasound Option Syntax (EOS) compatible, 
 * comma-separated list of parameter  values for 
 * chain operator 'chainop'.
 */
string ECA_OBJECT_FACTORY::operator_parameters_to_eos(const OPERATOR* chainop)
{
  MESSAGE_ITEM t;
  
  for(int n = 0; n < chainop->number_of_params(); n++) {
    /* FIXME: escape commas */
    t << chainop->get_parameter(n + 1);
    if (n + 1 < chainop->number_of_params()) t << ",";
  }

  return t.to_string();
}

/**
 * Return a string compliant with Ecasound Option Syntax (EOS)
 * describing the object 'aiod'.
 *
 * @pre direction == "i" || direction == "o"
 */
string ECA_OBJECT_FACTORY::audio_object_to_eos(const AUDIO_IO* aiod, const std::string& direction)
{
  MESSAGE_ITEM t;
  t << "-" << direction << ":";
  for(int n = 0; n < aiod->number_of_params(); n++) {
    /* step: if parameter has commas, or whitespace, quote the whole parameter */
    std::string param = aiod->get_parameter(n + 1);
    if (find(param.begin(), param.end(), ',') != param.end() ||
	find(param.begin(), param.end(), ' ') != param.end() ||
	find(param.begin(), param.end(), '\t') != param.end()) {
      param = std::string("\"") + param + std::string("\"");
    }

    /* step: add processed parameter to the EOS string */
    t << param;
    if (n + 1 < aiod->number_of_params()) t << ",";
  }

  return t.to_string();
}

/**
 * Return a string compliant with Ecasound Option Syntax (EOS)
 * describing the audio format 'aformat'.
 */
string ECA_OBJECT_FACTORY::audio_format_to_eos(const ECA_AUDIO_FORMAT* aformat)
{
  MESSAGE_ITEM t;

  t << "-f:" << aformat->format_string() << "," <<
    aformat->channels() << ","  << aformat->samples_per_second();

  return t.to_string();
}

/**
 * Return a string compliant with Ecasound Option Syntax (EOS)
 * describing the audio format of object 'aiod'.
 */
string ECA_OBJECT_FACTORY::audio_object_format_to_eos(const AUDIO_IO* aio)
{
  return ECA_OBJECT_FACTORY::audio_format_to_eos(dynamic_cast<const ECA_AUDIO_FORMAT*>(aio));
#if 0
  MESSAGE_ITEM t;

  t << "-f:" << aiod->format_string() << "," <<
    aiod->channels() << ","  << aiod->samples_per_second();

  return t.to_string();
#endif
}
