2581 lines
70 KiB
C++
2581 lines
70 KiB
C++
// ------------------------------------------------------------------------
|
|
// 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 <config.h>
|
|
#endif
|
|
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <algorithm> /* find() */
|
|
#include <fstream>
|
|
#include <vector>
|
|
#include <list>
|
|
#include <iostream>
|
|
|
|
#include <sys/types.h> /* POSIX: getpid() */
|
|
#include <unistd.h> /* POSIX: getpid() */
|
|
#ifdef HAVE_SYS_MMAN_H
|
|
#include <sys/mman.h> /* POSIX: for mlockall() */
|
|
#endif
|
|
|
|
#ifdef ECA_COMPILE_JACK
|
|
#include <jack/jack.h>
|
|
#endif
|
|
|
|
#include <kvu_dbc.h>
|
|
#include <kvu_message_item.h>
|
|
#include <kvu_numtostr.h>
|
|
#include <kvu_rtcaps.h>
|
|
#include <kvu_utils.h>
|
|
|
|
#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<AUDIO_IO*>* 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<string>& 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<string> 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<string> 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<CHAIN*>::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<AUDIO_IO*>::iterator q = inputs.begin(); q != inputs.end(); q++) {
|
|
if (dynamic_cast<AUDIO_IO_DB_CLIENT*>(*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<AUDIO_IO*>::iterator q = inputs_direct_rep.begin(); q != inputs_direct_rep.end(); q++) {
|
|
if (dynamic_cast<LOOP_DEVICE*>(*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<AUDIO_IO*>::iterator q = outputs.begin(); q != outputs.end(); q++) {
|
|
if (dynamic_cast<AUDIO_IO_DB_CLIENT*>(*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<AUDIO_IO*>::iterator q = outputs_direct_rep.begin(); q != outputs_direct_rep.end(); q++) {
|
|
// trouble with dynamic_cast with libecasoundc apps like ecalength?
|
|
if (dynamic_cast<LOOP_DEVICE*>(*q) == 0) {
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "Deleting audio object \"" + (*q)->label() + "\".");
|
|
delete *q;
|
|
*q = 0;
|
|
}
|
|
}
|
|
|
|
/* delete loop objects */
|
|
for(map<string,LOOP_DEVICE*>::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<AUDIO_IO_MANAGER*>::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<int> conn_inputs, conn_outputs;
|
|
|
|
for(vector<CHAIN*>::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<int>(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<int>(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<AUDIO_IO*>* objs,
|
|
const vector<AUDIO_IO*>& directobjs)
|
|
{
|
|
// --
|
|
DBC_CHECK(objs->size() == directobjs.size());
|
|
// --
|
|
|
|
for(size_t n = 0; n < objs->size(); n++) {
|
|
AUDIO_IO_DB_CLIENT* pobj = dynamic_cast<AUDIO_IO_DB_CLIENT*>((*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<AUDIO_IO*>* objs,
|
|
const vector<AUDIO_IO*>& 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<string>& newchains)
|
|
{
|
|
// --------
|
|
DBC_REQUIRE(is_enabled() != true);
|
|
// --------
|
|
|
|
for(vector<string>::const_iterator p = newchains.begin(); p != newchains.end(); p++) {
|
|
bool exists = false;
|
|
for(vector<CHAIN*>::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<string>::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) {
|
|
vector<CHAIN*>::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<string>::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) {
|
|
for(vector<CHAIN*>::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<string>::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) {
|
|
for(vector<CHAIN*>::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<CHAIN*>::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<string>& schains = selected_chains();
|
|
vector<string>::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<string>::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) {
|
|
for(vector<CHAIN*>::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<string>::const_iterator a = selected_chainids.begin(); a != selected_chainids.end(); a++) {
|
|
for(vector<CHAIN*>::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<string> ECA_CHAINSETUP::chain_names(void) const
|
|
{
|
|
vector<string> result;
|
|
vector<CHAIN*>::const_iterator p = chains.begin();
|
|
while(p != chains.end()) {
|
|
result.push_back((*p)->name());
|
|
++p;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
vector<string> ECA_CHAINSETUP::audio_input_names(void) const
|
|
{
|
|
vector<string> result;
|
|
vector<AUDIO_IO*>::const_iterator p = inputs.begin();
|
|
while(p != inputs.end()) {
|
|
result.push_back((*p)->label());
|
|
++p;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
vector<string> ECA_CHAINSETUP::audio_output_names(void) const
|
|
{
|
|
vector<string> result;
|
|
vector<AUDIO_IO*>::const_iterator p = outputs.begin();
|
|
while(p != outputs.end()) {
|
|
result.push_back((*p)->label());
|
|
++p;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
vector<string> ECA_CHAINSETUP::get_attached_chains_to_input(AUDIO_IO* aiod) const
|
|
{
|
|
vector<string> res;
|
|
|
|
vector<CHAIN*>::const_iterator q = chains.begin();
|
|
while(q != chains.end()) {
|
|
if (aiod == inputs[(*q)->connected_input()]) {
|
|
res.push_back((*q)->name());
|
|
}
|
|
++q;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
vector<string> ECA_CHAINSETUP::get_attached_chains_to_output(AUDIO_IO* aiod) const
|
|
{
|
|
vector<string> res;
|
|
|
|
vector<CHAIN*>::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<CHAIN*>::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<CHAIN*>::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<CHAIN*>::const_iterator q = chains.begin();
|
|
while(q != chains.end()) {
|
|
if ((*q)->connected_output() == output_id) {
|
|
output_found = true;
|
|
AUDIO_IO_DEVICE* p = dynamic_cast<AUDIO_IO_DEVICE*>(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<string> ECA_CHAINSETUP::get_attached_chains_to_iodev(const string& filename) const
|
|
{
|
|
vector<AUDIO_IO*>::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<string> (0);
|
|
}
|
|
|
|
/**
|
|
* Returns number of realtime audio input objects.
|
|
*/
|
|
int ECA_CHAINSETUP::number_of_chain_operators(void) const
|
|
{
|
|
int cops = 0;
|
|
vector<CHAIN*>::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<int>(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<AUDIO_IO_DEVICE*>(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<AUDIO_IO_DEVICE*>(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<AUDIO_IO_MANAGER*>::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<AUDIO_IO_MANAGER*>::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<ECA_ENGINE_DRIVER*>(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<AUDIO_IO_MANAGER*>::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<AUDIO_IO_DEVICE*>(aio);
|
|
LOOP_DEVICE* q = dynamic_cast<LOOP_DEVICE*>(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<AUDIO_IO_DB_CLIENT*>(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<string,LOOP_DEVICE*>::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<AUDIO_IO*>* vec, const AUDIO_IO* obj)
|
|
{
|
|
vector<AUDIO_IO*>::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<AUDIO_IO*> *objs = (dir == cs_dir_input ? &inputs : &outputs);
|
|
vector<AUDIO_IO*> *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<int>(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<CHAIN*>::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<LOOP_DEVICE*>(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<MIDI_IO*>::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<CHAIN*>::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<CHAIN*>::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<AUDIO_IO*>::size_type c = 0;
|
|
|
|
while (c < inputs.size()) {
|
|
if (inputs[c] == obj) {
|
|
for(vector<CHAIN*>::iterator q = chains.begin(); q != chains.end(); q++) {
|
|
if ((*q)->connected_input() == static_cast<int>(c)) {
|
|
(*q)->disconnect_input();
|
|
}
|
|
}
|
|
temp += "Assigning file to chains:";
|
|
for(vector<string>::const_iterator p = selected_chainids.begin(); p!= selected_chainids.end(); p++) {
|
|
for(vector<CHAIN*>::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<AUDIO_IO*>::size_type c = 0;
|
|
while (c < outputs.size()) {
|
|
if (outputs[c] == obj) {
|
|
for(vector<CHAIN*>::iterator q = chains.begin(); q != chains.end(); q++) {
|
|
if ((*q)->connected_output() == static_cast<int>(c)) {
|
|
(*q)->disconnect_output();
|
|
}
|
|
}
|
|
temp += "Assigning file to chains:";
|
|
for(vector<string>::const_iterator p = selected_chainids.begin(); p!= selected_chainids.end(); p++) {
|
|
for(vector<CHAIN*>::iterator q = chains.begin(); q != chains.end(); q++) {
|
|
if (*p == (*q)->name()) {
|
|
(*q)->connect_output(static_cast<int>(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<AUDIO_IO*>& 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<AUDIO_IO_DEVICE*>(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<AUDIO_IO*>::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<AUDIO_IO*>::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<AUDIO_IO*>::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<MIDI_IO*>::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<AUDIO_IO*>::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<AUDIO_IO*>::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<MIDI_IO*>::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<int>(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<int>(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<int>(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<int>(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<long int>(jack_get_sample_rate(client);
|
|
result = static_cast<long int>(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<AUDIO_IO*>::iterator q = inputs.begin(); q != inputs.end(); q++) {
|
|
(*q)->set_samples_per_second(new_value);
|
|
}
|
|
|
|
for(vector<AUDIO_IO*>::iterator q = outputs.begin(); q != outputs.end(); q++) {
|
|
(*q)->set_samples_per_second(new_value);
|
|
}
|
|
|
|
for(vector<CHAIN*>::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<AUDIO_IO*>* objs, SAMPLE_SPECS::sample_pos_t pos, const std::string& tag)
|
|
{
|
|
for(vector<AUDIO_IO*>::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<AUDIO_IO_DEVICE*>(*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<CHAIN*>::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<string>& 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<string> schains = selected_chains();
|
|
for(vector<string>::const_iterator a = schains.begin(); a != schains.end(); a++) {
|
|
for(vector<CHAIN*>::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<AUDIO_STAMP_CLIENT*>(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<string> schains = selected_chains();
|
|
for(vector<string>::const_iterator a = schains.begin(); a != schains.end(); a++) {
|
|
for(vector<CHAIN*>::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<AUDIO_STAMP*>(cotmp);
|
|
if (p != 0) {
|
|
impl_repp->stamp_server_rep.register_stamp(p);
|
|
}
|
|
#endif
|
|
|
|
vector<string> schains = selected_chains();
|
|
for(vector<string>::const_iterator p = schains.begin(); p != schains.end(); p++) {
|
|
for(vector<CHAIN*>::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<string>& 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<string> 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<string> 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);
|
|
}
|
|
}
|