Re-integrate export-optimization branch.

Export now happens directly to file (unless normalizing is required), and can be easily optimized even further.
The Session process connection is still broken during export (as it was before this commit also).


git-svn-id: svn://localhost/ardour2/branches/3.0@6401 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Sakari Bergen 2009-12-27 14:46:23 +00:00
parent 35c72a53b4
commit dde0848a98
81 changed files with 5370 additions and 2214 deletions

View file

@ -18,7 +18,7 @@ fi
export VAMP_PATH=$libs/vamp-plugins:$VAMP_PATH
export LD_LIBRARY_PATH=$libs/vamp-sdk:$libs/surfaces:$libs/surfaces/control_protocol:$libs/ardour:$libs/midi++2:$libs/pbd:$libs/rubberband:$libs/soundtouch:$libs/gtkmm2ext:$libs/sigc++2:$libs/glibmm2:$libs/gtkmm2/atk:$libs/gtkmm2/pango:$libs/gtkmm2/gdk:$libs/gtkmm2/gtk:$libs/libgnomecanvasmm:$libs/libsndfile:$libs/appleutility:$libs/cairomm:$libs/taglib:$libs/evoral:$libs/evoral/src/libsmf:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$libs/vamp-sdk:$libs/surfaces:$libs/surfaces/control_protocol:$libs/ardour:$libs/midi++2:$libs/pbd:$libs/rubberband:$libs/soundtouch:$libs/gtkmm2ext:$libs/sigc++2:$libs/glibmm2:$libs/gtkmm2/atk:$libs/gtkmm2/pango:$libs/gtkmm2/gdk:$libs/gtkmm2/gtk:$libs/libgnomecanvasmm:$libs/libsndfile:$libs/appleutility:$libs/cairomm:$libs/taglib:$libs/evoral:$libs/evoral/src/libsmf:$libs/audiographer:$LD_LIBRARY_PATH
# DYLD_LIBRARY_PATH is for darwin.
export DYLD_FALLBACK_LIBRARY_PATH=$LD_LIBRARY_PATH

View file

@ -311,6 +311,10 @@ ExportDialog::show_progress ()
usleep (10000);
}
}
if (!status->aborted()) {
status->finish ();
}
}
gint

View file

@ -74,8 +74,8 @@ class AudioEngine : public SessionHandlePtr
Glib::Mutex& process_lock() { return _process_lock; }
nframes_t frame_rate();
nframes_t frames_per_cycle();
nframes_t frame_rate() const;
nframes_t frames_per_cycle() const;
size_t raw_buffer_size(DataType t);
@ -230,9 +230,9 @@ _ the regular process() call to session->process() is not made.
bool session_remove_pending;
bool _running;
bool _has_run;
nframes_t _buffer_size;
mutable nframes_t _buffer_size;
std::map<DataType,size_t> _raw_buffer_sizes;
nframes_t _frame_rate;
mutable nframes_t _frame_rate;
/// number of frames between each check for changes in monitor input
nframes_t monitor_check_interval;
/// time of the last monitor check in frames

View file

@ -1,100 +0,0 @@
/*
Copyright (C) 2000-2007 Paul Davis
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_export_h__
#define __ardour_export_h__
#include <map>
#include <vector>
#include <string>
#include <sndfile.h>
#include <samplerate.h>
#include "ardour/ardour.h"
#include "ardour/gdither.h"
namespace ARDOUR
{
class Port;
typedef std::pair<Port *, uint32_t> PortChannelPair;
typedef std::map<uint32_t, std::vector<PortChannelPair> > ExportPortMap;
struct ExportSpecification : public SF_INFO, public PBD::ScopedConnectionList {
ExportSpecification();
~ExportSpecification ();
void init ();
void clear ();
int prepare (nframes_t blocksize, nframes_t frame_rate);
int process (nframes_t nframes);
/* set by the user */
std::string path;
nframes_t sample_rate;
int src_quality;
SNDFILE* out;
uint32_t channels;
ExportPortMap port_map;
nframes_t start_frame;
nframes_t end_frame;
GDitherType dither_type;
bool do_freewheel;
/* used exclusively during export */
nframes_t frame_rate;
GDither dither;
float* dataF;
float* dataF2;
float* leftoverF;
nframes_t leftover_frames;
nframes_t max_leftover_frames;
void* output_data;
nframes_t out_samples_max;
uint32_t sample_bytes;
uint32_t data_width;
nframes_t total_frames;
SF_INFO sfinfo;
SRC_DATA src_data;
SRC_STATE* src_state;
nframes_t pos;
PBD::ScopedConnection freewheel_connection;
/* shared between UI thread and audio thread */
volatile float progress; /* audio thread sets this */
volatile bool stop; /* UI sets this */
volatile bool running; /* audio thread sets to false when export is done */
int status;
};
} // namespace ARDOUR
#endif /* __ardour_export_h__ */

View file

