r269@gandalf: fugalh | 2006-08-03 20:18:05 -0600
[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 bool
242 AudioFileSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
243 {
244 #ifdef HAVE_COREAUDIO
245         OSStatus err = noErr;
246     FSRef ref; 
247         ExtAudioFileRef af = 0;
248         size_t size;
249     CFStringRef name;
250
251     err = FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0);
252         if (err != noErr) {
253         ExtAudioFileDispose (af);
254                 goto libsndfile;
255         }
256
257         err = ExtAudioFileOpen(&ref, &af);
258         if (err != noErr) {
259         ExtAudioFileDispose (af);
260                 goto libsndfile;
261         }
262
263         AudioStreamBasicDescription absd;
264         memset(&absd, 0, sizeof(absd));
265         size = sizeof(AudioStreamBasicDescription);
266         err = ExtAudioFileGetProperty(af,
267                         kExtAudioFileProperty_FileDataFormat, &size, &absd);
268         if (err != noErr) {
269         ExtAudioFileDispose (af);
270                 goto libsndfile;
271         }
272
273         _info.samplerate = absd.mSampleRate;
274         _info.channels   = absd.mChannelsPerFrame;
275
276     size = sizeof(_info.length);
277     err = ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length);
278     if (err != noErr) {
279         ExtAudioFileDispose (af);
280                 goto libsndfile;
281     }
282
283         size = sizeof(CFStringRef);
284         err = AudioFormatGetProperty(
285                         kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name);
286         if (err != noErr) {
287         ExtAudioFileDispose (af);
288                 goto libsndfile;
289         }
290
291         _info.format_name = CFStringRefToStdString(name);
292
293     ExtAudioFileDispose (af);
294         return true;
295         
296 libsndfile:
297 #endif // HAVE_COREAUDIO
298
299         SNDFILE *sf;
300         SF_INFO sf_info;
301
302         sf_info.format = 0; // libsndfile says to clear this before sf_open().
303
304         if ((sf = sf_open ((char*) path.c_str(), SFM_READ, &sf_info)) == 0) { 
305                 char errbuf[256];
306                 error_msg = sf_error_str (0, errbuf, sizeof (errbuf) - 1);
307                 return false;
308         }
309
310         sf_close (sf);
311
312         _info.samplerate  = sf_info.samplerate;
313         _info.channels    = sf_info.channels;
314         _info.length      = sf_info.frames;
315         _info.format_name = string_compose("Format: %1, %2",
316                                            sndfile_major_format(sf_info.format),
317                                            sndfile_minor_format(sf_info.format));
318         return true;
319 }
320
321 XMLNode&
322 AudioFileSource::get_state ()
323 {
324         XMLNode& root (AudioSource::get_state());
325         char buf[16];
326         snprintf (buf, sizeof (buf), "0x%x", (int)_flags);
327         root.add_property ("flags", buf);
328         return root;
329 }
330
331 int
332 AudioFileSource::set_state (const XMLNode& node)
333 {
334         const XMLProperty* prop;
335
336         if (AudioSource::set_state (node)) {
337                 return -1;
338         }
339
340         if ((prop = node.property (X_("flags"))) != 0) {
341
342                 int ival;
343                 sscanf (prop->value().c_str(), "0x%x", &ival);
344                 _flags = Flag (ival);
345
346         } else {
347
348                 _flags = Flag (0);
349
350         }
351
352         return 0;
353 }
354
355 void
356 AudioFileSource::mark_for_remove ()
357 {
358         if (!writable()) {
359                 return;
360         }
361         _flags = Flag (_flags | RemoveAtDestroy);
362 }
363
364 void
365 AudioFileSource::mark_streaming_write_completed ()
366 {
367         if (!writable()) {
368                 return;
369         }
370
371         Glib::Mutex::Lock lm (_lock);
372
373         next_peak_clear_should_notify = true;
374
375         if (_peaks_built || pending_peak_builds.empty()) {
376                 _peaks_built = true;
377                  PeaksReady (); /* EMIT SIGNAL */
378         }
379 }
380
381 void
382 AudioFileSource::mark_take (string id)
383 {
384         if (writable()) {
385                 _take_id = id;
386         }
387 }
388
389 int
390 AudioFileSource::move_to_trash (const string trash_dir_name)
391 {
392         string newpath;
393
394         if (!writable()) {
395                 return -1;
396         }
397
398         /* don't move the file across filesystems, just
399            stick it in the `trash_dir_name' directory
400            on whichever filesystem it was already on.
401         */
402
403         newpath = Glib::path_get_dirname (_path);
404         newpath = Glib::path_get_dirname (newpath);
405
406         newpath += '/';
407         newpath += trash_dir_name;
408         newpath += '/';
409         newpath += Glib::path_get_basename (_path);
410
411         if (access (newpath.c_str(), F_OK) == 0) {
412
413                 /* the new path already exists, try versioning */
414                 
415                 char buf[PATH_MAX+1];
416                 int version = 1;
417                 string newpath_v;
418
419                 snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
420                 newpath_v = buf;
421
422                 while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
423                         snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
424                         newpath_v = buf;
425                 }
426                 
427                 if (version == 999) {
428                         error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
429                                           newpath)
430                               << endmsg;
431                 } else {
432                         newpath = newpath_v;
433                 }
434
435         } else {
436
437                 /* it doesn't exist, or we can't read it or something */
438
439         }
440
441         if (::rename (_path.c_str(), newpath.c_str()) != 0) {
442                 error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
443                                   _path, newpath, strerror (errno))
444                       << endmsg;
445                 return -1;
446         }
447
448         if (::unlink (peakpath.c_str()) != 0) {
449                 error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
450                                   peakpath, _path, strerror (errno))
451                       << endmsg;
452                 /* try to back out */
453                 rename (newpath.c_str(), _path.c_str());
454                 return -1;
455         }
456             
457         _path = newpath;
458         peakpath = "";
459         
460         /* file can not be removed twice, since the operation is not idempotent */
461
462         _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
463
464         return 0;
465 }
466
467 bool
468 AudioFileSource::find (string pathstr, bool must_exist, bool& isnew)
469 {
470         string::size_type pos;
471         bool ret = false;
472
473         isnew = false;
474
475         /* clean up PATH:CHANNEL notation so that we are looking for the correct path */
476
477         if ((pos = pathstr.find_last_of (':')) == string::npos) {
478                 pathstr = pathstr;
479         } else {
480                 pathstr = pathstr.substr (0, pos);
481         }
482
483         if (pathstr[0] != '/') {
484
485                 /* non-absolute pathname: find pathstr in search path */
486
487                 vector<string> dirs;
488                 int cnt;
489                 string fullpath;
490                 string keeppath;
491
492                 if (search_path.length() == 0) {
493                         error << _("FileSource: search path not set") << endmsg;
494                         goto out;
495                 }
496
497                 split (search_path, dirs, ':');
498
499                 cnt = 0;
500                 
501                 for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
502
503                         fullpath = *i;
504                         if (fullpath[fullpath.length()-1] != '/') {
505                                 fullpath += '/';
506                         }
507                         fullpath += pathstr;
508                         
509                         if (access (fullpath.c_str(), R_OK) == 0) {
510                                 keeppath = fullpath;
511                                 ++cnt;
512                         } 
513                 }
514
515                 if (cnt > 1) {
516
517                         error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, search_path) << endmsg;
518                         goto out;
519
520                 } else if (cnt == 0) {
521
522                         if (must_exist) {
523                                 error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, search_path) << endmsg;
524                                 goto out;
525                         } else {
526                                 isnew = true;
527                         }
528                 }
529                 
530                 _name = pathstr;
531                 _path = keeppath;
532                 ret = true;
533
534         } else {
535                 
536                 /* external files and/or very very old style sessions include full paths */
537                 
538                 _path = pathstr;
539                 _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
540                 
541                 if (access (_path.c_str(), R_OK) != 0) {
542
543                         /* file does not exist or we cannot read it */
544
545                         if (must_exist) {
546                                 error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
547                                 goto out;
548                         }
549                         
550                         if (errno != ENOENT) {
551                                 error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
552                                 goto out;
553                         }
554                         
555                         /* a new file */
556
557                         isnew = true;
558                         ret = true;
559
560                 } else {
561                         
562                         /* already exists */
563
564                         ret = true;
565                 }
566         }
567         
568   out:
569         return ret;
570 }
571
572 void
573 AudioFileSource::set_search_path (string p)
574 {
575         search_path = p;
576 }
577
578 void
579 AudioFileSource::set_header_position_offset (jack_nframes_t offset)
580 {
581         header_position_offset = offset;
582         cerr << "hpo set to " << offset << endl;
583         HeaderPositionOffsetChanged ();
584 }
585
586 void
587 AudioFileSource::set_timeline_position (jack_nframes_t pos)
588 {
589         timeline_position = pos;
590 }
591
592 void
593 AudioFileSource::set_allow_remove_if_empty (bool yn)
594 {
595         if (writable()) {
596                 allow_remove_if_empty = yn;
597         }
598 }
599
600 int
601 AudioFileSource::set_name (string newname, bool destructive)
602 {
603         Glib::Mutex::Lock lm (_lock);
604         string oldpath = _path;
605         string newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
606
607         if (newpath.empty()) {
608                 error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
609                 return -1;
610         }
611
612         // Test whether newpath exists, if yes notify the user but continue. 
613         if (access(newpath.c_str(),F_OK) == 0) {
614                 error << _("Programming error! Ardour tried to rename a file over another file! It's safe to continue working, but please report this to the developers.") << endmsg;
615                 return -1;
616         }
617
618         if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
619                 error << string_compose (_("cannot rename audio file for %1 to %2"), _name, newpath) << endmsg;
620                 return -1;
621         }
622
623         _name = Glib::path_get_basename (newpath);
624         _path = newpath;
625
626         return rename_peakfile (peak_path (_path));
627 }
628
629 bool
630 AudioFileSource::is_empty (string path)
631 {
632         bool ret = false;
633         AudioFileSource* afs = create (path, NoPeakFile);
634
635         if (afs) {
636                 ret = (afs->length() == 0);
637                 delete afs;
638         }
639
640         return ret;
641 }
642