diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2008-09-17 12:58:33 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2008-09-17 12:58:33 +0000 |
commit | f2b007195cd75b195e38a4cd7757debac73e7792 (patch) | |
tree | 90474413776806f02794602bbb495663e07a81ea /libs | |
parent | 6ba5125e991e08a9d117b39a4c337cf453fd015d (diff) |
new files from sakari, missed last time
git-svn-id: svn://localhost/ardour2/branches/3.0@3740 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs')
37 files changed, 9547 insertions, 0 deletions
diff --git a/libs/ardour/ardour/audiofile_tagger.h b/libs/ardour/ardour/audiofile_tagger.h new file mode 100644 index 0000000000..1f2cbf223c --- /dev/null +++ b/libs/ardour/ardour/audiofile_tagger.h @@ -0,0 +1,56 @@ +/* + 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_audiofile_tagger_h__ +#define __ardour_audiofile_tagger_h__ + +#include <string> + +#include <taglib/taglib.h> +#include <taglib/tag.h> +#include <taglib/xiphcomment.h> + +using std::string; + +namespace ARDOUR +{ + +class SessionMetadata; + +/// Clas with static functions for tagging audiofiles +class AudiofileTagger +{ + public: + + /* Tags file with metadata, return true on success */ + + static bool tag_file (string const & filename, SessionMetadata const & metadata); + + private: + + static bool tag_generic (TagLib::Tag & tag, SessionMetadata const & metadata); + static bool tag_vorbis_comment (TagLib::Ogg::XiphComment & tag, SessionMetadata const & metadata); +}; + + + +} // namespace ARDOUR + +#endif /* __ardour_audiofile_tagger_h__ */ diff --git a/libs/ardour/ardour/broadcast_info.h b/libs/ardour/ardour/broadcast_info.h new file mode 100644 index 0000000000..07522839fa --- /dev/null +++ b/libs/ardour/ardour/broadcast_info.h @@ -0,0 +1,95 @@ +/* + 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_broadcast_info_h__ +#define __ardour_broadcast_info_h__ + +#include <string> +#include <ctime> + +#include <sndfile.h> + +#include <ardour/types.h> + +using std::string; + +namespace ARDOUR +{ + +class Session; + +class BroadcastInfo +{ + public: + + /// Construct empty broadcast info + BroadcastInfo (); + ~BroadcastInfo (); + + /// Returns last error sring from libsndfile + string get_error () const { return error; } + + /* Convenience functions */ + + void set_from_session (Session const & session, int64_t time); + + /* Reading */ + + bool load_from_file (string const & filename); + bool load_from_file (SNDFILE* sf); + + string get_description () const; + int64_t get_time_reference () const; + struct tm get_origination_time () const; + string get_originator () const; + string get_originator_ref () const; + + /* Writing */ + + bool write_to_file (string const & filename); + bool write_to_file (SNDFILE* sf); + + void set_description (string const & desc); + void set_time_reference (int64_t when); + void set_origination_time (struct tm * now = 0); // if 0, use time generated at construction + void set_originator (string const & str = ""); + void set_originator_ref (string const & str = ""); + + /* State info */ + + /// Returns true if a info has been succesfully loaded or anything has been manually set + bool has_info () const { return _has_info; } + + private: + + SF_BROADCAST_INFO * info; + struct tm _time; + + void update_error (); + string error; + + bool _has_info; +}; + + + +} // namespace ARDOUR + +#endif /* __ardour_broadcast_info_h__ */ diff --git a/libs/ardour/ardour/export_channel_configuration.h b/libs/ardour/ardour/export_channel_configuration.h new file mode 100644 index 0000000000..8f2fcdbd28 --- /dev/null +++ b/libs/ardour/ardour/export_channel_configuration.h @@ -0,0 +1,138 @@ +/* + 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_channel_configuration_h__ +#define __ardour_export_channel_configuration_h__ + +#include <set> +#include <list> + +#include <glibmm/ustring.h> +#include <sigc++/signal.h> + +#include <ardour/export_status.h> +#include <ardour/ardour.h> + +using Glib::ustring; + +namespace ARDOUR +{ + +class ExportHandler; +class AudioPort; +class ExportChannel; +class ExportFormatSpecification; +class ExportFilename; +class ExportProcessor; +class ExportTimespan; + +class ExportChannel : public std::set<AudioPort *> +{ + public: + ExportChannel (); + ~ExportChannel (); + + void add_port (AudioPort * port) { if (port) { insert (port); } } + void read_ports (float * data, nframes_t frames) const; +}; + +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 (ExportStatus & status); + + public: + ~ExportChannelConfiguration (); + + typedef boost::shared_ptr<ExportChannel const> ChannelPtr; + typedef std::list<ChannelPtr> ChannelList; + + ChannelList const & get_channels () { return channels; } + bool all_channels_have_ports (); + + ustring name () const { return _name; } + void set_name (ustring name) { _name = name; } + void set_split (bool value) { split = value; } + + bool get_split () { return split; } + uint32_t get_n_chans () { return channels.size(); } + + void register_channel (ChannelPtr 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); + sigc::signal<void> FilesWritten; + + // Tells the handler the necessary information for it to handle tempfiles + void register_with_timespan (TimespanPtr timespan); + + void unregister_all (); + + private: + + // 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; + ExportStatus & status; + + bool files_written; + + TimespanPtr timespan; + ChannelList channels; + FileConfigList file_configs; + + bool split; // Split to mono files + ustring _name; +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_channel_configuration_h__ */ diff --git a/libs/ardour/ardour/export_failed.h b/libs/ardour/ardour/export_failed.h new file mode 100644 index 0000000000..de00fba87e --- /dev/null +++ b/libs/ardour/ardour/export_failed.h @@ -0,0 +1,61 @@ +/* + 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_failed_h__ +#define __ardour_export_failed_h__ + +#include <exception> +#include <string> +#include <pbd/error.h> + +#include "i18n.h" + +using namespace PBD; + +namespace ARDOUR +{ + +class ExportFailed : public std::exception +{ + public: + ExportFailed (std::string const & reason, std::string const & description) : + reason (reason.c_str()), + description (description.c_str()) + { + error << string_compose (_("Export failed: %1"), reason) << endmsg; + } + + ~ExportFailed () throw() { } + + const char* what() const throw() + { + return description; + } + + private: + + const char * reason; + const char * description; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_failed_h__ */ diff --git a/libs/ardour/ardour/export_file_io.h b/libs/ardour/ardour/export_file_io.h new file mode 100644 index 0000000000..e0b1c95323 --- /dev/null +++ b/libs/ardour/ardour/export_file_io.h @@ -0,0 +1,144 @@ +/* + 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 <sndfile.h> + +#include <ardour/graph.h> +#include <ardour/types.h> +#include <ardour/ardour.h> + +namespace ARDOUR +{ + +/// Common part for all export file writers +class ExportFileWriter +{ + public: + ExportFileWriter (string filename) : _filename (filename) {} + virtual ~ExportFileWriter () {} + + string filename () const { return _filename; } + nframes_t position () const { return _position; } + + void set_position (nframes_t position) { _position = position; } + + protected: + string _filename; + nframes_t _position; +}; + +/// Common interface for templated libsndfile writers +class SndfileWriterBase : public ExportFileWriter +{ + public: + SndfileWriterBase (int channels, nframes_t samplerate, int format, string const & path); + virtual ~SndfileWriterBase (); + + SNDFILE * get_sndfile () const { return sndfile; } + + protected: + SF_INFO sf_info; + SNDFILE * sndfile; +}; + + +/// Template parameter specific parts of sndfile writer +template <typename T> +class SndfileWriter : public SndfileWriterBase, public GraphSink<T> +{ + public: + SndfileWriter (int channels, nframes_t samplerate, int format, string const & path); + virtual ~SndfileWriter () {} + + nframes_t write (T * data, nframes_t frames); + + 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; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_file_io_h__ */ diff --git a/libs/ardour/ardour/export_filename.h b/libs/ardour/ardour/export_filename.h new file mode 100644 index 0000000000..e44c50f394 --- /dev/null +++ b/libs/ardour/ardour/export_filename.h @@ -0,0 +1,140 @@ +/* + 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_filename_h__ +#define __ardour_export_filename_h__ + +#include <glibmm/ustring.h> +#include <pbd/statefuldestructible.h> + +#include <ardour/session.h> + +using Glib::ustring; + +namespace ARDOUR +{ + +class Session; +class ExportTimespan; +class ExportChannelConfiguration; +class ExportFormatSpecification; + +class ExportFilename { + private: + + typedef boost::shared_ptr<ExportTimespan> TimespanPtr; + typedef boost::shared_ptr<ExportChannelConfiguration> ChannelConfigPtr; + typedef boost::shared_ptr<ExportFormatSpecification const> FormatPtr; + + public: + + enum DateFormat { + D_None, + D_ISO, // ISO 8601 full date + D_ISOShortY, // Like ISO 8601, but short year representation + D_BE, // big endian (no deliminator) + D_BEShortY // big endian short year representation + }; + + enum TimeFormat { + T_None, + T_NoDelim, + T_Delim + }; + + private: + friend class ExportElementFactory; + ExportFilename (Session & session); + + public: + /* Serialization */ + + XMLNode & get_state (); + int set_state (const XMLNode &); + + /* data access */ + + ustring get_path (FormatPtr format) const; + ustring get_folder () const { return folder; } + + TimeFormat get_time_format () const { return time_format; } + DateFormat get_date_format () const { return date_format; } + ustring get_time_format_str (TimeFormat format) const; + ustring get_date_format_str (DateFormat format) const; + + ustring get_label () const { return label; } + uint32_t get_revision () const { return revision; } + + /* data modification */ + + void set_time_format (TimeFormat format); + void set_date_format (DateFormat format); + void set_label (ustring value); + void set_revision (uint32_t value) { revision = value; } + void set_channel (uint32_t value) { channel = value; } + bool set_folder (ustring path); + + void set_timespan (TimespanPtr ts) { timespan = ts; } + void set_channel_config (ChannelConfigPtr cc) { channel_config = cc; } + + /* public members */ + + bool include_label; + bool include_session; + bool include_revision; + bool include_channel_config; + bool include_channel; + bool include_timespan; + bool include_time; + bool include_date; + + private: + + Session & session; + + ustring label; + uint32_t revision; + uint32_t channel; + + ustring folder; + + DateFormat date_format; + TimeFormat time_format; + + ustring get_formatted_time (ustring const & format) const; + struct tm * time_struct; // Due to static allocation no destructor or copy-ctor is needed because of this + + TimespanPtr timespan; + ChannelConfigPtr channel_config; + + /* Serialization helpers */ + + typedef std::pair<bool, ustring> FieldPair; + + void add_field (XMLNode * node, ustring const & name, bool enabled, ustring const & value = ""); + FieldPair get_field (XMLNode const & node, ustring const & name); + FieldPair analyse_folder (); + +}; + + +} // namespace ARDOUR + +#endif /* __ardour_export_filename_h__ */ diff --git a/libs/ardour/ardour/export_format_base.h b/libs/ardour/ardour/export_format_base.h new file mode 100644 index 0000000000..210452add2 --- /dev/null +++ b/libs/ardour/ardour/export_format_base.h @@ -0,0 +1,199 @@ +/* + 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_format_base_h__ +#define __ardour_export_format_base_h__ + +#include <set> +#include <string> +#include <algorithm> +#include <boost/shared_ptr.hpp> +#include <glibmm/ustring.h> + +#include <sndfile.h> +#include <samplerate.h> +#include <ardour/gdither_types.h> +#include <ardour/ardour.h> + +using std::string; + +namespace ARDOUR +{ + +class HasSampleFormat; + +class ExportFormatBase { + public: + + enum Type { + T_None = 0, + T_Sndfile + }; + + enum FormatId { + F_None = 0, + F_WAV = SF_FORMAT_WAV, + F_W64 = SF_FORMAT_W64, + F_AIFF = SF_FORMAT_AIFF, + F_AU = SF_FORMAT_AU, + F_IRCAM = SF_FORMAT_IRCAM, + F_RAW = SF_FORMAT_RAW, + F_FLAC = SF_FORMAT_FLAC, + F_Ogg = SF_FORMAT_OGG + }; + + enum Endianness { + E_FileDefault = SF_ENDIAN_FILE, /* Default file endian-ness. */ + E_Little = SF_ENDIAN_LITTLE, /* Force little endian-ness. */ + E_Big = SF_ENDIAN_BIG, /* Force big endian-ness. */ + E_Cpu = SF_ENDIAN_CPU /* Force CPU endian-ness. */ + }; + + enum SampleFormat { + SF_None = 0, + SF_8 = SF_FORMAT_PCM_S8, + SF_16 = SF_FORMAT_PCM_16, + SF_24 = SF_FORMAT_PCM_24, + SF_32 = SF_FORMAT_PCM_32, + SF_U8 = SF_FORMAT_PCM_U8, + SF_Float = SF_FORMAT_FLOAT, + SF_Double = SF_FORMAT_DOUBLE, + SF_Vorbis = SF_FORMAT_VORBIS + }; + + enum DitherType { + D_None = GDitherNone, + D_Rect = GDitherRect, + D_Tri = GDitherTri, + D_Shaped = GDitherShaped + }; + + enum Quality { + Q_None = 0, + Q_Any, + Q_LosslessLinear, + Q_LosslessCompression, + Q_LossyCompression + }; + + enum SampleRate { + SR_None = 0, + SR_22_05 = 220500, + SR_44_1 = 44100, + SR_48 = 48000, + SR_88_2 = 88200, + SR_96 = 96000, + SR_192 = 192000 + }; + + enum SRCQuality { + SRC_SincBest = SRC_SINC_BEST_QUALITY, + SRC_SincMedium = SRC_SINC_MEDIUM_QUALITY, + SRC_SincFast = SRC_SINC_FASTEST, + SRC_ZeroOrderHold = SRC_ZERO_ORDER_HOLD, + SRC_Linear = SRC_LINEAR + }; + + /// Class for managing selection and compatibility states + class SelectableCompatible { + public: + SelectableCompatible () : + _selected (false), _compatible (true) { } + ~SelectableCompatible () {} + + sigc::signal<void, bool> SelectChanged; + sigc::signal<void, bool> CompatibleChanged; + + bool selected () const { return _selected; } + bool compatible () const { return _compatible; } + Glib::ustring name () const { return _name; } + + void set_selected (bool value); + void set_compatible (bool value); + + protected: + void set_name (Glib::ustring name) { _name = name; } + + private: + bool _selected; + bool _compatible; + + Glib::ustring _name; + }; + + public: + + ExportFormatBase (); + ExportFormatBase (ExportFormatBase const & other); + + virtual ~ExportFormatBase (); + + boost::shared_ptr<ExportFormatBase> get_intersection (ExportFormatBase const & other) const; + boost::shared_ptr<ExportFormatBase> get_difference (ExportFormatBase const & other) const; + boost::shared_ptr<ExportFormatBase> get_union (ExportFormatBase const & other) const; + + bool endiannesses_empty () const { return endiannesses.empty (); } + bool sample_formats_empty () const { return sample_formats.empty (); } + bool sample_rates_empty () const { return sample_rates.empty (); } + bool formats_empty () const { return format_ids.empty (); } + bool qualities_empty () const { return qualities.empty (); } + + bool has_endianness (Endianness endianness) const { return endiannesses.find (endianness) != endiannesses.end() ; } + bool has_sample_format (SampleFormat format) const { return sample_formats.find (format) != sample_formats.end(); } + bool has_sample_rate (SampleRate rate) const { return sample_rates.find (rate) != sample_rates.end(); } + bool has_format (FormatId format) const { return format_ids.find (format) != format_ids.end(); } + bool has_quality (Quality quality) const { return qualities.find (quality) != qualities.end(); } + + void set_extension (Glib::ustring const & extension) { _extension = extension; } + Glib::ustring const & extension () const { return _extension; } + + protected: + + friend class HasSampleFormat; + typedef std::set<SampleFormat> SampleFormatSet; + SampleFormatSet sample_formats; + + protected: + typedef std::set<Endianness> EndianSet; + typedef std::set<SampleRate> SampleRateSet; + typedef std::set<FormatId> FormatSet; + typedef std::set<Quality> QualitySet; + + EndianSet endiannesses; + SampleRateSet sample_rates; + FormatSet format_ids; + QualitySet qualities; + + private: + + Glib::ustring _extension; + + enum SetOperation { + SetUnion, + SetDifference, + SetIntersection + }; + + boost::shared_ptr<ExportFormatBase> do_set_operation (ExportFormatBase const & other, SetOperation operation) const; +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_format_base_h__ */ diff --git a/libs/ardour/ardour/export_format_compatibility.h b/libs/ardour/ardour/export_format_compatibility.h new file mode 100644 index 0000000000..617b5ee1d0 --- /dev/null +++ b/libs/ardour/ardour/export_format_compatibility.h @@ -0,0 +1,57 @@ +/* + 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_format_compatibility_h__ +#define __ardour_export_format_compatibility_h__ + +#include <ardour/export_format_base.h> + +namespace ARDOUR +{ + +/// Allows adding to all sets. A format should be able to test if it is compatible with this +class ExportFormatCompatibility : public ExportFormatBase, public ExportFormatBase::SelectableCompatible { + private: + + public: + ExportFormatCompatibility (Glib::ustring name) + { + set_name (name); + sample_formats.insert (SF_None); + sample_rates.insert (SR_None); + format_ids.insert (F_None); + qualities.insert (Q_None); + } + + ~ExportFormatCompatibility () {}; + + ExportFormatCompatibility (ExportFormatBase const & other) : + ExportFormatBase (other) {} + + void add_endianness (Endianness endianness) { endiannesses.insert (endianness); } + void add_sample_format (SampleFormat format) { sample_formats.insert (format); } + void add_sample_rate (SampleRate rate) { sample_rates.insert (rate); } + void add_format_id (FormatId id) { format_ids.insert (id); } + void add_quality (Quality quality) { qualities.insert (quality); } +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_format_compatibility_h__ */ diff --git a/libs/ardour/ardour/export_format_manager.h b/libs/ardour/ardour/export_format_manager.h new file mode 100644 index 0000000000..8c54c9cd45 --- /dev/null +++ b/libs/ardour/ardour/export_format_manager.h @@ -0,0 +1,176 @@ +/* + 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_format_manager_h__ +#define __ardour_export_format_manager_h__ + +#include <list> +#include <string> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <sigc++/signal.h> +#include <sigc++/trackable.h> + +#include <ardour/export_formats.h> + +using std::string; + +namespace ARDOUR +{ + +class ExportFormat; +class ExportFormatCompatibility; +class ExportFormatSpecification; +class AnyTime; + +class ExportFormatManager : public sigc::trackable +{ + public: + + typedef boost::shared_ptr<ExportFormatCompatibility> CompatPtr; + typedef boost::weak_ptr<ExportFormatCompatibility> WeakCompatPtr; + typedef std::list<CompatPtr> CompatList; + + typedef boost::shared_ptr<ExportFormat> FormatPtr; + typedef boost::weak_ptr<ExportFormat> WeakFormatPtr; + typedef std::list<FormatPtr> FormatList; + + typedef HasSampleFormat::SampleFormatPtr SampleFormatPtr; + typedef HasSampleFormat::SampleFormatList SampleFormatList; + typedef HasSampleFormat::WeakSampleFormatPtr WeakSampleFormatPtr; + + typedef HasSampleFormat::DitherTypePtr DitherTypePtr; + typedef HasSampleFormat::WeakDitherTypePtr WeakDitherTypePtr; + + typedef boost::shared_ptr<ExportFormatSpecification> SpecPtr; + typedef boost::shared_ptr<ExportFormatBase> FormatBasePtr; + + /* Quality states */ + + class QualityState : public ExportFormatBase::SelectableCompatible { + public: + QualityState (ExportFormatBase::Quality quality, Glib::ustring name) : + quality (quality) { set_name (name); } + ExportFormatBase::Quality quality; + }; + typedef boost::shared_ptr<QualityState> QualityPtr; + typedef boost::weak_ptr<QualityState> WeakQualityPtr; + typedef std::list<QualityPtr> QualityList; + + /* Sample rate states */ + + class SampleRateState : public ExportFormatBase::SelectableCompatible { + public: + SampleRateState (ExportFormatBase::SampleRate rate, Glib::ustring name) : + rate (rate) { set_name (name); } + ExportFormatBase::SampleRate rate; + }; + typedef boost::shared_ptr<SampleRateState> SampleRatePtr; + typedef boost::weak_ptr<SampleRateState> WeakSampleRatePtr; + typedef std::list<SampleRatePtr> SampleRateList; + + public: + + explicit ExportFormatManager (SpecPtr specification); + ~ExportFormatManager (); + + /* Signals */ + + sigc::signal<void, bool> CompleteChanged; + + /* Access to lists */ + + CompatList const & get_compatibilities () { return compatibilities; } + QualityList const & get_qualities () { return qualities; } + FormatList const & get_formats () { return formats; } + SampleRateList const & get_sample_rates () { return sample_rates; } + + /* Non interactive selections */ + + void set_name (Glib::ustring name); + + void select_src_quality (ExportFormatBase::SRCQuality value); + void select_trim_beginning (bool value); + void select_silence_beginning (AnyTime const & time); + void select_trim_end (bool value); + void select_silence_end (AnyTime const & time); + void select_normalize (bool value); + void select_normalize_target (float value); + void select_tagging (bool tag); + + private: + + void init_compatibilities (); + void init_qualities (); + void init_formats (); + void init_sample_rates (); + + void add_compatibility (CompatPtr ptr); + void add_quality (QualityPtr ptr); + void add_format (FormatPtr ptr); + void add_sample_rate (SampleRatePtr ptr); + + /* Connected to signals */ + + void change_compatibility_selection (bool select, WeakCompatPtr const & compat); + void change_quality_selection (bool select, WeakQualityPtr const & quality); + void change_format_selection (bool select, WeakFormatPtr const & format); + void change_sample_rate_selection (bool select, WeakSampleRatePtr const & rate); + + void change_sample_format_selection (bool select, WeakSampleFormatPtr const & format); + void change_dither_type_selection (bool select, WeakDitherTypePtr const & type); + + /* Do actual selection */ + + void select_compatibility (WeakCompatPtr const & compat); + void select_quality (QualityPtr const & quality); + void select_format (FormatPtr const & format); + void select_sample_rate (SampleRatePtr const & rate); + + void select_sample_format (SampleFormatPtr const & format); + void select_dither_type (DitherTypePtr const & type); + + bool pending_selection_change; + void selection_changed (); + + /* Formats and compatibilities */ + + QualityPtr get_selected_quality (); + FormatPtr get_selected_format (); + SampleRatePtr get_selected_sample_rate (); + + SampleFormatPtr get_selected_sample_format (); + + FormatBasePtr get_compatibility_intersection (); + + FormatBasePtr universal_set; + SpecPtr current_selection; + + CompatList compatibilities; + QualityList qualities; + FormatList formats; + SampleRateList sample_rates; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_format_manager_h__ */ diff --git a/libs/ardour/ardour/export_format_specification.h b/libs/ardour/ardour/export_format_specification.h new file mode 100644 index 0000000000..41f71e8275 --- /dev/null +++ b/libs/ardour/ardour/export_format_specification.h @@ -0,0 +1,185 @@ +/* + 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_format_specification_h__ +#define __ardour_export_format_specification_h__ + +#include <glibmm/ustring.h> + +#include <ardour/types.h> +#include <ardour/export_format_base.h> + +using std::string; + +class XMLNode; + +namespace ARDOUR +{ + +class ExportFormat; +class ExportFormatCompatibility; +class Session; + +class ExportFormatSpecification : public ExportFormatBase { + + private: + + /* Time struct for keeping time formats as close as possible to what was requested */ + + struct Time : public AnyTime { + Time (Session & session) : AnyTime (), session (session) {} + Time & operator= (AnyTime const & other); + + nframes_t get_frames (nframes_t target_rate) const; + + /* Serialization */ + + XMLNode & get_state (); + int set_state (const XMLNode & node); + + private: + Session & session; + }; + + private: + friend class ExportElementFactory; + explicit ExportFormatSpecification (Session & s); + ExportFormatSpecification (Session & s, XMLNode const & state); + + public: + ExportFormatSpecification (ExportFormatSpecification const & other); + ~ExportFormatSpecification (); + + /* compatibility */ + + bool is_compatible_with (ExportFormatCompatibility const & compatibility) const; + bool is_complete () const; + + /* Modifying functions */ + + void set_format (boost::shared_ptr<ExportFormat> format); + + void set_name (Glib::ustring const & name) { _name = name; } + + void set_type (Type type) { _type = type; } + void set_format_id (FormatId value) { format_ids.clear(); format_ids.insert (value); } + void set_endianness (Endianness value) { endiannesses.clear(); endiannesses.insert (value); } + void set_sample_format (SampleFormat value) { sample_formats.clear(); sample_formats.insert (value); } + void set_sample_rate (SampleRate value) { sample_rates.clear(); sample_rates.insert (value); } + void set_quality (Quality value) { qualities.clear(); qualities.insert (value); } + + void set_dither_type (DitherType value) { _dither_type = value; } + void set_src_quality (SRCQuality value) { _src_quality = value; } + void set_trim_beginning (bool value) { _trim_beginning = value; } + void set_trim_end (bool value) { _trim_end = value; } + void set_normalize (bool value) { _normalize = value; } + void set_normalize_target (float value) { _normalize_target = value; } + + void set_tag (bool tag_it) { _tag = tag_it; } + + void set_silence_beginning (AnyTime const & value) { _silence_beginning = value; } + void set_silence_end (AnyTime const & value) { _silence_end = value; } + + /* Accessing functions */ + + uint32_t id () { return _id; } + Glib::ustring const & name () const { return _name; } + Glib::ustring description (); + + bool has_broadcast_info () const { return _has_broadcast_info; } + uint32_t channel_limit () const { return _channel_limit; } + Glib::ustring format_name () const { return _format_name; } + + Type type () const { return _type; } + FormatId format_id () const { return *format_ids.begin(); } + Endianness endianness () const { return *endiannesses.begin(); } + SampleFormat sample_format () const { return *sample_formats.begin(); } + SampleRate sample_rate () const { return *sample_rates.begin(); } + Quality quality () const { return *qualities.begin(); } + + DitherType dither_type () const { return _dither_type; } + SRCQuality src_quality () const { return _src_quality; } + bool trim_beginning () const { return _trim_beginning; } + bool trim_end () const { return _trim_end; } + bool normalize () const { return _normalize; } + float normalize_target () const { return _normalize_target; } + + bool tag () const { return _tag && supports_tagging; } + + nframes_t silence_beginning () const { return _silence_beginning.get_frames (sample_rate()); } + nframes_t silence_end () const { return _silence_end.get_frames (sample_rate()); } + + AnyTime silence_beginning_time () const { return _silence_beginning; } + AnyTime silence_end_time () const { return _silence_end; } + + /* Serialization */ + + XMLNode & get_state (); + int set_state (const XMLNode & root); + + + private: + + Session & session; + + /* The variables below do not have setters (usually set via set_format) */ + + Glib::ustring _format_name; + bool has_sample_format; + bool supports_tagging; + bool _has_broadcast_info; + uint32_t _channel_limit; + + /* The variables below have getters and setters */ + + Glib::ustring _name; + uint32_t _id; + + Type _type; + DitherType _dither_type; + SRCQuality _src_quality; + + bool _tag; + + bool _trim_beginning; + Time _silence_beginning; + bool _trim_end; + Time _silence_end; + + bool _normalize; + float _normalize_target; + + /* serialization helpers */ + + void add_option (XMLNode * node, std::string const & name, std::string const & value); + std::string get_option (XMLNode const * node, std::string const & name); + + /*** Static stuff for id management, ExportElementFactory will have access to these ***/ + + static void init_counter (uint32_t val) { if (val > _counter) { _counter = val; } } + static uint32_t counter () { return _counter; } + + static uint32_t _counter; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_format_specification_h__ */ diff --git a/libs/ardour/ardour/export_formats.h b/libs/ardour/ardour/export_formats.h new file mode 100644 index 0000000000..558223d75a --- /dev/null +++ b/libs/ardour/ardour/export_formats.h @@ -0,0 +1,206 @@ +/* + 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_formats_h__ +#define __ardour_export_formats_h__ + +#include <ardour/export_format_base.h> +#include <ardour/export_format_compatibility.h> + +#include <list> +#include <boost/weak_ptr.hpp> + +namespace ARDOUR +{ + +class HasSampleFormat; + +/// Base class for formats +class ExportFormat : public ExportFormatBase, public ExportFormatBase::SelectableCompatible { + + public: + ExportFormat () {}; + ~ExportFormat () {}; + + virtual bool set_compatibility_state (ExportFormatCompatibility const & compatibility) = 0; + virtual Type get_type () const = 0; + + FormatId get_format_id () const { return *format_ids.begin(); } + Quality get_quality () const { return *qualities.begin(); } + + bool has_sample_format (); + bool sample_format_is_compatible (SampleFormat format) const; + + /* If the format has a specific sample format, this function should be overriden + * if the format has a selectable sample format, do not override this! + */ + + virtual SampleFormat get_explicit_sample_format () const { return SF_None; } + + /* If the above is not overriden, this one should be */ + + virtual ExportFormat::SampleFormat default_sample_format () const { return SF_None; } + + /* If the format has a channel count limit, override this */ + + virtual uint32_t get_channel_limit () const { return 256; } + + /* If the format can be tagged with metadata override this */ + + virtual bool supports_tagging () const { return false; } + + /* If the format contains broadcast info override this */ + + virtual bool has_broadcast_info () const { return false; } + + protected: + + void add_sample_rate (SampleRate rate) { sample_rates.insert (rate); } + void add_endianness (Endianness endianness) { endiannesses.insert (endianness); } + + void set_format_id (FormatId id) { format_ids.clear (); format_ids.insert (id); } + void set_quality (Quality value) { qualities.clear(); qualities.insert (value); } +}; + +/// Class to be inherited by export formats that have a selectable sample format +class HasSampleFormat { + public: + + class SampleFormatState : public ExportFormatBase::SelectableCompatible { + public: + SampleFormatState (ExportFormatBase::SampleFormat format, Glib::ustring name) : + format (format) { set_name (name); } + + ExportFormatBase::SampleFormat format; + }; + + class DitherTypeState : public ExportFormatBase::SelectableCompatible { + public: + DitherTypeState (ExportFormatBase::DitherType type, Glib::ustring name) : + type (type) { set_name (name); } + + ExportFormatBase::DitherType type; + }; + + typedef boost::shared_ptr<SampleFormatState> SampleFormatPtr; + typedef boost::weak_ptr<SampleFormatState> WeakSampleFormatPtr; + typedef std::list<SampleFormatPtr> SampleFormatList; + + typedef boost::shared_ptr<DitherTypeState> DitherTypePtr; + typedef boost::weak_ptr<DitherTypeState> WeakDitherTypePtr; + typedef std::list<DitherTypePtr> DitherTypeList; + + public: + + HasSampleFormat (ExportFormatBase::SampleFormatSet & sample_formats); + virtual ~HasSampleFormat () {}; + + void add_sample_format (ExportFormatBase::SampleFormat format); + + SampleFormatList const & get_sample_formats () const { return sample_format_states; } + DitherTypeList const & get_dither_types () const { return dither_type_states; } + + SampleFormatPtr get_selected_sample_format (); + DitherTypePtr get_selected_dither_type (); + + /* Proxies for signals from sample formats and dither types */ + + sigc::signal<void, bool, WeakSampleFormatPtr> SampleFormatSelectChanged; + sigc::signal<void, bool, WeakSampleFormatPtr> SampleFormatCompatibleChanged; + + sigc::signal<void, bool, WeakDitherTypePtr> DitherTypeSelectChanged; + sigc::signal<void, bool, WeakDitherTypePtr> DitherTypeCompatibleChanged; + + static string get_sample_format_name (ExportFormatBase::SampleFormat format); + + protected: + /* State lists */ + + DitherTypeList dither_type_states; + SampleFormatList sample_format_states; + + private: + /* Connected to signals */ + + void add_dither_type (ExportFormatBase::DitherType type, Glib::ustring name); + void update_sample_format_selection (bool); + void update_dither_type_selection (bool); + + /* Reference to ExportFormatBase::sample_formats */ + ExportFormatBase::SampleFormatSet & _sample_formats; +}; + +class ExportFormatLinear : public ExportFormat, public HasSampleFormat { + public: + + ExportFormatLinear (Glib::ustring name, FormatId format_id); + ~ExportFormatLinear () {}; + + bool set_compatibility_state (ExportFormatCompatibility const & compatibility); + Type get_type () const { return T_Sndfile; } + + void add_endianness (Endianness endianness) { endiannesses.insert (endianness); } + + void set_default_sample_format (SampleFormat sf) { _default_sample_format = sf; } + SampleFormat default_sample_format () const { return _default_sample_format; } + + protected: + SampleFormat _default_sample_format; +}; + +class ExportFormatOggVorbis : public ExportFormat { + public: + ExportFormatOggVorbis (); + ~ExportFormatOggVorbis () {}; + + bool set_compatibility_state (ExportFormatCompatibility const & compatibility); + Type get_type () const { return T_Sndfile; } + SampleFormat get_explicit_sample_format () const { return SF_Vorbis; } + virtual bool supports_tagging () const { return true; } +}; + +class ExportFormatFLAC : public ExportFormat, public HasSampleFormat { + public: + ExportFormatFLAC (); + ~ExportFormatFLAC () {}; + + bool set_compatibility_state (ExportFormatCompatibility const & compatibility); + Type get_type () const { return T_Sndfile; } + + uint32_t get_channel_limit () const { return 8; } + SampleFormat default_sample_format () const { return SF_16; } + virtual bool supports_tagging () const { return true; } +}; + +class ExportFormatBWF : public ExportFormat, public HasSampleFormat { + public: + ExportFormatBWF (); + ~ExportFormatBWF () {}; + + bool set_compatibility_state (ExportFormatCompatibility const & compatibility); + Type get_type () const { return T_Sndfile; } + + SampleFormat default_sample_format () const { return SF_16; } + virtual bool has_broadcast_info () const { return true; } +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_formats__ */ diff --git a/libs/ardour/ardour/export_handler.h b/libs/ardour/ardour/export_handler.h new file mode 100644 index 0000000000..99de563fa9 --- /dev/null +++ b/libs/ardour/ardour/export_handler.h @@ -0,0 +1,182 @@ +/* + 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_handler_h__ +#define __ardour_export_handler_h__ + +#include <map> +#include <list> +#include <fstream> + +#include <boost/shared_ptr.hpp> + +#include <ardour/ardour.h> +#include <ardour/session.h> +#include <ardour/types.h> + +namespace ARDOUR +{ + +class ExportTimespan; +class ExportChannelConfiguration; +class ExportFormatSpecification; +class ExportFilename; +class ExportProcessor; + +class ExportElementFactory +{ + protected: + typedef boost::shared_ptr<ExportTimespan> TimespanPtr; + typedef boost::shared_ptr<ExportChannelConfiguration> ChannelConfigPtr; + typedef boost::shared_ptr<ExportFormatSpecification> FormatPtr; + typedef boost::shared_ptr<ExportFilename> FilenamePtr; + + public: + + ExportElementFactory (Session & session); + ~ExportElementFactory (); + + TimespanPtr add_timespan (); + + ChannelConfigPtr add_channel_config (); + + FormatPtr add_format (); + FormatPtr add_format (XMLNode const & state); + FormatPtr add_format_copy (FormatPtr other); + + FilenamePtr add_filename (); + FilenamePtr add_filename_copy (FilenamePtr other); + + private: + Session & session; +}; + +class ExportHandler : public ExportElementFactory, public sigc::trackable +{ + private: + + /* Stuff for export configs + * The multimap maps timespans to file specifications + */ + + struct FileSpec { + + FileSpec (ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename) : + channel_config (channel_config), + format (format), + filename (filename) + {} + + ChannelConfigPtr channel_config; + FormatPtr format; + FilenamePtr filename; + }; + + typedef std::pair<TimespanPtr, FileSpec> ConfigPair; + typedef std::multimap<TimespanPtr, FileSpec> ConfigMap; + + typedef boost::shared_ptr<ExportProcessor> ProcessorPtr; + + private: + /* Session::get_export_handler() should be used to obtain an export handler + * This ensures that it doesn't go out of scope before finalize_audio_export is called + */ + + friend boost::shared_ptr<ExportHandler> Session::get_export_handler(); + ExportHandler (Session & session); + + public: + ~ExportHandler (); + + bool add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename); + void do_export (bool rt = false); + + private: + + Session & session; + ProcessorPtr processor; + ConfigMap config_map; + + bool realtime; + + sigc::connection files_written_connection; + std::list<Glib::ustring> files_written; + + /* CD Marker stuff */ + + struct CDMarkerStatus { + CDMarkerStatus (string out_file, TimespanPtr timespan, FormatPtr format, string filename) : + out (out_file.c_str()), timespan (timespan), format (format), filename (filename), + track_number (1), track_position (0), track_duration (0), track_start_frame (0), + index_number (1), index_position (0) + {} + + /* General info */ + std::ofstream out; + TimespanPtr timespan; + FormatPtr format; + string filename; + Location * marker; + + /* Track info */ + uint32_t track_number; + nframes_t track_position; + nframes_t track_duration; + nframes_t track_start_frame; + + /* Index info */ + uint32_t index_number; + nframes_t index_position; + }; + + + void export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format); + + void write_cue_header (CDMarkerStatus & status); + void write_toc_header (CDMarkerStatus & status); + + void write_track_info_cue (CDMarkerStatus & status); + void write_track_info_toc (CDMarkerStatus & status); + + 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); + + 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; + sigc::connection channel_config_connection; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_handler_h__ */ diff --git a/libs/ardour/ardour/export_multiplication.h b/libs/ardour/ardour/export_multiplication.h new file mode 100644 index 0000000000..c8fd20c60e --- /dev/null +++ b/libs/ardour/ardour/export_multiplication.h @@ -0,0 +1,189 @@ +/* This file is not used at the moment. It includes code related to export a + * multiplication graph system that can be used together with the ExportMultiplicator + * class in the gtk2_ardour folder. + * - Sakari Bergen 6.8.2008 - + */ + +/*** Graph classes ***/ + public: + + /// A node in the hierarchical graph that represents a multiplicatable export item + class GraphNode { + public: + GraphNode (); + virtual ~GraphNode (); + + uint32_t id() const { return _id; } + + /* Children and parents. Note: only children are kept in order! */ + + list<GraphNode *> const & get_parents () const { return parents; } + + void add_child (GraphNode * child, GraphNode * left_sibling); + void remove_child (GraphNode * child); + GraphNode * first_child () const { return children.front(); } + GraphNode * last_child () const { return children.back(); } + list<GraphNode *> const & get_children () const { return children; } + + /* Relation functions */ + + bool is_ancestor_of (GraphNode const * node) const; + bool is_descendant_of (GraphNode const * node) const; + bool equals (GraphNode const * node) const { return node == this; } + + /* Selection functions */ + + bool selected () const { return _selected; } + void select (bool value); + + sigc::signal<void, bool> SelectChanged; + + protected: + + /* Parent manipulation functions should be used only from child manipulation functions! */ + + void add_parent (GraphNode * parent); + void remove_parent (GraphNode * parent); + + list<GraphNode *> parents; + list<GraphNode *> children; + + bool _selected; + uint32_t _id; + static uint32_t id_counter; + }; + + /// A graph node that contains data + template <typename T> + class DataNode : public GraphNode { + private: + typedef boost::shared_ptr<T> DataPtr; + typedef boost::shared_ptr<DataNode<T> > SelfPtr; + typedef boost::weak_ptr<DataNode<T> > WeakSelfPtr; + + DataNode (DataPtr data) : _data (data) {} + void set_self_ptr (boost::shared_ptr<DataNode<T> > ptr) { _self_ptr = ptr; } + + public: + static SelfPtr create (T * data) + { + SelfPtr ptr = SelfPtr (new DataNode<T> (DataPtr (data))); + ptr->set_self_ptr (ptr); + return ptr; + } + + static SelfPtr create (DataPtr data) + { + SelfPtr ptr = SelfPtr (new DataNode<T> (data)); + ptr->set_self_ptr (ptr); + return ptr; + } + + DataPtr data() { return _data; } + SelfPtr self_ptr () { return _self_ptr.lock(); } + + template<typename P> // Parent's data type + void sort_parents (list<boost::shared_ptr<DataNode<P> > > const & sort_list) + { + parents.sort (NodeSorter<P> (sort_list)); + } + + private: + DataPtr _data; + WeakSelfPtr _self_ptr; + }; + + private: + /* Sorts GraphNodes according to a list of DataNodes */ + + template<typename T> + class NodeSorter { + public: + typedef list<boost::shared_ptr<DataNode<T> > > ListType; + + NodeSorter (ListType const & list) : list (list) {} + + bool operator() (GraphNode * one, GraphNode * other) // '<' operator + { + if (one == other) { return false; } // Strict weak ordering + for (typename ListType::const_iterator it = list.begin(); it != list.end(); ++it) { + if (it->get() == one) { + return true; + } + if (it->get() == other) { + return false; + } + } + + std::cerr << "Invalid comparison list given to NodeSorter" << std::endl; + + abort(); + } + + private: + ListType const & list; + }; + +/*** Multiplication management ***/ + public: + + typedef DataNode<TimespanState> TimespanNode; + typedef boost::shared_ptr<TimespanNode> TimespanNodePtr; + + typedef DataNode<ChannelConfigState> ChannelConfigNode; + typedef boost::shared_ptr<ChannelConfigNode> ChannelConfigNodePtr; + + typedef DataNode<FormatState> FormatNode; + typedef boost::shared_ptr<FormatNode> FormatNodePtr; + + typedef DataNode<FilenameState> FilenameNode; + typedef boost::shared_ptr<FilenameNode> FilenameNodePtr; + + struct MultiplicationGraph { + list<TimespanNodePtr> timespans; + list<ChannelConfigNodePtr> channel_configs; + list<FormatNodePtr> formats; + list<FilenameNodePtr> filenames; + }; + + MultiplicationGraph const & get_graph () { return graph; } + + void split_node (GraphNode * node, float position); + void remove_node (GraphNode * node); + + sigc::signal<void> GraphChanged; + + private: + + void purge_graph (); + + template<typename T> + static void insert_after (list<T> & the_list, T const & position, T const & element); + + template<typename T> + static void remove_by_element (list<T> & the_list, T const & element); + + bool nodes_have_one_common_child (list<GraphNode *> const & the_list); + list<GraphNode *>::const_iterator end_of_common_child_range (list<GraphNode *> const & the_list, list<GraphNode *>::const_iterator beginning); + void split_node_at_position (GraphNode * old_node, GraphNode * new_node, float position); + + void split_timespan (TimespanNodePtr node, float position = 0.5); + void split_channel_config (ChannelConfigNodePtr node, float position = 0.5); + void split_format (FormatNodePtr node, float position = 0.5); + void split_filename (FilenameNodePtr node, float position = 0.5); + + void duplicate_timespan_children (TimespanNodePtr source, TimespanNodePtr target, GraphNode * insertion_point = 0); + void duplicate_channel_config_children (ChannelConfigNodePtr source, ChannelConfigNodePtr target, GraphNode * insertion_point = 0); + void duplicate_format_children (FormatNodePtr source, FormatNodePtr target, GraphNode * insertion_point = 0); + + TimespanNodePtr duplicate_timespan_node (TimespanNodePtr node); + ChannelConfigNodePtr duplicate_channel_config_node (ChannelConfigNodePtr node); + FormatNodePtr duplicate_format_node (FormatNodePtr node); + FilenameNodePtr duplicate_filename_node (FilenameNodePtr node); + + void remove_timespan (TimespanNodePtr node); + void remove_channel_config (ChannelConfigNodePtr node); + void remove_format (FormatNodePtr node); + void remove_filename (FilenameNodePtr node); + + MultiplicationGraph graph;
\ No newline at end of file diff --git a/libs/ardour/ardour/export_processor.h b/libs/ardour/ardour/export_processor.h new file mode 100644 index 0000000000..fdb8213c68 --- /dev/null +++ b/libs/ardour/ardour/export_processor.h @@ -0,0 +1,139 @@ +/* + 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> + +using Glib::ustring; +using std::list; + +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 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; + + 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 sigc::signal<void, Glib::ustring> WritingFile; + + private: + + void reset (); + FloatSinkPtr prepare_sndfile_writer (FormatPtr format, uint32_t channels, ustring const & filename); + + Session & session; + 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 new file mode 100644 index 0000000000..1a855d6868 --- /dev/null +++ b/libs/ardour/ardour/export_profile_manager.h @@ -0,0 +1,316 @@ +/* + 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_profile_manager_h__ +#define __ardour_export_profile_manager_h__ + +#include <list> +#include <vector> +#include <map> +#include <set> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <sigc++/signal.h> +#include <glibmm/ustring.h> + +#include <pbd/file_utils.h> +#include <pbd/xml++.h> + +#include <ardour/filesystem_paths.h> +#include <ardour/location.h> +#include <ardour/types.h> + +using std::string; +using std::list; +using std::set; + +namespace ARDOUR +{ + +class ExportHandler; +class ExportTimespan; +class ExportChannelConfiguration; +class ExportFormatSpecification; +class ExportFilename; +class Location; +class Session; + +/// Manages (de)serialization of export profiles and related classes +class ExportProfileManager +{ + public: + class Preset { + public: + Preset (string filename, Session & s); + ~Preset (); + + uint32_t id () const { return _id; } + string name () const { return _name; } + + void set_name (string name); + void set_id (uint32_t id); + + // Note: The set_..._state functions take ownership of the XMLNode + void set_global_state (XMLNode & state); + void set_local_state (XMLNode & state); + + XMLNode const * get_global_state () const { return global.root(); } + XMLNode const * get_local_state () const { return local; } + + void save () const; + void remove_local () const; + + private: + + XMLNode * get_instant_xml () const; + void save_instant_xml () const; + void remove_instant_xml () const; + + uint32_t _id; + string _name; + + Session & session; + XMLTree global; + XMLNode * local; + + }; + + typedef boost::shared_ptr<Preset> PresetPtr; + typedef std::list<PresetPtr> PresetList; + + public: + + ExportProfileManager (Session & s); + ~ExportProfileManager (); + + void load_profile (); + void prepare_for_export (); + + PresetList const & get_presets () { return preset_list; } + void load_preset (PresetPtr preset); + PresetPtr save_preset (string const & name); + void remove_preset (); + + private: + typedef boost::shared_ptr<ExportHandler> HandlerPtr; + + typedef std::pair<uint32_t, PBD::sys::path> FilePair; + typedef std::map<uint32_t, PBD::sys::path> FileMap; + + HandlerPtr handler; + Session & session; + + void load_presets (); + uint32_t load_preset_from_disk (PBD::sys::path const & path); // Returns preset id + + void set_state (XMLNode const & root); + void set_global_state (XMLNode const & root); + void set_local_state (XMLNode const & root); + + void serialize_profile (XMLNode & root); + void serialize_global_profile (XMLNode & root); + void serialize_local_profile (XMLNode & root); + + PresetList preset_list; + PresetPtr current_preset; + uint32_t preset_id_counter; + FileMap preset_file_map; + + std::vector<PBD::sys::path> find_file (std::string const & pattern); + + PBD::sys::path export_config_dir; + PBD::SearchPath search_path; + +/* Timespans */ + public: + + typedef boost::shared_ptr<ExportTimespan> TimespanPtr; + typedef std::list<TimespanPtr> TimespanList; + typedef boost::shared_ptr<TimespanList> TimespanListPtr; + typedef std::list<Location *> LocationList; + + enum TimeFormat { + SMPTE, + BBT, + MinSec, + Frames, + Off + }; + + struct TimespanState { + TimespanListPtr timespans; + TimeFormat time_format; + + boost::shared_ptr<Location> session_range; + boost::shared_ptr<Location> selection_range; + boost::shared_ptr<LocationList> ranges; + + TimespanState (boost::shared_ptr<Location> session_range, + boost::shared_ptr<Location> selection_range, + boost::shared_ptr<LocationList> ranges) : + timespans (new TimespanList ()), + time_format (SMPTE), + + session_range (session_range), + selection_range (selection_range), + ranges (ranges) + {} + }; + + typedef boost::shared_ptr<TimespanState> TimespanStatePtr; + typedef std::list<TimespanStatePtr> TimespanStateList; + + void set_selection_range (nframes_t start = 0, nframes_t end = 0); + TimespanStateList const & get_timespans () { return timespans; } + + private: + + TimespanStateList timespans; + + void init_timespans (XMLNodeList nodes); + + TimespanStatePtr deserialize_timespan (XMLNode & root); + XMLNode & serialize_timespan (TimespanStatePtr state); + + /* Locations */ + + void update_ranges (); + + boost::shared_ptr<Location> session_range; + boost::shared_ptr<Location> selection_range; + boost::shared_ptr<LocationList> ranges; + +/* Channel Configs */ + public: + + typedef boost::shared_ptr<ExportChannelConfiguration> ChannelConfigPtr; + + struct ChannelConfigState { + ChannelConfigPtr config; + + ChannelConfigState (ChannelConfigPtr ptr) : config (ptr) {} + }; + typedef boost::shared_ptr<ChannelConfigState> ChannelConfigStatePtr; + typedef std::list<ChannelConfigStatePtr> ChannelConfigStateList; + + ChannelConfigStateList const & get_channel_configs () { return channel_configs; } + + private: + + ChannelConfigStateList channel_configs; + + void init_channel_configs (XMLNodeList nodes); + + ChannelConfigStatePtr deserialize_channel_config (XMLNode & root); + XMLNode & serialize_channel_config (ChannelConfigStatePtr state); + +/* Formats */ + public: + + typedef boost::shared_ptr<ExportFormatSpecification> FormatPtr; + typedef std::list<FormatPtr> FormatList; + + struct FormatState { + boost::shared_ptr<FormatList const> list; + FormatPtr format; + + FormatState (boost::shared_ptr<FormatList const> list, FormatPtr format) : + list (list), format (format) {} + }; + typedef boost::shared_ptr<FormatState> FormatStatePtr; + typedef std::list<FormatStatePtr> FormatStateList; + + FormatStateList const & get_formats () { return formats; } + FormatStatePtr duplicate_format_state (FormatStatePtr state); + void remove_format_state (FormatStatePtr state); + + PBD::sys::path save_format_to_disk (FormatPtr format); + void remove_format_profile (FormatPtr format); + FormatPtr get_new_format (FormatPtr original); + + sigc::signal<void> FormatListChanged; + + private: + + FormatStateList formats; + + void init_formats (XMLNodeList nodes); + FormatStatePtr deserialize_format (XMLNode & root); + XMLNode & serialize_format (FormatStatePtr state); + + void load_formats (); + + FormatPtr load_format (XMLNode & node); + void load_format_from_disk (PBD::sys::path const & path); + + boost::shared_ptr<FormatList> format_list; + FileMap format_file_map; + +/* Filenames */ + public: + + typedef boost::shared_ptr<ExportFilename> FilenamePtr; + + struct FilenameState { + FilenamePtr filename; + + FilenameState (FilenamePtr ptr) : filename (ptr) {} + }; + typedef boost::shared_ptr<FilenameState> FilenameStatePtr; + typedef std::list<FilenameStatePtr> FilenameStateList; + + FilenameStateList const & get_filenames () { return filenames; } + FilenameStatePtr duplicate_filename_state (FilenameStatePtr state); + void remove_filename_state (FilenameStatePtr state); + + private: + + FilenameStateList filenames; + + void init_filenames (XMLNodeList nodes); + + FilenameStatePtr deserialize_filename (XMLNode & root); + XMLNode & serialize_filename (FilenameStatePtr state); + + FilenamePtr load_filename (XMLNode & node); + +/* Warnings */ + public: + struct Warnings { + std::list<Glib::ustring> errors; + std::list<Glib::ustring> warnings; + std::list<Glib::ustring> conflicting_filenames; + }; + + boost::shared_ptr<Warnings> get_warnings (); + + private: + void check_config (boost::shared_ptr<Warnings> warnings, + TimespanStatePtr timespan_state, + ChannelConfigStatePtr channel_config_state, + FormatStatePtr format_state, + FilenameStatePtr filename_state); +}; + + +} // namespace ARDOUR + +#endif /* __ardour_export_profile_manager_h__ */ diff --git a/libs/ardour/ardour/export_status.h b/libs/ardour/ardour/export_status.h new file mode 100644 index 0000000000..be4bd25b9d --- /dev/null +++ b/libs/ardour/ardour/export_status.h @@ -0,0 +1,72 @@ +/* + 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_status_h__ +#define __ardour_export_status_h__ + +#include <list> + +#include <sigc++/signal.h> + +namespace ARDOUR +{ + +enum ExportStage { + export_None, + export_ReadTimespan, + export_PostProcess, + export_Write +}; + +struct ExportStatus { + + ExportStatus (); + void init (); + + /* Status info */ + + volatile bool stop; + volatile bool running; + + sigc::signal<void> Aborting; + void abort () { _aborted = true; Aborting(); } + bool aborted () const { return _aborted; } + + /* Progress info */ + + volatile ExportStage stage; + volatile float progress; + + volatile uint32_t total_timespans; + volatile uint32_t timespan; + + volatile uint32_t total_channel_configs; + volatile uint32_t channel_config; + + volatile uint32_t total_formats; + volatile uint32_t format; + + private: + volatile bool _aborted; +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_status_h__ */ diff --git a/libs/ardour/ardour/export_timespan.h b/libs/ardour/ardour/export_timespan.h new file mode 100644 index 0000000000..7995da36d2 --- /dev/null +++ b/libs/ardour/ardour/export_timespan.h @@ -0,0 +1,97 @@ +/* + 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_timespan_h__ +#define __ardour_export_timespan_h__ + +#include <map> +#include <list> + +#include <glibmm/ustring.h> + +#include <ardour/export_status.h> +#include <ardour/ardour.h> + +using Glib::ustring; + +namespace ARDOUR +{ + +class ExportChannel; +class ExportTempFile; + +class ExportTimespan : public sigc::trackable +{ + private: + typedef boost::shared_ptr<ExportTempFile> TempFilePtr; + typedef std::pair<ExportChannel const, TempFilePtr> ChannelFilePair; + typedef std::map<ExportChannel const, TempFilePtr> TempFileMap; + + private: + friend class ExportElementFactory; + ExportTimespan (ExportStatus & status, nframes_t frame_rate); + + public: + ~ExportTimespan (); + + ustring name () const { return _name; } + void set_name (ustring name) { _name = name; } + + ustring range_id () const { return _range_id; } + void set_range_id (ustring range_id) { _range_id = range_id; } + + /// Registers a channel to be read when export starts rolling + void register_channel (ExportChannel const & 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, ExportChannel const & channel); + + /// Reads data from each channel and writes to tempfile + int process (nframes_t frames); + + sigc::connection 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; } + nframes_t get_end () const { return end_frame; } + + private: + + ExportStatus & status; + + nframes_t start_frame; + nframes_t end_frame; + nframes_t position; + nframes_t frame_rate; + + TempFileMap filemap; + + ustring _name; + ustring _range_id; + +}; + +} // namespace ARDOUR + +#endif /* __ardour_export_timespan_h__ */ diff --git a/libs/ardour/ardour/export_utilities.h b/libs/ardour/ardour/export_utilities.h new file mode 100644 index 0000000000..e5660e3b75 --- /dev/null +++ b/libs/ardour/ardour/export_utilities.h @@ -0,0 +1,146 @@ +/* + 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 new file mode 100644 index 0000000000..80b5a9fe11 --- /dev/null +++ b/libs/ardour/ardour/graph.h @@ -0,0 +1,101 @@ +/* + 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/ardour/session_metadata.h b/libs/ardour/ardour/session_metadata.h new file mode 100644 index 0000000000..2163a3dfef --- /dev/null +++ b/libs/ardour/ardour/session_metadata.h @@ -0,0 +1,132 @@ +/* + 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_session_metadata_h__ +#define __ardour_session_metadata_h__ + +#include <string> +#include <glibmm/ustring.h> + +#include <map> +#include <utility> + +#include <pbd/statefuldestructible.h> +#include <pbd/xml++.h> + +namespace ARDOUR { + +using std::string; +using Glib::ustring; + +/** Represents metadata associated to a Session + * Metadata can be accessed and edited via this class. + * Exported files can also be tagged with this data. + */ +class SessionMetadata : public PBD::StatefulDestructible +{ + public: + SessionMetadata (); + ~SessionMetadata (); + + /*** Accessing ***/ + ustring comment () const; + ustring copyright () const; + ustring isrc () const; + uint32_t year () const; + + ustring grouping () const; + ustring title () const; + ustring subtitle () const; + + ustring artist () const; + ustring album_artist () const; + ustring lyricist () const; + ustring composer () const; + ustring conductor () const; + ustring remixer () const; + ustring arranger () const; + ustring engineer () const; + ustring producer () const; + ustring dj_mixer () const; + ustring mixer () const; + + ustring album () const; + ustring compilation () const; + ustring disc_subtitle () const; + uint32_t disc_number () const; + uint32_t total_discs () const; + uint32_t track_number () const; + uint32_t total_tracks () const; + + ustring genre () const; + + /*** Editing ***/ + void set_comment (const ustring &); + void set_copyright (const ustring &); + void set_isrc (const ustring &); + void set_year (uint32_t); + + void set_grouping (const ustring &); + void set_title (const ustring &); + void set_subtitle (const ustring &); + + void set_artist (const ustring &); + void set_album_artist (const ustring &); + void set_lyricist (const ustring &); + void set_composer (const ustring &); + void set_conductor (const ustring &); + void set_remixer (const ustring &); + void set_arranger (const ustring &); + void set_engineer (const ustring &); + void set_producer (const ustring &); + void set_dj_mixer (const ustring &); + void set_mixer (const ustring &); + + void set_album (const ustring &); + void set_compilation (const ustring &); + void set_disc_subtitle (const ustring &); + void set_disc_number (uint32_t); + void set_total_discs (uint32_t); + void set_track_number (uint32_t); + void set_total_tracks (uint32_t); + + void set_genre (const ustring &); + + /*** Serialization ***/ + XMLNode & get_state (); + int set_state (const XMLNode &); + + private: + + typedef std::pair<ustring, ustring> Property; + typedef std::map<ustring, ustring> PropertyMap; + PropertyMap map; + + XMLNode * get_xml (const ustring & name); + + ustring get_value (const ustring & name) const; + uint32_t get_uint_value (const ustring & name) const; + + void set_value (const ustring & name, const ustring & value); + void set_value (const ustring & name, uint32_t value); +}; + +} // namespace ARDOUR + +#endif // __ardour_session_metadata_h__ diff --git a/libs/ardour/audiofile_tagger.cc b/libs/ardour/audiofile_tagger.cc new file mode 100644 index 0000000000..7391bf9ae3 --- /dev/null +++ b/libs/ardour/audiofile_tagger.cc @@ -0,0 +1,117 @@ +/* + 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/audiofile_tagger.h> + +#include <ardour/session_metadata.h> + +#include <pbd/convert.h> + +#include <taglib/fileref.h> +#include <taglib/oggfile.h> +#include <taglib/flacfile.h> + +/* Convert Glib::ustring to TagLib::String */ +#define TL_STR(ustring) TagLib::String ((ustring).c_str(), TagLib::String::UTF8) + +using namespace PBD; + +namespace ARDOUR +{ + +bool +AudiofileTagger::tag_file (string const & filename, SessionMetadata const & metadata) +{ + TagLib::FileRef file (filename.c_str()); + TagLib::Tag & tag (*file.tag()); + + tag_generic (tag, metadata); + + /* FLAC */ + + TagLib::FLAC::File * flac_file; + if ((flac_file = dynamic_cast<TagLib::FLAC::File *> (file.file()))) { + TagLib::Ogg::XiphComment * vorbis_tag; + if ((vorbis_tag = dynamic_cast<TagLib::Ogg::XiphComment *> (flac_file->xiphComment (true)))) { + tag_vorbis_comment (*vorbis_tag, metadata); + } else { + std::cerr << "Could not get Xiph comment for FLAC file!" << std::endl; + } + } + + /* Ogg */ + + TagLib::Ogg::File * ogg_file; + if ((ogg_file = dynamic_cast<TagLib::Ogg::File *> (file.file()))) { + TagLib::Ogg::XiphComment * vorbis_tag; + if ((vorbis_tag = dynamic_cast<TagLib::Ogg::XiphComment *> (ogg_file->tag()))) { + tag_vorbis_comment (*vorbis_tag, metadata); + } else { + std::cerr << "Could not get Xiph comment for Ogg file!" << std::endl; + } + } + + file.save(); + return true; +} + +bool +AudiofileTagger::tag_generic (TagLib::Tag & tag, SessionMetadata const & metadata) +{ + tag.setTitle (TL_STR(metadata.title())); + tag.setArtist (TL_STR(metadata.artist())); + tag.setAlbum (TL_STR(metadata.album())); + tag.setComment (TL_STR(metadata.comment())); + tag.setGenre (TL_STR(metadata.genre())); + tag.setYear (metadata.year()); + tag.setTrack (metadata.track_number()); + + return true; +} + +bool +AudiofileTagger::tag_vorbis_comment (TagLib::Ogg::XiphComment & tag, SessionMetadata const & metadata) +{ + tag.addField ("COPYRIGHT", TL_STR(metadata.copyright())); + tag.addField ("ISRC", TL_STR(metadata.isrc())); + tag.addField ("GROUPING ", TL_STR(metadata.grouping())); + tag.addField ("SUBTITLE", TL_STR(metadata.subtitle())); + tag.addField ("ALBUMARTIST", TL_STR(metadata.album_artist())); + tag.addField ("LYRICIST", TL_STR(metadata.lyricist())); + tag.addField ("COMPOSER", TL_STR(metadata.composer())); + tag.addField ("CONDUCTOR", TL_STR(metadata.conductor())); + tag.addField ("REMIXER", TL_STR(metadata.remixer())); + tag.addField ("ARRANGER", TL_STR(metadata.arranger())); + tag.addField ("ENGINEER", TL_STR(metadata.engineer())); + tag.addField ("PRODUCER", TL_STR(metadata.producer())); + tag.addField ("DJMIXER", TL_STR(metadata.dj_mixer())); + tag.addField ("MIXER", TL_STR(metadata.mixer())); + tag.addField ("COMPILATION", TL_STR(metadata.compilation())); + tag.addField ("DISCSUBTITLE", TL_STR(metadata.disc_subtitle())); + tag.addField ("DISCNUMBER", to_string (metadata.disc_number(), std::dec)); + + // No field for total discs or tracks + + return true; +} + + +} // namespace ARDOUR + diff --git a/libs/ardour/broadcast_info.cc b/libs/ardour/broadcast_info.cc new file mode 100644 index 0000000000..b887c4fe18 --- /dev/null +++ b/libs/ardour/broadcast_info.cc @@ -0,0 +1,268 @@ +/* + 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/broadcast_info.h> + +#include <sstream> +#include <iomanip> + +#include <glibmm.h> + +#include <ardour/svn_revision.h> +#include <ardour/ardour.h> +#include <ardour/session.h> + +#include <pbd/convert.h> + +using namespace PBD; + +namespace ARDOUR +{ + +BroadcastInfo::BroadcastInfo () : + _has_info (false) +{ + info = new SF_BROADCAST_INFO; + memset (info, 0, sizeof (*info)); + + // Note: Set version to 1 when UMID is used, otherwise version should stay at 0 + info->version = 0; + + time_t rawtime; + std::time (&rawtime); + _time = *localtime (&rawtime); +} + +BroadcastInfo::~BroadcastInfo () +{ + delete info; +} + +void +BroadcastInfo::set_from_session (Session const & session, int64_t time) +{ + set_description (session.name()); + set_time_reference (time); + set_origination_time (); + set_originator (); + set_originator_ref (); +} + +bool +BroadcastInfo::load_from_file (string const & filename) +{ + SNDFILE * file = 0; + SF_INFO info; + + info.format = 0; + + if (!(file = sf_open (filename.c_str(), SFM_READ, &info))) { + update_error(); + return false; + } + + bool ret = load_from_file (file); + + sf_close (file); + return ret; +} + +bool +BroadcastInfo::load_from_file (SNDFILE* sf) +{ + if (sf_command (sf, SFC_GET_BROADCAST_INFO, info, sizeof (*info)) != SF_TRUE) { + update_error(); + _has_info = false; + return false; + } + + _has_info = true; + return true; +} + +string +BroadcastInfo::get_description () const +{ + return info->description; +} + +int64_t +BroadcastInfo::get_time_reference () const +{ + if (!_has_info) { + return 0; + } + + int64_t ret = (uint32_t) info->time_reference_high; + ret <<= 32; + ret |= (uint32_t) info->time_reference_low; + return ret; +} + +struct tm +BroadcastInfo::get_origination_time () const +{ + struct tm ret; + + string date = info->origination_date; + ret.tm_year = atoi (date.substr (0, 4)) - 1900; + ret.tm_mon = atoi (date.substr (5, 2)); + ret.tm_mday = atoi (date.substr (8, 2)); + + string time = info->origination_time; + ret.tm_hour = atoi (time.substr (0,2)); + ret.tm_min = atoi (time.substr (3,2)); + ret.tm_sec = atoi (time.substr (6,2)); + + return ret; +} + +string +BroadcastInfo::get_originator () const +{ + return info->originator; +} + +string +BroadcastInfo::get_originator_ref () const +{ + return info->originator_reference; +} + +bool +BroadcastInfo::write_to_file (string const & filename) +{ + SNDFILE * file = 0; + SF_INFO info; + + info.format = 0; + + if (!(file = sf_open (filename.c_str(), SFM_RDWR, &info))) { + update_error(); + return false; + } + + bool ret = write_to_file (file); + + sf_close (file); + return ret; +} + +bool +BroadcastInfo::write_to_file (SNDFILE* sf) +{ + if (sf_command (sf, SFC_SET_BROADCAST_INFO, info, sizeof (*info)) != SF_TRUE) { + update_error(); + return false; + } + + return true; +} + +void +BroadcastInfo::set_description (string const & desc) +{ + _has_info = true; + + snprintf (info->description, sizeof (info->description), desc.c_str()); +} + +void +BroadcastInfo::set_time_reference (int64_t when) +{ + _has_info = true; + + info->time_reference_high = (when >> 32); + info->time_reference_low = (when & 0xffffffff); +} + +void +BroadcastInfo::set_origination_time (struct tm * now) +{ + _has_info = true; + + if (now) { + _time = *now; + } + + snprintf (info->origination_date, sizeof (info->origination_date), "%4d-%02d-%02d", + _time.tm_year + 1900, + _time.tm_mon + 1, + _time.tm_mday); + + snprintf (info->origination_time, sizeof (info->origination_time), "%02d:%02d:%02d", + _time.tm_hour, + _time.tm_min, + _time.tm_sec); +} + +void +BroadcastInfo::set_originator (string const & str) +{ + _has_info = true; + + if (!str.empty()) { + snprintf (info->originator, sizeof (info->originator), str.c_str()); + return; + } + + snprintf (info->originator, sizeof (info->originator), Glib::get_real_name().c_str()); +} + +void +BroadcastInfo::set_originator_ref (string const & str) +{ + _has_info = true; + + if (!str.empty()) { + snprintf (info->originator_reference, sizeof (info->originator_reference), str.c_str()); + return; + } + + /* random code is 9 digits */ + + int random_code = random() % 999999999; + + /* Serial number is 12 chars */ + + std::ostringstream serial_number; + serial_number << "ARDOUR" << "r" << std::setfill('0') << std::right << std::setw(5) << svn_revision; + + snprintf (info->originator_reference, sizeof (info->originator_reference), "%2s%3s%12s%02d%02d%02d%9d", + Config->get_bwf_country_code().c_str(), + Config->get_bwf_organization_code().c_str(), + serial_number.str().c_str(), + _time.tm_hour, + _time.tm_min, + _time.tm_sec, + random_code); + +} + +void +BroadcastInfo::update_error () +{ + char errbuf[256]; + sf_error_str (0, errbuf, sizeof (errbuf) - 1); + error = errbuf; +} + +} // namespace ARDOUR + diff --git a/libs/ardour/export_channel_configuration.cc b/libs/ardour/export_channel_configuration.cc new file mode 100644 index 0000000000..cd29a4e578 --- /dev/null +++ b/libs/ardour/export_channel_configuration.cc @@ -0,0 +1,204 @@ +/* + 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_channel_configuration.h> + +#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> +#include <ardour/export_failed.h> +#include <ardour/midi_port.h> +#include <pbd/pthread_utils.h> + +namespace ARDOUR +{ + +/* ExportChannel */ + +ExportChannel::ExportChannel () +{ + +} + +ExportChannel::~ExportChannel () +{ + +} + +void +ExportChannel::read_ports (float * data, nframes_t frames) const +{ + memset (data, 0, frames * sizeof (float)); + + for (iterator it = begin(); it != end(); ++it) { + if (*it != 0) { + Sample* port_buffer = (*it)->get_audio_buffer().data(); + + for (uint32_t i = 0; i < frames; ++i) { + data[i] += (float) port_buffer[i]; + } + } + } +} + +/* ExportChannelConfiguration */ + +ExportChannelConfiguration::ExportChannelConfiguration (ExportStatus & status) : + writer_thread (*this), + status (status), + files_written (false), + split (false) +{ + +} + +ExportChannelConfiguration::~ExportChannelConfiguration () +{ + +} + +bool +ExportChannelConfiguration::all_channels_have_ports () +{ + for (ChannelList::iterator it = channels.begin(); it != channels.end(); ++it) { + if ((*it)->empty ()) { return false; } + } + + 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 (_("Export failed due to a 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) +{ + + PBD::ThreadCreated (pthread_self(), "Export post-processing"); + + // cc can be trated like 'this' + WriterThread & cc (*((WriterThread *) arg)); + + 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(); + } + + 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 new file mode 100644 index 0000000000..ed60713903 --- /dev/null +++ b/libs/ardour/export_file_io.cc @@ -0,0 +1,366 @@ +/* + 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_file_io.h> +#include <ardour/export_failed.h> +#include <pbd/failed_constructor.h> + +#include "i18n.h" + +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 (_("Export failed due to a programming error"), "Invalid format given for SndfileWriter!"); + } + + if (path.length() == 0) { + throw ExportFailed (_("Export failed due to a programming error"), "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(_("Export: cannot open output file \"%1\""), path), + string_compose(_("Export: cannot open output file \"%1\" for SndFileWriter (%2)"), path, errbuf)); + } + } else { + FILE * file; + if (!(file = tmpfile ())) { + throw ExportFailed (_("Export failed due to a programming error"), "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 (_("Writing export file failed"), 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 (_("Reading export file failed"), _("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); +} + +}; diff --git a/libs/ardour/export_filename.cc b/libs/ardour/export_filename.cc new file mode 100644 index 0000000000..b6f59d1a93 --- /dev/null +++ b/libs/ardour/export_filename.cc @@ -0,0 +1,362 @@ +/* + 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_filename.h> + +#include <pbd/xml++.h> +#include <pbd/convert.h> +#include <pbd/enumwriter.h> + +#include <ardour/session.h> +#include <ardour/session_directory.h> +#include <ardour/export_timespan.h> +#include <ardour/export_format_specification.h> +#include <ardour/export_channel_configuration.h> +#include <ardour/export_failed.h> + +#include "i18n.h" + +using namespace PBD; + +namespace ARDOUR +{ + +ExportFilename::ExportFilename (Session & session) : + include_label (false), + include_session (false), + include_revision (false), + include_channel_config (false), + include_channel (false), + include_timespan (true), // Include timespan name always + include_time (false), + include_date (false), + session (session), + revision (1) +{ + time_t rawtime; + std::time (&rawtime); + time_struct = localtime (&rawtime); + + folder = session.session_directory().export_path().to_string(); + + XMLNode * instant_node = session.instant_xml ("ExportFilename"); + if (instant_node) { + set_state (*instant_node); + } +} + +XMLNode & +ExportFilename::get_state () +{ + XMLNode * node = new XMLNode ("ExportFilename"); + XMLNode * child; + + FieldPair dir = analyse_folder(); + child = node->add_child ("Folder"); + child->add_property ("relative", dir.first ? "true" : "false"); + child->add_property ("path", dir.second); + + add_field (node, "label", include_label, label); + add_field (node, "session", include_session); + add_field (node, "revision", include_revision); + add_field (node, "time", include_time, enum_2_string (time_format)); + add_field (node, "date", include_date, enum_2_string (date_format)); + + XMLNode * instant_node = new XMLNode ("ExportRevision"); + instant_node->add_property ("revision", to_string (revision, std::dec)); + session.add_instant_xml (*instant_node); + + return *node; +} + +int +ExportFilename::set_state (const XMLNode & node) +{ + XMLNode * child; + XMLProperty * prop; + FieldPair pair; + + child = node.child ("Folder"); + if (!child) { return -1; } + + folder = ""; + + if ((prop = child->property ("relative"))) { + if (!prop->value().compare ("true")) { + folder = session.session_directory().root_path().to_string(); + } + } + + if ((prop = child->property ("path"))) { + folder += prop->value(); + } + + + pair = get_field (node, "label"); + include_label = pair.first; + label = pair.second; + + pair = get_field (node, "session"); + include_session = pair.first; + + pair = get_field (node, "revision"); + include_revision = pair.first; + + pair = get_field (node, "time"); + include_time = pair.first; + time_format = (TimeFormat) string_2_enum (pair.second, time_format); + + pair = get_field (node, "date"); + include_date = pair.first; + date_format = (DateFormat) string_2_enum (pair.second, date_format); + + XMLNode * instant_node = session.instant_xml ("ExportRevision"); + if (instant_node && (prop = instant_node->property ("revision"))) { + revision = atoi (prop->value()); + } + + return 0; +} + +ustring +ExportFilename::get_path (FormatPtr format) const +{ + ustring path = folder; + bool filename_empty = true; + + path += "/"; + + if (include_session) { + path += filename_empty ? "" : "_"; + path += session.name(); + filename_empty = false; + } + + if (include_label) { + path += filename_empty ? "" : "_"; + path += label; + filename_empty = false; + } + + if (include_revision) { + path += filename_empty ? "" : "_"; + path += "r"; + path += to_string (revision, std::dec); + filename_empty = false; + } + + if (include_timespan && timespan) { + path += filename_empty ? "" : "_"; + path += timespan->name(); + filename_empty = false; + } + + if (include_channel_config && channel_config) { + path += filename_empty ? "" : "_"; + path += channel_config->name(); + filename_empty = false; + } + + if (include_channel) { + path += filename_empty ? "" : "_"; + path += "channel"; + path += to_string (channel, std::dec); + filename_empty = false; + } + + if (include_date) { + path += filename_empty ? "" : "_"; + path += get_date_format_str (date_format); + filename_empty = false; + } + + if (include_time) { + path += filename_empty ? "" : "_"; + path += get_time_format_str (time_format); + filename_empty = false; + } + + path += "."; + path += format->extension (); + + return path; +} + +ustring +ExportFilename::get_time_format_str (TimeFormat format) const +{ + switch ( format ) { + case T_None: + return _("No Time"); + + case T_NoDelim: + return get_formatted_time ("%H%M"); + + case T_Delim: + return get_formatted_time ("%H.%M"); + + default: + return _("Invalid time format"); + } +} + +ustring +ExportFilename::get_date_format_str (DateFormat format) const +{ + switch (format) { + case D_None: + return _("No Date"); + + case D_BE: + return get_formatted_time ("%Y%m%d"); + + case D_ISO: + return get_formatted_time ("%Y-%m-%d"); + + case D_BEShortY: + return get_formatted_time ("%y%m%d"); + + case D_ISOShortY: + return get_formatted_time ("%y-%m-%d"); + + default: + return _("Invalid date format"); + } +} + +void +ExportFilename::set_time_format (TimeFormat format) +{ + time_format = format; + + if (format == T_None) { + include_time = false; + } else { + include_time = true; + } +} + +void +ExportFilename::set_date_format (DateFormat format) +{ + date_format = format; + + if (format == D_None) { + include_date = false; + } else { + include_date = true; + } +} + +void +ExportFilename::set_label (ustring value) +{ + label = value; + include_label = !value.compare (""); +} + +bool +ExportFilename::set_folder (ustring path) +{ + // TODO check folder existence + folder = path; + return true; +} + +ustring +ExportFilename::get_formatted_time (ustring const & format) const +{ + char buffer [80]; + strftime (buffer, 80, format.c_str(), time_struct); + + ustring return_value (buffer); + return return_value; +} + +void +ExportFilename::add_field (XMLNode * node, ustring const & name, bool enabled, ustring const & value) +{ + XMLNode * child = node->add_child ("Field"); + + if (!child) { + std::cerr << "Error adding a field to ExportFilename XML-tree" << std::endl; + return; + } + + child->add_property ("name", name); + child->add_property ("enabled", enabled ? "true" : "false"); + if (!value.empty()) { + child->add_property ("value", value); + } +} + +ExportFilename::FieldPair +ExportFilename::get_field (XMLNode const & node, ustring const & name) +{ + FieldPair pair; + pair.first = false; + + XMLNodeList children = node.children(); + + for (XMLNodeList::iterator it = children.begin(); it != children.end(); ++it) { + XMLProperty * prop = (*it)->property ("name"); + if (prop && !prop->value().compare (name)) { + + prop = (*it)->property ("enabled"); + if (prop && !prop->value().compare ("true")) { + pair.first = true; + } else { + pair.first = false; + } + + prop = (*it)->property ("value"); + if (prop) { + pair.second = prop->value(); + } + + return pair; + } + } + + return pair; +} + +ExportFilename::FieldPair +ExportFilename::analyse_folder () +{ + FieldPair pair; + + ustring session_dir = session.session_directory().root_path().to_string(); + ustring::size_type session_dir_len = session_dir.length(); + + ustring folder_beginning = folder.substr (0, session_dir_len); + + if (!folder_beginning.compare (session_dir)) { + pair.first = true; + pair.second = folder.substr (session_dir_len); + } else { + pair.first = false; + pair.second = folder; + } + + return pair; +} + +} // namespace ARDOUR diff --git a/libs/ardour/export_format_base.cc b/libs/ardour/export_format_base.cc new file mode 100644 index 0000000000..bddd61d0ab --- /dev/null +++ b/libs/ardour/export_format_base.cc @@ -0,0 +1,201 @@ +/* + 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_format_base.h> + +namespace ARDOUR +{ + +void +ExportFormatBase::SelectableCompatible::set_selected (bool value) +{ + if (_selected != value) { + _selected = value; + SelectChanged (value); + } +} + +void +ExportFormatBase::SelectableCompatible::set_compatible (bool value) +{ + if (_compatible != value) { + _compatible = value; + CompatibleChanged (value); + } + if (!value) { + set_selected (false); + } +} + +ExportFormatBase::ExportFormatBase () +{ + +} + +ExportFormatBase::ExportFormatBase (ExportFormatBase const & other) : + sample_formats (other.sample_formats), + endiannesses (other.endiannesses), + sample_rates (other.sample_rates), + format_ids (other.format_ids), + qualities (other.qualities) +{ + +} + +ExportFormatBase::~ExportFormatBase () +{ + +} + +boost::shared_ptr<ExportFormatBase> +ExportFormatBase::get_intersection (ExportFormatBase const & other) const +{ + return do_set_operation (other, SetIntersection); +} + +boost::shared_ptr<ExportFormatBase> +ExportFormatBase::get_difference (ExportFormatBase const & other) const +{ + return do_set_operation (other, SetDifference); +} + +boost::shared_ptr<ExportFormatBase> +ExportFormatBase::get_union (ExportFormatBase const & other) const +{ + return do_set_operation (other, SetUnion); +} + +boost::shared_ptr<ExportFormatBase> +ExportFormatBase::do_set_operation (ExportFormatBase const & other, SetOperation operation) const +{ + boost::shared_ptr<ExportFormatBase> result (new ExportFormatBase ()); + + /* Sets */ + + // Endiannesses + { + EndianSet::const_iterator start1 = endiannesses.begin(); + EndianSet::const_iterator end1 = endiannesses.end(); + EndianSet::const_iterator start2 = other.endiannesses.begin(); + EndianSet::const_iterator end2 = other.endiannesses.end(); + std::insert_iterator<EndianSet> insert (result->endiannesses, result->endiannesses.begin()); + + switch (operation) { + case SetIntersection: + std::set_intersection (start1, end1, start2, end2, insert); + break; + case SetDifference: + std::set_difference (start1, end1, start2, end2, insert); + break; + case SetUnion: + std::set_union (start1, end1, start2, end2, insert); + break; + } + } + + // Sample formats + { + SampleFormatSet::const_iterator start1 = sample_formats.begin(); + SampleFormatSet::const_iterator end1 = sample_formats.end(); + SampleFormatSet::const_iterator start2 = other.sample_formats.begin(); + SampleFormatSet::const_iterator end2 = other.sample_formats.end(); + std::insert_iterator<SampleFormatSet> insert (result->sample_formats, result->sample_formats.begin()); + + switch (operation) { + case SetIntersection: + std::set_intersection (start1, end1, start2, end2, insert); + break; + case SetDifference: + std::set_difference (start1, end1, start2, end2, insert); + break; + case SetUnion: + std::set_union (start1, end1, start2, end2, insert); + break; + } + } + + + // Sample rates + { + SampleRateSet::const_iterator start1 = sample_rates.begin(); + SampleRateSet::const_iterator end1 = sample_rates.end(); + SampleRateSet::const_iterator start2 = other.sample_rates.begin(); + SampleRateSet::const_iterator end2 = other.sample_rates.end(); + std::insert_iterator<SampleRateSet> insert (result->sample_rates, result->sample_rates.begin()); + + switch (operation) { + case SetIntersection: + std::set_intersection (start1, end1, start2, end2, insert); + break; + case SetDifference: + std::set_difference (start1, end1, start2, end2, insert); + break; + case SetUnion: + std::set_union (start1, end1, start2, end2, insert); + break; + } + } + + // Format ids + { + FormatSet::const_iterator start1 = format_ids.begin(); + FormatSet::const_iterator end1 = format_ids.end(); + FormatSet::const_iterator start2 = other.format_ids.begin(); + FormatSet::const_iterator end2 = other.format_ids.end(); + std::insert_iterator<FormatSet> insert (result->format_ids, result->format_ids.begin()); + + switch (operation) { + case SetIntersection: + std::set_intersection (start1, end1, start2, end2, insert); + break; + case SetDifference: + std::set_difference (start1, end1, start2, end2, insert); + break; + case SetUnion: + std::set_union (start1, end1, start2, end2, insert); + break; + } + } + + // Qualities + { + QualitySet::const_iterator start1 = qualities.begin(); + QualitySet::const_iterator end1 = qualities.end(); + QualitySet::const_iterator start2 = other.qualities.begin(); + QualitySet::const_iterator end2 = other.qualities.end(); + std::insert_iterator<QualitySet> insert (result->qualities, result->qualities.begin()); + + switch (operation) { + case SetIntersection: + std::set_intersection (start1, end1, start2, end2, insert); + break; + case SetDifference: + std::set_difference (start1, end1, start2, end2, insert); + break; + case SetUnion: + std::set_union (start1, end1, start2, end2, insert); + break; + } + } + + return result; +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_format_manager.cc b/libs/ardour/export_format_manager.cc new file mode 100644 index 0000000000..e9e722dfec --- /dev/null +++ b/libs/ardour/export_format_manager.cc @@ -0,0 +1,749 @@ +/* + 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_format_manager.h> + +#include <ardour/types.h> +#include <ardour/export_format_specification.h> +#include <ardour/export_format_compatibility.h> + +#include "i18n.h" + +namespace ARDOUR +{ + +ExportFormatManager::ExportFormatManager (SpecPtr specification) : + pending_selection_change (false), + universal_set (new ExportFormatBase ()) +{ + current_selection = specification; + + init_compatibilities (); + init_qualities (); + init_formats (); + init_sample_rates (); +} + +ExportFormatManager::~ExportFormatManager () +{ + +} + +void +ExportFormatManager::init_compatibilities () +{ + CompatPtr c_ptr; + + c_ptr.reset (new ExportFormatCompatibility (_("CD"))); + c_ptr->add_sample_rate (ExportFormatBase::SR_44_1); + c_ptr->add_format_id (ExportFormatBase::F_WAV); + c_ptr->add_format_id (ExportFormatBase::F_AIFF); + c_ptr->add_quality (ExportFormatBase::Q_LosslessLinear); + c_ptr->add_sample_format (ExportFormatBase::SF_16); + c_ptr->add_endianness (ExportFormatBase::E_FileDefault); + add_compatibility (c_ptr); + + c_ptr.reset (new ExportFormatCompatibility (_("DVD-A"))); + c_ptr->add_sample_rate (ExportFormatBase::SR_44_1); + c_ptr->add_sample_rate (ExportFormatBase::SR_48); + c_ptr->add_sample_rate (ExportFormatBase::SR_88_2); + c_ptr->add_sample_rate (ExportFormatBase::SR_96); + c_ptr->add_sample_rate (ExportFormatBase::SR_192); + c_ptr->add_format_id (ExportFormatBase::F_WAV); + c_ptr->add_format_id (ExportFormatBase::F_AIFF); + c_ptr->add_quality (ExportFormatBase::Q_LosslessLinear); + c_ptr->add_sample_format (ExportFormatBase::SF_16); + c_ptr->add_sample_format (ExportFormatBase::SF_24); + c_ptr->add_endianness (ExportFormatBase::E_FileDefault); + add_compatibility (c_ptr); + + c_ptr.reset (new ExportFormatCompatibility (_("iPod"))); + c_ptr->add_sample_rate (ExportFormatBase::SR_44_1); + c_ptr->add_sample_rate (ExportFormatBase::SR_48); + c_ptr->add_format_id (ExportFormatBase::F_WAV); + c_ptr->add_format_id (ExportFormatBase::F_AIFF); + c_ptr->add_quality (ExportFormatBase::Q_LosslessLinear); + c_ptr->add_sample_format (ExportFormatBase::SF_16); + c_ptr->add_sample_format (ExportFormatBase::SF_24); + c_ptr->add_endianness (ExportFormatBase::E_FileDefault); + add_compatibility (c_ptr); + + c_ptr.reset (new ExportFormatCompatibility (_("Something else"))); + c_ptr->add_sample_rate (ExportFormatBase::SR_44_1); + c_ptr->add_sample_rate (ExportFormatBase::SR_48); + c_ptr->add_format_id (ExportFormatBase::F_WAV); + c_ptr->add_format_id (ExportFormatBase::F_AIFF); + c_ptr->add_format_id (ExportFormatBase::F_AU); + c_ptr->add_format_id (ExportFormatBase::F_FLAC); + c_ptr->add_quality (ExportFormatBase::Q_LosslessLinear); + c_ptr->add_quality (ExportFormatBase::Q_LosslessCompression); + c_ptr->add_sample_format (ExportFormatBase::SF_16); + c_ptr->add_sample_format (ExportFormatBase::SF_24); + c_ptr->add_sample_format (ExportFormatBase::SF_32); + c_ptr->add_endianness (ExportFormatBase::E_FileDefault); + add_compatibility (c_ptr); +} + +void +ExportFormatManager::init_qualities () +{ + add_quality (QualityPtr (new QualityState (ExportFormatBase::Q_Any, _("Any")))); + add_quality (QualityPtr (new QualityState (ExportFormatBase::Q_LosslessLinear, _("Lossless (linear PCM)")))); + add_quality (QualityPtr (new QualityState (ExportFormatBase::Q_LossyCompression, _("Lossy compression")))); + add_quality (QualityPtr (new QualityState (ExportFormatBase::Q_LosslessCompression, _("Lossless compression")))); +} + +void +ExportFormatManager::init_formats () +{ + FormatPtr f_ptr; + ExportFormatLinear * fl_ptr; + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("AIFF", ExportFormatBase::F_AIFF)); + fl_ptr->add_sample_format (ExportFormatBase::SF_U8); + fl_ptr->add_sample_format (ExportFormatBase::SF_8); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->add_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_16); + fl_ptr->set_extension ("aiff"); + add_format (f_ptr); + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("AU", ExportFormatBase::F_AU)); + fl_ptr->add_sample_format (ExportFormatBase::SF_8); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->add_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_16); + fl_ptr->set_extension ("au"); + add_format (f_ptr); + + f_ptr.reset (new ExportFormatBWF ()); + add_format (f_ptr); + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("IRCAM", ExportFormatBase::F_IRCAM)); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_24); + fl_ptr->set_extension ("sf"); + add_format (f_ptr); + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("WAV", ExportFormatBase::F_WAV)); + fl_ptr->add_sample_format (ExportFormatBase::SF_U8); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->add_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_16); + fl_ptr->set_extension ("wav"); + add_format (f_ptr); + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("W64", ExportFormatBase::F_W64)); + fl_ptr->add_sample_format (ExportFormatBase::SF_U8); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->add_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_extension ("w64"); + add_format (f_ptr); + + f_ptr.reset (fl_ptr = new ExportFormatLinear ("RAW", ExportFormatBase::F_RAW)); + fl_ptr->add_sample_format (ExportFormatBase::SF_U8); + fl_ptr->add_sample_format (ExportFormatBase::SF_8); + fl_ptr->add_sample_format (ExportFormatBase::SF_16); + fl_ptr->add_sample_format (ExportFormatBase::SF_24); + fl_ptr->add_sample_format (ExportFormatBase::SF_32); + fl_ptr->add_sample_format (ExportFormatBase::SF_Float); + fl_ptr->add_sample_format (ExportFormatBase::SF_Double); + fl_ptr->set_default_sample_format (ExportFormatBase::SF_Float); + fl_ptr->set_extension ("raw"); + add_format (f_ptr); + + f_ptr.reset (new ExportFormatOggVorbis ()); + add_format (f_ptr); + + f_ptr.reset (new ExportFormatFLAC ()); + add_format (f_ptr); +} + +void +ExportFormatManager::init_sample_rates () +{ + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_22_05, "22,05 kHz"))); + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_44_1, "44,1 kHz"))); + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_48, "48 kHz"))); + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_88_2, "88,2 kHz"))); + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_96, "96 kHz"))); + add_sample_rate (SampleRatePtr (new SampleRateState (ExportFormatBase::SR_192, "192 kHz"))); +} + +void +ExportFormatManager::add_compatibility (CompatPtr ptr) +{ + compatibilities.push_back (ptr); + ptr->SelectChanged.connect (sigc::bind (sigc::mem_fun (*this, &ExportFormatManager::change_compatibility_selection), WeakCompatPtr (ptr))); +} + +void +ExportFormatManager::add_quality (QualityPtr ptr) +{ + ptr->SelectChanged.connect (sigc::bind (sigc::mem_fun (*this, &ExportFormatManager::change_quality_selection), WeakQualityPtr (ptr))); + qualities.push_back (ptr); +} + +void +ExportFormatManager::add_format (FormatPtr ptr) +{ + formats.push_back (ptr); + ptr->SelectChanged.connect (sigc::bind (sigc::mem_fun (*this, &ExportFormatManager::change_format_selection), WeakFormatPtr (ptr))); + universal_set = universal_set->get_union (*ptr); + + /* Encoding options */ + + boost::shared_ptr<HasSampleFormat> hsf; + + if (hsf = boost::dynamic_pointer_cast<HasSampleFormat> (ptr)) { + hsf->SampleFormatSelectChanged.connect (sigc::mem_fun (*this, &ExportFormatManager::change_sample_format_selection)); + hsf->DitherTypeSelectChanged.connect (sigc::mem_fun (*this, &ExportFormatManager::change_dither_type_selection)); + } +} + +void +ExportFormatManager::add_sample_rate (SampleRatePtr ptr) +{ + ptr->SelectChanged.connect (sigc::bind (sigc::mem_fun (*this, &ExportFormatManager::change_sample_rate_selection), WeakSampleRatePtr (ptr))); + sample_rates.push_back (ptr); +} + +void +ExportFormatManager::set_name (Glib::ustring name) +{ + current_selection->set_name (name); +} + +void +ExportFormatManager::select_src_quality (ExportFormatBase::SRCQuality value) +{ + current_selection->set_src_quality (value); +} + +void +ExportFormatManager::select_trim_beginning (bool value) +{ + current_selection->set_trim_beginning (value); +} + +void +ExportFormatManager::select_silence_beginning (AnyTime const & time) +{ + current_selection->set_silence_beginning (time); +} + +void +ExportFormatManager::select_trim_end (bool value) +{ + current_selection->set_trim_end (value); +} + +void +ExportFormatManager::select_silence_end (AnyTime const & time) +{ + current_selection->set_silence_end (time); +} + +void +ExportFormatManager::select_normalize (bool value) +{ + current_selection->set_normalize (value); +} + +void +ExportFormatManager::select_normalize_target (float value) +{ + current_selection->set_normalize_target (value); +} + +void +ExportFormatManager::select_tagging (bool tag) +{ + current_selection->set_tag (tag); +} + +void +ExportFormatManager::change_compatibility_selection (bool select, WeakCompatPtr const & compat) +{ + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + CompatPtr ptr = compat.lock(); + + if (ptr && select) { + select_compatibility (ptr); + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::change_quality_selection (bool select, WeakQualityPtr const & quality) +{ + QualityPtr ptr = quality.lock (); + + if (!ptr) { + return; + } + + if (select) { + select_quality (ptr); + } else if (ptr->quality == current_selection->quality()) { + ptr.reset(); + select_quality (ptr); + } +} + +void +ExportFormatManager::change_format_selection (bool select, WeakFormatPtr const & format) +{ + FormatPtr ptr = format.lock(); + + if (!ptr) { + return; + } + + if (select) { + select_format (ptr); + } else if (ptr->get_format_id() == current_selection->format_id()) { + ptr.reset(); + select_format (ptr); + } +} + +void +ExportFormatManager::change_sample_rate_selection (bool select, WeakSampleRatePtr const & rate) +{ + SampleRatePtr ptr = rate.lock(); + + if (!ptr) { + return; + } + + if (select) { + select_sample_rate (ptr); + } else if (ptr->rate == current_selection->sample_rate()) { + ptr.reset(); + select_sample_rate (ptr); + } +} + +void +ExportFormatManager::change_sample_format_selection (bool select, WeakSampleFormatPtr const & format) +{ + SampleFormatPtr ptr = format.lock(); + + if (!ptr) { + return; + } + + if (select) { + select_sample_format (ptr); + } else if (ptr->format == current_selection->sample_format()) { + ptr.reset(); + select_sample_format (ptr); + } +} + +void +ExportFormatManager::change_dither_type_selection (bool select, WeakDitherTypePtr const & type) +{ + DitherTypePtr ptr = type.lock(); + + if (!ptr) { + return; + } + + if (select) { + select_dither_type (ptr); + } else if (ptr->type == current_selection->dither_type()) { + ptr.reset(); + select_dither_type (ptr); + } +} + +void +ExportFormatManager::select_compatibility (WeakCompatPtr const & compat) +{ + /* Calculate compatibility intersection for the selection */ + + FormatBasePtr compat_intersect = get_compatibility_intersection (); + + /* Unselect incompatible items */ + + boost::shared_ptr<ExportFormatBase> select_intersect; + + select_intersect = compat_intersect->get_intersection (*current_selection); + if (select_intersect->qualities_empty()) { + select_quality (QualityPtr()); + } + + select_intersect = compat_intersect->get_intersection (*current_selection); + if (select_intersect->formats_empty()) { + select_format (FormatPtr()); + } + + select_intersect = compat_intersect->get_intersection (*current_selection); + if (select_intersect->sample_rates_empty()) { + select_sample_rate (SampleRatePtr()); + } + + select_intersect = compat_intersect->get_intersection (*current_selection); + if (select_intersect->sample_formats_empty()) { + select_sample_format (SampleFormatPtr()); + } +} + +void +ExportFormatManager::select_quality (QualityPtr const & quality) +{ + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + if (quality) { + current_selection->set_quality (quality->quality); + + /* Deselect format if it is incompatible */ + + FormatPtr format = get_selected_format(); + if (format && !format->has_quality (quality->quality)) { + format->set_selected (false); + } + + } else { + current_selection->set_quality (ExportFormatBase::Q_None); + + QualityPtr current_quality = get_selected_quality(); + if (current_quality) { + current_quality->set_selected (false); + } + + /* Note: + * A quality is never explicitly deselected without also deselecting the format + * so we don't need to deselect the format here. + * doing so causes extra complications + */ + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::select_format (FormatPtr const & format) +{ + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + current_selection->set_format (format); + + if (format) { + + /* Slect right quality for format */ + + ExportFormatBase::Quality quality = format->get_quality(); + for (QualityList::iterator it = qualities.begin (); it != qualities.end (); ++it) { + if ((*it)->quality == quality) { + (*it)->set_selected (true); + } else { + (*it)->set_selected (false); + } + } + + /* Handle sample formats */ + + ExportFormatBase::SampleFormat format_to_select; + if (format->sample_format_is_compatible (current_selection->sample_format())) { + format_to_select = current_selection->sample_format(); + } else { + format_to_select = format->default_sample_format(); + } + + boost::shared_ptr<HasSampleFormat> hsf; + if ((hsf = boost::dynamic_pointer_cast<HasSampleFormat> (format))) { + SampleFormatList sample_formats = hsf->get_sample_formats(); + for (SampleFormatList::iterator it = sample_formats.begin (); it != sample_formats.end (); ++it) { + if ((*it)->format == format_to_select) { + (*it)->set_selected (true); + } else { + (*it)->set_selected (false); + } + } + } + + current_selection->set_sample_format (format_to_select); + + } else { + FormatPtr current_format = get_selected_format (); + if (current_format) { + current_format->set_selected (false); + } + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::select_sample_rate (SampleRatePtr const & rate) +{ + + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + if (rate) { + current_selection->set_sample_rate (rate->rate); + } else { + current_selection->set_sample_rate (ExportFormatBase::SR_None); + + SampleRatePtr current_rate = get_selected_sample_rate(); + if (current_rate) { + current_rate->set_selected (false); + } + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::select_sample_format (SampleFormatPtr const & format) +{ + + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + if (format) { + current_selection->set_sample_format (format->format); + } else { + current_selection->set_sample_format (ExportFormatBase::SF_None); + + SampleFormatPtr current_format = get_selected_sample_format(); + if (current_format) { + current_format->set_selected (false); + } + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::select_dither_type (DitherTypePtr const & type) +{ + + bool do_selection_changed = !pending_selection_change; + if (!pending_selection_change) { + pending_selection_change = true; + } + + if (type) { + current_selection->set_dither_type (type->type); + } else { + current_selection->set_dither_type (ExportFormatBase::D_None); + } + + if (do_selection_changed) { + selection_changed (); + } +} + +void +ExportFormatManager::selection_changed () +{ + /* Get a list of incompatible compatibility selections */ + + CompatList incompatibles; + for (CompatList::iterator it = compatibilities.begin(); it != compatibilities.end(); ++it) { + if (!current_selection->is_compatible_with (**it)) { + incompatibles.push_back (*it); + } + } + + /* Deselect them */ + + for (CompatList::iterator it = incompatibles.begin(); it != incompatibles.end(); ++it) { + (*it)->set_selected (false); + } + + /* Mark compatibility for everything necessary */ + + std::set<ExportFormatBase::Quality> compatible_qualities; + FormatBasePtr compat_intersect = get_compatibility_intersection (); + ExportFormatCompatibility global_compat (*compat_intersect); + + for (FormatList::iterator it = formats.begin(); it != formats.end(); ++it) { + if ((*it)->set_compatibility_state (global_compat)) { + compatible_qualities.insert ((*it)->get_quality()); + } + } + + bool any_quality_compatible = true; + for (QualityList::iterator it = qualities.begin(); it != qualities.end(); ++it) { + if (compatible_qualities.find((*it)->quality) != compatible_qualities.end()) { + (*it)->set_compatible (true); + + } else { + (*it)->set_compatible (false); + + if ((*it)->quality != ExportFormatBase::Q_Any) { + any_quality_compatible = false; + } + } + } + + if (any_quality_compatible) { + for (QualityList::iterator it = qualities.begin(); it != qualities.end(); ++it) { + if ((*it)->quality == ExportFormatBase::Q_Any) { + (*it)->set_compatible (true); + break; + } + } + } + + for (SampleRateList::iterator it = sample_rates.begin(); it != sample_rates.end(); ++it) { + if (compat_intersect->has_sample_rate ((*it)->rate)) { + (*it)->set_compatible (true); + } else { + (*it)->set_compatible (false); + } + } + + boost::shared_ptr<HasSampleFormat> hsf; + if (hsf = boost::dynamic_pointer_cast<HasSampleFormat> (get_selected_format())) { + + SampleFormatList sf_list = hsf->get_sample_formats(); + for (SampleFormatList::iterator it = sf_list.begin(); it != sf_list.end(); ++it) { + if (compat_intersect->has_sample_format ((*it)->format)) { + (*it)->set_compatible (true); + } else { + (*it)->set_compatible (false); + } + } + + } + + /* Signal completeness */ + + CompleteChanged (current_selection->is_complete()); + + /* Reset pending state */ + + pending_selection_change = false; +} + +ExportFormatManager::QualityPtr +ExportFormatManager::get_selected_quality () +{ + for (QualityList::iterator it = qualities.begin(); it != qualities.end(); ++it) { + if ((*it)->selected()) { + return *it; + } + } + + return QualityPtr(); +} + +ExportFormatManager::FormatPtr +ExportFormatManager::get_selected_format () +{ + FormatPtr format; + + for (FormatList::iterator it = formats.begin(); it != formats.end(); ++it) { + if ((*it)->selected()) { + return *it; + } + } + + return format; +} + +ExportFormatManager::SampleRatePtr +ExportFormatManager::get_selected_sample_rate () +{ + for (SampleRateList::iterator it = sample_rates.begin(); it != sample_rates.end(); ++it) { + if ((*it)->selected()) { + return *it; + } + } + + return SampleRatePtr(); +} + +ExportFormatManager::SampleFormatPtr +ExportFormatManager::get_selected_sample_format () +{ + boost::shared_ptr<HasSampleFormat> hsf; + + if (hsf = boost::dynamic_pointer_cast<HasSampleFormat> (get_selected_format())) { + return hsf->get_selected_sample_format (); + } else { + return SampleFormatPtr (); + } +} + + +ExportFormatManager::FormatBasePtr +ExportFormatManager::get_compatibility_intersection () +{ + FormatBasePtr compat_intersect = universal_set; + + for (CompatList::iterator it = compatibilities.begin(); it != compatibilities.end(); ++it) { + if ((*it)->selected ()) { + compat_intersect = compat_intersect->get_intersection (**it); + } + } + + return compat_intersect; +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_format_specification.cc b/libs/ardour/export_format_specification.cc new file mode 100644 index 0000000000..61d9bef523 --- /dev/null +++ b/libs/ardour/export_format_specification.cc @@ -0,0 +1,602 @@ +/* + 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_format_specification.h> + +#include <sstream> + +#include <ardour/export_format_compatibility.h> +#include <ardour/export_formats.h> +#include <ardour/session.h> + +#include <pbd/error.h> +#include <pbd/xml++.h> +#include <pbd/enumwriter.h> +#include <pbd/convert.h> + +#include "i18n.h" + +namespace ARDOUR +{ + +using namespace PBD; +using std::string; + +/* The id counter is initialized to 1000 so that user created profiles have a id > 1000 + * while ones shipped with ardour have one < 1000 + */ +uint32_t ExportFormatSpecification::_counter = 1000; + +ExportFormatSpecification::Time & +ExportFormatSpecification::Time::operator= (AnyTime const & other) +{ + type = other.type; + smpte = other.smpte; + bbt = other.bbt; + + if (type == Frames) { + frames = other.frames; + } else { + seconds = other.seconds; + } + + return *this; +} + +nframes_t +ExportFormatSpecification::Time::get_frames (nframes_t target_rate) const +{ + //TODO position + nframes_t duration = session.convert_to_frames_at (0, *this); + + return ((double) target_rate / session.frame_rate()) * duration + 0.5; +} + +XMLNode & +ExportFormatSpecification::Time::get_state () +{ + + XMLNode * node = new XMLNode ("Duration"); + + node->add_property ("format", enum_2_string (type)); + + switch (type) { + case SMPTE: + node->add_property ("hours", to_string (smpte.hours, std::dec)); + node->add_property ("minutes", to_string (smpte.minutes, std::dec)); + node->add_property ("seconds", to_string (smpte.seconds, std::dec)); + node->add_property ("frames", to_string (smpte.frames, std::dec)); + break; + case BBT: + node->add_property ("bars", to_string (bbt.bars, std::dec)); + node->add_property ("beats", to_string (bbt.beats, std::dec)); + node->add_property ("ticks", to_string (bbt.ticks, std::dec)); + break; + case Frames: + node->add_property ("frames", to_string (frames, std::dec)); + break; + case Seconds: + node->add_property ("seconds", to_string (seconds, std::dec)); + break; + } + + return *node; +} + +int +ExportFormatSpecification::Time::set_state (const XMLNode & node) +{ + XMLProperty const * prop; + + prop = node.property ("format"); + + if (!prop) { return -1; } + + type = (Type) string_2_enum (prop->value(), Type); + + switch (type) { + case SMPTE: + if ((prop = node.property ("hours"))) { + smpte.hours = atoi (prop->value()); + } + + if ((prop = node.property ("minutes"))) { + smpte.minutes = atoi (prop->value()); + } + + if ((prop = node.property ("seconds"))) { + smpte.seconds = atoi (prop->value()); + } + + if ((prop = node.property ("frames"))) { + smpte.frames = atoi (prop->value()); + } + + break; + + case BBT: + if ((prop = node.property ("bars"))) { + bbt.bars = atoi (prop->value()); + } + + if ((prop = node.property ("beats"))) { + bbt.beats = atoi (prop->value()); + } + + if ((prop = node.property ("ticks"))) { + bbt.ticks = atoi (prop->value()); + } + + break; + + case Frames: + if ((prop = node.property ("frames"))) { + std::istringstream iss (prop->value()); + iss >> frames; + } + + break; + + case Seconds: + if ((prop = node.property ("seconds"))) { + seconds = atof (prop->value()); + } + + break; + } + + return 0; +} + +ExportFormatSpecification::ExportFormatSpecification (Session & s) : + session (s), + + has_sample_format (false), + supports_tagging (false), + _has_broadcast_info (false), + _channel_limit (0), + _dither_type (D_None), + _src_quality (SRC_SincBest), + _tag (true), + + _trim_beginning (false), + _silence_beginning (s), + _trim_end (false), + _silence_end (s), + + _normalize (false), + _normalize_target (1.0) +{ + format_ids.insert (F_None); + endiannesses.insert (E_FileDefault); + sample_formats.insert (SF_None); + sample_rates.insert (SR_None); + qualities.insert (Q_None); + + _id = ++_counter; +} + +ExportFormatSpecification::ExportFormatSpecification (Session & s, XMLNode const & state) : + session (s), + _silence_beginning (s), + _silence_end (s) +{ + _silence_beginning.type = Time::SMPTE; + _silence_end.type = Time::SMPTE; + + set_state (state); +} + +ExportFormatSpecification::ExportFormatSpecification (ExportFormatSpecification const & other) : + session (other.session), + _silence_beginning (other.session), + _silence_end (other.session) +{ + set_name (other.name() + " (copy)"); + _id = ++_counter; + + _format_name = other._format_name; + has_sample_format = other.has_sample_format; + + supports_tagging = other.supports_tagging; + _has_broadcast_info = other._has_broadcast_info; + _channel_limit = other._channel_limit; + + set_type (other.type()); + set_format_id (other.format_id()); + set_endianness (other.endianness()); + set_sample_format (other.sample_format()); + set_sample_rate (other.sample_rate()); + set_quality (other.quality()); + + set_dither_type (other.dither_type()); + set_src_quality (other.src_quality()); + set_trim_beginning (other.trim_beginning()); + set_trim_end (other.trim_end()); + set_normalize (other.normalize()); + set_normalize_target (other.normalize_target()); + + set_tag (other.tag()); + + set_silence_beginning (other.silence_beginning_time()); + set_silence_end (other.silence_end_time()); +} + +ExportFormatSpecification::~ExportFormatSpecification () +{ +} + +XMLNode & +ExportFormatSpecification::get_state () +{ + XMLNode * node; + XMLNode * root = new XMLNode ("ExportFormatSpecification"); + + root->add_property ("name", _name); + root->add_property ("id", to_string (_id, std::dec)); + + node = root->add_child ("Encoding"); + node->add_property ("id", enum_2_string (format_id())); + node->add_property ("type", enum_2_string (type())); + node->add_property ("extension", extension()); + node->add_property ("name", _format_name); + node->add_property ("has-sample-format", has_sample_format ? "true" : "false"); + node->add_property ("channel-limit", to_string (_channel_limit, std::dec)); + + node = root->add_child ("SampleRate"); + node->add_property ("rate", to_string (sample_rate(), std::dec)); + + node = root->add_child ("SRCQuality"); + node->add_property ("quality", enum_2_string (src_quality())); + + XMLNode * enc_opts = root->add_child ("EncodingOptions"); + + add_option (enc_opts, "sample-format", enum_2_string (sample_format())); + add_option (enc_opts, "dithering", enum_2_string (dither_type())); + add_option (enc_opts, "tag-metadata", _tag ? "true" : "false"); + add_option (enc_opts, "tag-support", supports_tagging ? "true" : "false"); + add_option (enc_opts, "broadcast-info", _has_broadcast_info ? "true" : "false"); + + XMLNode * processing = root->add_child ("Processing"); + + node = processing->add_child ("Normalize"); + node->add_property ("enabled", normalize() ? "true" : "false"); + node->add_property ("target", to_string (normalize_target(), std::dec)); + + XMLNode * silence = processing->add_child ("Silence"); + XMLNode * start = silence->add_child ("Start"); + XMLNode * end = silence->add_child ("End"); + + node = start->add_child ("Trim"); + node->add_property ("enabled", trim_beginning() ? "true" : "false"); + + node = start->add_child ("Add"); + node->add_property ("enabled", silence_beginning() > 0 ? "true" : "false"); + node->add_child_nocopy (_silence_beginning.get_state()); + + node = end->add_child ("Trim"); + node->add_property ("enabled", trim_end() ? "true" : "false"); + + node = end->add_child ("Add"); + node->add_property ("enabled", silence_end() > 0 ? "true" : "false"); + node->add_child_nocopy (_silence_end.get_state()); + + return *root; +} + +int +ExportFormatSpecification::set_state (const XMLNode & root) +{ + XMLProperty const * prop; + XMLNode const * child; + string value; + + if ((prop = root.property ("name"))) { + _name = prop->value(); + } + + if ((prop = root.property ("id"))) { + std::istringstream iss (prop->value()); + iss >> _id; + } + + /* Encoding and SRC */ + + if ((child = root.child ("Encoding"))) { + if ((prop = child->property ("id"))) { + set_format_id ((FormatId) string_2_enum (prop->value(), FormatId)); + } + + if ((prop = child->property ("type"))) { + set_type ((Type) string_2_enum (prop->value(), Type)); + } + + if ((prop = child->property ("extension"))) { + set_extension (prop->value()); + } + + if ((prop = child->property ("name"))) { + _format_name = prop->value(); + } + + if ((prop = child->property ("has-sample-format"))) { + has_sample_format = !prop->value().compare ("true"); + } + + if ((prop = child->property ("channel-limit"))) { + _channel_limit = atoi (prop->value()); + } + } + + if ((child = root.child ("SampleRate"))) { + if ((prop = child->property ("rate"))) { + set_sample_rate ( (SampleRate) string_2_enum (prop->value(), SampleRate)); + } + } + + if ((child = root.child ("SRCQuality"))) { + if ((prop = child->property ("quality"))) { + _src_quality = (SRCQuality) string_2_enum (prop->value(), SRCQuality); + } + } + + /* Encoding options */ + + if ((child = root.child ("EncodingOptions"))) { + set_sample_format ((SampleFormat) string_2_enum (get_option (child, "sample-format"), SampleFormat)); + set_dither_type ((DitherType) string_2_enum (get_option (child, "dithering"), DitherType)); + set_tag (!(get_option (child, "tag-metadata").compare ("true"))); + supports_tagging = (!(get_option (child, "tag-support").compare ("true"))); + _has_broadcast_info = (!(get_option (child, "broadcast-info").compare ("true"))); + } + + /* Processing */ + + XMLNode const * proc = root.child ("Processing"); + if (!proc) { std::cerr << X_("Could not load processing for export format") << std::endl; return -1; } + + if ((child = proc->child ("Normalize"))) { + if ((prop = child->property ("enabled"))) { + _normalize = (!prop->value().compare ("true")); + } + + if ((prop = child->property ("target"))) { + _normalize_target = atof (prop->value()); + } + } + + XMLNode const * silence = proc->child ("Silence"); + if (!silence) { std::cerr << X_("Could not load silence for export format") << std::endl; return -1; } + + XMLNode const * start = silence->child ("Start"); + XMLNode const * end = silence->child ("End"); + if (!start || !end) { std::cerr << X_("Could not load end or start silence for export format") << std::endl; return -1; } + + /* Silence start */ + + if ((child = start->child ("Trim"))) { + if ((prop = child->property ("enabled"))) { + _trim_beginning = (!prop->value().compare ("true")); + } + } + + if ((child = start->child ("Add"))) { + if ((prop = child->property ("enabled"))) { + if (!prop->value().compare ("true")) { + if ((child = child->child ("Duration"))) { + _silence_beginning.set_state (*child); + } + } else { + _silence_beginning.type = Time::SMPTE; + } + } + } + + /* Silence end */ + + if ((child = end->child ("Trim"))) { + if ((prop = child->property ("enabled"))) { + _trim_end = (!prop->value().compare ("true")); + } + } + + if ((child = end->child ("Add"))) { + if ((prop = child->property ("enabled"))) { + if (!prop->value().compare ("true")) { + if ((child = child->child ("Duration"))) { + _silence_end.set_state (*child); + } + } else { + _silence_end.type = Time::SMPTE; + } + } + } + + return 0; +} + +bool +ExportFormatSpecification::is_compatible_with (ExportFormatCompatibility const & compatibility) const +{ + boost::shared_ptr<ExportFormatBase> intersection = get_intersection (compatibility); + + if (intersection->formats_empty() && format_id() != 0) { + return false; + } + + if (intersection->endiannesses_empty() && endianness() != E_FileDefault) { + return false; + } + + if (intersection->sample_rates_empty() && sample_rate() != SR_None) { + return false; + } + + if (intersection->sample_formats_empty() && sample_format() != SF_None) { + return false; + } + + if (intersection->qualities_empty() && quality() != Q_None) { + return false; + } + + return true; +} + +bool +ExportFormatSpecification::is_complete () const +{ + if (type() == T_None) { + return false; + } + + if (!format_id()) { + return false; + } + + if (!sample_rate()) { + return false; + } + + if (has_sample_format) { + if (sample_format() == SF_None) { + return false; + } + } + + return true; +} + +void +ExportFormatSpecification::set_format (boost::shared_ptr<ExportFormat> format) +{ + if (format) { + set_format_id (format->get_format_id ()); + set_type (format->get_type()); + set_extension (format->extension()); + + if (format->get_explicit_sample_format()) { + set_sample_format (format->get_explicit_sample_format()); + } + + if (format->has_sample_format()) { + has_sample_format = true; + } + + if (format->has_broadcast_info()) { + _has_broadcast_info = true; + } + + supports_tagging = format->supports_tagging (); + _channel_limit = format->get_channel_limit(); + + _format_name = format->name(); + } else { + set_format_id (F_None); + set_type (T_None); + set_extension (""); + _has_broadcast_info = false; + has_sample_format = false; + supports_tagging = false; + _channel_limit = 0; + _format_name = ""; + } +} + +Glib::ustring +ExportFormatSpecification::description () +{ + Glib::ustring desc; + + desc = _name + ": "; + + if (_normalize) { + desc += _("normalize, "); + } + + if (_trim_beginning && _trim_end) { + desc += _("trim, "); + } else if (_trim_beginning) { + desc += _("trim start, "); + } else if (_trim_end) { + desc += "trim end, "; + } + + desc += _format_name + ", "; + + if (has_sample_format) { + desc += HasSampleFormat::get_sample_format_name (sample_format()) + ", "; + } + + switch (sample_rate()) { + case SR_22_05: + desc += "22,5 kHz"; + break; + case SR_44_1: + desc += "44,1 kHz"; + break; + case SR_48: + desc += "48 kHz"; + break; + case SR_88_2: + desc += "88,2 kHz"; + break; + case SR_96: + desc += "96 kHz"; + break; + case SR_192: + desc += "192 kHz"; + break; + case SR_None: + break; + } + + return desc; +} + +void +ExportFormatSpecification::add_option (XMLNode * node, std::string const & name, std::string const & value) +{ + node = node->add_child ("Option"); + node->add_property ("name", name); + node->add_property ("value", value); +} + +std::string +ExportFormatSpecification::get_option (XMLNode const * node, std::string const & name) +{ + XMLNodeList list (node->children ("Option")); + + for (XMLNodeList::iterator it = list.begin(); it != list.end(); ++it) { + XMLProperty * prop = (*it)->property ("name"); + if (prop && !name.compare (prop->value())) { + prop = (*it)->property ("value"); + if (prop) { + return prop->value(); + } + } + } + + std::cerr << "Could not load encoding option \"" << name << "\" for export format" << std::endl; + + return ""; +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_formats.cc b/libs/ardour/export_formats.cc new file mode 100644 index 0000000000..7df54bad1c --- /dev/null +++ b/libs/ardour/export_formats.cc @@ -0,0 +1,331 @@ +/* + 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_formats.h> + +#include "i18n.h" + +namespace ARDOUR +{ + +bool +ExportFormat::has_sample_format () +{ + return dynamic_cast<HasSampleFormat *> (this); +} + +bool +ExportFormat::sample_format_is_compatible (SampleFormat format) const +{ + return (sample_formats.find (format) != sample_formats.end()); +} + +/*** HasSampleFormat ***/ + +HasSampleFormat::HasSampleFormat (ExportFormatBase::SampleFormatSet & sample_formats) : + _sample_formats (sample_formats) +{ + /* Dither Types */ + + add_dither_type (ExportFormatBase::D_Shaped, _("Shaped Noise")); + add_dither_type (ExportFormatBase::D_Tri, _("Triangular")); + add_dither_type (ExportFormatBase::D_Rect, _("Rectangular")); + add_dither_type (ExportFormatBase::D_None, _("None")); +} + +void +HasSampleFormat::add_sample_format (ExportFormatBase::SampleFormat format) +{ + _sample_formats.insert (format); + + SampleFormatPtr ptr (new SampleFormatState (format, get_sample_format_name (format))); + sample_format_states.push_back (ptr); + ptr->SelectChanged.connect (sigc::bind (SampleFormatSelectChanged.make_slot(), WeakSampleFormatPtr (ptr))); + ptr->SelectChanged.connect (sigc::mem_fun (*this, &HasSampleFormat::update_sample_format_selection)); + ptr->CompatibleChanged.connect (sigc::bind (SampleFormatCompatibleChanged.make_slot(), WeakSampleFormatPtr (ptr))); +} + +void +HasSampleFormat::add_dither_type (ExportFormatBase::DitherType type, Glib::ustring name) +{ + DitherTypePtr ptr (new DitherTypeState (type, name)); + dither_type_states.push_back (ptr); + ptr->SelectChanged.connect (sigc::bind (DitherTypeSelectChanged.make_slot(), WeakDitherTypePtr (ptr))); + ptr->SelectChanged.connect (sigc::mem_fun (*this, &HasSampleFormat::update_dither_type_selection)); + ptr->CompatibleChanged.connect (sigc::bind (DitherTypeCompatibleChanged.make_slot(), WeakDitherTypePtr (ptr))); +} + +HasSampleFormat::SampleFormatPtr +HasSampleFormat::get_selected_sample_format () +{ + for (SampleFormatList::iterator it = sample_format_states.begin(); it != sample_format_states.end(); ++it) { + if ((*it)->selected()) { + return *it; + } + } + + return SampleFormatPtr(); +} + +HasSampleFormat::DitherTypePtr +HasSampleFormat::get_selected_dither_type () +{ + for (DitherTypeList::iterator it = dither_type_states.begin(); it != dither_type_states.end(); ++it) { + if ((*it)->selected()) { + return *it; + } + } + + return DitherTypePtr(); +} + +void +HasSampleFormat::update_sample_format_selection (bool) +{ + SampleFormatPtr format = get_selected_sample_format(); + if (!format) { + return; + } + + if (format->format == ExportFormatBase::SF_24 || + format->format == ExportFormatBase::SF_32 || + format->format == ExportFormatBase::SF_Float || + format->format == ExportFormatBase::SF_Double) { + for (DitherTypeList::iterator it = dither_type_states.begin(); it != dither_type_states.end(); ++it) { + if ((*it)->type == ExportFormatBase::D_None) { + (*it)->set_selected (true); + } else { + (*it)->set_compatible (false); + } + } + + } else { + for (DitherTypeList::iterator it = dither_type_states.begin(); it != dither_type_states.end(); ++it) { + (*it)->set_compatible (true); + } + } +} + +void +HasSampleFormat::update_dither_type_selection (bool) +{ + DitherTypePtr type = get_selected_dither_type(); + if (!type) { + return; + } + + if (!type->compatible()) { + SampleFormatPtr format = get_selected_sample_format(); + if (format) { + format->set_selected (false); + } + + for (DitherTypeList::iterator it = dither_type_states.begin(); it != dither_type_states.end(); ++it) { + (*it)->set_compatible (true); + } + } +} + +string +HasSampleFormat::get_sample_format_name (ExportFormatBase::SampleFormat format) +{ + switch (format) { + case ExportFormatBase::SF_8: + return _("8bit"); + case ExportFormatBase::SF_16: + return _("16bit"); + case ExportFormatBase::SF_24: + return _("24bit"); + case ExportFormatBase::SF_32: + return _("32bit"); + case ExportFormatBase::SF_Float: + return _("float"); + case ExportFormatBase::SF_Double: + return _("double"); + case ExportFormatBase::SF_U8: + return _("8bit unsigned"); + case ExportFormatBase::SF_Vorbis: + return _("Vorbis sample format"); + case ExportFormatBase::SF_None: + return _("No sample format"); + } + return ""; +} + +/*** Linear ***/ + +ExportFormatLinear::ExportFormatLinear (Glib::ustring name, FormatId format_id) : + HasSampleFormat (sample_formats), + _default_sample_format (SF_None) +{ + set_name (name); + set_format_id (format_id); + + add_sample_rate (SR_22_05); + add_sample_rate (SR_44_1); + add_sample_rate (SR_48); + add_sample_rate (SR_88_2); + add_sample_rate (SR_96); + add_sample_rate (SR_192); + + add_endianness (E_FileDefault); + + set_quality (Q_LosslessLinear); +} + +bool +ExportFormatLinear::set_compatibility_state (ExportFormatCompatibility const & compatibility) +{ + /* Global state */ + + bool compatible = true; + + if (!compatibility.has_quality (Q_LosslessLinear)) { + compatible = false; + } + + if (!compatibility.has_format (get_format_id())) { + compatible = false; + } + + boost::shared_ptr<ExportFormatBase> intersection = get_intersection (compatibility); + + if (intersection->endiannesses_empty()) { + compatible = false; + } + + if (intersection->sample_rates_empty()) { + compatible = false; + } + + if (intersection->sample_formats_empty()) { + compatible = false; + } + + set_compatible (compatible); + + /* Sample Formats */ + + for (SampleFormatList::iterator it = sample_format_states.begin(); it != sample_format_states.end(); ++it) { + (*it)->set_compatible (compatibility.has_sample_format ((*it)->format)); + } + + return compatible; +} + +/*** Ogg Vorbis ***/ + +ExportFormatOggVorbis::ExportFormatOggVorbis () +{ + set_name ("Ogg Vorbis"); + set_format_id (F_Ogg); + sample_formats.insert (SF_Vorbis); + + add_sample_rate (SR_22_05); + add_sample_rate (SR_44_1); + add_sample_rate (SR_48); + add_sample_rate (SR_88_2); + add_sample_rate (SR_96); + add_sample_rate (SR_192); + + add_endianness (E_FileDefault); + + set_extension ("ogg"); + set_quality (Q_LossyCompression); +} + +bool +ExportFormatOggVorbis::set_compatibility_state (ExportFormatCompatibility const & compatibility) +{ + bool compatible = compatibility.has_format (F_Ogg); + set_compatible (compatible); + return compatible; +} + +/*** FLAC ***/ + +ExportFormatFLAC::ExportFormatFLAC () : + HasSampleFormat (sample_formats) +{ + set_name ("FLAC"); + set_format_id (F_FLAC); + + add_sample_rate (SR_22_05); + add_sample_rate (SR_44_1); + add_sample_rate (SR_48); + add_sample_rate (SR_88_2); + add_sample_rate (SR_96); + add_sample_rate (SR_192); + + add_sample_format (SF_8); + add_sample_format (SF_16); + add_sample_format (SF_24); + + add_endianness (E_FileDefault); + + set_extension ("flac"); + set_quality (Q_LosslessCompression); +} + +bool +ExportFormatFLAC::set_compatibility_state (ExportFormatCompatibility const & compatibility) +{ + bool compatible = compatibility.has_format (F_FLAC); + set_compatible (compatible); + return compatible; +} + +/*** BWF ***/ + +ExportFormatBWF::ExportFormatBWF () : + HasSampleFormat (sample_formats) +{ + set_name ("BWF"); + set_format_id (F_WAV); + + add_sample_rate (SR_22_05); + add_sample_rate (SR_44_1); + add_sample_rate (SR_48); + add_sample_rate (SR_88_2); + add_sample_rate (SR_96); + add_sample_rate (SR_192); + + add_sample_format (SF_U8); + add_sample_format (SF_16); + add_sample_format (SF_24); + add_sample_format (SF_32); + add_sample_format (SF_Float); + add_sample_format (SF_Double); + + add_endianness (E_FileDefault); + + set_extension ("wav"); + set_quality (Q_LosslessLinear); +} + +bool +ExportFormatBWF::set_compatibility_state (ExportFormatCompatibility const & compatibility) +{ + bool compatible = compatibility.has_format (F_WAV); + set_compatible (compatible); + return compatible; +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_handler.cc b/libs/ardour/export_handler.cc new file mode 100644 index 0000000000..9173449bec --- /dev/null +++ b/libs/ardour/export_handler.cc @@ -0,0 +1,552 @@ +/* + 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_handler.h> + +#include <pbd/convert.h> +#include <pbd/filesystem.h> + +#include <ardour/ardour.h> +#include <ardour/configuration.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 PBD; +using std::ofstream; + +namespace ARDOUR +{ + +/*** ExportElementFactory ***/ + +ExportElementFactory::ExportElementFactory (Session & session) : + session (session) +{ + XMLProperty * prop; + XMLNode * instant_node = ARDOUR::Config->instant_xml ("ExportFormatSpecification"); + if (instant_node && (prop = instant_node->property ("id-counter"))) { + ExportFormatSpecification::init_counter (atoi (prop->value())); + } +} + +ExportElementFactory::~ExportElementFactory () +{ + XMLNode * instant_node = new XMLNode ("ExportFormatSpecification"); + instant_node->add_property ("id-counter", to_string (ExportFormatSpecification::counter(), std::dec)); + ARDOUR::Config->add_instant_xml (*instant_node); +} + +ExportElementFactory::TimespanPtr +ExportElementFactory::add_timespan () +{ + return TimespanPtr (new ExportTimespan (session.export_status, session.frame_rate())); +} + +ExportElementFactory::ChannelConfigPtr +ExportElementFactory::add_channel_config () +{ + return ChannelConfigPtr (new ExportChannelConfiguration (session.export_status)); +} + +ExportElementFactory::FormatPtr +ExportElementFactory::add_format () +{ + return FormatPtr (new ExportFormatSpecification (session)); +} + +ExportElementFactory::FormatPtr +ExportElementFactory::add_format (XMLNode const & state) +{ + return FormatPtr (new ExportFormatSpecification (session, state)); +} + +ExportElementFactory::FormatPtr +ExportElementFactory::add_format_copy (FormatPtr other) +{ + return FormatPtr (new ExportFormatSpecification (*other)); +} + +ExportElementFactory::FilenamePtr +ExportElementFactory::add_filename () +{ + return FilenamePtr (new ExportFilename (session)); +} + +ExportElementFactory::FilenamePtr +ExportElementFactory::add_filename_copy (FilenamePtr other) +{ + return FilenamePtr (new ExportFilename (*other)); +} + +/*** ExportHandler ***/ + +ExportHandler::ExportHandler (Session & session) : + ExportElementFactory (session), + session (session), + realtime (false) +{ + processor.reset (new ExportProcessor (session)); + + files_written_connection = ExportProcessor::WritingFile.connect (sigc::mem_fun (files_written, &std::list<Glib::ustring>::push_back)); +} + +ExportHandler::~ExportHandler () +{ + if (session.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(); +} + +bool +ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename) +{ + FileSpec spec (channel_config, format, filename); + ConfigPair pair (timespan, spec); + config_map.insert (pair); + + 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 ExportFinished 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) +{ + /* Count timespans */ + + ExportStatus & status = session.export_status; + status.init(); + std::set<TimespanPtr> timespan_set; + for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) { + timespan_set.insert (it->first); + } + status.total_timespans = timespan_set.size(); + + /* Start export */ + + realtime = rt; + + session.ExportFinished.connect (sigc::mem_fun (*this, &ExportHandler::finish_timespan)); + session.pre_export (); + start_timespan (); +} + +struct LocationSortByStart { + bool operator() (Location *a, Location *b) { + return a->start() < b->start(); + } +}; + +void +ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format) +{ + string filepath; + void (ExportHandler::*header_func) (CDMarkerStatus &); + void (ExportHandler::*track_func) (CDMarkerStatus &); + void (ExportHandler::*index_func) (CDMarkerStatus &); + + switch (format) { + case CDMarkerTOC: + filepath = filename + ".toc"; + header_func = &ExportHandler::write_toc_header; + track_func = &ExportHandler::write_track_info_toc; + index_func = &ExportHandler::write_index_info_toc; + break; + case CDMarkerCUE: + filepath = filename + ".cue"; + header_func = &ExportHandler::write_cue_header; + track_func = &ExportHandler::write_track_info_cue; + index_func = &ExportHandler::write_index_info_cue; + break; + default: + return; + } + + CDMarkerStatus status (filepath, timespan, file_format, filename); + + if (!status.out) { + error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg; + return; + } + + (this->*header_func) (status); + + /* Get locations and sort */ + + Locations::LocationList const & locations (session.locations()->list()); + Locations::LocationList::const_iterator i; + Locations::LocationList temp; + + for (i = locations.begin(); i != locations.end(); ++i) { + if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) { + temp.push_back (*i); + } + } + + if (temp.empty()) { + // TODO One index marker for whole thing + return; + } + + LocationSortByStart cmp; + temp.sort (cmp); + Locations::LocationList::const_iterator nexti; + + /* Start actual marker stuff */ + + nframes_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) { + + status.marker = *i; + + if ((*i)->start() < last_end_time) { + if ((*i)->is_mark()) { + /* Index within track */ + + status.index_position = (*i)->start() - timespan->get_start(); + (this->*index_func) (status); + } + + continue; + } + + /* A track, defined by a cd range marker or a cd location marker outside of a cd range */ + + status.track_position = last_end_time - timespan->get_start(); + status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap + status.track_duration = 0; + + if ((*i)->is_mark()) { + // a mark track location needs to look ahead to the next marker's start to determine length + nexti = i; + ++nexti; + + if (nexti != temp.end()) { + status.track_duration = (*nexti)->start() - last_end_time; + + last_start_time = (*i)->start(); + last_end_time = (*nexti)->start(); + } else { + // this was the last marker, use timespan end + status.track_duration = timespan->get_end() - last_end_time; + + last_start_time = (*i)->start(); + last_end_time = timespan->get_end(); + } + } else { + // range + status.track_duration = (*i)->end() - last_end_time; + + last_start_time = (*i)->start(); + last_end_time = (*i)->end(); + } + + (this->*track_func) (status); + } +} + +void +ExportHandler::write_cue_header (CDMarkerStatus & status) +{ + Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name(); + + status.out << "REM Cue file generated by Ardour" << endl; + status.out << "TITLE \"" << title << "\"" << endl; + + // TODO + if (!status.format->format_name().compare ("WAV")) { + status.out << "FILE " << status.filename << " WAVE" << endl; + } else { + status.out << "FILE " << status.filename << ' ' << status.format->format_name() << endl; + } +} + +void +ExportHandler::write_toc_header (CDMarkerStatus & status) +{ + Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name(); + + status.out << "CD_DA" << endl; + status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl; + status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl; +} + +void +ExportHandler::write_track_info_cue (CDMarkerStatus & status) +{ + gchar buf[18]; + + status.out << endl << "TRACK " << status.track_number << " AUDIO" << endl; + status.out << "FLAGS " ; + + if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) { + status.out << "SCMS "; + } else { + status.out << "DCP "; + } + + if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) { + status.out << "PRE"; + } + status.out << endl; + + if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { + status.out << "ISRC " << status.marker->cd_info["isrc"] << endl; + + } + if (status.marker->name() != "") { + status.out << "TITLE \"" << status.marker->name() << "\"" << endl; + } + + if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) { + status.out << "PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl; + } + + if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) { + status.out << "SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl; + } + + frames_to_cd_frames_string (buf, status.track_position); + status.out << "INDEX 00" << buf << endl; + + frames_to_cd_frames_string (buf, status.track_start_frame); + status.out << "INDEX 01" << buf << endl; + + status.index_number = 2; + status.track_number++; +} + +void +ExportHandler::write_track_info_toc (CDMarkerStatus & status) +{ + gchar buf[18]; + + status.out << endl << "TRACK AUDIO" << endl; + + if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) { + status.out << "NO "; + } + status.out << "COPY" << endl; + + if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) { + status.out << "PRE_EMPHASIS" << endl; + } else { + status.out << "NO PRE_EMPHASIS" << endl; + } + + if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { + status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl; + } + + status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl; + if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) { + status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl; + } + if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) { + status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl; + } + + if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) { + status.out << " ISRC \""; + status.out << status.marker->cd_info["isrc"].substr(0,2) << "-"; + status.out << status.marker->cd_info["isrc"].substr(2,3) << "-"; + status.out << status.marker->cd_info["isrc"].substr(5,2) << "-"; + status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl; + } + + status.out << " }" << endl << "}" << endl; + + frames_to_cd_frames_string (buf, status.track_position); + status.out << "FILE \"" << status.filename << "\" " << buf; + + frames_to_cd_frames_string (buf, status.track_duration); + status.out << buf << endl; + + frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position); + status.out << "START" << buf << endl; +} + +void +ExportHandler::write_index_info_cue (CDMarkerStatus & status) +{ + gchar buf[18]; + + snprintf (buf, sizeof(buf), "INDEX %02d", cue_indexnum); + status.out << buf; + frames_to_cd_frames_string (buf, status.index_position); + status.out << buf << endl; + + cue_indexnum++; +} + +void +ExportHandler::write_index_info_toc (CDMarkerStatus & status) +{ + gchar buf[18]; + + frames_to_cd_frames_string (buf, status.index_position - status.track_position); + status.out << "INDEX" << buf << endl; +} + +void +ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when) +{ + nframes_t remainder; + nframes_t fr = session.nominal_frame_rate(); + int mins, secs, frames; + + mins = when / (60 * fr); + remainder = when - (mins * 60 * fr); + secs = remainder / fr; + remainder -= secs * fr; + frames = remainder / (fr / 75); + sprintf (buf, " %02d:%02d:%02d", mins, secs, frames); +} + +void +ExportHandler::start_timespan () +{ + session.export_status.timespan++; + + if (config_map.empty()) { + session.finalize_audio_export(); + 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 */ + + current_timespan->process_connection = session.ProcessExport.connect (sigc::mem_fun (*current_timespan, &ExportTimespan::process)); + 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 */ + + session.export_status.total_formats = 0; + session.export_status.format = 0; + + for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) { + + session.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(); + + 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; + } + + channel_config_connection = cc->FilesWritten.connect (sigc::mem_fun (*this, &ExportHandler::timespan_thread_finished)); + ++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_multiplication.cc b/libs/ardour/export_multiplication.cc new file mode 100644 index 0000000000..da10d54eaa --- /dev/null +++ b/libs/ardour/export_multiplication.cc @@ -0,0 +1,677 @@ +/* This file is not used at the moment. It includes code related to export a + * multiplication graph system that can be used together with the ExportMultiplicator + * class in the gtk2_ardour folder. + * - Sakari Bergen 6.8.2008 - + */ + +void +ExportProfileManager::register_all_configs () +{ + list<TimespanNodePtr>::iterator tsl_it; // timespan list node iterator + for (tsl_it = graph.timespans.begin(); tsl_it != graph.timespans.end(); ++tsl_it) { + list<GraphNode *>::const_iterator cc_it; // channel config node iterator + for (cc_it = (*tsl_it)->get_children().begin(); cc_it != (*tsl_it)->get_children().end(); ++cc_it) { + list<GraphNode *>::const_iterator f_it; // format node iterator + for (f_it = (*cc_it)->get_children().begin(); f_it != (*cc_it)->get_children().end(); ++f_it) { + list<GraphNode *>::const_iterator fn_it; // filename node iterator + for (fn_it = (*f_it)->get_children().begin(); fn_it != (*f_it)->get_children().end(); ++fn_it) { + /* Finally loop through each timespan in the timespan list */ + + TimespanNodePtr ts_node; + if (!(ts_node = boost::dynamic_pointer_cast<TimespanNode> (*tsl_it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + + TimespanListPtr ts_list = ts_node->data()->timespans; + TimespanList::iterator ts_it; + for (ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) { + + TimespanPtr timespan = *ts_it; + + ChannelConfigNode * cc_node; + if (!(cc_node = dynamic_cast<ChannelConfigNode *> (*cc_it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + ChannelConfigPtr channel_config = cc_node->data()->config; + + FormatNode * f_node; + if (!(f_node = dynamic_cast<FormatNode *> (*f_it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + FormatPtr format = f_node->data()->format; + + FilenameNode * fn_node; + if (!(fn_node = dynamic_cast<FilenameNode *> (*fn_it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + FilenamePtr filename = fn_node->data()->filename; + + handler->add_export_config (timespan, channel_config, format, filename); + } + } + } + } + } +} + +void +ExportProfileManager::create_empty_config () +{ + TimespanNodePtr timespan = TimespanNode::create (new TimespanState ()); + timespan->data()->timespans->push_back (handler->add_timespan()); + + ChannelConfigNodePtr channel_config = ChannelConfigNode::create (new ChannelConfigState(handler->add_channel_config())); + + FormatNodePtr format; + load_formats (); + if (!format_list.empty()) { + format = FormatNode::create (new FormatState (*format_list.begin ())); + } else { + format = FormatNode::create (new FormatState (handler->add_format ())); + } + + FilenameNodePtr filename = FilenameNode::create (new FilenameState (handler->add_filename())); + + /* Bring everything together */ + + timespan->add_child (channel_config.get(), 0); + channel_config->add_child (format.get(), 0); + format->add_child (filename.get(), 0); + + graph.timespans.push_back (timespan); + graph.channel_configs.push_back (channel_config); + graph.formats.push_back (format); + graph.filenames.push_back (filename); +} + +/*** GraphNode ***/ + +uint32_t ExportProfileManager::GraphNode::id_counter = 0; + +ExportProfileManager::GraphNode::GraphNode () +{ + _id = ++id_counter; +} + +ExportProfileManager::GraphNode::~GraphNode () +{ + while (!children.empty()) { + remove_child (children.front()); + } + + while (!parents.empty()) { + parents.front()->remove_child (this); + } +} + +void +ExportProfileManager::GraphNode::add_parent (GraphNode * parent) +{ + for (list<GraphNode *>::iterator it = parents.begin(); it != parents.end(); ++it) { + if (*it == parent) { + return; + } + } + + parents.push_back (parent); +} + +void +ExportProfileManager::GraphNode::add_child (GraphNode * child, GraphNode * left_sibling) +{ + for (list<GraphNode *>::iterator it = children.begin(); it != children.end(); ++it) { + if (*it == child) { + return; + } + } + + if (left_sibling) { + insert_after (children, left_sibling, child); + } else { + children.push_back (child); + } + + child->add_parent (this); +} + +bool +ExportProfileManager::GraphNode::is_ancestor_of (GraphNode const * node) const +{ + for (list<GraphNode *>::const_iterator it = children.begin(); it != children.end(); ++it) { + if (*it == node || (*it)->is_ancestor_of (node)) { + return true; + } + } + + return false; +} + +bool +ExportProfileManager::GraphNode::is_descendant_of (GraphNode const * node) const +{ + for (list<GraphNode *>::const_iterator it = parents.begin(); it != parents.end(); ++it) { + if (*it == node || (*it)->is_descendant_of (node)) { + return true; + } + } + + return false; +} + +void +ExportProfileManager::GraphNode::select (bool value) +{ + if (_selected == value) { return; } + + _selected = value; + SelectChanged (value); +} + +void +ExportProfileManager::GraphNode::remove_parent (GraphNode * parent) +{ + for (list<GraphNode *>::iterator it = parents.begin(); it != parents.end(); ++it) { + if (*it == parent) { + parents.erase (it); + break; + } + } +} + +void +ExportProfileManager::GraphNode::remove_child (GraphNode * child) +{ + for (list<GraphNode *>::iterator it = children.begin(); it != children.end(); ++it) { + if (*it == child) { + children.erase (it); + break; + } + } + + child->remove_parent (this); +} + +void +ExportProfileManager::split_node (GraphNode * node, float position) +{ + TimespanNode * ts_node; + if ((ts_node = dynamic_cast<TimespanNode *> (node))) { + split_timespan (ts_node->self_ptr(), position); + return; + } + + ChannelConfigNode * cc_node; + if ((cc_node = dynamic_cast<ChannelConfigNode *> (node))) { + split_channel_config (cc_node->self_ptr(), position); + return; + } + + FormatNode * f_node; + if ((f_node = dynamic_cast<FormatNode *> (node))) { + split_format (f_node->self_ptr(), position); + return; + } + + FilenameNode * fn_node; + if ((fn_node = dynamic_cast<FilenameNode *> (node))) { + split_filename (fn_node->self_ptr(), position); + return; + } +} + +void +ExportProfileManager::remove_node (GraphNode * node) +{ + TimespanNode * ts_node; + if ((ts_node = dynamic_cast<TimespanNode *> (node))) { + remove_timespan (ts_node->self_ptr()); + return; + } + + ChannelConfigNode * cc_node; + if ((cc_node = dynamic_cast<ChannelConfigNode *> (node))) { + remove_channel_config (cc_node->self_ptr()); + return; + } + + FormatNode * f_node; + if ((f_node = dynamic_cast<FormatNode *> (node))) { + remove_format (f_node->self_ptr()); + return; + } + + FilenameNode * fn_node; + if ((fn_node = dynamic_cast<FilenameNode *> (node))) { + remove_filename (fn_node->self_ptr()); + return; + } +} + +void +ExportProfileManager::purge_graph () +{ + for (list<TimespanNodePtr>::iterator it = graph.timespans.begin(); it != graph.timespans.end(); ) { + list<TimespanNodePtr>::iterator tmp = it; + ++it; + + if ((*tmp)->get_children().empty()) { + graph.timespans.erase (tmp); + } + } + + for (list<ChannelConfigNodePtr>::iterator it = graph.channel_configs.begin(); it != graph.channel_configs.end(); ) { + list<ChannelConfigNodePtr>::iterator tmp = it; + ++it; + + if ((*tmp)->get_parents().empty()) { + graph.channel_configs.erase (tmp); + } + } + + for (list<FormatNodePtr>::iterator it = graph.formats.begin(); it != graph.formats.end(); ) { + list<FormatNodePtr>::iterator tmp = it; + ++it; + + if ((*tmp)->get_parents().empty()) { + graph.formats.erase (tmp); + } + } + + for (list<FilenameNodePtr>::iterator it = graph.filenames.begin(); it != graph.filenames.end(); ) { + list<FilenameNodePtr>::iterator tmp = it; + ++it; + + if ((*tmp)->get_parents().empty()) { + graph.filenames.erase (tmp); + } + } + + GraphChanged(); +} + +template<typename T> +void +ExportProfileManager::insert_after (list<T> & the_list, T const & position, T const & element) +{ + typename list<T>::iterator it; + for (it = the_list.begin(); it != the_list.end(); ++it) { + if (*it == position) { + the_list.insert (++it, element); + return; + } + } + + std::cerr << "invalid position given to ExportProfileManager::insert_after (aborting)" << std::endl; + + abort(); +} + +template<typename T> +void +ExportProfileManager::remove_by_element (list<T> & the_list, T const & element) +{ + typename list<T>::iterator it; + for (it = the_list.begin(); it != the_list.end(); ++it) { + if (*it == element) { + the_list.erase (it); + return; + } + } +} + +bool +ExportProfileManager::nodes_have_one_common_child (list<GraphNode *> const & the_list) +{ + return end_of_common_child_range (the_list, the_list.begin()) == --the_list.end(); +} + +list<ExportProfileManager::GraphNode *>::const_iterator +ExportProfileManager::end_of_common_child_range (list<GraphNode *> const & the_list, list<GraphNode *>::const_iterator beginning) +{ + if ((*beginning)->get_children().size() != 1) { return beginning; } + GraphNode * child = (*beginning)->get_children().front(); + + list<GraphNode *>::const_iterator it = beginning; + while (it != the_list.end() && (*it)->get_children().size() == 1 && (*it)->get_children().front() == child) { + ++it; + } + + return --it; +} + +void +ExportProfileManager::split_node_at_position (GraphNode * old_node, GraphNode * new_node, float position) +{ + list<GraphNode *> const & node_parents = old_node->get_parents(); + uint32_t split_index = (int) (node_parents.size() * position + 0.5); + split_index = std::max ((uint32_t) 1, std::min (split_index, node_parents.size() - 1)); + + list<GraphNode *>::const_iterator it = node_parents.begin(); + for (uint32_t index = 1; it != node_parents.end(); ++index) { + if (index > split_index) { + list<GraphNode *>::const_iterator tmp = it++; + (*tmp)->add_child (new_node, old_node); + (*tmp)->remove_child (old_node); + } else { + ++it; + } + } +} + +void +ExportProfileManager::split_timespan (TimespanNodePtr node, float) +{ + TimespanNodePtr new_timespan = duplicate_timespan_node (node); + insert_after (graph.timespans, node, new_timespan); + + /* Note: Since a timespan selector allows all combinations of ranges + * there is no reason for a channel configuration to have two parents + */ + + duplicate_timespan_children (node->self_ptr(), new_timespan); + + GraphChanged(); +} + +void +ExportProfileManager::split_channel_config (ChannelConfigNodePtr node, float) +{ + ChannelConfigNodePtr new_config = duplicate_channel_config_node (node); + insert_after (graph.channel_configs, node, new_config); + + /* Channel configs have only one parent, see above! */ + node->get_parents().front()->add_child (new_config.get(), node.get()); + + if (node->get_children().size() == 1) { + new_config->add_child (node->first_child(), 0); + } else { + duplicate_channel_config_children (node, new_config); + } + + GraphChanged(); +} + +void +ExportProfileManager::split_format (FormatNodePtr node, float position) +{ + FormatNodePtr new_format = duplicate_format_node (node); + insert_after (graph.formats, node, new_format); + + list<GraphNode *> const & node_parents = node->get_parents(); + if (node_parents.size() == 1) { + node_parents.front()->add_child (new_format.get(), 0); + } else { + node->sort_parents (graph.channel_configs); + split_node_at_position (node.get(), new_format.get(), position); + } + + if (node->get_children().size() == 1) { + new_format->add_child (node->first_child(), 0); + } else { + duplicate_format_children (node, new_format); + } + + GraphChanged(); +} + +void +ExportProfileManager::split_filename (FilenameNodePtr node, float position) +{ + FilenameNodePtr new_filename = duplicate_filename_node (node); + insert_after (graph.filenames, node, new_filename); + + list<GraphNode *> const & node_parents = node->get_parents(); + + if (node_parents.size() == 1) { + node_parents.front()->add_child (new_filename.get(), 0); + } else { + node->sort_parents (graph.formats); + split_node_at_position (node.get(), new_filename.get(), position); + } + + GraphChanged(); +} + +void +ExportProfileManager::duplicate_timespan_children (TimespanNodePtr source, TimespanNodePtr target, GraphNode * insertion_point) +{ + list<GraphNode *> const & source_children = source->get_children(); + bool one_grandchild = nodes_have_one_common_child (source_children); + GraphNode * child_insertion_point = 0; + + ChannelConfigNodePtr node_insertion_point; + ChannelConfigNode * node_insertion_ptr; + if (!insertion_point) { insertion_point = source->last_child(); } + if (!(node_insertion_ptr = dynamic_cast<ChannelConfigNode *> (insertion_point))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + node_insertion_point = node_insertion_ptr->self_ptr(); + + /* Keep track of common children */ + + list<GraphNode *>::const_iterator common_children_begin = source_children.begin(); + list<GraphNode *>::const_iterator common_children_end = end_of_common_child_range (source_children, source_children.begin()); + GraphNode * common_child = 0; + + for (list<GraphNode *>::const_iterator it = source_children.begin(); it != source_children.end(); ++it) { + /* Duplicate node */ + + ChannelConfigNode * node; + ChannelConfigNodePtr new_node; + + if (!(node = dynamic_cast<ChannelConfigNode *> (*it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + + new_node = duplicate_channel_config_node (node->self_ptr()); + + /* Insert in gaph's list and update insertion position */ + + insert_after (graph.channel_configs, node_insertion_point, new_node); + node_insertion_point = new_node; + + /* Handle children */ + + target->add_child (new_node.get(), child_insertion_point); + child_insertion_point = new_node.get(); + + if (one_grandchild) { + new_node->add_child (node->first_child(), 0); + } else { + list<GraphNode *>::const_iterator past_end = common_children_end; + if (it == ++past_end) { // At end => start new range + common_children_begin = it; + common_children_end = end_of_common_child_range (source_children, it); + } + + if (it == common_children_begin) { // At beginning => do duplication + GraphNode * grand_child_ins_pt = common_child; + if (!grand_child_ins_pt) { + grand_child_ins_pt = source->last_child()->last_child(); + } + duplicate_channel_config_children (node->self_ptr(), new_node, grand_child_ins_pt); + common_child = new_node->first_child(); + } else { // Somewhere in between end and beginning => share child + new_node->add_child (common_child, 0); + } + } + } +} + +void +ExportProfileManager::duplicate_channel_config_children (ChannelConfigNodePtr source, ChannelConfigNodePtr target, GraphNode * insertion_point) +{ + list<GraphNode *> const & source_children = source->get_children(); + bool one_grandchild = nodes_have_one_common_child (source_children); + GraphNode * child_insertion_point = 0; + + FormatNodePtr node_insertion_point; + FormatNode * node_insertion_ptr; + if (!insertion_point) { insertion_point = source->last_child(); } + if (!(node_insertion_ptr = dynamic_cast<FormatNode *> (insertion_point))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + node_insertion_point = node_insertion_ptr->self_ptr(); + + /* Keep track of common children */ + + list<GraphNode *>::const_iterator common_children_begin = source_children.begin(); + list<GraphNode *>::const_iterator common_children_end = end_of_common_child_range (source_children, source_children.begin()); + GraphNode * common_child = 0; + + for (list<GraphNode *>::const_iterator it = source_children.begin(); it != source_children.end(); ++it) { + /* Duplicate node */ + + FormatNode * node; + FormatNodePtr new_node; + + if (!(node = dynamic_cast<FormatNode *> (*it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + + new_node = duplicate_format_node (node->self_ptr()); + + /* Insert in gaph's list and update insertion position */ + + insert_after (graph.formats, node_insertion_point, new_node); + node_insertion_point = new_node; + + /* Handle children */ + + target->add_child (new_node.get(), child_insertion_point); + child_insertion_point = new_node.get(); + + if (one_grandchild) { + new_node->add_child (node->first_child(), 0); + } else { + list<GraphNode *>::const_iterator past_end = common_children_end; + if (it == ++past_end) { // At end => start new range + common_children_begin = it; + common_children_end = end_of_common_child_range (source_children, it); + } + + if (it == common_children_begin) { // At beginning => do duplication + GraphNode * grand_child_ins_pt = common_child; + if (!grand_child_ins_pt) { + grand_child_ins_pt = source->get_parents().back()->last_child()->last_child()->last_child(); + } + duplicate_format_children (node->self_ptr(), new_node, grand_child_ins_pt); + common_child = new_node->first_child(); + } else { // Somewhere in between end and beginning => share child + new_node->add_child (common_child, 0); + } + } + } +} + +void +ExportProfileManager::duplicate_format_children (FormatNodePtr source, FormatNodePtr target, GraphNode * insertion_point) +{ + GraphNode * child_insertion_point = 0; + + FilenameNodePtr node_insertion_point; + FilenameNode * node_insertion_ptr; + if (!insertion_point) { insertion_point = source->last_child(); } + if (!(node_insertion_ptr = dynamic_cast<FilenameNode *> (insertion_point))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + node_insertion_point = node_insertion_ptr->self_ptr(); + + for (list<GraphNode *>::const_iterator it = source->get_children().begin(); it != source->get_children().end(); ++it) { + /* Duplicate node */ + + FilenameNode * node; + FilenameNodePtr new_node; + + if (!(node = dynamic_cast<FilenameNode *> (*it))) { + throw ExportFailed (_("Export failed due to a programming error"), "Invalid pointer cast in ExportProfileManager"); + } + + new_node = duplicate_filename_node (node->self_ptr()); + + /* Insert in gaph's list and update insertion position */ + + insert_after (graph.filenames, node_insertion_point, new_node); + node_insertion_point = new_node; + + /* Handle children */ + + target->add_child (new_node.get(), child_insertion_point); + child_insertion_point = new_node.get(); + } +} + +ExportProfileManager::TimespanNodePtr +ExportProfileManager::duplicate_timespan_node (TimespanNodePtr node) +{ + TimespanStatePtr state = node->data(); + TimespanStatePtr new_state (new TimespanState ()); + TimespanNodePtr new_node = TimespanNode::create (new_state); + + for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) { + new_state->timespans->push_back (handler->add_timespan_copy (*it)); + } + + new_state->time_format = state->time_format; + new_state->marker_format = state->marker_format; + + return new_node; +} + +ExportProfileManager::ChannelConfigNodePtr +ExportProfileManager::duplicate_channel_config_node (ChannelConfigNodePtr node) +{ + ChannelConfigStatePtr state = node->data(); + ChannelConfigStatePtr new_state (new ChannelConfigState (handler->add_channel_config_copy (state->config))); + ChannelConfigNodePtr new_node = ChannelConfigNode::create (new_state); + + return new_node; +} + +ExportProfileManager::FormatNodePtr +ExportProfileManager::duplicate_format_node (FormatNodePtr node) +{ + FormatStatePtr state = node->data(); + FormatStatePtr new_state (new FormatState (handler->add_format_copy (state->format))); + FormatNodePtr new_node = FormatNode::create (new_state); + + return new_node; +} + +ExportProfileManager::FilenameNodePtr +ExportProfileManager::duplicate_filename_node (FilenameNodePtr node) +{ + FilenameStatePtr state = node->data(); + FilenameStatePtr new_state (new FilenameState (handler->add_filename_copy (state->filename))); + FilenameNodePtr new_node = FilenameNode::create (new_state); + + return new_node; +} + +void +ExportProfileManager::remove_timespan (TimespanNodePtr node) +{ + remove_by_element (graph.timespans, node); + purge_graph (); +} + +void +ExportProfileManager::remove_channel_config (ChannelConfigNodePtr node) +{ + remove_by_element (graph.channel_configs, node); + purge_graph (); +} + +void +ExportProfileManager::remove_format (FormatNodePtr node) +{ + remove_by_element (graph.formats, node); + purge_graph (); +} + +void +ExportProfileManager::remove_filename (FilenameNodePtr node) +{ + remove_by_element (graph.filenames, node); + purge_graph (); +}
\ No newline at end of file diff --git a/libs/ardour/export_processor.cc b/libs/ardour/export_processor.cc new file mode 100644 index 0000000000..87f1772c90 --- /dev/null +++ b/libs/ardour/export_processor.cc @@ -0,0 +1,347 @@ +/* + 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 <ardour/sndfile_helpers.h> + +#include "i18n.h" + +using namespace PBD; + +namespace ARDOUR +{ + +sigc::signal<void, Glib::ustring> ExportProcessor::WritingFile; + +ExportProcessor::ExportProcessor (Session & session) : + session (session), + status (session.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) +{ + session.export_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); + } + + /* File writer(s) */ + + FloatSinkPtr (ExportProcessor::*prep_function) (FormatPtr, uint32_t, ustring const &); + + switch (format->type()) { + case ExportFormatBase::T_Sndfile: + prep_function = &ExportProcessor::prepare_sndfile_writer; + break; + + default: + throw ExportFailed (_("Export failed due to a programming error"), "Invalid format given for ExportProcessor::prepare!"); + break; + + } + + /* Ensure directory exists */ + + sys::path folder (filename->get_folder()); + if (!sys::exists (folder)) { + if (!sys::create_directory (folder)) { + throw ExportFailed ("Export could not create the directory you requested for", "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); + file_sinks.push_back ((this->*prep_function) (format, 1, filename->get_path (format))); + WritingFile (filename->get_path (format)); + } + + } else { + file_sinks.push_back ((this->*prep_function) (format, channels, filename->get_path (format))); + 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()); + } +} + +ExportProcessor::FloatSinkPtr +ExportProcessor::prepare_sndfile_writer (FormatPtr format, uint32_t channels, ustring const & filename) +{ + 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)); + + writer_list.push_back (boost::static_pointer_cast<ExportFileWriter> (sfw)); + + sfc->pipe_to (sfw); + return boost::static_pointer_cast<FloatSink> (sfc); + + } 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)); + + writer_list.push_back (boost::static_pointer_cast<ExportFileWriter> (sfw)); + + sfc->pipe_to (sfw); + return boost::static_pointer_cast<FloatSink> (sfc); + + } else { + + 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)); + + writer_list.push_back (boost::static_pointer_cast<ExportFileWriter> (sfw)); + + sfc->pipe_to (sfw); + return boost::static_pointer_cast<FloatSink> (sfc); + } + +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_profile_manager.cc b/libs/ardour/export_profile_manager.cc new file mode 100644 index 0000000000..88432bcedc --- /dev/null +++ b/libs/ardour/export_profile_manager.cc @@ -0,0 +1,927 @@ +/* + 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_profile_manager.h> + +#include <cassert> +#include <stdexcept> + +#include <glibmm/fileutils.h> + +#include <pbd/enumwriter.h> +#include <pbd/xml++.h> +#include <pbd/convert.h> + +#include <ardour/audioengine.h> +#include <ardour/export_failed.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_handler.h> +#include <ardour/session.h> + +#include "i18n.h" + +using namespace PBD; + +namespace ARDOUR +{ + +ExportProfileManager::Preset::Preset (string filename, Session & s) : + _id (0), session (s), global (filename), local (0) +{ + XMLNode * root; + if ((root = global.root())) { + XMLProperty * prop; + if ((prop = root->property ("id"))) { + set_id ((uint32_t) atoi (prop->value())); + } + if ((prop = root->property ("name"))) { + set_name (prop->value()); + } + + XMLNode * instant_xml = get_instant_xml (); + if (instant_xml) { + XMLNode * instant_copy = new XMLNode (*instant_xml); + set_local_state (*instant_copy); + } + } +} + +ExportProfileManager::Preset::~Preset () +{ + if (local) { + delete local; + } +} + +void +ExportProfileManager::Preset::set_name (string name) +{ + _name = name; + + XMLNode * node; + if ((node = global.root())) { + node->add_property ("name", name); + } + if (local) { + local->add_property ("name", name); + } +} + +void +ExportProfileManager::Preset::set_id (uint32_t id) +{ + _id = id; + + XMLNode * node; + if ((node = global.root())) { + node->add_property ("id", id); + } + if (local) { + local->add_property ("id", id); + } +} + +void +ExportProfileManager::Preset::set_global_state (XMLNode & state) +{ + delete global.root (); + global.set_root (&state); + + set_id (_id); + set_name (_name); +} + +void +ExportProfileManager::Preset::set_local_state (XMLNode & state) +{ + delete local; + local = &state; + + set_id (_id); + set_name (_name); +} + +void +ExportProfileManager::Preset::save () const +{ + save_instant_xml (); + if (global.root()) { + global.write (); + } +} + +void +ExportProfileManager::Preset::remove_local () const +{ + remove_instant_xml (); +} + +XMLNode * +ExportProfileManager::Preset::get_instant_xml () const +{ + XMLNode * instant_xml; + + if ((instant_xml = session.instant_xml ("ExportPresets"))) { + XMLNodeList children = instant_xml->children ("ExportPreset"); + for (XMLNodeList::iterator it = children.begin(); it != children.end(); ++it) { + XMLProperty * prop; + if ((prop = (*it)->property ("id")) && _id == (uint32_t) atoi (prop->value())) { + return *it; + } + } + } + + return 0; +} + +void +ExportProfileManager::Preset::save_instant_xml () const +{ + if (!local) { return; } + + /* First remove old, then add new */ + + remove_instant_xml (); + + XMLNode * instant_xml; + if ((instant_xml = session.instant_xml ("ExportPresets"))) { + instant_xml->add_child_copy (*local); + } else { + instant_xml = new XMLNode ("ExportPresets"); + instant_xml->add_child_copy (*local); + session.add_instant_xml (*instant_xml, false); + } +} + +void +ExportProfileManager::Preset::remove_instant_xml () const +{ + XMLNode * instant_xml; + if ((instant_xml = session.instant_xml ("ExportPresets"))) { + instant_xml->remove_nodes_and_delete ("id", to_string (_id, std::dec)); + } +} + +ExportProfileManager::ExportProfileManager (Session & s) : + handler (s.get_export_handler()), + session (s), + + session_range (new Location ()), + ranges (new LocationList ()), + + format_list (new FormatList ()) +{ + + /* Initialize path variables */ + + sys::path path; + + export_config_dir = user_config_directory(); + export_config_dir /= "export"; + search_path += export_config_dir; + + path = ardour_search_path().to_string(); + path /= "export"; + search_path += path; + + path = system_config_search_path().to_string(); + path /= "export"; + search_path += path; + + /* create export config directory if necessary */ + + if (!sys::exists (export_config_dir)) { + sys::create_directory (export_config_dir); + } + + load_presets (); + load_formats (); +} + +ExportProfileManager::~ExportProfileManager () +{ + XMLNode * instant_xml (new XMLNode ("ExportProfile")); + serialize_profile (*instant_xml); + session.add_instant_xml (*instant_xml, false); +} + +void +ExportProfileManager::load_profile () +{ + XMLNode * instant_node = session.instant_xml ("ExportProfile"); + if (instant_node) { + set_state (*instant_node); + } else { + XMLNode empty_node ("ExportProfile"); + set_state (empty_node); + } +} + +void +ExportProfileManager::prepare_for_export () +{ + ChannelConfigPtr channel_config = channel_configs.front()->config; + TimespanListPtr ts_list = timespans.front()->timespans; + + FormatStateList::const_iterator format_it; + FilenameStateList::const_iterator filename_it; + + for (TimespanList::iterator ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) { + for (format_it = formats.begin(), filename_it = filenames.begin(); + format_it != formats.end() && filename_it != filenames.end(); + ++format_it, ++filename_it) { + +// filename->include_timespan = (ts_list->size() > 1); Disabled for now... + handler->add_export_config (*ts_it, channel_config, (*format_it)->format, (*filename_it)->filename); + } + } +} + +void +ExportProfileManager::load_preset (PresetPtr preset) +{ + current_preset = preset; + if (!preset) { return; } + + XMLNode const * state; + if ((state = preset->get_local_state())) { + set_local_state (*state); + } + + if ((state = preset->get_global_state())) { + set_global_state (*state); + } +} + +void +ExportProfileManager::load_presets () +{ + preset_id_counter = 0; + + vector<sys::path> found = find_file ("*.preset"); + + for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) { + preset_id_counter = std::max (preset_id_counter, load_preset_from_disk (*it)); + } +} + +ExportProfileManager::PresetPtr +ExportProfileManager::save_preset (string const & name) +{ + if (!current_preset) { + ++preset_id_counter; + string filename = export_config_dir.to_string() + "/" + to_string (preset_id_counter, std::dec) + ".preset"; + current_preset.reset (new Preset (filename, session)); + preset_list.push_back (current_preset); + current_preset->set_id (preset_id_counter); + } + + XMLNode * global_preset = new XMLNode ("ExportPreset"); + XMLNode * local_preset = new XMLNode ("ExportPreset"); + + serialize_global_profile (*global_preset); + serialize_local_profile (*local_preset); + + current_preset->set_name (name); + current_preset->set_global_state (*global_preset); + current_preset->set_local_state (*local_preset); + + current_preset->save(); + + return current_preset; +} + +void +ExportProfileManager::remove_preset () +{ + if (!current_preset) { return; } + + for (PresetList::iterator it = preset_list.begin(); it != preset_list.end(); ++it) { + if (*it == current_preset) { + preset_list.erase (it); + break; + } + } + + FileMap::iterator it = preset_file_map.find (current_preset->id()); + if (it != preset_file_map.end()) { + sys::remove (it->second); + preset_file_map.erase (it); + } + + current_preset->remove_local(); + current_preset.reset(); +} + +uint32_t +ExportProfileManager::load_preset_from_disk (PBD::sys::path const & path) +{ + PresetPtr preset (new Preset (path.to_string(), session)); + preset_list.push_back (preset); + + /* Handle id to filename mapping */ + + FilePair pair (preset->id(), path); + preset_file_map.insert (pair); + + return preset->id(); +} + +void +ExportProfileManager::set_state (XMLNode const & root) +{ + set_global_state (root); + set_local_state (root); +} + +void +ExportProfileManager::set_global_state (XMLNode const & root) +{ + init_formats (root.children ("ExportFormat")); + init_filenames (root.children ("ExportFilename")); +} + +void +ExportProfileManager::set_local_state (XMLNode const & root) +{ + init_timespans (root.children ("ExportTimespan"));; + init_channel_configs (root.children ("ExportChannelConfiguration")); +} + +void +ExportProfileManager::serialize_profile (XMLNode & root) +{ + serialize_local_profile (root); + serialize_global_profile (root); +} + +void +ExportProfileManager::serialize_global_profile (XMLNode & root) +{ + for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) { + root.add_child_nocopy (serialize_format (*it)); + } + + for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) { + root.add_child_nocopy (serialize_filename (*it)); + } +} + +void +ExportProfileManager::serialize_local_profile (XMLNode & root) +{ + for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) { + root.add_child_nocopy (serialize_timespan (*it)); + } + + for (ChannelConfigStateList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) { + root.add_child_nocopy (serialize_channel_config (*it)); + } +} + +std::vector<sys::path> +ExportProfileManager::find_file (std::string const & pattern) +{ + vector<sys::path> found; + + Glib::PatternSpec pattern_spec (pattern); + find_matching_files_in_search_path (search_path, pattern_spec, found); + + return found; +} + +void +ExportProfileManager::set_selection_range (nframes_t start, nframes_t end) +{ + + if (start || end) { + selection_range.reset (new Location()); + selection_range->set_name (_("Selection")); + selection_range->set (start, end); + } else { + selection_range.reset(); + } + + for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) { + (*it)->selection_range = selection_range; + } +} + +void +ExportProfileManager::init_timespans (XMLNodeList nodes) +{ + timespans.clear (); + + if (nodes.empty()) { + update_ranges (); + + TimespanStatePtr timespan (new TimespanState (session_range, selection_range, ranges)); + + timespans.push_back (timespan); + return; + } + + for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { + timespans.push_back (deserialize_timespan (**it)); + } +} + +ExportProfileManager::TimespanStatePtr +ExportProfileManager::deserialize_timespan (XMLNode & root) +{ + TimespanStatePtr state (new TimespanState (session_range, selection_range, ranges)); + XMLProperty const * prop; + + update_ranges (); + + XMLNodeList spans = root.children ("Range"); + for (XMLNodeList::iterator node_it = spans.begin(); node_it != spans.end(); ++node_it) { + + prop = (*node_it)->property ("id"); + if (!prop) { continue; } + ustring id = prop->value(); + + for (LocationList::iterator it = ranges->begin(); it != ranges->end(); ++it) { + if ((!id.compare ("session") && *it == session_range.get()) || + (!id.compare ("selection") && *it == selection_range.get()) || + (!id.compare ((*it)->id().to_s()))) { + TimespanPtr timespan = handler->add_timespan(); + timespan->set_name ((*it)->name()); + timespan->set_range_id (id); + timespan->set_range ((*it)->start(), (*it)->end()); + state->timespans->push_back (timespan); + } + } + } + + if ((prop = root.property ("format"))) { + state->time_format = (TimeFormat) string_2_enum (prop->value(), TimeFormat); + } + + return state; +} + +XMLNode & +ExportProfileManager::serialize_timespan (TimespanStatePtr state) +{ + XMLNode & root = *(new XMLNode ("ExportTimespan")); + XMLNode * span; + + update_ranges (); + + for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) { + if ((span = root.add_child ("Range"))) { + span->add_property ("id", (*it)->range_id()); + } + } + + root.add_property ("format", enum_2_string (state->time_format)); + + return root; +} + +void +ExportProfileManager::update_ranges () { + ranges->clear(); + + /* Session */ + + session_range->set_name (_("Session")); + session_range->set (session.current_start_frame(), session.current_end_frame()); + ranges->push_back (session_range.get()); + + /* Selection */ + + if (selection_range) { + ranges->push_back (selection_range.get()); + } + + /* ranges */ + + LocationList const & list (session.locations()->list()); + for (LocationList::const_iterator it = list.begin(); it != list.end(); ++it) { + if ((*it)->is_range_marker()) { + ranges->push_back (*it); + } + } +} + +void +ExportProfileManager::init_channel_configs (XMLNodeList nodes) +{ + channel_configs.clear(); + + if (nodes.empty()) { + ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config())); + channel_configs.push_back (config); + return; + } + + for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { + channel_configs.push_back (deserialize_channel_config (**it)); + } +} + +ExportProfileManager::ChannelConfigStatePtr +ExportProfileManager::deserialize_channel_config (XMLNode & root) +{ + + ChannelConfigStatePtr state (new ChannelConfigState (handler->add_channel_config())); + XMLProperty const * prop; + + if ((prop = root.property ("split"))) { + state->config->set_split(!prop->value().compare ("true")); + } + + XMLNodeList channels = root.children ("Channel"); + for (XMLNodeList::iterator it = channels.begin(); it != channels.end(); ++it) { + boost::shared_ptr<ExportChannel> channel (new ExportChannel ()); + + XMLNodeList ports = (*it)->children ("Port"); + for (XMLNodeList::iterator p_it = ports.begin(); p_it != ports.end(); ++p_it) { + if ((prop = (*p_it)->property ("name"))) { + channel->add_port (dynamic_cast<AudioPort *> (session.engine().get_port_by_name (prop->value()))); + } + } + + state->config->register_channel (channel); + } + + return state; +} + +XMLNode & +ExportProfileManager::serialize_channel_config (ChannelConfigStatePtr state) +{ + XMLNode * root = new XMLNode ("ExportChannelConfiguration"); + XMLNode * channel; + XMLNode * port_node; + + root->add_property ("split", state->config->get_split() ? "true" : "false"); + root->add_property ("channels", to_string (state->config->get_n_chans(), std::dec)); + + uint32_t i = 1; + ExportChannelConfiguration::ChannelList const & chan_list = state->config->get_channels(); + for (ExportChannelConfiguration::ChannelList::const_iterator c_it = chan_list.begin(); c_it != chan_list.end(); ++c_it) { + channel = root->add_child ("Channel"); + if (!channel) { continue; } + + channel->add_property ("number", to_string (i, std::dec)); + + for (ExportChannel::const_iterator p_it = (*c_it)->begin(); p_it != (*c_it)->end(); ++p_it) { + if ((port_node = channel->add_child ("Port"))) { + port_node->add_property ("name", (*p_it)->name()); + } + } + + ++i; + } + + return *root; +} + +ExportProfileManager::FormatStatePtr +ExportProfileManager::duplicate_format_state (FormatStatePtr state) +{ + /* Note: The pointer in the new FormatState should point to the same format spec + as the original state's pointer. The spec itself should not be copied! */ + + FormatStatePtr format (new FormatState (format_list, state->format)); + formats.push_back (format); + return format; +} + +void +ExportProfileManager::remove_format_state (FormatStatePtr state) +{ + for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) { + if (*it == state) { + formats.erase (it); + return; + } + } +} + +sys::path +ExportProfileManager::save_format_to_disk (FormatPtr format) +{ + // TODO filename character stripping + + /* Get filename for file */ + + Glib::ustring new_name = format->name(); + new_name += ".format"; + + sys::path new_path (export_config_dir); + new_path /= new_name; + + /* Check if format is on disk already */ + FileMap::iterator it; + if ((it = format_file_map.find (format->id())) != format_file_map.end()) { + /* Update data */ + { + XMLTree tree (it->second.to_string()); + tree.set_root (&format->get_state()); + tree.write(); + } + + /* Rename if necessary */ + + if (new_name.compare (it->second.leaf())) { + sys::rename (it->second, new_path); + } + + } else { + /* Write new file */ + + XMLTree tree (new_path.to_string()); + tree.set_root (&format->get_state()); + tree.write(); + } + + FormatListChanged (); + return new_path; +} + +void +ExportProfileManager::remove_format_profile (FormatPtr format) +{ + for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) { + if (*it == format) { + format_list->erase (it); + break; + } + } + + FileMap::iterator it = format_file_map.find (format->id()); + if (it != format_file_map.end()) { + sys::remove (it->second); + format_file_map.erase (it); + } + + FormatListChanged (); +} + +ExportProfileManager::FormatPtr +ExportProfileManager::get_new_format (FormatPtr original) +{ + FormatPtr format; + if (original) { + format.reset (new ExportFormatSpecification (*original)); + } else { + format = handler->add_format(); + format->set_name ("empty format"); + } + + sys::path path = save_format_to_disk (format); + FilePair pair (format->id(), path); + format_file_map.insert (pair); + + format_list->push_back (format); + FormatListChanged (); + + return format; +} + +void +ExportProfileManager::init_formats (XMLNodeList nodes) +{ + formats.clear(); + + if (nodes.empty()) { + FormatStatePtr format (new FormatState (format_list, FormatPtr ())); + formats.push_back (format); + return; + } + + for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { + FormatStatePtr format; + if ((format = deserialize_format (**it))) { + formats.push_back (format); + } + } + + if (formats.empty ()) { + FormatStatePtr format (new FormatState (format_list, FormatPtr ())); + formats.push_back (format); + } +} + +ExportProfileManager::FormatStatePtr +ExportProfileManager::deserialize_format (XMLNode & root) +{ + XMLProperty * prop; + uint32_t id = 0; + + if ((prop = root.property ("id"))) { + id = atoi (prop->value()); + } + + for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) { + if ((*it)->id() == id) { + return FormatStatePtr (new FormatState (format_list, *it)); + } + } + + return FormatStatePtr (); +} + +XMLNode & +ExportProfileManager::serialize_format (FormatStatePtr state) +{ + XMLNode * root = new XMLNode ("ExportFormat"); + + string id = state->format ? to_string (state->format->id(), std::dec) : "0"; + root->add_property ("id", id); + + return *root; +} + +void +ExportProfileManager::load_formats () +{ + vector<sys::path> found = find_file ("*.format"); + + for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) { + load_format_from_disk (*it); + } +} + +void +ExportProfileManager::load_format_from_disk (PBD::sys::path const & path) +{ + XMLTree const tree (path.to_string()); + FormatPtr format = handler->add_format (*tree.root()); + format_list->push_back (format); + + /* Handle id to filename mapping */ + + FilePair pair (format->id(), path); + format_file_map.insert (pair); + + FormatListChanged (); +} + +ExportProfileManager::FilenameStatePtr +ExportProfileManager::duplicate_filename_state (FilenameStatePtr state) +{ + FilenameStatePtr filename (new FilenameState (handler->add_filename_copy (state->filename))); + filenames.push_back (filename); + return filename; +} + +void +ExportProfileManager::remove_filename_state (FilenameStatePtr state) +{ + for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) { + if (*it == state) { + filenames.erase (it); + return; + } + } +} + +void +ExportProfileManager::init_filenames (XMLNodeList nodes) +{ + filenames.clear (); + + if (nodes.empty()) { + FilenameStatePtr filename (new FilenameState (handler->add_filename())); + filenames.push_back (filename); + return; + } + + for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { + filenames.push_back (deserialize_filename (**it)); + } +} + +ExportProfileManager::FilenameStatePtr +ExportProfileManager::deserialize_filename (XMLNode & root) +{ + FilenamePtr filename = handler->add_filename(); + filename->set_state (root); + return FilenameStatePtr (new FilenameState (filename)); +} + +XMLNode & +ExportProfileManager::serialize_filename (FilenameStatePtr state) +{ + return state->filename->get_state(); +} + +boost::shared_ptr<ExportProfileManager::Warnings> +ExportProfileManager::get_warnings () +{ + boost::shared_ptr<Warnings> warnings (new Warnings ()); + + ChannelConfigStatePtr channel_config_state = channel_configs.front(); + TimespanStatePtr timespan_state = timespans.front(); + + /*** Check "global" config ***/ + + TimespanListPtr timespans = timespan_state->timespans; + ChannelConfigPtr channel_config = channel_config_state->config; + + /* Check Timespans are not empty */ + + if (timespans->empty()) { + warnings->errors.push_back (_("No timespan has been selected!")); + } + + /* Check channel config ports */ + + if (!channel_config->all_channels_have_ports ()) { + warnings->warnings.push_back (_("Some channels are empty")); + } + + /*** Check files ***/ + + FormatStateList::const_iterator format_it; + FilenameStateList::const_iterator filename_it; + for (format_it = formats.begin(), filename_it = filenames.begin(); + format_it != formats.end() && filename_it != filenames.end(); + ++format_it, ++filename_it) { + check_config (warnings, timespan_state, channel_config_state, *format_it, *filename_it); + } + + return warnings; +} + +void +ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings, + TimespanStatePtr timespan_state, + ChannelConfigStatePtr channel_config_state, + FormatStatePtr format_state, + FilenameStatePtr filename_state) +{ + TimespanListPtr timespans = timespan_state->timespans; + ChannelConfigPtr channel_config = channel_config_state->config; + FormatPtr format = format_state->format; + FilenamePtr filename = filename_state->filename; + + /* Check format and maximum channel count */ + if (!format || !format->type()) { + warnings->errors.push_back (_("No format selected!")); + } else if (format->channel_limit() < channel_config->get_n_chans()) { + warnings->errors.push_back + (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"), + format->format_name(), + format->channel_limit(), + channel_config->get_n_chans())); + } + + if (!warnings->errors.empty()) { return; } + + /* Check filenames */ + +// filename->include_timespan = (timespans->size() > 1); Disabled for now... + + for (std::list<TimespanPtr>::iterator timespan_it = timespans->begin(); timespan_it != timespans->end(); ++timespan_it) { + filename->set_timespan (*timespan_it); + + if (channel_config->get_split()) { + filename->include_channel = true; + + for (uint32_t chan = 1; chan <= channel_config->get_n_chans(); ++chan) { + filename->set_channel (chan); + + Glib::ustring path = filename->get_path (format); + + if (sys::exists (sys::path (path))) { + warnings->conflicting_filenames.push_back (path); + } + } + + } else { + Glib::ustring path = filename->get_path (format); + + if (sys::exists (sys::path (path))) { + warnings->conflicting_filenames.push_back (path); + } + } + } +} + +}; // namespace ARDOUR diff --git a/libs/ardour/export_status.cc b/libs/ardour/export_status.cc new file mode 100644 index 0000000000..c64a03fe23 --- /dev/null +++ b/libs/ardour/export_status.cc @@ -0,0 +1,51 @@ +/* + 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_status.h> + +namespace ARDOUR +{ + +ExportStatus::ExportStatus () +{ + init(); +} + +void +ExportStatus::init () +{ + stop = false; + running = false; + _aborted = false; + + stage = export_None; + progress = 0.0; + + total_timespans = 0; + timespan = 0; + + total_channel_configs = 0; + channel_config = 0; + + total_formats = 0; + format = 0; +} + +} // namespace ARDOUR diff --git a/libs/ardour/export_timespan.cc b/libs/ardour/export_timespan.cc new file mode 100644 index 0000000000..14273d835f --- /dev/null +++ b/libs/ardour/export_timespan.cc @@ -0,0 +1,113 @@ +/* + 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_timespan.h> + +#include <ardour/export_channel_configuration.h> +#include <ardour/export_filename.h> +#include <ardour/export_file_io.h> +#include <ardour/export_failed.h> + +namespace ARDOUR +{ + +ExportTimespan::ExportTimespan (ExportStatus & status, nframes_t frame_rate) : + status (status), + start_frame (0), + end_frame (0), + position (0), + frame_rate (frame_rate) +{ +} + +ExportTimespan::~ExportTimespan () +{ +} + +void +ExportTimespan::register_channel (ExportChannel const & 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, ExportChannel const & channel) +{ + TempFileMap::iterator it = filemap.find (channel); + if (it == filemap.end()) { + throw ExportFailed (_("Export failed due to programming error"), _("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; + position = start_frame; + 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_ports (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 new file mode 100644 index 0000000000..5eafbb2d15 --- /dev/null +++ b/libs/ardour/export_utilities.cc @@ -0,0 +1,349 @@ +/* + 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 <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), + 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 (_("cannot initialize sample rate conversion: %1"), src_strerror (err)), "Cannot initialize sample rate conversion"); + } + + src_data.src_ratio = out_rate / (double) in_rate; +} + +SampleRateConverter::~SampleRateConverter () +{ + if (src_state) { + src_delete (src_state); + } + if (data_out) { + 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) { + + free (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 (_("A memory allocation error occured during sample rate conversion"), "Samplerate conversion failed"); + } + + 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 (_("an error occured during sample rate conversion"), 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); + if (frames_written < 0) { + return frames_written; + } else { + 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); + } + if (data_out) { + 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) { + free (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_metadata.cc b/libs/ardour/session_metadata.cc new file mode 100644 index 0000000000..7eb24e7586 --- /dev/null +++ b/libs/ardour/session_metadata.cc @@ -0,0 +1,500 @@ +/* + 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/session_metadata.h> + +#include <iostream> +#include <sstream> + +using namespace ARDOUR; + + +SessionMetadata::SessionMetadata () +{ + /*** General ***/ + map.insert (Property ("comment", "")); + map.insert (Property ("copyright", "")); + map.insert (Property ("isrc", "")); + map.insert (Property ("year", "")); + + /*** Title and friends ***/ + map.insert (Property ("grouping", "")); + map.insert (Property ("title", "")); + map.insert (Property ("subtitle", "")); + + /*** People... ***/ + map.insert (Property ("artist", "")); + map.insert (Property ("album_artist", "")); + map.insert (Property ("lyricist", "")); + map.insert (Property ("composer", "")); + map.insert (Property ("conductor", "")); + map.insert (Property ("remixer", "")); + map.insert (Property ("arranger", "")); + map.insert (Property ("engineer", "")); + map.insert (Property ("producer", "")); + map.insert (Property ("dj_mixer", "")); + map.insert (Property ("mixer", "")); + //map.insert (Property ("performers", "")); // Multiple values [instrument] + + /*** Album info ***/ + map.insert (Property ("album", "")); + map.insert (Property ("compilation", "")); + map.insert (Property ("disc_subtitle", "")); + map.insert (Property ("disc_number", "")); + map.insert (Property ("total_discs", "")); + map.insert (Property ("track_number", "")); + map.insert (Property ("total_tracks", "")); + + /*** Style ***/ + map.insert (Property ("genre", "")); + //map.insert (Property ("mood", "")); + //map.insert (Property ("bpm", "")); + + /*** Other ***/ + //map.insert (Property ("lyrics", "")); + //map.insert (Property ("media", "")); + //map.insert (Property ("label", "")); + //map.insert (Property ("barcode", "")); + //map.insert (Property ("encoded_by", "")); + //map.insert (Property ("catalog_number", "")); + + /*** Sorting orders ***/ + //map.insert (Property ("album_sort", "")); + //map.insert (Property ("album_artist_sort", "")); + //map.insert (Property ("artist_sort", "")); + //map.insert (Property ("title_sort", "")); +} + +SessionMetadata::~SessionMetadata () +{ + +} + +XMLNode * +SessionMetadata::get_xml (const ustring & name) +{ + ustring value = get_value (name); + if (value.empty()) { + return 0; + } + + XMLNode val ("value", value); + XMLNode * node = new XMLNode (name); + node->add_child_copy (val); + + return node; +} + +ustring +SessionMetadata::get_value (const ustring & name) const +{ + PropertyMap::const_iterator it = map.find (name); + if (it == map.end()) { + // Should not be reached! + std::cerr << "Programming error in SessionMetadata::get_value" << std::endl; + return ""; + } + + return it->second; +} + +uint32_t +SessionMetadata::get_uint_value (const ustring & name) const +{ + return atoi (get_value (name).c_str()); +} + +void +SessionMetadata::set_value (const ustring & name, const ustring & value) +{ + PropertyMap::iterator it = map.find (name); + if (it == map.end()) { + // Should not be reached! + std::cerr << "Programming error in SessionMetadata::set_value" << std::endl; + return; + } + + it->second = value; +} + +void +SessionMetadata::set_value (const ustring & name, uint32_t value) +{ + std::ostringstream oss; + oss << value; + if (oss.str().compare("0")) { + set_value (name, oss.str()); + } else { + set_value (name, ""); + } +} + +/*** Serialization ***/ +XMLNode & +SessionMetadata::get_state () +{ + XMLNode * node = new XMLNode ("Metadata"); + XMLNode * prop; + + for (PropertyMap::const_iterator it = map.begin(); it != map.end(); ++it) { + if ((prop = get_xml (it->first))) { + node->add_child_nocopy (*prop); + } + } + + return *node; +} + +int +SessionMetadata::set_state (const XMLNode & state) +{ + const XMLNodeList & children = state.children(); + ustring name; + ustring value; + XMLNode * node; + + for (XMLNodeConstIterator it = children.begin(); it != children.end(); it++) { + node = *it; + if (node->children().empty()) { + continue; + } + + name = node->name(); + node = *node->children().begin(); + value = node->content(); + + set_value (name, value); + } + + return 0; +} + +/*** Accessing ***/ +ustring +SessionMetadata::comment () const +{ + return get_value("comment"); +} + +ustring +SessionMetadata::copyright () const +{ + return get_value("copyright"); +} + +ustring +SessionMetadata::isrc () const +{ + return get_value("isrc"); +} + +uint32_t +SessionMetadata::year () const +{ + return get_uint_value("year"); +} + +ustring +SessionMetadata::grouping () const +{ + return get_value("grouping"); +} + +ustring +SessionMetadata::title () const +{ + return get_value("title"); +} + +ustring +SessionMetadata::subtitle () const +{ + return get_value("subtitle"); +} + +ustring +SessionMetadata::artist () const +{ + return get_value("artist"); +} + +ustring +SessionMetadata::album_artist () const +{ + return get_value("album_artist"); +} + +ustring +SessionMetadata::lyricist () const +{ + return get_value("lyricist"); +} + +ustring +SessionMetadata::composer () const +{ + return get_value("composer"); +} + +ustring +SessionMetadata::conductor () const +{ + return get_value("conductor"); +} + +ustring +SessionMetadata::remixer () const +{ + return get_value("remixer"); +} + +ustring +SessionMetadata::arranger () const +{ + return get_value("arranger"); +} + +ustring +SessionMetadata::engineer () const +{ + return get_value("engineer"); +} + +ustring +SessionMetadata::producer () const +{ + return get_value("producer"); +} + +ustring +SessionMetadata::dj_mixer () const +{ + return get_value("dj_mixer"); +} + +ustring +SessionMetadata::mixer () const +{ + return get_value("mixer"); +} + +ustring +SessionMetadata::album () const +{ + return get_value("album"); +} + +ustring +SessionMetadata::compilation () const +{ + return get_value("compilation"); +} + +ustring +SessionMetadata::disc_subtitle () const +{ + return get_value("disc_subtitle"); +} + +uint32_t +SessionMetadata::disc_number () const +{ + return get_uint_value("disc_number"); +} + +uint32_t +SessionMetadata::total_discs () const +{ + return get_uint_value("total_discs"); +} + +uint32_t +SessionMetadata::track_number () const +{ + return get_uint_value("track_number"); +} + +uint32_t +SessionMetadata::total_tracks () const +{ + return get_uint_value("total_tracks"); +} + +ustring +SessionMetadata::genre () const +{ + return get_value("genre"); +} + +/*** Editing ***/ +void +SessionMetadata::set_comment (const ustring & v) +{ + set_value ("comment", v); +} + +void +SessionMetadata::set_copyright (const ustring & v) +{ + set_value ("copyright", v); +} + +void +SessionMetadata::set_isrc (const ustring & v) +{ + set_value ("isrc", v); +} + +void +SessionMetadata::set_year (uint32_t v) +{ + set_value ("year", v); +} + +void +SessionMetadata::set_grouping (const ustring & v) +{ + set_value ("grouping", v); +} + +void +SessionMetadata::set_title (const ustring & v) +{ + set_value ("title", v); +} + +void +SessionMetadata::set_subtitle (const ustring & v) +{ + set_value ("subtitle", v); +} + +void +SessionMetadata::set_artist (const ustring & v) +{ + set_value ("artist", v); +} + +void +SessionMetadata::set_album_artist (const ustring & v) +{ + set_value ("album_artist", v); +} + +void +SessionMetadata::set_lyricist (const ustring & v) +{ + set_value ("lyricist", v); +} + +void +SessionMetadata::set_composer (const ustring & v) +{ + set_value ("composer", v); +} + +void +SessionMetadata::set_conductor (const ustring & v) +{ + set_value ("conductor", v); +} + +void +SessionMetadata::set_remixer (const ustring & v) +{ + set_value ("remixer", v); +} + +void +SessionMetadata::set_arranger (const ustring & v) +{ + set_value ("arranger", v); +} + +void +SessionMetadata::set_engineer (const ustring & v) +{ + set_value ("engineer", v); +} + +void +SessionMetadata::set_producer (const ustring & v) +{ + set_value ("producer", v); +} + +void +SessionMetadata::set_dj_mixer (const ustring & v) +{ + set_value ("dj_mixer", v); +} + +void +SessionMetadata::set_mixer (const ustring & v) +{ + set_value ("mixer", v); +} + +void +SessionMetadata::set_album (const ustring & v) +{ + set_value ("album", v); +} + +void +SessionMetadata::set_compilation (const ustring & v) +{ + set_value ("compilation", v); +} + +void +SessionMetadata::set_disc_subtitle (const ustring & v) +{ + set_value ("disc_subtitle", v); +} + +void +SessionMetadata::set_disc_number (uint32_t v) +{ + set_value ("disc_number", v); +} + +void +SessionMetadata::set_total_discs (uint32_t v) +{ + set_value ("total_discs", v); +} + +void +SessionMetadata::set_track_number (uint32_t v) +{ + set_value ("track_number", v); +} + +void +SessionMetadata::set_total_tracks (uint32_t v) +{ + set_value ("total_tracks", v); +} + +void +SessionMetadata::set_genre (const ustring & v) +{ + set_value ("genre", v); +} |