@ -25,6 +25,7 @@
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/operators.hpp>
#include "ardour/audioregion.h"
#include "ardour/buffer_set.h"
@ -36,7 +37,7 @@ class AudioTrack;
class AudioPort;
/// Export channel base class interface for different source types
class ExportChannel
class ExportChannel : public boost::less_than_comparable<ExportChannel>
{
public:
@ -57,6 +58,7 @@ class ExportChannel
/// Safe pointer for storing ExportChannels in ordered STL containers
class ExportChannelPtr : public boost::shared_ptr<ExportChannel>
, public boost::less_than_comparable<ExportChannel>
{
public:
ExportChannelPtr () {}

View file

@ -45,34 +45,15 @@ class Session;
class ExportChannelConfiguration
{
private:
typedef boost::shared_ptr<ExportProcessor> ProcessorPtr;
typedef boost::shared_ptr<ExportTimespan> TimespanPtr;
typedef boost::shared_ptr<ExportFormatSpecification const> FormatPtr;
typedef boost::shared_ptr<ExportFilename> FilenamePtr;
typedef std::pair<FormatPtr, FilenamePtr> FileConfig;
typedef std::list<FileConfig> FileConfigList;
/// Struct for threading, acts like a pointer to a ExportChannelConfiguration
struct WriterThread {
WriterThread (ExportChannelConfiguration & channel_config) :
channel_config (channel_config), running (false) {}
ExportChannelConfiguration * operator-> () { return &channel_config; }
ExportChannelConfiguration & operator* () { return channel_config; }
ExportChannelConfiguration & channel_config;
pthread_t thread;
bool running;
};
private:
friend class ExportElementFactory;
ExportChannelConfiguration (Session & session);
public:
bool operator== (ExportChannelConfiguration const & other) const { return channels == other.channels; }
bool operator!= (ExportChannelConfiguration const & other) const { return channels != other.channels; }
XMLNode & get_state ();
int set_state (const XMLNode &);
@ -89,40 +70,13 @@ class ExportChannelConfiguration
uint32_t get_n_chans () const { return channels.size(); }
void register_channel (ExportChannelPtr channel) { channels.push_back (channel); }
void register_file_config (FormatPtr format, FilenamePtr filename) { file_configs.push_back (FileConfig (format, filename)); }
void clear_channels () { channels.clear (); }
/// Writes all files for this channel config @return true if a new thread was spawned
bool write_files (boost::shared_ptr<ExportProcessor> new_processor);
PBD::Signal0<void> FilesWritten;
// Tells the handler the necessary information for it to handle tempfiles
void register_with_timespan (TimespanPtr timespan);
void unregister_all ();
private:
typedef boost::shared_ptr<ExportStatus> ExportStatusPtr;
Session & session;
// processor has to be prepared before doing this.
void write_file ();
/// The actual write files, needed for threading
static void * _write_files (void *arg);
WriterThread writer_thread;
ProcessorPtr processor;
ExportStatusPtr status;
bool files_written;
TimespanPtr timespan;
ChannelList channels;
FileConfigList file_configs;
bool split; // Split to mono files
Glib::ustring _name;
};

View file

@ -1,177 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_export_file_io_h__
#define __ardour_export_file_io_h__
#include <stdint.h>
#include <utility>
#include <boost/shared_ptr.hpp>
#include <glibmm/ustring.h>
#include "ardour/sndfile_helpers.h"
#include "ardour/graph.h"
#include "ardour/types.h"
#include "ardour/ardour.h"
#include "ardour/export_format_specification.h"
#include "ardour/export_utilities.h"
namespace ARDOUR
{
/// Common part for all export file writers
class ExportFileWriter
{
public:
virtual ~ExportFileWriter () {}
std::string filename () const { return _filename; }
nframes_t position () const { return _position; }
void set_position (nframes_t position) { _position = position; }
protected:
ExportFileWriter (std::string filename) : _filename (filename) {}
std::string _filename;
nframes_t _position;
};
/// Common interface for templated libsndfile writers
class SndfileWriterBase : public ExportFileWriter
{
public:
SNDFILE * get_sndfile () const { return sndfile; }
protected:
SndfileWriterBase (int channels, nframes_t samplerate, int format, std::string const & path);
virtual ~SndfileWriterBase ();
SF_INFO sf_info;
SNDFILE * sndfile;
};
/// Template parameter specific parts of sndfile writer
template <typename T>
class SndfileWriter : public SndfileWriterBase, public GraphSink<T>
{
// FIXME: having this protected doesn't work with Apple's gcc
// Should only be created vie ExportFileFactory and derived classes
public: // protected
friend class ExportFileFactory;
SndfileWriter (int channels, nframes_t samplerate, int format, std::string const & path);
public:
nframes_t write (T * data, nframes_t frames);
virtual ~SndfileWriter () {}
protected:
sf_count_t (*write_func)(SNDFILE *, const T *, sf_count_t);
private:
void init (); // Inits write function
};
/// Writes and reads a RAW tempfile (file aquired with tmpfile())
class ExportTempFile : public SndfileWriter<float>, public GraphSource<float>
{
public:
ExportTempFile (uint32_t channels, nframes_t samplerate);
~ExportTempFile () {}
/// Causes the file to be read from the beginning again
void reset_read () { reading = false; }
nframes_t read (float * data, nframes_t frames);
/* Silence management */
nframes_t trim_beginning (bool yn = true);
nframes_t trim_end (bool yn = true);
void set_silence_beginning (nframes_t frames);
void set_silence_end (nframes_t frames);
private:
/* File access */
sf_count_t get_length ();
sf_count_t get_position ();
sf_count_t get_read_position (); // get position seems to default to the write pointer
sf_count_t locate_to (nframes_t frames);
sf_count_t _read (float * data, nframes_t frames);
uint32_t channels;
bool reading;
/* Silence related */
/* start and end are used by read() */
nframes_t start;
nframes_t end;
/* these are the silence processing results and state */
void process_beginning ();
void process_end ();
bool beginning_processed;
bool end_processed;
nframes_t silent_frames_beginning;
nframes_t silent_frames_end;
/* Silence to add to start and end */
nframes_t silence_beginning;
nframes_t silence_end;
/* Takes care that the end postion gets set at some stage */
bool end_set;
};
class ExportFileFactory
{
public:
typedef boost::shared_ptr<ExportFormatSpecification const> FormatPtr;
typedef GraphSink<float> FloatSink;
typedef boost::shared_ptr<FloatSink> FloatSinkPtr;
typedef boost::shared_ptr<ExportFileWriter> FileWriterPtr;
typedef std::pair<FloatSinkPtr, FileWriterPtr> FilePair;
static FilePair create (FormatPtr format, uint32_t channels, Glib::ustring const & filename);
static bool check (FormatPtr format, uint32_t channels);
private:
static FilePair create_sndfile (FormatPtr format, unsigned int channels, Glib::ustring const & filename);
static bool check_sndfile (FormatPtr format, unsigned int channels);
};
} // namespace ARDOUR
#endif /* __ardour_export_file_io_h__ */

View file

@ -28,9 +28,11 @@
#include <sndfile.h>
#include <samplerate.h>
#include "ardour/gdither_types.h"
#include "ardour/ardour.h"
#include "audiographer/sample_format_converter.h"
namespace ARDOUR
{
@ -76,10 +78,10 @@ class ExportFormatBase {
};
enum DitherType {
D_None = GDitherNone,
D_Rect = GDitherRect,
D_Tri = GDitherTri,
D_Shaped = GDitherShaped
D_None = AudioGrapher::D_None,
D_Rect = AudioGrapher::D_Rect,
D_Tri = AudioGrapher::D_Tri,
D_Shaped = AudioGrapher::D_Shaped
};
enum Quality {

View file

@ -126,6 +126,9 @@ class ExportFormatSpecification : public ExportFormatBase {
nframes_t silence_beginning () const { return _silence_beginning.get_frames (sample_rate()); }
nframes_t silence_end () const { return _silence_end.get_frames (sample_rate()); }
nframes_t silence_beginning (nframes_t samplerate) const { return _silence_beginning.get_frames (samplerate); }
nframes_t silence_end (nframes_t samplerate) const { return _silence_end.get_frames (samplerate); }
AnyTime silence_beginning_time () const { return _silence_beginning; }
AnyTime silence_end_time () const { return _silence_end; }

View file

@ -0,0 +1,225 @@
/*
Copyright (C) 2009 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_export_graph_builder_h__
#define __ardour_export_graph_builder_h__
#include "ardour/ardour.h"
#include "ardour/export_handler.h"
#include "ardour/export_channel.h"
#include "ardour/export_format_base.h"
#include "audiographer/identity_vertex.h"
#include <glibmm/threadpool.h>
namespace AudioGrapher {
class SampleRateConverter;
class PeakReader;
class Normalizer;
template <typename T> class SampleFormatConverter;
template <typename T> class Interleaver;
template <typename T> class SndfileWriter;
template <typename T> class SilenceTrimmer;
template <typename T> class TmpFile;
template <typename T> class Threader;
template <typename T> class AllocatingProcessContext;
}
namespace ARDOUR
{
class ExportGraphBuilder
{
private:
typedef ExportHandler::FileSpec FileSpec;
typedef ExportElementFactory::FilenamePtr FilenamePtr;
typedef boost::shared_ptr<AudioGrapher::Sink<Sample> > FloatSinkPtr;
typedef boost::shared_ptr<AudioGrapher::IdentityVertex<Sample> > IdentityVertexPtr;
typedef std::map<ExportChannelPtr, IdentityVertexPtr> ChannelMap;
public:
ExportGraphBuilder (Session const & session);
~ExportGraphBuilder ();
int process (nframes_t frames, bool last_cycle);
void reset ();
void add_config (FileSpec const & config);
private:
class Encoder : public sigc::trackable {
public:
template <typename T> boost::shared_ptr<AudioGrapher::Sink<T> > init (FileSpec const & new_config);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
static int get_real_format (FileSpec const & config);
private:
typedef boost::shared_ptr<AudioGrapher::SndfileWriter<Sample> > FloatWriterPtr;
typedef boost::shared_ptr<AudioGrapher::SndfileWriter<int> > IntWriterPtr;
typedef boost::shared_ptr<AudioGrapher::SndfileWriter<short> > ShortWriterPtr;
template<typename T> void init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer);
void copy_files (std::string orig_path);
FileSpec config;
std::list<FilenamePtr> filenames;
// Only one of these should be available at a time
FloatWriterPtr float_writer;
IntWriterPtr int_writer;
ShortWriterPtr short_writer;
};
// sample format converter
class SFC {
public:
// This constructor so that this can be constructed like a Normalizer
SFC (ExportGraphBuilder &) {}
FloatSinkPtr init (FileSpec const & new_config, nframes_t max_frames);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
private:
typedef boost::shared_ptr<AudioGrapher::SampleFormatConverter<Sample> > FloatConverterPtr;
typedef boost::shared_ptr<AudioGrapher::SampleFormatConverter<int> > IntConverterPtr;
typedef boost::shared_ptr<AudioGrapher::SampleFormatConverter<short> > ShortConverterPtr;
FileSpec config;
std::list<Encoder> children;
int data_width;
// Only one of these should be available at a time
FloatConverterPtr float_converter;
IntConverterPtr int_converter;
ShortConverterPtr short_converter;
};
class Normalizer : public sigc::trackable {
public:
Normalizer (ExportGraphBuilder & parent) : parent (parent) {}
FloatSinkPtr init (FileSpec const & new_config, nframes_t max_frames);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
private:
typedef boost::shared_ptr<AudioGrapher::PeakReader> PeakReaderPtr;
typedef boost::shared_ptr<AudioGrapher::Normalizer> NormalizerPtr;
typedef boost::shared_ptr<AudioGrapher::TmpFile<Sample> > TmpFilePtr;
typedef boost::shared_ptr<AudioGrapher::Threader<Sample> > ThreaderPtr;
typedef boost::shared_ptr<AudioGrapher::AllocatingProcessContext<Sample> > BufferPtr;
void start_post_processing();
void do_post_processing();
ExportGraphBuilder & parent;
FileSpec config;
nframes_t max_frames_out;
BufferPtr buffer;
PeakReaderPtr peak_reader;
TmpFilePtr tmp_file;
NormalizerPtr normalizer;
ThreaderPtr threader;
std::list<SFC> children;
};
// sample rate converter
class SRC {
public:
SRC (ExportGraphBuilder & parent) : parent (parent) {}
FloatSinkPtr init (FileSpec const & new_config, nframes_t max_frames);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
private:
typedef boost::shared_ptr<AudioGrapher::SampleRateConverter> SRConverterPtr;
template<typename T>
void add_child_to_list (FileSpec const & new_config, std::list<T> & list);
ExportGraphBuilder & parent;
FileSpec config;
std::list<SFC> children;
std::list<Normalizer> normalized_children;
SRConverterPtr converter;
nframes_t max_frames_out;
};
// Silence trimmer + adder
class SilenceHandler {
public:
SilenceHandler (ExportGraphBuilder & parent) : parent (parent) {}
FloatSinkPtr init (FileSpec const & new_config, nframes_t max_frames);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
private:
typedef boost::shared_ptr<AudioGrapher::SilenceTrimmer<Sample> > SilenceTrimmerPtr;
ExportGraphBuilder & parent;
FileSpec config;
std::list<SRC> children;
SilenceTrimmerPtr silence_trimmer;
nframes_t max_frames_in;
};
// channel configuration
class ChannelConfig {
public:
ChannelConfig (ExportGraphBuilder & parent) : parent (parent) {}
void init (FileSpec const & new_config, ChannelMap & channel_map);
void add_child (FileSpec const & new_config);
bool operator== (FileSpec const & other_config) const;
private:
typedef boost::shared_ptr<AudioGrapher::Interleaver<Sample> > InterleaverPtr;
ExportGraphBuilder & parent;
FileSpec config;
std::list<SilenceHandler> children;
InterleaverPtr interleaver;
nframes_t max_frames;
};
Session const & session;
// Roots for export processor trees
typedef std::list<ChannelConfig> ChannelConfigList;
ChannelConfigList channel_configs;
// The sources of all data, each channel is read only once
ChannelMap channels;
Sample * process_buffer;
nframes_t process_buffer_frames;
Glib::ThreadPool thread_pool;
};
} // namespace ARDOUR
#endif /* __ardour_export_graph_builder_h__ */

View file

@ -27,8 +27,8 @@
#include <boost/shared_ptr.hpp>
#include "ardour/session.h"
#include "ardour/ardour.h"
#include "ardour/session.h"
#include "ardour/types.h"
namespace ARDOUR
@ -38,11 +38,11 @@ class ExportTimespan;
class ExportChannelConfiguration;
class ExportFormatSpecification;
class ExportFilename;
class ExportProcessor;
class ExportGraphBuilder;
class ExportElementFactory
{
protected:
public:
typedef boost::shared_ptr<ExportTimespan> TimespanPtr;
typedef boost::shared_ptr<ExportChannelConfiguration> ChannelConfigPtr;
typedef boost::shared_ptr<ExportFormatSpecification> FormatPtr;
@ -70,29 +70,30 @@ class ExportElementFactory
class ExportHandler : public ExportElementFactory
{
public:
struct FileSpec {
FileSpec() {}
FileSpec (ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
: channel_config (channel_config)
, format (format)
, filename (filename)
{}
ChannelConfigPtr channel_config;
FormatPtr format;
FilenamePtr filename;
};
private:
/* Stuff for export configs
* The multimap maps timespans to file specifications
*/
struct FileSpec {
FileSpec (ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename) :
channel_config (channel_config),
format (format),
filename (filename)
{}
ChannelConfigPtr channel_config;
FormatPtr format;
FilenamePtr filename;
};
typedef std::pair<TimespanPtr, FileSpec> ConfigPair;
typedef std::multimap<TimespanPtr, FileSpec> ConfigMap;
typedef boost::shared_ptr<ExportProcessor> ProcessorPtr;
typedef boost::shared_ptr<ExportGraphBuilder> GraphBuilderPtr;
typedef boost::shared_ptr<ExportStatus> StatusPtr;
private:
@ -112,16 +113,26 @@ class ExportHandler : public ExportElementFactory
private:
Session & session;
ProcessorPtr processor;
GraphBuilderPtr graph_builder;
StatusPtr export_status;
ConfigMap config_map;
bool realtime;
PBD::ScopedConnection files_written_connection;
PBD::ScopedConnection export_read_finished_connection;
std::list<Glib::ustring> files_written;
void add_file (const Glib::ustring&);
/* Timespan management */
void start_timespan ();
int process_timespan (nframes_t frames);
void finish_timespan ();
typedef std::pair<ConfigMap::iterator, ConfigMap::iterator> TimespanBounds;
TimespanPtr current_timespan;
TimespanBounds timespan_bounds;
PBD::ScopedConnection process_connection;
sframes_t process_position;
PBD::ScopedConnection export_read_finished_connection;
/* CD Marker stuff */
@ -141,13 +152,13 @@ class ExportHandler : public ExportElementFactory
/* Track info */
uint32_t track_number;
nframes_t track_position;
nframes_t track_duration;
nframes_t track_start_frame;
sframes_t track_position;
sframes_t track_duration;
sframes_t track_start_frame;
/* Index info */
uint32_t index_number;
nframes_t index_position;
sframes_t index_position;
};
@ -162,23 +173,10 @@ class ExportHandler : public ExportElementFactory
void write_index_info_cue (CDMarkerStatus & status);
void write_index_info_toc (CDMarkerStatus & status);
void frames_to_cd_frames_string (char* buf, nframes_t when);
void frames_to_cd_frames_string (char* buf, sframes_t when);
int cue_tracknum;
int cue_indexnum;
/* Timespan management */
void start_timespan ();
void finish_timespan ();
void timespan_thread_finished ();
typedef std::pair<ConfigMap::iterator, ConfigMap::iterator> TimespanBounds;
TimespanPtr current_timespan;
ConfigMap::iterator current_map_it;
TimespanBounds timespan_bounds;
PBD::ScopedConnection channel_config_connection;
};
} // namespace ARDOUR

View file

@ -1,127 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_export_processor_h__
#define __ardour_export_processor_h__
#include <vector>
#include <boost/smart_ptr.hpp>
#include <glibmm/ustring.h>
#include "ardour/graph.h"
#include "ardour/export_file_io.h"
#include "ardour/export_utilities.h"
namespace ARDOUR
{
class Session;
class ExportStatus;
class ExportFilename;
class ExportFormatSpecification;
/// Sets up components for export post processing
class ExportProcessor
{
private:
/* Typedefs for utility processors */
typedef boost::shared_ptr<SampleRateConverter> SRConverterPtr;
typedef boost::shared_ptr<PeakReader> PReaderPtr;
typedef boost::shared_ptr<Normalizer> NormalizerPtr;
typedef boost::shared_ptr<ExportTempFile> TempFilePtr;
typedef GraphSink<float> FloatSink;
typedef boost::shared_ptr<FloatSink> FloatSinkPtr;
typedef std::vector<FloatSinkPtr> FloatSinkVect;
typedef boost::shared_ptr<ExportFilename> FilenamePtr;
typedef boost::shared_ptr<ExportFormatSpecification const> FormatPtr;
typedef boost::shared_ptr<ExportFileWriter> FileWriterPtr;
typedef std::list<FileWriterPtr> FileWriterList;
public:
ExportProcessor (Session & session);
~ExportProcessor ();
ExportProcessor * copy() { return new ExportProcessor (session); }
/// Do preparations for exporting
/** Should be called before process
* @return 0 on success
*/
int prepare (FormatPtr format, FilenamePtr fname, uint32_t chans, bool split = false, nframes_t start = 0);
/// Process data
/** @param frames frames to process @return frames written **/
nframes_t process (float * data, nframes_t frames);
/** should be called after all data is given to process **/
void prepare_post_processors ();
void write_files ();
static PBD::Signal1<void,const Glib::ustring&> WritingFile;
private:
void reset ();
Session & session;
boost::shared_ptr<ExportStatus> status;
/* these are initalized in prepare() */
FilenamePtr filename;
NormalizerPtr normalizer;
SRConverterPtr src;
PReaderPtr peak_reader;
TempFilePtr temp_file;
FloatSinkVect file_sinks;
FileWriterList writer_list;
/* general info */
uint32_t channels;
nframes_t blocksize;
nframes_t frame_rate;
/* Processing */
bool tag;
bool broadcast_info;
bool split_files;
bool normalize;
bool trim_beginning;
bool trim_end;
nframes_t silence_beginning;
nframes_t silence_end;
/* Progress info */
nframes_t temp_file_position;
nframes_t temp_file_length;
};
} // namespace ARDOUR
#endif /* __ardour_export_processor_h__ */

View file

@ -264,6 +264,9 @@ class ExportProfileManager
ChannelConfigStatePtr channel_config_state,
FormatStatePtr format_state,
FilenameStatePtr filename_state);
bool check_format (FormatPtr format, uint32_t channels);
bool check_sndfile_format (FormatPtr format, unsigned int channels);
/* Utilities */

View file

@ -39,9 +39,6 @@ class ExportTempFile;
class ExportTimespan
{
private:
typedef boost::shared_ptr<ExportTempFile> TempFilePtr;
typedef std::pair<ExportChannelPtr, TempFilePtr> ChannelFilePair;
typedef std::map<ExportChannelPtr, TempFilePtr> TempFileMap;
typedef boost::shared_ptr<ExportStatus> ExportStatusPtr;
private:
@ -57,20 +54,6 @@ class ExportTimespan
Glib::ustring range_id () const { return _range_id; }
void set_range_id (Glib::ustring range_id) { _range_id = range_id; }
/// Registers a channel to be read when export starts rolling
void register_channel (ExportChannelPtr channel);
/// "Rewinds" the tempfiles to start reading the beginnings again
void rewind ();
/// Reads data from the tempfile belonging to channel into data
nframes_t get_data (float * data, nframes_t frames, ExportChannelPtr channel);
/// Reads data from each channel and writes to tempfile
int process (nframes_t frames);
PBD::ScopedConnection process_connection;
void set_range (nframes_t start, nframes_t end);
nframes_t get_length () const { return end_frame - start_frame; }
nframes_t get_start () const { return start_frame; }
@ -85,8 +68,6 @@ class ExportTimespan
nframes_t position;
nframes_t frame_rate;
TempFileMap filemap;
Glib::ustring _name;
Glib::ustring _range_id;

View file

@ -1,146 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_export_utilities_h__
#define __ardour_export_utilities_h__
#include <samplerate.h>
#include "ardour/graph.h"
#include "ardour/types.h"
#include "ardour/ardour.h"
#include "ardour/export_format_base.h"
#include "ardour/runtime_functions.h"
namespace ARDOUR
{
/* Processors */
/* Sample rate converter */
class SampleRateConverter : public GraphSinkVertex<float, float>
{
public:
SampleRateConverter (uint32_t channels, nframes_t in_rate, nframes_t out_rate, int quality);
~SampleRateConverter ();
protected:
nframes_t process (float * data, nframes_t frames);
private:
bool active;
uint32_t channels;
nframes_t leftover_frames;
nframes_t max_leftover_frames;
nframes_t frames_in;
nframes_t frames_out;
float * data_in;
float * leftover_data;
float * data_out;
nframes_t data_out_size;
SRC_DATA src_data;
SRC_STATE* src_state;
};
/* Sample format converter */
template <typename TOut>
class SampleFormatConverter : public GraphSinkVertex<float, TOut>
{
public:
SampleFormatConverter (uint32_t channels, ExportFormatBase::DitherType type = ExportFormatBase::D_None, int data_width_ = 0);
~SampleFormatConverter ();
void set_clip_floats (bool yn) { clip_floats = yn; }
protected:
nframes_t process (float * data, nframes_t frames);
private:
uint32_t channels;
int data_width;
GDither dither;
nframes_t data_out_size;
TOut * data_out;
bool clip_floats;
};
/* Peak reader */
class PeakReader : public GraphSinkVertex<float, float>
{
public:
PeakReader (uint32_t channels) : channels (channels), peak (0) {}
~PeakReader () {}
float get_peak () { return peak; }
protected:
nframes_t process (float * data, nframes_t frames)
{
peak = compute_peak (data, channels * frames, peak);
return piped_to->write (data, frames);
}
private:
uint32_t channels;
float peak;
};
/* Normalizer */
class Normalizer : public GraphSinkVertex<float, float>
{
public:
Normalizer (uint32_t channels, float target_dB);
~Normalizer ();
void set_peak (float peak);
protected:
nframes_t process (float * data, nframes_t frames);
private:
uint32_t channels;
bool enabled;
gain_t target;
gain_t gain;
};
/* Other */
class NullSink : public GraphSink<float>
{
public:
nframes_t write (float * /*data*/, nframes_t frames) { return frames; }
};
} // namespace ARDOUR
#endif /* __ardour_export_utilities_h__ */

View file

@ -1,101 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_graph_h__
#define __ardour_graph_h__
#include <boost/shared_ptr.hpp>
#include "ardour/types.h"
namespace ARDOUR
{
/// Takes data in
template <typename T>
class GraphSink {
public:
GraphSink () : end_of_input (false) {}
virtual ~GraphSink () { end_of_input = false; }
// writes data and return number of frames written
virtual nframes_t write (T * data, nframes_t frames) = 0;
// Notifies end of input. All left over data must be written at this stage
virtual void set_end_of_input (bool state = true)
{
end_of_input = state;
}
protected:
bool end_of_input;
};
/// is a source for data
template <typename T>
class GraphSource {
public:
GraphSource () {}
virtual ~GraphSource () {}
virtual nframes_t read (T * data, nframes_t frames) = 0;
};
/// Takes data in, processes it and passes it on to another sink
template <typename TIn, typename TOut>
class GraphSinkVertex : public GraphSink<TIn> {
public:
GraphSinkVertex () {}
virtual ~GraphSinkVertex () {}
void pipe_to (boost::shared_ptr<GraphSink<TOut> > dest) {
piped_to = dest;
}
nframes_t write (TIn * data, nframes_t frames)
{
if (!piped_to) {
return -1;
}
return process (data, frames);
}
virtual void set_end_of_input (bool state = true)
{
if (!piped_to) {
return;
}
piped_to->set_end_of_input (state);
GraphSink<TIn>::end_of_input = state;
}
protected:
boost::shared_ptr<GraphSink<TOut> > piped_to;
/* process must process data,
use piped_to->write to write the data
and return number of frames written */
virtual nframes_t process (TIn * data, nframes_t frames) = 0;
};
} // namespace ARDOUR
#endif /* __ardour_graph_h__ */

View file

@ -809,7 +809,7 @@ AudioEngine::disconnect (Port& port)
}
ARDOUR::nframes_t
AudioEngine::frame_rate ()
AudioEngine::frame_rate () const
{
GET_PRIVATE_JACK_POINTER_RET (_jack,0);
if (_frame_rate == 0) {
@ -827,7 +827,7 @@ AudioEngine::raw_buffer_size (DataType t)
}
ARDOUR::nframes_t
AudioEngine::frames_per_cycle ()
AudioEngine::frames_per_cycle () const
{
GET_PRIVATE_JACK_POINTER_RET (_jack,0);
if (_buffer_size == 0) {

View file

@ -22,7 +22,6 @@
#include "ardour/export_handler.h"
#include "ardour/export_filename.h"
#include "ardour/export_processor.h"
#include "ardour/export_timespan.h"
#include "ardour/audio_port.h"
@ -43,15 +42,11 @@ namespace ARDOUR
ExportChannelConfiguration::ExportChannelConfiguration (Session & session) :
session (session),
writer_thread (*this),
status (session.get_export_status ()),
files_written (false),
split (false)
{
}
XMLNode &
ExportChannelConfiguration::get_state ()
{
@ -104,121 +99,4 @@ ExportChannelConfiguration::all_channels_have_ports () const
return true;
}
bool
ExportChannelConfiguration::write_files (boost::shared_ptr<ExportProcessor> new_processor)
{
if (files_written || writer_thread.running) {
return false;
}
files_written = true;
if (!timespan) {
throw ExportFailed (X_("Programming error: No timespan registered to channel configuration when requesting files to be written"));
}
/* Take a local copy of the processor to be used in the thread that is created below */
processor.reset (new_processor->copy());
/* Create new thread for post processing */
pthread_create (&writer_thread.thread, 0, _write_files, &writer_thread);
writer_thread.running = true;
pthread_detach (writer_thread.thread);
return true;
}
void
ExportChannelConfiguration::write_file ()
{
timespan->rewind ();
nframes_t progress = 0;
nframes_t timespan_length = timespan->get_length();
nframes_t frames = 2048; // TODO good block size ?
nframes_t frames_read = 0;
float * channel_buffer = new float [frames];
float * file_buffer = new float [channels.size() * frames];
uint32_t channel_count = channels.size();
uint32_t channel;
do {
if (status->aborted()) { break; }
channel = 0;
for (ChannelList::iterator it = channels.begin(); it != channels.end(); ++it) {
/* Get channel data */
frames_read = timespan->get_data (channel_buffer, frames, *it);
/* Interleave into file buffer */
for (uint32_t i = 0; i < frames_read; ++i) {
file_buffer[channel + (channel_count * i)] = channel_buffer[i];
}
++channel;
}
progress += frames_read;
status->progress = (float) progress / timespan_length;
} while (processor->process (file_buffer, frames_read) > 0);
delete [] channel_buffer;
delete [] file_buffer;
}
void *
ExportChannelConfiguration::_write_files (void *arg)
{
SessionEvent::create_per_thread_pool ("exporter events", 64);
// cc can be trated like 'this'
WriterThread & cc (*((WriterThread *) arg));
try {
for (FileConfigList::iterator it = cc->file_configs.begin(); it != cc->file_configs.end(); ++it) {
if (cc->status->aborted()) {
break;
}
cc->processor->prepare (it->first, it->second, cc->channels.size(), cc->split, cc->timespan->get_start());
cc->write_file (); // Writes tempfile
cc->processor->prepare_post_processors ();
cc->processor->write_files();
}
} catch (ExportFailed & e) {
cc->status->abort (true);
}
cc.running = false;
cc->files_written = true;
cc->FilesWritten();
return 0; // avoid compiler warnings
}
void
ExportChannelConfiguration::register_with_timespan (TimespanPtr new_timespan)
{
timespan = new_timespan;
for (ChannelList::iterator it = channels.begin(); it != channels.end(); ++it) {
timespan->register_channel (*it);
}
}
void
ExportChannelConfiguration::unregister_all ()
{
timespan.reset();
processor.reset();
file_configs.clear();
files_written = false;
}
} // namespace ARDOUR

View file

@ -1,445 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include "ardour/export_file_io.h"
#include "ardour/export_failed.h"
#include "pbd/failed_constructor.h"
#include "i18n.h"
using namespace std;
using namespace Glib;
using namespace PBD;
namespace ARDOUR
{
/* SndfileWriterBase */
SndfileWriterBase::SndfileWriterBase (int channels, nframes_t samplerate, int format, string const & path) :
ExportFileWriter (path)
{
char errbuf[256];
sf_info.channels = channels;
sf_info.samplerate = samplerate;
sf_info.format = format;
if (!sf_format_check (&sf_info)) {
throw ExportFailed (X_("Invalid format given for SndfileWriter!"));
}
if (path.length() == 0) {
throw ExportFailed (X_("No output file specified for SndFileWriter"));
}
/* TODO add checks that the directory path exists, and also
check if we are overwriting an existing file...
*/
// Open file TODO make sure we have enough disk space for the output
if (path.compare ("temp")) {
if ((sndfile = sf_open (path.c_str(), SFM_WRITE, &sf_info)) == 0) {
sf_error_str (0, errbuf, sizeof (errbuf) - 1);
throw ExportFailed (string_compose(X_("Cannot open output file \"%1\" for SndFileWriter (%2)"), path, errbuf));
}
} else {
FILE * file;
if (!(file = tmpfile ())) {
throw ExportFailed (X_("Cannot open tempfile"));
}
sndfile = sf_open_fd (fileno(file), SFM_RDWR, &sf_info, true);
}
}
SndfileWriterBase::~SndfileWriterBase ()
{
sf_close (sndfile);
}
/* SndfileWriter */
template <typename T>
SndfileWriter<T>::SndfileWriter (int channels, nframes_t samplerate, int format, string const & path) :
SndfileWriterBase (channels, samplerate, format, path)
{
// init write function
init ();
}
template <>
void
SndfileWriter<float>::init ()
{
write_func = &sf_writef_float;
}
template <>
void
SndfileWriter<int>::init ()
{
write_func = &sf_writef_int;
}
template <>
void
SndfileWriter<short>::init ()
{
write_func = &sf_writef_short;
}
template <typename T>
nframes_t
SndfileWriter<T>::write (T * data, nframes_t frames)
{
char errbuf[256];
nframes_t written = (*write_func) (sndfile, data, frames);
if (written != frames) {
sf_error_str (sndfile, errbuf, sizeof (errbuf) - 1);
throw ExportFailed (string_compose(_("Could not write data to output file (%1)"), errbuf));
}
if (GraphSink<T>::end_of_input) {
sf_write_sync (sndfile);
}
return frames;
}
template class SndfileWriter<short>;
template class SndfileWriter<int>;
template class SndfileWriter<float>;
/* ExportTempFile */
ExportTempFile::ExportTempFile (uint32_t channels, nframes_t samplerate) :
SndfileWriter<float> (channels, samplerate, SF_FORMAT_RAW | SF_FORMAT_FLOAT | SF_ENDIAN_FILE, "temp"),
channels (channels),
reading (false),
start (0),
end (0),
beginning_processed (false),
end_processed (false),
silence_beginning (0),
silence_end (0),
end_set (false)
{
}
nframes_t
ExportTempFile::read (float * data, nframes_t frames)
{
nframes_t frames_read = 0;
nframes_t to_read = 0;
sf_count_t read_status = 0;
/* Initialize state at first read */
if (!reading) {
if (!end_set) {
end = get_length();
end_set = true;
}
locate_to (start);
reading = true;
}
/* Add silence to beginning */
if (silence_beginning > 0) {
if (silence_beginning >= frames) {
memset (data, 0, channels * frames * sizeof (float));
silence_beginning -= frames;
return frames;
}
memset (data, 0, channels * silence_beginning * sizeof (float));
frames_read += silence_beginning;
data += channels * silence_beginning;
silence_beginning = 0;
}
/* Read file, but don't read past end */
if (get_read_position() >= end) {
// File already read, do nothing!
} else {
if ((get_read_position() + (frames - frames_read)) > end) {
to_read = end - get_read_position();
} else {
to_read = frames - frames_read;
}
read_status = sf_readf_float (sndfile, data, to_read);
frames_read += to_read;
data += channels * to_read;
}
/* Check for errors */
if (read_status != to_read) {
throw ExportFailed (X_("Error reading temporary export file, export might not be complete!"));
}
/* Add silence at end */
if (silence_end > 0) {
to_read = frames - frames_read;
if (silence_end < to_read) {
to_read = silence_end;
}
memset (data, 0, channels * to_read * sizeof (float));
silence_end -= to_read;
frames_read += to_read;
}
return frames_read;
}
nframes_t
ExportTempFile::trim_beginning (bool yn)
{
if (!yn) {
start = 0;
return start;
}
if (!beginning_processed) {
process_beginning ();
}
start = silent_frames_beginning;
return start;
}
nframes_t
ExportTempFile::trim_end (bool yn)
{
end_set = true;
if (!yn) {
end = get_length();
return end;
}
if (!end_processed) {
process_end ();
}
end = silent_frames_end;
return end;
}
void
ExportTempFile::process_beginning ()
{
nframes_t frames = 1024;
nframes_t frames_read;
float * buf = new float[channels * frames];
nframes_t pos = 0;
locate_to (pos);
while ((frames_read = _read (buf, frames)) > 0) {
for (nframes_t i = 0; i < frames_read; i++) {
for (uint32_t chn = 0; chn < channels; ++chn) {
if (buf[chn + i * channels] != 0.0f) {
--pos;
goto out;
}
}
++pos;
}
}
out:
silent_frames_beginning = pos;
beginning_processed = true;
delete [] buf;
}
void
ExportTempFile::process_end ()
{
nframes_t frames = 1024;
nframes_t frames_read;
float * buf = new float[channels * frames];
nframes_t pos = get_length() - 1;
while (pos > 0) {
if (pos > frames) {
locate_to (pos - frames);
frames_read = _read (buf, frames);
} else {
// Last time reading
locate_to (0);
frames_read = _read (buf, pos);
}
for (nframes_t i = frames_read; i > 0; --i) {
for (uint32_t chn = 0; chn < channels; ++chn) {
if (buf[chn + (i - 1) * channels] != 0.0f) {
goto out;
}
}
--pos;
}
}
out:
silent_frames_end = pos;
end_processed = true;
delete [] buf;
}
void
ExportTempFile::set_silence_beginning (nframes_t frames)
{
silence_beginning = frames;
}
void
ExportTempFile::set_silence_end (nframes_t frames)
{
silence_end = frames;
}
sf_count_t
ExportTempFile::get_length ()
{
sf_count_t pos = get_position();
sf_count_t len = sf_seek (sndfile, 0, SEEK_END);
locate_to (pos);
return len;
}
sf_count_t
ExportTempFile::get_position ()
{
return sf_seek (sndfile, 0, SEEK_CUR);
}
sf_count_t
ExportTempFile::get_read_position ()
{
return sf_seek (sndfile, 0, SEEK_CUR | SFM_READ);
}
sf_count_t
ExportTempFile::locate_to (nframes_t frames)
{
return sf_seek (sndfile, frames, SEEK_SET);
}
sf_count_t
ExportTempFile::_read (float * data, nframes_t frames)
{
return sf_readf_float (sndfile, data, frames);
}
ExportFileFactory::FilePair
ExportFileFactory::create (FormatPtr format, uint32_t channels, ustring const & filename)
{
switch (format->type()) {
case ExportFormatBase::T_Sndfile:
return create_sndfile (format, channels, filename);
default:
throw ExportFailed (X_("Invalid format given for ExportFileFactory::create!"));
}
}
bool
ExportFileFactory::check (FormatPtr format, uint32_t channels)
{
switch (format->type()) {
case ExportFormatBase::T_Sndfile:
return check_sndfile (format, channels);
default:
throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
}
}
ExportFileFactory::FilePair
ExportFileFactory::create_sndfile (FormatPtr format, unsigned int channels, ustring const & filename)
{
typedef boost::shared_ptr<SampleFormatConverter<short> > ShortConverterPtr;
typedef boost::shared_ptr<SampleFormatConverter<int> > IntConverterPtr;
typedef boost::shared_ptr<SampleFormatConverter<float> > FloatConverterPtr;
typedef boost::shared_ptr<SndfileWriter<short> > ShortWriterPtr;
typedef boost::shared_ptr<SndfileWriter<int> > IntWriterPtr;
typedef boost::shared_ptr<SndfileWriter<float> > FloatWriterPtr;
int real_format = format->format_id() | format->sample_format() | format->endianness();
uint32_t data_width = sndfile_data_width (real_format);
if (data_width == 8 || data_width == 16) {
ShortConverterPtr sfc = ShortConverterPtr (new SampleFormatConverter<short> (channels, format->dither_type(), data_width));
ShortWriterPtr sfw = ShortWriterPtr (new SndfileWriter<short> (channels, format->sample_rate(), real_format, filename));
sfc->pipe_to (sfw);
return std::make_pair (boost::static_pointer_cast<FloatSink> (sfc), boost::static_pointer_cast<ExportFileWriter> (sfw));
} else if (data_width == 24 || data_width == 32) {
IntConverterPtr sfc = IntConverterPtr (new SampleFormatConverter<int> (channels, format->dither_type(), data_width));
IntWriterPtr sfw = IntWriterPtr (new SndfileWriter<int> (channels, format->sample_rate(), real_format, filename));
sfc->pipe_to (sfw);
return std::make_pair (boost::static_pointer_cast<FloatSink> (sfc), boost::static_pointer_cast<ExportFileWriter> (sfw));
}
FloatConverterPtr sfc = FloatConverterPtr (new SampleFormatConverter<float> (channels, format->dither_type(), data_width));
FloatWriterPtr sfw = FloatWriterPtr (new SndfileWriter<float> (channels, format->sample_rate(), real_format, filename));
sfc->pipe_to (sfw);
return std::make_pair (boost::static_pointer_cast<FloatSink> (sfc), boost::static_pointer_cast<ExportFileWriter> (sfw));
}
bool
ExportFileFactory::check_sndfile (FormatPtr format, unsigned int channels)
{
SF_INFO sf_info;
sf_info.channels = channels;
sf_info.samplerate = format->sample_rate ();
sf_info.format = format->format_id () | format->sample_format ();
return (sf_format_check (&sf_info) == SF_TRUE ? true : false);
}
} // namespace ARDOUR

View file

@ -0,0 +1,413 @@
#include "ardour/export_graph_builder.h"
#include "audiographer/interleaver.h"
#include "audiographer/normalizer.h"
#include "audiographer/peak_reader.h"
#include "audiographer/process_context.h"
#include "audiographer/sample_format_converter.h"
#include "audiographer/sndfile_writer.h"
#include "audiographer/sr_converter.h"
#include "audiographer/silence_trimmer.h"
#include "audiographer/threader.h"
#include "audiographer/tmp_file.h"
#include "audiographer/utils.h"
#include "ardour/audioengine.h"
#include "ardour/export_channel_configuration.h"
#include "ardour/export_filename.h"
#include "ardour/export_format_specification.h"
#include "ardour/sndfile_helpers.h"
#include "pbd/filesystem.h"
using namespace AudioGrapher;
namespace ARDOUR {
ExportGraphBuilder::ExportGraphBuilder (Session const & session)
: session (session)
, thread_pool (4) // FIXME thread amount to cores amount
{
process_buffer_frames = session.engine().frames_per_cycle();
process_buffer = new Sample[process_buffer_frames];
// TODO move and/or use global silent buffers
AudioGrapher::Utils::init_zeros<Sample> (process_buffer_frames);
}
ExportGraphBuilder::~ExportGraphBuilder ()
{
delete [] process_buffer;
// TODO see bove
AudioGrapher::Utils::free_resources();
}
int
ExportGraphBuilder::process (nframes_t frames, bool last_cycle)
{
for (ChannelMap::iterator it = channels.begin(); it != channels.end(); ++it) {
it->first->read (process_buffer, process_buffer_frames);
ProcessContext<Sample> context(process_buffer, process_buffer_frames, 1);
if (last_cycle) { context.set_flag (ProcessContext<Sample>::EndOfInput); }
it->second->process (context);
}
return 0;
}
void
ExportGraphBuilder::reset ()
{
channel_configs.clear ();
channels.clear ();
}
void
ExportGraphBuilder::add_config (FileSpec const & config)
{
for (ChannelConfigList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
if (*it == config) {
it->add_child (config);
return;
}
}
// No duplicate channel config found, create new one
channel_configs.push_back (ChannelConfig (*this));
ChannelConfig & c_config (channel_configs.back());
c_config.init (config, channels);
}
/* Encoder */
template <>
boost::shared_ptr<AudioGrapher::Sink<Sample> >
ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
{
config = new_config;
init_writer (float_writer);
return float_writer;
}
template <>
boost::shared_ptr<AudioGrapher::Sink<int> >
ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
{
config = new_config;
init_writer (int_writer);
return int_writer;
}
template <>
boost::shared_ptr<AudioGrapher::Sink<short> >
ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
{
config = new_config;
init_writer (short_writer);
return short_writer;
}
void
ExportGraphBuilder::Encoder::add_child (FileSpec const & new_config)
{
filenames.push_back (new_config.filename);
}
bool
ExportGraphBuilder::Encoder::operator== (FileSpec const & other_config) const
{
return get_real_format (config) == get_real_format (other_config);
}
int
ExportGraphBuilder::Encoder::get_real_format (FileSpec const & config)
{
ExportFormatSpecification & format = *config.format;
return format.format_id() | format.sample_format() | format.endianness();
}
template<typename T>
void
ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer)
{
unsigned channels = config.channel_config->get_n_chans();
int format = get_real_format (config);
Glib::ustring filename = config.filename->get_path (config.format);
writer.reset (new AudioGrapher::SndfileWriter<T> (channels, config.format->sample_rate(), format, filename));
writer->FileWritten.connect (sigc::mem_fun (*this, &ExportGraphBuilder::Encoder::copy_files));
}
void
ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
{
while (filenames.size()) {
FilenamePtr & filename = filenames.front();
PBD::sys::copy_file (orig_path, filename->get_path (config.format).c_str());
filenames.pop_front();
}
}
/* SFC */
ExportGraphBuilder::FloatSinkPtr
ExportGraphBuilder::SFC::init (FileSpec const & new_config, nframes_t max_frames)
{
config = new_config;
data_width = sndfile_data_width (Encoder::get_real_format (config));
unsigned channels = new_config.channel_config->get_n_chans();
if (data_width == 8 || data_width == 16) {
short_converter = ShortConverterPtr (new SampleFormatConverter<short> (channels));
short_converter->init (max_frames, config.format->dither_type(), data_width);
add_child (config);
return short_converter;
} else if (data_width == 24 || data_width == 32) {
int_converter = IntConverterPtr (new SampleFormatConverter<int> (channels));
int_converter->init (max_frames, config.format->dither_type(), data_width);
add_child (config);
return int_converter;
} else {
float_converter = FloatConverterPtr (new SampleFormatConverter<Sample> (channels));
float_converter->init (max_frames, config.format->dither_type(), data_width);
add_child (config);
return float_converter;
}
}
void
ExportGraphBuilder::SFC::add_child (FileSpec const & new_config)
{
for (std::list<Encoder>::iterator it = children.begin(); it != children.end(); ++it) {
if (*it == new_config) {
it->add_child (new_config);
return;
}
}
children.push_back (Encoder());
Encoder & encoder = children.back();
if (data_width == 8 || data_width == 16) {
short_converter->add_output (encoder.init<short> (new_config));
} else if (data_width == 24 || data_width == 32) {
int_converter->add_output (encoder.init<int> (new_config));
} else {
float_converter->add_output (encoder.init<Sample> (new_config));
}
}
bool
ExportGraphBuilder::SFC::operator== (FileSpec const & other_config) const
{
return config.format->sample_format() == other_config.format->sample_format();
}
/* Normalizer */
ExportGraphBuilder::FloatSinkPtr
ExportGraphBuilder::Normalizer::init (FileSpec const & new_config, nframes_t /*max_frames*/)
{
config = new_config;
max_frames_out = 4086; // TODO good chunk size
buffer.reset (new AllocatingProcessContext<Sample> (max_frames_out, config.channel_config->get_n_chans()));
peak_reader.reset (new PeakReader ());
normalizer.reset (new AudioGrapher::Normalizer (config.format->normalize_target()));
threader.reset (new Threader<Sample> (parent.thread_pool));
normalizer->alloc_buffer (max_frames_out);
normalizer->add_output (threader);
int format = ExportFormatBase::F_RAW | ExportFormatBase::SF_Float;
tmp_file.reset (new TmpFile<float> (config.channel_config->get_n_chans(),
config.format->sample_rate(), format));
tmp_file->FileWritten.connect (sigc::hide (sigc::mem_fun (*this, &Normalizer::start_post_processing)));
add_child (new_config);
peak_reader->add_output (tmp_file);
return peak_reader;
}
void
ExportGraphBuilder::Normalizer::add_child (FileSpec const & new_config)
{
for (std::list<SFC>::iterator it = children.begin(); it != children.end(); ++it) {
if (*it == new_config) {
it->add_child (new_config);
return;
}
}
children.push_back (SFC (parent));
threader->add_output (children.back().init (new_config, max_frames_out));
}
bool
ExportGraphBuilder::Normalizer::operator== (FileSpec const & other_config) const
{
return config.format->normalize() == other_config.format->normalize() &&
config.format->normalize_target() == other_config.format->normalize_target();
}
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);
}
}
/* SRC */
ExportGraphBuilder::FloatSinkPtr
ExportGraphBuilder::SRC::init (FileSpec const & new_config, nframes_t max_frames)
{
config = new_config;
converter.reset (new SampleRateConverter (new_config.channel_config->get_n_chans()));
ExportFormatSpecification & format = *new_config.format;
converter->init (parent.session.nominal_frame_rate(), format.sample_rate(), format.src_quality());
max_frames_out = converter->allocate_buffers (max_frames);
add_child (new_config);
return converter;
}
void
ExportGraphBuilder::SRC::add_child (FileSpec const & new_config)
{
if (new_config.format->normalize()) {
add_child_to_list (new_config, normalized_children);
} else {
add_child_to_list (new_config, children);
}
}
template<typename T>
void
ExportGraphBuilder::SRC::add_child_to_list (FileSpec const & new_config, std::list<T> & list)
{
for (typename std::list<T>::iterator it = list.begin(); it != list.end(); ++it) {
if (*it == new_config) {
it->add_child (new_config);
return;
}
}
list.push_back (T (parent));
converter->add_output (list.back().init (new_config, max_frames_out));
}
bool
ExportGraphBuilder::SRC::operator== (FileSpec const & other_config) const
{
return config.format->sample_rate() == other_config.format->sample_rate();
}
/* SilenceHandler */
ExportGraphBuilder::FloatSinkPtr
ExportGraphBuilder::SilenceHandler::init (FileSpec const & new_config, nframes_t max_frames)
{
config = new_config;
max_frames_in = max_frames;
nframes_t sample_rate = parent.session.nominal_frame_rate();
silence_trimmer.reset (new SilenceTrimmer<Sample>());
silence_trimmer->set_trim_beginning (config.format->trim_beginning());
silence_trimmer->set_trim_end (config.format->trim_end());
silence_trimmer->add_silence_to_beginning (config.format->silence_beginning(sample_rate));
silence_trimmer->add_silence_to_end (config.format->silence_end(sample_rate));
silence_trimmer->limit_output_size (max_frames_in);
add_child (new_config);
return silence_trimmer;
}
void
ExportGraphBuilder::SilenceHandler::add_child (FileSpec const & new_config)
{
for (std::list<SRC>::iterator it = children.begin(); it != children.end(); ++it) {
if (*it == new_config) {
it->add_child (new_config);
return;
}
}
children.push_back (SRC (parent));
silence_trimmer->add_output (children.back().init (new_config, max_frames_in));
}
bool
ExportGraphBuilder::SilenceHandler::operator== (FileSpec const & other_config) const
{
ExportFormatSpecification & format = *config.format;
ExportFormatSpecification & other_format = *other_config.format;
return (format.trim_beginning() == other_format.trim_beginning()) &&
(format.trim_end() == other_format.trim_end()) &&
(format.silence_beginning() == other_format.silence_beginning()) &&
(format.silence_end() == other_format.silence_end());
}
/* ChannelConfig */
void
ExportGraphBuilder::ChannelConfig::init (FileSpec const & new_config, ChannelMap & channel_map)
{
typedef ExportChannelConfiguration::ChannelList ChannelList;
config = new_config;
max_frames = parent.session.engine().frames_per_cycle();
interleaver.reset (new Interleaver<Sample> ());
interleaver->init (new_config.channel_config->get_n_chans(), max_frames);
ChannelList const & channel_list = config.channel_config->get_channels();
unsigned chan = 0;
for (ChannelList::const_iterator it = channel_list.begin(); it != channel_list.end(); ++it, ++chan) {
ChannelMap::iterator map_it = channel_map.find (*it);
if (map_it == channel_map.end()) {
std::pair<ChannelMap::iterator, bool> result_pair =
channel_map.insert (std::make_pair (*it, IdentityVertexPtr (new IdentityVertex<Sample> ())));
assert (result_pair.second);
map_it = result_pair.first;
}
map_it->second->add_output (interleaver->input (chan));
}
add_child (new_config);
}
void
ExportGraphBuilder::ChannelConfig::add_child (FileSpec const & new_config)
{
for (std::list<SilenceHandler>::iterator it = children.begin(); it != children.end(); ++it) {
if (*it == new_config) {
it->add_child (new_config);
return;
}
}
children.push_back (SilenceHandler (parent));
nframes_t max_frames_out = new_config.channel_config->get_n_chans() * max_frames;
interleaver->add_output (children.back().init (new_config, max_frames_out));
}
bool
ExportGraphBuilder::ChannelConfig::operator== (FileSpec const & other_config) const
{
return config.channel_config == other_config.channel_config;
}
} // namespace ARDOUR

View file

@ -1,5 +1,5 @@
/*
Copyright (C) 2008 Paul Davis
Copyright (C) 2008-2009 Paul Davis
Author: Sakari Bergen
This program is free software; you can redistribute it and/or modify
@ -27,12 +27,12 @@
#include "ardour/ardour.h"
#include "ardour/configuration.h"
#include "ardour/export_graph_builder.h"
#include "ardour/export_timespan.h"
#include "ardour/export_channel_configuration.h"
#include "ardour/export_status.h"
#include "ardour/export_format_specification.h"
#include "ardour/export_filename.h"
#include "ardour/export_processor.h"
#include "ardour/export_failed.h"
using namespace std;
@ -98,33 +98,19 @@ ExportElementFactory::add_filename_copy (FilenamePtr other)
/*** ExportHandler ***/
ExportHandler::ExportHandler (Session & session)
: ExportElementFactory (session)
, session (session)
, export_status (session.get_export_status ())
, realtime (false)
{
processor.reset (new ExportProcessor (session));
ExportHandler::ExportHandler (Session & session)
: ExportElementFactory (session)
, session (session)
, graph_builder (new ExportGraphBuilder (session))
, export_status (session.get_export_status ())
, realtime (false)
ExportProcessor::WritingFile.connect_same_thread (files_written_connection, boost::bind (&ExportHandler::add_file, this, _1));
{
}
ExportHandler::~ExportHandler ()
{
if (export_status->aborted()) {
for (std::list<Glib::ustring>::iterator it = files_written.begin(); it != files_written.end(); ++it) {
sys::remove (sys::path (*it));
}
}
channel_config_connection.disconnect();
files_written_connection.disconnect();
}
void
ExportHandler::add_file (const Glib::ustring& str)
{
files_written.push_back (str);
// TODO remove files that were written but not finsihed
}
bool
@ -137,21 +123,6 @@ ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel
return true;
}
/// Starts exporting the registered configurations
/** The following happens, when do_export is called:
* 1. Session is prepared in do_export
* 2. start_timespan is called, which then registers all necessary channel configs to a timespan
* 3. The timespan reads each unique channel into a tempfile and calls Session::stop_export when the end is reached
* 4. stop_export emits ExportReadFinished after stopping the transport, this ends up calling finish_timespan
* 5. finish_timespan registers all the relevant formats and filenames to relevant channel configurations
* 6. finish_timespan does a manual call to timespan_thread_finished, which gets the next channel configuration
* for the current timespan, calling write_files for it
* 7. write_files writes the actual export files, composing them from the individual channels from tempfiles and
* emits FilesWritten when it is done, which ends up calling timespan_thread_finished
* 8. Once all channel configs are written, a new timespan is started by calling start_timespan
* 9. When all timespans are written the session is taken out of export.
*/
void
ExportHandler::do_export (bool rt)
{
@ -172,6 +143,73 @@ ExportHandler::do_export (bool rt)
start_timespan ();
}
void
ExportHandler::start_timespan ()
{
export_status->timespan++;
if (config_map.empty()) {
export_status->running = false;
return;
}
current_timespan = config_map.begin()->first;
/* Register file configurations to graph builder */
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);
}
/* start export */
session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process_timespan, this, _1));
process_position = current_timespan->get_start();
session.start_audio_export (process_position, realtime);
}
int
ExportHandler::process_timespan (nframes_t frames)
{
/* update position */
nframes_t frames_to_read = 0;
sframes_t const start = current_timespan->get_start();
sframes_t const end = current_timespan->get_end();
bool const last_cycle = (process_position + frames >= end);
if (last_cycle) {
frames_to_read = end - process_position;
export_status->stop = true;
} else {
frames_to_read = frames;
}
process_position += frames_to_read;
export_status->progress = (float) (process_position - start) / (end - start);
/* Do actual processing */
return graph_builder->process (frames_to_read, last_cycle);
}
void
ExportHandler::finish_timespan ()
{
process_connection.disconnect ();
while (config_map.begin() != timespan_bounds.second) {
config_map.erase (config_map.begin());
}
start_timespan ();
}
/*** CD Marker sutff ***/
struct LocationSortByStart {
bool operator() (Location *a, Location *b) {
return a->start() < b->start();
@ -242,7 +280,7 @@ ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_forma
/* Start actual marker stuff */
nframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
sframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
status.track_position = last_start_time - timespan->get_start();
for (i = temp.begin(); i != temp.end(); ++i) {
@ -469,9 +507,9 @@ ExportHandler::write_index_info_toc (CDMarkerStatus & status)
}
void
ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when)
ExportHandler::frames_to_cd_frames_string (char* buf, sframes_t when)
{
nframes_t remainder;
sframes_t remainder;
nframes_t fr = session.nominal_frame_rate();
int mins, secs, frames;
@ -483,109 +521,4 @@ ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when)
sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
}
void
ExportHandler::start_timespan ()
{
export_status->timespan++;
if (config_map.empty()) {
export_status->finish ();
return;
}
current_timespan = config_map.begin()->first;
/* Register channel configs with timespan */
timespan_bounds = config_map.equal_range (current_timespan);
for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
it->second.channel_config->register_with_timespan (current_timespan);
}
/* connect stuff and start export */
session.ProcessExport.connect_same_thread (current_timespan->process_connection, boost::bind (&ExportTimespan::process, current_timespan, _1));
session.start_audio_export (current_timespan->get_start(), realtime);
}
void
ExportHandler::finish_timespan ()
{
current_timespan->process_connection.disconnect ();
/* Register formats and filenames to relevant channel configs */
export_status->total_formats = 0;
export_status->format = 0;
for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
export_status->total_formats++;
/* Setup filename */
it->second.filename->set_timespan (current_timespan);
it->second.filename->set_channel_config (it->second.channel_config);
/* Do actual registration */
ChannelConfigPtr chan_config = it->second.channel_config;
chan_config->register_file_config (it->second.format, it->second.filename);
}
/* Start writing files by doing a manual call to timespan_thread_finished */
current_map_it = timespan_bounds.first;
timespan_thread_finished ();
}
void
ExportHandler::timespan_thread_finished ()
{
channel_config_connection.disconnect();
export_read_finished_connection.disconnect ();
if (current_map_it != timespan_bounds.second) {
/* Get next configuration as long as no new export process is started */
ChannelConfigPtr cc = current_map_it->second.channel_config;
while (!cc->write_files(processor)) {
++current_map_it;
if (current_map_it == timespan_bounds.second) {
/* reached end of bounds, this call will end up in the else block below */
timespan_thread_finished ();
return;
}
cc = current_map_it->second.channel_config;
}
cc->FilesWritten.connect_same_thread (channel_config_connection, boost::bind (&ExportHandler::timespan_thread_finished, this));
++current_map_it;
} else { /* All files are written from current timespan, reset timespan and start new */
/* Unregister configs and remove configs with this timespan */
for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second;) {
it->second.channel_config->unregister_all ();
ConfigMap::iterator to_erase = it;
++it;
config_map.erase (to_erase);
}
/* Start new timespan */
start_timespan ();
}
}
} // namespace ARDOUR

