- Fix process callbakc handling during export
- Fix filename handling when exporting multiple files - Some updates to audiographer git-svn-id: svn://localhost/ardour2/branches/3.0@6402 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
dde0848a98
commit
8da27200d1
|
@ -62,6 +62,7 @@ class ExportGraphBuilder
|
|||
~ExportGraphBuilder ();
|
||||
|
||||
int process (nframes_t frames, bool last_cycle);
|
||||
bool process_normalize (); // returns true when finished
|
||||
|
||||
void reset ();
|
||||
void add_config (FileSpec const & config);
|
||||
|
@ -124,6 +125,9 @@ class ExportGraphBuilder
|
|||
void add_child (FileSpec const & new_config);
|
||||
bool operator== (FileSpec const & other_config) const;
|
||||
|
||||
/// Returns true when finished
|
||||
bool process ();
|
||||
|
||||
private:
|
||||
typedef boost::shared_ptr<AudioGrapher::PeakReader> PeakReaderPtr;
|
||||
typedef boost::shared_ptr<AudioGrapher::Normalizer> NormalizerPtr;
|
||||
|
@ -132,7 +136,6 @@ class ExportGraphBuilder
|
|||
typedef boost::shared_ptr<AudioGrapher::AllocatingProcessContext<Sample> > BufferPtr;
|
||||
|
||||
void start_post_processing();
|
||||
void do_post_processing();
|
||||
|
||||
ExportGraphBuilder & parent;
|
||||
|
||||
|
@ -217,6 +220,8 @@ class ExportGraphBuilder
|
|||
Sample * process_buffer;
|
||||
nframes_t process_buffer_frames;
|
||||
|
||||
std::list<Normalizer *> normalizers;
|
||||
|
||||
Glib::ThreadPool thread_pool;
|
||||
};
|
||||
|
||||
|
|
|
@ -112,17 +112,21 @@ class ExportHandler : public ExportElementFactory
|
|||
|
||||
private:
|
||||
|
||||
int process (nframes_t frames);
|
||||
|
||||
Session & session;
|
||||
GraphBuilderPtr graph_builder;
|
||||
StatusPtr export_status;
|
||||
ConfigMap config_map;
|
||||
|
||||
bool realtime;
|
||||
bool normalizing;
|
||||
|
||||
/* Timespan management */
|
||||
|
||||
void start_timespan ();
|
||||
int process_timespan (nframes_t frames);
|
||||
int process_normalize ();
|
||||
void finish_timespan ();
|
||||
|
||||
typedef std::pair<ConfigMap::iterator, ConfigMap::iterator> TimespanBounds;
|
||||
|
@ -131,8 +135,6 @@ class ExportHandler : public ExportElementFactory
|
|||
|
||||
PBD::ScopedConnection process_connection;
|
||||
sframes_t process_position;
|
||||
|
||||
PBD::ScopedConnection export_read_finished_connection;
|
||||
|
||||
/* CD Marker stuff */
|
||||
|
||||
|
|
|
@ -530,7 +530,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
|
|||
int start_audio_export (nframes_t position, bool realtime);
|
||||
|
||||
PBD::Signal1<int,nframes_t> ProcessExport;
|
||||
PBD::Signal0<void> ExportReadFinished;
|
||||
static PBD::Signal2<void,std::string, std::string> Exported;
|
||||
|
||||
void add_source (boost::shared_ptr<Source>);
|
||||
|
@ -934,7 +933,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
|
|||
nframes_t post_export_position;
|
||||
|
||||
bool _exporting;
|
||||
bool _exporting_realtime;
|
||||
bool _export_rolling;
|
||||
|
||||
boost::shared_ptr<ExportHandler> export_handler;
|
||||
boost::shared_ptr<ExportStatus> export_status;
|
||||
|
|
|
@ -56,11 +56,26 @@ ExportGraphBuilder::process (nframes_t frames, bool last_cycle)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ExportGraphBuilder::process_normalize ()
|
||||
{
|
||||
for (std::list<Normalizer *>::iterator it = normalizers.begin(); it != normalizers.end(); /* ++ in loop */) {
|
||||
if ((*it)->process()) {
|
||||
it = normalizers.erase (it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return normalizers.empty();
|
||||
}
|
||||
|
||||
void
|
||||
ExportGraphBuilder::reset ()
|
||||
{
|
||||
channel_configs.clear ();
|
||||
channels.clear ();
|
||||
normalizers.clear ();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -252,20 +267,21 @@ ExportGraphBuilder::Normalizer::operator== (FileSpec const & other_config) const
|
|||
config.format->normalize_target() == other_config.format->normalize_target();
|
||||
}
|
||||
|
||||
bool
|
||||
ExportGraphBuilder::Normalizer::process()
|
||||
{
|
||||
ProcessContext<Sample> buffer_copy (*buffer);
|
||||
tmp_file->read (buffer_copy);
|
||||
normalizer->process (buffer_copy);
|
||||
return buffer_copy.frames() != buffer->frames();
|
||||
}
|
||||
|
||||
void
|
||||
ExportGraphBuilder::Normalizer::start_post_processing()
|
||||
{
|
||||
normalizer->set_peak (peak_reader->get_peak());
|
||||
tmp_file->seek (0, SndfileReader<Sample>::SeekBeginning);
|
||||
parent.thread_pool.push (sigc::mem_fun (*this, &Normalizer::do_post_processing));
|
||||
}
|
||||
|
||||
void
|
||||
ExportGraphBuilder::Normalizer::do_post_processing()
|
||||
{
|
||||
while (tmp_file->read (*buffer) == buffer->frames()) {
|
||||
normalizer->process (*buffer);
|
||||
}
|
||||
parent.normalizers.push_back (this);
|
||||
}
|
||||
|
||||
/* SRC */
|
||||
|
|
|
@ -119,7 +119,7 @@ ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel
|
|||
FileSpec spec (channel_config, format, filename);
|
||||
ConfigPair pair (timespan, spec);
|
||||
config_map.insert (pair);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -138,8 +138,6 @@ ExportHandler::do_export (bool rt)
|
|||
/* Start export */
|
||||
|
||||
realtime = rt;
|
||||
|
||||
session.ExportReadFinished.connect_same_thread (export_read_finished_connection, boost::bind (&ExportHandler::finish_timespan, this));
|
||||
start_timespan ();
|
||||
}
|
||||
|
||||
|
@ -149,6 +147,7 @@ ExportHandler::start_timespan ()
|
|||
export_status->timespan++;
|
||||
|
||||
if (config_map.empty()) {
|
||||
// freewheeling has to be stopped from outside the process cycle
|
||||
export_status->running = false;
|
||||
return;
|
||||
}
|
||||
|
@ -160,16 +159,32 @@ ExportHandler::start_timespan ()
|
|||
timespan_bounds = config_map.equal_range (current_timespan);
|
||||
graph_builder->reset ();
|
||||
for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
|
||||
graph_builder->add_config (it->second);
|
||||
// Filenames can be shared across timespans
|
||||
FileSpec & spec = it->second;
|
||||
spec.filename->set_timespan (it->first);
|
||||
graph_builder->add_config (spec);
|
||||
}
|
||||
|
||||
/* start export */
|
||||
|
||||
session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process_timespan, this, _1));
|
||||
normalizing = false;
|
||||
session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
|
||||
process_position = current_timespan->get_start();
|
||||
session.start_audio_export (process_position, realtime);
|
||||
}
|
||||
|
||||
int
|
||||
ExportHandler::process (nframes_t frames)
|
||||
{
|
||||
if (!export_status->running) {
|
||||
return 0;
|
||||
} else if (normalizing) {
|
||||
return process_normalize ();
|
||||
} else {
|
||||
return process_timespan (frames);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ExportHandler::process_timespan (nframes_t frames)
|
||||
{
|
||||
|
@ -184,6 +199,7 @@ ExportHandler::process_timespan (nframes_t frames)
|
|||
if (last_cycle) {
|
||||
frames_to_read = end - process_position;
|
||||
export_status->stop = true;
|
||||
normalizing = true;
|
||||
} else {
|
||||
frames_to_read = frames;
|
||||
}
|
||||
|
@ -196,11 +212,19 @@ ExportHandler::process_timespan (nframes_t frames)
|
|||
return graph_builder->process (frames_to_read, last_cycle);
|
||||
}
|
||||
|
||||
int
|
||||
ExportHandler::process_normalize ()
|
||||
{
|
||||
if (graph_builder->process_normalize ()) {
|
||||
finish_timespan ();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
ExportHandler::finish_timespan ()
|
||||
{
|
||||
process_connection.disconnect ();
|
||||
|
||||
while (config_map.begin() != timespan_bounds.second) {
|
||||
config_map.erase (config_map.begin());
|
||||
}
|
||||
|
|
|
@ -123,8 +123,6 @@ Session::start_audio_export (nframes_t position, bool realtime)
|
|||
*/
|
||||
|
||||
_transport_frame = position;
|
||||
|
||||
_exporting_realtime = realtime;
|
||||
export_status->stop = false;
|
||||
|
||||
/* get transport ready. note how this is calling butler functions
|
||||
|
@ -144,39 +142,30 @@ Session::start_audio_export (nframes_t position, bool realtime)
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (realtime) {
|
||||
last_process_function = process_function;
|
||||
process_function = &Session::process_export;
|
||||
} else {
|
||||
_engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1));
|
||||
return _engine.freewheel (true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
_engine.Freewheel.connect_same_thread (export_freewheel_connection, boost::bind (&Session::process_export_fw, this, _1));
|
||||
_export_rolling = true;
|
||||
return _engine.freewheel (true);
|
||||
}
|
||||
|
||||
void
|
||||
Session::process_export (nframes_t nframes)
|
||||
{
|
||||
try {
|
||||
if (_export_rolling && export_status->stop) {
|
||||
stop_audio_export ();
|
||||
}
|
||||
|
||||
if (export_status->stop) {
|
||||
stop_audio_export ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_exporting_realtime) {
|
||||
/* make sure we've caught up with disk i/o, since
|
||||
we're running faster than realtime c/o JACK.
|
||||
*/
|
||||
|
||||
_butler->wait_until_finished ();
|
||||
}
|
||||
if (_export_rolling) {
|
||||
/* make sure we've caught up with disk i/o, since
|
||||
we're running faster than realtime c/o JACK.
|
||||
*/
|
||||
_butler->wait_until_finished ();
|
||||
|
||||
/* do the usual stuff */
|
||||
|
||||
process_without_events (nframes);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
/* handle export - XXX what about error handling? */
|
||||
|
||||
ProcessExport (nframes);
|
||||
|
@ -197,23 +186,16 @@ Session::process_export_fw (nframes_t nframes)
|
|||
int
|
||||
Session::stop_audio_export ()
|
||||
{
|
||||
if (_exporting_realtime) {
|
||||
process_function = last_process_function;
|
||||
} else {
|
||||
export_freewheel_connection.disconnect();
|
||||
}
|
||||
|
||||
/* can't use stop_transport() here because we need
|
||||
an immediate halt and don't require all the declick
|
||||
stuff that stop_transport() implements.
|
||||
*/
|
||||
|
||||
realtime_stop (true, true);
|
||||
_export_rolling = false;
|
||||
_butler->schedule_transport_work ();
|
||||
|
||||
if (!export_status->aborted()) {
|
||||
ExportReadFinished ();
|
||||
} else {
|
||||
if (export_status->aborted()) {
|
||||
finalize_audio_export ();
|
||||
}
|
||||
|
||||
|
@ -225,20 +207,11 @@ void
|
|||
Session::finalize_audio_export ()
|
||||
{
|
||||
_exporting = false;
|
||||
export_status->running = false;
|
||||
|
||||
if (!_exporting_realtime) {
|
||||
_engine.freewheel (false);
|
||||
_exporting_realtime = false;
|
||||
}
|
||||
_export_rolling = false;
|
||||
|
||||
/* Clean up */
|
||||
|
||||
/* BOOST SIGNAL are these necessary?
|
||||
ProcessExport.clear();
|
||||
ExportReadFinished.clear();
|
||||
*/
|
||||
|
||||
_engine.freewheel (false);
|
||||
export_freewheel_connection.disconnect();
|
||||
export_handler.reset();
|
||||
export_status.reset();
|
||||
|
|
|
@ -209,7 +209,6 @@ Session::first_stage_init (string fullpath, string snapshot_name)
|
|||
g_atomic_int_set (&_capture_load_min, 100);
|
||||
_play_range = false;
|
||||
_exporting = false;
|
||||
_exporting_realtime = false;
|
||||
_gain_automation_buffer = 0;
|
||||
_pan_automation_buffer = 0;
|
||||
_npan_buffers = 0;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef AUDIOGRAPHER_DEBUG_UTILS_H
|
||||
#define AUDIOGRAPHER_DEBUG_UTILS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
struct DebugUtils
|
||||
{
|
||||
template<typename T>
|
||||
static std::string demangled_name (T const & obj)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
int status;
|
||||
char * res = abi::__cxa_demangle (typeid(obj).name(), 0, 0, &status);
|
||||
if (status == 0) {
|
||||
std::string s(res);
|
||||
free (res);
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
return typeid(obj).name();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // AUDIOGRAPHER_DEBUG_UTILS_H
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef AUDIOGRAPHER_DEBUGGABLE_H
|
||||
#define AUDIOGRAPHER_DEBUGGABLE_H
|
||||
|
||||
#ifndef DEFAULT_DEBUG_LEVEL
|
||||
#define DEFAULT_DEBUG_LEVEL DebugNone
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
enum DebugLevel
|
||||
{
|
||||
DebugNone, //< Disabled
|
||||
DebugObject, //< Object level stuff, ctors, initalizers etc.
|
||||
DebugProcess, //< Process cycle level stuff
|
||||
DebugVerbose, //< Lots of output, not on sample level
|
||||
DebugSample //< Sample level stuff
|
||||
};
|
||||
|
||||
/// Class that allows optimizing out debugging code during compile time
|
||||
template<DebugLevel L = DEFAULT_DEBUG_LEVEL>
|
||||
class Debuggable
|
||||
{
|
||||
protected:
|
||||
Debuggable(std::ostream & debug_stream = std::cerr)
|
||||
: stream (debug_stream) {}
|
||||
|
||||
bool debug_level (DebugLevel level) {
|
||||
#ifdef NDEBUG
|
||||
return false;
|
||||
#else
|
||||
return L >= level;
|
||||
#endif
|
||||
}
|
||||
std::ostream & debug_stream() { return stream; }
|
||||
|
||||
private:
|
||||
std::ostream & stream;
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // AUDIOGRAPHER_DEBUGGABLE_H
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <cxxabi.h>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "audiographer/debug_utils.h"
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
|
@ -15,8 +16,9 @@ class Exception : public std::exception
|
|||
public:
|
||||
template<typename T>
|
||||
Exception (T const & thrower, std::string const & reason)
|
||||
: reason (boost::str (boost::format (
|
||||
"Exception thrown by %1%: %2%") % name (thrower) % reason))
|
||||
: reason (boost::str (boost::format
|
||||
("Exception thrown by %1%: %2%")
|
||||
% DebugUtils::demangled_name (thrower) % reason))
|
||||
{}
|
||||
|
||||
virtual ~Exception () throw() { }
|
||||
|
@ -25,22 +27,6 @@ class Exception : public std::exception
|
|||
{
|
||||
return reason.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename T>
|
||||
std::string name (T const & obj)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
int status;
|
||||
char * res = abi::__cxa_demangle (typeid(obj).name(), 0, 0, &status);
|
||||
if (status == 0) {
|
||||
std::string s(res);
|
||||
free (res);
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
return typeid(obj).name();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string const reason;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#ifndef AUDIOGRAPHER_PROCESS_CONTEXT_H
|
||||
#define AUDIOGRAPHER_PROCESS_CONTEXT_H
|
||||
|
||||
#include "types.h"
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/type_traits.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include "types.h"
|
||||
#include "type_utils.h"
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
@ -14,7 +16,9 @@ namespace AudioGrapher
|
|||
|
||||
template <typename T>
|
||||
class ProcessContext {
|
||||
|
||||
|
||||
BOOST_STATIC_ASSERT (boost::has_trivial_destructor<T>::value);
|
||||
|
||||
public:
|
||||
|
||||
typedef FlagField::Flag Flag;
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
#include <sigc++/signal.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "debuggable.h"
|
||||
|
||||
namespace AudioGrapher {
|
||||
|
||||
/// Common interface for templated libsndfile readers/writers
|
||||
class SndfileBase
|
||||
class SndfileBase : public Debuggable<>
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
|
@ -3,14 +3,20 @@
|
|||
|
||||
#include <samplerate.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "debuggable.h"
|
||||
#include "listed_source.h"
|
||||
#include "sink.h"
|
||||
#include "throwing.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
class SampleRateConverter : public ListedSource<float>, public Sink<float>
|
||||
class SampleRateConverter
|
||||
: public ListedSource<float>
|
||||
, public Sink<float>
|
||||
, public Debuggable<>
|
||||
, public Throwing<>
|
||||
{
|
||||
public:
|
||||
SampleRateConverter (uint32_t channels);
|
||||
|
|
|
@ -23,7 +23,8 @@ class ThreaderException : public Exception
|
|||
ThreaderException (T const & thrower, std::exception const & e)
|
||||
: Exception (thrower,
|
||||
boost::str ( boost::format
|
||||
("\n\t- Dynamic type: %1%\n\t- what(): %2%") % name (e) % e.what() ))
|
||||
("\n\t- Dynamic type: %1%\n\t- what(): %2%")
|
||||
% DebugUtils::demangled_name (e) % e.what() ))
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef AUDIOGRAPHER_THROWING_H
|
||||
#define AUDIOGRAPHER_THROWING_H
|
||||
|
||||
#ifndef DEFAULT_THROW_LEVEL
|
||||
#define DEFAULT_THROW_LEVEL ThrowStrict
|
||||
#endif
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
enum ThrowLevel
|
||||
{
|
||||
ThrowNone, //< Not allowed to throw
|
||||
ThrowObject, //< Object level stuff, ctors, initalizers etc.
|
||||
ThrowProcess, //< Process cycle level stuff
|
||||
ThrowStrict, //< Stricter checks than ThrowProcess, less than ThrowSample
|
||||
ThrowSample //< Sample level stuff
|
||||
};
|
||||
|
||||
/// Class that allows optimizing out error checking during compile time
|
||||
template<ThrowLevel L = DEFAULT_THROW_LEVEL>
|
||||
class Throwing
|
||||
{
|
||||
protected:
|
||||
Throwing() {}
|
||||
bool throw_level (ThrowLevel level) { return L >= level; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // AUDIOGRAPHER_THROWING_H
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef AUDIOGRAPHER_TYPE_UTILS_H
|
||||
#define AUDIOGRAPHER_TYPE_UTILS_H
|
||||
|
||||
#include "types.h"
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/type_traits.hpp>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
|
||||
class TypeUtilsBase
|
||||
{
|
||||
protected:
|
||||
|
||||
template<typename T, bool b>
|
||||
static void do_fill(T * buffer, nframes_t frames, const boost::integral_constant<bool, b>&)
|
||||
{ std::uninitialized_fill_n (buffer, frames, T()); }
|
||||
|
||||
template<typename T>
|
||||
static void do_fill(T * buffer, nframes_t frames, const boost::true_type&)
|
||||
{ memset (buffer, frames * sizeof(T), 0); }
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class TypeUtils : private TypeUtilsBase
|
||||
{
|
||||
BOOST_STATIC_ASSERT (boost::has_trivial_destructor<T>::value);
|
||||
|
||||
typedef boost::integral_constant<bool,
|
||||
boost::is_floating_point<T>::value ||
|
||||
boost::is_signed<T>::value> zero_fillable;
|
||||
public:
|
||||
inline static void zero_fill (T * buffer, nframes_t frames)
|
||||
{ do_zero_fill(buffer, frames, zero_fillable()); }
|
||||
|
||||
inline static void copy (T* source, T* destination, nframes_t frames)
|
||||
{ std::uninitialized_copy (source, &source[frames], destination); }
|
||||
|
||||
inline static void move (T* source, T* destination, nframes_t frames)
|
||||
{
|
||||
if (destination < source) {
|
||||
std::copy (source, &source[frames], destination);
|
||||
} else {
|
||||
std::copy_backward (source, &source[frames], destination);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // AUDIOGRAPHER_TYPE_UTILS_H
|
|
@ -18,10 +18,15 @@ class FlagField {
|
|||
FlagField() : _flags (0) {}
|
||||
FlagField(FlagField const & other) : _flags (other._flags) {}
|
||||
|
||||
inline bool has (Flag flag) const { return _flags & (1 << flag); }
|
||||
inline void set (Flag flag) { _flags |= (1 << flag); }
|
||||
inline void remove (Flag flag) { _flags &= ~(1 << flag); }
|
||||
inline storage_type flags () const { return _flags; }
|
||||
inline bool has (Flag flag) const { return _flags & (1 << flag); }
|
||||
inline storage_type flags () const { return _flags; }
|
||||
inline operator bool() const { return _flags; }
|
||||
inline void set (Flag flag) { _flags |= (1 << flag); }
|
||||
inline void remove (Flag flag) { _flags &= ~(1 << flag); }
|
||||
inline void reset () { _flags = 0; }
|
||||
|
||||
inline FlagField & operator+= (FlagField const & other) { _flags |= other._flags; return *this; }
|
||||
inline bool operator== (FlagField const & other) const { return _flags == other._flags; }
|
||||
|
||||
private:
|
||||
storage_type _flags;
|
||||
|
|
|
@ -33,6 +33,7 @@ SndfileReader<T>::read (ProcessContext<T> & context)
|
|||
|
||||
nframes_t frames_read = (*read_func) (sndfile, context.data(), context.frames());
|
||||
if (frames_read < context.frames()) {
|
||||
context.frames() = frames_read;
|
||||
context.set_flag (ProcessContext<T>::EndOfInput);
|
||||
}
|
||||
output (context);
|
||||
|
|
|
@ -60,9 +60,10 @@ SndfileWriter<T>::process (ProcessContext<T> const & c)
|
|||
|
||||
if (c.has_flag(ProcessContext<T>::EndOfInput)) {
|
||||
sf_write_sync (sndfile);
|
||||
//#ifdef HAVE_SIGCPP
|
||||
FileWritten (path);
|
||||
//#endif
|
||||
if (debug_level (DebugProcess)) {
|
||||
debug_stream() << str ( format("Finished writing file %1%") % path) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
#include "audiographer/sr_converter.h"
|
||||
#include "audiographer/exception.h"
|
||||
#include "audiographer/type_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
#include <iostream>
|
||||
#define DEBUG(str) std::cout << str << std::endl;
|
||||
#else
|
||||
#define DEBUG(str)
|
||||
#endif
|
||||
|
||||
namespace AudioGrapher
|
||||
{
|
||||
using boost::format;
|
||||
|
@ -44,8 +35,11 @@ SampleRateConverter::init (nframes_t in_rate, nframes_t out_rate, int quality)
|
|||
|
||||
active = true;
|
||||
int err;
|
||||
if ((src_state = src_new (quality, channels, &err)) == 0) {
|
||||
throw Exception (*this, str (format ("Cannot initialize sample rate converter: %1%") % src_strerror (err)));
|
||||
src_state = src_new (quality, channels, &err);
|
||||
if (throw_level (ThrowObject) && !src_state) {
|
||||
throw Exception (*this, str (format
|
||||
("Cannot initialize sample rate converter: %1%")
|
||||
% src_strerror (err)));
|
||||
}
|
||||
|
||||
src_data.src_ratio = (double) out_rate / (double) in_rate;
|
||||
|
@ -70,7 +64,7 @@ SampleRateConverter::allocate_buffers (nframes_t max_frames)
|
|||
|
||||
max_leftover_frames = 4 * max_frames;
|
||||
leftover_data = (float *) realloc (leftover_data, max_leftover_frames * sizeof (float));
|
||||
if (!leftover_data) {
|
||||
if (throw_level (ThrowObject) && !leftover_data) {
|
||||
throw Exception (*this, "A memory allocation error occured");
|
||||
}
|
||||
|
||||
|
@ -92,13 +86,13 @@ SampleRateConverter::process (ProcessContext<float> const & c)
|
|||
nframes_t frames = c.frames();
|
||||
float * in = const_cast<float *> (c.data()); // TODO check if this is safe!
|
||||
|
||||
if (frames > max_frames_in) {
|
||||
if (throw_level (ThrowStrict) && frames > max_frames_in) {
|
||||
throw Exception (*this, str (format (
|
||||
"process() called with too many frames, %1% instead of %2%")
|
||||
% frames % max_frames_in));
|
||||
}
|
||||
|
||||
if (frames % channels != 0) {
|
||||
if (throw_level (ThrowStrict) && frames % channels != 0) {
|
||||
throw Exception (*this, boost::str (boost::format (
|
||||
"Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels")
|
||||
% frames % channels));
|
||||
|
@ -121,7 +115,7 @@ SampleRateConverter::process (ProcessContext<float> const & c)
|
|||
|
||||
/* first time, append new data from data_in into the leftover_data buffer */
|
||||
|
||||
memcpy (&leftover_data [leftover_frames * channels], in, frames * sizeof(float));
|
||||
TypeUtils<float>::copy (&leftover_data [leftover_frames * channels], in, frames);
|
||||
src_data.input_frames = frames + leftover_frames;
|
||||
} else {
|
||||
|
||||
|
@ -140,23 +134,28 @@ SampleRateConverter::process (ProcessContext<float> const & c)
|
|||
|
||||
first_time = false;
|
||||
|
||||
DEBUG ("data_in: " << src_data.data_in);
|
||||
DEBUG ("input_frames: " << src_data.input_frames);
|
||||
DEBUG ("data_out: " << src_data.data_out);
|
||||
DEBUG ("output_frames: " << src_data.output_frames);
|
||||
if (debug_level (DebugVerbose)) {
|
||||
debug_stream() << "data_in: " << src_data.data_in <<
|
||||
", input_frames: " << src_data.input_frames <<
|
||||
", data_out: " << src_data.data_out <<
|
||||
", output_frames: " << src_data.output_frames << std::endl;
|
||||
}
|
||||
|
||||
if ((err = src_process (src_state, &src_data)) != 0) {
|
||||
throw Exception (*this, str (format ("An error occured during sample rate conversion: %1%") % src_strerror (err)));
|
||||
err = src_process (src_state, &src_data);
|
||||
if (throw_level (ThrowProcess) && err) {
|
||||
throw Exception (*this, str (format
|
||||
("An error occured during sample rate conversion: %1%")
|
||||
% src_strerror (err)));
|
||||
}
|
||||
|
||||
leftover_frames = src_data.input_frames - src_data.input_frames_used;
|
||||
|
||||
if (leftover_frames > 0) {
|
||||
if (leftover_frames > max_leftover_frames) {
|
||||
if (throw_level (ThrowProcess) && leftover_frames > max_leftover_frames) {
|
||||
throw Exception(*this, "leftover frames overflowed");
|
||||
}
|
||||
memmove (leftover_data, (char *) &src_data.data_in[src_data.input_frames_used * channels],
|
||||
leftover_frames * channels * sizeof(float));
|
||||
TypeUtils<float>::move (&src_data.data_in[src_data.input_frames_used * channels],
|
||||
leftover_data, leftover_frames * channels);
|
||||
}
|
||||
|
||||
ProcessContext<float> c_out (c, data_out, src_data.output_frames_gen * channels);
|
||||
|
@ -165,11 +164,17 @@ SampleRateConverter::process (ProcessContext<float> const & c)
|
|||
}
|
||||
output (c_out);
|
||||
|
||||
DEBUG ("src_data.output_frames_gen: " << src_data.output_frames_gen << ", leftover_frames: " << leftover_frames);
|
||||
if (debug_level (DebugProcess)) {
|
||||
debug_stream() <<
|
||||
"src_data.output_frames_gen: " << src_data.output_frames_gen <<
|
||||
", leftover_frames: " << leftover_frames << std::endl;
|
||||
}
|
||||
|
||||
if (src_data.output_frames_gen == 0 && leftover_frames) { throw Exception (*this, boost::str (boost::format (
|
||||
"No output frames genereated with %1% leftover frames")
|
||||
% leftover_frames)); }
|
||||
if (throw_level (ThrowProcess) && src_data.output_frames_gen == 0 && leftover_frames) {
|
||||
throw Exception (*this, boost::str (boost::format
|
||||
("No output frames genereated with %1% leftover frames")
|
||||
% leftover_frames));
|
||||
}
|
||||
|
||||
} while (leftover_frames > frames);
|
||||
|
||||
|
@ -189,7 +194,9 @@ void SampleRateConverter::set_end_of_input (ProcessContext<float> const & c)
|
|||
/* No idea why this has to be done twice for all data to be written,
|
||||
* but that just seems to be the way it is...
|
||||
*/
|
||||
dummy.remove_flag (ProcessContext<float>::EndOfInput);
|
||||
process (dummy);
|
||||
dummy.set_flag (ProcessContext<float>::EndOfInput);
|
||||
process (dummy);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ class SampleRateConverterTest : public CppUnit::TestFixture
|
|||
CPPUNIT_TEST (testNoConversion);
|
||||
CPPUNIT_TEST (testUpsampleLength);
|
||||
CPPUNIT_TEST (testDownsampleLength);
|
||||
CPPUNIT_TEST (testRespectsEndOfInput);
|
||||
CPPUNIT_TEST_SUITE_END ();
|
||||
|
||||
public:
|
||||
|
@ -17,6 +18,7 @@ class SampleRateConverterTest : public CppUnit::TestFixture
|
|||
frames = 128;
|
||||
random_data = TestUtils::init_random_data(frames);
|
||||
sink.reset (new AppendingVectorSink<float>());
|
||||
grabber.reset (new ProcessContextGrabber<float>());
|
||||
converter.reset (new SampleRateConverter (1));
|
||||
}
|
||||
|
||||
|
@ -88,10 +90,36 @@ class SampleRateConverterTest : public CppUnit::TestFixture
|
|||
CPPUNIT_ASSERT (half_frames - tolerance < frames_output && frames_output < half_frames + tolerance);
|
||||
}
|
||||
|
||||
void testRespectsEndOfInput()
|
||||
{
|
||||
assert (frames % 2 == 0);
|
||||
nframes_t const half_frames = frames / 2;
|
||||
|
||||
converter->init (44100, 48000);
|
||||
converter->allocate_buffers (half_frames);
|
||||
converter->add_output (grabber);
|
||||
|
||||
ProcessContext<float> c (random_data, half_frames, 1);
|
||||
converter->process (c);
|
||||
ProcessContext<float> c2 (&random_data[half_frames], half_frames / 2, 1);
|
||||
c2.set_flag (ProcessContext<float>::EndOfInput);
|
||||
converter->process (c2);
|
||||
|
||||
for (std::list<ProcessContext<float> >::iterator it = grabber->contexts.begin(); it != grabber->contexts.end(); ++it) {
|
||||
std::list<ProcessContext<float> >::iterator next = it; ++next;
|
||||
if (next == grabber->contexts.end()) {
|
||||
CPPUNIT_ASSERT (it->has_flag (ProcessContext<float>::EndOfInput));
|
||||
} else {
|
||||
CPPUNIT_ASSERT (!it->has_flag (ProcessContext<float>::EndOfInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
boost::shared_ptr<SampleRateConverter > converter;
|
||||
boost::shared_ptr<AppendingVectorSink<float> > sink;
|
||||
boost::shared_ptr<ProcessContextGrabber<float> > grabber;
|
||||
|
||||
float * random_data;
|
||||
nframes_t frames;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "audiographer/exception.h"
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
@ -116,4 +117,19 @@ class ThrowingSink : public AudioGrapher::Sink<T>
|
|||
using AudioGrapher::Sink<T>::process;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ProcessContextGrabber : public AudioGrapher::Sink<T>
|
||||
{
|
||||
public:
|
||||
void process (AudioGrapher::ProcessContext<T> const & c)
|
||||
{
|
||||
contexts.push_back (c);
|
||||
}
|
||||
using AudioGrapher::Sink<T>::process;
|
||||
|
||||
typedef std::list<AudioGrapher::ProcessContext<T> > ContextList;
|
||||
ContextList contexts;
|
||||
|
||||
};
|
||||
|
||||
#endif // AUDIOGRAPHER_TESTS_UTILS_H
|
||||
|
|
Loading…
Reference in New Issue