fully implement and deploy explicit x-thread signal connection syntax (testing comes...
[ardour.git] / libs / ardour / sndfilesource.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "libardour-config.h"
22 #endif
23
24 #include <cstring>
25 #include <cerrno>
26 #include <climits>
27 #include <cstdarg>
28
29 #include <pwd.h>
30 #include <sys/utsname.h>
31 #include <sys/stat.h>
32
33 #include <glibmm/miscutils.h>
34
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"
41
42 #include "i18n.h"
43
44 using namespace std;
45 using namespace ARDOUR;
46 using namespace PBD;
47 using Glib::ustring;
48
49 gain_t* SndFileSource::out_coefficient = 0;
50 gain_t* SndFileSource::in_coefficient = 0;
51 nframes_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 );
57
58 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
59         : Source(s, node)
60         , AudioFileSource (s, node)
61 {
62         init_sndfile ();
63
64         if (open()) {
65                 throw failed_constructor ();
66         }
67 }
68
69 /** Files created this way are never writable or removable */
70 SndFileSource::SndFileSource (Session& s, const ustring& path, int chn, Flag flags)
71         : Source(s, DataType::AUDIO, path, flags)
72         , AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
73 {
74         _channel = chn;
75
76         init_sndfile ();
77
78         if (open()) {
79                 throw failed_constructor ();
80         }
81 }
82
83 /** This constructor is used to construct new files, not open existing ones. */
84 SndFileSource::SndFileSource (Session& s, const ustring& path, 
85                 SampleFormat sfmt, HeaderFormat hf, nframes_t rate, Flag flags)
86         : Source(s, DataType::AUDIO, path, flags)
87         , AudioFileSource (s, path, flags, sfmt, hf)
88 {
89         int fmt = 0;
90
91         init_sndfile ();
92
93         _file_is_new = true;
94
95         switch (hf) {
96         case CAF:
97                 fmt = SF_FORMAT_CAF;
98                 _flags = Flag (_flags & ~Broadcast);
99                 break;
100
101         case AIFF:
102                 fmt = SF_FORMAT_AIFF;
103                 _flags = Flag (_flags & ~Broadcast);
104                 break;
105
106         case BWF:
107                 fmt = SF_FORMAT_WAV;
108                 _flags = Flag (_flags | Broadcast);
109                 break;
110
111         case WAVE:
112                 fmt = SF_FORMAT_WAV;
113                 _flags = Flag (_flags & ~Broadcast);
114                 break;
115
116         case WAVE64:
117                 fmt = SF_FORMAT_W64;
118                 _flags = Flag (_flags & ~Broadcast);
119                 break;
120
121         default:
122                 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
123                 /*NOTREACHED*/
124                 break;
125
126         }
127
128         switch (sfmt) {
129         case FormatFloat:
130                 fmt |= SF_FORMAT_FLOAT;
131                 break;
132
133         case FormatInt24:
134                 fmt |= SF_FORMAT_PCM_24;
135                 break;
136
137         case FormatInt16:
138                 fmt |= SF_FORMAT_PCM_16;
139                 break;
140         }
141
142         _info.channels = 1;
143         _info.samplerate = rate;
144         _info.format = fmt;
145
146         if (open()) {
147                 throw failed_constructor();
148         }
149
150         if (writable() && (_flags & Broadcast)) {
151
152                 if (!_broadcast_info) {
153                         _broadcast_info = new BroadcastInfo;
154                 }
155
156                 _broadcast_info->set_from_session (s, header_position_offset);
157                 _broadcast_info->set_description (string_compose ("BWF %1", _name));
158
159                 if (!_broadcast_info->write_to_file (sf)) {
160                         error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
161                                                    _path, _broadcast_info->get_error())
162                               << endmsg;
163                         _flags = Flag (_flags & ~Broadcast);
164                         delete _broadcast_info;
165                         _broadcast_info = 0;
166                 }
167         }
168 }
169
170 void
171 SndFileSource::init_sndfile ()
172 {
173         ustring file;
174
175         // lets try to keep the object initalizations here at the top
176         xfade_buf = 0;
177         sf = 0;
178         _broadcast_info = 0;
179
180         /* although libsndfile says we don't need to set this,
181            valgrind and source code shows us that we do.
182         */
183
184         memset (&_info, 0, sizeof(_info));
185
186         _capture_start = false;
187         _capture_end = false;
188         file_pos = 0;
189
190         if (destructive()) {
191                 xfade_buf = new Sample[xfade_frames];
192                 _timeline_position = header_position_offset;
193         }
194
195         AudioFileSource::HeaderPositionOffsetChanged.connect_same_thread (header_position_connection, boost::bind (&SndFileSource::handle_header_position_change, this));
196 }
197
198 int
199 SndFileSource::open ()
200 {
201         if ((sf = sf_open (_path.c_str(), (writable() ? SFM_RDWR : SFM_READ), &_info)) == 0) {
202                 char errbuf[256];
203                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
204 #ifndef HAVE_COREAUDIO
205                 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
206                    so we don't want to see this message.
207                 */
208
209                 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"),
210                                         _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
211 #endif
212                 return -1;
213         }
214
215         if (_channel >= _info.channels) {
216 #ifndef HAVE_COREAUDIO
217                 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
218 #endif
219                 sf_close (sf);
220                 sf = 0;
221                 return -1;
222         }
223
224         _length = _info.frames;
225
226         if (!_broadcast_info) {
227                 _broadcast_info = new BroadcastInfo;
228         }
229
230         bool bwf_info_exists = _broadcast_info->load_from_file (sf);
231
232         set_timeline_position (bwf_info_exists ? _broadcast_info->get_time_reference() : header_position_offset);
233
234         if (_length != 0 && !bwf_info_exists) {
235                 delete _broadcast_info;
236                 _broadcast_info = 0;
237                 _flags = Flag (_flags & ~Broadcast);
238         }
239
240         if (writable()) {
241                 sf_command (sf, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
242         }
243
244         return 0;
245 }
246
247 SndFileSource::~SndFileSource ()
248 {
249         drop_references ();
250
251         if (sf) {
252                 sf_close (sf);
253                 sf = 0;
254
255                 /* stupid libsndfile updated the headers on close,
256                    so touch the peakfile if it exists and has data
257                    to make sure its time is as new as the audio
258                    file.
259                 */
260
261                 touch_peakfile ();
262         }
263
264         delete _broadcast_info;
265         delete [] xfade_buf;
266 }
267
268 float
269 SndFileSource::sample_rate () const
270 {
271         return _info.samplerate;
272 }
273
274 nframes_t
275 SndFileSource::read_unlocked (Sample *dst, sframes_t start, nframes_t cnt) const
276 {
277         int32_t nread;
278         float *ptr;
279         uint32_t real_cnt;
280         nframes_t file_cnt;
281
282         if (start > _length) {
283
284                 /* read starts beyond end of data, just memset to zero */
285
286                 file_cnt = 0;
287
288         } else if (start + cnt > _length) {
289
290                 /* read ends beyond end of data, read some, memset the rest */
291
292                 file_cnt = _length - start;
293
294         } else {
295
296                 /* read is entirely within data */
297
298                 file_cnt = cnt;
299         }
300
301         if (file_cnt != cnt) {
302                 nframes_t delta = cnt - file_cnt;
303                 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
304         }
305
306         if (file_cnt) {
307
308                 if (sf_seek (sf, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
309                         char errbuf[256];
310                         sf_error_str (0, errbuf, sizeof (errbuf) - 1);
311                         error << string_compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.substr (1), errbuf) << endmsg;
312                         return 0;
313                 }
314
315                 if (_info.channels == 1) {
316                         nframes_t ret = sf_read_float (sf, dst, file_cnt);
317                         _read_data_count = ret * sizeof(float);
318                         if (ret != file_cnt) {
319                                 char errbuf[256];
320                                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
321                                 cerr << string_compose(_("SndFileSource: @ %1 could not read %2 within %3 (%4) (len = %5)"), start, file_cnt, _name.substr (1), errbuf, _length) << endl;
322                         }
323                         return ret;
324                 }
325         }
326
327         real_cnt = cnt * _info.channels;
328
329         Sample* interleave_buf = get_interleave_buffer (real_cnt);
330
331         nread = sf_read_float (sf, interleave_buf, real_cnt);
332         ptr = interleave_buf + _channel;
333         nread /= _info.channels;
334
335         /* stride through the interleaved data */
336
337         for (int32_t n = 0; n < nread; ++n) {
338                 dst[n] = *ptr;
339                 ptr += _info.channels;
340         }
341
342         _read_data_count = cnt * sizeof(float);
343
344         return nread;
345 }
346
347 nframes_t
348 SndFileSource::write_unlocked (Sample *data, nframes_t cnt)
349 {
350         if (destructive()) {
351                 return destructive_write_unlocked (data, cnt);
352         } else {
353                 return nondestructive_write_unlocked (data, cnt);
354         }
355 }
356
357 nframes_t
358 SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
359 {
360         if (!writable()) {
361                 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
362                 return 0;
363         }
364
365         if (_info.channels != 1) {
366                 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
367                 /*NOTREACHED*/
368                 return 0;
369         }
370
371         nframes_t oldlen;
372         int32_t frame_pos = _length;
373
374         if (write_float (data, frame_pos, cnt) != cnt) {
375                 return 0;
376         }
377
378         oldlen = _length;
379         update_length (oldlen, cnt);
380
381         if (_build_peakfiles) {
382                 compute_and_write_peaks (data, frame_pos, cnt, false, true);
383         }
384
385         _write_data_count = cnt;
386
387         return cnt;
388 }
389
390 nframes_t
391 SndFileSource::destructive_write_unlocked (Sample* data, nframes_t cnt)
392 {
393         nframes_t old_file_pos;
394
395         if (!writable()) {
396                 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
397                 return 0;
398         }
399
400         if (_capture_start && _capture_end) {
401
402                 /* start and end of capture both occur within the data we are writing,
403                    so do both crossfades.
404                 */
405
406                 _capture_start = false;
407                 _capture_end = false;
408
409                 /* move to the correct location place */
410                 file_pos = capture_start_frame - _timeline_position;
411
412                 // split cnt in half
413                 nframes_t subcnt = cnt / 2;
414                 nframes_t ofilepos = file_pos;
415
416                 // fade in
417                 if (crossfade (data, subcnt, 1) != subcnt) {
418                         return 0;
419                 }
420
421                 file_pos += subcnt;
422                 Sample * tmpdata = data + subcnt;
423
424                 // fade out
425                 subcnt = cnt - subcnt;
426                 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
427                         return 0;
428                 }
429
430                 file_pos = ofilepos; // adjusted below
431
432         } else if (_capture_start) {
433
434                 /* start of capture both occur within the data we are writing,
435                    so do the fade in
436                 */
437
438                 _capture_start = false;
439                 _capture_end = false;
440
441                 /* move to the correct location place */
442                 file_pos = capture_start_frame - _timeline_position;
443
444                 if (crossfade (data, cnt, 1) != cnt) {
445                         return 0;
446                 }
447
448         } else if (_capture_end) {
449
450                 /* end of capture both occur within the data we are writing,
451                    so do the fade out
452                 */
453
454                 _capture_start = false;
455                 _capture_end = false;
456
457                 if (crossfade (data, cnt, 0) != cnt) {
458                         return 0;
459                 }
460
461         } else {
462
463                 /* in the middle of recording */
464
465                 if (write_float (data, file_pos, cnt) != cnt) {
466                         return 0;
467                 }
468         }
469
470         old_file_pos = file_pos;
471         update_length (file_pos, cnt);
472
473         if (_build_peakfiles) {
474                 compute_and_write_peaks (data, file_pos, cnt, false, true);
475         }
476
477         file_pos += cnt;
478
479         return cnt;
480 }
481
482 int
483 SndFileSource::update_header (sframes_t when, struct tm& now, time_t tnow)
484 {
485         set_timeline_position (when);
486
487         if (_flags & Broadcast) {
488                 if (setup_broadcast_info (when, now, tnow)) {
489                         return -1;
490                 }
491         }
492
493         return flush_header ();
494 }
495
496 int
497 SndFileSource::flush_header ()
498 {
499         if (!writable() || (sf == 0)) {
500                 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
501                 return -1;
502         }
503         return (sf_command (sf, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE);
504 }
505
506 int
507 SndFileSource::setup_broadcast_info (sframes_t /*when*/, struct tm& now, time_t /*tnow*/)
508 {
509         if (!writable()) {
510                 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
511                 return -1;
512         }
513
514         if (!(_flags & Broadcast)) {
515                 return 0;
516         }
517
518         _broadcast_info->set_originator_ref (_session);
519         _broadcast_info->set_origination_time (&now);
520
521         /* now update header position taking header offset into account */
522
523         set_header_timeline_position ();
524
525         if (!_broadcast_info->write_to_file (sf)) {
526                 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
527                                            _path, _broadcast_info->get_error())
528                       << endmsg;
529                 _flags = Flag (_flags & ~Broadcast);
530                 delete _broadcast_info;
531                 _broadcast_info = 0;
532         }
533
534         return 0;
535 }
536
537 void
538 SndFileSource::set_header_timeline_position ()
539 {
540         if (!(_flags & Broadcast)) {
541                 return;
542         }
543
544         _broadcast_info->set_time_reference (_timeline_position);
545
546         if (!_broadcast_info->write_to_file (sf)) {
547                 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
548                                            _path, _broadcast_info->get_error())
549                       << endmsg;
550                 _flags = Flag (_flags & ~Broadcast);
551                 delete _broadcast_info;
552                 _broadcast_info = 0;
553         }
554 }
555
556 nframes_t
557 SndFileSource::write_float (Sample* data, sframes_t frame_pos, nframes_t cnt)
558 {
559         if (sf_seek (sf, frame_pos, SEEK_SET|SFM_WRITE) < 0) {
560                 char errbuf[256];
561                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
562                 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3"), _path, frame_pos, errbuf) << endmsg;
563                 return 0;
564         }
565
566         if (sf_writef_float (sf, data, cnt) != (ssize_t) cnt) {
567                 return 0;
568         }
569
570         return cnt;
571 }
572
573 sframes_t
574 SndFileSource::natural_position() const
575 {
576         return _timeline_position;
577 }
578
579 bool
580 SndFileSource::set_destructive (bool yn)
581 {
582         if (yn) {
583                 _flags = Flag (_flags | Destructive);
584                 if (!xfade_buf) {
585                         xfade_buf = new Sample[xfade_frames];
586                 }
587                 clear_capture_marks ();
588                 _timeline_position = header_position_offset;
589         } else {
590                 _flags = Flag (_flags & ~Destructive);
591                 _timeline_position = 0;
592                 /* leave xfade buf alone in case we need it again later */
593         }
594
595         return true;
596 }
597
598 void
599 SndFileSource::clear_capture_marks ()
600 {
601         _capture_start = false;
602         _capture_end = false;
603 }
604
605 void
606 SndFileSource::mark_capture_start (sframes_t pos)
607 {
608         if (destructive()) {
609                 if (pos < _timeline_position) {
610                         _capture_start = false;
611                 } else {
612                         _capture_start = true;
613                         capture_start_frame = pos;
614                 }
615         }
616 }
617
618 void
619 SndFileSource::mark_capture_end()
620 {
621         if (destructive()) {
622                 _capture_end = true;
623         }
624 }
625
626 nframes_t
627 SndFileSource::crossfade (Sample* data, nframes_t cnt, int fade_in)
628 {
629         nframes_t xfade = min (xfade_frames, cnt);
630         nframes_t nofade = cnt - xfade;
631         Sample* fade_data = 0;
632         nframes_t fade_position = 0; // in frames
633         ssize_t retval;
634         nframes_t file_cnt;
635
636         if (fade_in) {
637                 fade_position = file_pos;
638                 fade_data = data;
639         } else {
640                 fade_position = file_pos + nofade;
641                 fade_data = data + nofade;
642         }
643
644         if (fade_position > _length) {
645
646                 /* read starts beyond end of data, just memset to zero */
647
648                 file_cnt = 0;
649
650         } else if (fade_position + xfade > _length) {
651
652                 /* read ends beyond end of data, read some, memset the rest */
653
654                 file_cnt = _length - fade_position;
655
656         } else {
657
658                 /* read is entirely within data */
659
660                 file_cnt = xfade;
661         }
662
663         if (file_cnt) {
664
665                 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
666                         if (retval >= 0 && errno == EAGAIN) {
667                                 /* XXX - can we really trust that errno is meaningful here?  yes POSIX, i'm talking to you.
668                                  * short or no data there */
669                                 memset (xfade_buf, 0, xfade * sizeof(Sample));
670                         } else {
671                                 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
672                                 return 0;
673                         }
674                 }
675         }
676
677         if (file_cnt != xfade) {
678                 nframes_t delta = xfade - file_cnt;
679                 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
680         }
681
682         if (nofade && !fade_in) {
683                 if (write_float (data, file_pos, nofade) != nofade) {
684                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
685                         return 0;
686                 }
687         }
688
689         if (xfade == xfade_frames) {
690
691                 nframes_t n;
692
693                 /* use the standard xfade curve */
694
695                 if (fade_in) {
696
697                         /* fade new material in */
698
699                         for (n = 0; n < xfade; ++n) {
700                                 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
701                         }
702
703                 } else {
704
705
706                         /* fade new material out */
707
708                         for (n = 0; n < xfade; ++n) {
709                                 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
710                         }
711                 }
712
713         } else if (xfade < xfade_frames) {
714
715                 gain_t in[xfade];
716                 gain_t out[xfade];
717
718                 /* short xfade, compute custom curve */
719
720                 compute_equal_power_fades (xfade, in, out);
721
722                 for (nframes_t n = 0; n < xfade; ++n) {
723                         xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
724                 }
725
726         } else if (xfade) {
727
728                 /* long xfade length, has to be computed across several calls */
729
730         }
731
732         if (xfade) {
733                 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
734                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
735                         return 0;
736                 }
737         }
738
739         if (fade_in && nofade) {
740                 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
741                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
742                         return 0;
743                 }
744         }
745
746         return cnt;
747 }
748
749 sframes_t
750 SndFileSource::last_capture_start_frame () const
751 {
752         if (destructive()) {
753                 return capture_start_frame;
754         } else {
755                 return 0;
756         }
757 }
758
759 void
760 SndFileSource::handle_header_position_change ()
761 {
762         if (destructive()) {
763                 if ( _length != 0 ) {
764                         error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
765                         //in the future, pop up a dialog here that allows user to regenerate file with new start offset
766                 } else if (writable()) {
767                         _timeline_position = header_position_offset;
768                         set_header_timeline_position ();  //this will get flushed if/when the file is recorded to
769                 }
770         }
771 }
772
773 void
774 SndFileSource::setup_standard_crossfades (Session const & s, nframes_t rate)
775 {
776         /* This static method is assumed to have been called by the Session
777            before any DFS's are created.
778         */
779
780         xfade_frames = (nframes_t) floor ((s.config.get_destructive_xfade_msecs () / 1000.0) * rate);
781
782         delete [] out_coefficient;
783         delete [] in_coefficient;
784
785         out_coefficient = new gain_t[xfade_frames];
786         in_coefficient = new gain_t[xfade_frames];
787
788         compute_equal_power_fades (xfade_frames, in_coefficient, out_coefficient);
789 }
790
791 void
792 SndFileSource::set_timeline_position (int64_t pos)
793 {
794         // destructive track timeline postion does not change
795         // except at instantion or when header_position_offset
796         // (session start) changes
797
798         if (!destructive()) {
799                 AudioFileSource::set_timeline_position (pos);
800         }
801 }
802
803 int
804 SndFileSource::get_soundfile_info (const ustring& path, SoundFileInfo& info, string& error_msg)
805 {
806         SNDFILE *sf;
807         SF_INFO sf_info;
808         BroadcastInfo binfo;
809
810         sf_info.format = 0; // libsndfile says to clear this before sf_open().
811
812         if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
813                 char errbuf[256];
814                 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
815                 return false;
816         }
817
818         info.samplerate  = sf_info.samplerate;
819         info.channels    = sf_info.channels;
820         info.length      = sf_info.frames;
821         info.format_name = string_compose("Format: %1, %2",
822                                            sndfile_major_format(sf_info.format),
823                                            sndfile_minor_format(sf_info.format));
824
825         info.timecode = binfo.load_from_file (sf) ? binfo.get_time_reference() : 0;
826
827         sf_close (sf);
828
829         return true;
830 }
831
832 bool
833 SndFileSource::one_of_several_channels () const
834 {
835         return _info.channels > 1;
836 }
837