View file

@ -1,295 +0,0 @@
/*
Copyright (C) 2008 Paul Davis
Author: Sakari Bergen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ardour/export_processor.h"
#include "pbd/error.h"
#include "pbd/filesystem.h"
#include "ardour/session.h"
#include "ardour/audiofile_tagger.h"
#include "ardour/broadcast_info.h"
#include "ardour/export_failed.h"
#include "ardour/export_filename.h"
#include "ardour/export_status.h"
#include "ardour/export_format_specification.h"
#include "i18n.h"
using namespace PBD;
namespace ARDOUR
{
PBD::Signal1<void,const Glib::ustring&> ExportProcessor::WritingFile;
ExportProcessor::ExportProcessor (Session & session) :
session (session),
status (session.get_export_status()),
blocksize (session.get_block_size()),
frame_rate (session.frame_rate())
{
reset ();
}
ExportProcessor::~ExportProcessor ()
{
}
void
ExportProcessor::reset ()
{
file_sinks.clear();
writer_list.clear();
filename.reset();
normalizer.reset();
src.reset();
peak_reader.reset();
temp_file.reset();
}
int
ExportProcessor::prepare (FormatPtr format, FilenamePtr fname, uint32_t chans, bool split, nframes_t start)
{
status->format++;
temp_file_length = 0;
/* Reset just to be sure all references are dropped */
reset();
/* Get parameters needed later on */
channels = chans;
split_files = split;
filename = fname;
tag = format->tag();
broadcast_info = format->has_broadcast_info();
normalize = format->normalize();
trim_beginning = format->trim_beginning();
trim_end = format->trim_end();
silence_beginning = format->silence_beginning();
silence_end = format->silence_end();
/* SRC */
src.reset (new SampleRateConverter (channels, frame_rate, format->sample_rate(), format->src_quality()));
/* Construct export pipe to temp file */
status->stage = export_PostProcess;
if (normalize) {
/* Normalizing => we need a normalizer, peak reader and tempfile */
normalizer.reset (new Normalizer (channels, format->normalize_target()));
peak_reader.reset (new PeakReader (channels));
temp_file.reset (new ExportTempFile (channels, format->sample_rate()));
src->pipe_to (peak_reader);
peak_reader->pipe_to (temp_file);
} else if (trim_beginning || trim_end) {
/* Not normalizing, but silence will be trimmed => need for a tempfile */
temp_file.reset (new ExportTempFile (channels, format->sample_rate()));
src->pipe_to (temp_file);
} else {
/* Due to complexity and time running out, a tempfile will be created for this also... */
temp_file.reset (new ExportTempFile (channels, format->sample_rate()));
src->pipe_to (temp_file);
}
/* Ensure directory exists */
sys::path folder (filename->get_folder());
if (!sys::exists (folder)) {
if (!sys::create_directory (folder)) {
throw ExportFailed (X_("sys::create_directory failed for export dir"));
}
}
/* prep file sinks */
if (split) {
filename->include_channel = true;
for (uint32_t chn = 1; chn <= channels; ++chn) {
filename->set_channel (chn);
ExportFileFactory::FilePair pair = ExportFileFactory::create (format, 1, filename->get_path (format));
file_sinks.push_back (pair.first);
writer_list.push_back (pair.second);
WritingFile (filename->get_path (format));
}
} else {
ExportFileFactory::FilePair pair = ExportFileFactory::create (format, channels, filename->get_path (format));
file_sinks.push_back (pair.first);
writer_list.push_back (pair.second);
WritingFile (filename->get_path (format));
}
/* Set position info */
nframes_t start_position = ((double) format->sample_rate() / frame_rate) * start + 0.5;
for (FileWriterList::iterator it = writer_list.begin(); it != writer_list.end(); ++it) {
(*it)->set_position (start_position);
}
/* set broadcast info if necessary */
if (broadcast_info) {
for (FileWriterList::iterator it = writer_list.begin(); it != writer_list.end(); ++it) {
BroadcastInfo bci;
bci.set_from_session (session, (*it)->position());
boost::shared_ptr<SndfileWriterBase> sndfile_ptr;
if ((sndfile_ptr = boost::dynamic_pointer_cast<SndfileWriterBase> (*it))) {
if (!bci.write_to_file (sndfile_ptr->get_sndfile())) {
std::cerr << bci.get_error() << std::endl;
}
} else {
if (!bci.write_to_file ((*it)->filename())) {
std::cerr << bci.get_error() << std::endl;
}
}
}
}
return 0;
}
nframes_t
ExportProcessor::process (float * data, nframes_t frames)
{
nframes_t frames_written = src->write (data, frames);
temp_file_length += frames_written;
return frames_written;
}
void
ExportProcessor::prepare_post_processors ()
{
/* Set end of input and do last write */
float dummy;
src->set_end_of_input ();
src->write (&dummy, 0);
/* Trim and add silence */
temp_file->trim_beginning (trim_beginning);
temp_file->trim_end (trim_end);
temp_file->set_silence_beginning (silence_beginning);
temp_file->set_silence_end (silence_end);
/* Set up normalizer */
if (normalize) {
normalizer->set_peak (peak_reader->get_peak ());
}
}
void
ExportProcessor::write_files ()
{
/* Write to disk */
status->stage = export_Write;
temp_file_position = 0;
uint32_t buffer_size = 4096; // TODO adjust buffer size?
float * buf = new float[channels * buffer_size];
int frames_read;
FloatSinkPtr disk_sink;
if (normalize) {
disk_sink = boost::dynamic_pointer_cast<FloatSink> (normalizer);
normalizer->pipe_to (file_sinks[0]);
} else {
disk_sink = file_sinks[0];
}
if (split_files) {
/* Get buffers for each channel separately */
std::vector<float *> chan_bufs;
for (uint32_t i = 0; i < channels; ++i) {
chan_bufs.push_back(new float[buffer_size]);
}
/* de-interleave and write files */
while ((frames_read = temp_file->read (buf, buffer_size)) > 0) {
for (uint32_t channel = 0; channel < channels; ++channel) {
for (uint32_t i = 0; i < buffer_size; ++i) {
chan_bufs[channel][i] = buf[channel + (channels * i)];
}
if (normalize) {
normalizer->pipe_to (file_sinks[channel]);
} else {
disk_sink = file_sinks[channel];
}
disk_sink->write (chan_bufs[channel], frames_read);
}
if (status->aborted()) { break; }
temp_file_position += frames_read;
status->progress = (float) temp_file_position / temp_file_length;
}
/* Clean up */
for (std::vector<float *>::iterator it = chan_bufs.begin(); it != chan_bufs.end(); ++it) {
delete[] *it;
}
} else {
while ((frames_read = temp_file->read (buf, buffer_size)) > 0) {
disk_sink->write (buf, frames_read);
if (status->aborted()) { break; }
temp_file_position += frames_read;
status->progress = (float) temp_file_position / temp_file_length;
}
}
delete [] buf;
/* Tag files if necessary and send exported signal */
for (FileWriterList::iterator it = writer_list.begin(); it != writer_list.end(); ++it) {
if (tag) {
AudiofileTagger::tag_file ((*it)->filename(), session.metadata());
}
session.Exported ((*it)->filename(), session.name());
}
}
}; // namespace ARDOUR

