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/sndfilesource.h"
40 #include "ardour/sndfile_helpers.h"
41 #include "ardour/utils.h"
42 #include "ardour/session.h"
47 using namespace ARDOUR;
51 gain_t* SndFileSource::out_coefficient = 0;
52 gain_t* SndFileSource::in_coefficient = 0;
53 framecnt_t SndFileSource::xfade_frames = 64;
54 const Source::Flag SndFileSource::default_writable_flags = Source::Flag (
57 Source::RemovableIfEmpty |
60 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
62 , AudioFileSource (s, node)
65 , _capture_start (false)
66 , _capture_end (false)
72 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
76 throw failed_constructor ();
80 /** Constructor for existing external-to-session files.
81 Files created this way are never writable or removable
83 SndFileSource::SndFileSource (Session& s, const string& path, int chn, Flag flags)
84 : Source(s, DataType::AUDIO, path, flags)
85 /* note that the origin of an external file is itself */
86 , AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
89 , _capture_start (false)
90 , _capture_end (false)
98 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
102 throw failed_constructor ();
106 /** This constructor is used to construct new internal-to-session files,
107 not open existing ones.
109 SndFileSource::SndFileSource (Session& s, const string& path, const string& origin,
110 SampleFormat sfmt, HeaderFormat hf, framecnt_t rate, Flag flags)
111 : Source(s, DataType::AUDIO, path, flags)
112 , AudioFileSource (s, path, origin, flags, sfmt, hf)
114 , _broadcast_info (0)
115 , _capture_start (false)
116 , _capture_end (false)
124 assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
132 _flags = Flag (_flags & ~Broadcast);
136 fmt = SF_FORMAT_AIFF;
137 _flags = Flag (_flags & ~Broadcast);
142 _flags = Flag (_flags | Broadcast);
147 _flags = Flag (_flags & ~Broadcast);
152 _flags = Flag (_flags & ~Broadcast);
156 fmt = SF_FORMAT_RF64;
157 _flags = Flag (_flags & ~Broadcast);
158 _flags = Flag (_flags | RF64_RIFF);
162 fmt = SF_FORMAT_RF64;
163 _flags = Flag (_flags | Broadcast);
164 _flags = Flag (_flags | RF64_RIFF);
168 fmt = SF_FORMAT_RF64;
169 _flags = Flag (_flags & ~Broadcast);
173 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
174 abort(); /*NOTREACHED*/
181 fmt |= SF_FORMAT_FLOAT;
185 fmt |= SF_FORMAT_PCM_24;
189 fmt |= SF_FORMAT_PCM_16;
194 _info.samplerate = rate;
197 if (_flags & Destructive) {
199 throw failed_constructor();
202 /* normal mode: do not open the file here - do that in {read,write}_unlocked() as needed
207 /** Constructor to be called for recovering files being used for
208 * capture. They are in-session, they already exist, they should not
209 * be writable. They are an odd hybrid (from a constructor point of
210 * view) of the previous two constructors.
212 SndFileSource::SndFileSource (Session& s, const string& path, int chn)
213 : Source (s, DataType::AUDIO, path, Flag (0))
214 /* the final boolean argument is not used, its value is irrelevant. see audiofilesource.h for explanation */
215 , AudioFileSource (s, path, Flag (0))
217 , _broadcast_info (0)
218 , _capture_start (false)
219 , _capture_end (false)
227 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
231 throw failed_constructor ();
235 /** Constructor to losslessly compress existing source to flac */
236 SndFileSource::SndFileSource (Session& s, const AudioFileSource& other, const string& path, bool use16bits, Progress* progress)
237 : Source(s, DataType::AUDIO, path, Flag ((other.flags () | default_writable_flags | NoPeakFile) & ~RF64_RIFF))
238 , AudioFileSource (s, path, "", Flag ((other.flags () | default_writable_flags | NoPeakFile) & ~RF64_RIFF), /*unused*/ FormatFloat, /*unused*/ WAVE64)
240 , _broadcast_info (0)
241 , _capture_start (false)
242 , _capture_end (false)
246 if (other.readable_length () == 0) {
247 throw failed_constructor();
250 assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
252 _channel = other.channel ();
257 _info.channels = other.n_channels();
258 _info.samplerate = other.sample_rate ();
259 _info.format = SF_FORMAT_FLAC | (use16bits ? SF_FORMAT_PCM_16 : SF_FORMAT_PCM_24);
261 /* flac is either read or write -- never both,
262 * so we need to special-case ::open () */
263 #ifdef PLATFORM_WINDOWS
264 int fd = g_open (_path.c_str(), O_CREAT | O_RDWR, 0644);
266 int fd = ::open (_path.c_str(), O_CREAT | O_RDWR, 0644);
269 throw failed_constructor();
272 _sndfile = sf_open_fd (fd, SFM_WRITE, &_info, true);
275 throw failed_constructor();
281 framecnt_t len = other.read (buf, off, 8192, /*channel*/0);
285 len = other.read (buf, off, 8192, /*channel*/0);
287 progress->set_progress ((float) off / other.readable_length ());
293 SndFileSource::init_sndfile ()
295 /* although libsndfile says we don't need to set this,
296 valgrind and source code shows us that we do.
299 memset (&_info, 0, sizeof(_info));
302 xfade_buf = new Sample[xfade_frames];
303 _timeline_position = header_position_offset;
306 AudioFileSource::HeaderPositionOffsetChanged.connect_same_thread (header_position_connection, boost::bind (&SndFileSource::handle_header_position_change, this));
310 SndFileSource::close ()
320 SndFileSource::open ()
326 // We really only want to use g_open for all platforms but because of this
327 // method(SndfileSource::open), the compiler(or at least GCC) is confused
328 // because g_open will expand to "open" on non-POSIX systems and needs the
329 // global namespace qualifer. The problem is since since C99 ::g_open will
330 // apparently expand to ":: open"
331 #ifdef PLATFORM_WINDOWS
332 int fd = g_open (_path.c_str(), writable() ? O_CREAT | O_RDWR : O_RDONLY, writable() ? 0644 : 0444);
334 int fd = ::open (_path.c_str(), writable() ? O_CREAT | O_RDWR : O_RDONLY, writable() ? 0644 : 0444);
338 error << string_compose (
339 _ ("SndFileSource: cannot open file \"%1\" for %2"),
341 (writable () ? "read+write" : "reading")) << endmsg;
345 if ((_info.format & SF_FORMAT_TYPEMASK ) == SF_FORMAT_FLAC) {
346 assert (!writable());
347 _sndfile = sf_open_fd (fd, SFM_READ, &_info, true);
349 _sndfile = sf_open_fd (fd, writable() ? SFM_RDWR : SFM_READ, &_info, true);
354 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
355 #ifndef HAVE_COREAUDIO
356 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
357 so we don't want to see this message.
360 cerr << "failed to open " << _path << " with name " << _name << endl;
362 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"),
363 _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
368 if (_channel >= _info.channels) {
369 #ifndef HAVE_COREAUDIO
370 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
377 _length = _info.frames;
379 #ifdef HAVE_RF64_RIFF
380 if (_file_is_new && _length == 0 && writable()) {
381 if (_flags & RF64_RIFF) {
382 if (sf_command (_sndfile, SFC_RF64_AUTO_DOWNGRADE, 0, 0) != SF_TRUE) {
384 sf_error_str (_sndfile, errbuf, sizeof (errbuf) - 1);
385 error << string_compose (_("Cannot mark RF64 audio file for automatic downgrade to WAV: %1"), errbuf)
392 if (!_broadcast_info) {
393 _broadcast_info = new BroadcastInfo;
396 bool bwf_info_exists = _broadcast_info->load_from_file (_sndfile);
398 if (_file_is_new && _length == 0 && writable() && !bwf_info_exists) {
399 /* newly created files will not have a BWF header at this point in time.
400 * Import will have called Source::set_timeline_position() if one exists
401 * in the original. */
402 header_position_offset = _timeline_position;
405 /* Set our timeline position to either the time reference from a BWF header or the current
406 start of the session.
408 set_timeline_position (bwf_info_exists ? _broadcast_info->get_time_reference() : header_position_offset);
410 if (_length != 0 && !bwf_info_exists) {
411 delete _broadcast_info;
413 _flags = Flag (_flags & ~Broadcast);
416 /* Set the broadcast flag if the BWF info is already there. We need
417 * this when recovering or using existing files.
420 if (bwf_info_exists) {
421 _flags = Flag (_flags | Broadcast);
425 sf_command (_sndfile, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
427 if (_flags & Broadcast) {
429 if (!_broadcast_info) {
430 _broadcast_info = new BroadcastInfo;
433 _broadcast_info->set_from_session (_session, header_position_offset);
434 _broadcast_info->set_description (string_compose ("BWF %1", _name));
436 if (!_broadcast_info->write_to_file (_sndfile)) {
437 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
438 _path, _broadcast_info->get_error())
440 _flags = Flag (_flags & ~Broadcast);
441 delete _broadcast_info;
450 SndFileSource::~SndFileSource ()
453 delete _broadcast_info;
458 SndFileSource::sample_rate () const
460 return _info.samplerate;
464 SndFileSource::read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const
473 if (writable() && !_sndfile) {
474 /* file has not been opened yet - nothing written to it */
475 memset (dst, 0, sizeof (Sample) * cnt);
479 if (const_cast<SndFileSource*>(this)->open()) {
480 error << string_compose (_("could not open file %1 for reading."), _path) << endmsg;
484 if (start > _length) {
486 /* read starts beyond end of data, just memset to zero */
490 } else if (start + cnt > _length) {
492 /* read ends beyond end of data, read some, memset the rest */
494 file_cnt = _length - start;
498 /* read is entirely within data */
503 assert (file_cnt >= 0);
505 if (file_cnt != cnt) {
506 framepos_t delta = cnt - file_cnt;
507 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
512 if (sf_seek (_sndfile, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
514 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
515 error << string_compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.val().substr (1), errbuf) << endmsg;
519 if (_info.channels == 1) {
520 framecnt_t ret = sf_read_float (_sndfile, dst, file_cnt);
521 if (ret != file_cnt) {
523 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
524 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;
530 real_cnt = cnt * _info.channels;
532 Sample* interleave_buf = get_interleave_buffer (real_cnt);
534 nread = sf_read_float (_sndfile, interleave_buf, real_cnt);
535 ptr = interleave_buf + _channel;
536 nread /= _info.channels;
538 /* stride through the interleaved data */
540 for (framecnt_t n = 0; n < nread; ++n) {
542 ptr += _info.channels;
549 SndFileSource::write_unlocked (Sample *data, framecnt_t cnt)
556 return destructive_write_unlocked (data, cnt);
558 return nondestructive_write_unlocked (data, cnt);
563 SndFileSource::nondestructive_write_unlocked (Sample *data, framecnt_t cnt)
566 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
570 if (_info.channels != 1) {
571 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
572 abort(); /*NOTREACHED*/
576 framepos_t frame_pos = _length;
578 if (write_float (data, frame_pos, cnt) != cnt) {
582 update_length (_length + cnt);
584 if (_build_peakfiles) {
585 compute_and_write_peaks (data, frame_pos, cnt, true, true);
592 SndFileSource::destructive_write_unlocked (Sample* data, framecnt_t cnt)
595 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
599 if (_capture_start && _capture_end) {
601 /* start and end of capture both occur within the data we are writing,
602 so do both crossfades.
605 _capture_start = false;
606 _capture_end = false;
608 /* move to the correct location place */
609 file_pos = capture_start_frame - _timeline_position;
612 framecnt_t subcnt = cnt / 2;
613 framecnt_t ofilepos = file_pos;
616 if (crossfade (data, subcnt, 1) != subcnt) {
621 Sample * tmpdata = data + subcnt;
624 subcnt = cnt - subcnt;
625 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
629 file_pos = ofilepos; // adjusted below
631 } else if (_capture_start) {
633 /* start of capture both occur within the data we are writing,
637 _capture_start = false;
638 _capture_end = false;
640 /* move to the correct location place */
641 file_pos = capture_start_frame - _timeline_position;
643 if (crossfade (data, cnt, 1) != cnt) {
647 } else if (_capture_end) {
649 /* end of capture both occur within the data we are writing,
653 _capture_start = false;
654 _capture_end = false;
656 if (crossfade (data, cnt, 0) != cnt) {
662 /* in the middle of recording */
664 if (write_float (data, file_pos, cnt) != cnt) {
669 update_length (file_pos + cnt);
671 if (_build_peakfiles) {
672 compute_and_write_peaks (data, file_pos, cnt, true, true);
681 SndFileSource::update_header (framepos_t when, struct tm& now, time_t tnow)
683 set_timeline_position (when);
685 if (_flags & Broadcast) {
686 if (setup_broadcast_info (when, now, tnow)) {
691 return flush_header ();
695 SndFileSource::flush_header ()
698 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
703 error << string_compose (_("could not allocate file %1 to write header"), _path) << endmsg;
707 int const r = sf_command (_sndfile, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE;
713 SndFileSource::flush ()
716 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
721 error << string_compose (_("could not allocate file %1 to flush contents"), _path) << endmsg;
725 // Hopefully everything OK
726 sf_write_sync (_sndfile);
730 SndFileSource::setup_broadcast_info (framepos_t /*when*/, struct tm& now, time_t /*tnow*/)
733 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
738 warning << string_compose (_("attempt to set BWF info for an un-opened audio file source (%1)"), _path) << endmsg;
742 if (!(_flags & Broadcast) || !_broadcast_info) {
746 _broadcast_info->set_originator_ref_from_session (_session);
747 _broadcast_info->set_origination_time (&now);
749 /* now update header position taking header offset into account */
751 set_header_timeline_position ();
757 SndFileSource::set_header_timeline_position ()
759 if (!(_flags & Broadcast)) {
762 assert (_broadcast_info);
764 _broadcast_info->set_time_reference (_timeline_position);
766 if (_sndfile == 0 || !_broadcast_info->write_to_file (_sndfile)) {
767 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
768 _path, _broadcast_info->get_error())
770 _flags = Flag (_flags & ~Broadcast);
771 delete _broadcast_info;
777 SndFileSource::write_float (Sample* data, framepos_t frame_pos, framecnt_t cnt)
779 if ((_info.format & SF_FORMAT_TYPEMASK ) == SF_FORMAT_FLAC) {
780 assert (_length == frame_pos);
782 else if (_sndfile == 0 || sf_seek (_sndfile, frame_pos, SEEK_SET|SFM_WRITE) < 0) {
784 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
785 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3)"), _path, frame_pos, errbuf) << endmsg;
789 if (sf_writef_float (_sndfile, data, cnt) != (ssize_t) cnt) {
797 SndFileSource::natural_position() const
799 return _timeline_position;
803 SndFileSource::set_destructive (bool yn)
806 _flags = Flag (_flags | Writable | Destructive);
808 xfade_buf = new Sample[xfade_frames];
810 clear_capture_marks ();
811 _timeline_position = header_position_offset;
813 _flags = Flag (_flags & ~Destructive);
814 _timeline_position = 0;
815 /* leave xfade buf alone in case we need it again later */
822 SndFileSource::clear_capture_marks ()
824 _capture_start = false;
825 _capture_end = false;
828 /** @param pos Capture start position in session frames */
830 SndFileSource::mark_capture_start (framepos_t pos)
833 if (pos < _timeline_position) {
834 _capture_start = false;
836 _capture_start = true;
837 capture_start_frame = pos;
843 SndFileSource::mark_capture_end()
851 SndFileSource::crossfade (Sample* data, framecnt_t cnt, int fade_in)
853 framecnt_t xfade = min (xfade_frames, cnt);
854 framecnt_t nofade = cnt - xfade;
855 Sample* fade_data = 0;
856 framepos_t fade_position = 0; // in frames
861 fade_position = file_pos;
864 fade_position = file_pos + nofade;
865 fade_data = data + nofade;
868 if (fade_position > _length) {
870 /* read starts beyond end of data, just memset to zero */
874 } else if (fade_position + xfade > _length) {
876 /* read ends beyond end of data, read some, memset the rest */
878 file_cnt = _length - fade_position;
882 /* read is entirely within data */
889 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
890 if (retval >= 0 && errno == EAGAIN) {
891 /* XXX - can we really trust that errno is meaningful here? yes POSIX, i'm talking to you.
892 * short or no data there */
893 memset (xfade_buf, 0, xfade * sizeof(Sample));
895 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
901 if (file_cnt != xfade) {
902 framecnt_t delta = xfade - file_cnt;
903 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
906 if (nofade && !fade_in) {
907 if (write_float (data, file_pos, nofade) != nofade) {
908 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
913 if (xfade == xfade_frames) {
917 /* use the standard xfade curve */
921 /* fade new material in */
923 for (n = 0; n < xfade; ++n) {
924 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
930 /* fade new material out */
932 for (n = 0; n < xfade; ++n) {
933 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
937 } else if (xfade < xfade_frames) {
939 std::vector<gain_t> in(xfade);
940 std::vector<gain_t> out(xfade);
942 /* short xfade, compute custom curve */
944 compute_equal_power_fades (xfade, &in[0], &out[0]);
946 for (framecnt_t n = 0; n < xfade; ++n) {
947 xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
952 /* long xfade length, has to be computed across several calls */
957 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
958 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
963 if (fade_in && nofade) {
964 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
965 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
974 SndFileSource::last_capture_start_frame () const
977 return capture_start_frame;
984 SndFileSource::handle_header_position_change ()
987 if ( _length != 0 ) {
988 error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
989 //in the future, pop up a dialog here that allows user to regenerate file with new start offset
990 } else if (writable()) {
991 _timeline_position = header_position_offset;
992 set_header_timeline_position (); //this will get flushed if/when the file is recorded to
998 SndFileSource::setup_standard_crossfades (Session const & s, framecnt_t rate)
1000 /* This static method is assumed to have been called by the Session
1001 before any DFS's are created.
1004 xfade_frames = (framecnt_t) floor ((s.config.get_destructive_xfade_msecs () / 1000.0) * rate);
1006 delete [] out_coefficient;
1007 delete [] in_coefficient;
1009 out_coefficient = new gain_t[xfade_frames];
1010 in_coefficient = new gain_t[xfade_frames];
1012 compute_equal_power_fades (xfade_frames, in_coefficient, out_coefficient);
1016 SndFileSource::set_timeline_position (framepos_t pos)
1018 // destructive track timeline postion does not change
1019 // except at instantion or when header_position_offset
1020 // (session start) changes
1022 if (!destructive()) {
1023 AudioFileSource::set_timeline_position (pos);
1028 SndFileSource::get_soundfile_info (const string& path, SoundFileInfo& info, string& error_msg)
1032 BroadcastInfo binfo;
1034 sf_info.format = 0; // libsndfile says to clear this before sf_open().
1036 if (path.empty() || Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
1040 #ifdef PLATFORM_WINDOWS
1041 int fd = g_open (path.c_str(), O_RDONLY, 0444);
1043 int fd = ::open (path.c_str(), O_RDONLY, 0444);
1047 error << string_compose ( _("SndFileSource: cannot open file \"%1\" for reading"), path)
1051 if ((sf = sf_open_fd (fd, SFM_READ, &sf_info, true)) == 0) {
1053 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
1057 info.samplerate = sf_info.samplerate;
1058 info.channels = sf_info.channels;
1059 info.length = sf_info.frames;
1061 string major = sndfile_major_format(sf_info.format);
1062 string minor = sndfile_minor_format(sf_info.format);
1064 if (major.length() + minor.length() < 16) { /* arbitrary */
1065 info.format_name = string_compose("%1/%2", major, minor);
1067 info.format_name = string_compose("%1\n%2", major, minor);
1070 info.timecode = binfo.load_from_file (sf) ? binfo.get_time_reference() : 0;
1078 SndFileSource::one_of_several_channels () const
1080 return _info.channels > 1;
1084 SndFileSource::clamped_at_unity () const
1086 int const type = _info.format & SF_FORMAT_TYPEMASK;
1087 int const sub = _info.format & SF_FORMAT_SUBMASK;
1088 /* XXX: this may not be the full list of formats that are unclamped */
1089 return (sub != SF_FORMAT_FLOAT && sub != SF_FORMAT_DOUBLE && type != SF_FORMAT_OGG);
1093 SndFileSource::file_closed ()
1095 /* stupid libsndfile updated the headers on close,
1096 so touch the peakfile if it exists and has data
1097 to make sure its time is as new as the audio
1105 SndFileSource::set_path (const string& p)
1107 FileSource::set_path (p);