Merged with trunk R708
[ardour.git] / libs / ardour / audiofilesource.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 <vector>
21
22 #include <sys/time.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #include <errno.h>
26
27 #include <pbd/mountpoint.h>
28 #include <pbd/pathscanner.h>
29 #include <pbd/stl_delete.h>
30 #include <pbd/strsplit.h>
31
32 #include <sndfile.h>
33
34 #include <glibmm/miscutils.h>
35
36 #include <ardour/audiofilesource.h>
37 #include <ardour/sndfile_helpers.h>
38 #include <ardour/sndfilesource.h>
39 #include <ardour/destructive_filesource.h>
40 #include <ardour/session.h>
41
42 // if these headers come before sigc++ is included
43 // the parser throws ObjC++ errors. (nil is a keyword)
44 #ifdef HAVE_COREAUDIO 
45 #include <ardour/coreaudiosource.h>
46 #include <AudioToolbox/ExtendedAudioFile.h>
47 #include <AudioToolbox/AudioFormat.h>
48 #endif // HAVE_COREAUDIO
49
50 #include "i18n.h"
51
52 using namespace ARDOUR;
53 using namespace PBD;
54
55 string AudioFileSource::peak_dir = "";
56 string AudioFileSource::search_path;
57
58 sigc::signal<void> AudioFileSource::HeaderPositionOffsetChanged;
59 uint64_t           AudioFileSource::header_position_offset = 0;
60
61 char   AudioFileSource::bwf_country_code[3] = "US";
62 char   AudioFileSource::bwf_organization_code[4] = "LAS";
63 char   AudioFileSource::bwf_serial_number[13] = "000000000000";
64
65 AudioFileSource::AudioFileSource (string idstr, Flag flags)
66         : AudioSource (idstr), _flags (flags)
67 {
68         /* constructor used for existing external to session files. file must exist already */
69
70         if (init (idstr, true)) {
71                 throw failed_constructor ();
72         }
73
74 }
75
76 AudioFileSource::AudioFileSource (std::string path, Flag flags, SampleFormat samp_format, HeaderFormat hdr_format)
77         : AudioSource (path), _flags (flags)
78 {
79         /* constructor used for new internal-to-session files. file cannot exist */
80
81         if (init (path, false)) {
82                 throw failed_constructor ();
83         }
84 }
85
86 AudioFileSource::AudioFileSource (const XMLNode& node)
87         : AudioSource (node), _flags (Flag (Writable|CanRename))
88 {
89         /* constructor used for existing internal-to-session files. file must exist */
90
91         if (set_state (node)) {
92                 throw failed_constructor ();
93         }
94         
95         if (init (_name, true)) {
96                 throw failed_constructor ();
97         }
98 }
99
100 AudioFileSource::~AudioFileSource ()
101 {
102         if (removable()) {
103                 unlink (_path.c_str());
104                 unlink (peakpath.c_str());
105         }
106 }
107
108 bool
109 AudioFileSource::removable () const
110 {
111         return (_flags & Removable) && ((_flags & RemoveAtDestroy) || 
112                                       ((_flags & RemovableIfEmpty) && is_empty (_path)));
113 }
114
115 int
116 AudioFileSource::init (string pathstr, bool must_exist)
117 {
118         bool is_new = false;
119
120         _length = 0;
121         next_peak_clear_should_notify = false;
122         
123         if (!find (pathstr, must_exist, is_new)) {
124                 return -1;
125         }
126
127         if (is_new && must_exist) {
128                 return -1;
129         }
130
131         return 0;
132 }
133
134
135 string
136 AudioFileSource::peak_path (string audio_path)
137 {
138         return Session::peak_path_from_audio_path (audio_path);
139 }
140
141 string
142 AudioFileSource::old_peak_path (string audio_path)
143 {
144         /* XXX hardly bombproof! fix me */
145
146         struct stat stat_file;
147         struct stat stat_mount;
148
149         string mp = mountpoint (audio_path);
150
151         stat (audio_path.c_str(), &stat_file);
152         stat (mp.c_str(), &stat_mount);
153
154         char buf[32];
155 #ifdef __APPLE__
156         snprintf (buf, sizeof (buf), "%u-%u-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
157 #else
158         snprintf (buf, sizeof (buf), "%ld-%ld-%d.peak", stat_mount.st_ino, stat_file.st_ino, channel);
159 #endif
160
161         string res = peak_dir;
162         res += buf;
163
164         return res;
165 }
166
167 #ifdef HAVE_COREAUDIO
168
169 AudioFileSource*
170 AudioFileSource::create (const XMLNode& node)
171 {
172         AudioFileSource* es = 0;
173
174         if (node.property (X_("destructive")) != 0) {
175                 
176                 es = new DestructiveFileSource (node);
177         
178         } else {
179                 
180                 try {
181                         es = new CoreAudioSource (node);
182                 } 
183                 
184                 
185                 catch (failed_constructor& err) {
186                         es = new SndFileSource (node);
187                 }
188         }
189         
190         return es;
191 }
192
193 #else
194
195 AudioFileSource*
196 AudioFileSource::create (const XMLNode& node)
197 {
198         if (node.property (X_("destructive")) != 0) {
199                 
200                 return new DestructiveFileSource (node);
201                 
202         } else {
203                 
204                 return new SndFileSource (node);
205         }
206 }
207
208 #endif // HAVE_COREAUDIO
209
210 #ifdef HAVE_COREAUDIO
211 AudioFileSource*
212 AudioFileSource::create (const string& idstr, Flag flags)
213 {
214         AudioFileSource* es = 0;
215
216         if (flags & Destructive) {
217                 return new DestructiveFileSource (idstr, flags);
218         }
219
220         try {
221                 es = new CoreAudioSource (idstr, flags);
222         }
223
224         catch (failed_constructor& err) {
225                 es = new SndFileSource (idstr, flags);
226         }
227
228         return es;
229 }
230
231 #else
232
233 AudioFileSource*
234 AudioFileSource::create (const string& idstr, Flag flags)
235 {
236         return new SndFileSource (idstr, flags);
237 }
238
239 #endif // HAVE_COREAUDIO
240
241 #ifdef HAVE_COREAUDIO
242 std::string 
243 CFStringRefToStdString(CFStringRef stringRef)
244 {
245         CFIndex size = 
246                 CFStringGetMaximumSizeForEncoding(CFStringGetLength(stringRef) , 
247                 kCFStringEncodingASCII);
248             char *buf = new char[size];
249         
250         std::string result;
251
252         if(CFStringGetCString(stringRef, buf, size, kCFStringEncodingASCII)) {
253             result = buf;
254         }
255         delete [] buf;
256         return result;
257 }
258 #endif // HAVE_COREAUDIO
259
260 bool
261 AudioFileSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
262 {
263 #ifdef HAVE_COREAUDIO
264         OSStatus err = noErr;
265     FSRef ref; 
266         ExtAudioFileRef af = 0;
267         size_t size;
268     CFStringRef name;
269
270     err = FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0);
271         if (err != noErr) {
272         ExtAudioFileDispose (af);
273                 goto libsndfile;
274         }
275
276         err = ExtAudioFileOpen(&ref, &af);
277         if (err != noErr) {
278         ExtAudioFileDispose (af);
279                 goto libsndfile;
280         }
281
282         AudioStreamBasicDescription absd;
283         memset(&absd, 0, sizeof(absd));
284         size = sizeof(AudioStreamBasicDescription);
285         err = ExtAudioFileGetProperty(af,
286                         kExtAudioFileProperty_FileDataFormat, &size, &absd);
287         if (err != noErr) {
288         ExtAudioFileDispose (af);
289                 goto libsndfile;
290         }
291
292         _info.samplerate = absd.mSampleRate;
293         _info.channels   = absd.mChannelsPerFrame;
294
295     size = sizeof(_info.length);
296     err = ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length);
297     if (err != noErr) {
298         ExtAudioFileDispose (af);
299                 goto libsndfile;
300     }
301
302         size = sizeof(CFStringRef);
303         err = AudioFormatGetProperty(
304                         kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name);
305         if (err != noErr) {
306         ExtAudioFileDispose (af);
307                 goto libsndfile;
308         }
309
310         _info.format_name = CFStringRefToStdString(name);
311
312     ExtAudioFileDispose (af);
313         return true;
314         
315 libsndfile:
316 #endif // HAVE_COREAUDIO
317
318         SNDFILE *sf;
319         SF_INFO sf_info;
320
321         sf_info.format = 0; // libsndfile says to clear this before sf_open().
322
323         if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) { 
324                 char errbuf[256];
325                 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
326                 return false;
327         }
328
329         sf_close (sf);
330
331         _info.samplerate  = sf_info.samplerate;
332         _info.channels    = sf_info.channels;
333         _info.length      = sf_info.frames;
334         _info.format_name = string_compose("Format: %1, %2",
335                                            sndfile_major_format(sf_info.format),
336                                            sndfile_minor_format(sf_info.format));
337         return true;
338 }
339
340 XMLNode&
341 AudioFileSource::get_state ()
342 {
343         XMLNode& root (AudioSource::get_state());
344         char buf[16];
345         snprintf (buf, sizeof (buf), "0x%x", (int)_flags);
346         root.add_property ("flags", buf);
347         return root;
348 }
349
350 int
351 AudioFileSource::set_state (const XMLNode& node)
352 {
353         const XMLProperty* prop;
354
355         if (AudioSource::set_state (node)) {
356                 return -1;
357         }
358
359         if ((prop = node.property (X_("flags"))) != 0) {
360
361                 int ival;
362                 sscanf (prop->value().c_str(), "0x%x", &ival);
363                 _flags = Flag (ival);
364
365         } else {
366
367                 _flags = Flag (0);
368
369         }
370
371         return 0;
372 }
373
374 void
375 AudioFileSource::mark_for_remove ()
376 {
377         if (!writable()) {
378                 return;
379         }
380         _flags = Flag (_flags | RemoveAtDestroy);
381 }
382
383 void
384 AudioFileSource::mark_streaming_write_completed ()
385 {
386         if (!writable()) {
387                 return;
388         }
389
390         Glib::Mutex::Lock lm (_lock);
391
392         next_peak_clear_should_notify = true;
393
394         if (_peaks_built || pending_peak_builds.empty()) {
395                 _peaks_built = true;
396                  PeaksReady (); /* EMIT SIGNAL */
397         }
398 }
399
400 void
401 AudioFileSource::mark_take (string id)
402 {
403         if (writable()) {
404                 _take_id = id;
405         }
406 }
407
408 int
409 AudioFileSource::move_to_trash (const string trash_dir_name)
410 {
411         string newpath;
412
413         if (!writable()) {
414                 return -1;
415         }
416
417         /* don't move the file across filesystems, just
418            stick it in the `trash_dir_name' directory
419            on whichever filesystem it was already on.
420         */
421
422         newpath = Glib::path_get_dirname (_path);
423         newpath = Glib::path_get_dirname (newpath);
424
425         newpath += '/';
426         newpath += trash_dir_name;
427         newpath += '/';
428         newpath += Glib::path_get_basename (_path);
429
430         if (access (newpath.c_str(), F_OK) == 0) {
431
432                 /* the new path already exists, try versioning */
433                 
434                 char buf[PATH_MAX+1];
435                 int version = 1;
436                 string newpath_v;
437
438                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
439                 newpath_v = buf;
440
441                 while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
442                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
443                         newpath_v = buf;
444                 }
445                 
446                 if (version == 999) {
447                         error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
448                                           newpath)
449                               << endmsg;
450                 } else {
451                         newpath = newpath_v;
452                 }
453
454         } else {
455
456                 /* it doesn't exist, or we can't read it or something */
457
458         }
459
460         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
461                 error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
462                                   _path, newpath, strerror (errno))
463                       << endmsg;
464                 return -1;
465         }
466
467         if (::unlink (peakpath.c_str()) != 0) {
468                 error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
469                                   peakpath, _path, strerror (errno))
470                       << endmsg;
471                 /* try to back out */
472                 rename (newpath.c_str(), _path.c_str());
473                 return -1;
474         }
475             
476         _path = newpath;
477         peakpath = "";
478         
479         /* file can not be removed twice, since the operation is not idempotent */
480
481         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
482
483         return 0;
484 }
485
486 bool
487 AudioFileSource::find (string pathstr, bool must_exist, bool& isnew)
488 {
489         string::size_type pos;
490         bool ret = false;
491
492         isnew = false;
493
494         /* clean up PATH:CHANNEL notation so that we are looking for the correct path */
495
496         if ((pos = pathstr.find_last_of (':')) == string::npos) {
497                 pathstr = pathstr;
498         } else {
499                 pathstr = pathstr.substr (0, pos);
500         }
501
502         if (pathstr[0] != '/') {
503
504                 /* non-absolute pathname: find pathstr in search path */
505
506                 vector<string> dirs;
507                 int cnt;
508                 string fullpath;
509                 string keeppath;
510
511                 if (search_path.length() == 0) {
512                         error << _("FileSource: search path not set") << endmsg;
513                         goto out;
514                 }
515
516                 split (search_path, dirs, ':');
517
518                 cnt = 0;
519                 
520                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
521
522                         fullpath = *i;
523                         if (fullpath[fullpath.length()-1] != '/') {
524                                 fullpath += '/';
525                         }
526                         fullpath += pathstr;
527                         
528                         if (access (fullpath.c_str(), R_OK) == 0) {
529                                 keeppath = fullpath;
530                                 ++cnt;
531                         } 
532                 }
533
534                 if (cnt > 1) {
535
536                         error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, search_path) << endmsg;
537                         goto out;
538
539                 } else if (cnt == 0) {
540
541                         if (must_exist) {
542                                 error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, search_path) << endmsg;
543                                 goto out;
544                         } else {
545                                 isnew = true;
546                         }
547                 }
548                 
549                 _name = pathstr;
550                 _path = keeppath;
551                 ret = true;
552
553         } else {
554                 
555                 /* external files and/or very very old style sessions include full paths */
556                 
557                 _path = pathstr;
558                 _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
559                 
560                 if (access (_path.c_str(), R_OK) != 0) {
561
562                         /* file does not exist or we cannot read it */
563
564                         if (must_exist) {
565                                 error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
566                                 goto out;
567                         }
568                         
569                         if (errno != ENOENT) {
570                                 error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
571                                 goto out;
572                         }
573                         
574                         /* a new file */
575
576                         isnew = true;
577                         ret = true;
578
579                 } else {
580                         
581                         /* already exists */
582
583                         ret = true;
584                 }
585         }
586         
587   out:
588         return ret;
589 }
590
591 void
592 AudioFileSource::set_search_path (string p)
593 {
594         search_path = p;
595 }
596
597 void
598 AudioFileSource::set_header_position_offset (jack_nframes_t offset)
599 {
600         header_position_offset = offset;
601         cerr << "hpo set to " << offset << endl;
602         HeaderPositionOffsetChanged ();
603 }
604
605 void 
606 AudioFileSource::handle_header_position_change ()
607 {
608         if (writable()) {
609                 set_header_timeline_position ();
610                 flush_header ();
611         }
612 }
613
614 void
615 AudioFileSource::set_timeline_position (jack_nframes_t pos)
616 {
617         timeline_position = pos;
618 }
619
620 void
621 AudioFileSource::set_allow_remove_if_empty (bool yn)
622 {
623         if (writable()) {
624                 allow_remove_if_empty = yn;
625         }
626 }
627
628 int
629 AudioFileSource::set_name (string newname, bool destructive)
630 {
631         Glib::Mutex::Lock lm (_lock);
632         string oldpath = _path;
633         string newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
634
635         if (newpath.empty()) {
636                 error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
637                 return -1;
638         }
639
640         if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
641                 error << string_compose (_("cannot rename audio file for %1 to %2"), _name, newpath) << endmsg;
642                 return -1;
643         }
644
645         _name = Glib::path_get_basename (newpath);
646         _path = newpath;
647
648         return rename_peakfile (peak_path (_path));
649 }
650
651 bool
652 AudioFileSource::is_empty (string path)
653 {
654         bool ret = false;
655         AudioFileSource* afs = create (path, NoPeakFile);
656
657         if (afs) {
658                 ret = (afs->length() == 0);
659                 delete afs;
660         }
661
662         return ret;
663 }
664