View file

@ -28,13 +28,13 @@
#include "pbd/convert.h"
#include "ardour/export_profile_manager.h"
#include "ardour/export_file_io.h"
#include "ardour/export_format_specification.h"
#include "ardour/export_timespan.h"
#include "ardour/export_channel_configuration.h"
#include "ardour/export_filename.h"
#include "ardour/export_preset.h"
#include "ardour/export_handler.h"
#include "ardour/export_failed.h"
#include "ardour/filename_extensions.h"
#include "ardour/session.h"
@ -724,7 +724,7 @@ ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings,
warnings->errors.push_back (_("No format selected!"));
} else if (!channel_config->get_n_chans()) {
warnings->errors.push_back (_("All channels are empty!"));
} else if (!ExportFileFactory::check (format, channel_config->get_n_chans())) {
} else if (!check_format (format, channel_config->get_n_chans())) {
warnings->errors.push_back (_("One or more of the selected formats is not compatible with this system!"));
} else if (format->channel_limit() < channel_config->get_n_chans()) {
warnings->errors.push_back
@ -766,4 +766,27 @@ ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings,
}
}
bool
ExportProfileManager::check_format (FormatPtr format, uint32_t channels)
{
switch (format->type()) {
case ExportFormatBase::T_Sndfile:
return check_sndfile_format (format, channels);
default:
throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
}
}
bool
ExportProfileManager::check_sndfile_format (FormatPtr format, unsigned int channels)
{
SF_INFO sf_info;
sf_info.channels = channels;
sf_info.samplerate = format->sample_rate ();
sf_info.format = format->format_id () | format->sample_format ();
return (sf_format_check (&sf_info) == SF_TRUE ? true : false);
}
}; // namespace ARDOUR

View file

@ -22,7 +22,6 @@
#include "ardour/export_channel_configuration.h"
#include "ardour/export_filename.h"
#include "ardour/export_file_io.h"
#include "ardour/export_failed.h"
namespace ARDOUR
@ -41,33 +40,6 @@ ExportTimespan::~ExportTimespan ()
{
}
void
ExportTimespan::register_channel (ExportChannelPtr channel)
{
TempFilePtr ptr (new ExportTempFile (1, frame_rate));
ChannelFilePair pair (channel, ptr);
filemap.insert (pair);
}
void
ExportTimespan::rewind ()
{
for (TempFileMap::iterator it = filemap.begin(); it != filemap.end(); ++it) {
it->second->reset_read ();
}
}
nframes_t
ExportTimespan::get_data (float * data, nframes_t frames, ExportChannelPtr channel)
{
TempFileMap::iterator it = filemap.find (channel);
if (it == filemap.end()) {
throw ExportFailed (X_("Trying to get data from ExportTimespan for channel that was never registered!"));
}
return it->second->read (data, frames);
}
void
ExportTimespan::set_range (nframes_t start, nframes_t end)
{
@ -76,38 +48,4 @@ ExportTimespan::set_range (nframes_t start, nframes_t end)
end_frame = end;
}
int
ExportTimespan::process (nframes_t frames)
{
status->stage = export_ReadTimespan;
/* update position */
nframes_t frames_to_read;
if (position + frames <= end_frame) {
frames_to_read = frames;
} else {
frames_to_read = end_frame - position;
status->stop = true;
}
position += frames_to_read;
status->progress = (float) (position - start_frame) / (end_frame - start_frame);
/* Read channels from ports and save to tempfiles */
float * data = new float[frames_to_read];
for (TempFileMap::iterator it = filemap.begin(); it != filemap.end(); ++it) {
it->first->read (data, frames_to_read);
it->second->write (data, frames_to_read);
}
delete [] data;
return 0;
}
} // namespace ARDOUR

View file

@ -1,352 +0,0 @@
/*
Copyright (C) 1999-2008 Paul Davis
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* see gdither.cc for why we have to do this */
#define _ISOC9X_SOURCE 1
#define _ISOC99_SOURCE 1
#include <cmath>
#undef _ISOC99_SOURCE
#undef _ISOC9X_SOURCE
#undef __USE_SVID
#define __USE_SVID 1
#include <cstdlib>
#undef __USE_SVID
#include <unistd.h>
#include <inttypes.h>
#include <float.h>
/* ...*/
#include "ardour/export_utilities.h"
#include <string.h>
#include "ardour/export_failed.h"
#include "ardour/gdither.h"
#include "ardour/dB.h"
#include "pbd/failed_constructor.h"
#include "i18n.h"
using namespace PBD;
namespace ARDOUR
{
/* SampleRateConverter */
SampleRateConverter::SampleRateConverter (uint32_t channels, nframes_t in_rate, nframes_t out_rate, int quality) :
channels (channels),
leftover_frames (0),
max_leftover_frames (0),
frames_in (0),
frames_out(0),
data_in (0),
leftover_data (0),
data_out (0),
data_out_size (0),
src_state (0)
{
if (in_rate == out_rate) {
active = false;
return;
}
active = true;
int err;
if ((src_state = src_new (quality, channels, &err)) == 0) {
throw ExportFailed (string_compose (X_("Cannot initialize sample rate conversion: %1"), src_strerror (err)));
}
src_data.src_ratio = out_rate / (double) in_rate;
}
SampleRateConverter::~SampleRateConverter ()
{
if (src_state) {
src_delete (src_state);
}
delete [] data_out;
if (leftover_data) {
free (leftover_data);
}
}
nframes_t
SampleRateConverter::process (float * data, nframes_t frames)
{
if (!active) {
// Just pass it on...
return piped_to->write (data, frames);
}
/* Manage memory */
nframes_t out_samples_max = (nframes_t) ceil (frames * src_data.src_ratio * channels);
if (data_out_size < out_samples_max) {
delete[] data_out;
data_out = new float[out_samples_max];
src_data.data_out = data_out;
max_leftover_frames = 4 * frames;
leftover_data = (float *) realloc (leftover_data, max_leftover_frames * channels * sizeof (float));
if (!leftover_data) {
throw ExportFailed (X_("A memory allocation error occured during sample rate conversion"));
}
data_out_size = out_samples_max;
}
/* Do SRC */
data_in = data;
frames_in = frames;
int err;
int cnt = 0;
nframes_t frames_out_total = 0;
do {
src_data.output_frames = out_samples_max / channels;
src_data.end_of_input = end_of_input;
src_data.data_out = data_out;
if (leftover_frames > 0) {
/* input data will be in leftover_data rather than data_in */
src_data.data_in = leftover_data;
if (cnt == 0) {
/* first time, append new data from data_in into the leftover_data buffer */
memcpy (leftover_data + (leftover_frames * channels), data_in, frames_in * channels * sizeof(float));
src_data.input_frames = frames_in + leftover_frames;
} else {
/* otherwise, just use whatever is still left in leftover_data; the contents
were adjusted using memmove() right after the last SRC call (see
below)
*/
src_data.input_frames = leftover_frames;
}
} else {
src_data.data_in = data_in;
src_data.input_frames = frames_in;
}
++cnt;
if ((err = src_process (src_state, &src_data)) != 0) {
throw ExportFailed (string_compose ("An error occured during sample rate conversion: %1", src_strerror (err)));
}
frames_out = src_data.output_frames_gen;
leftover_frames = src_data.input_frames - src_data.input_frames_used;
if (leftover_frames > 0) {
if (leftover_frames > max_leftover_frames) {
error << _("warning, leftover frames overflowed, glitches might occur in output") << endmsg;
leftover_frames = max_leftover_frames;
}
memmove (leftover_data, (char *) (src_data.data_in + (src_data.input_frames_used * channels)),
leftover_frames * channels * sizeof(float));
}
nframes_t frames_written = piped_to->write (data_out, frames_out);
frames_out_total += frames_written;
} while (leftover_frames > frames_in);
return frames_out_total;
}
/* SampleFormatConverter */
template <typename TOut>
SampleFormatConverter<TOut>::SampleFormatConverter (uint32_t channels, ExportFormatBase::DitherType type, int data_width_) :
channels (channels),
data_width (data_width_),
dither (0),
data_out_size (0),
data_out (0),
clip_floats (false)
{
if (data_width != 24) {
data_width = sizeof (TOut) * 8;
}
GDitherSize dither_size = GDitherFloat;
switch (data_width) {
case 8:
dither_size = GDither8bit;
break;
case 16:
dither_size = GDither16bit;
break;
case 24:
dither_size = GDither32bit;
}
dither = gdither_new ((GDitherType) type, channels, dither_size, data_width);
}
template <typename TOut>
SampleFormatConverter<TOut>::~SampleFormatConverter ()
{
if (dither) {
gdither_free (dither);
}
delete[] data_out;
}
template <typename TOut>
nframes_t
SampleFormatConverter<TOut>::process (float * data, nframes_t frames)
{
/* Make sure we have enough memory allocated */
size_t data_size = channels * frames * sizeof (TOut);
if (data_size > data_out_size) {
delete[] data_out;
data_out = new TOut[data_size];
data_out_size = data_size;
}
/* Do conversion */
if (data_width < 32) {
for (uint32_t chn = 0; chn < channels; ++chn) {
gdither_runf (dither, chn, frames, data, data_out);
}
} else {
for (uint32_t chn = 0; chn < channels; ++chn) {
TOut * ob = data_out;
const double int_max = (float) INT_MAX;
const double int_min = (float) INT_MIN;
nframes_t i;
for (nframes_t x = 0; x < frames; ++x) {
i = chn + (x * channels);
if (data[i] > 1.0f) {
ob[i] = static_cast<TOut> (INT_MAX);
} else if (data[i] < -1.0f) {
ob[i] = static_cast<TOut> (INT_MIN);
} else {
if (data[i] >= 0.0f) {
ob[i] = lrintf (int_max * data[i]);
} else {
ob[i] = - lrintf (int_min * data[i]);
}
}
}
}
}
/* Write forward */
return GraphSinkVertex<float, TOut>::piped_to->write (data_out, frames);
}
template<>
nframes_t
SampleFormatConverter<float>::process (float * data, nframes_t frames)
{
if (clip_floats) {
for (nframes_t x = 0; x < frames * channels; ++x) {
if (data[x] > 1.0f) {
data[x] = 1.0f;
} else if (data[x] < -1.0f) {
data[x] = -1.0f;
}
}
}
return piped_to->write (data, frames);
}
template class SampleFormatConverter<short>;
template class SampleFormatConverter<int>;
template class SampleFormatConverter<float>;
/* Normalizer */
Normalizer::Normalizer (uint32_t channels, float target_dB) :
channels (channels),
enabled (false)
{
target = dB_to_coefficient (target_dB);
if (target == 1.0f) {
/* do not normalize to precisely 1.0 (0 dBFS), to avoid making it appear
that we may have clipped.
*/
target -= FLT_EPSILON;
}
}
Normalizer::~Normalizer ()
{
}
void
Normalizer::set_peak (float peak)
{
if (peak == 0.0f || peak == target) {
/* don't even try */
enabled = false;
} else {
enabled = true;
gain = target / peak;
}
}
nframes_t
Normalizer::process (float * data, nframes_t frames)
{
if (enabled) {
for (nframes_t i = 0; i < (channels * frames); ++i) {
data[i] *= gain;
}
}
return piped_to->write (data, frames);
}
};

View file

@ -25,10 +25,8 @@
#include "ardour/audioengine.h"
#include "ardour/butler.h"
#include "ardour/export_failed.h"
#include "ardour/export_file_io.h"
#include "ardour/export_handler.h"
#include "ardour/export_status.h"
#include "ardour/export_utilities.h"
#include "ardour/route.h"
#include "ardour/session.h"
@ -183,7 +181,8 @@ Session::process_export (nframes_t nframes)
ProcessExport (nframes);
} catch (ExportFailed e) {
} catch (std::exception & e) {
std::cout << e.what() << std::endl;
export_status->abort (true);
}
}

View file

@ -79,26 +79,23 @@ libardour_sources = [
'event_type_map.cc',
'export_channel.cc',
'export_channel_configuration.cc',
'export_file_io.cc',
'export_filename.cc',
'export_format_base.cc',
'export_format_manager.cc',
'export_format_specification.cc',
'export_formats.cc',
'export_graph_builder.cc',
'export_handler.cc',
'export_preset.cc',
'export_processor.cc',
'export_profile_manager.cc',
'export_status.cc',
'export_timespan.cc',
'export_utilities.cc',
'file_source.cc',
'filename_extensions.cc',
'filesystem_paths.cc',
'filter.cc',
'find_session.cc',
'gain.cc',
'gdither.cc',
'globals.cc',
'import.cc',
'internal_return.cc',
@ -268,7 +265,7 @@ def build(bld):
obj.name = 'libardour'
obj.target = 'ardour'
obj.uselib = 'GLIBMM GTHREAD AUBIO SIGCPP XML UUID JACK SNDFILE SAMPLERATE LRDF OSX COREAUDIO'
obj.uselib_local = 'libpbd libmidipp libevoral libvamphost libvampplugin libtaglib librubberband'
obj.uselib_local = 'libpbd libmidipp libevoral libvamphost libvampplugin libtaglib librubberband libaudiographer'
obj.vnum = LIBARDOUR_LIB_VERSION
obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3')
obj.cxxflags = ['-DPACKAGE="libardour3"']

340
libs/audiographer/COPYING Normal file
View file

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View file

@ -0,0 +1,54 @@
#ifndef AUDIOGRAPHER_CHUNKER_H
#define AUDIOGRAPHER_CHUNKER_H
#include "listed_source.h"
#include "sink.h"
#include <cstring>
namespace AudioGrapher
{
template<typename T>
class Chunker : public ListedSource<T>, public Sink<T>
{
public:
Chunker (nframes_t chunk_size)
: chunk_size (chunk_size)
, position (0)
{
buffer = new T[chunk_size];
}
~Chunker()
{
delete [] buffer;
}
void process (ProcessContext<T> const & context)
{
if (position + context.frames() < chunk_size) {
memcpy (&buffer[position], (float const *)context.data(), context.frames() * sizeof(T));
position += context.frames();
} else {
nframes_t const frames_to_copy = chunk_size - position;
memcpy (&buffer[position], context.data(), frames_to_copy * sizeof(T));
ProcessContext<T> c_out (context, buffer, chunk_size);
ListedSource<T>::output (c_out);
memcpy (buffer, &context.data()[frames_to_copy], (context.frames() - frames_to_copy) * sizeof(T));
position = context.frames() - frames_to_copy;
}
}
using Sink<T>::process;
private:
nframes_t chunk_size;
nframes_t position;
T * buffer;
};
} // namespace
#endif // AUDIOGRAPHER_CHUNKER_H

View file

@ -0,0 +1,78 @@
template<typename T>
DeInterleaver<T>::DeInterleaver()
: channels (0)
, max_frames (0)
, buffer (0)
{}
template<typename T>
void
DeInterleaver<T>::init (unsigned int num_channels, nframes_t max_frames_per_channel)
{
reset();
channels = num_channels;
max_frames = max_frames_per_channel;
buffer = new T[max_frames];
for (unsigned int i = 0; i < channels; ++i) {
outputs.push_back (OutputPtr (new IdentityVertex<T>));
}
}
template<typename T>
typename DeInterleaver<T>::SourcePtr
DeInterleaver<T>::output (unsigned int channel)
{
if (channel >= channels) {
throw Exception (*this, "channel out of range");
}
return boost::static_pointer_cast<Source<T> > (outputs[channel]);
}
template<typename T>
void
DeInterleaver<T>::process (ProcessContext<T> const & c)
{
nframes_t frames = c.frames();
T const * data = c.data();
if (frames == 0) { return; }
nframes_t const frames_per_channel = frames / channels;
if (c.channels() != channels) {
throw Exception (*this, "wrong amount of channels given to process()");
}
if (frames % channels != 0) {
throw Exception (*this, "wrong amount of frames given to process()");
}
if (frames_per_channel > max_frames) {
throw Exception (*this, "too many frames given to process()");
}
unsigned int channel = 0;
for (typename std::vector<OutputPtr>::iterator it = outputs.begin(); it != outputs.end(); ++it, ++channel) {
if (!*it) { continue; }
for (unsigned int i = 0; i < frames_per_channel; ++i) {
buffer[i] = data[channel + (channels * i)];
}
ProcessContext<T> c_out (c, buffer, frames_per_channel, 1);
(*it)->process (c_out);
}
}
template<typename T>
void
DeInterleaver<T>::reset ()
{
outputs.clear();
delete [] buffer;
buffer = 0;
channels = 0;
max_frames = 0;
}

View file

@ -0,0 +1,46 @@
#ifndef AUDIOGRAPHER_DEINTERLEAVER_H
#define AUDIOGRAPHER_DEINTERLEAVER_H
#include "types.h"
#include "source.h"
#include "sink.h"
#include "identity_vertex.h"
#include "exception.h"
#include <vector>
namespace AudioGrapher
{
template<typename T>
class DeInterleaver : public Sink<T>
{
private:
typedef boost::shared_ptr<IdentityVertex<T> > OutputPtr;
public:
DeInterleaver();
~DeInterleaver() { reset(); }
typedef boost::shared_ptr<Source<T> > SourcePtr;
void init (unsigned int num_channels, nframes_t max_frames_per_channel);
SourcePtr output (unsigned int channel);
void process (ProcessContext<T> const & c);
using Sink<T>::process;
private:
void reset ();
std::vector<OutputPtr> outputs;
unsigned int channels;
nframes_t max_frames;
T * buffer;
};
#include "deinterleaver-inl.h"
} // namespace
#endif // AUDIOGRAPHER_DEINTERLEAVER_H

View file

@ -0,0 +1,52 @@
#ifndef AUDIOGRAPHER_EXCEPTION_H
#define AUDIOGRAPHER_EXCEPTION_H
#include <exception>
#include <string>
#include <cxxabi.h>
#include <boost/format.hpp>
namespace AudioGrapher
{
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))
{}
virtual ~Exception () throw() { }
const char* what() const throw()
{
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;
};
} // namespace AudioGrapher
#endif // AUDIOGRAPHER_EXCEPTION_H

View file

@ -0,0 +1,21 @@
#ifndef AUDIOGRAPHER_IDENTITY_VERTEX_H
#define AUDIOGRAPHER_IDENTITY_VERTEX_H
#include "listed_source.h"
#include "sink.h"
namespace AudioGrapher
{
template<typename T>
class IdentityVertex : public ListedSource<T>, Sink<T>
{
public:
void process (ProcessContext<T> const & c) { ListedSource<T>::output(c); }
void process (ProcessContext<T> & c) { ListedSource<T>::output(c); }
};
} // namespace
#endif // AUDIOGRAPHER_IDENTITY_VERTEX_H

View file

@ -0,0 +1,92 @@
template<typename T>
Interleaver<T>::Interleaver()
: channels (0)
, max_frames (0)
, buffer (0)
{}
template<typename T>
void
Interleaver<T>::init (unsigned int num_channels, nframes_t max_frames_per_channel)
{
reset();
channels = num_channels;
max_frames = max_frames_per_channel;
buffer = new T[channels * max_frames];
for (unsigned int i = 0; i < channels; ++i) {
inputs.push_back (InputPtr (new Input (*this, i)));
}
}
template<typename T>
typename Source<T>::SinkPtr
Interleaver<T>::input (unsigned int channel)
{
if (channel >= channels) {
throw Exception (*this, "Channel out of range");
}
return boost::static_pointer_cast<Sink<T> > (inputs[channel]);
}
template<typename T>
void
Interleaver<T>::reset_channels ()
{
for (unsigned int i = 0; i < channels; ++i) {
inputs[i]->reset();
}
}
template<typename T>
void
Interleaver<T>::reset ()
{
inputs.clear();
delete [] buffer;
buffer = 0;
channels = 0;
max_frames = 0;
}
template<typename T>
void
Interleaver<T>::write_channel (ProcessContext<T> const & c, unsigned int channel)
{
if (c.frames() > max_frames) {
reset_channels();
throw Exception (*this, "Too many frames given to an input");
}
for (unsigned int i = 0; i < c.frames(); ++i) {
buffer[channel + (channels * i)] = c.data()[i];
}
nframes_t const ready_frames = ready_to_output();
if (ready_frames) {
ProcessContext<T> c_out (c, buffer, ready_frames, channels);
ListedSource<T>::output (c_out);
reset_channels ();
}
}
template<typename T>
nframes_t
Interleaver<T>::ready_to_output ()
{
nframes_t ready_frames = inputs[0]->frames();
if (!ready_frames) { return 0; }
for (unsigned int i = 1; i < channels; ++i) {
nframes_t const frames = inputs[i]->frames();
if (!frames) { return 0; }
if (frames != ready_frames) {
init (channels, max_frames);
throw Exception (*this, "Frames count out of sync");
}
}
return ready_frames * channels;
}

View file

@ -0,0 +1,71 @@
#ifndef AUDIOGRAPHER_INTERLEAVER_H
#define AUDIOGRAPHER_INTERLEAVER_H
#include "types.h"
#include "listed_source.h"
#include "sink.h"
#include "exception.h"
#include <vector>
#include <cmath>
namespace AudioGrapher
{
template<typename T>
class Interleaver : public ListedSource<T>
{
public:
Interleaver();
~Interleaver() { reset(); }
void init (unsigned int num_channels, nframes_t max_frames_per_channel);
typename Source<T>::SinkPtr input (unsigned int channel);
private:
class Input : public Sink<T>
{
public:
Input (Interleaver & parent, unsigned int channel)
: frames_written (0), parent (parent), channel (channel) {}
void process (ProcessContext<T> const & c)
{
if (c.channels() > 1) { throw Exception (*this, "Data input has more than on channel"); }
if (frames_written) { throw Exception (*this, "Input channels out of sync"); }
frames_written = c.frames();
parent.write_channel (c, channel);
}
using Sink<T>::process;
nframes_t frames() { return frames_written; }
void reset() { frames_written = 0; }
private:
nframes_t frames_written;
Interleaver & parent;
unsigned int channel;
};
void reset ();
void reset_channels ();
void write_channel (ProcessContext<T> const & c, unsigned int channel);
nframes_t ready_to_output();
void output();
typedef boost::shared_ptr<Input> InputPtr;
std::vector<InputPtr> inputs;
unsigned int channels;
nframes_t max_frames;
T * buffer;
};
#include "interleaver-inl.h"
} // namespace
#endif // AUDIOGRAPHER_INTERLEAVER_H

View file

