Trim include dependency graph, especially for io.h and session.h.
[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 AudioFileSource::Flag SndFileSource::default_writable_flags = AudioFileSource::Flag (AudioFileSource::Writable|
47                                                                                            AudioFileSource::Removable|
48                                                                                            AudioFileSource::RemovableIfEmpty|
49                                                                                            AudioFileSource::CanRename);
50
51 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
52         : AudioFileSource (s, node)
53 {
54         init ();
55
56         if (open()) {
57                 throw failed_constructor ();
58         }
59 }
60
61 SndFileSource::SndFileSource (Session& s, ustring path, int chn, Flag flags)
62           /* files created this way are never writable or removable */
63         : AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
64 {
65         _channel = chn;
66
67         init ();
68
69         if (open()) {
70                 throw failed_constructor ();
71         }
72 }
73
74 SndFileSource::SndFileSource (Session& s, ustring path, SampleFormat sfmt, HeaderFormat hf, nframes_t rate, Flag flags)
75         : AudioFileSource (s, path, flags, sfmt, hf)
76 {
77         int fmt = 0;
78
79         init ();
80
81         /* this constructor is used to construct new files, not open
82            existing ones.
83         */
84
85         file_is_new = true;
86
87         switch (hf) {
88         case CAF:
89                 fmt = SF_FORMAT_CAF;
90                 _flags = Flag (_flags & ~Broadcast);
91                 break;
92
93         case AIFF:
94                 fmt = SF_FORMAT_AIFF;
95                 _flags = Flag (_flags & ~Broadcast);
96                 break;
97
98         case BWF:
99                 fmt = SF_FORMAT_WAV;
100                 _flags = Flag (_flags | Broadcast);
101                 break;
102
103         case WAVE:
104                 fmt = SF_FORMAT_WAV;
105                 _flags = Flag (_flags & ~Broadcast);
106                 break;
107
108         case WAVE64:
109                 fmt = SF_FORMAT_W64;
110                 _flags = Flag (_flags & ~Broadcast);
111                 break;
112
113         default:
114                 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
115                 /*NOTREACHED*/
116                 break;
117
118         }
119
120         switch (sfmt) {
121         case FormatFloat:
122                 fmt |= SF_FORMAT_FLOAT;
123                 break;
124
125         case FormatInt24:
126                 fmt |= SF_FORMAT_PCM_24;
127                 break;
128
129         case FormatInt16:
130                 fmt |= SF_FORMAT_PCM_16;
131                 break;
132         }
133         
134         _info.channels = 1;
135         _info.samplerate = rate;
136         _info.format = fmt;
137
138         if (open()) {
139                 throw failed_constructor();
140         }
141
142         if (writable() && (_flags & Broadcast)) {
143
144                 if (!_broadcast_info) {
145                         _broadcast_info = new BroadcastInfo;
146                 }
147
148                 _broadcast_info->set_from_session (s, header_position_offset);
149                 _broadcast_info->set_description (string_compose ("BWF %1", _name));
150
151                 if (!_broadcast_info->write_to_file (sf)) {
152                         error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"),
153                                                    _path, _broadcast_info->get_error())
154                               << endmsg;
155                         _flags = Flag (_flags & ~Broadcast);
156                         delete _broadcast_info;
157                         _broadcast_info = 0;
158                 }
159         }
160 }
161
162 void 
163 SndFileSource::init ()
164 {
165         ustring file;
166
167         // lets try to keep the object initalizations here at the top
168         xfade_buf = 0;
169         sf = 0;
170         _broadcast_info = 0;
171
172         if (is_embedded()) {
173                 _name = _path;
174         } else {
175                 _name = Glib::path_get_basename (_path);
176         }
177
178         /* although libsndfile says we don't need to set this,
179            valgrind and source code shows us that we do.
180         */
181
182         memset (&_info, 0, sizeof(_info));
183
184         _capture_start = false;
185         _capture_end = false;
186         file_pos = 0;
187
188         if (destructive()) {    
189                 xfade_buf = new Sample[xfade_frames];
190                 timeline_position = header_position_offset;
191         }
192
193         AudioFileSource::HeaderPositionOffsetChanged.connect (mem_fun (*this, &SndFileSource::handle_header_position_change));
194 }
195
196 int
197 SndFileSource::open ()
198 {
199         if ((sf = sf_open (_path.c_str(), (writable() ? SFM_RDWR : SFM_READ), &_info)) == 0) {
200                 char errbuf[256];
201                 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
202 #ifndef HAVE_COREAUDIO
203                 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
204                    so we don't want to see this message.
205                 */
206
207                 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"), 
208                                         _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
209 #endif
210                 return -1;
211         }
212
213         if (_channel >= _info.channels) {
214 #ifndef HAVE_COREAUDIO
215                 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
216 #endif
217                 sf_close (sf);
218                 sf = 0;
219                 return -1;
220         }
221
222         _length = _info.frames;
223
224         if (!_broadcast_info) {
225                 _broadcast_info = new BroadcastInfo;
226         }
227         
228         bool bwf_info_exists = _broadcast_info->load_from_file (sf);
229
230         set_timeline_position (bwf_info_exists ? _broadcast_info->get_time_reference() : header_position_offset);
231
232         if (_length != 0 && !bwf_info_exists) {
233                 delete _broadcast_info;
234                 _broadcast_info = 0;
235                 _flags = Flag (_flags & ~Broadcast);
236         }
237
238         if (writable()) {
239                 sf_command (sf, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
240         }
241
242         return 0;
243 }
244
245 SndFileSource::~SndFileSource ()
246 {
247         GoingAway (); /* EMIT SIGNAL */
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, nframes_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.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.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 (nframes_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 (nframes_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 ();
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, nframes_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 nframes_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 (nframes_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 nframes_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 (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 ((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