// ------------------------------------------------------------------------ // eca-chainsetup.cpp: Class representing an ecasound chainsetup object. // Copyright (C) 1999-2006,2008,2009 Kai Vehmanen // Copyright (C) 2005 Stuart Allie // // Attributes: // eca-style-version: 3 (see Ecasound Programmer's Guide) // // 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 #include #include /* find() */ #include #include #include #include #include /* POSIX: getpid() */ #include /* POSIX: getpid() */ #ifdef HAVE_SYS_MMAN_H #include /* POSIX: for mlockall() */ #endif #ifdef ECA_COMPILE_JACK #include #endif #include #include #include #include #include #include "eca-resources.h" #include "eca-session.h" #include "generic-controller.h" #include "eca-chainop.h" #include "eca-chain.h" #include "audioio.h" #include "audioio-manager.h" #include "audioio-device.h" #include "audioio-buffered.h" #include "audioio-loop.h" #include "audioio-null.h" #include "audioio-resample.h" #include "eca-engine-driver.h" #include "eca-object-factory.h" #include "eca-object-map.h" #include "midiio.h" #include "midi-client.h" #include "eca-object-factory.h" #include "eca-chainsetup-position.h" #include "sample-specs.h" #include "eca-error.h" #include "eca-logger.h" #include "eca-chainsetup.h" #include "eca-chainsetup_impl.h" using std::cerr; using std::endl; using namespace ECA; const string ECA_CHAINSETUP::default_audio_format_const = "s16_le,2,44100,i"; const string ECA_CHAINSETUP::default_bmode_nonrt_const = "1024,false,50,false,100000,true"; const string ECA_CHAINSETUP::default_bmode_rt_const = "1024,true,50,true,100000,true"; const string ECA_CHAINSETUP::default_bmode_rtlowlatency_const = "256,true,50,true,100000,false"; static void priv_erase_object(std::vector* vec, const AUDIO_IO* obj); /** * Construct from a vector of options. * * If any invalid options are passed us argument, * interpret_result() will be 'false', and * interpret_result_verbose() contains more detailed * error description. */ ECA_CHAINSETUP::ECA_CHAINSETUP(const vector& opts) : cparser_rep(this), is_enabled_rep(false) { impl_repp = new ECA_CHAINSETUP_impl; // FIXME: set default audio format here! setup_name_rep = "untitled-chainsetup"; setup_filename_rep = ""; set_defaults(); vector options (opts); cparser_rep.preprocess_options(options); interpret_options(options); if (interpret_result() == true) { /* do not add default if there were parsing errors as * it might hide real problems */ add_default_output(); add_default_midi_device(); } ECA_LOG_MSG(ECA_LOGGER::info, "Chainsetup \"" + setup_name_rep + "\""); } /** * Constructs an empty chainsetup. * * @post buffersize != 0 */ ECA_CHAINSETUP::ECA_CHAINSETUP(void) : cparser_rep(this), is_enabled_rep(false) { impl_repp = new ECA_CHAINSETUP_impl; setup_name_rep = ""; set_defaults(); ECA_LOG_MSG(ECA_LOGGER::info, "Chainsetup created (empty)"); } /** * Construct from a chainsetup file. * * If any invalid options are passed us argument, * interpret_result() will be 'false', and * interpret_result_verbose() contains more detailed * error description. * * @post buffersize != 0 */ ECA_CHAINSETUP::ECA_CHAINSETUP(const string& setup_file) : cparser_rep(this), is_enabled_rep(false) { impl_repp = new ECA_CHAINSETUP_impl; setup_name_rep = ""; set_defaults(); vector options; load_from_file(setup_file, options); set_filename(setup_file); if (name() == "") set_name(setup_file); cparser_rep.preprocess_options(options); interpret_options(options); if (interpret_result() == true) { /* do not add default if there were parsing errors as * it might hide real problems */ add_default_output(); } ECA_LOG_MSG(ECA_LOGGER::info, "Chainsetup \"" + name() + "\" created (file: " + setup_file + ")"); } /** * Destructor */ ECA_CHAINSETUP::~ECA_CHAINSETUP(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects,"ECA_CHAINSETUP destructor-in"); DBC_CHECK(is_locked() != true); if (is_enabled() == true) { disable(); } DBC_CHECK(is_enabled() != true); /* delete chain objects */ for(vector::iterator q = chains.begin(); q != chains.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting chain \"" + (*q)->name() + "\"."); delete *q; *q = 0; } /* delete input db objects; reset all pointers to null */ for(vector::iterator q = inputs.begin(); q != inputs.end(); q++) { if (dynamic_cast(*q) != 0) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio db-client \"" + (*q)->label() + "\"."); delete *q; } *q = 0; } /* delete all actual audio input objects except loop devices; reset all pointers to null */ for(vector::iterator q = inputs_direct_rep.begin(); q != inputs_direct_rep.end(); q++) { if (dynamic_cast(*q) == 0) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio object \"" + (*q)->label() + "\"."); delete *q; } *q = 0; } /* delete output db objects; reset all pointers to null */ for(vector::iterator q = outputs.begin(); q != outputs.end(); q++) { if (dynamic_cast(*q) != 0) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio db-client \"" + (*q)->label() + "\"."); delete *q; } *q = 0; } /* delete all actual audio output objects except loop devices; reset all pointers to null */ for(vector::iterator q = outputs_direct_rep.begin(); q != outputs_direct_rep.end(); q++) { // trouble with dynamic_cast with libecasoundc apps like ecalength? if (dynamic_cast(*q) == 0) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio object \"" + (*q)->label() + "\"."); delete *q; *q = 0; } } /* delete loop objects */ for(map::iterator q = loop_map.begin(); q != loop_map.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting loop device \"" + q->second->label() + "\"."); delete q->second; q->second = 0; } /* delete aio manager objects */ for(vector::iterator q = aio_managers_rep.begin(); q != aio_managers_rep.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio manager \"" + (*q)->name() + "\"."); delete *q; *q = 0; } delete impl_repp; ECA_LOG_MSG(ECA_LOGGER::system_objects,"ECA_CHAINSETUP destructor-out"); } /** * Sets default values. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::set_defaults(void) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- /* note: defaults are set as specified in ecasoundrc(5) */ precise_sample_rates_rep = false; ignore_xruns_rep = true; pserver_repp = &impl_repp->pserver_rep; midi_server_repp = &impl_repp->midi_server_rep; engine_driver_repp = 0; if (kvu_check_for_sched_fifo() == true) { rtcaps_rep = true; ECA_LOG_MSG(ECA_LOGGER::system_objects, "Rtcaps detected."); } else rtcaps_rep = false; db_clients_rep = 0; multitrack_mode_rep = false; multitrack_mode_override_rep = false; memory_locked_rep = false; midi_server_needed_rep = false; is_locked_rep = false; selected_chain_index_rep = 0; selected_ctrl_index_rep = 0; selected_ctrl_param_index_rep = 0; multitrack_mode_offset_rep = -1; buffering_mode_rep = cs_bmode_auto; active_buffering_mode_rep = cs_bmode_none; set_output_openmode(AUDIO_IO::io_readwrite); ECA_RESOURCES ecaresources; if (ecaresources.has_any() != true) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Unable to read global resources. May result in incorrect behaviour."); } set_default_midi_device(ecaresources.resource("midi-device")); string rc_temp = set_resource_helper(ecaresources, "default-audio-format", ECA_CHAINSETUP::default_audio_format_const); cparser_rep.interpret_object_option("-f:" + rc_temp); set_samples_per_second(default_audio_format().samples_per_second()); toggle_precise_sample_rates(ecaresources.boolean_resource("default-to-precise-sample-rates")); rc_temp = set_resource_helper(ecaresources, "default-mix-mode", "avg"); cparser_rep.interpret_object_option("-z:mixmode," + rc_temp); impl_repp->bmode_nonrt_rep.set_all(set_resource_helper(ecaresources, "bmode-defaults-nonrt", ECA_CHAINSETUP::default_bmode_nonrt_const)); impl_repp->bmode_rt_rep.set_all(set_resource_helper(ecaresources, "bmode-defaults-rt", ECA_CHAINSETUP::default_bmode_rt_const)); impl_repp->bmode_rtlowlatency_rep.set_all(set_resource_helper(ecaresources, "bmode-defaults-rtlowlatency", ECA_CHAINSETUP::default_bmode_rtlowlatency_const)); impl_repp->bmode_active_rep = impl_repp->bmode_nonrt_rep; } /** * Sets a resource value. * * Only used by ECA_CHAINSETUP::set_defaults. */ string ECA_CHAINSETUP::set_resource_helper(const ECA_RESOURCES& ecaresources, const string& tag, const string& alternative) { if (ecaresources.has(tag) == true) { return ecaresources.resource(tag); } else { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Using hardcoded defaults for \"" + tag + "\"."); return alternative; } } /** * Tests whether chainsetup is in a valid state. */ bool ECA_CHAINSETUP::is_valid(void) const { return is_valid_for_connection(false); } /** * Checks whether chainsetup is valid for enabling/connecting. * If chainsetup is not valid and 'verbose' is true, detected * errors are reported via the logging subsystem. */ bool ECA_CHAINSETUP::is_valid_for_connection(bool verbose) const { bool result = true; if (inputs.size() == 0) { if (verbose) ECA_LOG_MSG(ECA_LOGGER::info, "Unable to connect: No inputs in the current chainsetup. (1.1-NO-INPUTS)"); result = false; } else if (outputs.size() == 0) { if (verbose) ECA_LOG_MSG(ECA_LOGGER::info, "Unable to connect: No outputs in the current chainsetup. (1.2-NO-OUTPUTS)"); result = false; } else if (chains.size() == 0) { if (verbose) ECA_LOG_MSG(ECA_LOGGER::info, "Unable to connect: No chains in the current chainsetup. (1.3-NO-CHAINS)"); result = false; } else { list conn_inputs, conn_outputs; for(vector::const_iterator q = chains.begin(); q != chains.end(); q++) { /* log messages printed in CHAIN::is_valid() */ int id = (*q)->connected_input(); if (id > -1) conn_inputs.push_back(id); if ((*q)->is_valid() == false) { result = false; if (verbose) ECA_LOG_MSG(ECA_LOGGER::info, "Unable to connect: Chain \"" + (*q)->name() + "\" is not valid. Following errors were detected:"); if (verbose && id == -1) { ECA_LOG_MSG(ECA_LOGGER::info, "Chain \"" + (*q)->name() + "\" is not connected to any input. " "All chains must have exactly one valid input. (2.1-NO-CHAIN-INPUT)"); } } id = (*q)->connected_output(); if (id > -1) conn_outputs.push_back(id); if (verbose && (*q)->is_valid() == false) { if (id == -1) { ECA_LOG_MSG(ECA_LOGGER::info, "Chain \"" + (*q)->name() + "\" is not connected to any output. " "All chains must have exactly one valid output. (2.2-NO-CHAIN-OUTPUT)"); } } } // FIXME: doesn't work yet if (verbose) { for(int n = 0; n < static_cast(inputs.size()); n++) { if (std::find(conn_inputs.begin(), conn_inputs.end(), n) == conn_inputs.end()) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Input \"" + inputs[n]->label() + "\" is not connected to any chain. (3.1-DISCON-INPUT)"); } } for(int n = 0; n < static_cast(outputs.size()); n++) { if (std::find(conn_outputs.begin(), conn_outputs.end(), n) == conn_outputs.end()) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Output \"" + outputs[n]->label() + "\" is not connected to any chain. (3.2-DISCON-OUTPUT)"); } } } } /* (verbose == true) */ return result; } void ECA_CHAINSETUP::set_buffering_mode(Buffering_mode_t value) { if (value == ECA_CHAINSETUP::cs_bmode_none) buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_auto; else buffering_mode_rep = value; } /** * Sets audio i/o manager option for manager * object type 'mgrname' to be 'optionstr'. * Previously set option string is overwritten. */ void ECA_CHAINSETUP::set_audio_io_manager_option(const string& mgrname, const string& optionstr) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Set manager \"" + mgrname + "\" option string to \"" + optionstr + "\"."); aio_manager_option_map_rep[mgrname] = optionstr; propagate_audio_io_manager_options(); } /** * Determinates the active buffering parameters based on * defaults, user overrides and analyzing the current * chainsetup configuration. If the resulting parameters * are different from current ones, a state change is * performed. */ void ECA_CHAINSETUP::select_active_buffering_mode(void) { if (buffering_mode() == ECA_CHAINSETUP::cs_bmode_none) { active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_auto; } if (!(multitrack_mode_override_rep == true && multitrack_mode_rep != true) && ((multitrack_mode_override_rep == true && multitrack_mode_rep == true) || (number_of_realtime_inputs() > 0 && number_of_realtime_outputs() > 0 && number_of_non_realtime_inputs() > 0 && number_of_non_realtime_outputs() > 0 && chains.size() > 1))) { ECA_LOG_MSG(ECA_LOGGER::info, "Multitrack-mode enabled."); multitrack_mode_rep = true; } else multitrack_mode_rep = false; if (buffering_mode() == ECA_CHAINSETUP::cs_bmode_auto) { /* initialize to 'nonrt', mt-disabled */ active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_nonrt; if (has_realtime_objects() == true) { /* case 1: a multitrack setup */ if (multitrack_mode_rep == true) { active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_rt; ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection case-1"); } /* case 2: rt-objects without priviledges for rt-scheduling */ else if (rtcaps_rep != true) { ECA_LOG_MSG(ECA_LOGGER::info, "NOTE: Real-time configuration, but insufficient privileges to utilize real-time scheduling (SCHED_FIFO). With small buffersizes, this may cause audible glitches during processing."); toggle_raised_priority(false); active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_rt; ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection case-2"); } /* case 3: no chain operators and "one-way rt-operation" */ else if (number_of_chain_operators() == 0 && (number_of_realtime_inputs() == 0 || number_of_realtime_outputs() == 0)) { active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_rt; ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection case-3"); } /* case 4: default for rt-setups */ else { active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_rtlowlatency; ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection case-4"); } } else { /* case 5: no rt-objects */ active_buffering_mode_rep = ECA_CHAINSETUP::cs_bmode_nonrt; ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection case-5"); } } else { /* user has explicitly selected the buffering mode */ active_buffering_mode_rep = buffering_mode(); ECA_LOG_MSG(ECA_LOGGER::system_objects, "bmode-selection explicit"); } switch(active_buffering_mode_rep) { case ECA_CHAINSETUP::cs_bmode_nonrt: { impl_repp->bmode_active_rep = impl_repp->bmode_nonrt_rep; ECA_LOG_MSG(ECA_LOGGER::info, "\"nonrt\" buffering mode selected."); break; } case ECA_CHAINSETUP::cs_bmode_rt: { impl_repp->bmode_active_rep = impl_repp->bmode_rt_rep; ECA_LOG_MSG(ECA_LOGGER::info, "\"rt\" buffering mode selected."); break; } case ECA_CHAINSETUP::cs_bmode_rtlowlatency: { impl_repp->bmode_active_rep = impl_repp->bmode_rtlowlatency_rep; ECA_LOG_MSG(ECA_LOGGER::info, "\"rtlowlatency\" buffering mode selected."); break; } default: { /* error! */ } } ECA_LOG_MSG(ECA_LOGGER::system_objects, "Set buffering parameters to: \n--cut--" + impl_repp->bmode_active_rep.to_string() +"\n--cut--"); } /** * Enable chosen active buffering mode. * * Called only from enable(). */ void ECA_CHAINSETUP::enable_active_buffering_mode(void) { /* 1. if requested, lock all memory */ if (raised_priority() == true) { lock_all_memory(); } else { unlock_all_memory(); } /* 2. if necessary, switch between different db and direct modes */ if (double_buffering() == true) { if (has_realtime_objects() != true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "No realtime objects; switching to direct mode."); switch_to_direct_mode(); impl_repp->bmode_active_rep.toggle_double_buffering(false); } else if (has_nonrealtime_objects() != true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Only realtime objects; switching to direct mode."); switch_to_direct_mode(); impl_repp->bmode_active_rep.toggle_double_buffering(false); } else if (db_clients_rep == 0) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Switching to db mode."); switch_to_db_mode(); } if (buffersize() != 0) { impl_repp->pserver_rep.set_buffer_defaults(double_buffer_size() / buffersize(), buffersize()); } else { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Buffersize set to 0."); impl_repp->pserver_rep.set_buffer_defaults(0, 0); } } else { /* double_buffering() != true */ if (db_clients_rep > 0) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Switching to direct mode."); switch_to_direct_mode(); } } /* 3. propagate buffersize value to all dependent objects */ /* FIXME: create a system for tracking buffesize aware objs */ } void ECA_CHAINSETUP::switch_to_direct_mode(void) { switch_to_direct_mode_helper(&inputs, inputs_direct_rep); switch_to_direct_mode_helper(&outputs, outputs_direct_rep); // -- DBC_ENSURE(db_clients_rep == 0); // -- } void ECA_CHAINSETUP::switch_to_direct_mode_helper(vector* objs, const vector& directobjs) { // -- DBC_CHECK(objs->size() == directobjs.size()); // -- for(size_t n = 0; n < objs->size(); n++) { AUDIO_IO_DB_CLIENT* pobj = dynamic_cast((*objs)[n]); if (pobj != 0) { delete (*objs)[n]; (*objs)[n] = directobjs[n]; --db_clients_rep; } } } void ECA_CHAINSETUP::switch_to_db_mode(void) { switch_to_db_mode_helper(&inputs, inputs_direct_rep); switch_to_db_mode_helper(&outputs, outputs_direct_rep); } void ECA_CHAINSETUP::switch_to_db_mode_helper(vector* objs, const vector& directobjs) { // -- DBC_REQUIRE(db_clients_rep == 0); DBC_CHECK(objs->size() == directobjs.size()); // -- for(size_t n = 0; n < directobjs.size(); n++) { (*objs)[n] = add_audio_object_helper(directobjs[n]); } } /** * Locks all memory with mlockall(). */ void ECA_CHAINSETUP::lock_all_memory(void) { #ifdef HAVE_MLOCKALL if (::mlockall (MCL_CURRENT|MCL_FUTURE)) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Couldn't lock all memory!"); } else { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Memory locked!"); memory_locked_rep = true; } #else ECA_LOG_MSG(ECA_LOGGER::info, "Memory locking not available."); #endif } /** * Unlocks all memory with munlockall(). */ void ECA_CHAINSETUP::unlock_all_memory(void) { #ifdef HAVE_MUNLOCKALL if (memory_locked_rep == true) { if (::munlockall()) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "WARNING: Couldn't unlock all memory!"); } else ECA_LOG_MSG(ECA_LOGGER::system_objects, "Memory unlocked!"); memory_locked_rep = false; } #else memory_locked_rep = false; ECA_LOG_MSG(ECA_LOGGER::system_objects, "Memory unlocking not available."); #endif } /** * Adds a "default" chain to this chainsetup. * * @pre buffersize >= 0 && chains.size() == 0 * @pre is_locked() != true * * @post chains.back()->name() == "default" && * @post active_chainids.back() == "default" */ void ECA_CHAINSETUP::add_default_chain(void) { // -------- DBC_REQUIRE(buffersize() >= 0); DBC_REQUIRE(chains.size() == 0); DBC_REQUIRE(is_locked() != true); // -------- add_chain_helper("default"); selected_chainids.push_back("default"); // -------- DBC_ENSURE(chains.back()->name() == "default"); DBC_ENSURE(selected_chainids.back() == "default"); // -------- } /** * Adds new chains to this chainsetup. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::add_new_chains(const vector& newchains) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- for(vector::const_iterator p = newchains.begin(); p != newchains.end(); p++) { bool exists = false; for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*p == (*q)->name()) exists = true; } if (exists == false) { add_chain_helper(*p); } } } void ECA_CHAINSETUP::add_chain_helper(const string& name) { chains.push_back(new CHAIN()); chains.back()->name(name); chains.back()->set_samples_per_second(samples_per_second()); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Chain \"" + name + "\" created."); } /** * Removes all selected chains from this chainsetup. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::remove_chains(void) { // -------- DBC_REQUIRE(is_enabled() != true); DBC_DECLARE(size_t old_chains_size = chains.size()); DBC_DECLARE(size_t sel_chains_size = selected_chainids.size()); // -------- for(vector::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) { vector::iterator q = chains.begin(); while(q != chains.end()) { if (*a == (*q)->name()) { delete *q; chains.erase(q); break; } ++q; } } selected_chainids.resize(0); // -- DBC_ENSURE(chains.size() == old_chains_size - sel_chains_size); // -- } /** * Clears all selected chains. Removes all chain operators * and controllers. * * @pre is_locked() != true */ void ECA_CHAINSETUP::clear_chains(void) { // -------- DBC_REQUIRE(is_locked() != true); // -------- for(vector::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { (*q)->clear(); } } } } /** * Renames the first selected chain. */ void ECA_CHAINSETUP::rename_chain(const string& name) { for(vector::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { (*q)->name(name); return; } } } } /** * Selects all chains present in this chainsetup. */ void ECA_CHAINSETUP::select_all_chains(void) { vector::const_iterator p = chains.begin(); selected_chainids.resize(0); while(p != chains.end()) { selected_chainids.push_back((*p)->name()); ++p; } } /** * Returns the index number of first selected chains. If no chains * are selected, returns 'last_index + 1' (==chains.size()). */ unsigned int ECA_CHAINSETUP::first_selected_chain(void) const { const vector& schains = selected_chains(); vector::const_iterator o = schains.begin(); unsigned int p = chains.size(); while(o != schains.end()) { for(p = 0; p != chains.size(); p++) { if (chains[p]->name() == *o) return p; } ++o; } return p; } /** * Toggles chain muting of all selected chains. * * @pre is_locked() != true */ void ECA_CHAINSETUP::toggle_chain_muting(void) { // --- DBC_REQUIRE(is_locked() != true); // --- for(vector::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { if ((*q)->is_muted()) (*q)->toggle_muting(false); else (*q)->toggle_muting(true); } } } } /** * Toggles chain bypass of all selected chains. * * @pre is_locked() != true */ void ECA_CHAINSETUP::toggle_chain_bypass(void) { // --- DBC_REQUIRE(is_locked() != true); // --- for(vector::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { if ((*q)->is_processing()) (*q)->toggle_processing(false); else (*q)->toggle_processing(true); } } } } const ECA_CHAINSETUP_BUFPARAMS& ECA_CHAINSETUP::active_buffering_parameters(void) const { return impl_repp->bmode_active_rep; } const ECA_CHAINSETUP_BUFPARAMS& ECA_CHAINSETUP::override_buffering_parameters(void) const { return impl_repp->bmode_override_rep; } vector ECA_CHAINSETUP::chain_names(void) const { vector result; vector::const_iterator p = chains.begin(); while(p != chains.end()) { result.push_back((*p)->name()); ++p; } return result; } vector ECA_CHAINSETUP::audio_input_names(void) const { vector result; vector::const_iterator p = inputs.begin(); while(p != inputs.end()) { result.push_back((*p)->label()); ++p; } return result; } vector ECA_CHAINSETUP::audio_output_names(void) const { vector result; vector::const_iterator p = outputs.begin(); while(p != outputs.end()) { result.push_back((*p)->label()); ++p; } return result; } vector ECA_CHAINSETUP::get_attached_chains_to_input(AUDIO_IO* aiod) const { vector res; vector::const_iterator q = chains.begin(); while(q != chains.end()) { if (aiod == inputs[(*q)->connected_input()]) { res.push_back((*q)->name()); } ++q; } return res; } vector ECA_CHAINSETUP::get_attached_chains_to_output(AUDIO_IO* aiod) const { vector res; vector::const_iterator q = chains.begin(); while(q != chains.end()) { if (aiod == outputs[(*q)->connected_output()]) { res.push_back((*q)->name()); } ++q; } return(res); } int ECA_CHAINSETUP::number_of_attached_chains_to_input(AUDIO_IO* aiod) const { int count = 0; vector::const_iterator q = chains.begin(); while(q != chains.end()) { if (aiod == inputs[(*q)->connected_input()]) { ++count; } ++q; } return count; } int ECA_CHAINSETUP::number_of_attached_chains_to_output(AUDIO_IO* aiod) const { int count = 0; vector::const_iterator q = chains.begin(); while(q != chains.end()) { if (aiod == outputs[(*q)->connected_output()]) { ++count; } ++q; } return count; } /** * Output object is realtime target if it is not * connected to any chains with non-realtime inputs. * In other words all data coming to a rt target * output comes from realtime devices. */ bool ECA_CHAINSETUP::is_realtime_target_output(int output_id) const { bool result = true; bool output_found = false; vector::const_iterator q = chains.begin(); while(q != chains.end()) { if ((*q)->connected_output() == output_id) { output_found = true; AUDIO_IO_DEVICE* p = dynamic_cast(inputs[(*q)->connected_input()]); if (p == 0) { result = false; } } ++q; } if (output_found == true && result == true) ECA_LOG_MSG(ECA_LOGGER::system_objects,"slave output detected: " + outputs[output_id]->label()); else result = false; return result; } vector ECA_CHAINSETUP::get_attached_chains_to_iodev(const string& filename) const { vector::size_type p; p = 0; while (p < inputs.size()) { if (inputs[p]->label() == filename) return get_attached_chains_to_input(inputs[p]); ++p; } p = 0; while (p < outputs.size()) { if (outputs[p]->label() == filename) return get_attached_chains_to_output(outputs[p]); ++p; } return vector (0); } /** * Returns number of realtime audio input objects. */ int ECA_CHAINSETUP::number_of_chain_operators(void) const { int cops = 0; vector::const_iterator q = chains.begin(); while(q != chains.end()) { cops += (*q)->number_of_chain_operators(); ++q; } return cops; } /** * Returns true if the connected chainsetup contains at least * one realtime audio input or output. */ bool ECA_CHAINSETUP::has_realtime_objects(void) const { if (number_of_realtime_inputs() > 0 || number_of_realtime_outputs() > 0) return true; return false; } /** * Returns true if the connected chainsetup contains at least * one nonrealtime audio input or output. */ bool ECA_CHAINSETUP::has_nonrealtime_objects(void) const { if (static_cast(inputs_direct_rep.size() + outputs_direct_rep.size()) > number_of_realtime_inputs() + number_of_realtime_outputs()) return true; return false; } /** * Returns a string containing currently active chainsetup * options and settings. Syntax is the same as used for * saved chainsetup files. */ string ECA_CHAINSETUP::options_to_string(void) const { return cparser_rep.general_options_to_string(); } /** * Returns number of realtime audio input objects. */ int ECA_CHAINSETUP::number_of_realtime_inputs(void) const { int res = 0; for(size_t n = 0; n < inputs_direct_rep.size(); n++) { AUDIO_IO_DEVICE* p = dynamic_cast(inputs_direct_rep[n]); if (p != 0) res++; } return res; } /** * Returns number of realtime audio output objects. */ int ECA_CHAINSETUP::number_of_realtime_outputs(void) const { int res = 0; for(size_t n = 0; n < outputs_direct_rep.size(); n++) { AUDIO_IO_DEVICE* p = dynamic_cast(outputs_direct_rep[n]); if (p != 0) res++; } return res; } /** * Returns number of non-realtime audio input objects. */ int ECA_CHAINSETUP::number_of_non_realtime_inputs(void) const { return inputs.size() - number_of_realtime_inputs(); } /** * Returns number of non-realtime audio input objects. */ int ECA_CHAINSETUP::number_of_non_realtime_outputs(void) const { return outputs.size() - number_of_realtime_outputs(); } /** * Returns a pointer to the manager handling audio object 'aobj'. * * @return 0 if 'aobj' is not handled by any manager */ AUDIO_IO_MANAGER* ECA_CHAINSETUP::get_audio_object_manager(AUDIO_IO* aio) const { for(vector::const_iterator q = aio_managers_rep.begin(); q != aio_managers_rep.end(); q++) { if ((*q)->is_managed_type(aio) && (*q)->get_object_id(aio) != -1) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Found object manager \"" + (*q)->name() + "\" for aio \"" + aio->label() + "\"."); return *q; } } return 0; } /** * Returns a pointer to the manager handling audio * objects of type 'aobj'. * * @return 0 if 'aobj' type is not handled by any manager */ AUDIO_IO_MANAGER* ECA_CHAINSETUP::get_audio_object_type_manager(AUDIO_IO* aio) const { for(vector::const_iterator q = aio_managers_rep.begin(); q != aio_managers_rep.end(); q++) { if ((*q)->is_managed_type(aio) == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Found object manager \"" + (*q)->name() + "\" for aio type \"" + aio->name() + "\"."); return *q; } } return 0; } /** * If 'amgr' implements the ECA_ENGINE_DRIVER interface, * it is registered as the active driver. */ void ECA_CHAINSETUP::register_engine_driver(AUDIO_IO_MANAGER* amgr) { ECA_ENGINE_DRIVER* driver = dynamic_cast(amgr); if (driver != 0) { engine_driver_repp = driver; ECA_LOG_MSG(ECA_LOGGER::system_objects, "Registered audio i/o manager \"" + amgr->name() + "\" as the current engine driver."); } } /** * Registers audio object to a manager. If no managers are * available for object's type, and it can create one, * a new manager is created. */ void ECA_CHAINSETUP::register_audio_object_to_manager(AUDIO_IO* aio) { AUDIO_IO_MANAGER* mgr = get_audio_object_type_manager(aio); if (mgr == 0) { mgr = aio->create_object_manager(); if (mgr != 0) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Creating object manager \"" + mgr->name() + "\" for aio \"" + aio->name() + "\"."); aio_managers_rep.push_back(mgr); propagate_audio_io_manager_options(); mgr->register_object(aio); /* in case manager is also a driver */ register_engine_driver(mgr); } } else { mgr->register_object(aio); } } /** * Unregisters audio object from manager. */ void ECA_CHAINSETUP::unregister_audio_object_from_manager(AUDIO_IO* aio) { AUDIO_IO_MANAGER* mgr = get_audio_object_manager(aio); if (mgr != 0) { int id = mgr->get_object_id(aio); if (id != -1) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Unregistering object \"" + aio->name() + "\" from manager \"" + mgr->name() + "\"."); mgr->unregister_object(id); } } } /** * Propagates to set manager options to all existing * audio i/o manager objects. */ void ECA_CHAINSETUP::propagate_audio_io_manager_options(void) { for(vector::const_iterator q = aio_managers_rep.begin(); q != aio_managers_rep.end(); q++) { if (aio_manager_option_map_rep.find((*q)->name()) != aio_manager_option_map_rep.end()) { const string& optstring = aio_manager_option_map_rep[(*q)->name()]; int numparams = (*q)->number_of_params(); for(int n = 0; n < numparams; n++) { (*q)->set_parameter(n + 1, kvu_get_argument_number(n + 1, optstring)); ECA_LOG_MSG(ECA_LOGGER::system_objects, "Manager \"" + (*q)->name() + "\", " + kvu_numtostr(n + 1) + ". parameter set to \"" + (*q)->get_parameter(n + 1) + "\"."); } } } } /** * Helper function used by add_input() and add_output(). * * All audio object creates go through this function, * so this is good place to do global operations that * apply to both inputs and outputs. */ AUDIO_IO* ECA_CHAINSETUP::add_audio_object_helper(AUDIO_IO* aio) { AUDIO_IO* retobj = aio; AUDIO_IO_DEVICE* p = dynamic_cast(aio); LOOP_DEVICE* q = dynamic_cast(aio); if (p == 0 && q == 0) { /* not a realtime or loop device */ retobj = new AUDIO_IO_DB_CLIENT(&impl_repp->pserver_rep, aio, false); ++db_clients_rep; } return retobj; } /** * Helper function used by remove_audio_object(). */ void ECA_CHAINSETUP::remove_audio_object_proxy(AUDIO_IO* aio) { AUDIO_IO_DB_CLIENT* p = dynamic_cast(aio); if (p != 0) { /* a proxied object */ ECA_LOG_MSG(ECA_LOGGER::user_objects, "Delete proxy object " + aio->label() + "."); delete aio; --db_clients_rep; } } /** * Helper function used bu remove_audio_object() to remove input * and output loop devices. */ void ECA_CHAINSETUP::remove_audio_object_loop(const string& label, AUDIO_IO* aio, int dir) { int rdir = (dir == cs_dir_input ? cs_dir_output : cs_dir_input); /* loop devices are registered simultaneously to both input * and output object vectors, so they have to be removed * from both, but deleted only once */ remove_audio_object_impl(label, rdir, false); /* we also need to remove the loop device from * the loop_map table */ map::iterator iter = loop_map.begin(); while(iter != loop_map.end()) { if (iter->second == aio) { loop_map.erase(iter); break; } ++iter; } } /** * Adds a new input object and attaches it to selected chains. * * If double-buffering is enabled (double_buffering() == true), * and the object in question is not a realtime object, it * is wrapped in a AUDIO_IO_DB_CLIENT object before * inserted to the chainsetup. Otherwise object is added * as is. * * Ownership of the insert object is transfered to * ECA_CHAINSETUP. * * @pre aiod != 0 * @pre is_enabled() != true * @post inputs.size() == old(inputs.size() + 1 */ void ECA_CHAINSETUP::add_input(AUDIO_IO* aio) { // -------- DBC_REQUIRE(aio != 0); DBC_REQUIRE(is_enabled() != true); DBC_DECLARE(size_t old_inputs_size = inputs.size()); // -------- aio->set_io_mode(AUDIO_IO::io_read); aio->set_audio_format(default_audio_format()); aio->set_buffersize(buffersize()); register_audio_object_to_manager(aio); AUDIO_IO* layerobj = add_audio_object_helper(aio); inputs.push_back(layerobj); inputs_direct_rep.push_back(aio); input_start_pos.push_back(0); attach_input_to_selected_chains(layerobj); // -------- DBC_ENSURE(inputs.size() == old_inputs_size + 1); DBC_ENSURE(inputs.size() == inputs_direct_rep.size()); // -------- } /** * Add a new output object and attach it to selected chains. * * If double-buffering is enabled (double_buffering() == true), * and the object in question is not a realtime object, it * is wrapped in a AUDIO_IO_DB_CLIENT object before * inserted to the chainsetup. Otherwise object is added * as is. * * Ownership of the insert object is transfered to * ECA_CHAINSETUP. * * @pre aiod != 0 * @pre is_enabled() != true * @post outputs.size() == outputs_direct_rep.size() */ void ECA_CHAINSETUP::add_output(AUDIO_IO* aio, bool truncate) { // -------- DBC_REQUIRE(aio != 0); DBC_REQUIRE(is_enabled() != true); DBC_DECLARE(size_t old_outputs_size = outputs.size()); // -------- aio->set_audio_format(default_audio_format()); aio->set_buffersize(buffersize()); if (truncate == true) aio->set_io_mode(AUDIO_IO::io_write); else aio->set_io_mode(AUDIO_IO::io_readwrite); register_audio_object_to_manager(aio); AUDIO_IO* layerobj = add_audio_object_helper(aio); outputs.push_back(layerobj); outputs_direct_rep.push_back(aio); output_start_pos.push_back(0); attach_output_to_selected_chains(layerobj); // --- DBC_ENSURE(outputs.size() == old_outputs_size + 1); DBC_ENSURE(outputs.size() == outputs_direct_rep.size()); // --- } /** * Erases an element matching 'obj' from 'vec'. At most one element * is removed. The function does not delete the referred object, just * removes it from the vector. */ static void priv_erase_object(std::vector* vec, const AUDIO_IO* obj) { vector::iterator p = vec->begin(); while(p != vec->end()) { if (*p == obj) { vec->erase(p); break; } ++p; } } /** * Removes the labeled audio object from this chainsetup. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::remove_audio_object_impl(const string& label, int dir, bool destroy) { // --- DBC_REQUIRE(is_enabled() != true); // --- vector *objs = (dir == cs_dir_input ? &inputs : &outputs); vector *objs_dir = (dir == cs_dir_input ? &inputs_direct_rep : &outputs_direct_rep); DBC_DECLARE(size_t oldsize = objs->size()); AUDIO_IO *obj_to_remove = 0, *obj_dir_to_remove = NULL; int remove_index = -1; /* Notes * - objs and objs_dir vectors are always of the same size * - for non-proxied objects 'objs[n] == objs_dir[n]' for all n */ for(size_t n = 0; n < objs->size(); n++) { if ((*objs)[n]->label() == label) { obj_to_remove = (*objs)[n]; obj_dir_to_remove = (*objs_dir)[n]; remove_index = static_cast(n); } } if (obj_to_remove) { DBC_CHECK(remove_index >= 0); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Removing object " + obj_to_remove->label() + "."); /* disconnect object from chains */ vector::iterator q = chains.begin(); while(q != chains.end()) { if (dir == cs_dir_input) { if ((*q)->connected_input() == remove_index) { (*q)->disconnect_input(); } } else { if ((*q)->connected_output() == remove_index) { (*q)->disconnect_output(); } } ++q; } /* unregister from manager (always the objs_dir object) */ unregister_audio_object_from_manager((*objs_dir)[remove_index]); /* delete proxy object if any */ if (obj_to_remove != obj_dir_to_remove) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Audio object proxied: " + obj_to_remove->label()); remove_audio_object_proxy(obj_to_remove); } priv_erase_object(objs, obj_to_remove); priv_erase_object(objs_dir, obj_dir_to_remove); LOOP_DEVICE* loop_dev = dynamic_cast(obj_dir_to_remove); if (loop_dev != 0 && destroy == true) { /* note: destroy must be true to limit recursion */ remove_audio_object_loop(label, obj_dir_to_remove, dir); } /* finally actually delete the object */ if (destroy == true) delete obj_dir_to_remove; } // --- DBC_ENSURE(objs->size() == objs_dir->size()); DBC_ENSURE(oldsize == objs->size() + 1); // --- } /** * Removes the labeled audio input from this chainsetup. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::remove_audio_input(const string& label) { // --- DBC_REQUIRE(is_enabled() != true); DBC_DECLARE(size_t oldsize = inputs.size()); // --- remove_audio_object_impl(label, cs_dir_input, true); // --- DBC_ENSURE(inputs.size() == inputs_direct_rep.size()); DBC_ENSURE(oldsize == inputs.size() + 1); // --- } /** * Removes the labeled audio output from this chainsetup. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::remove_audio_output(const string& label) { // -------- DBC_REQUIRE(is_enabled() != true); DBC_DECLARE(size_t oldsize = outputs.size()); // -------- remove_audio_object_impl(label, cs_dir_output, true); // --- DBC_ENSURE(outputs.size() == outputs_direct_rep.size()); DBC_ENSURE(oldsize == outputs.size() + 1); // --- } /** * Print trace messages when opening audio file 'aio'. * * @pre aio != 0 */ void ECA_CHAINSETUP::audio_object_open_info(const AUDIO_IO* aio) { // -------- DBC_REQUIRE(aio != 0); // -------- string temp = "Opened "; temp += (aio->io_mode() == AUDIO_IO::io_read) ? "input" : "output"; temp += " \"" + aio->label(); temp += "\", mode \""; if (aio->io_mode() == AUDIO_IO::io_read) temp += "read"; if (aio->io_mode() == AUDIO_IO::io_write) temp += "write"; if (aio->io_mode() == AUDIO_IO::io_readwrite) temp += "read/write (update)"; temp += "\". "; temp += aio->format_info(); ECA_LOG_MSG(ECA_LOGGER::info, temp); } /** * Adds a new MIDI-device object. * * @pre mididev != 0 * @pre is_enabled() != true * @post midi_devices.size() > 0 */ void ECA_CHAINSETUP::add_midi_device(MIDI_IO* mididev) { // -------- DBC_REQUIRE(mididev != 0); DBC_REQUIRE(is_enabled() != true); // -------- midi_devices.push_back(mididev); impl_repp->midi_server_rep.register_client(mididev); // -------- DBC_ENSURE(midi_devices.size() > 0); // -------- } /** * Remove an MIDI-device by the name 'mdev_name'. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::remove_midi_device(const string& mdev_name) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- for(vector::iterator q = midi_devices.begin(); q != midi_devices.end(); q++) { if (mdev_name == (*q)->label()) { delete *q; midi_devices.erase(q); break; } } } const CHAIN* ECA_CHAINSETUP::get_chain_with_name(const string& name) const { vector::const_iterator p = chains.begin(); while(p != chains.end()) { if ((*p)->name() == name) return(*p); ++p; } return 0; } /** * Returns a non-zero index for chain 'name'. If the chain * does not exist, -1 is returned. * * The chain index can be used with ECA::chainsetup_edit_t * items passed to ECA_CHAINSETUP::execute_edit(). * * Note: Mapping of chain names to indices can change if any * chains are either added or removed. If that happens, * the indices need to be recalculated. */ int ECA_CHAINSETUP::get_chain_index(const string& name) const { int retval = -1; vector::const_iterator p = chains.begin(); for(int n = 1; p != chains.end(); n++) { if ((*p)->name() == name) { retval = n; break; } ++p; } return retval; } /** * Attaches input 'obj' to all selected chains. * * @pre is_locked() != true */ void ECA_CHAINSETUP::attach_input_to_selected_chains(const AUDIO_IO* obj) { // -------- DBC_REQUIRE(obj != 0); DBC_REQUIRE(is_locked() != true); // -------- string temp; vector::size_type c = 0; while (c < inputs.size()) { if (inputs[c] == obj) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if ((*q)->connected_input() == static_cast(c)) { (*q)->disconnect_input(); } } temp += "Assigning file to chains:"; for(vector::const_iterator p = selected_chainids.begin(); p!= selected_chainids.end(); p++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*p == (*q)->name()) { (*q)->connect_input(c); temp += " " + *p; } } } } ++c; } ECA_LOG_MSG(ECA_LOGGER::system_objects, temp); } /** * Attaches output 'obj' to all selected chains. * * @pre is_locked() != true */ void ECA_CHAINSETUP::attach_output_to_selected_chains(const AUDIO_IO* obj) { // -------- DBC_REQUIRE(obj != 0); DBC_REQUIRE(is_locked() != true); // -------- string temp; vector::size_type c = 0; while (c < outputs.size()) { if (outputs[c] == obj) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if ((*q)->connected_output() == static_cast(c)) { (*q)->disconnect_output(); } } temp += "Assigning file to chains:"; for(vector::const_iterator p = selected_chainids.begin(); p!= selected_chainids.end(); p++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*p == (*q)->name()) { (*q)->connect_output(static_cast(c)); temp += " " + *p; } } } } ++c; } ECA_LOG_MSG(ECA_LOGGER::system_objects, temp); } /** * Returns true if 'aobj' is a pointer to some input * or output object. */ bool ECA_CHAINSETUP::ok_audio_object(const AUDIO_IO* aobj) const { if (ok_audio_object_helper(aobj, inputs) == true || ok_audio_object_helper(aobj, outputs) == true ) return(true); return false; } bool ECA_CHAINSETUP::ok_audio_object_helper(const AUDIO_IO* aobj, const vector& aobjs) { for(size_t n = 0; n < aobjs.size(); n++) { if (aobjs[n] == aobj) return(true); } return false; } void ECA_CHAINSETUP::check_object_samplerate(const AUDIO_IO* obj, SAMPLE_SPECS::sample_rate_t srate) throw(ECA_ERROR&) { if (obj->samples_per_second() != srate) { throw(ECA_ERROR("ECA-CHAINSETUP", string("All audio objects must have a common") + " sampling rate; sampling rate of audio object \"" + obj->label() + "\" differs from engine rate (" + kvu_numtostr(obj->samples_per_second()) + " <-> " + kvu_numtostr(srate) + "); unable to continue.")); } } void ECA_CHAINSETUP::enable_audio_object_helper(AUDIO_IO* aobj) const { aobj->set_buffersize(buffersize()); AUDIO_IO_DEVICE* dev = dynamic_cast(aobj); if (dev != 0) { dev->toggle_max_buffers(max_buffers()); dev->toggle_ignore_xruns(ignore_xruns()); } if (aobj->is_open() == false) { const std::string req_format = ECA_OBJECT_FACTORY::audio_object_format_to_eos(aobj); aobj->open(); const std::string act_format = ECA_OBJECT_FACTORY::audio_object_format_to_eos(aobj); if (act_format != req_format) { DBC_CHECK(aobj->locked_audio_format() == true); ECA_LOG_MSG(ECA_LOGGER::info, "NOTE: using existing audio parameters " + act_format + " for object '" + aobj->label() + " (tried to open with " + req_format + ")."); } } if (aobj->is_open() == true) { aobj->seek_position_in_samples(aobj->position_in_samples()); audio_object_open_info(aobj); } } /** * Enable chainsetup. Opens all devices and reinitializes all * chain operators if necessary. * * This action is performed before connecting the chainsetup * to a engine object (for instance ECA_ENGINE). * * @pre is_locked() != true * @post is_enabled() == true */ void ECA_CHAINSETUP::enable(void) throw(ECA_ERROR&) { // -------- DBC_REQUIRE(is_locked() != true); // -------- try { if (is_enabled_rep != true) { /* 1. check that current buffersize is supported by all devices */ long int locked_bsize = check_for_locked_buffersize(); if (locked_bsize != -1) { set_buffersize(locked_bsize); } /* 2. select and enable buffering parameters */ select_active_buffering_mode(); enable_active_buffering_mode(); /* 3.1 open input devices */ for(vector::iterator q = inputs.begin(); q != inputs.end(); q++) { enable_audio_object_helper(*q); if ((*q)->is_open() != true) { throw(ECA_ERROR("ECA-CHAINSETUP", "Open failed without explicit exception!")); } } /* 3.2. make sure that all input devices have a common * sampling rate */ SAMPLE_SPECS::sample_rate_t first_locked_srate = 0; for(vector::iterator q = inputs.begin(); q != inputs.end(); q++) { if (first_locked_srate == 0) { if ((*q)->locked_audio_format() == true) { first_locked_srate = (*q)->samples_per_second(); /* set chainsetup sampling rate to 'first_srate'. */ set_samples_per_second(first_locked_srate); } } else { check_object_samplerate(*q, first_locked_srate); } } /* 4. open output devices */ for(vector::iterator q = outputs.begin(); q != outputs.end(); q++) { enable_audio_object_helper(*q); if ((*q)->is_open() != true) { throw(ECA_ERROR("ECA-CHAINSETUP", "Open failed without explicit exception!")); } if (first_locked_srate == 0) { if ((*q)->locked_audio_format() == true) { first_locked_srate = (*q)->samples_per_second(); /* set chainsetup sampling rate to 'first_srate'. */ set_samples_per_second(first_locked_srate); } } else { check_object_samplerate(*q, first_locked_srate); } } /* 5. in case there were no objects with locked srates */ if (first_locked_srate == 0) { if (inputs.size() > 0) { /* set chainsetup srate to that of the first input */ set_samples_per_second(inputs[0]->samples_per_second()); } } /* 6. enable the MIDI server */ if (impl_repp->midi_server_rep.is_enabled() != true && midi_devices.size() > 0) { impl_repp->midi_server_rep.set_schedrealtime(raised_priority()); impl_repp->midi_server_rep.set_schedpriority(get_sched_priority()); impl_repp->midi_server_rep.enable(); } /* 7. enable all MIDI-devices */ for(vector::iterator q = midi_devices.begin(); q != midi_devices.end(); q++) { (*q)->toggle_nonblocking_mode(true); if ((*q)->is_open() != true) { (*q)->open(); if ((*q)->is_open() != true) { throw(ECA_ERROR("ECA-CHAINSETUP", string("Unable to open MIDI-device: ") + (*q)->label() + ".")); } } } /* 8. calculate chainsetup length */ calculate_processing_length(); } is_enabled_rep = true; } catch(AUDIO_IO::SETUP_ERROR& e) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Connecting chainsetup failed, throwing an SETUP_ERROR exception."); throw(ECA_ERROR("ECA-CHAINSETUP", string("Enabling chainsetup: ") + e.message())); } catch(...) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Connecting chainsetup failed, throwing a generic exception."); throw; } // -------- DBC_ENSURE(is_enabled() == true); // -------- } /** * Disable chainsetup. Closes all devices. * * This action is performed before disconnecting the * chainsetup from a engine object (for instance * ECA_ENGINE). * * @pre is_locked() != true * @post is_enabled() != true */ void ECA_CHAINSETUP::disable(void) { // -------- DBC_REQUIRE(is_locked() != true); // -------- /* calculate chainsetup length in case it has changed during processing */ calculate_processing_length(); if (is_enabled_rep == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Closing chainsetup \"" + name() + "\""); for(vector::iterator q = inputs.begin(); q != inputs.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Closing audio device/file \"" + (*q)->label() + "\"."); if ((*q)->is_open() == true) (*q)->close(); } for(vector::iterator q = outputs.begin(); q != outputs.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Closing audio device/file \"" + (*q)->label() + "\"."); if ((*q)->is_open() == true) (*q)->close(); } if (impl_repp->midi_server_rep.is_enabled() == true) impl_repp->midi_server_rep.disable(); for(vector::iterator q = midi_devices.begin(); q != midi_devices.end(); q++) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Closing midi device \"" + (*q)->label() + "\"."); if ((*q)->is_open() == true) (*q)->close(); } is_enabled_rep = false; } // -------- DBC_ENSURE(is_enabled() != true); // -------- } /** * Executes chainsetup edit 'edit'. * * @return true if succesful, false if edit cannot * be performed */ bool ECA_CHAINSETUP::execute_edit(const chainsetup_edit_t& edit) { bool retval = true; ECA_LOG_MSG(ECA_LOGGER::user_objects, "Executing edit type of " + kvu_numtostr(static_cast(edit.type))); if (edit.cs_ptr != this) { ECA_LOG_MSG(ECA_LOGGER::errors, "ERROR: chainsetup edit executed on wrong object"); return false; } switch(edit.type) { case edit_cop_set_param: { if (edit.m.cop_set_param.chain < 1 || edit.m.cop_set_param.chain > static_cast(chains.size())) { retval = false; break; } CHAIN *ch = chains[edit.m.cop_set_param.chain - 1]; ch->set_parameter(edit.m.cop_set_param.op, edit.m.cop_set_param.param, edit.m.cop_set_param.value); break; } case edit_ctrl_set_param: { if (edit.m.ctrl_set_param.chain < 1 || edit.m.ctrl_set_param.chain > static_cast(chains.size())) { retval = false; break; } CHAIN *ch = chains[edit.m.ctrl_set_param.chain - 1]; ch->set_controller_parameter(edit.m.ctrl_set_param.op, edit.m.ctrl_set_param.param, edit.m.ctrl_set_param.value); break; } default: { DBC_NEVER_REACHED(); retval = false; ECA_LOG_MSG(ECA_LOGGER::info, "Unknown edit of type " + kvu_numtostr(static_cast(edit.type))); break; } } return retval; } /** * Updates the chainsetup processing length based on * 1) requested length, 2) lengths of individual * input objects, and 3) looping settings. */ void ECA_CHAINSETUP::calculate_processing_length(void) { long int max_input_length = 0; for(unsigned int n = 0; n < inputs.size(); n++) { if (inputs[n]->length_in_samples() > max_input_length) max_input_length = inputs[n]->length_in_samples(); } /* note! here we set the _actual_ length of the * chainsetup */ set_length_in_samples(max_input_length); if (looping_enabled() == true) { if (max_length_set() != true && max_input_length > 0) { /* looping but length not set */ ECA_LOG_MSG(ECA_LOGGER::info, "Setting loop point to " + kvu_numtostr(length_in_seconds_exact()) + "."); set_max_length_in_samples(max_input_length); } } } /** * Check whether the buffersize is locked to some * specific value. * * @return -1 if not locked, otherwise the locked * value */ long int ECA_CHAINSETUP::check_for_locked_buffersize(void) const { long int result = -1; #ifdef ECA_COMPILE_JACK int pid = getpid(); string cname = "ecasound-ctrl-" + kvu_numtostr(pid); int jackobjs = 0; for(size_t n = 0; n < inputs_direct_rep.size(); n++) { if (inputs_direct_rep[n]->name() == "JACK interface") ++jackobjs; } for(size_t n = 0; n < outputs_direct_rep.size(); n++) { if (outputs_direct_rep[n]->name() == "JACK interface") ++jackobjs; } /* contact jackd only if there is at least one jack audio object * present */ if (jackobjs > 0) { jack_client_t *client = jack_client_new (cname.c_str()); if (client != 0) { // xxx = static_cast(jack_get_sample_rate(client); result = static_cast(jack_get_buffer_size(client)); ECA_LOG_MSG(ECA_LOGGER::user_objects, "jackd buffersize check returned " + kvu_numtostr(result) + "."); jack_client_close(client); client = 0; } else { ECA_LOG_MSG(ECA_LOGGER::user_objects, "unable to perform jackd buffersize check."); } DBC_CHECK(client == 0); } #endif return result; } /** * Reimplemented from ECA_CHAINSETUP_POSITION. */ void ECA_CHAINSETUP::set_samples_per_second(SAMPLE_SPECS::sample_rate_t new_value) { /* not necessarily a problem */ DBC_CHECK(is_locked() != true); ECA_LOG_MSG(ECA_LOGGER::user_objects, "sample rate change, chainsetup " + name() + " to rate " + kvu_numtostr(new_value) + "."); for(vector::iterator q = inputs.begin(); q != inputs.end(); q++) { (*q)->set_samples_per_second(new_value); } for(vector::iterator q = outputs.begin(); q != outputs.end(); q++) { (*q)->set_samples_per_second(new_value); } for(vector::iterator q = chains.begin(); q != chains.end(); q++) { (*q)->set_samples_per_second(new_value); } ECA_CHAINSETUP_POSITION::set_samples_per_second(new_value); } static void priv_seek_position_helper(std::vector* objs, SAMPLE_SPECS::sample_pos_t pos, const std::string& tag) { for(vector::iterator q = objs->begin(); q != objs->end(); q++) { /* note: don't try to seek real-time devices (only * allowed exception, try seeking all other * objects */ if (dynamic_cast(*q) == 0) { (*q)->seek_position_in_samples(pos); /* note: report if object claims it supports seeking, but * in fact the seek failed */ if ((*q)->supports_seeking() == true) { if (pos <= (*q)->length_in_samples() && (*q)->position_in_samples() != pos) ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: sample accurate seek failed with " + tag + " \"" + (*q)->name() + "\""); } } } } /** * Reimplemented from ECA_AUDIO_POSITION. */ SAMPLE_SPECS::sample_pos_t ECA_CHAINSETUP::seek_position(SAMPLE_SPECS::sample_pos_t pos) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "seek position, chainsetup \"" + name() + "\" to pos in sec " + kvu_numtostr(pos) + "."); if (is_enabled() == true) { if (double_buffering() == true) pserver_repp->flush(); } priv_seek_position_helper(&inputs, pos, "input"); priv_seek_position_helper(&outputs, pos, "output"); for(vector::iterator q = chains.begin(); q != chains.end(); q++) { (*q)->seek_position_in_samples(pos); if ((*q)->position_in_samples() != pos) ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: sample accurate seek failed with chainop \"" + (*q)->name() + "\""); } return pos; } /** * Interprets one option. This is the most generic variant of * the interpretation routines; both global and object specific * options are handled. * * @pre argu.size() > 0 * @pre argu[0] == '-' * @pre is_enabled() != true * * @post (option succesfully interpreted && interpret_result() == true) || * (unknown or invalid option && interpret_result() != true) */ void ECA_CHAINSETUP::interpret_option (const string& arg) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- cparser_rep.interpret_option(arg); } /** * Interprets one option. All non-global options are ignored. Global * options can be interpreted multiple times and in any order. * * @pre argu.size() > 0 * @pre argu[0] == '-' * @pre is_enabled() != true * @post (option succesfully interpreted && interpretation_result() == true) || * (unknown or invalid option && interpretation_result() == false) */ void ECA_CHAINSETUP::interpret_global_option (const string& arg) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- cparser_rep.interpret_global_option(arg); } /** * Interprets one option. All options not directly related to * ecasound objects are ignored. * * @pre argu.size() > 0 * @pre argu[0] == '-' * @pre is_enabled() != true * * @post (option succesfully interpreted && interpretation_result() == true) || * (unknown or invalid option && interpretation_result() == false) */ void ECA_CHAINSETUP::interpret_object_option (const string& arg) { // -------- // FIXME: this requirement is broken by eca-control.h (for // adding effects on-the-fly, just stopping the engine) // DBC_REQUIRE(is_enabled() != true); // -------- cparser_rep.interpret_object_option(arg); } /** * Interpret a vector of options. * * If any invalid options are passed us argument, * interpret_result() will be 'false', and * interpret_result_verbose() contains more detailed * error description. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::interpret_options(const vector& opts) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- cparser_rep.interpret_options(opts); } void ECA_CHAINSETUP::set_buffersize(long int value) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "overriding buffersize."); impl_repp->bmode_override_rep.set_buffersize(value); } void ECA_CHAINSETUP::toggle_raised_priority(bool value) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "overriding raised priority."); impl_repp->bmode_override_rep.toggle_raised_priority(value); } void ECA_CHAINSETUP::set_sched_priority(int value) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "sched_priority."); impl_repp->bmode_override_rep.set_sched_priority(value); } void ECA_CHAINSETUP::toggle_double_buffering(bool value) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "overriding doublebuffering."); impl_repp->bmode_override_rep.toggle_double_buffering(value); } void ECA_CHAINSETUP::set_double_buffer_size(long int v) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "overriding db-size."); impl_repp->bmode_override_rep.set_double_buffer_size(v); } void ECA_CHAINSETUP::toggle_max_buffers(bool v) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "overriding max_buffers."); impl_repp->bmode_override_rep.toggle_max_buffers(v); } long int ECA_CHAINSETUP::buffersize(void) const { if (impl_repp->bmode_override_rep.is_set_buffersize() == true) return impl_repp->bmode_override_rep.buffersize(); return impl_repp->bmode_active_rep.buffersize(); } bool ECA_CHAINSETUP::raised_priority(void) const { if (impl_repp->bmode_override_rep.is_set_raised_priority() == true) return impl_repp->bmode_override_rep.raised_priority(); return impl_repp->bmode_active_rep.raised_priority(); } int ECA_CHAINSETUP::get_sched_priority(void) const { if (impl_repp->bmode_override_rep.is_set_sched_priority() == true) return impl_repp->bmode_override_rep.get_sched_priority(); return impl_repp->bmode_active_rep.get_sched_priority(); } bool ECA_CHAINSETUP::double_buffering(void) const { if (impl_repp->bmode_override_rep.is_set_double_buffering() == true) return impl_repp->bmode_override_rep.double_buffering(); return impl_repp->bmode_active_rep.double_buffering(); } long int ECA_CHAINSETUP::double_buffer_size(void) const { if (impl_repp->bmode_override_rep.is_set_double_buffer_size() == true) return impl_repp->bmode_override_rep.double_buffer_size(); return impl_repp->bmode_active_rep.double_buffer_size(); } bool ECA_CHAINSETUP::max_buffers(void) const { if (impl_repp->bmode_override_rep.is_set_max_buffers() == true) return impl_repp->bmode_override_rep.max_buffers(); return impl_repp->bmode_active_rep.max_buffers(); } void ECA_CHAINSETUP::set_default_audio_format(ECA_AUDIO_FORMAT& value) { impl_repp->default_audio_format_rep = value; } const ECA_AUDIO_FORMAT& ECA_CHAINSETUP::default_audio_format(void) const { return impl_repp->default_audio_format_rep; } /** * Select controllers as targets for parameter control */ void ECA_CHAINSETUP::set_target_to_controller(void) { vector schains = selected_chains(); for(vector::const_iterator a = schains.begin(); a != schains.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { (*q)->selected_controller_as_target(); return; } } } } /** * Add general controller to selected chainop. * * @pre csrc != 0 * @pre is_locked() != true * @pre selected_chains().size() == 1 */ void ECA_CHAINSETUP::add_controller(GENERIC_CONTROLLER* csrc) { // -------- DBC_REQUIRE(csrc != 0); DBC_REQUIRE(is_locked() != true); DBC_REQUIRE(selected_chains().size() == 1); // -------- #ifndef ECA_DISABLE_EFFECTS AUDIO_STAMP_CLIENT* p = dynamic_cast(csrc->source_pointer()); if (p != 0) { p->register_server(&impl_repp->stamp_server_rep); } #endif DBC_CHECK(buffersize() != 0); DBC_CHECK(samples_per_second() != 0); vector schains = selected_chains(); for(vector::const_iterator a = schains.begin(); a != schains.end(); a++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*a == (*q)->name()) { if ((*q)->selected_target() == 0) return; (*q)->add_controller(csrc); return; } } } } /** * Add chain operator to selected chain. * * @pre cotmp != 0 * @pre is_locked() != true * @pre selected_chains().size() == 1 */ void ECA_CHAINSETUP::add_chain_operator(CHAIN_OPERATOR* cotmp) { // -------- DBC_REQUIRE(cotmp != 0); DBC_REQUIRE(is_locked() != true); DBC_REQUIRE(selected_chains().size() == 1); // -------- #ifndef ECA_DISABLE_EFFECTS AUDIO_STAMP* p = dynamic_cast(cotmp); if (p != 0) { impl_repp->stamp_server_rep.register_stamp(p); } #endif vector schains = selected_chains(); for(vector::const_iterator p = schains.begin(); p != schains.end(); p++) { for(vector::iterator q = chains.begin(); q != chains.end(); q++) { if (*p == (*q)->name()) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Adding chainop to chain " + (*q)->name() + "."); (*q)->add_chain_operator(cotmp); (*q)->selected_chain_operator_as_target(); return; } } } } /** * If chainsetup has inputs, but no outputs, a default output is * added. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::add_default_output(void) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- if (inputs.size() > 0 && outputs.size() == 0) { // No -o[:] options specified; let's use the default output select_all_chains(); interpret_object_option(string("-o:") + ECA_OBJECT_FACTORY::probe_default_output_device()); } } /** * If chainsetup has objects that need MIDI services, * but no MIDI-devices defined, a default MIDI-device is * added. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::add_default_midi_device(void) { if (midi_server_needed_rep == true && midi_devices.size() == 0) { cparser_rep.interpret_object_option("-Md:" + default_midi_device()); } } /** * Loads chainsetup options from file. * * @pre is_enabled() != true */ void ECA_CHAINSETUP::load_from_file(const string& filename, vector& opts) const throw(ECA_ERROR&) { // -------- DBC_REQUIRE(is_enabled() != true); // -------- std::ifstream fin (filename.c_str()); if (!fin) throw(ECA_ERROR("ECA_CHAINSETUP", "Couldn't open setup read file: \"" + filename + "\".", ECA_ERROR::retry)); vector options; string temp; while(getline(fin,temp)) { if (temp.size() > 0 && temp[0] == '#') { continue; } // FIXME: we should add quoting when saving the chainsetup or // give on quoting altogether... vector words = kvu_string_to_tokens_quoted(temp); for(unsigned int n = 0; n < words.size(); n++) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Adding \"" + words[n] + "\" to options (loaded from \"" + filename + "\"."); options.push_back(words[n]); } } fin.close(); opts = COMMAND_LINE::combine(options); } void ECA_CHAINSETUP::save(void) throw(ECA_ERROR&) { if (setup_filename_rep.empty() == true) setup_filename_rep = setup_name_rep + ".ecs"; save_to_file(setup_filename_rep); } void ECA_CHAINSETUP::save_to_file(const string& filename) throw(ECA_ERROR&) { // make sure that all overrides are processed select_active_buffering_mode(); std::ofstream fout (filename.c_str()); if (!fout) { cerr << "Going to throw an exception...\n"; throw(ECA_ERROR("ECA_CHAINSETUP", "Couldn't open setup save file: \"" + filename + "\".", ECA_ERROR::retry)); } else { fout << "# ecasound chainsetup file" << endl; fout << endl; fout << "# general " << endl; fout << cparser_rep.general_options_to_string() << endl; fout << endl; string tmpstr = cparser_rep.midi_to_string(); if (tmpstr.size() > 0) { fout << "# MIDI " << endl; fout << tmpstr << endl; fout << endl; } fout << "# audio inputs " << endl; fout << cparser_rep.inputs_to_string() << endl; fout << endl; fout << "# audio outputs " << endl; fout << cparser_rep.outputs_to_string() << endl; fout << endl; tmpstr = cparser_rep.chains_to_string(); if (tmpstr.size() > 0) { fout << "# chain operators and controllers " << endl; fout << tmpstr << endl; fout << endl; } fout.close(); set_filename(filename); } }