// ------------------------------------------------------------------------ // eca-object-factory.cpp: Abstract factory for creating libecasound // objects. // Copyright (C) 2000-2005,2007 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 #endif #include /* ANSI-C++: find() */ #include #include #include #include #include #include #include #ifdef ECA_COMPILE_JACK #include #endif #include #include "kvu_locks.h" #include #include #include "audioio.h" #include "audioio-loop.h" #include "midiio.h" #include "audiofx_ladspa.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::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 * 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(); 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); // -- string fname = kvu_get_argument_number(1, arg); if (fname.find(".") != string::npos) { fname = string(fname, fname.find_last_of("."), string::npos); } const AUDIO_IO* main_file = 0; main_file = dynamic_cast(ECA_OBJECT_FACTORY::audio_io_rt_map().object_expr(fname)); if (main_file == 0) { main_file = dynamic_cast(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)."); for(int n = 0; n < new_file->number_of_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(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* loop_map) { // -------- DBC_REQUIRE(argu.empty() != true); // -------- LOOP_DEVICE* p = 0; string tname = kvu_get_argument_number(1, argu); if (tname.find("loop") != string::npos) { int id = atoi(kvu_get_argument_number(2, argu).c_str()); 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* loop_map) { // -------- DBC_REQUIRE(argu.empty() != true); // -------- LOOP_DEVICE* p = 0; string tname = kvu_get_argument_number(1, argu); if (tname.find("loop") != string::npos) { int id = atoi(kvu_get_argument_number(2, argu).c_str()); 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(ECA_OBJECT_FACTORY::ladspa_plugin_map().object(unique)); else cop = dynamic_cast(ECA_OBJECT_FACTORY::ladspa_plugin_id_map().object(unique)); CHAIN_OPERATOR* new_cop = 0; if (cop != 0) { new_cop = dynamic_cast(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; } /** * 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(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); MESSAGE_ITEM otemp; otemp.setprecision(3); const CHAIN_OPERATOR* cop = dynamic_cast(ECA_OBJECT_FACTORY::chain_operator_map().object(prefix)); CHAIN_OPERATOR* new_cop = 0; if (cop != 0) { new_cop = dynamic_cast(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: "; 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; } 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(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); 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; string default_output = ecaresources.resource("default-output"); bool output_selected = true; if (default_output == "autodetect") { output_selected = false; #ifdef ECA_COMPILE_JACK /* phase 1: check for JACK */ int pid = getpid(); string cname = "ecasound-autodetect-" + kvu_numtostr(pid); bool env_changed = false; char* oldenv = getenv("JACK_NO_START_SERVER"); if (oldenv == NULL) { env_changed = true; setenv("JACK_NO_START_SERVER", "yes", 1); } jack_client_t *client = jack_client_new (cname.c_str()); if (client != 0) { jack_client_close(client); default_output = "jack_alsa"; output_selected = true; } if (env_changed == true) { unsetenv("JACK_NO_START_SERVER"); } #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 LADPSA-plugins #ifndef ECA_DISABLE_EFFECTS const EFFECT_LADSPA* ladspa = dynamic_cast(chainop); 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++) { 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 of object 'aiod'. */ string ECA_OBJECT_FACTORY::audio_object_format_to_eos(const AUDIO_IO* aiod) { MESSAGE_ITEM t; t << "-f:" << aiod->format_string() << "," << aiod->channels() << "," << aiod->samples_per_second(); return t.to_string(); }