use libsndfile in preference to ExtAudioFile when getting info on a source file,...
[ardour2.git] / libs / ardour / sndfilesource.cc
blob1759b4a6c1c6330136b1d117241495f0a0561698
1 /*
2 Copyright (C) 2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include <cstring>
21 #include <cerrno>
22 #include <climits>
23 #include <cstdarg>
25 #include <pwd.h>
26 #include <sys/utsname.h>
27 #include <sys/stat.h>
29 #include <glibmm/miscutils.h>
30 #include <pbd/stacktrace.h>
32 #include <ardour/sndfilesource.h>
33 #include <ardour/sndfile_helpers.h>
34 #include <ardour/utils.h>
35 #include <ardour/version.h>
37 #include "i18n.h"
39 using namespace std;
40 using namespace ARDOUR;
41 using namespace PBD;
42 using Glib::ustring;
44 gain_t* SndFileSource::out_coefficient = 0;
45 gain_t* SndFileSource::in_coefficient = 0;
46 nframes_t SndFileSource::xfade_frames = 64;
47 const AudioFileSource::Flag SndFileSource::default_writable_flags = AudioFileSource::Flag (AudioFileSource::Writable|
48 AudioFileSource::Removable|
49 AudioFileSource::RemovableIfEmpty|
50 AudioFileSource::CanRename);
52 static void
53 snprintf_bounded_null_filled (char* target, size_t target_size, const char* fmt, ...)
55 char buf[target_size+1];
56 va_list ap;
58 va_start (ap, fmt);
59 vsnprintf (buf, target_size+1, fmt, ap);
60 va_end (ap);
62 memset (target, 0, target_size);
63 memcpy (target, buf, target_size);
67 SndFileSource::SndFileSource (Session& s, const XMLNode& node)
68 : AudioFileSource (s, node)
70 init ();
72 if (open()) {
73 throw failed_constructor ();
77 SndFileSource::SndFileSource (Session& s, ustring path, int chn, Flag flags)
78 /* files created this way are never writable or removable */
79 : AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
81 _channel = chn;
83 init ();
85 if (open()) {
86 throw failed_constructor ();
90 SndFileSource::SndFileSource (Session& s, ustring path, SampleFormat sfmt, HeaderFormat hf, nframes_t rate, Flag flags)
91 : AudioFileSource (s, path, flags, sfmt, hf)
93 int fmt = 0;
95 init ();
97 /* this constructor is used to construct new files, not open
98 existing ones.
101 file_is_new = true;
103 switch (hf) {
104 case CAF:
105 fmt = SF_FORMAT_CAF;
106 _flags = Flag (_flags & ~Broadcast);
107 break;
109 case AIFF:
110 fmt = SF_FORMAT_AIFF;
111 _flags = Flag (_flags & ~Broadcast);
112 break;
114 case BWF:
115 fmt = SF_FORMAT_WAV;
116 _flags = Flag (_flags | Broadcast);
117 break;
119 case WAVE:
120 fmt = SF_FORMAT_WAV;
121 _flags = Flag (_flags & ~Broadcast);
122 break;
124 case WAVE64:
125 fmt = SF_FORMAT_W64;
126 _flags = Flag (_flags & ~Broadcast);
127 break;
129 default:
130 fatal << string_compose (_("programming error: %1"), X_("unsupported audio header format requested")) << endmsg;
131 /*NOTREACHED*/
132 break;
136 switch (sfmt) {
137 case FormatFloat:
138 fmt |= SF_FORMAT_FLOAT;
139 break;
141 case FormatInt24:
142 fmt |= SF_FORMAT_PCM_24;
143 break;
145 case FormatInt16:
146 fmt |= SF_FORMAT_PCM_16;
147 break;
150 _info.channels = 1;
151 _info.samplerate = rate;
152 _info.format = fmt;
154 if (open()) {
155 throw failed_constructor();
158 if (writable() && (_flags & Broadcast)) {
160 if (!_broadcast_info) {
161 _broadcast_info = new SF_BROADCAST_INFO;
162 memset (_broadcast_info, 0, sizeof (*_broadcast_info));
165 snprintf_bounded_null_filled (_broadcast_info->description, sizeof (_broadcast_info->description), "BWF %s", _name.c_str());
166 snprintf_bounded_null_filled (_broadcast_info->originator, sizeof (_broadcast_info->originator), "ardour %d.%d.%d %s",
167 libardour2_major_version,
168 libardour2_minor_version,
169 libardour2_micro_version,
170 Glib::get_real_name().c_str());
172 _broadcast_info->version = 1;
173 _broadcast_info->time_reference_low = 0;
174 _broadcast_info->time_reference_high = 0;
176 /* XXX do something about this field */
178 snprintf_bounded_null_filled (_broadcast_info->umid, sizeof (_broadcast_info->umid), "%s", "fnord");
180 /* coding history is added by libsndfile */
182 if (sf_command (sf, SFC_SET_BROADCAST_INFO, _broadcast_info, sizeof (*_broadcast_info)) != SF_TRUE) {
183 char errbuf[256];
184 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
185 error << string_compose (_("cannot set broadcast info for audio file %1 (%2); dropping broadcast info for this file"), _path, errbuf) << endmsg;
186 _flags = Flag (_flags & ~Broadcast);
187 delete _broadcast_info;
188 _broadcast_info = 0;
193 void
194 SndFileSource::init ()
196 ustring file;
198 // lets try to keep the object initalizations here at the top
199 xfade_buf = 0;
200 sf = 0;
201 _broadcast_info = 0;
203 if (is_embedded()) {
204 _name = _path;
205 } else {
206 _name = Glib::path_get_basename (_path);
209 /* although libsndfile says we don't need to set this,
210 valgrind and source code shows us that we do.
213 memset (&_info, 0, sizeof(_info));
215 _capture_start = false;
216 _capture_end = false;
217 file_pos = 0;
219 if (destructive()) {
220 xfade_buf = new Sample[xfade_frames];
221 timeline_position = header_position_offset;
224 AudioFileSource::HeaderPositionOffsetChanged.connect (mem_fun (*this, &SndFileSource::handle_header_position_change));
228 SndFileSource::open ()
230 if ((sf = sf_open (_path.c_str(), (writable() ? SFM_RDWR : SFM_READ), &_info)) == 0) {
231 char errbuf[256];
232 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
233 #ifndef HAVE_COREAUDIO
234 /* if we have CoreAudio, we will be falling back to that if libsndfile fails,
235 so we don't want to see this message.
238 error << string_compose(_("SndFileSource: cannot open file \"%1\" for %2 (%3)"),
239 _path, (writable() ? "read+write" : "reading"), errbuf) << endmsg;
240 #endif
241 return -1;
244 if (_channel >= _info.channels) {
245 #ifndef HAVE_COREAUDIO
246 error << string_compose(_("SndFileSource: file only contains %1 channels; %2 is invalid as a channel number"), _info.channels, _channel) << endmsg;
247 #endif
248 sf_close (sf);
249 sf = 0;
250 return -1;
253 _length = _info.frames;
255 if (!_broadcast_info) {
256 _broadcast_info = new SF_BROADCAST_INFO;
257 memset (_broadcast_info, 0, sizeof (*_broadcast_info));
260 bool timecode_info_exists;
262 set_timeline_position (get_timecode_info (sf, _broadcast_info, timecode_info_exists));
264 if (_length != 0 && !timecode_info_exists) {
265 delete _broadcast_info;
266 _broadcast_info = 0;
267 _flags = Flag (_flags & ~Broadcast);
270 if (writable()) {
271 sf_command (sf, SFC_SET_UPDATE_HEADER_AUTO, 0, SF_FALSE);
274 return 0;
277 SndFileSource::~SndFileSource ()
279 GoingAway (); /* EMIT SIGNAL */
281 if (sf) {
282 sf_close (sf);
283 sf = 0;
285 /* stupid libsndfile updated the headers on close,
286 so touch the peakfile if it exists and has data
287 to make sure its time is as new as the audio
288 file.
291 touch_peakfile ();
294 if (_broadcast_info) {
295 delete _broadcast_info;
298 if (xfade_buf) {
299 delete [] xfade_buf;
303 float
304 SndFileSource::sample_rate () const
306 return _info.samplerate;
309 nframes_t
310 SndFileSource::read_unlocked (Sample *dst, nframes_t start, nframes_t cnt) const
312 int32_t nread;
313 float *ptr;
314 uint32_t real_cnt;
315 nframes_t file_cnt;
317 if (start > _length) {
319 /* read starts beyond end of data, just memset to zero */
321 file_cnt = 0;
323 } else if (start + cnt > _length) {
325 /* read ends beyond end of data, read some, memset the rest */
327 file_cnt = _length - start;
329 } else {
331 /* read is entirely within data */
333 file_cnt = cnt;
336 if (file_cnt != cnt) {
337 nframes_t delta = cnt - file_cnt;
338 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
341 if (file_cnt) {
343 if (sf_seek (sf, (sf_count_t) start, SEEK_SET|SFM_READ) != (sf_count_t) start) {
344 char errbuf[256];
345 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
346 error << string_compose(_("SndFileSource: could not seek to frame %1 within %2 (%3)"), start, _name.substr (1), errbuf) << endmsg;
347 return 0;
350 if (_info.channels == 1) {
351 nframes_t ret = sf_read_float (sf, dst, file_cnt);
352 _read_data_count = ret * sizeof(float);
353 if (ret != file_cnt) {
354 char errbuf[256];
355 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
356 cerr << string_compose(_("SndFileSource: @ %1 could not read %2 within %3 (%4) (len = %5)"), start, file_cnt, _name.substr (1), errbuf, _length) << endl;
358 return ret;
362 real_cnt = cnt * _info.channels;
364 Sample* interleave_buf = get_interleave_buffer (real_cnt);
366 nread = sf_read_float (sf, interleave_buf, real_cnt);
367 ptr = interleave_buf + _channel;
368 nread /= _info.channels;
370 /* stride through the interleaved data */
372 for (int32_t n = 0; n < nread; ++n) {
373 dst[n] = *ptr;
374 ptr += _info.channels;
377 _read_data_count = cnt * sizeof(float);
379 return nread;
382 nframes_t
383 SndFileSource::write_unlocked (Sample *data, nframes_t cnt)
385 if (destructive()) {
386 return destructive_write_unlocked (data, cnt);
387 } else {
388 return nondestructive_write_unlocked (data, cnt);
392 nframes_t
393 SndFileSource::nondestructive_write_unlocked (Sample *data, nframes_t cnt)
395 if (!writable()) {
396 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
397 return 0;
400 if (_info.channels != 1) {
401 fatal << string_compose (_("programming error: %1 %2"), X_("SndFileSource::write called on non-mono file"), _path) << endmsg;
402 /*NOTREACHED*/
403 return 0;
406 nframes_t oldlen;
407 int32_t frame_pos = _length;
409 if (write_float (data, frame_pos, cnt) != cnt) {
410 return 0;
413 oldlen = _length;
414 update_length (oldlen, cnt);
416 if (_build_peakfiles) {
417 compute_and_write_peaks (data, frame_pos, cnt, false, true);
420 _write_data_count = cnt;
422 return cnt;
425 nframes_t
426 SndFileSource::destructive_write_unlocked (Sample* data, nframes_t cnt)
428 nframes_t old_file_pos;
430 if (!writable()) {
431 warning << string_compose (_("attempt to write a non-writable audio file source (%1)"), _path) << endmsg;
432 return 0;
435 if (_capture_start && _capture_end) {
437 /* start and end of capture both occur within the data we are writing,
438 so do both crossfades.
441 _capture_start = false;
442 _capture_end = false;
444 /* move to the correct location place */
445 file_pos = capture_start_frame - timeline_position;
447 // split cnt in half
448 nframes_t subcnt = cnt / 2;
449 nframes_t ofilepos = file_pos;
451 // fade in
452 if (crossfade (data, subcnt, 1) != subcnt) {
453 return 0;
456 file_pos += subcnt;
457 Sample * tmpdata = data + subcnt;
459 // fade out
460 subcnt = cnt - subcnt;
461 if (crossfade (tmpdata, subcnt, 0) != subcnt) {
462 return 0;
465 file_pos = ofilepos; // adjusted below
467 } else if (_capture_start) {
469 /* start of capture both occur within the data we are writing,
470 so do the fade in
473 _capture_start = false;
474 _capture_end = false;
476 /* move to the correct location place */
477 file_pos = capture_start_frame - timeline_position;
479 if (crossfade (data, cnt, 1) != cnt) {
480 return 0;
483 } else if (_capture_end) {
485 /* end of capture both occur within the data we are writing,
486 so do the fade out
489 _capture_start = false;
490 _capture_end = false;
492 if (crossfade (data, cnt, 0) != cnt) {
493 return 0;
496 } else {
498 /* in the middle of recording */
500 if (write_float (data, file_pos, cnt) != cnt) {
501 return 0;
505 old_file_pos = file_pos;
506 update_length (file_pos, cnt);
508 if (_build_peakfiles) {
509 compute_and_write_peaks (data, file_pos, cnt, false, true);
512 file_pos += cnt;
514 return cnt;
518 SndFileSource::update_header (nframes_t when, struct tm& now, time_t tnow)
520 set_timeline_position (when);
522 if (_flags & Broadcast) {
523 if (setup_broadcast_info (when, now, tnow)) {
524 return -1;
528 return flush_header ();
532 SndFileSource::flush_header ()
534 if (!writable() || (sf == 0)) {
535 warning << string_compose (_("attempt to flush a non-writable audio file source (%1)"), _path) << endmsg;
536 return -1;
538 return (sf_command (sf, SFC_UPDATE_HEADER_NOW, 0, 0) != SF_TRUE);
542 SndFileSource::setup_broadcast_info (nframes_t when, struct tm& now, time_t tnow)
544 if (!writable()) {
545 warning << string_compose (_("attempt to store broadcast info in a non-writable audio file source (%1)"), _path) << endmsg;
546 return -1;
549 if (!(_flags & Broadcast)) {
550 return 0;
553 /* random code is 9 digits */
555 int random_code = random() % 999999999;
557 snprintf_bounded_null_filled (_broadcast_info->originator_reference, sizeof (_broadcast_info->originator_reference), "%2s%3s%12s%02d%02d%02d%9d",
558 Config->get_bwf_country_code().c_str(),
559 Config->get_bwf_organization_code().c_str(),
560 bwf_serial_number,
561 now.tm_hour,
562 now.tm_min,
563 now.tm_sec,
564 random_code);
566 snprintf_bounded_null_filled (_broadcast_info->origination_date, sizeof (_broadcast_info->origination_date), "%4d-%02d-%02d",
567 1900 + now.tm_year,
568 now.tm_mon + 1, // shift range from 0..11 to 1..12
569 now.tm_mday);
571 snprintf_bounded_null_filled (_broadcast_info->origination_time, sizeof (_broadcast_info->origination_time), "%02d:%02d:%02d",
572 now.tm_hour,
573 now.tm_min,
574 now.tm_sec);
576 /* now update header position taking header offset into account */
578 set_header_timeline_position ();
580 if (sf_command (sf, SFC_SET_BROADCAST_INFO, _broadcast_info, sizeof (*_broadcast_info)) != SF_TRUE) {
581 error << string_compose (_("cannot set broadcast info for audio file %1; Dropping broadcast info for this file"), _path) << endmsg;
582 _flags = Flag (_flags & ~Broadcast);
583 delete _broadcast_info;
584 _broadcast_info = 0;
585 return -1;
588 return 0;
591 void
592 SndFileSource::set_header_timeline_position ()
594 if (!(_flags & Broadcast)) {
595 return;
598 _broadcast_info->time_reference_high = (timeline_position >> 32);
599 _broadcast_info->time_reference_low = (timeline_position & 0xffffffff);
601 if (sf_command (sf, SFC_SET_BROADCAST_INFO, _broadcast_info, sizeof (*_broadcast_info)) != SF_TRUE) {
602 error << string_compose (_("cannot set broadcast info for audio file %1; Dropping broadcast info for this file"), _path) << endmsg;
603 _flags = Flag (_flags & ~Broadcast);
604 delete _broadcast_info;
605 _broadcast_info = 0;
609 nframes_t
610 SndFileSource::write_float (Sample* data, nframes_t frame_pos, nframes_t cnt)
612 if (sf_seek (sf, frame_pos, SEEK_SET|SFM_WRITE) < 0) {
613 char errbuf[256];
614 sf_error_str (0, errbuf, sizeof (errbuf) - 1);
615 error << string_compose (_("%1: cannot seek to %2 (libsndfile error: %3"), _path, frame_pos, errbuf) << endmsg;
616 return 0;
619 if (sf_writef_float (sf, data, cnt) != (ssize_t) cnt) {
620 return 0;
623 return cnt;
626 nframes_t
627 SndFileSource::natural_position() const
629 return timeline_position;
632 bool
633 SndFileSource::set_destructive (bool yn)
635 if (yn) {
636 _flags = Flag (_flags | Destructive);
637 if (!xfade_buf) {
638 xfade_buf = new Sample[xfade_frames];
640 clear_capture_marks ();
641 timeline_position = header_position_offset;
642 } else {
643 _flags = Flag (_flags & ~Destructive);
644 timeline_position = 0;
645 /* leave xfade buf alone in case we need it again later */
648 return true;
651 void
652 SndFileSource::clear_capture_marks ()
654 _capture_start = false;
655 _capture_end = false;
658 void
659 SndFileSource::mark_capture_start (nframes_t pos)
661 if (destructive()) {
662 if (pos < timeline_position) {
663 _capture_start = false;
664 } else {
665 _capture_start = true;
666 capture_start_frame = pos;
671 void
672 SndFileSource::mark_capture_end()
674 if (destructive()) {
675 _capture_end = true;
679 nframes_t
680 SndFileSource::crossfade (Sample* data, nframes_t cnt, int fade_in)
682 nframes_t xfade = min (xfade_frames, cnt);
683 nframes_t nofade = cnt - xfade;
684 Sample* fade_data = 0;
685 nframes_t fade_position = 0; // in frames
686 ssize_t retval;
687 nframes_t file_cnt;
689 if (fade_in) {
690 fade_position = file_pos;
691 fade_data = data;
692 } else {
693 fade_position = file_pos + nofade;
694 fade_data = data + nofade;
697 if (fade_position > _length) {
699 /* read starts beyond end of data, just memset to zero */
701 file_cnt = 0;
703 } else if (fade_position + xfade > _length) {
705 /* read ends beyond end of data, read some, memset the rest */
707 file_cnt = _length - fade_position;
709 } else {
711 /* read is entirely within data */
713 file_cnt = xfade;
716 if (file_cnt) {
718 if ((retval = read_unlocked (xfade_buf, fade_position, file_cnt)) != (ssize_t) file_cnt) {
719 if (retval >= 0 && errno == EAGAIN) {
720 /* XXX - can we really trust that errno is meaningful here? yes POSIX, i'm talking to you.
721 * short or no data there */
722 memset (xfade_buf, 0, xfade * sizeof(Sample));
723 } else {
724 error << string_compose(_("SndFileSource: \"%1\" bad read retval: %2 of %5 (%3: %4)"), _path, retval, errno, strerror (errno), xfade) << endmsg;
725 return 0;
730 if (file_cnt != xfade) {
731 nframes_t delta = xfade - file_cnt;
732 memset (xfade_buf+file_cnt, 0, sizeof (Sample) * delta);
735 if (nofade && !fade_in) {
736 if (write_float (data, file_pos, nofade) != nofade) {
737 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
738 return 0;
742 if (xfade == xfade_frames) {
744 nframes_t n;
746 /* use the standard xfade curve */
748 if (fade_in) {
750 /* fade new material in */
752 for (n = 0; n < xfade; ++n) {
753 xfade_buf[n] = (xfade_buf[n] * out_coefficient[n]) + (fade_data[n] * in_coefficient[n]);
756 } else {
759 /* fade new material out */
761 for (n = 0; n < xfade; ++n) {
762 xfade_buf[n] = (xfade_buf[n] * in_coefficient[n]) + (fade_data[n] * out_coefficient[n]);
766 } else if (xfade < xfade_frames) {
768 gain_t in[xfade];
769 gain_t out[xfade];
771 /* short xfade, compute custom curve */
773 compute_equal_power_fades (xfade, in, out);
775 for (nframes_t n = 0; n < xfade; ++n) {
776 xfade_buf[n] = (xfade_buf[n] * out[n]) + (fade_data[n] * in[n]);
779 } else if (xfade) {
781 /* long xfade length, has to be computed across several calls */
785 if (xfade) {
786 if (write_float (xfade_buf, fade_position, xfade) != xfade) {
787 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
788 return 0;
792 if (fade_in && nofade) {
793 if (write_float (data + xfade, file_pos + xfade, nofade) != nofade) {
794 error << string_compose(_("SndFileSource: \"%1\" bad write (%2)"), _path, strerror (errno)) << endmsg;
795 return 0;
799 return cnt;
802 nframes_t
803 SndFileSource::last_capture_start_frame () const
805 if (destructive()) {
806 return capture_start_frame;
807 } else {
808 return 0;
812 void
813 SndFileSource::handle_header_position_change ()
815 if (destructive()) {
816 if ( _length != 0 ) {
817 error << string_compose(_("Filesource: start time is already set for existing file (%1): Cannot change start time."), _path ) << endmsg;
818 //in the future, pop up a dialog here that allows user to regenerate file with new start offset
819 } else if (writable()) {
820 timeline_position = header_position_offset;
821 set_header_timeline_position (); //this will get flushed if/when the file is recorded to
826 void
827 SndFileSource::setup_standard_crossfades (nframes_t rate)
829 /* This static method is assumed to have been called by the Session
830 before any DFS's are created.
833 xfade_frames = (nframes_t) floor ((Config->get_destructive_xfade_msecs () / 1000.0) * rate);
835 if (out_coefficient) {
836 delete [] out_coefficient;
839 if (in_coefficient) {
840 delete [] in_coefficient;
843 out_coefficient = new gain_t[xfade_frames];
844 in_coefficient = new gain_t[xfade_frames];
846 compute_equal_power_fades (xfade_frames, in_coefficient, out_coefficient);
849 void
850 SndFileSource::set_timeline_position (int64_t pos)
852 // destructive track timeline postion does not change
853 // except at instantion or when header_position_offset
854 // (session start) changes
856 if (!destructive()) {
857 AudioFileSource::set_timeline_position (pos);
862 SndFileSource::get_soundfile_info (const ustring& path, SoundFileInfo& info, string& error_msg)
864 SNDFILE *sf;
865 SF_INFO sf_info;
866 SF_BROADCAST_INFO binfo;
867 bool timecode_exists;
869 sf_info.format = 0; // libsndfile says to clear this before sf_open().
871 if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) {
872 char errbuf[256];
873 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
874 return false;
877 info.samplerate = sf_info.samplerate;
878 info.channels = sf_info.channels;
879 info.length = sf_info.frames;
881 string major = sndfile_major_format(sf_info.format);
882 string minor = sndfile_minor_format(sf_info.format);
884 if (major.length() + minor.length() < 16) { /* arbitrary */
885 info.format_name = string_compose("%1/%2", major, minor);
886 } else {
887 info.format_name = string_compose("%1\n%2", major, minor);
890 memset (&binfo, 0, sizeof (binfo));
891 info.timecode = get_timecode_info (sf, &binfo, timecode_exists);
893 if (!timecode_exists) {
894 info.timecode = 0;
897 sf_close (sf);
899 return true;
902 int64_t
903 SndFileSource::get_timecode_info (SNDFILE* sf, SF_BROADCAST_INFO* binfo, bool& exists)
905 if (sf_command (sf, SFC_GET_BROADCAST_INFO, binfo, sizeof (*binfo)) != SF_TRUE) {
906 exists = false;
907 return (header_position_offset);
910 /* XXX 64 bit alert: when JACK switches to a 64 bit frame count, this needs to use the high bits
911 of the time reference.
914 exists = true;
915 int64_t ret = (uint32_t) binfo->time_reference_high;
916 ret <<= 32;
917 ret |= (uint32_t) binfo->time_reference_low;
918 return ret;
921 bool
922 SndFileSource::one_of_several_channels () const
924 return _info.channels > 1;