// ------------------------------------------------------------------------ // eca-engine.cpp: Main processing engine // Copyright (C) 1999-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 // ------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include /* gettimeofday() */ #include #include #include #include #include #include "samplebuffer.h" #include "audioio.h" #include "audioio-buffered.h" #include "audioio-device.h" #include "audioio-db-client.h" #include "audioio-loop.h" #include "audioio-barrier.h" #include "audioio-mp3.h" #include "midi-server.h" #include "eca-chain.h" #include "eca-chainop.h" #include "eca-error.h" #include "eca-logger.h" #include "eca-chainsetup-edit.h" #include "eca-engine.h" #include "eca-engine_impl.h" using std::cerr; using std::endl; using std::vector; /** * Enable and disable features */ /* Profile callback execution */ // #define PROFILE_ENGINE /** * Local macro definitions */ #ifdef PROFILE_ENGINE_EXECUTION #define PROFILE_ENGINE_STATEMENT(x) (x) #else #define PROFILE_ENGINE_STATEMENT(x) ((void)0) #endif /** * Prototypes of static functions */ static void mix_to_outputs_divide_helper(const SAMPLE_BUFFER *from, SAMPLE_BUFFER *to, int divide_by, bool first_time); static void mix_to_outputs_sum_helper(const SAMPLE_BUFFER *from, SAMPLE_BUFFER *to, bool first_time); /** * Implementations of non-static functions */ /********************************************************************** * Driver implementation **********************************************************************/ int ECA_ENGINE_DEFAULT_DRIVER::exec(ECA_ENGINE* engine, ECA_CHAINSETUP* csetup) { engine_repp = engine; exit_request_rep = false; engine->init_engine_state(); while(true) { engine_repp->check_command_queue(); /* case 1: external exit request */ if (exit_request_rep == true) break; /* case 2: engine running, execute one loop iteration */ if (engine_repp->status() == ECA_ENGINE::engine_status_running) { engine_repp->engine_iteration(); } else { /* case 3a-i: engine finished and in batch mode -> exit */ if (engine_repp->status() == ECA_ENGINE::engine_status_finished && engine_repp->batch_mode() == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "batch finished in exec, exit"); break; } /* case 3a-ii: engine error occured -> exit */ else if (engine_repp->status() == ECA_ENGINE::engine_status_error) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "engine error, exit"); break; } /* case 3b: engine not running, wait for commands */ engine_repp->wait_for_commands(); } engine_repp->update_engine_state(); } if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); return 0; } void ECA_ENGINE_DEFAULT_DRIVER::start(void) { if (engine_repp->is_prepared() != true) engine_repp->prepare_operation(); engine_repp->start_operation(); } void ECA_ENGINE_DEFAULT_DRIVER::stop(void) { if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); } void ECA_ENGINE_DEFAULT_DRIVER::exit(void) { if (engine_repp->is_prepared() == true) engine_repp->stop_operation(); exit_request_rep = true; /* no need to block here as the next * function will be exec() */ } /********************************************************************** * Engine implementation - Public functions **********************************************************************/ /** * Context help: * J = originates from driver callback * E = ----- " ------- engine thread (exec()) * C = ----- " ------- control thread (external) * * X-level-Y -> Y = steps from originating functions */ /** * Class constructor. A pointer to an enabled * ECA_CHAINSETUP object must be given as argument. * * @pre csetup != 0 * @pre csetup->is_enabled() == true * @post status() == ECA_ENGINE::engine_status_stopped */ ECA_ENGINE::ECA_ENGINE(ECA_CHAINSETUP* csetup) : prepared_rep(false), running_rep(false), edit_lock_rep(false), finished_rep(false), outputs_finished_rep(0), driver_errors_rep(0), csetup_repp(csetup), mixslot_repp(0) { // -- DBC_REQUIRE(csetup != 0); DBC_REQUIRE(csetup->is_enabled() == true); // -- ECA_LOG_MSG(ECA_LOGGER::system_objects, "ECA_ENGINE constructor"); csetup_repp->toggle_locked_state(true); impl_repp = new ECA_ENGINE_impl; mixslot_repp = new SAMPLE_BUFFER(buffersize(), 0); init_variables(); init_connection_to_chainsetup(); PROFILE_ENGINE_STATEMENT(init_profiling()); csetup_repp->toggle_locked_state(false); // -- DBC_ENSURE(status() == ECA_ENGINE::engine_status_stopped); // -- } /** * Class destructor. */ ECA_ENGINE::~ECA_ENGINE(void) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "ECA_ENGINE destructor"); if (csetup_repp != 0) { command(ECA_ENGINE::ep_exit, 0.0f); wait_for_exit(5); if (csetup_repp != 0) { cleanup(); } } PROFILE_ENGINE_STATEMENT(dump_profile_info()); if (driver_local == true) { delete driver_repp; driver_repp = 0; } for(size_t n = 0; n < cslots_rep.size(); n++) { delete cslots_rep[n]; } delete mixslot_repp; delete impl_repp; ECA_LOG_MSG(ECA_LOGGER::subsystems, "Engine exiting"); } /** * Launches the engine. This function will block * until processing is finished. * * Note that a exec() is a one-shot function. * It's not possible to call it multiple times. * * @param batch_mode if true, once engine is started * it will continue processing until * 'status() == engine_status_finished' * condition is reached and then exit; * if false, engine will react to * commands until explicit 'exit' is * given * * @see command() * @see status() * * @pre is_valid() == true * @post status() == ECA_ENGINE::engine_status_notready * @post is_valid() != true */ int ECA_ENGINE::exec(bool batch_mode) { // -- DBC_REQUIRE(csetup_repp != 0); DBC_REQUIRE(csetup_repp->is_enabled() == true); // -- int result = 0; csetup_repp->toggle_locked_state(true); batchmode_enabled_rep = batch_mode; ECA_LOG_MSG(ECA_LOGGER::subsystems, "Engine - Driver start"); int res = driver_repp->exec(this, csetup_repp); if (res < 0) { ++driver_errors_rep; ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Engine has raised an error! " "Possible causes: connection lost to system services, unable to adapt " "to changes in operating environment, etc."); } csetup_repp->toggle_locked_state(false); signal_exit(); if (outputs_finished_rep > 0) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: An output object has raised an error! " "Possible causes: Out of disk space, permission denied, unable to launch external " "applications needed in processing, etc."); } DBC_CHECK(status() == ECA_ENGINE::engine_status_stopped || status() == ECA_ENGINE::engine_status_finished || status() == ECA_ENGINE::engine_status_error); if (status() == ECA_ENGINE::engine_status_error) { result = -1; } cleanup(); ECA_LOG_MSG(ECA_LOGGER::user_objects, "Engine state when finishing: " + kvu_numtostr(static_cast(status()))); // -- DBC_ENSURE(status() == ECA_ENGINE::engine_status_notready); // -- return result; } /** * Sends 'cmd' to engines command queue. Commands are * processed in the server's main loop. * * context: C-level-0 * must no be called from exec() context */ void ECA_ENGINE::command(Engine_command_t cmd, double arg) { ECA_ENGINE::complex_command_t item; item.type = cmd; item.m.legacy.value = arg; impl_repp->command_queue_rep.push_back(item); } /** * Sends 'ccmd' to engines command queue. Commands are * processed in the server's main loop. Passing a complex * command allows to address objects regardless of * the state of ECA_CHAINSETUP iterators (i.e. currently * selected objects). * * context: C-level-0 * must no be called from exec() context */ void ECA_ENGINE::command(complex_command_t ccmd) { impl_repp->command_queue_rep.push_back(ccmd); } /** * Wait for a stop signal. Functions blocks until * the signal is received or 'timeout' seconds * has elapsed. * * context: C-level-0 * must not be run from the engine * driver context * * @see signal_stop() */ void ECA_ENGINE::wait_for_stop(int timeout) { int ret = kvu_pthread_timed_wait(&impl_repp->ecasound_stop_mutex_repp, &impl_repp->ecasound_stop_cond_repp, timeout); ECA_LOG_MSG(ECA_LOGGER::system_objects, kvu_pthread_timed_wait_result(ret, "wait_for_stop")); } /** * Wait for an exit signal. Function blocks until * the signal is received or 'timeout' seconds * has elapsed. * * context: C-level-0 * * @see signal_exit() */ void ECA_ENGINE::wait_for_exit(int timeout) { int ret = kvu_pthread_timed_wait(&impl_repp->ecasound_exit_mutex_repp, &impl_repp->ecasound_exit_cond_repp, timeout); ECA_LOG_MSG(ECA_LOGGER::info, kvu_pthread_timed_wait_result(ret, "(eca_main) wait_for_exit")); } /********************************************************************** * Engine implementation - Public functions for observing engine * status information **********************************************************************/ /** * Returns true engine's internal * state is valid for processing. * * context: C-level-0 * no limitations */ bool ECA_ENGINE::is_valid(void) const { if (csetup_repp == 0 || csetup_repp->is_enabled() != true || csetup_repp->is_valid() != true || chains_repp == 0 || chains_repp->size() == 0 || inputs_repp == 0 || inputs_repp->size() == 0 || outputs_repp == 0 || outputs_repp->size() == 0) { return false; } return true; } /** * Whether current setup has finite length. * * context: C-level-0 */ bool ECA_ENGINE::is_finite_length(void) const { DBC_CHECK(csetup_repp != 0); if (csetup_repp->max_length_set() == true || csetup_repp->number_of_realtime_inputs() == 0) { return true; } return false; } /** * Returns engine's current status. * * context: C-level-0 * no limitations */ ECA_ENGINE::Engine_status_t ECA_ENGINE::status(void) const { if (csetup_repp == 0) return ECA_ENGINE::engine_status_notready; /* calculated in update_engine_status() */ if (finished_rep == true) return ECA_ENGINE::engine_status_finished; if (outputs_finished_rep > 0 || driver_errors_rep > 0) return ECA_ENGINE::engine_status_error; if (is_running() == true) return ECA_ENGINE::engine_status_running; if (is_prepared() == true) return ECA_ENGINE::engine_status_stopped; return ECA_ENGINE::engine_status_stopped; } /********************************************************************** * Engine implementation - API for engine driver objects **********************************************************************/ /** * Processes available new commands. If no * messages are available, function will return * immediately without blocking. * * context: E-level-1 * can be run at the same time as engine_iteration(); * note! this is called with the engine lock held * so no long operations allowed! */ void ECA_ENGINE::check_command_queue(void) { while(impl_repp->command_queue_rep.is_empty() != true) { ECA_ENGINE::complex_command_t item; int popres = impl_repp->command_queue_rep.pop_front(&item); if (popres <= 0) { /* queue is empty or temporarily unavailable, unable to continue * processing messages without blocking */ break; } switch(item.type) { // --- // Basic commands. // --- case ep_exit: { edit_lock_rep = true; if (status() == engine_status_running || status() == engine_status_finished) request_stop(); impl_repp->command_queue_rep.clear(); ECA_LOG_MSG(ECA_LOGGER::system_objects,"ecasound_queue: exit!"); driver_repp->exit(); return; } // --- // Chain operators (stateless addressing) // --- case ep_exec_edit: { csetup_repp->execute_edit(item.m.cs); break; } case ep_prepare: { if (is_prepared() != true) prepare_operation(); break; } case ep_start: { if (status() != engine_status_running) request_start(); break; } case ep_stop: { if (status() == engine_status_running || status() == engine_status_finished) request_stop(); break; } // --- // Edit locks // --- case ep_edit_lock: { edit_lock_rep = true; break; } case ep_edit_unlock: { edit_lock_rep = false; break; } // --- // Section/chain (en/dis)abling commands. // --- case ep_c_select: { csetup_repp->selected_chain_index_rep = static_cast(item.m.legacy.value); break; } case ep_c_muting: { chain_muting(); break; } case ep_c_bypass: { chain_processing(); break; } // --- // Global position // --- case ep_rewind: { change_position(- item.m.legacy.value); break; } case ep_forward: { change_position(item.m.legacy.value); break; } case ep_setpos: { set_position(item.m.legacy.value); break; } case ep_setpos_samples: { set_position_samples(static_cast(item.m.legacy.value)); break; } case ep_setpos_live_samples: { set_position_samples_live(static_cast(item.m.legacy.value)); break; } case ep_debug: break; } /* switch */ } } /** * Waits for new commands to arrive. Function * will block until at least one new message * is available or until a timeout occurs. * * context: E-level-1 * can be run at the same time as * engine_iteration() */ void ECA_ENGINE::wait_for_commands(void) { impl_repp->command_queue_rep.poll(5, 0); } /** * Intializes internal state variables. * * context: E-level-1 * must not be run at the same * time as engine_iteration() * * @see update_engine_state() */ void ECA_ENGINE::init_engine_state(void) { finished_rep = false; inputs_not_finished_rep = 1; // for the 1st iteration outputs_finished_rep = 0; mixslot_repp->event_tag_set(SAMPLE_BUFFER::tag_end_of_stream, false); for(size_t n = 0; n < cslots_rep.size(); n++) { cslots_rep[n]->event_tag_set(SAMPLE_BUFFER::tag_end_of_stream, false); } } /** * Updates engine state to match current * situation. * * context: J-level-0 * must not be run at the same * time as engine_iteration() * * @see init_engine_state() */ void ECA_ENGINE::update_engine_state(void) { // -- // Check whether all inputs have finished // (note: as update_engine_state() is // guaranteed to not to be called at the same // time as engine_iteration(), this is the // only safe place to calculate finished state if (inputs_not_finished_rep == 0 && outputs_finished_rep == 0 && finished_rep != true) { if (is_running() == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects,"all inputs finished - stop"); // FIXME: this is still wrong, command() is not fully rt-safe // we are not allowed to call request_stop here command(ECA_ENGINE::ep_stop, 0.0f); } state_change_to_finished(); } // -- // Check whether some output has raised an error if (status() == ECA_ENGINE::engine_status_error) { if (is_running() == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects,"output error - stop"); // FIXME: this is still wrong, command() is not fully rt-safe // we are not allowed to call request_stop here command(ECA_ENGINE::ep_stop, 0.0f); } } } /** * Executes one engine loop iteration. It is critical * that this function is only called when engine is * running. * * @pre is_running() == true * * context: J-level-0 */ void ECA_ENGINE::engine_iteration(void) { DBC_CHECK(is_running() == true); PROFILE_ENGINE_STATEMENT(impl_repp->looptimer_rep.start(); impl_repp->looptimer_range_rep.start()); inputs_not_finished_rep = 0; prehandle_control_position(); inputs_to_chains(); process_chains(); // FIXME: add support for sub-buffersize offsets if (preroll_samples_rep >= recording_offset_rep) { /* record material to non-real-time outputs */ mix_to_outputs(false); } else { /* skip slave targets */ mix_to_outputs(true); preroll_samples_rep += buffersize(); } posthandle_control_position(); PROFILE_ENGINE_STATEMENT(impl_repp->looptimer_rep.stop(); impl_repp->looptimer_range_rep.stop()); } /** * Prepares engine for operation. Prepares all * realtime devices and starts servers. * * This function should be called by the * driver before it starts iterating the * engine's main loop. * * context: E-level-1/3 * must not be run at the same time * as engine_iteration() * * @pre is_running() != true * @pre is_prepared() != true * @post is_prepared() == true * @post status() == ECA_ENGINE::engine_status_running */ void ECA_ENGINE::prepare_operation(void) { // --- DBC_REQUIRE(is_running() != true); DBC_REQUIRE(is_prepared() != true); // --- /* 1. acquire rt-lock for chainsetup and samplebuffers */ csetup_repp->toggle_locked_state(true); for(size_t n = 0; n < cslots_rep.size(); n++) { cslots_rep[n]->set_rt_lock(true); } mixslot_repp->set_rt_lock(true); /* 2. reinitialize chains if necessary */ for(size_t i = 0; i != chains_repp->size(); i++) { if ((*chains_repp)[i]->is_initialized() != true) (*chains_repp)[i]->init(0, 0, 0); } /* 3. start subsystem servers and forked audio objects */ start_forked_objects(); start_servers(); /* 4. prepare rt objects */ prepare_realtime_objects(); /* ... initial offset is needed because preroll is * incremented only after checking whether we are * still in preroll mode */ preroll_samples_rep = buffersize(); /* 6. enable rt-scheduling */ if (csetup_repp->raised_priority() == true) { if (kvu_set_thread_scheduling(SCHED_FIFO, csetup_repp->get_sched_priority()) != 0) ECA_LOG_MSG(ECA_LOGGER::system_objects, "Unable to change scheduling policy!"); else ECA_LOG_MSG(ECA_LOGGER::info, std::string("Using realtime-scheduling (SCHED_FIFO:") + kvu_numtostr(csetup_repp->get_sched_priority()) + ")."); } /* 7. change engine to active and running */ prepared_rep = true; init_engine_state(); // --- DBC_ENSURE(is_prepared() == true); DBC_ENSURE(status() == ECA_ENGINE::engine_status_stopped); // --- } /** * Starts engine operation. * * This function should be called by the * driver just before it starts iterating the * engine's main loop. * * context: E-level-1/3 * must not be run at the same time * as engine_iteration() * * @pre is_prepared() == true * @pre is_running() != true * @post is_running() == true * @post status() == ECA_ENGINE::engine_status_running */ void ECA_ENGINE::start_operation(void) { // --- DBC_REQUIRE(is_prepared() == true); DBC_REQUIRE(is_running() != true); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "starting engine operation!"); start_realtime_objects(); running_rep = true; // --- DBC_ENSURE(is_running() == true); DBC_ENSURE(status() == ECA_ENGINE::engine_status_running); // --- } /** * Stops all realtime devices and servers. * * This function should be called by the * driver when it stops iterating the * engine's main loop. * * context: E-level-1/3 * must not be run at the same time * as engine_iteration() * * @pre is_running() == true * @post is_running() != true * @post is_prepared() != true */ void ECA_ENGINE::stop_operation(void) { // --- DBC_REQUIRE(is_prepared() == true); // --- ECA_LOG_MSG(ECA_LOGGER::system_objects, "stopping engine operation!"); running_rep = false; /* stop realtime devices */ for (unsigned int adev_sizet = 0; adev_sizet != realtime_objects_rep.size(); adev_sizet++) { if (realtime_objects_rep[adev_sizet]->is_running() == true) realtime_objects_rep[adev_sizet]->stop(); } prepared_rep = false; /* release samplebuffer rt-locks */ for(size_t n = 0; n < cslots_rep.size(); n++) { cslots_rep[n]->set_rt_lock(false); } mixslot_repp->set_rt_lock(false); stop_servers(); stop_forked_objects(); /* lower priority back to normal */ if (csetup_repp->raised_priority() == true) { if (kvu_set_thread_scheduling(SCHED_OTHER, 0) != 0) ECA_LOG_MSG(ECA_LOGGER::info, "Unable to change scheduling back to SCHED_OTHER!"); else ECA_LOG_MSG(ECA_LOGGER::system_objects, "Changed back to non-realtime scheduling SCHED_OTHER."); } /* release chainsetup lock */ csetup_repp->toggle_locked_state(false); /* signals wait_for_stop() that engine operation has stopped */ signal_stop(); // --- DBC_ENSURE(is_running() != true); DBC_ENSURE(is_prepared() != true); // --- } /** * Whether engine has been actived * with prepare_operation(). * * context: no limitations */ bool ECA_ENGINE::is_prepared(void) const { return prepared_rep; } /** * Whether engine has been started * with start_operation(). * * context: no limitations */ bool ECA_ENGINE::is_running(void) const { return running_rep; } /********************************************************************** * Engine implementation - Attribute functions **********************************************************************/ long int ECA_ENGINE::buffersize(void) const { DBC_CHECK(csetup_repp != 0); return csetup_repp->buffersize(); } int ECA_ENGINE::max_channels(void) const { int result = 0; for(unsigned int n = 0; n < csetup_repp->inputs.size(); n++) { if (csetup_repp->inputs[n]->channels() > result) result = csetup_repp->inputs[n]->channels(); } for(unsigned int n = 0; n < csetup_repp->outputs.size(); n++) { if (csetup_repp->outputs[n]->channels() > result) result = csetup_repp->outputs[n]->channels(); } return result; } /********************************************************************** * Engine implementation - Private functions for transport control **********************************************************************/ /** * Requests the engine driver to start operation. * * This function should only be called from * check_command_queue(). * * @pre status() != engine_status_running * * context: E-level-2 */ void ECA_ENGINE::request_start(void) { // --- DBC_REQUIRE(status() != engine_status_running); // --- ECA_LOG_MSG(ECA_LOGGER::user_objects, "Request start"); // -- // start the driver driver_repp->start(); } /** * Requests the engine driver to stop operation. * * This function should only be called from * check_command_queue(). * * @pre status() == ECA_ENGINE::engine_status_running || * status() == ECA_ENGINE::engine_status_finished * * context: E-level-2 */ void ECA_ENGINE::request_stop(void) { // --- DBC_REQUIRE(status() == engine_status_running || status() == engine_status_finished); // --- ECA_LOG_MSG(ECA_LOGGER::user_objects, "Request stop"); driver_repp->stop(); } /** * Sends a stop signal indicating that engine * state has changed to stopped. * * context: E-level-1/4 * * @see wait_for_stop() */ void ECA_ENGINE::signal_stop(void) { pthread_mutex_lock(&impl_repp->ecasound_stop_mutex_repp); ECA_LOG_MSG(ECA_LOGGER::system_objects, "Signaling stop"); pthread_cond_broadcast(&impl_repp->ecasound_stop_cond_repp); pthread_mutex_unlock(&impl_repp->ecasound_stop_mutex_repp); } /** * Sends an exit signal indicating that engine * driver has exited. * * context: E-level-1/4 * * @see wait_for_exit() */ void ECA_ENGINE::signal_exit(void) { pthread_mutex_lock(&impl_repp->ecasound_exit_mutex_repp); ECA_LOG_MSG(ECA_LOGGER::system_objects, "Signaling exit"); pthread_cond_broadcast(&impl_repp->ecasound_exit_cond_repp); pthread_mutex_unlock(&impl_repp->ecasound_exit_mutex_repp); } /** * Processing is start If and only if processing * previously stopped with conditional_stop(). * * context: E-level-3 */ void ECA_ENGINE::conditional_start(void) { if (was_running_rep == true) { // don't call request_start(), as it would signal that we are // starting from completely halted state if (is_prepared() != true) prepare_operation(); start_operation(); } } /** * Processing is stopped. * * context: E-level-3 * * @see conditional_stop() * @see request_stop() */ void ECA_ENGINE::conditional_stop(void) { if (status() == ECA_ENGINE::engine_status_running) { ECA_LOG_MSG(ECA_LOGGER::system_objects,"conditional stop"); was_running_rep = true; // don't call request_stop(), as it would signal that we are // stopping completely (JACK transport stop will be sent to all) if (is_prepared() == true) stop_operation(); } else was_running_rep = false; } void ECA_ENGINE::start_servers(void) { if (csetup_repp->double_buffering() == true) { csetup_repp->pserver_repp->start(); ECA_LOG_MSG(ECA_LOGGER::user_objects, "prefilling i/o buffers."); csetup_repp->pserver_repp->wait_for_full(); ECA_LOG_MSG(ECA_LOGGER::user_objects, "i/o buffers prefilled."); } if (use_midi_rep == true) { csetup_repp->midi_server_repp->start(); } } void ECA_ENGINE::stop_servers(void) { if (csetup_repp->double_buffering() == true) { csetup_repp->pserver_repp->stop(); csetup_repp->pserver_repp->wait_for_stop(); } if (use_midi_rep == true) { csetup_repp->midi_server_repp->stop(); } } /** * Goes through all input/outputs in 'vec', checks whether they * implement the AUDIO_IO_BARRIER interface, and if yes, * issues either 'start_io()' or 'stop_io()' on them. */ static void priv_toggle_forked_objects(bool start, std::vector* vec) { unsigned int n; for(n = 0; n < vec->size(); n++) { AUDIO_IO_BARRIER *barrier = dynamic_cast((*vec)[n]); if (barrier) { if (start) barrier->start_io(); else barrier->stop_io(); } } } void ECA_ENGINE::start_forked_objects(void) { priv_toggle_forked_objects(true, inputs_repp); priv_toggle_forked_objects(true, outputs_repp); } void ECA_ENGINE::stop_forked_objects(void) { priv_toggle_forked_objects(false, inputs_repp); priv_toggle_forked_objects(false, outputs_repp); } void ECA_ENGINE::state_change_to_finished(void) { if (finished_rep != true) { ECA_LOG_MSG_NOPREFIX(ECA_LOGGER::info, ""); ECA_LOG_MSG(ECA_LOGGER::subsystems, "Engine - Processing finished"); } finished_rep = true; } void ECA_ENGINE::prepare_realtime_objects(void) { /* 1. prepare objects */ for (unsigned int n = 0; n < realtime_objects_rep.size(); n++) { realtime_objects_rep[n]->prepare(); } /* 2. prefill rt output objects with silence */ mixslot_repp->make_silent(); for (unsigned int n = 0; n < realtime_outputs_rep.size(); n++) { if (realtime_outputs_rep[n]->prefill_space() > 0) { if (realtime_outputs_rep[n]->prefill_space() < prefill_threshold_rep * buffersize()) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "audio output '" + realtime_outputs_rep[n]->name() + "' only offers " + kvu_numtostr(realtime_outputs_rep[n]->prefill_space()) + " frames of prefill space. Decreasing amount of prefill."); prefill_threshold_rep = realtime_outputs_rep[n]->prefill_space() / buffersize(); } ECA_LOG_MSG(ECA_LOGGER::user_objects, "prefilling rt-outputs with " + kvu_numtostr(prefill_threshold_rep) + " blocks."); for (int m = 0; m < prefill_threshold_rep; m++) { realtime_outputs_rep[n]->write_buffer(mixslot_repp); } } } } void ECA_ENGINE::start_realtime_objects(void) { /* 1. start all realtime devices */ for (unsigned int n = 0; n < realtime_objects_rep.size(); n++) realtime_objects_rep[n]->start(); } /** * Performs a close-open cycle for all realtime * devices. */ void ECA_ENGINE::reset_realtime_devices(void) { for (size_t n = 0; n < realtime_objects_rep.size(); n++) { if (realtime_objects_rep[n]->is_open() == true) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Reseting rt-object " + realtime_objects_rep[n]->label()); realtime_objects_rep[n]->close(); } } for (size_t n = 0; n < realtime_objects_rep.size(); n++) { realtime_objects_rep[n]->open(); } } /********************************************************************** * Engine implementation - Private functions for observing and * modifying position **********************************************************************/ SAMPLE_SPECS::sample_pos_t ECA_ENGINE::current_position_in_samples(void) const { return csetup_repp->position_in_samples(); } double ECA_ENGINE::current_position_in_seconds_exact(void) const { return csetup_repp->position_in_seconds_exact(); } // FIXME: remove #if 0 double ECA_ENGINE::current_position_chain(void) const { AUDIO_IO* ptr = (*inputs_repp)[(*chains_repp)[csetup_repp->active_chain_index_rep]->connected_input()]; return ptr->position_in_seconds_exact(); return 0.0f; } #endif /** * Seeks to position 'seconds'. Affects all input and * outputs objects, and the chainsetup object position. * * context: E-level-2 */ void ECA_ENGINE::set_position(double seconds) { conditional_stop(); csetup_repp->seek_position_in_seconds(seconds); // FIXME: calling init_engine_state() may lead to races init_engine_state(); conditional_start(); } /** * Seeks to position 'samples'. Affects all input and * outputs objects, and the chainsetup object position. * * context: E-level-2 */ void ECA_ENGINE::set_position_samples(SAMPLE_SPECS::sample_pos_t samples) { conditional_stop(); csetup_repp->seek_position_in_samples(samples); // FIXME: calling init_engine_state() may lead to races init_engine_state(); conditional_start(); } /** * Seeks to position 'samples' without stopping the engine. * Affects all input and outputs objects, and the chainsetup * object position. * * context: E-level-2 */ void ECA_ENGINE::set_position_samples_live(SAMPLE_SPECS::sample_pos_t samples) { csetup_repp->seek_position_in_samples(samples); // FIXME: calling init_engine_state() may lead to races init_engine_state(); } /** * Seeks to position 'current+seconds'. Affects all input and * outputs objects, and the chainsetup object position. * * context: E-level-2 */ void ECA_ENGINE::change_position(double seconds) { double curpos = csetup_repp->position_in_seconds_exact(); conditional_stop(); csetup_repp->seek_position_in_seconds(curpos + seconds); conditional_start(); } /** * Calculates how much data we need to process and sets the * buffersize accordingly for all non-real-time inputs. * * context: J-level-1 */ void ECA_ENGINE::prehandle_control_position(void) { csetup_repp->change_position_in_samples(buffersize()); if (csetup_repp->max_length_set() == true && csetup_repp->is_over_max_length() == true) { int buffer_remain = csetup_repp->position_in_samples() - csetup_repp->max_length_in_samples(); for(unsigned int adev_sizet = 0; adev_sizet < non_realtime_inputs_rep.size(); adev_sizet++) { non_realtime_inputs_rep[adev_sizet]->set_buffersize(buffer_remain); } } } /** * If we've processed all the data that was requested, stop or rewind. * Also resets buffersize to its default value. If finished * state is reached, engine status is set to * 'ECA_ENGINE::engine_status_finished'. */ void ECA_ENGINE::posthandle_control_position(void) { if (csetup_repp->max_length_set() == true && csetup_repp->is_over_max_length() == true) { if (csetup_repp->looping_enabled() == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects,"loop point reached"); inputs_not_finished_rep = 1; csetup_repp->seek_position_in_samples(0); for(unsigned int adev_sizet = 0; adev_sizet < non_realtime_inputs_rep.size(); adev_sizet++) { non_realtime_inputs_rep[adev_sizet]->set_buffersize(buffersize()); } } else { ECA_LOG_MSG(ECA_LOGGER::system_objects,"posthandle_c_p over_max - stop"); if (status() == ECA_ENGINE::engine_status_running || status() == ECA_ENGINE::engine_status_finished) { command(ECA_ENGINE::ep_stop, 0.0f); } state_change_to_finished(); } } } /********************************************************************** * Engine implementation - Private functions for setup and cleanup **********************************************************************/ /** * Called only from class constructor. */ void ECA_ENGINE::init_variables(void) { use_midi_rep = false; batchmode_enabled_rep = false; driver_local = false; pthread_cond_init(&impl_repp->ecasound_stop_cond_repp, NULL); pthread_mutex_init(&impl_repp->ecasound_stop_mutex_repp, NULL); pthread_cond_init(&impl_repp->ecasound_exit_cond_repp, NULL); pthread_mutex_init(&impl_repp->ecasound_exit_mutex_repp, NULL); } /** * Called only from class constructor. */ void ECA_ENGINE::init_connection_to_chainsetup(void) { inputs_repp = &(csetup_repp->inputs); outputs_repp = &(csetup_repp->outputs); chains_repp = &(csetup_repp->chains); init_engine_state(); init_driver(); init_prefill(); init_servers(); init_chains(); create_cache_object_lists(); update_cache_chain_connections(); update_cache_latency_values(); } /** * Initializes the engine driver object. */ void ECA_ENGINE::init_driver(void) { if (csetup_repp->engine_driver_repp != 0) { driver_repp = csetup_repp->engine_driver_repp; driver_local = false; } else { driver_repp = new ECA_ENGINE_DEFAULT_DRIVER(); driver_local = true; } } /** * Initializes prefill variables. */ void ECA_ENGINE::init_prefill(void) { int channels = (max_channels() > 0 ? max_channels() : 1); prefill_threshold_rep = 0; if (csetup_repp->max_buffers() == true) prefill_threshold_rep = ECA_ENGINE::prefill_threshold_constant / buffersize() / channels; if (prefill_threshold_rep < ECA_ENGINE::prefill_blocks_constant) prefill_threshold_rep = ECA_ENGINE::prefill_blocks_constant; ECA_LOG_MSG(ECA_LOGGER::system_objects, "Prefill loops: " + kvu_numtostr(prefill_threshold_rep) + " (blocksize " + kvu_numtostr(buffersize()) + ")."); } /** * * Called only from init_connection_to_chainsetup(). */ void ECA_ENGINE::init_servers(void) { if (csetup_repp->midi_devices.size() > 0) { use_midi_rep = true; ECA_LOG_MSG(ECA_LOGGER::info, "Initializing MIDI-server."); csetup_repp->midi_server_repp->init(); } } /** * Called only from init_connection_to_chainsetup(). */ void ECA_ENGINE::init_chains(void) { mixslot_repp->number_of_channels(max_channels()); mixslot_repp->event_tag_set(SAMPLE_BUFFER::tag_mixed_content); cslots_rep.resize(chains_repp->size()); for(size_t n = 0; n < cslots_rep.size(); n++) { cslots_rep[n] = new SAMPLE_BUFFER(buffersize(), max_channels()); } for (unsigned int c = 0; c != chains_repp->size(); c++) { int inch = (*inputs_repp)[(*chains_repp)[c]->connected_input()]->channels(); int outch = (*outputs_repp)[(*chains_repp)[c]->connected_output()]->channels(); (*chains_repp)[c]->init(cslots_rep[c], inch, outch); } } /** * Frees all reserved resources. * * @post status() == ECA_ENGINE::engine_status_notready * @post is_valid() != true */ void ECA_ENGINE::cleanup(void) { if (csetup_repp != 0) { csetup_repp->toggle_locked_state(true); vector::iterator q = csetup_repp->chains.begin(); while(q != csetup_repp->chains.end()) { if (*q != 0) { (*q)->disconnect_buffer(); } ++q; } csetup_repp->toggle_locked_state(false); } csetup_repp = 0; // -- DBC_ENSURE(status() == ECA_ENGINE::engine_status_notready); DBC_ENSURE(is_valid() != true); // -- } /** * Updates 'input_chain_count_rep' and * 'output_chain_count_rep'. */ void ECA_ENGINE::update_cache_chain_connections(void) { input_chain_count_rep.resize(inputs_repp->size()); for(unsigned int n = 0; n < inputs_repp->size(); n++) { input_chain_count_rep[n] = csetup_repp->number_of_attached_chains_to_input(csetup_repp->inputs[n]); } output_chain_count_rep.resize(outputs_repp->size()); for(unsigned int n = 0; n < outputs_repp->size(); n++) { output_chain_count_rep[n] = csetup_repp->number_of_attached_chains_to_output(csetup_repp->outputs[n]); } } /** * Update system latency values for multitrack * recording. */ void ECA_ENGINE::update_cache_latency_values(void) { if (csetup_repp->multitrack_mode() == true && csetup_repp->multitrack_mode_offset() == -1) { long int in_latency = -1; for(unsigned int n = 0; n < realtime_inputs_rep.size(); n++) { if (in_latency == -1) { in_latency = realtime_inputs_rep[n]->latency(); } else { if (in_latency != realtime_inputs_rep[n]->latency()) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Latency mismatch between input objects!"); } } ECA_LOG_MSG(ECA_LOGGER::user_objects, "Input latency for '" + realtime_inputs_rep[n]->name() + "' is " + kvu_numtostr(in_latency) + "."); } long int out_latency = -1; for(unsigned int n = 0; n < realtime_outputs_rep.size(); n++) { if (out_latency == -1) { if (realtime_outputs_rep[n]->prefill_space() > 0) { long int max_prefill = prefill_threshold_rep * buffersize(); if (max_prefill > realtime_outputs_rep[n]->prefill_space()) { max_prefill = realtime_outputs_rep[n]->prefill_space(); } out_latency = max_prefill + realtime_outputs_rep[n]->latency(); } else out_latency = realtime_outputs_rep[n]->latency(); } else { if ((realtime_outputs_rep[n]->prefill_space() > 0 && out_latency != (prefill_threshold_rep * buffersize()) + realtime_outputs_rep[n]->latency()) || (realtime_outputs_rep[n]->prefill_space() == 0 && out_latency != realtime_outputs_rep[n]->latency())) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Latency mismatch between output objects!"); } } ECA_LOG_MSG(ECA_LOGGER::user_objects, "Output latency for '" + realtime_outputs_rep[n]->name() + "' is " + kvu_numtostr(out_latency) + "."); } recording_offset_rep = (out_latency > in_latency ? out_latency : in_latency); if (recording_offset_rep % buffersize()) { ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: Recording offset not divisible with chainsetup buffersize."); } ECA_LOG_MSG(ECA_LOGGER::user_objects, "recording offset is " + kvu_numtostr(recording_offset_rep) + " samples."); } else if (csetup_repp->multitrack_mode() == true) { /* multitrack_mode_offset() explicitly given (not -1) */ recording_offset_rep = csetup_repp->multitrack_mode_offset(); } else { recording_offset_rep = 0; } } /** * Assigns input and output objects in lists of realtime * and nonrealtime objects. */ void ECA_ENGINE::create_cache_object_lists(void) { for(unsigned int n = 0; n < inputs_repp->size(); n++) { if (AUDIO_IO_DEVICE::is_realtime_object((*inputs_repp)[n]) == true) { realtime_inputs_rep.push_back(static_cast((*inputs_repp)[n])); realtime_objects_rep.push_back(static_cast((*inputs_repp)[n])); } else { non_realtime_inputs_rep.push_back((*inputs_repp)[n]); non_realtime_objects_rep.push_back((*inputs_repp)[n]); } } DBC_CHECK(static_cast(realtime_inputs_rep.size()) == csetup_repp->number_of_realtime_inputs()); for(unsigned int n = 0; n < outputs_repp->size(); n++) { if (AUDIO_IO_DEVICE::is_realtime_object((*outputs_repp)[n]) == true) { realtime_outputs_rep.push_back(static_cast((*outputs_repp)[n])); realtime_objects_rep.push_back(static_cast((*outputs_repp)[n])); } else { non_realtime_outputs_rep.push_back((*outputs_repp)[n]); non_realtime_objects_rep.push_back((*outputs_repp)[n]); } } DBC_CHECK(static_cast(realtime_outputs_rep.size()) == csetup_repp->number_of_realtime_outputs()); } /** * Called only from class constructor. */ void ECA_ENGINE::init_profiling(void) { impl_repp->looptimer_low_rep = static_cast(buffersize()) / csetup_repp->samples_per_second(); impl_repp->looptimer_mid_rep = static_cast(buffersize() * 2) / csetup_repp->samples_per_second(); impl_repp->looptimer_high_rep = static_cast(buffersize()) * prefill_threshold_rep / csetup_repp->samples_per_second(); impl_repp->looptimer_rep.set_lower_bound_seconds(impl_repp->looptimer_low_rep); impl_repp->looptimer_rep.set_upper_bound_seconds(impl_repp->looptimer_high_rep); impl_repp->looptimer_range_rep.set_lower_bound_seconds(impl_repp->looptimer_mid_rep); impl_repp->looptimer_range_rep.set_upper_bound_seconds(impl_repp->looptimer_mid_rep); } /** * Prints profiling information to stderr. */ void ECA_ENGINE::dump_profile_info(void) { long int slower_than_rt = impl_repp->looptimer_rep.event_count() - impl_repp->looptimer_rep.events_under_lower_bound() - impl_repp->looptimer_rep.events_over_upper_bound(); cerr << "*** profile begin ***" << endl; cerr << "Loops faster than realtime: " << kvu_numtostr(impl_repp->looptimer_rep.events_under_lower_bound()); cerr << " (<" << kvu_numtostr(impl_repp->looptimer_low_rep * 1000, 1) << " msec)" << endl; cerr << "Loops slower than realtime: " << kvu_numtostr(slower_than_rt); cerr << " (>=" << kvu_numtostr(impl_repp->looptimer_low_rep * 1000, 1) << " msec)" << endl; cerr << "Loops slower than realtime: " << kvu_numtostr(impl_repp->looptimer_range_rep.events_over_upper_bound()); cerr << " (>" << kvu_numtostr(impl_repp->looptimer_mid_rep * 1000, 1) << " msec)" << endl; cerr << "Loops exceeding all buffering: " << kvu_numtostr(impl_repp->looptimer_rep.events_over_upper_bound()); cerr << " (>" << kvu_numtostr(impl_repp->looptimer_high_rep * 1000, 1) << " msec)" << endl; cerr << "Total loops: " << kvu_numtostr(impl_repp->looptimer_rep.event_count()) << endl; cerr << "Fastest/slowest/average loop time: "; cerr << kvu_numtostr(impl_repp->looptimer_rep.min_duration_seconds() * 1000, 1); cerr << "/"; cerr << kvu_numtostr(impl_repp->looptimer_rep.max_duration_seconds() * 1000, 1); cerr << "/"; cerr << kvu_numtostr(impl_repp->looptimer_rep.average_duration_seconds() * 1000, 1); cerr << " msec." << endl; cerr << "*** profile end ***" << endl; } /********************************************************************** * Engine implementation - Private functions for signal routing **********************************************************************/ /** * Reads audio data from input objects. * * context: J-level-1 (see */ void ECA_ENGINE::inputs_to_chains(void) { /** * - go through all inputs * - depending on connectivity, read either to a mixdown slot, or * directly to a per-chain slot */ for(size_t inputnum = 0; inputnum < inputs_repp->size(); inputnum++) { if (input_chain_count_rep[inputnum] > 1) { /* case-1a: read buffer from input 'inputnum' to 'mixslot'; * later (1b) the data is copied to each per-chain slow * to which input is connected to */ mixslot_repp->length_in_samples(buffersize()); if ((*inputs_repp)[inputnum]->finished() != true) { (*inputs_repp)[inputnum]->read_buffer(mixslot_repp); if ((*inputs_repp)[inputnum]->finished() != true) { inputs_not_finished_rep++; } } else { /* note: no more input data for this change (N:1 input-chain case) */ mixslot_repp->make_empty(); } } for (size_t c = 0; c != chains_repp->size(); c++) { if ((*chains_repp)[c]->connected_input() == static_cast(inputnum)) { if (input_chain_count_rep[inputnum] == 1) { /* case-2: read buffer from input 'inputnum' to chain 'c' */ cslots_rep[c]->length_in_samples(buffersize()); if ((*inputs_repp)[inputnum]->finished() != true) { (*inputs_repp)[inputnum]->read_buffer(cslots_rep[c]); if ((*inputs_repp)[inputnum]->finished() != true) { inputs_not_finished_rep++; } } else { /* note: no more input data for this change (1:1 input-chain case) */ cslots_rep[c]->make_empty(); } /* note: input connected to only one chain, so no need to iterate through the other chains */ break; } else { /* case-1b: input connected to chain 'n', copy 'mixslot' to * the matching per-chain slot */ cslots_rep[c]->copy_all_content(*mixslot_repp); } } } } } /** * context: J-level-1 */ void ECA_ENGINE::process_chains(void) { vector::const_iterator p = chains_repp->begin(); while(p != chains_repp->end()) { (*p)->process(); ++p; } } void mix_to_outputs_divide_helper(const SAMPLE_BUFFER *from, SAMPLE_BUFFER *to, int divide_by, bool first_time) { if (first_time == true) { // this is the first output connected to this chain if (from->number_of_channels() < to->number_of_channels()) { to->make_silent(); } to->copy_matching_channels(*from); to->divide_by(divide_by); } else { to->add_with_weight(*from, divide_by); } } void mix_to_outputs_sum_helper(const SAMPLE_BUFFER *from, SAMPLE_BUFFER *to, bool first_time) { if (first_time == true) { // this is the first output connected to this chain if (from->number_of_channels() < to->number_of_channels()) { to->make_silent(); } to->copy_matching_channels(*from); } else { to->add_matching_channels(*from); } } /** * context: J-level-1 */ void ECA_ENGINE::mix_to_outputs(bool skip_realtime_target_outputs) { for(size_t outputnum = 0; outputnum < outputs_repp->size(); outputnum++) { if (skip_realtime_target_outputs == true) { if (csetup_repp->is_realtime_target_output(outputnum) == true) { ECA_LOG_MSG(ECA_LOGGER::system_objects, "Skipping rt-target output " + (*outputs_repp)[outputnum]->label() + "."); continue; } } int count = 0; /* FIXME: number_of_channels() may end up allocating memory! */ mixslot_repp->number_of_channels((*outputs_repp)[outputnum]->channels()); for(size_t n = 0; n != chains_repp->size(); n++) { // -- // if chain is already released, skip // -- if ((*chains_repp)[n]->connected_output() == -1) { // -- // skip, if chain is not connected // -- continue; } if ((*chains_repp)[n]->connected_output() == static_cast(outputnum)) { // -- // output is connected to this chain // -- if (output_chain_count_rep[outputnum] == 1) { // -- // there's only one output connected to this chain, // so we don't need to mix anything // -- (*outputs_repp)[outputnum]->write_buffer(cslots_rep[n]); if ((*outputs_repp)[outputnum]->finished() == true) /* note: loop devices always connected both as inputs as * outputs, so their finished status must not be * counted as an error (like for other output types) */ if (dynamic_cast((*outputs_repp)[outputnum]) == 0) outputs_finished_rep++; break; } else { ++count; if (csetup_repp->mix_mode() == ECA_CHAINSETUP::cs_mmode_avg) mix_to_outputs_divide_helper(cslots_rep[n], mixslot_repp, output_chain_count_rep[outputnum], (count == 1)); else mix_to_outputs_sum_helper(cslots_rep[n], mixslot_repp, (count == 1)); mixslot_repp->event_tags_add(*cslots_rep[n]); if (count == output_chain_count_rep[outputnum]) { (*outputs_repp)[outputnum]->write_buffer(mixslot_repp); if ((*outputs_repp)[outputnum]->finished() == true) /* note: loop devices always connected both as inputs as * outputs, so their finished status must not be * counted as an error (like for other output types) */ if (dynamic_cast((*outputs_repp)[outputnum]) == 0) outputs_finished_rep++; } } } } } } /********************************************************************** * Engine implementation - Private functions for toggling features **********************************************************************/ /** * context: E-level-2 */ void ECA_ENGINE::chain_muting(void) { if ((*chains_repp)[csetup_repp->selected_chain_index_rep]->is_muted()) (*chains_repp)[csetup_repp->selected_chain_index_rep]->toggle_muting(false); else (*chains_repp)[csetup_repp->selected_chain_index_rep]->toggle_muting(true); } /** * context: E-level-2 */ void ECA_ENGINE::chain_processing(void) { if ((*chains_repp)[csetup_repp->selected_chain_index_rep]->is_processing()) (*chains_repp)[csetup_repp->selected_chain_index_rep]->toggle_processing(false); else (*chains_repp)[csetup_repp->selected_chain_index_rep]->toggle_processing(true); } /********************************************************************** * Engine implementation - Obsolete functions **********************************************************************/