sintonia/library/ecasound-2.7.2/libecasound/audioio-seqbase.cpp

517 lines
16 KiB
C++

// ------------------------------------------------------------------------
// audioio-seqbase.cpp: Base class for audio sequencer objects
// Copyright (C) 1999,2002,2005,2008,2009,2010 Kai Vehmanen
//
// 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 <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <cmath>
#include <kvu_message_item.h>
#include <kvu_numtostr.h>
#include <kvu_dbc.h>
#include "eca-object-factory.h"
#include "samplebuffer.h"
#include "audioio-seqbase.h"
#include "eca-error.h"
#include "eca-logger.h"
using std::cout;
using std::endl;
using SAMPLE_SPECS::sample_pos_t;
/**
* FIXME notes (last update 2008-03-04)
*
* - Add check for supports_sample_accurate_seek(). This could
* be used to warn users if EWF source file cannot provide
* accurate enough seeking (which will lead to unexpected
* results).
* - Remove write-support altogether (changes supported io_modes(),
* modify write_buffer(), and remove child_write_started.
* - Should extend the object length when looping is enabled.
*/
/**
* Returns the child position in samples for the given
* public position.
*/
sample_pos_t AUDIO_SEQUENCER_BASE::priv_public_to_child_pos(sample_pos_t pubpos) const
{
if (pubpos <= child_offset_rep.samples())
return child_start_pos_rep.samples();
if (child_looping_rep == true) {
DBC_CHECK(child()->finite_length_stream() == true);
sample_pos_t one_loop =
child_length_rep.samples() -
child_start_pos_rep.samples();
sample_pos_t res =
pubpos - child_offset_rep.samples();
DBC_CHECK(res > 0);
if (one_loop > 0)
res = res % one_loop;
return res + child_start_pos_rep.samples();
}
else {
/* not looping */
return pubpos - child_offset_rep.samples() + child_start_pos_rep.samples();
}
}
AUDIO_SEQUENCER_BASE::AUDIO_SEQUENCER_BASE (void)
: child_looping_rep(false),
child_length_set_by_client_rep(false),
buffersize_rep(-1),
child_write_started(false),
init_rep(false)
{
}
AUDIO_SEQUENCER_BASE::~AUDIO_SEQUENCER_BASE(void)
{
}
AUDIO_SEQUENCER_BASE* AUDIO_SEQUENCER_BASE::clone(void) const
{
AUDIO_SEQUENCER_BASE* target = new AUDIO_SEQUENCER_BASE();
for(int n = 0; n < number_of_params(); n++) {
target->set_parameter(n + 1, get_parameter(n + 1));
}
return target;
}
void AUDIO_SEQUENCER_BASE::change_child_name(const string& child_name) throw(AUDIO_IO::SETUP_ERROR &)
{
AUDIO_IO* tmp = 0;
if (child_name.size() > 0)
tmp = ECA_OBJECT_FACTORY::create_audio_object(child_name);
if (tmp == 0)
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Could not create child object."));
ECA_LOG_MSG(ECA_LOGGER::user_objects, "Creating audio sequencer file:" + tmp->label() + ".");
set_child(tmp);
init_rep = true;
}
void AUDIO_SEQUENCER_BASE::open(void) throw(AUDIO_IO::SETUP_ERROR &)
{
if (init_rep != true)
change_child_name(child_object_str_rep);
pre_child_open();
child()->open();
if (child()->finite_length_stream() != true &&
child_looping_rep) {
child()->close();
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Unable to loop an object with infinite length."));
}
if (child()->supports_seeking_sample_accurate() != true &&
child_start_pos_rep.samples() > 0) {
child()->close();
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Unable use sections of an object that does not support seeking."));
}
/* step: set srate for audio time variable */
child_offset_rep.set_samples_per_second_keeptime(child()->samples_per_second());
child_start_pos_rep.set_samples_per_second_keeptime(child()->samples_per_second());
child_length_rep.set_samples_per_second_keeptime(child()->samples_per_second());
post_child_open();
/* step: unless overridden by the client, store the length
* of child object */
ECA_AUDIO_TIME len_tmp (child()->length_in_samples(),
child()->samples_per_second());
if (child_length_set_by_client_rep != true)
set_child_length_private(len_tmp);
else if (len_tmp.valid() == true &&
child_length_rep.valid() == true) {
if (len_tmp.seconds() <
child_length_rep.seconds())
ECA_LOG_MSG(ECA_LOGGER::info,
"WARNING: object \"" + child()->label() +
"\" is too short, reducing segment length to '"
+ kvu_numtostr(len_tmp.seconds()) + "'sec.");
}
/* step: set public length of the EWF object;
* child length is infinite, we will extend our object
* length in read_buffer(), see also
* AUDIO_SEQUENCER_BASE::finite_length_stream() */
if (child_looping_rep != true)
set_length_in_samples(child_offset_rep.samples() + child_length_rep.samples());
//dump_child_debug("open");
tmp_buffer.number_of_channels(child()->channels());
tmp_buffer.length_in_samples(child()->buffersize());
AUDIO_IO_PROXY::open();
}
void AUDIO_SEQUENCER_BASE::close(void)
{
if (child()->is_open() == true)
child()->close();
AUDIO_IO_PROXY::close();
}
void AUDIO_SEQUENCER_BASE::read_buffer(SAMPLE_BUFFER* sbuf)
{
/**
* implementation notes:
*
* position: the current global position (note, this
* can exceed child_offset+child_length when
* looping is used.
* child_offset: global position when child is activated
* child_start_position: position inside the child-object where
* input is started (data between child
* beginning and child_start_pos is not used)
* child_length: amount of child data between start and end
* positions (end can in middle of stream or
* at end-of-file position)
* child_looping: when child end is reached, whether to jump
* back to start position?
*
* note! all cases (if-else blocks) end to setting a new
* position_in_samples value
*/
//dump_child_debug("read_buffer");
if (position_in_samples() + buffersize() <= child_offset_rep.samples()) {
// ---
// case 1: child not active yet
// ---
/* ensure that channel count matches that of child */
sbuf->number_of_channels(child()->channels());
sbuf->length_in_samples(buffersize());
sbuf->make_silent();
change_position_in_samples(buffersize());
//dump_child_debug("case1-out");
}
else if (position_in_samples() < child_offset_rep.samples()) {
// ---
// case 2: next read will bring in the first child samples
// ---
DBC_CHECK(position_in_samples() + buffersize() > child_offset_rep.samples());
/* make sure child position is correct at start */
sample_pos_t chipos =
priv_public_to_child_pos(position_in_samples());
if (chipos != child()->position_in_samples())
child()->seek_position_in_samples(chipos);
sample_pos_t segment =
position_in_samples()
+ buffersize()
- child_offset_rep.samples();
if (segment > buffersize())
segment = buffersize();
// dump_child_debug("case2-in");
ECA_LOG_MSG(ECA_LOGGER::user_objects,
"child-object \"" + child()->label() + "\" activated.");
/* step: read segment of audio and copy it to the correct
* location */
long int save_bsize = buffersize();
DBC_CHECK(save_bsize == buffersize());
child()->set_buffersize(segment);
child()->read_buffer(&tmp_buffer);
/* ensure that channel count matches that of child */
sbuf->number_of_channels(child()->channels());
sbuf->length_in_samples(buffersize());
sbuf->copy_range(tmp_buffer,
0,
tmp_buffer.length_in_samples(),
buffersize() - segment);
child()->set_buffersize(save_bsize);
change_position_in_samples(buffersize());
//dump_child_debug("case2-out");
}
else {
// ---
// case 3: child is active
// ---
sample_pos_t chipos1 =
priv_public_to_child_pos(position_in_samples());
sample_pos_t chipos2 =
priv_public_to_child_pos(position_in_samples() + buffersize());
if (chipos2 >=
(child_length_rep.samples() + child_start_pos_rep.samples()) &&
child_looping_rep != true &&
child()->finite_length_stream() == true) {
// ---
// case 3a: not looping, reaching child file end during the next read
// ---
/* note: max samples to included (assuming child object does
* not reach end-of-stream */
sample_pos_t samples_to_include =
(child_length_rep.samples() + child_start_pos_rep.samples())
- chipos1;
//dump_child_debug("case3a-in");
child()->set_buffersize(buffersize());
child()->read_buffer(sbuf);
DBC_CHECK((child()->finished() == true &&
buffersize() > sbuf->length_in_samples()) ||
child()->finished() != true);
/* resize the sbuf if needed: either EOF was encountered
* and sbuf is shorter than buffersize() or otherwise we
* need to drop some of the samples read so at to not
* go beyond set length */
if (sbuf->length_in_samples() >
samples_to_include) {
sbuf->length_in_samples(samples_to_include);
}
change_position_in_samples(sbuf->length_in_samples());
//dump_child_debug("case3a-out");
}
else if (chipos2 < chipos1 &&
child_looping_rep == true) {
// ---
// case 3b: looping, we will run out of data during read
// ---
//dump_child_debug("case3b-in");
child()->set_buffersize(buffersize());
child()->read_buffer(sbuf);
sample_pos_t over_child_eof = chipos2 - child_start_pos_rep.samples();
/* step: copy segment 1 from loop end, and segment 2 from
* loop start point */
sample_pos_t chistartpos =
priv_public_to_child_pos(position_in_samples() +
buffersize() - over_child_eof);
DBC_CHECK(chistartpos == child_start_pos_rep.samples());
child()->seek_position_in_samples(chistartpos);
if (over_child_eof > 0) {
long int save_bsize = buffersize();
DBC_CHECK(save_bsize == buffersize());
child()->set_buffersize(over_child_eof);
child()->read_buffer(&tmp_buffer);
DBC_CHECK(tmp_buffer.length_in_samples() == over_child_eof);
DBC_CHECK((buffersize() - over_child_eof) < buffersize());
sbuf->length_in_samples(buffersize());
sbuf->number_of_channels(channels());
sbuf->copy_range(tmp_buffer,
0,
tmp_buffer.length_in_samples(),
buffersize() - over_child_eof);
child()->set_buffersize(save_bsize);
}
change_position_in_samples(buffersize());
//dump_child_debug("case3b-out");
}
else {
// ---
// case 3c: normal case, read samples from child
// ---
child()->set_buffersize(buffersize());
child()->read_buffer(sbuf);
/* note: if the 'length' parameter value is longer than
* actual child object length, less than buffersize()
* samples will be read */
change_position_in_samples(sbuf->length_in_samples());
//dump_child_debug("case3c-out");
}
// dump_child_debug("case3-e");
}
/* note: as we are looping indefinitely, so our length must
* be continuously updated (see child_lengt() implementation) */
if (child_looping_rep == true ||
(child_length_set_by_client_rep != true &&
child()->finite_length_stream() != true))
extend_position();
DBC_ENSURE(channels() == child()->channels());
DBC_ENSURE(sbuf->number_of_channels() == channels());
DBC_ENSURE(sbuf->length_in_samples() <= buffersize());
}
void AUDIO_SEQUENCER_BASE::dump_child_debug(const char *tag)
{
sample_pos_t chipos1 =
priv_public_to_child_pos(position_in_samples());
cout << "TAG:" << tag << endl;
cout << "global position (in samples): " << position_in_samples() << endl;
cout << "child-pos: " << child()->position_in_samples() << endl;
cout << "child-derived-pos: " << chipos1 << endl;
cout << "child-offset: " << child_offset_rep.samples() << endl;
cout << "child-startpos: " << child_start_pos_rep.samples() << endl;
cout << "child-length: " << child_length_rep.samples() << endl;
}
void AUDIO_SEQUENCER_BASE::write_buffer(SAMPLE_BUFFER* sbuf)
{
if (child_write_started != true) {
child_write_started = true;
child_offset_rep.set_samples(position_in_samples());
MESSAGE_ITEM m;
m << "found child_offset_rep " << child_offset_rep.seconds() << ".";
ECA_LOG_MSG(ECA_LOGGER::user_objects, m.to_string());
}
child()->write_buffer(sbuf);
change_position_in_samples(sbuf->length_in_samples());
extend_position();
}
SAMPLE_SPECS::sample_pos_t AUDIO_SEQUENCER_BASE::seek_position(SAMPLE_SPECS::sample_pos_t pos)
{
/* in write mode, seek can be only performed once
* the initial write has been performed to the child */
if (is_open() == true &&
(io_mode() == AUDIO_IO::io_read ||
(io_mode() != AUDIO_IO::io_read &&
child_write_started == true))) {
sample_pos_t chipos =
priv_public_to_child_pos(pos);
child()->seek_position_in_samples(chipos);
}
return pos;
}
void AUDIO_SEQUENCER_BASE::set_child_object_string(const std::string& v)
{
child_object_str_rep = v;
change_child_name(child_object_str_rep);
}
void AUDIO_SEQUENCER_BASE::set_child_offset(const ECA_AUDIO_TIME& v)
{
child_offset_rep = v;
}
void AUDIO_SEQUENCER_BASE::set_child_start_position(const ECA_AUDIO_TIME& v)
{
if (is_open() == true &&
child()->supports_seeking_sample_accurate() != true) {
ECA_LOG_MSG(ECA_LOGGER::errors,
"ERROR: object \"" + child()->label() +
"\" does not support sample accurate seeking, unable to set start position.");
return;
}
child_start_pos_rep = v;
}
void AUDIO_SEQUENCER_BASE::set_child_length_private(const ECA_AUDIO_TIME& v)
{
child_length_rep = v;
}
void AUDIO_SEQUENCER_BASE::set_child_length(const ECA_AUDIO_TIME& v)
{
set_child_length_private(v);
child_length_set_by_client_rep = true;
}
ECA_AUDIO_TIME AUDIO_SEQUENCER_BASE::child_length(void) const
{
/* case: infinite child length */
if (child_length_set_by_client_rep != true &&
child()->finite_length_stream() != true) {
ECA_AUDIO_TIME tmp;
tmp.mark_as_invalid();
return tmp;
}
return child_length_rep;
}
bool AUDIO_SEQUENCER_BASE::finite_length_stream(void) const
{
if (child_looping_rep == true)
return false;
return child()->finite_length_stream();
}
bool AUDIO_SEQUENCER_BASE::finished(void) const
{
/**
* File is finished if...
* 1) the child object is out of data (implies that looping
* is disabled), or
* 2) file is open in read mode, looping is disabled, child is
* a finite length stream and its position has gone beyond
* the requested child_length
*/
if (child()->finished()) {
ECA_LOG_MSG(ECA_LOGGER::user_objects,
"Child object " + child()->label() + " finished.");
return true;
}
if (io_mode() != AUDIO_IO::io_read) {
return false;
}
if (child_looping_rep != true &&
child()->finite_length_stream() == true &&
priv_public_to_child_pos(position_in_samples())
>= child_length_rep.samples() + child_start_pos_rep.samples()) {
ECA_LOG_MSG(ECA_LOGGER::user_objects,
"Finite length child object " + child()->label() +
" finished. All samples from the requested range have been read.");
return true;
}
return false;
}