428 lines
12 KiB
C++
428 lines
12 KiB
C++
// ------------------------------------------------------------------------
|
||
// audioio.cpp: Routines common for all audio IO-devices.
|
||
// Copyright (C) 1999-2004,2008,2009 Kai Vehmanen
|
||
//
|
||
// Attributes:
|
||
// eca-style-version: 3
|
||
//
|
||
// This program is free software; you can redistribute it and/or modify
|
||
// it under the terms of the GNU General Public License as published by
|
||
// the Free Software Foundation; either version 2 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// This program is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU General Public License
|
||
// along with this program; if not, write to the Free Software
|
||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
// ------------------------------------------------------------------------
|
||
|
||
#include <cmath>
|
||
#include <string>
|
||
|
||
#include <kvu_dbc.h>
|
||
#include <kvu_message_item.h>
|
||
#include <kvu_numtostr.h>
|
||
|
||
#include "eca-error.h"
|
||
#include "audioio.h"
|
||
#include "eca-logger.h"
|
||
|
||
/**
|
||
* FIXME notes (last update 2008-03-11)
|
||
*
|
||
* - Modify default implementation of set_label()/label() so
|
||
* that is mapped directly to set/get_parameter(1, ...).
|
||
*/
|
||
|
||
|
||
const string& AUDIO_IO::SETUP_ERROR::message(void) const { return message_rep; }
|
||
AUDIO_IO::SETUP_ERROR::Error_type AUDIO_IO::SETUP_ERROR::type(void) const { return type_rep; }
|
||
AUDIO_IO::SETUP_ERROR::SETUP_ERROR(AUDIO_IO::SETUP_ERROR::Error_type type,
|
||
const string& message)
|
||
: type_rep(type), message_rep(message) { }
|
||
|
||
|
||
// ===================================================================
|
||
// Constructors and destructors
|
||
|
||
AUDIO_IO::~AUDIO_IO(void)
|
||
{
|
||
if (is_open() == true)
|
||
close();
|
||
}
|
||
|
||
AUDIO_IO::AUDIO_IO(const string& name,
|
||
int mode)
|
||
{
|
||
set_label(name);
|
||
set_io_mode(mode);
|
||
|
||
nonblocking_rep = false;
|
||
open_rep = false;
|
||
}
|
||
|
||
// ===================================================================
|
||
// Attributes
|
||
|
||
/**
|
||
* Returns info about supported I/O modes (bitwise-OR)
|
||
*
|
||
* Note that return value may change after device is
|
||
* opened (some objects will refine their attributes after
|
||
* external resources are acquired).
|
||
*
|
||
* By default, all I/O modes are supported.
|
||
*/
|
||
int AUDIO_IO::supported_io_modes(void) const { return (io_read | io_readwrite | io_write); }
|
||
|
||
/**
|
||
* Whether device supports non-blocking I/O mode.
|
||
*
|
||
* Note that return value may change after device is
|
||
* opened (some objects will refine their attributes after
|
||
* external resources are acquired).
|
||
*
|
||
* By default, nonblocking mode is not supported.
|
||
*/
|
||
bool AUDIO_IO::supports_nonblocking_mode(void) const { return false; }
|
||
|
||
/**
|
||
* Whether device supports seeking.
|
||
*
|
||
* Note that return value may change after device is
|
||
* opened (some objects will refine their attributes after
|
||
* external resources are acquired).
|
||
*
|
||
* By default, seeking is supported.
|
||
*
|
||
* @see supports_seeking_sample_accurate()
|
||
*/
|
||
bool AUDIO_IO::supports_seeking(void) const { return true; }
|
||
|
||
/**
|
||
* Whether device supports sample accurate seeking.
|
||
*
|
||
* @see supports_seeking()
|
||
*
|
||
* Note that return value may change after device is
|
||
* opened (some objects will refine their attributes after
|
||
* external resources are acquired).
|
||
*
|
||
* By default, sample accurate seeking is supported if
|
||
* supports_seeking() returns 'true'.
|
||
*/
|
||
bool AUDIO_IO::supports_seeking_sample_accurate(void) const { return AUDIO_IO::supports_seeking(); }
|
||
|
||
/**
|
||
* Whether audio stream has a distinct length. It's important
|
||
* to note the difference between this attribute and
|
||
* 'supports_seeking()'. For example, a file read through
|
||
* a pipe mechanism is not seekable and its length is not
|
||
* known until 'finished()<29> becomes true, but still, it is
|
||
* of finite length. A sine oscillator on the other hand
|
||
* can go on producing a signal forever, and is thus infinite.
|
||
*
|
||
* This attributes directly affects how 'finished()' should
|
||
* to be interpreted. @see finished().
|
||
*
|
||
* Note that return value may change after device is
|
||
* opened (some objects will refine their attributes after
|
||
* external resources are acquired).
|
||
*
|
||
* By default, audio streams are finite length.
|
||
*/
|
||
bool AUDIO_IO::finite_length_stream(void) const { return true; }
|
||
|
||
/**
|
||
* Whether audio format is locked. If this is true, audio object
|
||
* has a known audio format, and doesn't allow overriding it.
|
||
*
|
||
* By default, audio format is not locked.
|
||
*/
|
||
bool AUDIO_IO::locked_audio_format(void) const { return false; }
|
||
|
||
// ===================================================================
|
||
// Configuration (setting and getting configuration parameters)
|
||
|
||
/**
|
||
* Returns info about the current I/O mode.
|
||
*/
|
||
int AUDIO_IO::io_mode(void) const { return io_mode_rep; }
|
||
|
||
/**
|
||
* Set object input/output-mode. If the requested mode isn't
|
||
* supported, the nearest supported mode is used. Because
|
||
* of this, it's wise to afterwards check whether the requested
|
||
* mode was accepted.
|
||
*
|
||
* require:
|
||
* is_open() != true
|
||
*/
|
||
void AUDIO_IO::set_io_mode(int mode) { io_mode_rep = mode; }
|
||
|
||
/**
|
||
* Sets the object label. Label is used to identify the object instance.
|
||
* Unlike ECA_OBJECT::name(), which is typically the same for all
|
||
* instances of the class, label() is instance specific. Still it
|
||
* is not guaranteed to be unique for each object. Device and file
|
||
* names are typical label values.
|
||
*
|
||
* require:
|
||
* is_open() != true
|
||
*/
|
||
void AUDIO_IO::set_label(const string& id_label) { id_label_rep = id_label; }
|
||
|
||
/**
|
||
* Enable/disbale nonblocking mode.
|
||
*
|
||
* require:
|
||
* is_open() != true
|
||
*/
|
||
void AUDIO_IO::toggle_nonblocking_mode(bool value)
|
||
{
|
||
nonblocking_rep = value;
|
||
}
|
||
|
||
/**
|
||
* Returns the current label. See documentation for
|
||
* label(const string&).
|
||
*/
|
||
const string& AUDIO_IO::label(void) const { return id_label_rep; }
|
||
|
||
|
||
/**
|
||
* Returns a string containing info about sample format parameters.
|
||
*/
|
||
string AUDIO_IO::format_info(void) const
|
||
{
|
||
MESSAGE_ITEM otemp;
|
||
if (locked_audio_format() == true && is_open() != true) {
|
||
otemp << "Using audio format specified in file header data (file not yet opened).";
|
||
} else {
|
||
otemp << "Format: " << format_string();
|
||
otemp << ", channels " << channels();
|
||
otemp << ", srate " << samples_per_second();
|
||
|
||
if (interleaved_channels() == true)
|
||
otemp << ", interleaved";
|
||
else
|
||
otemp << ", noninterleaved";
|
||
|
||
if (locked_audio_format() == true)
|
||
otemp << " (locked params).";
|
||
else
|
||
otemp << ".";
|
||
}
|
||
return otemp.to_string();
|
||
}
|
||
|
||
void AUDIO_IO::set_parameter(int param,
|
||
string value)
|
||
{
|
||
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
||
AUDIO_IO::parameter_set_to_string(param, value));
|
||
if (param == 1) set_label(value);
|
||
}
|
||
|
||
string AUDIO_IO::get_parameter(int param) const
|
||
{
|
||
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
||
AUDIO_IO::parameter_get_to_string(param));
|
||
|
||
if (param == 1) return label();
|
||
return "";
|
||
}
|
||
|
||
/**
|
||
* Returns a debugging string for a parameter value change.
|
||
*/
|
||
string AUDIO_IO::parameter_get_to_string(int param) const
|
||
{
|
||
return string("get param ")
|
||
+ kvu_numtostr(param) + " of \"" + label() + "\": \""
|
||
+ kvu_numtostr(param) + "\"";
|
||
}
|
||
|
||
/**
|
||
* Returns a debugging string for a parameter value change.
|
||
*/
|
||
string AUDIO_IO::parameter_set_to_string(int param, string value) const
|
||
{
|
||
return string("set param ")
|
||
+ kvu_numtostr(param) + " of \""
|
||
+ label() + "\" to \"" + value + "\"";
|
||
}
|
||
|
||
// ===================================================================
|
||
// Main functionality
|
||
|
||
void AUDIO_IO::open(void) throw (AUDIO_IO::SETUP_ERROR &)
|
||
{
|
||
DBC_REQUIRE(is_open() != true);
|
||
|
||
DBC_CHECK(channels() > 0);
|
||
DBC_CHECK(sample_format() != ECA_AUDIO_FORMAT::sfmt_none);
|
||
DBC_CHECK(samples_per_second() > 0);
|
||
|
||
open_rep = true;
|
||
if (supports_seeking() == true)
|
||
seek_position(position_in_samples());
|
||
else
|
||
/* note: if seeking is not supported, object always starts
|
||
* at position 0 when opened */
|
||
set_position_in_samples(0);
|
||
}
|
||
|
||
void AUDIO_IO::close(void)
|
||
{
|
||
DBC_REQUIRE(is_open() == true);
|
||
open_rep = false;
|
||
}
|
||
|
||
// ===================================================================
|
||
// Runtime information
|
||
|
||
/**
|
||
* If applicable, returns total length of the audio data stored
|
||
* into current audio object. In many situations it's impossible
|
||
* enquire the whole length of the object. For instance, if the
|
||
* object is streaming a finite length audio stream audio object
|
||
* from other applications using some type of standard IPC,
|
||
* the actual length won't be known until the whole stream has
|
||
* been read. As a general rule, if 'supports_seeking() == true',
|
||
* length can be known right after initialization. Then again,
|
||
* if 'finite_length_stream() == true', the whole stream must
|
||
* be processed before we know the actual length. In other
|
||
* cases, length is unknown or infinite.
|
||
*/
|
||
ECA_AUDIO_TIME AUDIO_IO::length(void) const
|
||
{
|
||
return ECA_AUDIO_TIME(length_in_samples(), samples_per_second());
|
||
}
|
||
|
||
/**
|
||
* Returns the current position.
|
||
*/
|
||
ECA_AUDIO_TIME AUDIO_IO::position(void) const
|
||
{
|
||
return ECA_AUDIO_TIME(position_in_samples(), samples_per_second());
|
||
}
|
||
|
||
/**
|
||
* Is nonblocking mode is enabled?
|
||
*/
|
||
bool AUDIO_IO::nonblocking_mode(void) const { return nonblocking_rep; }
|
||
|
||
/**
|
||
* Is the audio object ready for reading?
|
||
*/
|
||
bool AUDIO_IO::readable(void) const { return is_open() && io_mode() != io_write; }
|
||
|
||
/**
|
||
* Is the audio object ready for writing?
|
||
*/
|
||
bool AUDIO_IO::writable(void) const { return is_open() && io_mode() != io_read; }
|
||
|
||
/**
|
||
* Sets the total length of audio object data.
|
||
*/
|
||
void AUDIO_IO::length(const ECA_AUDIO_TIME& v)
|
||
{
|
||
set_length_in_samples(v.samples());
|
||
}
|
||
|
||
/**
|
||
* Sets the current position.
|
||
*/
|
||
void AUDIO_IO::position(const ECA_AUDIO_TIME& v)
|
||
{
|
||
if (v.samples_per_second() == samples_per_second())
|
||
set_position_in_samples(v.samples());
|
||
else {
|
||
ECA_AUDIO_TIME modified (v);
|
||
modified.set_samples_per_second_keeptime(samples_per_second());
|
||
set_position_in_samples(modified.samples());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Optional status string
|
||
*
|
||
* An unformatted text string describing the state and status of
|
||
* the current object.
|
||
*/
|
||
string AUDIO_IO::status(void) const {
|
||
MESSAGE_ITEM mitem;
|
||
mitem.setprecision(3);
|
||
|
||
mitem << "position (" << position_in_seconds_exact() << "/";
|
||
if (finite_length_stream() == true)
|
||
mitem << length_in_seconds_exact();
|
||
else
|
||
mitem << "inf";
|
||
mitem << ") seconds.\n -> ";
|
||
|
||
if (is_open() == true)
|
||
mitem << "open, ";
|
||
else
|
||
mitem << "closed";
|
||
|
||
if (locked_audio_format() == true &&
|
||
is_open() != true) {
|
||
mitem << ", audio format not available until object is opened.";
|
||
}
|
||
else {
|
||
mitem << ", " << format_string() << "/" << channels() << "ch/" << samples_per_second();
|
||
mitem << "Hz, buffer " << buffersize() << ".";
|
||
}
|
||
|
||
return mitem.to_string();
|
||
}
|
||
|
||
/**
|
||
* Overrides the non-virtaul function
|
||
* ECA_SAMPLERATE_AWARE::samples_per_second(), that is
|
||
* present (through inheritance) in both ECA_AUDIO_FORMAT
|
||
* and ECA_AUDIO_POSITION.
|
||
*/
|
||
SAMPLE_SPECS::sample_rate_t AUDIO_IO::samples_per_second(void) const
|
||
{
|
||
DBC_CHECK(ECA_AUDIO_FORMAT::samples_per_second() ==
|
||
ECA_AUDIO_POSITION::samples_per_second());
|
||
return ECA_AUDIO_FORMAT::samples_per_second();
|
||
}
|
||
|
||
void AUDIO_IO::set_samples_per_second(SAMPLE_SPECS::sample_rate_t v)
|
||
{
|
||
ECA_AUDIO_FORMAT::set_samples_per_second(v);
|
||
ECA_AUDIO_POSITION::set_samples_per_second(v);
|
||
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
||
"set srate, aobj \"" +
|
||
name() + ":" + label() +
|
||
"\" to " +
|
||
kvu_numtostr(v) + ".");
|
||
}
|
||
|
||
void AUDIO_IO::set_audio_format(const ECA_AUDIO_FORMAT& f_str)
|
||
{
|
||
ECA_AUDIO_FORMAT::set_audio_format(f_str);
|
||
ECA_AUDIO_POSITION::set_samples_per_second(f_str.samples_per_second());
|
||
}
|
||
|
||
SAMPLE_SPECS::sample_pos_t AUDIO_IO::seek_position(SAMPLE_SPECS::sample_pos_t pos)
|
||
{
|
||
if (supports_seeking() != true &&
|
||
pos != 0) {
|
||
ECA_LOG_MSG(ECA_LOGGER::info,
|
||
"WARNING: seeking not supported by audio objects of type \"" +
|
||
name() + "\".");
|
||
return position_in_samples();
|
||
}
|
||
|
||
return pos;
|
||
}
|