@ -0,0 +1,50 @@
#ifndef AUDIOGRAPHER_LISTED_SOURCE_H
#define AUDIOGRAPHER_LISTED_SOURCE_H
#include "types.h"
#include "source.h"
#include <list>
namespace AudioGrapher
{
template<typename T>
class ListedSource : public Source<T>
{
public:
void add_output (typename Source<T>::SinkPtr output) { outputs.push_back(output); }
void clear_outputs () { outputs.clear(); }
void remove_output (typename Source<T>::SinkPtr output) { outputs.remove(output); }
protected:
typedef std::list<typename Source<T>::SinkPtr> SinkList;
/// Helper for derived classes
void output (ProcessContext<T> const & c)
{
for (typename SinkList::iterator i = outputs.begin(); i != outputs.end(); ++i) {
(*i)->process (c);
}
}
void output (ProcessContext<T> & c)
{
if (output_size_is_one()) {
// only one output, so we can keep this non-const
outputs.front()->process (c);
} else {
output (const_cast<ProcessContext<T> const &> (c));
}
}
inline bool output_size_is_one () { return (!outputs.empty() && ++outputs.begin() == outputs.end()); }
SinkList outputs;
};
} // namespace
#endif //AUDIOGRAPHER_LISTED_SOURCE_H

View file

@ -0,0 +1,82 @@
#ifndef AUDIOGRAPHER_NORMALIZER_H
#define AUDIOGRAPHER_NORMALIZER_H
#include "listed_source.h"
#include "sink.h"
#include "routines.h"
#include <cstring>
namespace AudioGrapher
{
class Normalizer : public ListedSource<float>, Sink<float>
{
public:
Normalizer (float target_dB)
: enabled (false)
, buffer (0)
, buffer_size (0)
{
target = pow (10.0f, target_dB * 0.05f);
}
~Normalizer()
{
delete [] buffer;
}
void set_peak (float peak)
{
if (peak == 0.0f || peak == target) {
/* don't even try */
enabled = false;
} else {
enabled = true;
gain = target / peak;
}
}
void alloc_buffer(nframes_t frames)
{
delete [] buffer;
buffer = new float[frames];
buffer_size = frames;
}
void process (ProcessContext<float> const & c)
{
if (c.frames() > buffer_size) {
throw Exception (*this, "Too many frames given to process()");
}
if (enabled) {
memcpy (buffer, c.data(), c.frames() * sizeof(float));
Routines::apply_gain_to_buffer (buffer, c.frames(), gain);
}
ProcessContext<float> c_out (c, buffer);
ListedSource<float>::output (c_out);
}
void process (ProcessContext<float> & c)
{
if (enabled) {
Routines::apply_gain_to_buffer (c.data(), c.frames(), gain);
}
ListedSource<float>::output(c);
}
private:
bool enabled;
float target;
float gain;
float * buffer;
nframes_t buffer_size;
};
} // namespace
#endif // AUDIOGRAPHER_NORMALIZER_H

View file

@ -0,0 +1,38 @@
#ifndef AUDIOGRAPHER_PEAK_READER_H
#define AUDIOGRAPHER_PEAK_READER_H
#include "listed_source.h"
#include "sink.h"
#include "routines.h"
namespace AudioGrapher
{
class PeakReader : public ListedSource<float>, public Sink<float>
{
public:
PeakReader() : peak (0.0) {}
float get_peak() { return peak; }
void reset() { peak = 0.0; }
void process (ProcessContext<float> const & c)
{
peak = Routines::compute_peak (c.data(), c.frames(), peak);
ListedSource<float>::output(c);
}
void process (ProcessContext<float> & c)
{
peak = Routines::compute_peak (c.data(), c.frames(), peak);
ListedSource<float>::output(c);
}
private:
float peak;
};
} // namespace
#endif // AUDIOGRAPHER_PEAK_READER_H

View file

@ -0,0 +1,154 @@
#ifndef AUDIOGRAPHER_PROCESS_CONTEXT_H
#define AUDIOGRAPHER_PROCESS_CONTEXT_H
#include "types.h"
#include <cstring>
namespace AudioGrapher
{
/**
* Processing context. Constness only applies to data, not flags
*/
template <typename T>
class ProcessContext {
public:
typedef FlagField::Flag Flag;
enum Flags {
EndOfInput = 0
};
public:
/// Basic constructor with data, frame and channel count
ProcessContext (T * data, nframes_t frames, ChannelCount channels)
: _data (data), _frames (frames), _channels (channels) {}
/// Normal copy constructor
ProcessContext (ProcessContext<T> const & other)
: _data (other._data), _frames (other._frames), _channels (other._channels), _flags (other._flags) {}
/// "Copy constructor" with unique data, frame and channel count, but copies flags
template<typename Y>
ProcessContext (ProcessContext<Y> const & other, T * data, nframes_t frames, ChannelCount channels)
: _data (data), _frames (frames), _channels (channels), _flags (other.flags()) {}
/// "Copy constructor" with unique data and frame count, but copies channel count and flags
template<typename Y>
ProcessContext (ProcessContext<Y> const & other, T * data, nframes_t frames)
: _data (data), _frames (frames), _channels (other.channels()), _flags (other.flags()) {}
/// "Copy constructor" with unique data, but copies frame and channel count + flags
template<typename Y>
ProcessContext (ProcessContext<Y> const & other, T * data)
: _data (data), _frames (other.frames()), _channels (other.channels()), _flags (other.flags()) {}
virtual ~ProcessContext () {}
/// \a data points to the array of data to process
inline T const * data() const { return _data; }
inline T * data() { return _data; }
/// \a frames tells how many frames the array pointed by data contains
inline nframes_t const & frames() const { return _frames; }
inline nframes_t & frames() { return _frames; }
/** \a channels tells how many interleaved channels \a data contains
* If \a channels is greater than 1, each channel contains \a frames / \a channels frames of data
*/
inline ChannelCount const & channels() const { return _channels; }
inline ChannelCount & channels() { return _channels; }
/// Returns the amount of frames per channel
inline nframes_t frames_per_channel() const { return _frames / _channels; }
/* Flags */
inline bool has_flag (Flag flag) const { return _flags.has (flag); }
inline void set_flag (Flag flag) const { _flags.set (flag); }
inline void remove_flag (Flag flag) const { _flags.remove (flag); }
inline FlagField const & flags () const { return _flags; }
protected:
T * const _data;
nframes_t _frames;
ChannelCount _channels;
mutable FlagField _flags;
};
/// A process context that allocates and owns it's data buffer
template <typename T>
struct AllocatingProcessContext : public ProcessContext<T>
{
/// Allocates uninitialized memory
AllocatingProcessContext (nframes_t frames, ChannelCount channels)
: ProcessContext<T> (new T[frames], frames, channels) {}
/// Copy constructor, copies data from other ProcessContext
AllocatingProcessContext (ProcessContext<T> const & other)
: ProcessContext<T> (other, new T[other._frames])
{ memcpy (ProcessContext<T>::_data, other._data, other._channels * other._frames * sizeof (T)); }
/// "Copy constructor" with uninitialized data, unique frame and channel count, but copies flags
template<typename Y>
AllocatingProcessContext (ProcessContext<Y> const & other, nframes_t frames, ChannelCount channels)
: ProcessContext<T> (other, new T[frames], frames, channels) {}
/// "Copy constructor" with uninitialized data, unique frame count, but copies channel count and flags
template<typename Y>
AllocatingProcessContext (ProcessContext<Y> const & other, nframes_t frames)
: ProcessContext<T> (other, new T[frames], frames, other.channels()) {}
/// "Copy constructor" uninitialized data, that copies frame and channel count + flags
template<typename Y>
AllocatingProcessContext (ProcessContext<Y> const & other)
: ProcessContext<T> (other, new T[other._frames]) {}
~AllocatingProcessContext () { delete [] ProcessContext<T>::_data; }
};
/// A wrapper for a const ProcesContext which can be created from const data
template <typename T>
class ConstProcessContext
{
public:
/// Basic constructor with data, frame and channel count
ConstProcessContext (T const * data, nframes_t frames, ChannelCount channels)
: context (const_cast<T *>(data), frames, channels) {}
/// Copy constructor from const ProcessContext
ConstProcessContext (ProcessContext<T> const & other)
: context (const_cast<ProcessContext<T> &> (other)) {}
/// "Copy constructor", with unique data, frame and channel count, but copies flags
template<typename ProcessContext>
ConstProcessContext (ProcessContext const & other, T const * data, nframes_t frames, ChannelCount channels)
: context (other, const_cast<T *>(data), frames, channels) {}
/// "Copy constructor", with unique data and frame count, but copies channel count and flags
template<typename ProcessContext>
ConstProcessContext (ProcessContext const & other, T const * data, nframes_t frames)
: context (other, const_cast<T *>(data), frames) {}
/// "Copy constructor", with unique data, but copies frame and channel count + flags
template<typename ProcessContext>
ConstProcessContext (ProcessContext const & other, T const * data)
: context (other, const_cast<T *>(data)) {}
inline operator ProcessContext<T> const & () { return context; }
inline ProcessContext<T> const & operator() () { return context; }
inline ProcessContext<T> const * operator& () { return &context; }
private:
ProcessContext<T> const context;
};
} // namespace
#endif // AUDIOGRAPHER_PROCESS_CONTEXT_H

View file

@ -0,0 +1,53 @@
#ifndef AUDIOGRAPHER_ROUTINES_H
#define AUDIOGRAPHER_ROUTINES_H
#include "types.h"
#include <cmath>
namespace AudioGrapher
{
class Routines
{
public:
typedef float (*compute_peak_t) (float const *, nframes_t, float);
typedef void (*apply_gain_to_buffer_t) (float *, nframes_t, float);
static void override_compute_peak (compute_peak_t func) { _compute_peak = func; }
static void override_apply_gain_to_buffer (apply_gain_to_buffer_t func) { _apply_gain_to_buffer = func; }
static inline float compute_peak (float const * data, nframes_t frames, float current_peak)
{
return (*_compute_peak) (data, frames, current_peak);
}
static inline void apply_gain_to_buffer (float * data, nframes_t frames, float gain)
{
(*_apply_gain_to_buffer) (data, frames, gain);
}
private:
static inline float default_compute_peak (float const * data, nframes_t frames, float current_peak)
{
for (nframes_t i = 0; i < frames; ++i) {
float abs = std::fabs(data[i]);
if (abs > current_peak) { current_peak = abs; }
}
return current_peak;
}
static inline void default_apply_gain_to_buffer (float * data, nframes_t frames, float gain)
{
for (nframes_t i = 0; i < frames; ++i) {
data[i] *= gain;
}
}
static compute_peak_t _compute_peak;
static apply_gain_to_buffer_t _apply_gain_to_buffer;
};
} // namespace
#endif // AUDIOGRAPHER_ROUTINES_H

View file

@ -0,0 +1,67 @@
#ifndef AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H
#define AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H
#include "listed_source.h"
#include "sink.h"
#include "gdither/gdither_types.h"
namespace AudioGrapher
{
/// Dither types from the gdither library
enum DitherType
{
D_None = GDitherNone, ///< No didtering
D_Rect = GDitherRect, ///< Rectangular dithering, i.e. white noise
D_Tri = GDitherTri, ///< Triangular dithering
D_Shaped = GDitherShaped ///< Actually noise shaping, only works for 46kHzish signals
};
/** Sample format converter that does dithering.
* This class can only convert floats to either \a float, \a int32_t, \a int16_t, or \a uint8_t
*/
template <typename TOut>
class SampleFormatConverter : public Sink<float>, public ListedSource<TOut>
{
public:
/** Constructor
* \param channels number of channels in stream
*/
SampleFormatConverter (uint32_t channels);
~SampleFormatConverter ();
/** Initialize and allocate buffers for processing.
* \param max_frames maximum number of frames that is allowed to be used in calls to \a process()
* \param type dither type from \a DitherType
* \param data_width data with in bits
* \note If the non-const version of process() is used with floats,
* there is no need to call this function.
*/
void init (nframes_t max_frames, int type, int data_width);
/// Set whether or not clipping to [-1.0, 1.0] should occur when TOut = float. Clipping is off by default
void set_clip_floats (bool yn) { clip_floats = yn; }
/// Processes data without modifying it
void process (ProcessContext<float> const & c_in);
/// This version is only different in the case when \a TOut = float, and float clipping is on.
void process (ProcessContext<float> & c_in);
private:
void reset();
void init_common(nframes_t max_frames); // not-template-specialized part of init
void check_frame_count(nframes_t frames);
uint32_t channels;
GDither dither;
nframes_t data_out_size;
TOut * data_out;
bool clip_floats;
};
} // namespace
#endif // AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H

View file

@ -0,0 +1,191 @@
#ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
#define AUDIOGRAPHER_SILENCE_TRIMMER_H
#include "listed_source.h"
#include "sink.h"
#include "exception.h"
#include "utils.h"
#include <cstring>
namespace AudioGrapher {
template<typename T>
class SilenceTrimmer : public ListedSource<T>, public Sink<T>
{
public:
SilenceTrimmer()
{
reset ();
}
void reset()
{
in_beginning = true;
in_end = false;
trim_beginning = false;
trim_end = false;
silence_frames = 0;
max_output_frames = 0;
add_to_beginning = 0;
add_to_end = 0;
}
void add_silence_to_beginning (nframes_t frames_per_channel)
{
if (!in_beginning) {
throw Exception(*this, "Tried to add silence to beginning after already outputting data");
}
add_to_beginning = frames_per_channel;
}
void add_silence_to_end (nframes_t frames_per_channel)
{
if (in_end) {
throw Exception(*this, "Tried to add silence to end after already reaching end");
}
add_to_end = frames_per_channel;
}
void set_trim_beginning (bool yn)
{
if (!in_beginning) {
throw Exception(*this, "Tried to set beginning trim after already outputting data");
}
trim_beginning = yn;
}
void set_trim_end (bool yn)
{
if (in_end) {
throw Exception(*this, "Tried to set end trim after already reaching end");
}
trim_end = yn;
}
void limit_output_size (nframes_t max_frames)
{
max_output_frames = max_frames;
}
void process (ProcessContext<T> const & c)
{
if (in_end) { throw Exception(*this, "process() after reacing end of input"); }
in_end = c.has_flag (ProcessContext<T>::EndOfInput);
nframes_t frame_index = 0;
if (in_beginning) {
bool has_data = true;
// only check silence if doing either of these
// This will set both has_data and frame_index
if (add_to_beginning || trim_beginning) {
has_data = find_first_non_zero_sample (c, frame_index);
}
// Added silence if there is silence to add
if (add_to_beginning) {
ConstProcessContext<T> c_copy (c);
if (has_data) { // There will be more output, so remove flag
c_copy().remove_flag (ProcessContext<T>::EndOfInput);
}
add_to_beginning *= c.channels();
output_silence_frames (c_copy, add_to_beginning);
}
// If we are not trimming the beginning, output everything
// Then has_data = true and frame_index = 0
// Otherwise these reflect the silence state
if (has_data) {
in_beginning = false;
ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
ListedSource<T>::output (c_out);
}
} else if (trim_end) { // Only check zero samples if trimming end
if (find_first_non_zero_sample (c, frame_index)) {
// context contains non-zero data
output_silence_frames (c, silence_frames); // flush intermediate silence
ListedSource<T>::output (c); // output rest of data
} else { // whole context is zero
silence_frames += c.frames();
}
} else { // no need to do anything special
ListedSource<T>::output (c);
}
// Finally if in end, add silence to end
if (in_end && add_to_end) {
add_to_end *= c.channels();
output_silence_frames (c, add_to_end, true);
}
}
using Sink<T>::process;
private:
bool find_first_non_zero_sample (ProcessContext<T> const & c, nframes_t & result_frame)
{
for (nframes_t i = 0; i < c.frames(); ++i) {
if (c.data()[i] != static_cast<T>(0.0)) {
result_frame = i;
// Round down to nearest interleaved "frame" beginning
result_frame -= result_frame % c.channels();
return true;
}
}
return false;
}
void output_silence_frames (ProcessContext<T> const & c, nframes_t & total_frames, bool adding_to_end = false)
{
nframes_t silence_buffer_size = Utils::get_zero_buffer_size<T>();
if (silence_buffer_size == 0) { throw Exception (*this, "Utils::init_zeros has not been called!"); }
bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
c.remove_flag (ProcessContext<T>::EndOfInput);
while (total_frames > 0) {
nframes_t frames = std::min (silence_buffer_size, total_frames);
if (max_output_frames) {
frames = std::min (frames, max_output_frames);
}
frames -= frames % c.channels();
total_frames -= frames;
ConstProcessContext<T> c_out (c, Utils::get_zeros<T>(frames), frames);
// boolean commentation :)
bool const no_more_silence_will_be_added = adding_to_end || (add_to_end == 0);
bool const is_last_frame_output_in_this_function = (total_frames == 0);
if (end_of_input && no_more_silence_will_be_added && is_last_frame_output_in_this_function) {
c_out().set_flag (ProcessContext<T>::EndOfInput);
}
ListedSource<T>::output (c_out);
}
}
bool in_beginning;
bool in_end;
bool trim_beginning;
bool trim_end;
nframes_t silence_frames;
nframes_t max_output_frames;
nframes_t add_to_beginning;
nframes_t add_to_end;
};
} // namespace
#endif // AUDIOGRAPHER_SILENCE_TRIMMER_H

View file

@ -0,0 +1,42 @@
#ifndef AUDIOGRAPHER_SINK_H
#define AUDIOGRAPHER_SINK_H
#include <boost/shared_ptr.hpp>
#include "process_context.h"
namespace AudioGrapher
{
template <typename T>
class Sink {
public:
virtual ~Sink () {}
/** Process given data.
* The data can not be modified, so in-place processing is not allowed.
* At least this function must be implemented by deriving classes
*/
virtual void process (ProcessContext<T> const & context) = 0;
/** Process given data
* Data may be modified, so in place processing is allowed.
* The default implementation calls the non-modifying version,
* so this function does not need to be overriden.
* However, if the sink can do in-place processing,
* overriding this is highly recommended.
*
* If this is not overridden adding "using Sink<T>::process;"
* to the deriving class declaration is suggested to avoid
* warnings about hidden virtual functions.
*/
inline virtual void process (ProcessContext<T> & context)
{
this->process (static_cast<ProcessContext<T> const &> (context));
}
};
} // namespace
#endif // AUDIOGRAPHER_SINK_H

View file

@ -0,0 +1,30 @@
#ifndef AUDIOGRAPHER_SNDFILE_BASE_H
#define AUDIOGRAPHER_SNDFILE_BASE_H
#include <string>
#include <sndfile.h>
#include <sigc++/signal.h>
#include "types.h"
namespace AudioGrapher {
/// Common interface for templated libsndfile readers/writers
class SndfileBase
{
public:
sigc::signal<void, std::string> FileWritten;
protected:
SndfileBase (ChannelCount channels, nframes_t samplerate, int format, std::string const & path);
virtual ~SndfileBase ();
std::string path;
SF_INFO sf_info;
SNDFILE * sndfile;
};
} // namespace
#endif // AUDIOGRAPHER_SNDFILE_BASE_H

View file

@ -0,0 +1,40 @@
#ifndef AUDIOGRAPHER_SNDFILE_READER_H
#define AUDIOGRAPHER_SNDFILE_READER_H
#include "sndfile_base.h"
#include "listed_source.h"
#include "process_context.h"
namespace AudioGrapher
{
/** Reader for audio files using libsndfile.
* Once again only short, int and float are valid template parameters
*/
template<typename T>
class SndfileReader : public virtual SndfileBase, public ListedSource<T>
{
public:
enum SeekType {
SeekBeginning = SEEK_SET, //< Seek from beginning of file
SeekCurrent = SEEK_CUR, //< Seek from current position
SeekEnd = SEEK_END //< Seek from end
};
public:
SndfileReader (ChannelCount channels, nframes_t samplerate, int format, std::string path);
nframes_t seek (nframes_t frames, SeekType whence);
nframes_t read (ProcessContext<T> & context);
private:
void init(); // init read function
sf_count_t (*read_func)(SNDFILE *, T *, sf_count_t);
};
} // namespace
#endif // AUDIOGRAPHER_SNDFILE_READER_H

View file

@ -0,0 +1,29 @@
#ifndef AUDIOGRAPHER_SNDFILE_WRITER_H
#define AUDIOGRAPHER_SNDFILE_WRITER_H
#include "sndfile_base.h"
#include "types.h"
#include "sink.h"
namespace AudioGrapher
{
/// Template parameter specific parts of sndfile writer
template <typename T>
class SndfileWriter : public virtual SndfileBase, public Sink<T>
{
public:
SndfileWriter (ChannelCount channels, nframes_t samplerate, int format, std::string const & path);
void process (ProcessContext<T> const & c);
using Sink<T>::process;
private:
void init (); // Inits write function
sf_count_t (*write_func)(SNDFILE *, const T *, sf_count_t);
};
} // namespace
#endif // AUDIOGRAPHER_SNDFILE_WRITER_H

View file

@ -0,0 +1,28 @@
#ifndef AUDIOGRAPHER_SOURCE_H
#define AUDIOGRAPHER_SOURCE_H
#include "types.h"
#include "sink.h"
#include <boost/shared_ptr.hpp>
namespace AudioGrapher
{
template<typename T>
class Source
{
public:
virtual ~Source () { }
typedef boost::shared_ptr<Sink<T> > SinkPtr;
virtual void add_output (SinkPtr output) = 0;
virtual void clear_outputs () = 0;
virtual void remove_output (SinkPtr output) = 0;
};
} // namespace
#endif //AUDIOGRAPHER_SOURCE_H

View file

@ -0,0 +1,51 @@
#ifndef AUDIOGRAPHER_SR_CONVERTER_H
#define AUDIOGRAPHER_SR_CONVERTER_H
#include <samplerate.h>
#include "types.h"
#include "listed_source.h"
#include "sink.h"
namespace AudioGrapher
{
class SampleRateConverter : public ListedSource<float>, public Sink<float>
{
public:
SampleRateConverter (uint32_t channels);
~SampleRateConverter ();
// not RT safe
void init (nframes_t in_rate, nframes_t out_rate, int quality = 0);
// returns max amount of frames that will be output
nframes_t allocate_buffers (nframes_t max_frames);
// could be RT safe (check libsamplerate to be sure)
void process (ProcessContext<float> const & c);
using Sink<float>::process;
private:
void set_end_of_input (ProcessContext<float> const & c);
void reset ();
bool active;
uint32_t channels;
nframes_t max_frames_in;
float * leftover_data;
nframes_t leftover_frames;
nframes_t max_leftover_frames;
float * data_out;
nframes_t data_out_size;
SRC_DATA src_data;
SRC_STATE* src_state;
};
} // namespace
#endif // AUDIOGRAPHER_SR_CONVERTER_H

View file

