merged with 2.0-ongoing changes 2582-2605 (not thoroughly tested but it compiles...
[ardour.git] / libs / ardour / sndfilesource.cc
index 3c3798c8abaab4e3b93f240be71961fe189e4ffc..7c4859aa55a06a1bee9d8569365e62102ffd8013 100644 (file)
@@ -15,7 +15,6 @@
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id$
 */
 
 #include <cerrno>
@@ -26,9 +25,9 @@
 #include <sys/stat.h>
 
 #include <glibmm/miscutils.h>
-
+#include <glibmm/thread.h>
 #include <ardour/sndfilesource.h>
-
+#include <ardour/sndfile_helpers.h>
 #include <ardour/utils.h>
 
 #include "i18n.h"
@@ -36,6 +35,7 @@
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
+using Glib::ustring;
 
 gain_t* SndFileSource::out_coefficient = 0;
 gain_t* SndFileSource::in_coefficient = 0;
@@ -45,33 +45,50 @@ const AudioFileSource::Flag SndFileSource::default_writable_flags = AudioFileSou
                                                                                           AudioFileSource::RemovableIfEmpty|
                                                                                           AudioFileSource::CanRename);
 
+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;
+
 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
        : AudioFileSource (s, node)
 {
-       init (_name);
+       init ();
 
        if (open()) {
                throw failed_constructor ();
        }
 }
 
