summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk2_ardour/ardev_common.sh.in2
-rw-r--r--gtk2_ardour/export_dialog.cc4
-rw-r--r--libs/ardour/ardour/audioengine.h8
-rw-r--r--libs/ardour/ardour/export.h100
-rw-r--r--libs/ardour/ardour/export_channel.h4
-rw-r--r--libs/ardour/ardour/export_channel_configuration.h52
-rw-r--r--libs/ardour/ardour/export_file_io.h177
-rw-r--r--libs/ardour/ardour/export_format_base.h12
-rw-r--r--libs/ardour/ardour/export_format_specification.h3
-rw-r--r--libs/ardour/ardour/export_graph_builder.h225
-rw-r--r--libs/ardour/ardour/export_handler.h76
-rw-r--r--libs/ardour/ardour/export_processor.h127
-rw-r--r--libs/ardour/ardour/export_profile_manager.h3
-rw-r--r--libs/ardour/ardour/export_timespan.h19
-rw-r--r--libs/ardour/ardour/export_utilities.h146
-rw-r--r--libs/ardour/ardour/graph.h101
-rw-r--r--libs/ardour/audioengine.cc4
-rw-r--r--libs/ardour/export_channel_configuration.cc122
-rw-r--r--libs/ardour/export_file_io.cc445
-rw-r--r--libs/ardour/export_graph_builder.cc413
-rw-r--r--libs/ardour/export_handler.cc227
-rw-r--r--libs/ardour/export_processor.cc295
-rw-r--r--libs/ardour/export_profile_manager.cc27
-rw-r--r--libs/ardour/export_timespan.cc62
-rw-r--r--libs/ardour/export_utilities.cc352
-rw-r--r--libs/ardour/session_export.cc5
-rw-r--r--libs/ardour/wscript7
-rw-r--r--libs/audiographer/COPYING340
-rw-r--r--libs/audiographer/audiographer/chunker.h54
-rw-r--r--libs/audiographer/audiographer/deinterleaver-inl.h78
-rw-r--r--libs/audiographer/audiographer/deinterleaver.h46
-rw-r--r--libs/audiographer/audiographer/exception.h52
-rw-r--r--libs/audiographer/audiographer/identity_vertex.h21
-rw-r--r--libs/audiographer/audiographer/interleaver-inl.h92
-rw-r--r--libs/audiographer/audiographer/interleaver.h71
-rw-r--r--libs/audiographer/audiographer/listed_source.h50
-rw-r--r--libs/audiographer/audiographer/normalizer.h82
-rw-r--r--libs/audiographer/audiographer/peak_reader.h38
-rw-r--r--libs/audiographer/audiographer/process_context.h154
-rw-r--r--libs/audiographer/audiographer/routines.h53
-rw-r--r--libs/audiographer/audiographer/sample_format_converter.h67
-rw-r--r--libs/audiographer/audiographer/silence_trimmer.h191
-rw-r--r--libs/audiographer/audiographer/sink.h42
-rw-r--r--libs/audiographer/audiographer/sndfile_base.h30
-rw-r--r--libs/audiographer/audiographer/sndfile_reader.h40
-rw-r--r--libs/audiographer/audiographer/sndfile_writer.h29
-rw-r--r--libs/audiographer/audiographer/source.h28
-rw-r--r--libs/audiographer/audiographer/sr_converter.h51
-rw-r--r--libs/audiographer/audiographer/threader.h120
-rw-r--r--libs/audiographer/audiographer/tmp_file.h25
-rw-r--r--libs/audiographer/audiographer/types.h32
-rw-r--r--libs/audiographer/audiographer/utils.h59
-rw-r--r--libs/audiographer/autowaf.py374
-rw-r--r--libs/audiographer/src/gdither/gdither.cc (renamed from libs/ardour/gdither.cc)14
-rw-r--r--libs/audiographer/src/gdither/gdither.h (renamed from libs/ardour/ardour/gdither.h)4
-rw-r--r--libs/audiographer/src/gdither/gdither_types.h (renamed from libs/ardour/ardour/gdither_types.h)0
-rw-r--r--libs/audiographer/src/gdither/gdither_types_internal.h (renamed from libs/ardour/ardour/gdither_types_internal.h)0
-rw-r--r--libs/audiographer/src/gdither/noise.h38
-rw-r--r--libs/audiographer/src/routines.cc7
-rw-r--r--libs/audiographer/src/sample_format_converter.cc190
-rw-r--r--libs/audiographer/src/sndfile_base.cc56
-rw-r--r--libs/audiographer/src/sndfile_reader.cc67
-rw-r--r--libs/audiographer/src/sndfile_writer.cc73
-rw-r--r--libs/audiographer/src/sr_converter.cc218
-rw-r--r--libs/audiographer/src/utils.cc14
-rw-r--r--libs/audiographer/tests/chunker_test.cc112
-rw-r--r--libs/audiographer/tests/deinterleaver_test.cc133
-rw-r--r--libs/audiographer/tests/identity_vertex_test.cc99
-rw-r--r--libs/audiographer/tests/interleaver_deinterleaver_test.cc120
-rw-r--r--libs/audiographer/tests/interleaver_test.cc132
-rw-r--r--libs/audiographer/tests/normalizer_test.cc60
-rw-r--r--libs/audiographer/tests/peak_reader_test.cc53
-rw-r--r--libs/audiographer/tests/sample_format_converter_test.cc209
-rw-r--r--libs/audiographer/tests/silence_trimmer_test.cc196
-rw-r--r--libs/audiographer/tests/sndfile_writer_test.cc42
-rw-r--r--libs/audiographer/tests/sr_converter_test.cc101
-rw-r--r--libs/audiographer/tests/test_runner.cc14
-rw-r--r--libs/audiographer/tests/threader_test.cc155
-rw-r--r--libs/audiographer/tests/utils.h119
-rw-r--r--libs/audiographer/wscript118
-rw-r--r--wscript1
81 files changed, 5369 insertions, 2213 deletions
diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in
index 9fb72fb57f..10a37a6ae8 100644
--- a/gtk2_ardour/ardev_common.sh.in
+++ b/gtk2_ardour/ardev_common.sh.in
@@ -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
diff --git a/gtk2_ardour/export_dialog.cc b/gtk2_ardour/export_dialog.cc
index 0f5c6a4551..f2feaee44d 100644
--- a/gtk2_ardour/export_dialog.cc
+++ b/gtk2_ardour/export_dialog.cc
@@ -311,6 +311,10 @@ ExportDialog::show_progress ()
usleep (10000);
}
}
+
+ if (!status->aborted()) {
+ status->finish ();
+ }
}
gint
diff --git a/libs/ardour/ardour/audioengine.h b/libs/ardour/ardour/audioengine.h
index b22ca9790f..cf0eafe288 100644
--- a/libs/ardour/ardour/audioengine.h
+++ b/libs/ardour/ardour/audioengine.h
@@ -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
diff --git a/libs/ardour/ardour/export.h b/libs/ardour/ardour/export.h
deleted file mode 100644
index ee533d4c6d..0000000000
--- a/libs/ardour/ardour/export.h
+++ /dev/null
@@ -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__ */
diff --git a/libs/ardour/ardour/export_channel.h b/libs/ardour/ardour/export_channel.h
index 73e3406869..51ecabee25 100644
--- a/libs/ardour/ardour/export_channel.h
+++ b/libs/ardour/ardour/export_channel.h
@@ -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 () {}
diff --git a/libs/ardour/ardour/export_channel_configuration.h b/libs/ardour/ardour/export_channel_configuration.h
index 9ca49f452d..4b027cc020 100644
--- a/libs/ardour/ardour/export_channel_configuration.h
+++ b/libs/ardour/ardour/export_channel_configuration.h
@@ -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;
};
diff --git a/libs/ardour/ardour/export_file_io.h b/libs/ardour/ardour/export_file_io.h
deleted file mode 100644
index 48d5984f78..0000000000
--- a/libs/ardour/ardour/export_file_io.h
+++ /dev/null
@@ -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__ */
diff --git a/libs/ardour/ardour/export_format_base.h b/libs/ardour/ardour/export_format_base.h
index dceb943e62..08bcbfb2bd 100644
--- a/libs/ardour/ardour/export_format_base.h
+++ b/libs/ardour/ardour/export_format_base.h
@@ -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 {
diff --git a/libs/ardour/ardour/export_format_specification.h b/libs/ardour/ardour/export_format_specification.h
index 628c70d25a..3b9382237c 100644
--- a/libs/ardour/ardour/export_format_specification.h
+++ b/libs/ardour/ardour/export_format_specification.h
@@ -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; }
diff --git a/libs/ardour/ardour/export_graph_builder.h b/libs/ardour/ardour/export_graph_builder.h
new file mode 100644
index 0000000000..1244afd647
--- /dev/null
+++ b/libs/ardour/ardour/export_graph_builder.h
@@ -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__ */
diff --git a/libs/ardour/ardour/export_handler.h b/libs/ardour/ardour/export_handler.h
index bbf1f7e208..f7f3b863b9 100644
--- a/libs/ardour/ardour/export_handler.h
+++ b/libs/ardour/ardour/export_handler.h
@@ -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
{
- private:
-
- /* Stuff for export configs
- * The multimap maps timespans to file specifications
- */
-
+ public:
struct FileSpec {
-
- FileSpec (ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename) :
- channel_config (channel_config),
- format (format),
- filename (filename)
- {}
+ 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
+ */
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
diff --git a/libs/ardour/ardour/export_processor.h b/libs/ardour/ardour/export_processor.h
deleted file mode 100644
index 493dd3231f..0000000000
--- a/libs/ardour/ardour/export_processor.h
+++ /dev/null
@@ -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__ */
diff --git a/libs/ardour/ardour/export_profile_manager.h b/libs/ardour/ardour/export_profile_manager.h
index a29979460f..793ceac1f5 100644
--- a/libs/ardour/ardour/export_profile_manager.h
+++ b/libs/ardour/ardour/export_profile_manager.h
@@ -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 */
diff --git a/libs/ardour/ardour/export_timespan.h b/libs/ardour/ardour/export_timespan.h
index 7b7ae7cd99..9053ace0f9 100644
--- a/libs/ardour/ardour/export_timespan.h
+++ b/libs/ardour/ardour/export_timespan.h
@@ -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;
diff --git a/libs/ardour/ardour/export_utilities.h b/libs/ardour/ardour/export_utilities.h
deleted file mode 100644
index 5733ebb403..0000000000
--- a/libs/ardour/ardour/export_utilities.h
+++ /dev/null
@@ -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__ */
diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h
deleted file mode 100644
index 5d6919f56e..0000000000
--- a/libs/ardour/ardour/graph.h
+++ /dev/null
@@ -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__ */
-
diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc
index 900f0893da..634d62cc0b 100644
--- a/libs/ardour/audioengine.cc
+++ b/libs/ardour/audioengine.cc
@@ -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) {
diff --git a/libs/ardour/export_channel_configuration.cc b/libs/ardour/export_channel_configuration.cc
index 89a4952feb..cc68356d82 100644
--- a/libs/ardour/export_channel_configuration.cc
+++ b/libs/ardour/export_channel_configuration.cc
@@ -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
diff --git a/libs/ardour/export_file_io.cc b/libs/ardour/export_file_io.cc
deleted file mode 100644
index 4c39e5c907..0000000000
--- a/libs/ardour/export_file_io.cc
+++ /dev/null
@@ -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
diff --git a/libs/ardour/export_graph_builder.cc b/libs/ardour/export_graph_builder.cc
new file mode 100644
index 0000000000..5b5ec16b36
--- /dev/null
+++ b/libs/ardour/export_graph_builder.cc
@@ -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
diff --git a/libs/ardour/export_handler.cc b/libs/ardour/export_handler.cc
index b78fc20f7e..3ce07cf44d 100644
--- a/libs/ardour/export_handler.cc
+++ b/libs/ardour/export_handler.cc
@@ -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));
-
- ExportProcessor::WritingFile.connect_same_thread (files_written_connection, boost::bind (&ExportHandler::add_file, this, _1));
-}
+ExportHandler::ExportHandler (Session & session)
+ : ExportElementFactory (session)
+ , session (session)
+ , graph_builder (new ExportGraphBuilder (session))
+ , export_status (session.get_export_status ())
+ , realtime (false)
-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)
+ExportHandler::~ExportHandler ()
{
- 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
diff --git a/libs/ardour/export_processor.cc b/libs/ardour/export_processor.cc
deleted file mode 100644
index c7fcbd55aa..0000000000
--- a/libs/ardour/export_processor.cc
+++ /dev/null
@@ -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
diff --git a/libs/ardour/export_profile_manager.cc b/libs/ardour/export_profile_manager.cc
index 2b1b15dbce..244ec788ae 100644
--- a/libs/ardour/export_profile_manager.cc
+++ b/libs/ardour/export_profile_manager.cc
@@ -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
diff --git a/libs/ardour/export_timespan.cc b/libs/ardour/export_timespan.cc
index d638c84b16..948bf768d5 100644
--- a/libs/ardour/export_timespan.cc
+++ b/libs/ardour/export_timespan.cc
@@ -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
@@ -42,33 +41,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)
{
start_frame = start;
@@ -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
diff --git a/libs/ardour/export_utilities.cc b/libs/ardour/export_utilities.cc
deleted file mode 100644
index a91912359e..0000000000
--- a/libs/ardour/export_utilities.cc
+++ /dev/null
@@ -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);
-}
-
-};
diff --git a/libs/ardour/session_export.cc b/libs/ardour/session_export.cc
index 840c752bad..86a6250ffd 100644
--- a/libs/ardour/session_export.cc
+++ b/libs/ardour/session_export.cc
@@ -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);
}
}
diff --git a/libs/ardour/wscript b/libs/ardour/wscript
index 97ad05cb68..2e2211b611 100644
--- a/libs/ardour/wscript
+++ b/libs/ardour/wscript
@@ -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"']
diff --git a/libs/audiographer/COPYING b/libs/audiographer/COPYING
new file mode 100644
index 0000000000..d60c31a97a
--- /dev/null
+++ b/libs/audiographer/COPYING
@@ -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.
diff --git a/libs/audiographer/audiographer/chunker.h b/libs/audiographer/audiographer/chunker.h
new file mode 100644
index 0000000000..afce921cc2
--- /dev/null
+++ b/libs/audiographer/audiographer/chunker.h
@@ -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
+
diff --git a/libs/audiographer/audiographer/deinterleaver-inl.h b/libs/audiographer/audiographer/deinterleaver-inl.h
new file mode 100644
index 0000000000..f93fdc53a4
--- /dev/null
+++ b/libs/audiographer/audiographer/deinterleaver-inl.h
@@ -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;
+}
diff --git a/libs/audiographer/audiographer/deinterleaver.h b/libs/audiographer/audiographer/deinterleaver.h
new file mode 100644
index 0000000000..3a4aa53c43
--- /dev/null
+++ b/libs/audiographer/audiographer/deinterleaver.h
@@ -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
diff --git a/libs/audiographer/audiographer/exception.h b/libs/audiographer/audiographer/exception.h
new file mode 100644
index 0000000000..a179b30f91
--- /dev/null
+++ b/libs/audiographer/audiographer/exception.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/identity_vertex.h b/libs/audiographer/audiographer/identity_vertex.h
new file mode 100644
index 0000000000..b53bd96851
--- /dev/null
+++ b/libs/audiographer/audiographer/identity_vertex.h
@@ -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
diff --git a/libs/audiographer/audiographer/interleaver-inl.h b/libs/audiographer/audiographer/interleaver-inl.h
new file mode 100644
index 0000000000..07e93b2a85
--- /dev/null
+++ b/libs/audiographer/audiographer/interleaver-inl.h
@@ -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;
+}
diff --git a/libs/audiographer/audiographer/interleaver.h b/libs/audiographer/audiographer/interleaver.h
new file mode 100644
index 0000000000..3d51fed5a5
--- /dev/null
+++ b/libs/audiographer/audiographer/interleaver.h
@@ -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
diff --git a/libs/audiographer/audiographer/listed_source.h b/libs/audiographer/audiographer/listed_source.h
new file mode 100644
index 0000000000..bc8f144d84
--- /dev/null
+++ b/libs/audiographer/audiographer/listed_source.h
@@ -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
+
diff --git a/libs/audiographer/audiographer/normalizer.h b/libs/audiographer/audiographer/normalizer.h
new file mode 100644
index 0000000000..dcaac75568
--- /dev/null
+++ b/libs/audiographer/audiographer/normalizer.h
@@ -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
diff --git a/libs/audiographer/audiographer/peak_reader.h b/libs/audiographer/audiographer/peak_reader.h
new file mode 100644
index 0000000000..e5aaf7081c
--- /dev/null
+++ b/libs/audiographer/audiographer/peak_reader.h
@@ -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
diff --git a/libs/audiographer/audiographer/process_context.h b/libs/audiographer/audiographer/process_context.h
new file mode 100644
index 0000000000..080e492944
--- /dev/null
+++ b/libs/audiographer/audiographer/process_context.h
@@ -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
diff --git a/libs/audiographer/audiographer/routines.h b/libs/audiographer/audiographer/routines.h
new file mode 100644
index 0000000000..9ae6b7a255
--- /dev/null
+++ b/libs/audiographer/audiographer/routines.h
@@ -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
diff --git a/libs/audiographer/audiographer/sample_format_converter.h b/libs/audiographer/audiographer/sample_format_converter.h
new file mode 100644
index 0000000000..12976ffdd2
--- /dev/null
+++ b/libs/audiographer/audiographer/sample_format_converter.h
@@ -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
diff --git a/libs/audiographer/audiographer/silence_trimmer.h b/libs/audiographer/audiographer/silence_trimmer.h
new file mode 100644
index 0000000000..46190cfeae
--- /dev/null
+++ b/libs/audiographer/audiographer/silence_trimmer.h
@@ -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
diff --git a/libs/audiographer/audiographer/sink.h b/libs/audiographer/audiographer/sink.h
new file mode 100644
index 0000000000..486fccfd13
--- /dev/null
+++ b/libs/audiographer/audiographer/sink.h
@@ -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
+
diff --git a/libs/audiographer/audiographer/sndfile_base.h b/libs/audiographer/audiographer/sndfile_base.h
new file mode 100644
index 0000000000..fd6c5f3552
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_base.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/sndfile_reader.h b/libs/audiographer/audiographer/sndfile_reader.h
new file mode 100644
index 0000000000..9e47da56b2
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_reader.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/sndfile_writer.h b/libs/audiographer/audiographer/sndfile_writer.h
new file mode 100644
index 0000000000..a92da982c1
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_writer.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/source.h b/libs/audiographer/audiographer/source.h
new file mode 100644
index 0000000000..8ffad204ba
--- /dev/null
+++ b/libs/audiographer/audiographer/source.h
@@ -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
+
diff --git a/libs/audiographer/audiographer/sr_converter.h b/libs/audiographer/audiographer/sr_converter.h
new file mode 100644
index 0000000000..073fdc8247
--- /dev/null
+++ b/libs/audiographer/audiographer/sr_converter.h
@@ -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
diff --git a/libs/audiographer/audiographer/threader.h b/libs/audiographer/audiographer/threader.h
new file mode 100644
index 0000000000..e6c3aa97bf
--- /dev/null
+++ b/libs/audiographer/audiographer/threader.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/tmp_file.h b/libs/audiographer/audiographer/tmp_file.h
new file mode 100644
index 0000000000..a5537e3a69
--- /dev/null
+++ b/libs/audiographer/audiographer/tmp_file.h
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/audiographer/types.h b/libs/audiographer/audiographer/types.h
new file mode 100644
index 0000000000..f48f8f807a
--- /dev/null
+++ b/libs/audiographer/audiographer/types.h
@@ -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__ \ No newline at end of file
diff --git a/libs/audiographer/audiographer/utils.h b/libs/audiographer/audiographer/utils.h
new file mode 100644
index 0000000000..2f9c51918b
--- /dev/null
+++ b/libs/audiographer/audiographer/utils.h
@@ -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
diff --git a/libs/audiographer/autowaf.py b/libs/audiographer/autowaf.py
new file mode 100644
index 0000000000..aaac8efc04
--- /dev/null
+++ b/libs/audiographer/autowaf.py
@@ -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
+
diff --git a/libs/ardour/gdither.cc b/libs/audiographer/src/gdither/gdither.cc
index f09e06edc2..966da47b06 100644
--- a/libs/ardour/gdither.cc
+++ b/libs/audiographer/src/gdither/gdither.cc
@@ -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;
diff --git a/libs/ardour/ardour/gdither.h b/libs/audiographer/src/gdither/gdither.h
index 67efcc3583..d2b2657f32 100644
--- a/libs/ardour/ardour/gdither.h
+++ b/libs/audiographer/src/gdither/gdither.h
@@ -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
}
diff --git a/libs/ardour/ardour/gdither_types.h b/libs/audiographer/src/gdither/gdither_types.h
index bcc0097d7f..bcc0097d7f 100644
--- a/libs/ardour/ardour/gdither_types.h
+++ b/libs/audiographer/src/gdither/gdither_types.h
diff --git a/libs/ardour/ardour/gdither_types_internal.h b/libs/audiographer/src/gdither/gdither_types_internal.h
index 6cb0c48af9..6cb0c48af9 100644
--- a/libs/ardour/ardour/gdither_types_internal.h
+++ b/libs/audiographer/src/gdither/gdither_types_internal.h
diff --git a/libs/audiographer/src/gdither/noise.h b/libs/audiographer/src/gdither/noise.h
new file mode 100644
index 0000000000..96a582ef9b
--- /dev/null
+++ b/libs/audiographer/src/gdither/noise.h
@@ -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
diff --git a/libs/audiographer/src/routines.cc b/libs/audiographer/src/routines.cc
new file mode 100644
index 0000000000..b97653be75
--- /dev/null
+++ b/libs/audiographer/src/routines.cc
@@ -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;
+}
diff --git a/libs/audiographer/src/sample_format_converter.cc b/libs/audiographer/src/sample_format_converter.cc
new file mode 100644
index 0000000000..5b2d3d6e8c
--- /dev/null
+++ b/libs/audiographer/src/sample_format_converter.cc
@@ -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
diff --git a/libs/audiographer/src/sndfile_base.cc b/libs/audiographer/src/sndfile_base.cc
new file mode 100644
index 0000000000..8d12f9341b
--- /dev/null
+++ b/libs/audiographer/src/sndfile_base.cc
@@ -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
diff --git a/libs/audiographer/src/sndfile_reader.cc b/libs/audiographer/src/sndfile_reader.cc
new file mode 100644
index 0000000000..0508110314
--- /dev/null
+++ b/libs/audiographer/src/sndfile_reader.cc
@@ -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 \ No newline at end of file
diff --git a/libs/audiographer/src/sndfile_writer.cc b/libs/audiographer/src/sndfile_writer.cc
new file mode 100644
index 0000000000..d12d6b943b
--- /dev/null
+++ b/libs/audiographer/src/sndfile_writer.cc
@@ -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
diff --git a/libs/audiographer/src/sr_converter.cc b/libs/audiographer/src/sr_converter.cc
new file mode 100644
index 0000000000..c61b3d0728
--- /dev/null
+++ b/libs/audiographer/src/sr_converter.cc
@@ -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
diff --git a/libs/audiographer/src/utils.cc b/libs/audiographer/src/utils.cc
new file mode 100644
index 0000000000..018fad3113
--- /dev/null
+++ b/libs/audiographer/src/utils.cc
@@ -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;
+} \ No newline at end of file
diff --git a/libs/audiographer/tests/chunker_test.cc b/libs/audiographer/tests/chunker_test.cc
new file mode 100644
index 0000000000..ab0c04d7f9
--- /dev/null
+++ b/libs/audiographer/tests/chunker_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/deinterleaver_test.cc b/libs/audiographer/tests/deinterleaver_test.cc
new file mode 100644
index 0000000000..b0adbc0444
--- /dev/null
+++ b/libs/audiographer/tests/deinterleaver_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/identity_vertex_test.cc b/libs/audiographer/tests/identity_vertex_test.cc
new file mode 100644
index 0000000000..5a3ae7c9f2
--- /dev/null
+++ b/libs/audiographer/tests/identity_vertex_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/interleaver_deinterleaver_test.cc b/libs/audiographer/tests/interleaver_deinterleaver_test.cc
new file mode 100644
index 0000000000..5655253e62
--- /dev/null
+++ b/libs/audiographer/tests/interleaver_deinterleaver_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/interleaver_test.cc b/libs/audiographer/tests/interleaver_test.cc
new file mode 100644
index 0000000000..abe385699d
--- /dev/null
+++ b/libs/audiographer/tests/interleaver_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/normalizer_test.cc b/libs/audiographer/tests/normalizer_test.cc
new file mode 100644
index 0000000000..711e0018ca
--- /dev/null
+++ b/libs/audiographer/tests/normalizer_test.cc
@@ -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);
diff --git a/libs/audiographer/tests/peak_reader_test.cc b/libs/audiographer/tests/peak_reader_test.cc
new file mode 100644
index 0000000000..dce03b6caf
--- /dev/null
+++ b/libs/audiographer/tests/peak_reader_test.cc
@@ -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);
diff --git a/libs/audiographer/tests/sample_format_converter_test.cc b/libs/audiographer/tests/sample_format_converter_test.cc
new file mode 100644
index 0000000000..f723f7af53
--- /dev/null
+++ b/libs/audiographer/tests/sample_format_converter_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/silence_trimmer_test.cc b/libs/audiographer/tests/silence_trimmer_test.cc
new file mode 100644
index 0000000000..16234bec37
--- /dev/null
+++ b/libs/audiographer/tests/silence_trimmer_test.cc
@@ -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);
diff --git a/libs/audiographer/tests/sndfile_writer_test.cc b/libs/audiographer/tests/sndfile_writer_test.cc
new file mode 100644
index 0000000000..359d456f15
--- /dev/null
+++ b/libs/audiographer/tests/sndfile_writer_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/sr_converter_test.cc b/libs/audiographer/tests/sr_converter_test.cc
new file mode 100644
index 0000000000..59c05806c6
--- /dev/null
+++ b/libs/audiographer/tests/sr_converter_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/test_runner.cc b/libs/audiographer/tests/test_runner.cc
new file mode 100644
index 0000000000..6fed393dc8
--- /dev/null
+++ b/libs/audiographer/tests/test_runner.cc
@@ -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;
+}
diff --git a/libs/audiographer/tests/threader_test.cc b/libs/audiographer/tests/threader_test.cc
new file mode 100644
index 0000000000..ac5588d79c
--- /dev/null
+++ b/libs/audiographer/tests/threader_test.cc
@@ -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);
+
diff --git a/libs/audiographer/tests/utils.h b/libs/audiographer/tests/utils.h
new file mode 100644
index 0000000000..6b6a1fea0f
--- /dev/null
+++ b/libs/audiographer/tests/utils.h
@@ -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
diff --git a/libs/audiographer/wscript b/libs/audiographer/wscript
new file mode 100644
index 0000000000..ed8388f580
--- /dev/null
+++ b/libs/audiographer/wscript
@@ -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()
+
diff --git a/wscript b/wscript
index 375e769eca..1bf6a0a8fb 100644
--- a/wscript
+++ b/wscript
@@ -28,6 +28,7 @@ children = [
'libs/ardour',
'libs/gtkmm2ext',
'libs/clearlooks-newer',
+ 'libs/audiographer',
'gtk2_ardour'
]