@ -0,0 +1,120 @@
#ifndef AUDIOGRAPHER_THREADER_H
#define AUDIOGRAPHER_THREADER_H
#include <glibmm/threadpool.h>
#include <sigc++/slot.h>
#include <boost/format.hpp>
#include <glib.h>
#include <vector>
#include <algorithm>
#include "source.h"
#include "sink.h"
#include "exception.h"
namespace AudioGrapher
{
class ThreaderException : public Exception
{
public:
template<typename T>
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() ))
{ }
};
template <typename T>
class Threader : public Source<T>, public Sink<T>
{
private:
typedef std::vector<typename Source<T>::SinkPtr> OutputVec;
public:
Threader (Glib::ThreadPool & thread_pool, long wait_timeout_milliseconds = 1000)
: thread_pool (thread_pool)
, readers (0)
, wait_timeout (wait_timeout_milliseconds)
{ }
virtual ~Threader () {}
void add_output (typename Source<T>::SinkPtr output) { outputs.push_back (output); }
void clear_outputs () { outputs.clear (); }
void remove_output (typename Source<T>::SinkPtr output) {
typename OutputVec::iterator new_end = std::remove(outputs.begin(), outputs.end(), output);
outputs.erase (new_end, outputs.end());
}
/* The context has to be const, because this is working concurrently */
void process (ProcessContext<T> const & c)
{
wait_mutex.lock();
exception.reset();
unsigned int outs = outputs.size();
g_atomic_int_add (&readers, outs);
for (unsigned int i = 0; i < outs; ++i) {
thread_pool.push (sigc::bind (sigc::mem_fun (this, &Threader::process_output), c, i));
}
wait();
}
using Sink<T>::process;
private:
void wait()
{
Glib::TimeVal wait_time;
wait_time.assign_current_time();
wait_time.add_milliseconds(wait_timeout);
wait_cond.timed_wait(wait_mutex, wait_time);
bool timed_out = (g_atomic_int_get (&readers) != 0);
wait_mutex.unlock();
if (timed_out) { throw Exception (*this, "wait timed out"); }
if (exception) {
throw *exception;
}
}
void process_output(ProcessContext<T> const & c, unsigned int output)
{
try {
outputs[output]->process (c);
} catch (std::exception const & e) {
// Only first exception will be passed on
exception_mutex.lock();
if(!exception) { exception.reset (new ThreaderException (*this, e)); }
exception_mutex.unlock();
}
if (g_atomic_int_dec_and_test (&readers)) {
wait_cond.signal();
}
}
OutputVec outputs;
Glib::ThreadPool & thread_pool;
Glib::Mutex wait_mutex;
Glib::Cond wait_cond;
gint readers;
long wait_timeout;
Glib::Mutex exception_mutex;
boost::shared_ptr<ThreaderException> exception;
};
} // namespace
#endif //AUDIOGRAPHER_THREADER_H

View file

@ -0,0 +1,25 @@
#ifndef AUDIOGRAPHER_TMP_FILE_H
#define AUDIOGRAPHER_TMP_FILE_H
#include "sndfile_writer.h"
#include "sndfile_reader.h"
namespace AudioGrapher
{
template<typename T>
class TmpFile : public SndfileWriter<T>, public SndfileReader<T>
{
public:
TmpFile (ChannelCount channels, nframes_t samplerate, int format)
: SndfileBase (channels, samplerate, format, "temp")
, SndfileWriter<T> (channels, samplerate, format, "temp")
, SndfileReader<T> (channels, samplerate, format, "temp")
{}
};
} // namespace
#endif // AUDIOGRAPHER_TMP_FILE_H

View file

@ -0,0 +1,32 @@
#ifndef AUDIOGRAPHER_TYPES_H
#define AUDIOGRAPHER_TYPES_H
#include <stdint.h>
namespace AudioGrapher {
typedef int64_t nframes_t;
typedef uint8_t ChannelCount;
/** Flag field capable of holding 32 flags.
Easily grown in size to 64 flags by changing storage_type */
class FlagField {
public:
typedef uint8_t Flag;
typedef uint32_t storage_type;
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; }
private:
storage_type _flags;
};
} // namespace
#endif // __audiographer_types_h__

View file

@ -0,0 +1,59 @@
#ifndef AUDIOGRAPHER_UTILS_H
#define AUDIOGRAPHER_UTILS_H
#include "types.h"
#include "exception.h"
#include <cstring>
namespace AudioGrapher
{
class Utils
{
public:
static void free_resources();
/// Initialize zero buffer, if buffer is != 0, it will be used as the zero buffer
template <typename T>
static void init_zeros (nframes_t frames, T const * buffer = 0)
{
if (frames == 0) {
throw Exception (Utils(), "init_zeros must be called with an argument greater than zero.");
}
unsigned long n_zeros = frames * sizeof (T);
if (n_zeros <= num_zeros) { return; }
delete [] zeros;
if (buffer) {
zeros = reinterpret_cast<char const *>(buffer);
} else {
zeros = new char[n_zeros];
memset (const_cast<char *>(zeros), 0, n_zeros);
}
num_zeros = n_zeros;
}
template <typename T>
static T const * get_zeros (nframes_t frames)
{
if (frames * sizeof (T) > num_zeros) {
throw Exception (Utils(), "init_zeros has not been called with a large enough frame count");
}
return reinterpret_cast<T const *> (zeros);
}
template <typename T>
static nframes_t get_zero_buffer_size ()
{
return num_zeros / sizeof (T);
}
private:
static char const * zeros;
static unsigned long num_zeros;
};
} // namespace
#endif // AUDIOGRAPHER_ROUTINES_H

View file

@ -0,0 +1,374 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Waf utilities for easily building standard unixey packages/libraries
# Licensed under the GNU GPL v2 or later, see COPYING file for details.
# Copyright (C) 2008 Dave Robillard
# Copyright (C) 2008 Nedko Arnaudov
import os
import misc
import Configure
import Options
import Utils
import sys
from TaskGen import feature, before, after
global g_is_child
g_is_child = False
# Only run autowaf hooks once (even if sub projects call several times)
global g_step
g_step = 0
# Compute dependencies globally
#import preproc
#preproc.go_absolute = True
@feature('cc', 'cxx')
@after('apply_lib_vars')
@before('apply_obj_vars_cc', 'apply_obj_vars_cxx')
def include_config_h(self):
self.env.append_value('INC_PATHS', self.bld.srcnode)
def set_options(opt):
"Add standard autowaf options if they havn't been added yet"
global g_step
if g_step > 0:
return
opt.tool_options('compiler_cc')
opt.tool_options('compiler_cxx')
opt.add_option('--debug', action='store_true', default=False, dest='debug',
help="Build debuggable binaries [Default: False]")
opt.add_option('--strict', action='store_true', default=False, dest='strict',
help="Use strict compiler flags and show all warnings [Default: False]")
opt.add_option('--build-docs', action='store_true', default=False, dest='build_docs',
help="Build documentation - requires doxygen [Default: False]")
opt.add_option('--bundle', action='store_true', default=False,
help="Build a self-contained bundle [Default: False]")
opt.add_option('--bindir', type='string',
help="Executable programs [Default: PREFIX/bin]")
opt.add_option('--libdir', type='string',
help="Libraries [Default: PREFIX/lib]")
opt.add_option('--includedir', type='string',
help="Header files [Default: PREFIX/include]")
opt.add_option('--datadir', type='string',
help="Shared data [Default: PREFIX/share]")
opt.add_option('--configdir', type='string',
help="Configuration data [Default: PREFIX/etc]")
opt.add_option('--mandir', type='string',
help="Manual pages [Default: DATADIR/man]")
opt.add_option('--htmldir', type='string',
help="HTML documentation [Default: DATADIR/doc/PACKAGE]")
opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
help="Install LV2 bundles to user-local location [Default: False]")
if sys.platform == "darwin":
opt.add_option('--lv2dir', type='string',
help="LV2 bundles [Default: /Library/Audio/Plug-Ins/LV2]")
else:
opt.add_option('--lv2dir', type='string',
help="LV2 bundles [Default: LIBDIR/lv2]")
g_step = 1
def check_header(conf, name, define='', mandatory=False):
"Check for a header iff it hasn't been checked for yet"
if type(conf.env['AUTOWAF_HEADERS']) != dict:
conf.env['AUTOWAF_HEADERS'] = {}
checked = conf.env['AUTOWAF_HEADERS']
if not name in checked:
checked[name] = True
if define != '':
conf.check(header_name=name, define_name=define, mandatory=mandatory)
else:
conf.check(header_name=name, mandatory=mandatory)
def nameify(name):
return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
def check_pkg(conf, name, **args):
if not 'mandatory' in args:
args['mandatory'] = True
"Check for a package iff it hasn't been checked for yet"
var_name = 'HAVE_' + nameify(args['uselib_store'])
check = not var_name in conf.env
if not check and 'atleast_version' in args:
# Re-check if version is newer than previous check
checked_version = conf.env['VERSION_' + name]
if checked_version and checked_version < args['atleast_version']:
check = True;
if check:
conf.check_cfg(package=name, args="--cflags --libs", **args)
found = bool(conf.env[var_name])
if found:
conf.define(var_name, int(found))
if 'atleast_version' in args:
conf.env['VERSION_' + name] = args['atleast_version']
else:
conf.undefine(var_name)
if args['mandatory'] == True:
conf.fatal("Required package " + name + " not found")
def chop_prefix(conf, var):
name = conf.env[var][len(conf.env['PREFIX']):]
if len(name) > 0 and name[0] == '/':
name = name[1:]
if name == "":
name = "/"
return name;
def configure(conf):
global g_step
if g_step > 1:
return
def append_cxx_flags(vals):
conf.env.append_value('CCFLAGS', vals.split())
conf.env.append_value('CXXFLAGS', vals.split())
conf.line_just = 43
conf.check_tool('misc')
conf.check_tool('compiler_cc')
conf.check_tool('compiler_cxx')
conf.env['BUILD_DOCS'] = Options.options.build_docs
conf.env['DEBUG'] = Options.options.debug
conf.env['STRICT'] = Options.options.strict
conf.env['PREFIX'] = os.path.abspath(os.path.expanduser(os.path.normpath(conf.env['PREFIX'])))
if Options.options.bundle:
conf.env['BUNDLE'] = True
conf.define('BUNDLE', 1)
conf.env['BINDIR'] = conf.env['PREFIX']
conf.env['INCLUDEDIR'] = os.path.join(conf.env['PREFIX'], 'Headers')
conf.env['LIBDIR'] = os.path.join(conf.env['PREFIX'], 'Libraries')
conf.env['DATADIR'] = os.path.join(conf.env['PREFIX'], 'Resources')
conf.env['HTMLDIR'] = os.path.join(conf.env['PREFIX'], 'Resources/Documentation')
conf.env['MANDIR'] = os.path.join(conf.env['PREFIX'], 'Resources/Man')
conf.env['LV2DIR'] = os.path.join(conf.env['PREFIX'], 'PlugIns')
else:
conf.env['BUNDLE'] = False
if Options.options.bindir:
conf.env['BINDIR'] = Options.options.bindir
else:
conf.env['BINDIR'] = os.path.join(conf.env['PREFIX'], 'bin')
if Options.options.includedir:
conf.env['INCLUDEDIR'] = Options.options.includedir
else:
conf.env['INCLUDEDIR'] = os.path.join(conf.env['PREFIX'], 'include')
if Options.options.libdir:
conf.env['LIBDIR'] = Options.options.libdir
else:
conf.env['LIBDIR'] = os.path.join(conf.env['PREFIX'], 'lib')
if Options.options.datadir:
conf.env['DATADIR'] = Options.options.datadir
else:
conf.env['DATADIR'] = os.path.join(conf.env['PREFIX'], 'share')
if Options.options.configdir:
conf.env['CONFIGDIR'] = Options.options.configdir
else:
conf.env['CONFIGDIR'] = os.path.join(conf.env['PREFIX'], 'etc')
if Options.options.htmldir:
conf.env['HTMLDIR'] = Options.options.htmldir
else:
conf.env['HTMLDIR'] = os.path.join(conf.env['DATADIR'], 'doc', Utils.g_module.APPNAME)
if Options.options.mandir:
conf.env['MANDIR'] = Options.options.mandir
else:
conf.env['MANDIR'] = os.path.join(conf.env['DATADIR'], 'man')
if Options.options.lv2dir:
conf.env['LV2DIR'] = Options.options.lv2dir
else:
if Options.options.lv2_user:
if sys.platform == "darwin":
conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
else:
conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
else:
if sys.platform == "darwin":
conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
else:
conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
conf.env['BINDIRNAME'] = chop_prefix(conf, 'BINDIR')
conf.env['LIBDIRNAME'] = chop_prefix(conf, 'LIBDIR')
conf.env['DATADIRNAME'] = chop_prefix(conf, 'DATADIR')
conf.env['CONFIGDIRNAME'] = chop_prefix(conf, 'CONFIGDIR')
conf.env['LV2DIRNAME'] = chop_prefix(conf, 'LV2DIR')
if Options.options.debug:
conf.env['CCFLAGS'] = [ '-O0', '-g' ]
conf.env['CXXFLAGS'] = [ '-O0', '-g' ]
else:
append_cxx_flags('-DNDEBUG')
if Options.options.strict:
conf.env.append_value('CCFLAGS', [ '-std=c99', '-pedantic' ])
conf.env.append_value('CXXFLAGS', [ '-ansi'])
append_cxx_flags('-Wall -Wextra -Wno-unused-parameter -Woverloaded-virtual')
append_cxx_flags('-fPIC -DPIC -fshow-column')
g_step = 2
def set_local_lib(conf, name, has_objects):
conf.define('HAVE_' + nameify(name.upper()), 1)
if has_objects:
if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
conf.env['AUTOWAF_LOCAL_LIBS'] = {}
conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
else:
if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
def use_lib(bld, obj, libs):
abssrcdir = os.path.abspath('.')
libs_list = libs.split()
for l in libs_list:
in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
in_libs = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
if in_libs:
if hasattr(obj, 'uselib_local'):
obj.uselib_local += ' lib' + l.lower() + ' '
else:
obj.uselib_local = 'lib' + l.lower() + ' '
if in_headers or in_libs:
inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
for f in ['CCFLAGS', 'CXXFLAGS']:
if not inc_flag in bld.env[f]:
bld.env.append_value(f, inc_flag)
else:
if hasattr(obj, 'uselib'):
obj.uselib += ' ' + l
else:
obj.uselib = l
def display_header(title):
Utils.pprint('BOLD', title)
def display_msg(conf, msg, status = None, color = None):
color = 'CYAN'
if type(status) == bool and status or status == "True":
color = 'GREEN'
elif type(status) == bool and not status or status == "False":
color = 'YELLOW'
Utils.pprint('NORMAL', "%s :" % msg.ljust(conf.line_just), sep='')
Utils.pprint(color, status)
def print_summary(conf):
global g_step
if g_step > 2:
print
return
e = conf.env
print
display_header('Global configuration')
display_msg(conf, "Install prefix", conf.env['PREFIX'])
display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
display_msg(conf, "Strict compiler flags", str(conf.env['STRICT']))
display_msg(conf, "Build documentation", str(conf.env['BUILD_DOCS']))
print
g_step = 3
def link_flags(env, lib):
return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
def compile_flags(env, lib):
return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['CPPPATH_' + lib]))
def set_recursive():
global g_is_child
g_is_child = True
def is_child():
global g_is_child
return g_is_child
# Pkg-config file
def build_pc(bld, name, version, libs):
'''Build a pkg-config file for a library.
name -- uppercase variable name (e.g. 'SOMENAME')
version -- version string (e.g. '1.2.3')
libs -- string/list of dependencies (e.g. 'LIBFOO GLIB')
'''
obj = bld.new_task_gen('subst')
obj.source = name.lower() + '.pc.in'
obj.target = name.lower() + '.pc'
obj.install_path = '${PREFIX}/${LIBDIRNAME}/pkgconfig'
pkg_prefix = bld.env['PREFIX']
if pkg_prefix[-1] == '/':
pkg_prefix = pkg_prefix[:-1]
obj.dict = {
'prefix' : pkg_prefix,
'exec_prefix' : '${prefix}',
'libdir' : '${exec_prefix}/lib',
'includedir' : '${prefix}/include',
name + '_VERSION' : version,
}
if type(libs) != list:
libs = libs.split()
for i in libs:
obj.dict[i + '_LIBS'] = link_flags(bld.env, i)
obj.dict[i + '_CFLAGS'] = compile_flags(bld.env, i)
# Doxygen API documentation
def build_dox(bld, name, version, srcdir, blddir):
if not bld.env['BUILD_DOCS']:
return
obj = bld.new_task_gen('subst')
obj.source = 'doc/reference.doxygen.in'
obj.target = 'doc/reference.doxygen'
if is_child():
src_dir = os.path.join(srcdir, name.lower())
doc_dir = os.path.join(blddir, 'default', name.lower(), 'doc')
else:
src_dir = srcdir
doc_dir = os.path.join(blddir, 'default', 'doc')
obj.dict = {
name + '_VERSION' : version,
name + '_SRCDIR' : os.path.abspath(src_dir),
name + '_DOC_DIR' : os.path.abspath(doc_dir)
}
obj.install_path = ''
out1 = bld.new_task_gen('command-output')
out1.dependencies = [obj]
out1.stdout = '/doc/doxygen.out'
out1.stdin = '/doc/reference.doxygen' # whatever..
out1.command = 'doxygen'
out1.argv = [os.path.abspath(doc_dir) + '/reference.doxygen']
out1.command_is_external = True
# Version code file generation
def build_version_files(header_path, source_path, domain, major, minor, micro):
header_path = os.path.abspath(header_path)
source_path = os.path.abspath(source_path)
text = "int " + domain + "_major_version = " + str(major) + ";\n"
text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
try:
o = file(source_path, 'w')
o.write(text)
o.close()
except IOError:
print "Could not open", source_path, " for writing\n"
sys.exit(-1)
text = "#ifndef __" + domain + "_version_h__\n"
text += "#define __" + domain + "_version_h__\n"
text += "extern const char* " + domain + "_revision;\n"
text += "extern int " + domain + "_major_version;\n"
text += "extern int " + domain + "_minor_version;\n"
text += "extern int " + domain + "_micro_version;\n"
text += "#endif /* __" + domain + "_version_h__ */\n"
try:
o = file(header_path, 'w')
o.write(text)
o.close()
except IOError:
print "Could not open", header_path, " for writing\n"
sys.exit(-1)
return None
def shutdown():
# This isn't really correct (for packaging), but people asking is annoying
if Options.commands['install']:
try: os.popen("/sbin/ldconfig")
except: pass

View file

@ -17,9 +17,9 @@
*
*/
#include "ardour/gdither_types_internal.h"
#include "ardour/gdither.h"
#include "ardour/noise.h"
#include "gdither_types_internal.h"
#include "gdither.h"
#include "noise.h"
/* this monstrosity is necessary to get access to lrintf() and random().
whoever is writing the glibc headers <cmath> and <cstdlib> should be
@ -169,7 +169,7 @@ inline static void gdither_innner_loop(const GDitherType dt,
const uint32_t post_scale, const int bit_depth,
const uint32_t channel, const uint32_t length, float *ts,
GDitherShapedState *ss, float *x, void *y, const int clamp_u,
GDitherShapedState *ss, float const *x, void *y, const int clamp_u,
const int clamp_l)
{
@ -245,7 +245,7 @@ inline static void gdither_innner_loop_fp(const GDitherType dt,
const float post_scale, const int bit_depth,
const uint32_t channel, const uint32_t length, float *ts,
GDitherShapedState *ss, float *x, void *y, const int clamp_u,
GDitherShapedState *ss, float const *x, void *y, const int clamp_u,
const int clamp_l)
{
@ -313,7 +313,7 @@ inline static void gdither_innner_loop_fp(const GDitherType dt,
#define GDITHER_CONV_BLOCK 512
void gdither_run(GDither s, uint32_t channel, uint32_t length,
double *x, void *y)
double const *x, void *y)
{
float conv[GDITHER_CONV_BLOCK];
uint32_t i, pos;
@ -351,7 +351,7 @@ void gdither_run(GDither s, uint32_t channel, uint32_t length,
}
void gdither_runf(GDither s, uint32_t channel, uint32_t length,
float *x, void *y)
float const *x, void *y)
{
uint32_t pos, i;
float tmp;

View file

@ -79,11 +79,11 @@ void gdither_free(GDither s);
* type for the chosen bit depth
*/
void gdither_runf(GDither s, uint32_t channel, uint32_t length,
float *x, void *y);
float const *x, void *y);
/* see gdither_runf, vut input argument is double format */
void gdither_run(GDither s, uint32_t channel, uint32_t length,
double *x, void *y);
double const *x, void *y);
#ifdef __cplusplus
}

View file

@ -0,0 +1,38 @@
/*
Copyright (C) 2000-2007 Paul Davis
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef NOISE_H
#define NOISE_H
/* Can be overrriden with any code that produces whitenoise between 0.0f and
* 1.0f, eg (random() / (float)RAND_MAX) should be a good source of noise, but
* its expensive */
#ifndef GDITHER_NOISE
#define GDITHER_NOISE gdither_noise()
#endif
inline static float gdither_noise()
{
static uint32_t rnd = 23232323;
rnd = (rnd * 196314165) + 907633515;
return rnd * 2.3283064365387e-10f;
}
#endif

View file

@ -0,0 +1,7 @@
#include "audiographer/routines.h"
namespace AudioGrapher
{
Routines::compute_peak_t Routines::_compute_peak = &Routines::default_compute_peak;
Routines::apply_gain_to_buffer_t Routines::_apply_gain_to_buffer = &Routines::default_apply_gain_to_buffer;
}

View file

@ -0,0 +1,190 @@
#include "audiographer/sample_format_converter.h"
#include "gdither/gdither.h"
#include "audiographer/exception.h"
#include <boost/format.hpp>
#include <cstring>
namespace AudioGrapher
{
template <typename TOut>
SampleFormatConverter<TOut>::SampleFormatConverter (uint32_t channels) :
channels (channels),
dither (0),
data_out_size (0),
data_out (0),
clip_floats (false)
{
}
template <>
void
SampleFormatConverter<float>::init (nframes_t max_frames, int type, int data_width)
{
if (data_width != 32) { throw Exception (*this, "Unsupported data width"); }
init_common (max_frames);
dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width);
}
template <>
void
SampleFormatConverter<int32_t>::init (nframes_t max_frames, int type, int data_width)
{
if(data_width < 24) { throw Exception (*this, "Use SampleFormatConverter<int16_t> for data widths < 24"); }
init_common (max_frames);
if (data_width == 24) {
dither = gdither_new ((GDitherType) type, channels, GDither32bit, data_width);
} else if (data_width == 32) {
dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width);
} else {
throw Exception (*this, "Unsupported data width");
}
}
template <>
void
SampleFormatConverter<int16_t>::init (nframes_t max_frames, int type, int data_width)
{
if (data_width != 16) { throw Exception (*this, "Unsupported data width"); }
init_common (max_frames);
dither = gdither_new ((GDitherType) type, channels, GDither16bit, data_width);
}
template <>
void
SampleFormatConverter<uint8_t>::init (nframes_t max_frames, int type, int data_width)
{
if (data_width != 8) { throw Exception (*this, "Unsupported data width"); }
init_common (max_frames);
dither = gdither_new ((GDitherType) type, channels, GDither8bit, data_width);
}
template <typename TOut>
void
SampleFormatConverter<TOut>::init_common (nframes_t max_frames )
{
reset();
if (max_frames > data_out_size) {
delete[] data_out;
data_out = new TOut[max_frames];
data_out_size = max_frames;
}
}
template <typename TOut>
SampleFormatConverter<TOut>::~SampleFormatConverter ()
{
reset();
}
template <typename TOut>
void
SampleFormatConverter<TOut>::reset()
{
if (dither) {
gdither_free (dither);
dither = 0;
}
delete[] data_out;
data_out_size = 0;
data_out = 0;
clip_floats = false;
}
/* Basic const version of process() */
template <typename TOut>
void
SampleFormatConverter<TOut>::process (ProcessContext<float> const & c_in)
{
float const * const data = c_in.data();
nframes_t const frames = c_in.frames();
check_frame_count (frames);
/* Do conversion */
for (uint32_t chn = 0; chn < channels; ++chn) {
gdither_runf (dither, chn, frames / channels, data, data_out);
}
/* Write forward */
ProcessContext<TOut> c_out(c_in, data_out);
output (c_out);
}
/* Basic non-const version of process(), calls the const one */
template<typename TOut>
void
SampleFormatConverter<TOut>::process (ProcessContext<float> & c_in)
{
process (static_cast<ProcessContext<float> const &> (c_in));
}
/* template specialization for float, in-place processing (non-const) */
template<>
void
SampleFormatConverter<float>::process (ProcessContext<float> & c_in)
{
nframes_t frames = c_in.frames();
float * data = c_in.data();
if (clip_floats) {
for (nframes_t x = 0; x < frames; ++x) {
if (data[x] > 1.0f) {
data[x] = 1.0f;
} else if (data[x] < -1.0f) {
data[x] = -1.0f;
}
}
}
output (c_in);
}
/* template specialized const version, copies the data, and calls the non-const version */
template<>
void
SampleFormatConverter<float>::process (ProcessContext<float> const & c_in)
{
// Make copy of data and pass it to non-const version
nframes_t frames = c_in.frames();
check_frame_count (frames);
memcpy (data_out, c_in.data(), frames * sizeof(float));
ProcessContext<float> c (c_in, data_out);
process (c);
}
template<typename TOut>
void
SampleFormatConverter<TOut>::check_frame_count(nframes_t frames)
{
if (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));
}
if (frames > data_out_size) {
throw Exception (*this, boost::str (boost::format (
"Too many frames given to process(), %1% instad of %2%")
% frames % data_out_size));
}
}
template class SampleFormatConverter<uint8_t>;
template class SampleFormatConverter<int16_t>;
template class SampleFormatConverter<int32_t>;
template class SampleFormatConverter<float>;
} // namespace

