diff options
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 ®istry = 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() + @@ -28,6 +28,7 @@ children = [ 'libs/ardour', 'libs/gtkmm2ext', 'libs/clearlooks-newer', + 'libs/audiographer', 'gtk2_ardour' ] |