new file
[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         if (sf) {
250                 sf_close (sf);
251                 sf = 0;
252
253                 /* stupid libsndfile updated the headers on close,
254                    so touch the peakfile if it exists and has data
255                    to make sure its time is as new as the audio
256                    file.
257                 */
258
259                 touch_peakfile ();
260         }
261
262         delete _broadcast_info;
263         delete [] xfade_buf;
264 }
265
266 float
267 SndFileSource::sample_rate () const
268 {
269         return _info.samplerate;
270 }
271
272 nframes_t
273 SndFileSource::read_unlocked (Sample *dst, sframes_t start, nframes_t cnt) const
274 {
275         int32_t nread;
276         float *ptr;
277         uint32_t real_cnt;
278         nframes_t file_cnt;
279
280         if (start > _length) {
281
282                 /* read starts beyond end of data, just memset to zero */
283
284                 file_cnt = 0;
285
286         } else if (start + cnt > _length) {
287
288                 /* read ends beyond end of data, read some, memset the rest */
289
290                 file_cnt = _length - start;
291
292         } else {
293
294                 /* read is entirely within data */
295
296                 file_cnt = cnt;
297         }
298
299         if (file_cnt != cnt) {
300                 nframes_t delta = cnt - file_cnt;
301                 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
302         }
303
304         if (file_cnt) {
305
306                 if (sf_seek (sf, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
307                         char errbuf[256];
308                         sf_error_str (0, errbuf, sizeof (errbuf) - 1);
309                         error << string_compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.val().substr (1), errbuf) << endmsg;
310                         return 0;
311                 }
312
313                 if (_info.channels == 1) {
314                         nframes_t ret = sf_read_float (sf, dst, file_cnt);
315                         _read_data_count = ret * sizeof(float);
316                         if (ret != file_cnt) {
317                                 char errbuf[256];
318                                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
319                                 cerr << string_compose(_("SndFileSource: @ %1 could not read %2 within %3 (%4) (len = %5)"), start, file_cnt, _name.val().substr (1), errbuf, _length) << endl;
320                         }
321                         return ret;
322                 }
323         }
324
325         real_cnt = cnt * _info.channels;
326
327         Sample* interleave_buf = get_interleave_buffer (real_cnt);
328
329         nread = sf_read_float (sf, interleave_buf, real_cnt);
330         ptr = interleave_buf + _channel;
331         nread /= _info.channels;
332
333         /* stride through the interleaved data */
334
335         for (int32_t n = 0; n < nread; ++n) {
336                 dst[n] = *ptr;
337                 ptr += _info.channels;
338         }
339
340         _read_data_count = cnt * sizeof(float);
341
342         return nread;
343 }
344
345 nframes_t
346 SndFileSource::write_unlocked (Sample *data, nframes_t cnt)
347 {
348         if (destructive()) {
349                 return destructive_write_unlocked (data, cnt);
350         } else {
351                 return nondestructive_write_unlocked (data, cnt);
352         }
353 }
354
355 nframes_t
356 SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
357 {
358         if (!writable()) {
359                 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
360                 return 0;
361         }
362
363         if (_info.channels != 1) {
364                 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
365                 /*NOTREACHED*/
366                 return 0;
367         }
368
369         nframes_t oldlen;
370         int32_t frame_pos = _length;
371
372         if (write_float (data, frame_pos, cnt) != cnt) {
373                 return 0;
374         }
375
376         oldlen = _length;
377         update_length (oldlen, cnt);
378
379         if (_build_peakfiles) {
380                 compute_and_write_peaks (data, frame_pos, cnt, false, true);
381         }
382
383         _write_data_count = cnt;
384
385         return cnt;
386 }
387
388 nframes_t
389 SndFileSource::destructive_write_unlocked (Sample* data, nframes_t cnt)
390 {
391         nframes_t old_file_pos;
392
393         if (!writable()) {
394                 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
395                 return 0;
396         }
397
398         if (_capture_start && _capture_end) {
399
400                 /* start and end of capture both occur within the data we are writing,
401                    so do both crossfades.
402                 */
403
404                 _capture_start = false;
405                 _capture_end = false;
406
407                 /* move to the correct location place */
408                 file_pos = capture_start_frame - _timeline_position;
409
410                 // split cnt in half
411                 nframes_t subcnt = cnt / 2;
412                 nframes_t ofilepos = file_pos;
413
414                 // fade in
415                 if (crossfade (data, subcnt, 1) != subcnt) {
416                         return 0;
417                 }
418
419                 file_pos += subcnt;
420                 Sample * tmpdata = data + subcnt;
421
422                 // fade out
423                 subcnt = cnt - subcnt;
424                 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
425                         return 0;
426                 }
427
428                 file_pos = ofilepos; // adjusted below
429
430         } else if (_capture_start) {
431
432                 /* start of capture both occur within the data we are writing,
433                    so do the fade in
434                 */
435
436                 _capture_start = false;
437                 _capture_end = false;
438
439                 /* move to the correct location place */
440                 file_pos = capture_start_frame - _timeline_position;
441
442                 if (crossfade (data, cnt, 1) != cnt) {
443                         return 0;
444                 }
445
446         } else if (_capture_end) {
447
448                 /* end of capture both occur within the data we are writing,
449                    so do the fade out
450                 */
451
452                 _capture_start = false;
453                 _capture_end = false;
454
455                 if (crossfade (data, cnt, 0) != cnt) {
456                         return 0;
457                 }
458
459         } else {
460
461                 /* in the middle of recording */
462
463                 if (write_float (data, file_pos, cnt) != cnt) {
464                         return 0;
465                 }
466         }
467
468         old_file_pos = file_pos;
469         update_length (file_pos, cnt);
470
471         if (_build_peakfiles) {
472                 compute_and_write_peaks (data, file_pos, cnt, false, true);
473         }
474
475         file_pos += cnt;
476
477         return cnt;
478 }
479
480 int
481 SndFileSource::update_header (sframes_t when, struct tm& now, time_t tnow)
482 {
483         set_timeline_position (when);
484
485         if (_flags & Broadcast) {
486                 if (setup_broadcast_info (when, now, tnow)) {
487                         return -1;
488                 }
489         }
490
491         return flush_header ();
492 }
493
494 int
495 SndFileSource::flush_header ()
496 {
497         if (!writable() || (sf == 0)) {
498                 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
499                 return -1;
500         }
501         return (sf_command (sf, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE);
502 }
503
504 int
505 SndFileSource::setup_broadcast_info (sframes_t /*when*/, struct tm& now, time_t /*tnow*/)
506 {
507         if (!writable()) {
508                 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
509                 return -1;
510         }
511
512         if (!(_flags & Broadcast)) {
513                 return 0;
514         }
515
516         _broadcast_info->set_originator_ref (_session);
517         _broadcast_info->set_origination_time (&now);
518
519         /* now update header position taking header offset into account */
520
521         set_header_timeline_position ();
522
523         if (!_broadcast_info->write_to_file (sf)) {
524                 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
525                                            _path, _broadcast_info->get_error())
526                       << endmsg;
527                 _flags = Flag (_flags & ~Broadcast);
528                 delete _broadcast_info;
529                 _broadcast_info = 0;
530         }
531
532         return 0;
533 }
534
535 void
536 SndFileSource::set_header_timeline_position ()
537 {
538         if (!(_flags & Broadcast)) {
539                 return;
540         }
541
542         _broadcast_info->set_time_reference (_timeline_position);
543
544         if (!_broadcast_info->write_to_file (sf)) {
545                 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
546                                            _path, _broadcast_info->get_error())
547                       << endmsg;
548                 _flags = Flag (_flags & ~Broadcast);
549                 delete _broadcast_info;
550                 _broadcast_info = 0;
551         }
552 }
553
554 nframes_t
555 SndFileSource::write_float (Sample* data, sframes_t frame_pos, nframes_t cnt)
556 {
557         if (sf_seek (sf, frame_pos, SEEK_SET|SFM_WRITE) < 0) {
558                 char errbuf[256];
559                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
560                 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3"), _path, frame_pos, errbuf) << endmsg;
561                 return 0;
562         }
563
564         if (sf_writef_float (sf, data, cnt) != (ssize_t) cnt) {
565                 return 0;
566         }
567
568         return cnt;
569 }
570
571 sframes_t
572 SndFileSource::natural_position() const
573 {
574         return _timeline_position;
575 }
576
577 bool
578 SndFileSource::set_destructive (bool yn)
579 {
580         if (yn) {
581                 _flags = Flag (_flags | Destructive);
582                 if (!xfade_buf) {
583                         xfade_buf = new Sample[xfade_frames];
584                 }
585                 clear_capture_marks ();
586                 _timeline_position = header_position_offset;
587         } else {
588                 _flags = Flag (_flags & ~Destructive);
589                 _timeline_position = 0;
590                 /* leave xfade buf alone in case we need it again later */
591         }
592
593         return true;
594 }
595
596 void
597 SndFileSource::clear_capture_marks ()
598 {
599         _capture_start = false;
600         _capture_end = false;
601 }
602
603 void
604 SndFileSource::mark_capture_start (sframes_t pos)
605 {
606         if (destructive()) {
607                 if (pos < _timeline_position) {
608                         _capture_start = false;
609                 } else {
610                         _capture_start = true;
611                         capture_start_frame = pos;
612                 }
613         }
614 }
615
616 void
617 SndFileSource::mark_capture_end()
618 {
619         if (destructive()) {
620                 _capture_end = true;
621         }
622 }
623
624 nframes_t
625 SndFileSource::crossfade (Sample* data, nframes_t cnt, int fade_in)
626 {
627         nframes_t xfade = min (xfade_frames, cnt);
628         nframes_t nofade = cnt - xfade;
629         Sample* fade_data = 0;
630         nframes_t fade_position = 0; // in frames
631         ssize_t retval;
632         nframes_t file_cnt;
633
634         if (fade_in) {
635                 fade_position = file_pos;
636                 fade_data = data;
637         } else {
638                 fade_position = file_pos + nofade;
639                 fade_data = data + nofade;
640         }
641
642         if (fade_position > _length) {
643
644                 /* read starts beyond end of data, just memset to zero */
645
646                 file_cnt = 0;
647
648         } else if (fade_position + xfade > _length) {
649
650                 /* read ends beyond end of data, read some, memset the rest */
651
652                 file_cnt = _length - fade_position;
653
654         } else {
655
656                 /* read is entirely within data */
657
658                 file_cnt = xfade;
659         }
660
661         if (file_cnt) {
662
663                 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
664                         if (retval >= 0 && errno == EAGAIN) {
665                                 /* XXX - can we really trust that errno is meaningful here?  yes POSIX, i'm talking to you.
666                                  * short or no data there */
667                                 memset (xfade_buf, 0, xfade * sizeof(Sample));
668                         } else {
669                                 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
670                                 return 0;
671                         }
672                 }
673         }
674
675         if (file_cnt != xfade) {
676                 nframes_t delta = xfade - file_cnt;
677                 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
678         }
679
680         if (nofade && !fade_in) {
681                 if (write_float (data, file_pos, nofade) != nofade) {
682                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
683                         return 0;
684                 }
685         }
686
687         if (xfade == xfade_frames) {
688
689                 nframes_t n;
690
691                 /* use the standard xfade curve */
692
693                 if (fade_in) {
694
695                         /* fade new material in */
696
697                         for (n = 0; n < xfade; ++n) {
698                                 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
699                         }
700
701                 } else {
702
703
704                         /* fade new material out */
705
706                         for (n = 0; n < xfade; ++n) {
707                                 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
708                         }
709                 }
710
711         } else if (xfade < xfade_frames) {
712
713                 gain_t in[xfade];
714                 gain_t out[xfade];
715
716                 /* short xfade, compute custom curve */
717
718                 compute_equal_power_fades (xfade, in, out);
719
720                 for (nframes_t n = 0; n < xfade; ++n) {
721                         xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
722                 }
723
724         } else if (xfade) {
725
726                 /* long xfade length, has to be computed across several calls */
727
728         }
729
730         if (xfade) {
731                 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
732                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
733                         return 0;
734                 }
735         }
736
737         if (fade_in && nofade) {
738                 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
739                         error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
740                         return 0;
741                 }
742         }
743
744         return cnt;
745 }
746
747 sframes_t
748 SndFileSource::last_capture_start_frame () const
749 {
750         if (destructive()) {
751                 return capture_start_frame;
752         } else {
753                 return 0;
754         }
755 }
756
757 void
758 SndFileSource::handle_header_position_change ()
759 {
760         if (destructive()) {
761                 if ( _length != 0 ) {
762                         error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
763                         //in the future, pop up a dialog here that allows user to regenerate file with new start offset
764                 } else if (writable()) {
765                         _timeline_position = header_position_offset;
766                         set_header_timeline_position ();  //this will get flushed if/when the file is recorded to
767                 }
768         }
769 }
770
771 void
772 SndFileSource::setup_standard_crossfades (Session const & s, nframes_t rate)
773 {
774         /* This static method is assumed to have been called by the Session
775            before any DFS's are created.
776         */
777
778         xfade_frames = (nframes_t) floor ((s.config.get_destructive_xfade_msecs () / 1000.0) * rate);
779
780         delete [] out_coefficient;
781         delete [] in_coefficient;
782
783         out_coefficient = new gain_t[xfade_frames];
784         in_coefficient = new gain_t[xfade_frames];
785
786         compute_equal_power_fades (xfade_frames, in_coefficient, out_coefficient);
787 }
788
789 void
790 SndFileSource::set_timeline_position (int64_t pos)
791 {
792         // destructive track timeline postion does not change
793         // except at instantion or when header_position_offset
794         // (session start) changes
795
796         if (!destructive()) {
797                 AudioFileSource::set_timeline_position (pos);
798         }
799 }
800
801 int
802 SndFileSource::get_soundfile_info (const ustring& path, SoundFileInfo& info, string& error_msg)
803 {
804         SNDFILE *sf;
805         SF_INFO sf_info;
806         BroadcastInfo binfo;
807
808         sf_info.format = 0; // libsndfile says to clear this before sf_open().
809
810         if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
811                 char errbuf[256];
812                 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
813                 return false;
814         }
815
816         info.samplerate  = sf_info.samplerate;
817         info.channels    = sf_info.channels;
818         info.length      = sf_info.frames;
819         info.format_name = string_compose("Format: %1, %2",
820                                            sndfile_major_format(sf_info.format),
821                                            sndfile_minor_format(sf_info.format));
822
823         info.timecode = binfo.load_from_file (sf) ? binfo.get_time_reference() : 0;
824
825         sf_close (sf);
826
827         return true;
828 }
829
830 bool
831 SndFileSource::one_of_several_channels () const
832 {
833         return _info.channels > 1;
834 }
835