-SndFileSource::SndFileSource (Session& s, string idstr, Flag flags)
-                                       /* files created this way are never writable or removable */
-       : AudioFileSource (s, idstr, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
+SndFileSource::SndFileSource (Session& s, ustring path, int chn, Flag flags)
+          /* files created this way are never writable or removable */
+       : AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
 {
-       init (idstr);
+       _channel = chn;
+
+       init ();
 
        if (open()) {
                throw failed_constructor ();
        }
 }
 
-SndFileSource::SndFileSource (Session& s, string idstr, SampleFormat sfmt, HeaderFormat hf, nframes_t rate, Flag flags)
-       : AudioFileSource (s, idstr, flags, sfmt, hf)
+SndFileSource::SndFileSource (Session& s, ustring path, SampleFormat sfmt, HeaderFormat hf, nframes_t rate, Flag flags)
+       : AudioFileSource (s, path, flags, sfmt, hf)
 {
        int fmt = 0;
 
-       init (idstr);
+       init ();
 
        /* this constructor is used to construct new files, not open
           existing ones.
@@ -120,6 +137,10 @@ SndFileSource::SndFileSource (Session& s, string idstr, SampleFormat sfmt, Heade
        case FormatInt24:
                fmt |= SF_FORMAT_PCM_24;
                break;
+
+       case FormatInt16:
+               fmt |= SF_FORMAT_PCM_16;
+               break;
        }
        
        _info.channels = 1;
@@ -174,32 +195,19 @@ SndFileSource::SndFileSource (Session& s, string idstr, SampleFormat sfmt, Heade
 }
 
 void 
-SndFileSource::init (string idstr)
+SndFileSource::init ()
 {
-       string::size_type pos;
-       string file;
+       ustring file;
 
        // lets try to keep the object initalizations here at the top
        xfade_buf = 0;
-       interleave_buf = 0;
-       interleave_bufsize = 0;
        sf = 0;
        _broadcast_info = 0;
 
-       string tmp_name;
-
-       if ((pos = idstr.find_last_of (':')) == string::npos) {
-               channel = 0;
-               tmp_name = idstr;
-       } else {
-               channel = atoi (idstr.substr (pos+1).c_str());
-               tmp_name = idstr.substr (0, pos);
-       }
-
        if (is_embedded()) {
-               _name = tmp_name;
+               _name = _path;
        } else {
-               _name = Glib::path_get_basename (tmp_name);
+               _name = Glib::path_get_basename (_path);
        }
 
        /* although libsndfile says we don't need to set this,
@@ -212,7 +220,7 @@ SndFileSource::init (string idstr)
        _capture_end = false;
        file_pos = 0;
 
-       if (destructive()) {
+       if (destructive()) {    
                xfade_buf = new Sample[xfade_frames];
                timeline_position = header_position_offset;
        }
@@ -231,8 +239,8 @@ SndFileSource::open ()
                return -1;
        }
 
-       if (channel >= _info.channels) {
-               error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, channel) << endmsg;
+       if (_channel >= _info.channels) {
+               error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
                sf_close (sf);
                sf = 0;
                return -1;
@@ -243,27 +251,14 @@ SndFileSource::open ()
        _broadcast_info = new SF_BROADCAST_INFO;
        memset (_broadcast_info, 0, sizeof (*_broadcast_info));
        
-       /* lookup broadcast info */
-       
-       if (sf_command (sf, SFC_GET_BROADCAST_INFO, _broadcast_info, sizeof (*_broadcast_info)) != SF_TRUE) {
-
-               /* if the file has data but no broadcast info, then clearly, there is no broadcast info */
-
-               if (_length) {
-                       delete _broadcast_info;
-                       _broadcast_info = 0;
-                       _flags = Flag (_flags & ~Broadcast);
-               }
+       bool timecode_info_exists;
 
-               set_timeline_position (header_position_offset);
-
-       } else {
-       
-               /* XXX 64 bit alert: when JACK switches to a 64 bit frame count, this needs to use the high bits
-                  of the time reference.
-               */
+       set_timeline_position (get_timecode_info (sf, _broadcast_info, timecode_info_exists));
 
-               set_timeline_position ( _broadcast_info->time_reference_low );
+       if (!timecode_info_exists) {
+               delete _broadcast_info;
+               _broadcast_info = 0;
+               _flags = Flag (_flags & ~Broadcast);
        }
 
        if (writable()) {
@@ -290,10 +285,6 @@ SndFileSource::~SndFileSource ()
                touch_peakfile ();
        }
 
-       if (interleave_buf) {
-               delete [] interleave_buf;
-       }
-
        if (_broadcast_info) {
                delete _broadcast_info;
        }
@@ -359,17 +350,10 @@ SndFileSource::read_unlocked (Sample *dst, nframes_t start, nframes_t cnt) const
 
        real_cnt = cnt * _info.channels;
 
-       if (interleave_bufsize < real_cnt) {
-               
-               if (interleave_buf) {
-                       delete [] interleave_buf;
-               }
-               interleave_bufsize = real_cnt;
-               interleave_buf = new float[interleave_bufsize];
-       }
+       Sample* interleave_buf = get_interleave_buffer (real_cnt);
        
        nread = sf_read_float (sf, interleave_buf, real_cnt);
-       ptr = interleave_buf + channel;
+       ptr = interleave_buf + _channel;
        nread /= _info.channels;
        
        /* stride through the interleaved data */
@@ -398,6 +382,7 @@ nframes_t
 SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
 {
        if (!writable()) {
+               warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
                return 0;
        }
 
@@ -409,7 +394,7 @@ SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
        
        nframes_t oldlen;
        int32_t frame_pos = _length;
-       
+
        if (write_float (data, frame_pos, cnt) != cnt) {
                return 0;
        }
@@ -418,29 +403,7 @@ SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
        update_length (oldlen, cnt);
 
        if (_build_peakfiles) {
-               PeakBuildRecord *pbr = 0;
-               
-               if (pending_peak_builds.size()) {
-                               pbr = pending_peak_builds.back();
-                       }
-                       
-                       if (pbr && pbr->frame + pbr->cnt == oldlen) {
-                               
-                               /* the last PBR extended to the start of the current write,
-                                  so just extend it again.
-                               */
-
-                               pbr->cnt += cnt;
-                       } else {
-                               pending_peak_builds.push_back (new PeakBuildRecord (oldlen, cnt));
-                       }
-                       
-                       _peaks_built = false;
-       }
-       
-       
-       if (_build_peakfiles) {
-               queue_for_peaks (shared_from_this ());
+               compute_and_write_peaks (data, frame_pos, cnt, false, true);
        }
 
        _write_data_count = cnt;
@@ -454,6 +417,7 @@ SndFileSource::destructive_write_unlocked (Sample* data, nframes_t cnt)
        nframes_t old_file_pos;
 
        if (!writable()) {
+               warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
                return 0;
        }
 
@@ -529,32 +493,12 @@ SndFileSource::destructive_write_unlocked (Sample* data, nframes_t cnt)
 
        old_file_pos = file_pos;
        update_length (file_pos, cnt);
-       file_pos += cnt;
 
        if (_build_peakfiles) {
-               PeakBuildRecord *pbr = 0;
-               
-               if (pending_peak_builds.size()) {
-                       pbr = pending_peak_builds.back();
-               }
-               
-               if (pbr && pbr->frame + pbr->cnt == old_file_pos) {
-                       
-                       /* the last PBR extended to the start of the current write,
-                          so just extend it again.
-                       */
-                       
-                       pbr->cnt += cnt;
-               } else {
-                       pending_peak_builds.push_back (new PeakBuildRecord (old_file_pos, cnt));
-               }
-               
-               _peaks_built = false;
+               compute_and_write_peaks (data, file_pos, cnt, false, true);
        }
 
-       if (_build_peakfiles) {
-               queue_for_peaks (shared_from_this ());
-       }
+       file_pos += cnt;
        
        return cnt;
 }
@@ -577,6 +521,7 @@ int
 SndFileSource::flush_header ()
 {
        if (!writable() || (sf == 0)) {
+               warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
                return -1;
        }
        return (sf_command (sf, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE);
@@ -586,6 +531,7 @@ int
 SndFileSource::setup_broadcast_info (nframes_t when, struct tm& now, time_t tnow)
 {
        if (!writable()) {
+               warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
                return -1;
        }
 
@@ -638,8 +584,8 @@ SndFileSource::set_header_timeline_position ()
                return;
        }
 
-       _broadcast_info->time_reference_high = (timeline_position >> 32);
-       _broadcast_info->time_reference_low = (timeline_position & 0xffffffff);
+       _broadcast_info->time_reference_high = (timeline_position >> 32);
+       _broadcast_info->time_reference_low = (timeline_position & 0xffffffff);
 
        if (sf_command (sf, SFC_SET_BROADCAST_INFO, _broadcast_info, sizeof (*_broadcast_info)) != SF_TRUE) {
                error << string_compose (_("cannot set broadcast info for audio file %1; Dropping broadcast info for this file"), _path) << endmsg;
@@ -647,9 +593,6 @@ SndFileSource::set_header_timeline_position ()
                delete _broadcast_info;
                _broadcast_info = 0;
        }
-
-       
-
 }
 
 nframes_t
@@ -680,11 +623,15 @@ SndFileSource::set_destructive (bool yn)
 {
        if (yn) {
                _flags = Flag (_flags | Destructive);
+               if (!xfade_buf) {
+                       xfade_buf = new Sample[xfade_frames];
+               }
                clear_capture_marks ();
                timeline_position = header_position_offset;
        } else {
                _flags = Flag (_flags & ~Destructive);
                timeline_position = 0;
+               /* leave xfade buf alone in case we need it again later */
        }
 
        return true;
@@ -889,7 +836,7 @@ SndFileSource::setup_standard_crossfades (nframes_t rate)
 }
 
 void
-SndFileSource::set_timeline_position (nframes_t pos)
+SndFileSource::set_timeline_position (int64_t pos)
 {
        // destructive track timeline postion does not change
        // except at instantion or when header_position_offset 
@@ -899,3 +846,81 @@ SndFileSource::set_timeline_position (nframes_t pos)
                AudioFileSource::set_timeline_position (pos);
        } 
 }
+
+int
+SndFileSource::get_soundfile_info (const ustring& path, SoundFileInfo& info, string& error_msg)
+{
+       SNDFILE *sf;
+       SF_INFO sf_info;
+       SF_BROADCAST_INFO binfo;
+       bool timecode_exists;
+
+       sf_info.format = 0; // libsndfile says to clear this before sf_open().
+
+       if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) { 
+               char errbuf[256];
+               error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+               return false;
+       }
+
+       info.samplerate  = sf_info.samplerate;
+       info.channels    = sf_info.channels;
+       info.length      = sf_info.frames;
+       info.format_name = string_compose("Format: %1, %2",
+                                          sndfile_major_format(sf_info.format),
+                                          sndfile_minor_format(sf_info.format));
+
+       memset (&binfo, 0, sizeof (binfo));
+       info.timecode  = get_timecode_info (sf, &binfo, timecode_exists);
+
+       if (!timecode_exists) {
+               info.timecode = 0;
+       }
+       
+       sf_close (sf);
+
+       return true;
+}
+
+int64_t
+SndFileSource::get_timecode_info (SNDFILE* sf, SF_BROADCAST_INFO* binfo, bool& exists)
+{
+       if (sf_command (sf, SFC_GET_BROADCAST_INFO, binfo, sizeof (*binfo)) != SF_TRUE) {
+               exists = false;
+               return (header_position_offset);
+       } 
+       
+       /* XXX 64 bit alert: when JACK switches to a 64 bit frame count, this needs to use the high bits
+          of the time reference.
+       */
+       
+       exists = true;
+       int64_t ret = (uint32_t) binfo->time_reference_high;
+       ret <<= 32;
+       ret |= (uint32_t) binfo->time_reference_low;
+       return ret;
+}
+
+bool
+SndFileSource::one_of_several_channels () const
+{
+       return _info.channels > 1;
+}
+
+Sample*
+SndFileSource::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;
+}