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