2 Copyright (C) 2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "libardour-config.h"
33 #include "pbd/gstdio_compat.h"
35 #include <glibmm/convert.h>
36 #include <glibmm/fileutils.h>
37 #include <glibmm/miscutils.h>
39 #include "ardour/runtime_functions.h"
40 #include "ardour/sndfilesource.h"
41 #include "ardour/sndfile_helpers.h"
42 #include "ardour/utils.h"
43 #include "ardour/session.h"
48 using namespace ARDOUR;
52 gain_t* SndFileSource::out_coefficient = 0;
53 gain_t* SndFileSource::in_coefficient = 0;
54 samplecnt_t SndFileSource::xfade_samples = 64;
55 const Source::Flag SndFileSource::default_writable_flags = Source::Flag (
58 Source::RemovableIfEmpty |
61 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
63 , AudioFileSource (s, node)
66 , _capture_start (false)
67 , _capture_end (false)
73 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
77 throw failed_constructor ();
81 /** Constructor for existing external-to-session files.
82 Files created this way are never writable or removable
84 SndFileSource::SndFileSource (Session& s, const string& path, int chn, Flag flags)
85 : Source(s, DataType::AUDIO, path, flags)
86 /* note that the origin of an external file is itself */
87 , AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
90 , _capture_start (false)
91 , _capture_end (false)
99 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
103 throw failed_constructor ();
107 /** This constructor is used to construct new internal-to-session files,
108 not open existing ones.
110 SndFileSource::SndFileSource (Session& s, const string& path, const string& origin,
111 SampleFormat sfmt, HeaderFormat hf, samplecnt_t rate, Flag flags)
112 : Source(s, DataType::AUDIO, path, flags)
113 , AudioFileSource (s, path, origin, flags, sfmt, hf)
115 , _broadcast_info (0)
116 , _capture_start (false)
117 , _capture_end (false)
125 assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
133 _flags = Flag (_flags & ~Broadcast);
137 fmt = SF_FORMAT_AIFF;
138 _flags = Flag (_flags & ~Broadcast);
143 _flags = Flag (_flags | Broadcast);
148 _flags = Flag (_flags & ~Broadcast);
153 _flags = Flag (_flags & ~Broadcast);
157 fmt = SF_FORMAT_RF64;
158 _flags = Flag (_flags & ~Broadcast);
159 _flags = Flag (_flags | RF64_RIFF);
163 fmt = SF_FORMAT_RF64;
164 _flags = Flag (_flags | Broadcast);
165 _flags = Flag (_flags | RF64_RIFF);
169 fmt = SF_FORMAT_RF64;
170 _flags = Flag (_flags & ~Broadcast);
174 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
175 abort(); /*NOTREACHED*/
182 fmt |= SF_FORMAT_FLOAT;
186 fmt |= SF_FORMAT_PCM_24;
190 fmt |= SF_FORMAT_PCM_16;
195 _info.samplerate = rate;
198 if (_flags & Destructive) {
200 throw failed_constructor();
203 /* normal mode: do not open the file here - do that in {read,write}_unlocked() as needed
208 /** Constructor to be called for recovering files being used for
209 * capture. They are in-session, they already exist, they should not
210 * be writable. They are an odd hybrid (from a constructor point of
211 * view) of the previous two constructors.
213 SndFileSource::SndFileSource (Session& s, const string& path, int chn)
214 : Source (s, DataType::AUDIO, path, Flag (0))
215 /* the final boolean argument is not used, its value is irrelevant. see audiofilesource.h for explanation */
216 , AudioFileSource (s, path, Flag (0))
218 , _broadcast_info (0)
219 , _capture_start (false)
220 , _capture_end (false)
228 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
232 throw failed_constructor ();
236 /** Constructor to losslessly compress existing source to flac */
237 SndFileSource::SndFileSource (Session& s, const AudioFileSource& other, const string& path, bool use16bits, Progress* progress)
238 : Source(s, DataType::AUDIO, path, Flag ((other.flags () | default_writable_flags | NoPeakFile) & ~RF64_RIFF))
239 , AudioFileSource (s, path, "", Flag ((other.flags () | default_writable_flags | NoPeakFile) & ~RF64_RIFF), /*unused*/ FormatFloat, /*unused*/ WAVE64)
241 , _broadcast_info (0)
242 , _capture_start (false)
243 , _capture_end (false)
247 if (other.readable_length () == 0) {
248 throw failed_constructor();
251 assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
253 _channel = other.channel ();
258 _info.channels = other.n_channels();
259 _info.samplerate = other.sample_rate ();
260 _info.format = SF_FORMAT_FLAC | (use16bits ? SF_FORMAT_PCM_16 : SF_FORMAT_PCM_24);
262 /* flac is either read or write -- never both,
263 * so we need to special-case ::open () */
264 #ifdef PLATFORM_WINDOWS
265 int fd = g_open (_path.c_str(), O_CREAT | O_RDWR, 0644);
267 int fd = ::open (_path.c_str(), O_CREAT | O_RDWR, 0644);
270 throw failed_constructor();
273 _sndfile = sf_open_fd (fd, SFM_WRITE, &_info, true);
276 throw failed_constructor();
284 /* normalize before converting to fixed point, calc gain factor */
285 samplecnt_t len = other.read (buf, off, 8192, /*channel*/0);
287 peak = compute_peak (buf, len, peak);
289 len = other.read (buf, off, 8192, /*channel*/0);
291 progress->set_progress (0.5f * (float) off / other.readable_length ());
302 len = other.read (buf, off, 8192, /*channel*/0);
305 for (samplecnt_t i = 0; i < len; ++i) {
311 len = other.read (buf, off, 8192, /*channel*/0);
313 progress->set_progress (0.5f + 0.5f * (float) off / other.readable_length ());
319 SndFileSource::init_sndfile ()
321 /* although libsndfile says we don't need to set this,
322 valgrind and source code shows us that we do.
325 memset (&_info, 0, sizeof(_info));
328 xfade_buf = new Sample[xfade_samples];
329 _timeline_position = header_position_offset;
332 AudioFileSource::HeaderPositionOffsetChanged.connect_same_thread (header_position_connection, boost::bind (&SndFileSource::handle_header_position_change, this));
336 SndFileSource::close ()
346 SndFileSource::open ()
352 // We really only want to use g_open for all platforms but because of this
353 // method(SndfileSource::open), the compiler(or at least GCC) is confused
354 // because g_open will expand to "open" on non-POSIX systems and needs the
355 // global namespace qualifer. The problem is since since C99 ::g_open will
356 // apparently expand to ":: open"
357 #ifdef PLATFORM_WINDOWS
358 int fd = g_open (_path.c_str(), writable() ? O_CREAT | O_RDWR : O_RDONLY, writable() ? 0644 : 0444);
360 int fd = ::open (_path.c_str(), writable() ? O_CREAT | O_RDWR : O_RDONLY, writable() ? 0644 : 0444);
364 error << string_compose (
365 _ ("SndFileSource: cannot open file \"%1\" for %2"),
367 (writable () ? "read+write" : "reading")) << endmsg;
371 if ((_info.format & SF_FORMAT_TYPEMASK ) == SF_FORMAT_FLAC) {
372 assert (!writable());
373 _sndfile = sf_open_fd (fd, SFM_READ, &_info, true);
375 _sndfile = sf_open_fd (fd, writable() ? SFM_RDWR : SFM_READ, &_info, true);
380 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
381 #ifndef HAVE_COREAUDIO
382 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
383 so we don't want to see this message.
386 cerr << "failed to open " << _path << " with name " << _name << endl;
388 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"),
389 _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
394 if (_channel >= _info.channels) {
395 #ifndef HAVE_COREAUDIO
396 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
403 _length = _info.frames;
405 #ifdef HAVE_RF64_RIFF
406 if (_file_is_new && _length == 0 && writable()) {
407 if (_flags & RF64_RIFF) {
408 if (sf_command (_sndfile, SFC_RF64_AUTO_DOWNGRADE, 0, 0) != SF_TRUE) {
410 sf_error_str (_sndfile, errbuf, sizeof (errbuf) - 1);
411 error << string_compose (_("Cannot mark RF64 audio file for automatic downgrade to WAV: %1"), errbuf)
418 if (!_broadcast_info) {
419 _broadcast_info = new BroadcastInfo;
422 bool bwf_info_exists = _broadcast_info->load_from_file (_sndfile);
424 if (_file_is_new && _length == 0 && writable() && !bwf_info_exists) {
425 /* newly created files will not have a BWF header at this point in time.
426 * Import will have called Source::set_timeline_position() if one exists
427 * in the original. */
428 header_position_offset = _timeline_position;
431 /* Set our timeline position to either the time reference from a BWF header or the current
432 start of the session.
434 set_timeline_position (bwf_info_exists ? _broadcast_info->get_time_reference() : header_position_offset);
436 if (_length != 0 && !bwf_info_exists) {
437 delete _broadcast_info;
439 _flags = Flag (_flags & ~Broadcast);
442 /* Set the broadcast flag if the BWF info is already there. We need
443 * this when recovering or using existing files.
446 if (bwf_info_exists) {
447 _flags = Flag (_flags | Broadcast);
451 sf_command (_sndfile, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
453 if (_flags & Broadcast) {
455 if (!_broadcast_info) {
456 _broadcast_info = new BroadcastInfo;
459 _broadcast_info->set_from_session (_session, header_position_offset);
460 _broadcast_info->set_description (string_compose ("BWF %1", _name));
462 if (!_broadcast_info->write_to_file (_sndfile)) {
463 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
464 _path, _broadcast_info->get_error())
466 _flags = Flag (_flags & ~Broadcast);
467 delete _broadcast_info;
476 SndFileSource::~SndFileSource ()
479 delete _broadcast_info;
484 SndFileSource::sample_rate () const
486 return _info.samplerate;
490 SndFileSource::read_unlocked (Sample *dst, samplepos_t start, samplecnt_t cnt) const
496 samplecnt_t real_cnt;
497 samplepos_t file_cnt;
499 if (writable() && !_sndfile) {
500 /* file has not been opened yet - nothing written to it */
501 memset (dst, 0, sizeof (Sample) * cnt);
505 if (const_cast<SndFileSource*>(this)->open()) {
506 error << string_compose (_("could not open file %1 for reading."), _path) << endmsg;
510 if (start > _length) {
512 /* read starts beyond end of data, just memset to zero */
516 } else if (start + cnt > _length) {
518 /* read ends beyond end of data, read some, memset the rest */
520 file_cnt = _length - start;
524 /* read is entirely within data */
529 assert (file_cnt >= 0);
531 if (file_cnt != cnt) {
532 samplepos_t delta = cnt - file_cnt;
533 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
538 if (sf_seek (_sndfile, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
540 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
541 error << string_compose(_("SndFileSource: could not seek to sample %1 within %2 (%3)"), start, _name.val().substr (1), errbuf) << endmsg;
545 if (_info.channels == 1) {
546 samplecnt_t ret = sf_read_float (_sndfile, dst, file_cnt);
547 if (ret != file_cnt) {
549 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
550 error << string_compose(_("SndFileSource: @ %1 could not read %2 within %3 (%4) (len = %5, ret was %6)"), start, file_cnt, _name.val().substr (1), errbuf, _length, ret) << endl;
553 for (samplecnt_t i = 0; i < ret; ++i) {
561 real_cnt = cnt * _info.channels;
563 Sample* interleave_buf = get_interleave_buffer (real_cnt);
565 nread = sf_read_float (_sndfile, interleave_buf, real_cnt);
566 ptr = interleave_buf + _channel;
567 nread /= _info.channels;
569 /* stride through the interleaved data */
572 for (samplecnt_t n = 0; n < nread; ++n) {
573 dst[n] = *ptr * _gain;
574 ptr += _info.channels;
577 for (samplecnt_t n = 0; n < nread; ++n) {
579 ptr += _info.channels;
587 SndFileSource::write_unlocked (Sample *data, samplecnt_t cnt)
594 return destructive_write_unlocked (data, cnt);
596 return nondestructive_write_unlocked (data, cnt);
601 SndFileSource::nondestructive_write_unlocked (Sample *data, samplecnt_t cnt)
604 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
608 if (_info.channels != 1) {
609 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
610 abort(); /*NOTREACHED*/
614 samplepos_t sample_pos = _length;
616 if (write_float (data, sample_pos, cnt) != cnt) {
620 update_length (_length + cnt);
622 if (_build_peakfiles) {
623 compute_and_write_peaks (data, sample_pos, cnt, true, true);
630 SndFileSource::destructive_write_unlocked (Sample* data, samplecnt_t cnt)
633 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
637 if (_capture_start && _capture_end) {
639 /* start and end of capture both occur within the data we are writing,
640 so do both crossfades.
643 _capture_start = false;
644 _capture_end = false;
646 /* move to the correct location place */
647 file_pos = capture_start_sample - _timeline_position;
650 samplecnt_t subcnt = cnt / 2;
651 samplecnt_t ofilepos = file_pos;
654 if (crossfade (data, subcnt, 1) != subcnt) {
659 Sample * tmpdata = data + subcnt;
662 subcnt = cnt - subcnt;
663 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
667 file_pos = ofilepos; // adjusted below
669 } else if (_capture_start) {
671 /* start of capture both occur within the data we are writing,
675 _capture_start = false;
676 _capture_end = false;
678 /* move to the correct location place */
679 file_pos = capture_start_sample - _timeline_position;
681 if (crossfade (data, cnt, 1) != cnt) {
685 } else if (_capture_end) {
687 /* end of capture both occur within the data we are writing,
691 _capture_start = false;
692 _capture_end = false;
694 if (crossfade (data, cnt, 0) != cnt) {
700 /* in the middle of recording */
702 if (write_float (data, file_pos, cnt) != cnt) {
707 update_length (file_pos + cnt);
709 if (_build_peakfiles) {
710 compute_and_write_peaks (data, file_pos, cnt, true, true);
719 SndFileSource::update_header (samplepos_t when, struct tm& now, time_t tnow)
721 set_timeline_position (when);
723 if (_flags & Broadcast) {
724 if (setup_broadcast_info (when, now, tnow)) {
729 return flush_header ();
733 SndFileSource::flush_header ()
736 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
741 error << string_compose (_("could not allocate file %1 to write header"), _path) << endmsg;
745 int const r = sf_command (_sndfile, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE;
751 SndFileSource::flush ()
754 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
759 error << string_compose (_("could not allocate file %1 to flush contents"), _path) << endmsg;
763 // Hopefully everything OK
764 sf_write_sync (_sndfile);
768 SndFileSource::setup_broadcast_info (samplepos_t /*when*/, struct tm& now, time_t /*tnow*/)
771 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
776 warning << string_compose (_("attempt to set BWF info for an un-opened audio file source (%1)"), _path) << endmsg;
780 if (!(_flags & Broadcast) || !_broadcast_info) {
784 _broadcast_info->set_originator_ref_from_session (_session);
785 _broadcast_info->set_origination_time (&now);
787 /* now update header position taking header offset into account */
789 set_header_timeline_position ();
795 SndFileSource::set_header_timeline_position ()
797 if (!(_flags & Broadcast)) {
800 assert (_broadcast_info);
802 _broadcast_info->set_time_reference (_timeline_position);
804 if (_sndfile == 0 || !_broadcast_info->write_to_file (_sndfile)) {
805 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
806 _path, _broadcast_info->get_error())
808 _flags = Flag (_flags & ~Broadcast);
809 delete _broadcast_info;
815 SndFileSource::write_float (Sample* data, samplepos_t sample_pos, samplecnt_t cnt)
817 if ((_info.format & SF_FORMAT_TYPEMASK ) == SF_FORMAT_FLAC) {
818 assert (_length == sample_pos);
820 else if (_sndfile == 0 || sf_seek (_sndfile, sample_pos, SEEK_SET|SFM_WRITE) < 0) {
822 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
823 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3)"), _path, sample_pos, errbuf) << endmsg;
827 if (sf_writef_float (_sndfile, data, cnt) != (ssize_t) cnt) {
835 SndFileSource::natural_position() const
837 return _timeline_position;
841 SndFileSource::clear_capture_marks ()
843 _capture_start = false;
844 _capture_end = false;
847 /** @param pos Capture start position in session samples */
849 SndFileSource::mark_capture_start (samplepos_t pos)
852 if (pos < _timeline_position) {
853 _capture_start = false;
855 _capture_start = true;
856 capture_start_sample = pos;
862 SndFileSource::mark_capture_end()
870 SndFileSource::crossfade (Sample* data, samplecnt_t cnt, int fade_in)
872 samplecnt_t xfade = min (xfade_samples, cnt);
873 samplecnt_t nofade = cnt - xfade;
874 Sample* fade_data = 0;
875 samplepos_t fade_position = 0; // in samples
877 samplecnt_t file_cnt;
880 fade_position = file_pos;
883 fade_position = file_pos + nofade;
884 fade_data = data + nofade;
887 if (fade_position > _length) {
889 /* read starts beyond end of data, just memset to zero */
893 } else if (fade_position + xfade > _length) {
895 /* read ends beyond end of data, read some, memset the rest */
897 file_cnt = _length - fade_position;
901 /* read is entirely within data */
908 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
909 if (retval >= 0 && errno == EAGAIN) {
910 /* XXX - can we really trust that errno is meaningful here? yes POSIX, i'm talking to you.
911 * short or no data there */
912 memset (xfade_buf, 0, xfade * sizeof(Sample));
914 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
920 if (file_cnt != xfade) {
921 samplecnt_t delta = xfade - file_cnt;
922 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
925 if (nofade && !fade_in) {
926 if (write_float (data, file_pos, nofade) != nofade) {
927 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
932 if (xfade == xfade_samples) {
936 /* use the standard xfade curve */
940 /* fade new material in */
942 for (n = 0; n < xfade; ++n) {
943 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
949 /* fade new material out */
951 for (n = 0; n < xfade; ++n) {
952 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
956 } else if (xfade < xfade_samples) {
958 std::vector<gain_t> in(xfade);
959 std::vector<gain_t> out(xfade);
961 /* short xfade, compute custom curve */
963 compute_equal_power_fades (xfade, &in[0], &out[0]);
965 for (samplecnt_t n = 0; n < xfade; ++n) {
966 xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
971 /* long xfade length, has to be computed across several calls */
976 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
977 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
982 if (fade_in && nofade) {
983 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
984 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
993 SndFileSource::last_capture_start_sample () const
996 return capture_start_sample;
1003 SndFileSource::handle_header_position_change ()
1005 if (destructive()) {
1006 if ( _length != 0 ) {
1007 error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
1008 //in the future, pop up a dialog here that allows user to regenerate file with new start offset
1009 } else if (writable()) {
1010 _timeline_position = header_position_offset;
1011 set_header_timeline_position (); //this will get flushed if/when the file is recorded to
1017 SndFileSource::setup_standard_crossfades (Session const & s, samplecnt_t rate)
1019 /* This static method is assumed to have been called by the Session
1020 before any DFS's are created.
1023 xfade_samples = (samplecnt_t) floor ((s.config.get_destructive_xfade_msecs () / 1000.0) * rate);
1025 delete [] out_coefficient;
1026 delete [] in_coefficient;
1028 out_coefficient = new gain_t[xfade_samples];
1029 in_coefficient = new gain_t[xfade_samples];
1031 compute_equal_power_fades (xfade_samples, in_coefficient, out_coefficient);
1035 SndFileSource::set_timeline_position (samplepos_t pos)
1037 // destructive track timeline postion does not change
1038 // except at instantion or when header_position_offset
1039 // (session start) changes
1041 if (!destructive()) {
1042 AudioFileSource::set_timeline_position (pos);
1047 SndFileSource::get_soundfile_info (const string& path, SoundFileInfo& info, string& error_msg)
1051 BroadcastInfo binfo;
1053 sf_info.format = 0; // libsndfile says to clear this before sf_open().
1055 if (path.empty() || Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
1059 #ifdef PLATFORM_WINDOWS
1060 int fd = g_open (path.c_str(), O_RDONLY, 0444);
1062 int fd = ::open (path.c_str(), O_RDONLY, 0444);
1066 error << string_compose ( _("SndFileSource: cannot open file \"%1\" for reading"), path)
1070 if ((sf = sf_open_fd (fd, SFM_READ, &sf_info, true)) == 0) {
1072 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
1076 info.samplerate = sf_info.samplerate;
1077 info.channels = sf_info.channels;
1078 info.length = sf_info.frames;
1080 string major = sndfile_major_format(sf_info.format);
1081 string minor = sndfile_minor_format(sf_info.format);
1083 if (major.length() + minor.length() < 16) { /* arbitrary */
1084 info.format_name = string_compose("%1/%2", major, minor);
1086 info.format_name = string_compose("%1\n%2", major, minor);
1089 info.timecode = binfo.load_from_file (sf) ? binfo.get_time_reference() : 0;
1097 SndFileSource::one_of_several_channels () const
1099 return _info.channels > 1;
1103 SndFileSource::clamped_at_unity () const
1105 int const type = _info.format & SF_FORMAT_TYPEMASK;
1106 int const sub = _info.format & SF_FORMAT_SUBMASK;
1107 /* XXX: this may not be the full list of formats that are unclamped */
1108 return (sub != SF_FORMAT_FLOAT && sub != SF_FORMAT_DOUBLE && type != SF_FORMAT_OGG);
1112 SndFileSource::file_closed ()
1114 /* stupid libsndfile updated the headers on close,
1115 so touch the peakfile if it exists and has data
1116 to make sure its time is as new as the audio
1124 SndFileSource::set_path (const string& p)
1126 FileSource::set_path (p);