Reset fades on regions copied from time ranges in other regions (#4035).
[ardour2.git] / libs / ardour / sndfilesource.cc
blob8fad3b37059873a1c1d291ada94d4859d7ccb509
1 /*
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.
20 #ifdef WAF_BUILD
21 #include "libardour-config.h"
22 #endif
24 #include <cstring>
25 #include <cerrno>
26 #include <climits>
27 #include <cstdarg>
29 #include <pwd.h>
30 #include <sys/utsname.h>
31 #include <sys/stat.h>
33 #include <glibmm/miscutils.h>
35 #include "ardour/sndfilesource.h"
36 #include "ardour/sndfile_helpers.h"
37 #include "ardour/utils.h"
38 #include "ardour/version.h"
39 #include "ardour/rc_configuration.h"
40 #include "ardour/session.h"
42 #include "i18n.h"
44 using namespace std;
45 using namespace ARDOUR;
46 using namespace PBD;
47 using std::string;
49 gain_t* SndFileSource::out_coefficient = 0;
50 gain_t* SndFileSource::in_coefficient = 0;
51 framecnt_t SndFileSource::xfade_frames = 64;
52 const Source::Flag SndFileSource::default_writable_flags = Source::Flag (
53 Source::Writable |
54 Source::Removable |
55 Source::RemovableIfEmpty |
56 Source::CanRename );
58 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
59 : Source(s, node)
60 , AudioFileSource (s, node)
62 init_sndfile ();
64 if (open()) {
65 throw failed_constructor ();
69 /** Files created this way are never writable or removable */
70 SndFileSource::SndFileSource (Session& s, const string& path, int chn, Flag flags)
71 : Source(s, DataType::AUDIO, path, flags)
72 /* note that the origin of an external file is itself */
73 , AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
75 _channel = chn;
77 init_sndfile ();
79 if (open()) {
80 throw failed_constructor ();
84 /** This constructor is used to construct new files, not open existing ones. */
85 SndFileSource::SndFileSource (Session& s, const string& path, const string& origin,
86 SampleFormat sfmt, HeaderFormat hf, framecnt_t rate, Flag flags)
87 : Source(s, DataType::AUDIO, path, flags)
88 , AudioFileSource (s, path, origin, flags, sfmt, hf)
90 int fmt = 0;
92 init_sndfile ();
94 _file_is_new = true;
96 switch (hf) {
97 case CAF:
98 fmt = SF_FORMAT_CAF;
99 _flags = Flag (_flags & ~Broadcast);
100 break;
102 case AIFF:
103 fmt = SF_FORMAT_AIFF;
104 _flags = Flag (_flags & ~Broadcast);
105 break;
107 case BWF:
108 fmt = SF_FORMAT_WAV;
109 _flags = Flag (_flags | Broadcast);
110 break;
112 case WAVE:
113 fmt = SF_FORMAT_WAV;
114 _flags = Flag (_flags & ~Broadcast);
115 break;
117 case WAVE64:
118 fmt = SF_FORMAT_W64;
119 _flags = Flag (_flags & ~Broadcast);
120 break;
122 default:
123 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
124 /*NOTREACHED*/
125 break;
129 switch (sfmt) {
130 case FormatFloat:
131 fmt |= SF_FORMAT_FLOAT;
132 break;
134 case FormatInt24:
135 fmt |= SF_FORMAT_PCM_24;
136 break;
138 case FormatInt16:
139 fmt |= SF_FORMAT_PCM_16;
140 break;
143 _info.channels = 1;
144 _info.samplerate = rate;
145 _info.format = fmt;
147 /* do not open the file here - do that in write_unlocked() as needed
151 void
152 SndFileSource::init_sndfile ()
154 string file;
156 _descriptor = 0;
158 // lets try to keep the object initalizations here at the top
159 xfade_buf = 0;
160 _broadcast_info = 0;
162 /* although libsndfile says we don't need to set this,
163 valgrind and source code shows us that we do.
166 memset (&_info, 0, sizeof(_info));
168 _capture_start = false;
169 _capture_end = false;
170 file_pos = 0;
172 if (destructive()) {
173 xfade_buf = new Sample[xfade_frames];
174 _timeline_position = header_position_offset;
177 AudioFileSource::HeaderPositionOffsetChanged.connect_same_thread (header_position_connection, boost::bind (&SndFileSource::handle_header_position_change, this));
181 SndFileSource::open ()
183 _descriptor = new SndFileDescriptor (_path, writable(), &_info);
184 _descriptor->Closed.connect_same_thread (file_manager_connection, boost::bind (&SndFileSource::file_closed, this));
185 SNDFILE* sf = _descriptor->allocate ();
187 if (sf == 0) {
188 char errbuf[256];
189 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
190 #ifndef HAVE_COREAUDIO
191 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
192 so we don't want to see this message.
195 cerr << "failed to open " << _path << " with name " << _name << endl;
197 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"),
198 _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
199 #endif
200 return -1;
203 if (_channel >= _info.channels) {
204 #ifndef HAVE_COREAUDIO
205 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
206 #endif
207 delete _descriptor;
208 _descriptor = 0;
209 return -1;
212 _length = _info.frames;
214 if (!_broadcast_info) {
215 _broadcast_info = new BroadcastInfo;
218 bool bwf_info_exists = _broadcast_info->load_from_file (sf);
220 set_timeline_position (bwf_info_exists ? _broadcast_info->get_time_reference() : header_position_offset);
222 if (_length != 0 && !bwf_info_exists) {
223 delete _broadcast_info;
224 _broadcast_info = 0;
225 _flags = Flag (_flags & ~Broadcast);
228 if (writable()) {
229 sf_command (sf, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
231 if (_flags & Broadcast) {
233 if (!_broadcast_info) {
234 _broadcast_info = new BroadcastInfo;
237 _broadcast_info->set_from_session (_session, header_position_offset);
238 _broadcast_info->set_description (string_compose ("BWF %1", _name));
240 if (!_broadcast_info->write_to_file (sf)) {
241 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
242 _path, _broadcast_info->get_error())
243 << endmsg;
244 _flags = Flag (_flags & ~Broadcast);
245 delete _broadcast_info;
246 _broadcast_info = 0;
251 _descriptor->release ();
252 _open = true;
253 return 0;
256 SndFileSource::~SndFileSource ()
258 delete _descriptor;
259 delete _broadcast_info;
260 delete [] xfade_buf;
263 float
264 SndFileSource::sample_rate () const
266 return _info.samplerate;
269 framecnt_t
270 SndFileSource::read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const
272 int32_t nread;
273 float *ptr;
274 uint32_t real_cnt;
275 framepos_t file_cnt;
277 if (writable() && !_open) {
278 /* file has not been opened yet - nothing written to it */
279 memset (dst, 0, sizeof (Sample) * cnt);
280 return cnt;
283 SNDFILE* sf = _descriptor->allocate ();
285 if (sf == 0) {
286 error << string_compose (_("could not allocate file %1 for reading."), _path) << endmsg;
287 return 0;
290 if (start > _length) {
292 /* read starts beyond end of data, just memset to zero */
294 file_cnt = 0;
296 } else if (start + cnt > _length) {
298 /* read ends beyond end of data, read some, memset the rest */
300 file_cnt = _length - start;
302 } else {
304 /* read is entirely within data */
306 file_cnt = cnt;
309 if (file_cnt != cnt) {
310 framepos_t delta = cnt - file_cnt;
311 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
314 if (file_cnt) {
316 if (sf_seek (sf, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
317 char errbuf[256];
318 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
319 error << string_compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.val().substr (1), errbuf) << endmsg;
320 _descriptor->release ();
321 return 0;
324 if (_info.channels == 1) {
325 framecnt_t ret = sf_read_float (sf, dst, file_cnt);
326 _read_data_count = ret * sizeof(float);
327 if (ret != file_cnt) {
328 char errbuf[256];
329 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
330 error << string_compose(_("SndFileSource: @ %1 could not read %2 within %3 (%4) (len = %5)"), start, file_cnt, _name.val().substr (1), errbuf, _length) << endl;
332 _descriptor->release ();
333 return ret;
337 real_cnt = cnt * _info.channels;
339 Sample* interleave_buf = get_interleave_buffer (real_cnt);
341 nread = sf_read_float (sf, interleave_buf, real_cnt);
342 ptr = interleave_buf + _channel;
343 nread /= _info.channels;
345 /* stride through the interleaved data */
347 for (int32_t n = 0; n < nread; ++n) {
348 dst[n] = *ptr;
349 ptr += _info.channels;
352 _read_data_count = cnt * sizeof(float);
354 _descriptor->release ();
355 return nread;
358 framecnt_t
359 SndFileSource::write_unlocked (Sample *data, framecnt_t cnt)
361 if (!_open && open()) {
362 return 0; // failure
365 if (destructive()) {
366 return destructive_write_unlocked (data, cnt);
367 } else {
368 return nondestructive_write_unlocked (data, cnt);
372 framecnt_t
373 SndFileSource::nondestructive_write_unlocked (Sample *data, framecnt_t cnt)
375 if (!writable()) {
376 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
377 return 0;
380 if (_info.channels != 1) {
381 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
382 /*NOTREACHED*/
383 return 0;
386 framecnt_t oldlen;
387 int32_t frame_pos = _length;
389 if (write_float (data, frame_pos, cnt) != cnt) {
390 return 0;
393 oldlen = _length;
394 update_length (oldlen, cnt);
396 if (_build_peakfiles) {
397 compute_and_write_peaks (data, frame_pos, cnt, false, true);
400 _write_data_count = cnt;
402 return cnt;
405 framecnt_t
406 SndFileSource::destructive_write_unlocked (Sample* data, framecnt_t cnt)
408 framepos_t old_file_pos;
410 if (!writable()) {
411 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
412 return 0;
415 if (_capture_start && _capture_end) {
417 /* start and end of capture both occur within the data we are writing,
418 so do both crossfades.
421 _capture_start = false;
422 _capture_end = false;
424 /* move to the correct location place */
425 file_pos = capture_start_frame - _timeline_position;
427 // split cnt in half
428 framecnt_t subcnt = cnt / 2;
429 framecnt_t ofilepos = file_pos;
431 // fade in
432 if (crossfade (data, subcnt, 1) != subcnt) {
433 return 0;
436 file_pos += subcnt;
437 Sample * tmpdata = data + subcnt;
439 // fade out
440 subcnt = cnt - subcnt;
441 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
442 return 0;
445 file_pos = ofilepos; // adjusted below
447 } else if (_capture_start) {
449 /* start of capture both occur within the data we are writing,
450 so do the fade in
453 _capture_start = false;
454 _capture_end = false;
456 /* move to the correct location place */
457 file_pos = capture_start_frame - _timeline_position;
459 if (crossfade (data, cnt, 1) != cnt) {
460 return 0;
463 } else if (_capture_end) {
465 /* end of capture both occur within the data we are writing,
466 so do the fade out
469 _capture_start = false;
470 _capture_end = false;
472 if (crossfade (data, cnt, 0) != cnt) {
473 return 0;
476 } else {
478 /* in the middle of recording */
480 if (write_float (data, file_pos, cnt) != cnt) {
481 return 0;
485 old_file_pos = file_pos;
486 update_length (file_pos, cnt);
488 if (_build_peakfiles) {
489 compute_and_write_peaks (data, file_pos, cnt, false, true);
492 file_pos += cnt;
494 return cnt;
498 SndFileSource::update_header (framepos_t when, struct tm& now, time_t tnow)
500 set_timeline_position (when);
502 if (_flags & Broadcast) {
503 if (setup_broadcast_info (when, now, tnow)) {
504 return -1;
508 return flush_header ();
512 SndFileSource::flush_header ()
514 if (!writable()) {
515 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
516 return -1;
519 if (!_open) {
520 warning << string_compose (_("attempt to flush an un-opened audio file source (%1)"), _path) << endmsg;
521 return -1;
524 SNDFILE* sf = _descriptor->allocate ();
525 if (sf == 0) {
526 error << string_compose (_("could not allocate file %1 to write header"), _path) << endmsg;
527 return -1;
530 int const r = sf_command (sf, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE;
531 _descriptor->release ();
533 return r;
537 SndFileSource::setup_broadcast_info (framepos_t /*when*/, struct tm& now, time_t /*tnow*/)
539 if (!writable()) {
540 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
541 return -1;
544 if (!_open) {
545 warning << string_compose (_("attempt to set BWF info for an un-opened audio file source (%1)"), _path) << endmsg;
546 return -1;
549 if (!(_flags & Broadcast)) {
550 return 0;
553 _broadcast_info->set_originator_ref_from_session (_session);
554 _broadcast_info->set_origination_time (&now);
556 /* now update header position taking header offset into account */
558 set_header_timeline_position ();
560 SNDFILE* sf = _descriptor->allocate ();
562 if (sf == 0 || !_broadcast_info->write_to_file (sf)) {
563 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
564 _path, _broadcast_info->get_error())
565 << endmsg;
566 _flags = Flag (_flags & ~Broadcast);
567 delete _broadcast_info;
568 _broadcast_info = 0;
571 _descriptor->release ();
572 return 0;
575 void
576 SndFileSource::set_header_timeline_position ()
578 if (!(_flags & Broadcast)) {
579 return;
582 _broadcast_info->set_time_reference (_timeline_position);
584 SNDFILE* sf = _descriptor->allocate ();
586 if (sf == 0 || !_broadcast_info->write_to_file (sf)) {
587 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
588 _path, _broadcast_info->get_error())
589 << endmsg;
590 _flags = Flag (_flags & ~Broadcast);
591 delete _broadcast_info;
592 _broadcast_info = 0;
595 _descriptor->release ();
598 framecnt_t
599 SndFileSource::write_float (Sample* data, framepos_t frame_pos, framecnt_t cnt)
601 SNDFILE* sf = _descriptor->allocate ();
603 if (sf == 0 || sf_seek (sf, frame_pos, SEEK_SET|SFM_WRITE) < 0) {
604 char errbuf[256];
605 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
606 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3"), _path, frame_pos, errbuf) << endmsg;
607 _descriptor->release ();
608 return 0;
611 if (sf_writef_float (sf, data, cnt) != (ssize_t) cnt) {
612 _descriptor->release ();
613 return 0;
616 _descriptor->release ();
617 return cnt;
620 framepos_t
621 SndFileSource::natural_position() const
623 return _timeline_position;
626 bool
627 SndFileSource::set_destructive (bool yn)
629 if (yn) {
630 _flags = Flag (_flags | Writable | Destructive);
631 if (!xfade_buf) {
632 xfade_buf = new Sample[xfade_frames];
634 clear_capture_marks ();
635 _timeline_position = header_position_offset;
636 } else {
637 _flags = Flag (_flags & ~Destructive);
638 _timeline_position = 0;
639 /* leave xfade buf alone in case we need it again later */
642 return true;
645 void
646 SndFileSource::clear_capture_marks ()
648 _capture_start = false;
649 _capture_end = false;
652 /** @param pos Capture start position in session frames */
653 void
654 SndFileSource::mark_capture_start (framepos_t pos)
656 if (destructive()) {
657 if (pos < _timeline_position) {
658 _capture_start = false;
659 } else {
660 _capture_start = true;
661 capture_start_frame = pos;
666 void
667 SndFileSource::mark_capture_end()
669 if (destructive()) {
670 _capture_end = true;
674 framecnt_t
675 SndFileSource::crossfade (Sample* data, framecnt_t cnt, int fade_in)
677 framecnt_t xfade = min (xfade_frames, cnt);
678 framecnt_t nofade = cnt - xfade;
679 Sample* fade_data = 0;
680 framepos_t fade_position = 0; // in frames
681 ssize_t retval;
682 framecnt_t file_cnt;
684 if (fade_in) {
685 fade_position = file_pos;
686 fade_data = data;
687 } else {
688 fade_position = file_pos + nofade;
689 fade_data = data + nofade;
692 if (fade_position > _length) {
694 /* read starts beyond end of data, just memset to zero */
696 file_cnt = 0;
698 } else if (fade_position + xfade > _length) {
700 /* read ends beyond end of data, read some, memset the rest */
702 file_cnt = _length - fade_position;
704 } else {
706 /* read is entirely within data */
708 file_cnt = xfade;
711 if (file_cnt) {
713 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
714 if (retval >= 0 && errno == EAGAIN) {
715 /* XXX - can we really trust that errno is meaningful here? yes POSIX, i'm talking to you.
716 * short or no data there */
717 memset (xfade_buf, 0, xfade * sizeof(Sample));
718 } else {
719 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
720 return 0;
725 if (file_cnt != xfade) {
726 framecnt_t delta = xfade - file_cnt;
727 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
730 if (nofade && !fade_in) {
731 if (write_float (data, file_pos, nofade) != nofade) {
732 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
733 return 0;
737 if (xfade == xfade_frames) {
739 framecnt_t n;
741 /* use the standard xfade curve */
743 if (fade_in) {
745 /* fade new material in */
747 for (n = 0; n < xfade; ++n) {
748 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
751 } else {
754 /* fade new material out */
756 for (n = 0; n < xfade; ++n) {
757 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
761 } else if (xfade < xfade_frames) {
763 gain_t in[xfade];
764 gain_t out[xfade];
766 /* short xfade, compute custom curve */
768 compute_equal_power_fades (xfade, in, out);
770 for (framecnt_t n = 0; n < xfade; ++n) {
771 xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
774 } else if (xfade) {
776 /* long xfade length, has to be computed across several calls */
780 if (xfade) {
781 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
782 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
783 return 0;
787 if (fade_in && nofade) {
788 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
789 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
790 return 0;
794 return cnt;
797 framepos_t
798 SndFileSource::last_capture_start_frame () const
800 if (destructive()) {
801 return capture_start_frame;
802 } else {
803 return 0;
807 void
808 SndFileSource::handle_header_position_change ()
810 if (destructive()) {
811 if ( _length != 0 ) {
812 error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
813 //in the future, pop up a dialog here that allows user to regenerate file with new start offset
814 } else if (writable()) {
815 _timeline_position = header_position_offset;
816 set_header_timeline_position (); //this will get flushed if/when the file is recorded to
821 void
822 SndFileSource::setup_standard_crossfades (Session const & s, framecnt_t rate)
824 /* This static method is assumed to have been called by the Session
825 before any DFS's are created.
828 xfade_frames = (framecnt_t) floor ((s.config.get_destructive_xfade_msecs () / 1000.0) * rate);
830 delete [] out_coefficient;
831 delete [] in_coefficient;
833 out_coefficient = new gain_t[xfade_frames];
834 in_coefficient = new gain_t[xfade_frames];
836 compute_equal_power_fades (xfade_frames, in_coefficient, out_coefficient);
839 void
840 SndFileSource::set_timeline_position (framepos_t pos)
842 // destructive track timeline postion does not change
843 // except at instantion or when header_position_offset
844 // (session start) changes
846 if (!destructive()) {
847 AudioFileSource::set_timeline_position (pos);
852 SndFileSource::get_soundfile_info (const string& path, SoundFileInfo& info, string& error_msg)
854 SNDFILE *sf;
855 SF_INFO sf_info;
856 BroadcastInfo binfo;
858 sf_info.format = 0; // libsndfile says to clear this before sf_open().
860 if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
861 char errbuf[256];
862 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
863 return false;
866 info.samplerate = sf_info.samplerate;
867 info.channels = sf_info.channels;
868 info.length = sf_info.frames;
870 string major = sndfile_major_format(sf_info.format);
871 string minor = sndfile_minor_format(sf_info.format);
873 if (major.length() + minor.length() < 16) { /* arbitrary */
874 info.format_name = string_compose("%1/%2", major, minor);
875 } else {
876 info.format_name = string_compose("%1\n%2", major, minor);
879 info.timecode = binfo.load_from_file (sf) ? binfo.get_time_reference() : 0;
881 sf_close (sf);
883 return true;
886 bool
887 SndFileSource::one_of_several_channels () const
889 return _info.channels > 1;
892 bool
893 SndFileSource::clamped_at_unity () const
895 int const type = _info.format & SF_FORMAT_TYPEMASK;
896 int const sub = _info.format & SF_FORMAT_SUBMASK;
897 /* XXX: this may not be the full list of formats that are unclamped */
898 return (sub != SF_FORMAT_FLOAT && sub != SF_FORMAT_DOUBLE && type != SF_FORMAT_OGG);
901 void
902 SndFileSource::file_closed ()
904 /* stupid libsndfile updated the headers on close,
905 so touch the peakfile if it exists and has data
906 to make sure its time is as new as the audio
907 file.
910 touch_peakfile ();
913 void
914 SndFileSource::set_path (const string& p)
916 FileSource::set_path (p);
918 if (_descriptor) {
919 _descriptor->set_path (_path);