View file

@ -0,0 +1,56 @@
#include "audiographer/sndfile_base.h"
#include "audiographer/exception.h"
#include <boost/format.hpp>
namespace AudioGrapher
{
using std::string;
using boost::str;
using boost::format;
/* SndfileWriterBase */
SndfileBase::SndfileBase (ChannelCount channels, nframes_t samplerate, int format, string const & path)
: path (path)
{
char errbuf[256];
sf_info.channels = channels;
sf_info.samplerate = samplerate;
sf_info.format = format;
if (!sf_format_check (&sf_info)) {
throw Exception (*this, "Invalid format in constructor");
}
if (path.length() == 0) {
throw Exception (*this, "No output file specified");
}
/* TODO add checks that the directory path exists, and also
check if we are overwriting an existing file...
*/
// Open file
if (path.compare ("temp")) {
if ((sndfile = sf_open (path.c_str(), SFM_WRITE, &sf_info)) == 0) {
sf_error_str (0, errbuf, sizeof (errbuf) - 1);
throw Exception (*this, str (boost::format ("Cannot open output file \"%1%\" (%2%)") % path % errbuf));
}
} else {
FILE * file;
if (!(file = tmpfile ())) {
throw Exception (*this, "Cannot open tempfile");
}
sndfile = sf_open_fd (fileno(file), SFM_RDWR, &sf_info, true);
}
}
SndfileBase::~SndfileBase ()
{
sf_close (sndfile);
}
} // namespace

View file

@ -0,0 +1,67 @@
#include "audiographer/sndfile_reader.h"
#include <boost/format.hpp>
#include "audiographer/exception.h"
namespace AudioGrapher
{
template<typename T>
SndfileReader<T>::SndfileReader (ChannelCount channels, nframes_t samplerate, int format, std::string path)
: SndfileBase (channels, samplerate, format, path)
{
init ();
}
template<typename T>
nframes_t
SndfileReader<T>::seek (nframes_t frames, SeekType whence)
{
return sf_seek (sndfile, frames, whence);
}
template<typename T>
nframes_t
SndfileReader<T>::read (ProcessContext<T> & context)
{
if (context.channels() != sf_info.channels) {
throw Exception (*this, boost::str (boost::format (
"ProcessContext given to read() has a wrong amount of channels: %1% instead of %2%")
% context.channels() % sf_info.channels));
}
nframes_t frames_read = (*read_func) (sndfile, context.data(), context.frames());
if (frames_read < context.frames()) {
context.set_flag (ProcessContext<T>::EndOfInput);
}
output (context);
return frames_read;
}
template<>
void
SndfileReader<short>::init()
{
read_func = &sf_read_short;
}
template<>
void
SndfileReader<int>::init()
{
read_func = &sf_read_int;
}
template<>
void
SndfileReader<float>::init()
{
read_func = &sf_read_float;
}
template class SndfileReader<short>;
template class SndfileReader<int>;
template class SndfileReader<float>;
} // namespace

View file

@ -0,0 +1,73 @@
#include "audiographer/sndfile_writer.h"
#include "audiographer/exception.h"
#include <cstring>
#include <boost/format.hpp>
namespace AudioGrapher
{
using std::string;
using boost::str;
using boost::format;
template <typename T>
SndfileWriter<T>::SndfileWriter (ChannelCount channels, nframes_t samplerate, int format, string const & path) :
SndfileBase (channels, samplerate, format, path)
{
// init write function
init ();
}
template <>
void
SndfileWriter<float>::init ()
{
write_func = &sf_write_float;
}
template <>
void
SndfileWriter<int>::init ()
{
write_func = &sf_write_int;
}
template <>
void
SndfileWriter<short>::init ()
{
write_func = &sf_write_short;
}
template <typename T>
void
SndfileWriter<T>::process (ProcessContext<T> const & c)
{
if (c.channels() != sf_info.channels) {
throw Exception (*this, str (boost::format(
"Wrong number of channels given to process(), %1% instead of %2%")
% c.channels() % sf_info.channels));
}
char errbuf[256];
nframes_t written = (*write_func) (sndfile, c.data(), c.frames());
if (written != c.frames()) {
sf_error_str (sndfile, errbuf, sizeof (errbuf) - 1);
throw Exception (*this, str ( format("Could not write data to output file (%1%)") % errbuf));
}
if (c.has_flag(ProcessContext<T>::EndOfInput)) {
sf_write_sync (sndfile);
//#ifdef HAVE_SIGCPP
FileWritten (path);
//#endif
}
}
template class SndfileWriter<short>;
template class SndfileWriter<int>;
template class SndfileWriter<float>;
} // namespace

View file

@ -0,0 +1,218 @@
#include "audiographer/sr_converter.h"
#include "audiographer/exception.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;
using boost::str;
SampleRateConverter::SampleRateConverter (uint32_t channels)
: active (false)
, channels (channels)
, max_frames_in(0)
, leftover_data (0)
, leftover_frames (0)
, max_leftover_frames (0)
, data_out (0)
, data_out_size (0)
, src_state (0)
{
}
void
SampleRateConverter::init (nframes_t in_rate, nframes_t out_rate, int quality)
{
reset();
if (in_rate == out_rate) {
src_data.src_ratio = 1;
return;
}
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_data.src_ratio = (double) out_rate / (double) in_rate;
}
SampleRateConverter::~SampleRateConverter ()
{
reset();
}
nframes_t
SampleRateConverter::allocate_buffers (nframes_t max_frames)
{
if (!active) { return max_frames; }
nframes_t max_frames_out = (nframes_t) ceil (max_frames * src_data.src_ratio);
if (data_out_size < max_frames_out) {
delete[] data_out;
data_out = new float[max_frames_out];
src_data.data_out = data_out;
max_leftover_frames = 4 * max_frames;
leftover_data = (float *) realloc (leftover_data, max_leftover_frames * sizeof (float));
if (!leftover_data) {
throw Exception (*this, "A memory allocation error occured");
}
max_frames_in = max_frames;
data_out_size = max_frames_out;
}
return max_frames_out;
}
void
SampleRateConverter::process (ProcessContext<float> const & c)
{
if (!active) {
output (c);
return;
}
nframes_t frames = c.frames();
float * in = const_cast<float *> (c.data()); // TODO check if this is safe!
if (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) {
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));
}
int err;
bool first_time = true;
do {
src_data.output_frames = data_out_size / channels;
src_data.data_out = data_out;
if (leftover_frames > 0) {
/* input data will be in leftover_data rather than data_in */
src_data.data_in = leftover_data;
if (first_time) {
/* first time, append new data from data_in into the leftover_data buffer */
memcpy (&leftover_data [leftover_frames * channels], in, frames * sizeof(float));
src_data.input_frames = frames + leftover_frames;
} else {
/* otherwise, just use whatever is still left in leftover_data; the contents
were adjusted using memmove() right after the last SRC call (see
below)
*/
src_data.input_frames = leftover_frames;
}
} else {
src_data.data_in = in;
src_data.input_frames = frames / channels;
}
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 ((err = src_process (src_state, &src_data)) != 0) {
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) {
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));
}
ProcessContext<float> c_out (c, data_out, src_data.output_frames_gen * channels);
if (!src_data.end_of_input || leftover_frames) {
c_out.remove_flag (ProcessContext<float>::EndOfInput);
}
output (c_out);
DEBUG ("src_data.output_frames_gen: " << src_data.output_frames_gen << ", leftover_frames: " << leftover_frames);
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)); }
} while (leftover_frames > frames);
// src_data.end_of_input has to be checked to prevent infinite recursion
if (!src_data.end_of_input && c.has_flag(ProcessContext<float>::EndOfInput)) {
set_end_of_input (c);
}
}
void SampleRateConverter::set_end_of_input (ProcessContext<float> const & c)
{
src_data.end_of_input = true;
float f;
ProcessContext<float> const dummy (c, &f, 0, channels);
/* 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...
*/
process (dummy);
process (dummy);
}
void SampleRateConverter::reset ()
{
active = false;
max_frames_in = 0;
src_data.end_of_input = false;
if (src_state) {
src_delete (src_state);
}
leftover_frames = 0;
max_leftover_frames = 0;
if (leftover_data) {
free (leftover_data);
}
data_out_size = 0;
delete [] data_out;
data_out = 0;
}
} // namespace

View file

@ -0,0 +1,14 @@
#include "audiographer/utils.h"
using namespace AudioGrapher;
char const * Utils::zeros = 0;
unsigned long Utils::num_zeros = 0;
void
Utils::free_resources()
{
num_zeros = 0;
delete [] zeros;
zeros = 0;
}

View file

@ -0,0 +1,112 @@
#include "utils.h"
#include "audiographer/chunker.h"
#include <cassert>
using namespace AudioGrapher;
class ChunkerTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (ChunkerTest);
CPPUNIT_TEST (testSynchronousProcess);
CPPUNIT_TEST (testAsynchronousProcess);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
sink.reset (new VectorSink<float>());
chunker.reset (new Chunker<float>(frames * 2));
}
void tearDown()
{
delete [] random_data;
}
void testSynchronousProcess()
{
chunker->add_output (sink);
nframes_t frames_output = 0;
ProcessContext<float> const context (random_data, frames, 1);
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames], frames));
sink->reset();
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames], frames));
}
void testAsynchronousProcess()
{
assert (frames % 2 == 0);
chunker->add_output (sink);
nframes_t frames_output = 0;
ProcessContext<float> const half_context (random_data, frames / 2, 1);
ProcessContext<float> const context (random_data, frames, 1);
// 0.5
chunker->process (half_context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
// 1.5
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
// 2.5
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames / 2));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames / 2], frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[ 3 * frames / 2], frames / 2));
sink->reset();
// 3.5
chunker->process (context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
// 4.0
chunker->process (half_context);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (&random_data[frames / 2], sink->get_array(), frames / 2));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames / 2], frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[ 3 * frames / 2], frames / 2));
}
private:
boost::shared_ptr<Chunker<float> > chunker;
boost::shared_ptr<VectorSink<float> > sink;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (ChunkerTest);

View file

@ -0,0 +1,133 @@
#include "utils.h"
#include "audiographer/deinterleaver.h"
using namespace AudioGrapher;
class DeInterleaverTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (DeInterleaverTest);
CPPUNIT_TEST (testUninitialized);
CPPUNIT_TEST (testInvalidOutputIndex);
CPPUNIT_TEST (testInvalidInputSize);
CPPUNIT_TEST (testOutputSize);
CPPUNIT_TEST (testZeroInput);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
channels = 3;
frames_per_channel = 128;
total_frames = channels * frames_per_channel;
random_data = TestUtils::init_random_data (total_frames, 1.0);
deinterleaver.reset (new DeInterleaver<float>());
sink_a.reset (new VectorSink<float>());
sink_b.reset (new VectorSink<float>());
sink_c.reset (new VectorSink<float>());
}
void tearDown()
{
delete [] random_data;
}
void testUninitialized()
{
deinterleaver.reset (new DeInterleaver<float>());
CPPUNIT_ASSERT_THROW (deinterleaver->output(0)->add_output (sink_a), Exception);
}
void testInvalidOutputIndex()
{
deinterleaver->init (3, frames_per_channel);
CPPUNIT_ASSERT_THROW (deinterleaver->output(3)->add_output (sink_a), Exception);
}
void testInvalidInputSize()
{
deinterleaver->init (channels, frames_per_channel);
ProcessContext<float> c (random_data, 0, channels);
// Too many, frames % channels == 0
c.frames() = total_frames + channels;
CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
// Too many, frames % channels != 0
c.frames() = total_frames + 1;
CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
// Too few, frames % channels != 0
c.frames() = total_frames - 1;
CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
}
void assert_outputs (nframes_t expected_frames)
{
nframes_t generated_frames = 0;
generated_frames = sink_a->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
generated_frames = sink_b->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
generated_frames = sink_c->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
}
void testOutputSize()
{
deinterleaver->init (channels, frames_per_channel);
deinterleaver->output (0)->add_output (sink_a);
deinterleaver->output (1)->add_output (sink_b);
deinterleaver->output (2)->add_output (sink_c);
// Test maximum frame input
ProcessContext<float> c (random_data, total_frames, channels);
deinterleaver->process (c);
assert_outputs (frames_per_channel);
// Now with less frames
nframes_t const less_frames = frames_per_channel / 4;
c.frames() = less_frames * channels;
deinterleaver->process (c);
assert_outputs (less_frames);
}
void testZeroInput()
{
deinterleaver->init (channels, frames_per_channel);
deinterleaver->output (0)->add_output (sink_a);
deinterleaver->output (1)->add_output (sink_b);
deinterleaver->output (2)->add_output (sink_c);
// Input zero frames
ProcessContext<float> c (random_data, 0, channels);
deinterleaver->process (c);
// ...and now test regular input
c.frames() = total_frames;
deinterleaver->process (c);
assert_outputs (frames_per_channel);
}
private:
boost::shared_ptr<DeInterleaver<float> > deinterleaver;
boost::shared_ptr<VectorSink<float> > sink_a;
boost::shared_ptr<VectorSink<float> > sink_b;
boost::shared_ptr<VectorSink<float> > sink_c;
float * random_data;
nframes_t frames_per_channel;
nframes_t total_frames;
unsigned int channels;
};
CPPUNIT_TEST_SUITE_REGISTRATION (DeInterleaverTest);

View file

@ -0,0 +1,99 @@
#include "utils.h"
#include "audiographer/identity_vertex.h"
using namespace AudioGrapher;
class IdentityVertexTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (IdentityVertexTest);
CPPUNIT_TEST (testProcess);
CPPUNIT_TEST (testRemoveOutput);
CPPUNIT_TEST (testClearOutputs);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
zero_data = new float[frames];
memset (zero_data, 0, frames * sizeof(float));
sink_a.reset (new VectorSink<float>());
sink_b.reset (new VectorSink<float>());
}
void tearDown()
{
delete [] random_data;
delete [] zero_data;
}
void testProcess()
{
vertex.reset (new IdentityVertex<float>());
vertex->add_output (sink_a);
vertex->add_output (sink_b);
nframes_t frames_output = 0;
ProcessContext<float> c (random_data, frames, 1);
vertex->process (c);
frames_output = sink_a->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
frames_output = sink_b->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_b->get_array(), frames));
}
void testRemoveOutput()
{
vertex.reset (new IdentityVertex<float>());
vertex->add_output (sink_a);
vertex->add_output (sink_b);
ProcessContext<float> c (random_data, frames, 1);
vertex->process (c);
vertex->remove_output (sink_a);
ProcessContext<float> zc (zero_data, frames, 1);
vertex->process (zc);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals (zero_data, sink_b->get_array(), frames));
}
void testClearOutputs()
{
vertex.reset (new IdentityVertex<float>());
vertex->add_output (sink_a);
vertex->add_output (sink_b);
ProcessContext<float> c (random_data, frames, 1);
vertex->process (c);
vertex->clear_outputs ();
ProcessContext<float> zc (zero_data, frames, 1);
vertex->process (zc);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_b->get_array(), frames));
}
private:
boost::shared_ptr<IdentityVertex<float> > vertex;
boost::shared_ptr<VectorSink<float> > sink_a;
boost::shared_ptr<VectorSink<float> > sink_b;
float * random_data;
float * zero_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (IdentityVertexTest);

View file

@ -0,0 +1,120 @@
#include "utils.h"
#include "audiographer/interleaver.h"
#include "audiographer/deinterleaver.h"
using namespace AudioGrapher;
class InterleaverDeInterleaverTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (InterleaverDeInterleaverTest);
CPPUNIT_TEST (testInterleavedInput);
CPPUNIT_TEST (testDeInterleavedInput);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
channels = 3;
frames_per_channel = 128;
total_frames = channels * frames_per_channel;
random_data_a = TestUtils::init_random_data (total_frames, 1.0);
random_data_b = TestUtils::init_random_data (frames_per_channel, 1.0);
random_data_c = TestUtils::init_random_data (frames_per_channel, 1.0);
deinterleaver.reset (new DeInterleaver<float>());
interleaver.reset (new Interleaver<float>());
sink_a.reset (new VectorSink<float>());
sink_b.reset (new VectorSink<float>());
sink_c.reset (new VectorSink<float>());
}
void tearDown()
{
delete [] random_data_a;
delete [] random_data_b;
delete [] random_data_c;
}
void testInterleavedInput()
{
deinterleaver->init (channels, frames_per_channel);
interleaver->init (channels, frames_per_channel);
deinterleaver->output (0)->add_output (interleaver->input (0));
deinterleaver->output (1)->add_output (interleaver->input (1));
deinterleaver->output (2)->add_output (interleaver->input (2));
interleaver->add_output (sink_a);
// Process and assert
ProcessContext<float> c (random_data_a, total_frames, channels);
deinterleaver->process (c);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), total_frames));
// And a second round...
nframes_t less_frames = (frames_per_channel / 10) * channels;
c.frames() = less_frames;
deinterleaver->process (c);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), less_frames));
}
void testDeInterleavedInput()
{
deinterleaver->init (channels, frames_per_channel);
interleaver->init (channels, frames_per_channel);
interleaver->add_output (deinterleaver);
deinterleaver->output (0)->add_output (sink_a);
deinterleaver->output (1)->add_output (sink_b);
deinterleaver->output (2)->add_output (sink_c);
ProcessContext<float> c_a (random_data_a, frames_per_channel, 1);
ProcessContext<float> c_b (random_data_b, frames_per_channel, 1);
ProcessContext<float> c_c (random_data_c, frames_per_channel, 1);
// Process and assert
interleaver->input (0)->process (c_a);
interleaver->input (1)->process (c_b);
interleaver->input (2)->process (c_c);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), frames_per_channel));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_b, sink_b->get_array(), frames_per_channel));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_c, sink_c->get_array(), frames_per_channel));
// And a second round...
nframes_t less_frames = frames_per_channel / 5;
c_a.frames() = less_frames;
c_b.frames() = less_frames;
c_c.frames() = less_frames;
interleaver->input (0)->process (c_a);
interleaver->input (1)->process (c_b);
interleaver->input (2)->process (c_c);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), less_frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_b, sink_b->get_array(), less_frames));
CPPUNIT_ASSERT (TestUtils::array_equals (random_data_c, sink_c->get_array(), less_frames));
}
private:
boost::shared_ptr<Interleaver<float> > interleaver;
boost::shared_ptr<DeInterleaver<float> > deinterleaver;
boost::shared_ptr<VectorSink<float> > sink_a;
boost::shared_ptr<VectorSink<float> > sink_b;
boost::shared_ptr<VectorSink<float> > sink_c;
float * random_data_a;
float * random_data_b;
float * random_data_c;
nframes_t frames_per_channel;
nframes_t total_frames;
unsigned int channels;
};
CPPUNIT_TEST_SUITE_REGISTRATION (InterleaverDeInterleaverTest);

View file

@ -0,0 +1,132 @@
#include "utils.h"
#include "audiographer/interleaver.h"
using namespace AudioGrapher;
class InterleaverTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (InterleaverTest);
CPPUNIT_TEST (testUninitialized);
CPPUNIT_TEST (testInvalidInputIndex);
CPPUNIT_TEST (testInvalidInputSize);
CPPUNIT_TEST (testOutputSize);
CPPUNIT_TEST (testZeroInput);
CPPUNIT_TEST (testChannelSync);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
channels = 3;
frames = 128;
random_data = TestUtils::init_random_data (frames, 1.0);
interleaver.reset (new Interleaver<float>());
sink.reset (new VectorSink<float>());
interleaver->init (channels, frames);
}
void tearDown()
{
delete [] random_data;
}
void testUninitialized()
{
interleaver.reset (new Interleaver<float>());
ProcessContext<float> c (random_data, frames, 1);
CPPUNIT_ASSERT_THROW (interleaver->input(0)->process (c), Exception);
}
void testInvalidInputIndex()
{
ProcessContext<float> c (random_data, frames, 1);
CPPUNIT_ASSERT_THROW (interleaver->input (3)->process (c), Exception);
}
void testInvalidInputSize()
{
ProcessContext<float> c (random_data, frames + 1, 1);
CPPUNIT_ASSERT_THROW (interleaver->input (0)->process (c), Exception);
c.frames() = frames;
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
c.frames() = frames -1;
CPPUNIT_ASSERT_THROW (interleaver->input (2)->process (c), Exception);
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
c.frames() = frames;
CPPUNIT_ASSERT_THROW (interleaver->input (2)->process (c), Exception);
}
void testOutputSize()
{
interleaver->add_output (sink);
ProcessContext<float> c (random_data, frames, 1);
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
interleaver->input (2)->process (c);
nframes_t expected_frames = frames * channels;
nframes_t generated_frames = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
nframes_t less_frames = frames / 2;
c.frames() = less_frames;
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
interleaver->input (2)->process (c);
expected_frames = less_frames * channels;
generated_frames = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
}
void testZeroInput()
{
interleaver->add_output (sink);
// input zero frames to all inputs
ProcessContext<float> c (random_data, 0, 1);
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
interleaver->input (2)->process (c);
// NOTE zero input is allowed to be a NOP
// ...now test regular input
c.frames() = frames;
interleaver->input (0)->process (c);
interleaver->input (1)->process (c);
interleaver->input (2)->process (c);
nframes_t expected_frames = frames * channels;
nframes_t generated_frames = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
}
void testChannelSync()
{
interleaver->add_output (sink);
ProcessContext<float> c (random_data, frames, 1);
interleaver->input (0)->process (c);
CPPUNIT_ASSERT_THROW (interleaver->input (0)->process (c), Exception);
}
private:
boost::shared_ptr<Interleaver<float> > interleaver;
boost::shared_ptr<VectorSink<float> > sink;
nframes_t channels;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (InterleaverTest);

View file

