summaryrefslogtreecommitdiff
path: root/libs/ardour/audiofilesource.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libs/ardour/audiofilesource.cc')
-rw-r--r--libs/ardour/audiofilesource.cc759
1 files changed, 759 insertions, 0 deletions
diff --git a/libs/ardour/audiofilesource.cc b/libs/ardour/audiofilesource.cc
new file mode 100644
index 0000000000..026cb3e7c0
--- /dev/null
+++ b/libs/ardour/audiofilesource.cc
@@ -0,0 +1,759 @@
+/*
+ Copyright (C) 2006 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.
+
+*/
+
+#include <vector>
+
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <stdio.h> // for rename(), sigh
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <pbd/convert.h>
+#include <pbd/basename.h>
+#include <pbd/mountpoint.h>
+#include <pbd/stl_delete.h>
+#include <pbd/strsplit.h>
+#include <pbd/shortpath.h>
+#include <pbd/enumwriter.h>
+
+#include <sndfile.h>
+
+#include <glibmm/miscutils.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/thread.h>
+
+#include <ardour/audiofilesource.h>
+#include <ardour/sndfile_helpers.h>
+#include <ardour/sndfilesource.h>
+#include <ardour/session.h>
+#include <ardour/session_directory.h>
+#include <ardour/source_factory.h>
+#include <ardour/filename_extensions.h>
+
+// if these headers come before sigc++ is included
+// the parser throws ObjC++ errors. (nil is a keyword)
+#ifdef HAVE_COREAUDIO
+#include <ardour/coreaudiosource.h>
+#include <AudioToolbox/ExtendedAudioFile.h>
+#include <AudioToolbox/AudioFormat.h>
+#endif // HAVE_COREAUDIO
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Glib;
+
+ustring AudioFileSource::peak_dir = "";
+ustring AudioFileSource::search_path;
+
+sigc::signal<void> AudioFileSource::HeaderPositionOffsetChanged;
+uint64_t AudioFileSource::header_position_offset = 0;
+
+/* XXX maybe this too */
+char AudioFileSource::bwf_serial_number[13] = "000000000000";
+
+struct SizedSampleBuffer {
+ nframes_t size;
+ Sample* buf;
+
+ SizedSampleBuffer (nframes_t sz) : size (sz) {
+ buf = new Sample[size];
+ }
+
+ ~SizedSampleBuffer() {
+ delete [] buf;
+ }
+};
+
+Glib::StaticPrivate<SizedSampleBuffer> thread_interleave_buffer = GLIBMM_STATIC_PRIVATE_INIT;
+
+AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags)
+ : AudioSource (s, path), _flags (flags),
+ _channel (0)
+{
+ /* constructor used for existing external to session files. file must exist already */
+ _is_embedded = AudioFileSource::determine_embeddedness (path);
+
+ if (init (path, true)) {
+ throw failed_constructor ();
+ }
+
+}
+
+AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags, SampleFormat samp_format, HeaderFormat hdr_format)
+ : AudioSource (s, path), _flags (flags),
+ _channel (0)
+{
+ /* constructor used for new internal-to-session files. file cannot exist */
+ _is_embedded = false;
+
+ if (init (path, false)) {
+ throw failed_constructor ();
+ }
+}
+
+AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist)
+ : AudioSource (s, node), _flags (Flag (Writable|CanRename))
+ /* _channel is set in set_state() or init() */
+{
+ /* constructor used for existing internal-to-session files. file must exist */
+
+ if (set_state (node)) {
+ throw failed_constructor ();
+ }
+
+ string foo = _name;
+
+ if (init (foo, must_exist)) {
+ throw failed_constructor ();
+ }
+}
+
+AudioFileSource::~AudioFileSource ()
+{
+ if (removable()) {
+ unlink (_path.c_str());
+ unlink (peakpath.c_str());
+ }
+}
+
+bool
+AudioFileSource::determine_embeddedness (ustring path)
+{
+ return (path.find("/") == 0);
+}
+
+bool
+AudioFileSource::removable () const
+{
+ return (_flags & Removable) && ((_flags & RemoveAtDestroy) || ((_flags & RemovableIfEmpty) && length() == 0));
+}
+
+int
+AudioFileSource::init (ustring pathstr, bool must_exist)
+{
+ _length = 0;
+ timeline_position = 0;
+ _peaks_built = false;
+
+ if (!find (pathstr, must_exist, file_is_new, _channel)) {
+ throw non_existent_source ();
+ }
+
+ if (file_is_new && must_exist) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+ustring
+AudioFileSource::peak_path (ustring audio_path)
+{
+ ustring base;
+
+ base = PBD::basename_nosuffix (audio_path);
+ base += '%';
+ base += (char) ('A' + _channel);
+
+ return _session.peak_path (base);
+}
+
+ustring
+AudioFileSource::find_broken_peakfile (ustring peak_path, ustring audio_path)
+{
+ ustring str;
+
+ /* check for the broken location in use by 2.0 for several months */
+
+ str = broken_peak_path (audio_path);
+
+ if (Glib::file_test (str, Glib::FILE_TEST_EXISTS)) {
+
+ if (is_embedded()) {
+
+ /* it would be nice to rename it but the nature of
+ the bug means that we can't reliably use it.
+ */
+
+ peak_path = str;
+
+ } else {
+ /* all native files are mono, so we can just rename
+ it.
+ */
+ ::rename (str.c_str(), peak_path.c_str());
+ }
+
+ } else {
+ /* Nasty band-aid for older sessions that were created before we
+ used libsndfile for all audio files.
+ */
+
+
+ str = old_peak_path (audio_path);
+ if (Glib::file_test (str, Glib::FILE_TEST_EXISTS)) {
+ peak_path = str;
+ }
+ }
+
+ return peak_path;
+}
+
+ustring
+AudioFileSource::broken_peak_path (ustring audio_path)
+{
+ return _session.peak_path (audio_path);
+}
+
+ustring
+AudioFileSource::old_peak_path (ustring audio_path)
+{
+ /* XXX hardly bombproof! fix me */
+
+ struct stat stat_file;
+ struct stat stat_mount;
+
+ ustring mp = mountpoint (audio_path);
+
+ stat (audio_path.c_str(), &stat_file);
+ stat (mp.c_str(), &stat_mount);
+
+ char buf[32];
+#ifdef __APPLE__
+ snprintf (buf, sizeof (buf), "%u-%u-%d.peak", stat_mount.st_ino, stat_file.st_ino, _channel);
+#else
+ snprintf (buf, sizeof (buf), "%ld-%ld-%d.peak", stat_mount.st_ino, stat_file.st_ino, _channel);
+#endif
+
+ ustring res = peak_dir;
+ res += buf;
+ res += peakfile_suffix;
+
+ return res;
+}
+
+bool
+AudioFileSource::get_soundfile_info (ustring path, SoundFileInfo& _info, string& error_msg)
+{
+#ifdef HAVE_COREAUDIO
+ if (CoreAudioSource::get_soundfile_info (path, _info, error_msg) == 0) {
+ return true;
+ }
+#endif // HAVE_COREAUDIO
+
+ if (SndFileSource::get_soundfile_info (path, _info, error_msg) != 0) {
+ return true;
+ }
+
+ return false;
+}
+
+XMLNode&
+AudioFileSource::get_state ()
+{
+ XMLNode& root (AudioSource::get_state());
+ char buf[32];
+ root.add_property (X_("flags"), enum_2_string (_flags));
+ snprintf (buf, sizeof (buf), "%u", _channel);
+ root.add_property (X_("channel"), buf);
+ return root;
+}
+
+int
+AudioFileSource::set_state (const XMLNode& node)
+{
+ const XMLProperty* prop;
+
+ if (AudioSource::set_state (node)) {
+ return -1;
+ }
+
+ if ((prop = node.property (X_("flags"))) != 0) {
+ _flags = Flag (string_2_enum (prop->value(), _flags));
+ } else {
+ _flags = Flag (0);
+
+ }
+
+ if ((prop = node.property (X_("channel"))) != 0) {
+ _channel = atoi (prop->value());
+ } else {
+ _channel = 0;
+ }
+
+ if ((prop = node.property (X_("name"))) != 0) {
+ _is_embedded = AudioFileSource::determine_embeddedness (prop->value());
+ } else {
+ _is_embedded = false;
+ }
+
+ if ((prop = node.property (X_("destructive"))) != 0) {
+ /* old style, from the period when we had DestructiveFileSource */
+ _flags = Flag (_flags | Destructive);
+ }
+
+ return 0;
+}
+
+void
+AudioFileSource::mark_for_remove ()
+{
+ // This operation is not allowed for sources for destructive tracks or embedded files.
+ // Fortunately mark_for_remove() is never called for embedded files. This function
+ // must be fixed if that ever happens.
+ if (_flags & Destructive) {
+ return;
+ }
+
+ _flags = Flag (_flags | Removable | RemoveAtDestroy);
+}
+
+void
+AudioFileSource::mark_streaming_write_completed ()
+{
+ if (!writable()) {
+ return;
+ }
+
+ /* XXX notice that we're readers of _peaks_built
+ but we must hold a solid lock on PeaksReady.
+ */
+
+ Glib::Mutex::Lock lm (_lock);
+
+ if (_peaks_built) {
+ PeaksReady (); /* EMIT SIGNAL */
+ }
+}
+
+void
+AudioFileSource::mark_take (ustring id)
+{
+ if (writable()) {
+ _take_id = id;
+ }
+}
+
+int
+AudioFileSource::move_to_trash (const ustring& trash_dir_name)
+{
+ if (is_embedded()) {
+ cerr << "tried to move an embedded region to trash" << endl;
+ return -1;
+ }
+
+ ustring newpath;
+
+ if (!writable()) {
+ return -1;
+ }
+
+ /* don't move the file across filesystems, just
+ stick it in the `trash_dir_name' directory
+ on whichever filesystem it was already on.
+ */
+
+ newpath = Glib::path_get_dirname (_path);
+ newpath = Glib::path_get_dirname (newpath);
+
+ cerr << "from " << _path << " dead dir looks like " << newpath << endl;
+
+ newpath += '/';
+ newpath += trash_dir_name;
+ newpath += '/';
+ newpath += Glib::path_get_basename (_path);
+
+ if (access (newpath.c_str(), F_OK) == 0) {
+
+ /* the new path already exists, try versioning */
+
+ char buf[PATH_MAX+1];
+ int version = 1;
+ ustring newpath_v;
+
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
+ newpath_v = buf;
+
+ while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
+ snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
+ newpath_v = buf;
+ }
+
+ if (version == 999) {
+ error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
+ newpath)
+ << endmsg;
+ } else {
+ newpath = newpath_v;
+ }
+
+ } else {
+
+ /* it doesn't exist, or we can't read it or something */
+
+ }
+
+ if (::rename (_path.c_str(), newpath.c_str()) != 0) {
+ error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
+ _path, newpath, strerror (errno))
+ << endmsg;
+ return -1;
+ }
+
+ if (::unlink (peakpath.c_str()) != 0) {
+ error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
+ peakpath, _path, strerror (errno))
+ << endmsg;
+ /* try to back out */
+ rename (newpath.c_str(), _path.c_str());
+ return -1;
+ }
+
+ _path = newpath;
+ peakpath = "";
+
+ /* file can not be removed twice, since the operation is not idempotent */
+
+ _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
+
+ return 0;
+}
+
+bool
+AudioFileSource::find (ustring& pathstr, bool must_exist, bool& isnew, uint16_t& chan)
+{
+ ustring::size_type pos;
+ bool ret = false;
+
+ isnew = false;
+
+ if (pathstr[0] != '/') {
+
+ /* non-absolute pathname: find pathstr in search path */
+
+ vector<ustring> dirs;
+ int cnt;
+ ustring fullpath;
+ ustring keeppath;
+
+ if (search_path.length() == 0) {
+ error << _("FileSource: search path not set") << endmsg;
+ goto out;
+ }
+
+ split (search_path, dirs, ':');
+
+ cnt = 0;
+
+ for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
+
+ fullpath = *i;
+ if (fullpath[fullpath.length()-1] != '/') {
+ fullpath += '/';
+ }
+
+ fullpath += pathstr;
+
+ /* i (paul) made a nasty design error by using ':' as a special character in
+ Ardour 0.99 .. this hack tries to make things sort of work.
+ */
+
+ if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
+
+ if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
+
+ /* its a real file, no problem */
+
+ keeppath = fullpath;
+ ++cnt;
+
+ } else {
+
+ if (must_exist) {
+
+ /* might be an older session using file:channel syntax. see if the version
+ without the :suffix exists
+ */
+
+ ustring shorter = pathstr.substr (0, pos);
+ fullpath = *i;
+
+ if (fullpath[fullpath.length()-1] != '/') {
+ fullpath += '/';
+ }
+
+ fullpath += shorter;
+
+ if (Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
+ chan = atoi (pathstr.substr (pos+1));
+ pathstr = shorter;
+ keeppath = fullpath;
+ ++cnt;
+ }
+
+ } else {
+
+ /* new derived file (e.g. for timefx) being created in a newer session */
+
+ }
+ }
+
+ } else {
+
+ if (Glib::file_test (fullpath, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
+ keeppath = fullpath;
+ ++cnt;
+ }
+ }
+ }
+
+ if (cnt > 1) {
+
+ error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, search_path) << endmsg;
+ goto out;
+
+ } else if (cnt == 0) {
+
+ if (must_exist) {
+ error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, search_path) << endmsg;
+ goto out;
+ } else {
+ isnew = true;
+ }
+ }
+
+ _name = pathstr;
+ _path = keeppath;
+ ret = true;
+
+ } else {
+
+ /* external files and/or very very old style sessions include full paths */
+
+ /* ugh, handle ':' situation */
+
+ if ((pos = pathstr.find_last_of (':')) != ustring::npos) {
+
+ ustring shorter = pathstr.substr (0, pos);
+
+ if (Glib::file_test (shorter, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
+ chan = atoi (pathstr.substr (pos+1));
+ pathstr = shorter;
+ }
+ }
+
+ _path = pathstr;
+
+ if (is_embedded()) {
+ _name = pathstr;
+ } else {
+ _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
+ }
+
+ if (!Glib::file_test (pathstr, Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
+
+ /* file does not exist or we cannot read it */
+
+ if (must_exist) {
+ error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ if (errno != ENOENT) {
+ error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
+ goto out;
+ }
+
+ /* a new file */
+
+ isnew = true;
+ ret = true;
+
+ } else {
+
+ /* already exists */
+
+ ret = true;
+
+ }
+ }
+
+ out:
+ return ret;
+}
+
+void
+AudioFileSource::set_search_path (ustring p)
+{
+ search_path = p;
+}
+
+void
+AudioFileSource::set_header_position_offset (nframes_t offset)
+{
+ header_position_offset = offset;
+ HeaderPositionOffsetChanged ();
+}
+
+void
+AudioFileSource::set_timeline_position (int64_t pos)
+{
+ timeline_position = pos;
+}
+
+void
+AudioFileSource::set_allow_remove_if_empty (bool yn)
+{
+ if (!writable()) {
+ return;
+ }
+
+ if (yn) {
+ _flags = Flag (_flags | RemovableIfEmpty);
+ } else {
+ _flags = Flag (_flags & ~RemovableIfEmpty);
+ }
+}
+
+int
+AudioFileSource::set_source_name (ustring newname, bool destructive)
+{
+ Glib::Mutex::Lock lm (_lock);
+ ustring oldpath = _path;
+ ustring newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
+
+ if (newpath.empty()) {
+ error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
+ return -1;
+ }
+
+ // Test whether newpath exists, if yes notify the user but continue.
+ if (access(newpath.c_str(),F_OK) == 0) {
+ error << _("Programming error! Ardour tried to rename a file over another file! It's safe to continue working, but please report this to the developers.") << endmsg;
+ return -1;
+ }
+
+ if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
+ error << string_compose (_("cannot rename audio file %1 to %2"), _name, newpath) << endmsg;
+ return -1;
+ }
+
+ _name = Glib::path_get_basename (newpath);
+ _path = newpath;
+
+ return rename_peakfile (peak_path (_path));
+}
+
+bool
+AudioFileSource::is_empty (Session& s, ustring path)
+{
+ SoundFileInfo info;
+ string err;
+
+ if (!get_soundfile_info (path, info, err)) {
+ /* dangerous: we can't get info, so assume that its not empty */
+ return false;
+ }
+
+ return info.length == 0;
+}
+
+int
+AudioFileSource::setup_peakfile ()
+{
+ if (!(_flags & NoPeakFile)) {
+ return initialize_peakfile (file_is_new, _path);
+ } else {
+ return 0;
+ }
+}
+
+bool
+AudioFileSource::safe_file_extension(ustring file)
+{
+ const char* suffixes[] = {
+ ".wav", ".WAV",
+ ".aiff", ".AIFF",
+ ".caf", ".CAF",
+ ".aif", ".AIF",
+ ".amb", ".AMB",
+ ".snd", ".SND",
+ ".au", ".AU",
+ ".raw", ".RAW",
+ ".sf", ".SF",
+ ".cdr", ".CDR",
+ ".smp", ".SMP",
+ ".maud", ".MAUD",
+ ".vwe", ".VWE",
+ ".paf", ".PAF",
+ ".voc", ".VOC",
+#ifdef HAVE_FLAC
+ ".flac", ".FLAC",
+#endif // HAVE_FLAC
+#ifdef HAVE_COREAUDIO
+ ".mp3", ".MP3",
+ ".aac", ".AAC",
+ ".mp4", ".MP4",
+#endif // HAVE_COREAUDIO
+ };
+
+ for (size_t n = 0; n < sizeof(suffixes)/sizeof(suffixes[0]); ++n) {
+ if (file.rfind (suffixes[n]) == file.length() - strlen (suffixes[n])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+AudioFileSource::mark_immutable ()
+{
+ /* destructive sources stay writable, and their other flags don't
+ change.
+ */
+
+ if (!(_flags & Destructive)) {
+ _flags = Flag (_flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy|CanRename));
+ }
+}
+
+
+Sample*
+AudioFileSource::get_interleave_buffer (nframes_t size)
+{
+ SizedSampleBuffer* ssb;
+
+ if ((ssb = thread_interleave_buffer.get()) == 0) {
+ ssb = new SizedSampleBuffer (size);
+ thread_interleave_buffer.set (ssb);
+ }
+
+ if (ssb->size < size) {
+ ssb = new SizedSampleBuffer (size);
+ thread_interleave_buffer.set (ssb);
+ }
+
+ return ssb->buf;
+}