@ -0,0 +1,60 @@
#include "utils.h"
#include "audiographer/normalizer.h"
#include "audiographer/peak_reader.h"
using namespace AudioGrapher;
class NormalizerTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (NormalizerTest);
CPPUNIT_TEST (testConstAmplify);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 1024;
}
void tearDown()
{
delete [] random_data;
}
void testConstAmplify()
{
float target = 0.0;
random_data = TestUtils::init_random_data(frames, 0.5);
normalizer.reset (new Normalizer(target));
peak_reader.reset (new PeakReader());
sink.reset (new VectorSink<float>());
ProcessContext<float> const c (random_data, frames, 1);
peak_reader->process (c);
float peak = peak_reader->get_peak();
normalizer->alloc_buffer (frames);
normalizer->set_peak (peak);
normalizer->add_output (sink);
normalizer->process (c);
peak_reader->reset();
ConstProcessContext<float> normalized (sink->get_array(), frames, 1);
peak_reader->process (normalized);
peak = peak_reader->get_peak();
CPPUNIT_ASSERT (-FLT_EPSILON <= (peak - 1.0) && (peak - 1.0) <= 0.0);
}
private:
boost::shared_ptr<Normalizer> normalizer;
boost::shared_ptr<PeakReader> peak_reader;
boost::shared_ptr<VectorSink<float> > sink;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (NormalizerTest);

View file

@ -0,0 +1,53 @@
#include "utils.h"
#include "audiographer/peak_reader.h"
using namespace AudioGrapher;
class PeakReaderTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (PeakReaderTest);
CPPUNIT_TEST (testProcess);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
}
void tearDown()
{
delete [] random_data;
}
void testProcess()
{
reader.reset (new PeakReader());
ProcessContext<float> c (random_data, frames, 1);
float peak = 1.5;
random_data[10] = peak;
reader->process (c);
CPPUNIT_ASSERT_EQUAL(peak, reader->get_peak());
peak = 2.0;
random_data[10] = peak;
reader->process (c);
CPPUNIT_ASSERT_EQUAL(peak, reader->get_peak());
peak = -2.1;
random_data[10] = peak;
reader->process (c);
float expected = fabs(peak);
CPPUNIT_ASSERT_EQUAL(expected, reader->get_peak());
}
private:
boost::shared_ptr<PeakReader> reader;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (PeakReaderTest);

View file

@ -0,0 +1,209 @@
#include "utils.h"
#include "audiographer/sample_format_converter.h"
using namespace AudioGrapher;
class SampleFormatConverterTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (SampleFormatConverterTest);
CPPUNIT_TEST (testInit);
CPPUNIT_TEST (testFrameCount);
CPPUNIT_TEST (testFloat);
CPPUNIT_TEST (testInt32);
CPPUNIT_TEST (testInt24);
CPPUNIT_TEST (testInt16);
CPPUNIT_TEST (testUint8);
CPPUNIT_TEST (testChannelCount);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames, 1.0);
}
void tearDown()
{
delete [] random_data;
}
void testInit()
{
boost::shared_ptr<SampleFormatConverter<float> > f_converter (new SampleFormatConverter<float>(1));
f_converter->init (frames, D_Tri, 32); // Doesn't throw
CPPUNIT_ASSERT_THROW (f_converter->init (frames, D_Tri, 24), Exception);
CPPUNIT_ASSERT_THROW (f_converter->init (frames, D_Tri, 48), Exception);
boost::shared_ptr<SampleFormatConverter<int32_t> > i_converter (new SampleFormatConverter<int32_t>(1));
i_converter->init (frames, D_Tri, 32); // Doesn't throw
i_converter->init (frames, D_Tri, 24); // Doesn't throw
CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 8), Exception);
CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 16), Exception);
CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 48), Exception);
boost::shared_ptr<SampleFormatConverter<int16_t> > i16_converter (new SampleFormatConverter<int16_t>(1));
i16_converter->init (frames, D_Tri, 16); // Doesn't throw
CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 8), Exception);
CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 32), Exception);
CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 48), Exception);
boost::shared_ptr<SampleFormatConverter<uint8_t> > ui_converter (new SampleFormatConverter<uint8_t>(1));
ui_converter->init (frames, D_Tri, 8); // Doesn't throw
CPPUNIT_ASSERT_THROW (ui_converter->init (frames, D_Tri, 4), Exception);
CPPUNIT_ASSERT_THROW (ui_converter->init (frames, D_Tri, 16), Exception);
}
void testFrameCount()
{
boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
converter->init (frames, D_Tri, 32);
converter->add_output (sink);
nframes_t frames_output = 0;
{
ProcessContext<float> pc(random_data, frames / 2, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames / 2, frames_output);
}
{
ProcessContext<float> pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
}
{
ProcessContext<float> pc(random_data, frames + 1, 1);
CPPUNIT_ASSERT_THROW(converter->process (pc), Exception);
}
}
void testFloat()
{
boost::shared_ptr<SampleFormatConverter<float> > converter (new SampleFormatConverter<float>(1));
boost::shared_ptr<VectorSink<float> > sink (new VectorSink<float>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 32);
converter->add_output (sink);
converter->set_clip_floats (false);
ProcessContext<float> const pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals(sink->get_array(), random_data, frames));
// Make sure a few samples are < -1.0 and > 1.0
random_data[10] = -1.5;
random_data[20] = 1.5;
converter->set_clip_floats (true);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
for (nframes_t i = 0; i < frames; ++i) {
// fp comparison needs a bit of tolerance, 1.01 << 1.5
CPPUNIT_ASSERT(sink->get_data()[i] < 1.01);
CPPUNIT_ASSERT(sink->get_data()[i] > -1.01);
}
}
void testInt32()
{
boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 32);
converter->add_output (sink);
ProcessContext<float> pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
}
void testInt24()
{
boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 24);
converter->add_output (sink);
ProcessContext<float> pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
}
void testInt16()
{
boost::shared_ptr<SampleFormatConverter<int16_t> > converter (new SampleFormatConverter<int16_t>(1));
boost::shared_ptr<VectorSink<int16_t> > sink (new VectorSink<int16_t>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 16);
converter->add_output (sink);
ProcessContext<float> pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
}
void testUint8()
{
boost::shared_ptr<SampleFormatConverter<uint8_t> > converter (new SampleFormatConverter<uint8_t>(1));
boost::shared_ptr<VectorSink<uint8_t> > sink (new VectorSink<uint8_t>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 8);
converter->add_output (sink);
ProcessContext<float> pc(random_data, frames, 1);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
}
void testChannelCount()
{
boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(3));
boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
nframes_t frames_output = 0;
converter->init(frames, D_Tri, 32);
converter->add_output (sink);
ProcessContext<float> pc(random_data, 4, 1);
CPPUNIT_ASSERT_THROW (converter->process (pc), Exception);
pc.frames() = frames - (frames % 3);
converter->process (pc);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (pc.frames(), frames_output);
CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), pc.frames()));
}
private:
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (SampleFormatConverterTest);

View file

@ -0,0 +1,196 @@
#include "utils.h"
#include "audiographer/silence_trimmer.h"
using namespace AudioGrapher;
class SilenceTrimmerTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (SilenceTrimmerTest);
CPPUNIT_TEST (testExceptions);
CPPUNIT_TEST (testFullBuffers);
CPPUNIT_TEST (testPartialBuffers);
CPPUNIT_TEST (testAddSilenceBeginning);
CPPUNIT_TEST (testAddSilenceEnd);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
random_data[0] = 0.5;
random_data[frames - 1] = 0.5;
zero_data = new float[frames];
memset(zero_data, 0, frames * sizeof(float));
half_random_data = TestUtils::init_random_data(frames);
memset(half_random_data, 0, (frames / 2) * sizeof(float));
trimmer.reset (new SilenceTrimmer<float>());
sink.reset (new AppendingVectorSink<float>());
trimmer->set_trim_beginning (true);
trimmer->set_trim_end (true);
}
void tearDown()
{
delete [] random_data;
delete [] zero_data;
delete [] half_random_data;
AudioGrapher::Utils::free_resources();
}
void testFullBuffers()
{
trimmer->add_output (sink);
AudioGrapher::Utils::init_zeros<float>(frames / 2);
{
ProcessContext<float> c (zero_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_processed);
}
{
ProcessContext<float> c (random_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_processed);
CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
}
{
ProcessContext<float> c (zero_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_processed);
}
{
ProcessContext<float> c (random_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (3 * frames, frames_processed);
CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames], zero_data, frames));
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[2 * frames], random_data, frames));
}
{
ProcessContext<float> c (zero_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (3 * frames, frames_processed);
}
}
void testPartialBuffers()
{
trimmer->add_output (sink);
AudioGrapher::Utils::init_zeros<float>(frames / 4);
{
ProcessContext<float> c (half_random_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames / 2, frames_processed);
CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), &half_random_data[frames / 2], frames / 2));
}
{
ProcessContext<float> c (zero_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames / 2, frames_processed);
}
{
ProcessContext<float> c (half_random_data, frames, 1);
trimmer->process (c);
nframes_t frames_processed = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (2 * frames + frames / 2, frames_processed);
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames + frames / 2], half_random_data, frames));
}
}
void testExceptions()
{
// TODO more tests here
trimmer->add_output (sink);
{
ProcessContext<float> c (random_data, frames, 1);
trimmer->process (c);
}
{
ProcessContext<float> c (zero_data, frames, 1);
trimmer->process (c);
}
{
// Zeros not inited, so this should throw
ProcessContext<float> c (random_data, frames, 1);
CPPUNIT_ASSERT_THROW (trimmer->process (c), Exception);
}
}
void testAddSilenceBeginning()
{
trimmer->add_output (sink);
AudioGrapher::Utils::init_zeros<float>(frames / 2);
nframes_t silence = frames / 2;
trimmer->add_silence_to_beginning (silence);
{
ProcessContext<float> c (random_data, frames, 1);
trimmer->process (c);
}
CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), zero_data, silence));
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[silence], random_data, frames));
}
void testAddSilenceEnd()
{
trimmer->add_output (sink);
AudioGrapher::Utils::init_zeros<float>(frames / 2);
nframes_t silence = frames / 3;
trimmer->add_silence_to_end (silence);
{
ProcessContext<float> c (random_data, frames, 1);
trimmer->process (c);
}
{
ProcessContext<float> c (random_data, frames, 1);
c.set_flag (ProcessContext<float>::EndOfInput);
trimmer->process (c);
}
CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames], random_data, frames));
CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames * 2], zero_data, silence));
}
private:
boost::shared_ptr<SilenceTrimmer<float> > trimmer;
boost::shared_ptr<AppendingVectorSink<float> > sink;
float * random_data;
float * zero_data;
float * half_random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (SilenceTrimmerTest);

View file

@ -0,0 +1,42 @@
#include "utils.h"
#include "audiographer/sndfile_writer.h"
using namespace AudioGrapher;
class SndfileWriterTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (SndfileWriterTest);
CPPUNIT_TEST (testProcess);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
}
void tearDown()
{
delete [] random_data;
}
void testProcess()
{
uint channels = 2;
std::string filename ("test.wav");
writer.reset (new SndfileWriter<float>(channels, 44100, SF_FORMAT_WAV | SF_FORMAT_FLOAT, filename));
ProcessContext<float> c (random_data, frames, channels);
c.set_flag (ProcessContext<float>::EndOfInput);
writer->process (c);
}
private:
boost::shared_ptr<SndfileWriter<float> > writer;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (SndfileWriterTest);

View file

@ -0,0 +1,101 @@
#include "utils.h"
#include "audiographer/sr_converter.h"
using namespace AudioGrapher;
class SampleRateConverterTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (SampleRateConverterTest);
CPPUNIT_TEST (testNoConversion);
CPPUNIT_TEST (testUpsampleLength);
CPPUNIT_TEST (testDownsampleLength);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data(frames);
sink.reset (new AppendingVectorSink<float>());
converter.reset (new SampleRateConverter (1));
}
void tearDown()
{
delete [] random_data;
}
void testNoConversion()
{
assert (frames % 2 == 0);
nframes_t const half_frames = frames / 2;
nframes_t frames_output = 0;
converter->init (44100, 44100);
converter->add_output (sink);
ProcessContext<float> c (random_data, half_frames, 1);
converter->process (c);
ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
c2.set_flag (ProcessContext<float>::EndOfInput);
converter->process (c2);
frames_output = sink->get_data().size();
CPPUNIT_ASSERT_EQUAL (frames, frames_output);
CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
}
void testUpsampleLength()
{
assert (frames % 2 == 0);
nframes_t const half_frames = frames / 2;
nframes_t frames_output = 0;
converter->init (44100, 88200);
converter->allocate_buffers (half_frames);
converter->add_output (sink);
ProcessContext<float> c (random_data, half_frames, 1);
converter->process (c);
ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
c2.set_flag (ProcessContext<float>::EndOfInput);
converter->process (c2);
frames_output = sink->get_data().size();
nframes_t tolerance = 3;
CPPUNIT_ASSERT (2 * frames - tolerance < frames_output && frames_output < 2 * frames + tolerance);
}
void testDownsampleLength()
{
assert (frames % 2 == 0);
nframes_t const half_frames = frames / 2;
nframes_t frames_output = 0;
converter->init (88200, 44100);
converter->allocate_buffers (half_frames);
converter->add_output (sink);
ProcessContext<float> c (random_data, half_frames, 1);
converter->process (c);
ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
c2.set_flag (ProcessContext<float>::EndOfInput);
converter->process (c2);
frames_output = sink->get_data().size();
nframes_t tolerance = 3;
CPPUNIT_ASSERT (half_frames - tolerance < frames_output && frames_output < half_frames + tolerance);
}
private:
boost::shared_ptr<SampleRateConverter > converter;
boost::shared_ptr<AppendingVectorSink<float> > sink;
float * random_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (SampleRateConverterTest);

View file

@ -0,0 +1,14 @@
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
#include <glibmm/thread.h>
int main()
{
Glib::thread_init();
CppUnit::TextUi::TestRunner runner;
CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest (registry.makeTest());
runner.run();
return 0;
}

View file

@ -0,0 +1,155 @@
#include "utils.h"
#include "audiographer/threader.h"
using namespace AudioGrapher;
class ThreaderTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE (ThreaderTest);
CPPUNIT_TEST (testProcess);
CPPUNIT_TEST (testRemoveOutput);
CPPUNIT_TEST (testClearOutputs);
CPPUNIT_TEST (testExceptions);
CPPUNIT_TEST_SUITE_END ();
public:
void setUp()
{
frames = 128;
random_data = TestUtils::init_random_data (frames, 1.0);
zero_data = new float[frames];
memset (zero_data, 0, frames * sizeof(float));
thread_pool = new Glib::ThreadPool (3);
threader.reset (new Threader<float> (*thread_pool));
sink_a.reset (new VectorSink<float>());
sink_b.reset (new VectorSink<float>());
sink_c.reset (new VectorSink<float>());
sink_d.reset (new VectorSink<float>());
sink_e.reset (new VectorSink<float>());
sink_f.reset (new VectorSink<float>());
throwing_sink.reset (new ThrowingSink<float>());
}
void tearDown()
{
delete [] random_data;
delete [] zero_data;
thread_pool->shutdown();
delete thread_pool;
}
void testProcess()
{
threader->add_output (sink_a);
threader->add_output (sink_b);
threader->add_output (sink_c);
threader->add_output (sink_d);
threader->add_output (sink_e);
threader->add_output (sink_f);
ProcessContext<float> c (random_data, frames, 1);
threader->process (c);
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_d->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
}
void testRemoveOutput()
{
threader->add_output (sink_a);
threader->add_output (sink_b);
threader->add_output (sink_c);
threader->add_output (sink_d);
threader->add_output (sink_e);
threader->add_output (sink_f);
ProcessContext<float> c (random_data, frames, 1);
threader->process (c);
// Remove a, b and f
threader->remove_output (sink_a);
threader->remove_output (sink_b);
threader->remove_output (sink_f);
ProcessContext<float> zc (zero_data, frames, 1);
threader->process (zc);
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_c->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_d->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_e->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
}
void testClearOutputs()
{
threader->add_output (sink_a);
threader->add_output (sink_b);
threader->add_output (sink_c);
threader->add_output (sink_d);
threader->add_output (sink_e);
threader->add_output (sink_f);
ProcessContext<float> c (random_data, frames, 1);
threader->process (c);
threader->clear_outputs();
ProcessContext<float> zc (zero_data, frames, 1);
threader->process (zc);
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_d->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
}
void testExceptions()
{
threader->add_output (sink_a);
threader->add_output (sink_b);
threader->add_output (sink_c);
threader->add_output (throwing_sink);
threader->add_output (sink_e);
threader->add_output (throwing_sink);
ProcessContext<float> c (random_data, frames, 1);
CPPUNIT_ASSERT_THROW (threader->process (c), Exception);
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
}
private:
Glib::ThreadPool * thread_pool;
boost::shared_ptr<Threader<float> > threader;
boost::shared_ptr<VectorSink<float> > sink_a;
boost::shared_ptr<VectorSink<float> > sink_b;
boost::shared_ptr<VectorSink<float> > sink_c;
boost::shared_ptr<VectorSink<float> > sink_d;
boost::shared_ptr<VectorSink<float> > sink_e;
boost::shared_ptr<VectorSink<float> > sink_f;
boost::shared_ptr<ThrowingSink<float> > throwing_sink;
float * random_data;
float * zero_data;
nframes_t frames;
};
CPPUNIT_TEST_SUITE_REGISTRATION (ThreaderTest);

View file

@ -0,0 +1,119 @@
#ifndef AUDIOGRAPHER_TESTS_UTILS_H
#define AUDIOGRAPHER_TESTS_UTILS_H
// Includes we want almost always
#include <cppunit/extensions/HelperMacros.h>
#include <boost/shared_ptr.hpp>
// includes used in this file
#include "audiographer/sink.h"
#include "audiographer/exception.h"
#include <vector>
#include <cstring>
#include <cstdlib>
#include <ctime>
using AudioGrapher::nframes_t;
struct TestUtils
{
template<typename T>
static bool array_equals (T const * a, T const * b, nframes_t frames)
{
for (nframes_t i = 0; i < frames; ++i) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
template<typename T>
static bool array_filled (T const * array, nframes_t frames)
{
for (nframes_t i = 0; i < frames; ++i) {
if (array[i] == static_cast<T> (0.0)) {
return false;
}
}
return true;
}
/// Generate random data, all samples guaranteed not to be 0.0, 1.0 or -1.0
static float * init_random_data (nframes_t frames, float range = 1.0)
{
unsigned int const granularity = 4096;
float * data = new float[frames];
srand (std::time (NULL));
for (nframes_t i = 0; i < frames; ++i) {
do {
int biased_int = (rand() % granularity) - (granularity / 2);
data[i] = (range * biased_int) / granularity;
} while (data[i] == 0.0 || data[i] == 1.0 || data[i] == -1.0);
}
return data;
}
};
template<typename T>
class VectorSink : public AudioGrapher::Sink<T>
{
public:
virtual void process (AudioGrapher::ProcessContext<T> const & c)
{
data.resize (c.frames());
memcpy (&data[0], c.data(), c.frames() * sizeof(T));
}
void process (AudioGrapher::ProcessContext<T> & c) { AudioGrapher::Sink<T>::process (c); }
using AudioGrapher::Sink<T>::process;
std::vector<T> const & get_data() const { return data; }
T const * get_array() const { return &data[0]; }
void reset() { data.clear(); }
protected:
std::vector<T> data;
};
template<typename T>
class AppendingVectorSink : public VectorSink<T>
{
public:
void process (AudioGrapher::ProcessContext<T> const & c)
{
std::vector<T> & data (VectorSink<T>::data);
data.resize (total_frames + c.frames());
memcpy (&data[total_frames], c.data(), c.frames() * sizeof(T));
total_frames += c.frames();
}
using AudioGrapher::Sink<T>::process;
void reset ()
{
total_frames = 0;
VectorSink<T>::reset();
}
private:
nframes_t total_frames;
};
template<typename T>
class ThrowingSink : public AudioGrapher::Sink<T>
{
public:
void process (AudioGrapher::ProcessContext<T> const &)
{
throw AudioGrapher::Exception(*this, "ThrowingSink threw!");
}
using AudioGrapher::Sink<T>::process;
};
#endif // AUDIOGRAPHER_TESTS_UTILS_H

118
libs/audiographer/wscript Normal file
View file

@ -0,0 +1,118 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import autowaf
# Version of this package (even if built as a child)
AUDIOGRAPHER_VERSION = '0.0.0'
# Library version (UNIX style major, minor, micro)
# major increment <=> incompatible changes
# minor increment <=> compatible changes (additions)
# micro increment <=> no interface changes
# Version history:
# 0.0.0 = 0,0,0
AUDIOGRAPHER_LIB_VERSION = '0.0.0'
# Variables for 'waf dist'
APPNAME = 'audiographer'
VERSION = AUDIOGRAPHER_VERSION
# Mandatory variables
srcdir = '.'
blddir = 'build'
def set_options(opt):
autowaf.set_options(opt)
def configure(conf):
autowaf.configure(conf)
conf.check_tool('compiler_cxx')
autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
autowaf.check_pkg(conf, 'sigc++-2.0', uselib_store='SIGCPP', atleast_version='2.0', mandatory=False)
autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2', mandatory=False)
autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0', mandatory=False)
autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0', mandatory=False)
autowaf.check_pkg(conf, 'samplerate', uselib_store='SAMPLERATE', atleast_version='0.1.7', mandatory=False)
autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', atleast_version='1.0.18', mandatory=False)
# Boost headers
autowaf.check_header(conf, 'boost/shared_ptr.hpp')
autowaf.check_header(conf, 'boost/format.hpp')
def build(bld):
# Headers
#bld.install_files('${INCLUDEDIR}/audiographer', 'audiographer/*.h')
bld.env['HAVE_ALL_GTHREAD'] = bld.env['HAVE_GLIB'] and bld.env['HAVE_GLIBMM'] and bld.env['HAVE_GTHREAD']
audiographer = bld.new_task_gen('cxx', 'shlib')
audiographer.source = '''
src/gdither/gdither.cc
src/sample_format_converter.cc
src/routines.cc
src/utils.cc
'''
if bld.env['HAVE_SNDFILE']:
audiographer.source += '''
src/sndfile_base.cc
src/sndfile_writer.cc
src/sndfile_reader.cc
'''
if bld.env['HAVE_SAMPLERATE']:
audiographer.source += '''
src/sr_converter.cc
'''
audiographer.name = 'libaudiographer'
audiographer.target = 'audiographer'
audiographer.export_incdirs = ['.', './src']
audiographer.includes = ['.', './src']
audiographer.uselib = 'GLIB GLIBMM GTHREAD SAMPLERATE SNDFILE'
audiographer.vnum = AUDIOGRAPHER_LIB_VERSION
audiographer.install_path = '${LIBDIR}'
if bld.env['BUILD_TESTS'] and bld.env['HAVE_CPPUNIT']:
# Unit tests
obj = bld.new_task_gen('cxx', 'program')
obj.source = '''
tests/identity_vertex_test.cc
tests/interleaver_test.cc
tests/deinterleaver_test.cc
tests/interleaver_deinterleaver_test.cc
tests/chunker_test.cc
tests/sample_format_converter_test.cc
tests/test_runner.cc
tests/peak_reader_test.cc
tests/normalizer_test.cc
tests/silence_trimmer_test.cc
'''
if bld.env['HAVE_ALL_GTHREAD']:
obj.source += '''
tests/threader_test.cc
'''
if bld.env['HAVE_SNDFILE']:
obj.source += '''
tests/sndfile_writer_test.cc
'''
if bld.env['HAVE_SAMPLERATE']:
obj.source += '''
tests/sr_converter_test.cc
'''
obj.uselib_local = 'libaudiographer'
obj.uselib = 'CPPUNIT GLIBMM'
obj.target = 'run-tests'
obj.install_path = ''
def shutdown():
autowaf.shutdown()

View file

@ -28,6 +28,7 @@ children = [
'libs/ardour',
'libs/gtkmm2ext',
'libs/clearlooks-newer',
'libs/audiographer',
'gtk2_ardour'
]