fix up BWF creation; make CoreAudioSource work; add CAImportable; refactor SourceFact...
[ardour.git] / libs / ardour / coreaudiosource.cc
1 /*
2     Copyright (C) 2006 Paul Davis
3     Written by Taybin Rutkin
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <algorithm>
22
23 #include <pbd/error.h>
24 #include <ardour/coreaudiosource.h>
25 #include <ardour/utils.h>
26
27 #include <appleutility/CAAudioFile.h>
28 #include <appleutility/CAStreamBasicDescription.h>
29
30 #include "i18n.h"
31
32 #include <AudioToolbox/AudioFormat.h>
33
34 using namespace std;
35 using namespace ARDOUR;
36 using namespace PBD;
37
38 CoreAudioSource::CoreAudioSource (Session& s, const XMLNode& node)
39         : AudioFileSource (s, node)
40 {
41         init ();
42 }
43
44 CoreAudioSource::CoreAudioSource (Session& s, const string& path, int chn, Flag flags)
45         /* files created this way are never writable or removable */
46         : AudioFileSource (s, path, Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
47 {
48         _channel = chn;
49         init ();
50 }
51
52 void 
53 CoreAudioSource::init ()
54 {
55         /* note that we temporarily truncated _id at the colon */
56         try {
57                 af.Open(_path.c_str());
58
59                 CAStreamBasicDescription file_format (af.GetFileDataFormat());
60                 n_channels = file_format.NumberChannels();
61                 
62                 if (_channel >= n_channels) {
63                         error << string_compose("CoreAudioSource: file only contains %1 channels; %2 is invalid as a channel number (%3)", n_channels, _channel, name()) << endmsg;
64                         throw failed_constructor();
65                 }
66
67                 _length = af.GetNumberFrames();
68
69                 CAStreamBasicDescription client_format (file_format);
70
71                 /* set canonial form (PCM, native float packed, 32 bit, with the correct number of channels
72                    and interleaved (since we plan to deinterleave ourselves)
73                 */
74
75                 client_format.SetCanonical(client_format.NumberChannels(), true);
76                 af.SetClientFormat (client_format);
77
78         } catch (CAXException& cax) {
79                 error << string_compose ("CoreAudioSource: %1 (%2)", cax.mOperation, name()) << endmsg;
80                 throw failed_constructor ();
81         }
82 }
83
84 CoreAudioSource::~CoreAudioSource ()
85 {
86         GoingAway (); /* EMIT SIGNAL */
87 }
88
89 int
90 CoreAudioSource::safe_read (Sample* dst, nframes_t start, nframes_t cnt, AudioBufferList& abl) const
91 {
92         nframes_t nread = 0;
93
94         while (nread < cnt) {
95                 
96                 try {
97                         af.Seek (start+nread);
98                 } catch (CAXException& cax) {
99                         error << string_compose("CoreAudioSource: %1 to %2 (%3)", cax.mOperation, start+nread, _name.substr (1)) << endmsg;
100                         return -1;
101                 }
102                 
103                 UInt32 new_cnt = cnt - nread;
104                 
105                 abl.mBuffers[0].mDataByteSize = new_cnt * n_channels * sizeof(Sample);
106                 abl.mBuffers[0].mData = dst + nread;
107                         
108                 try {
109                         af.Read (new_cnt, &abl);
110                 } catch (CAXException& cax) {
111                         error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
112                         return -1;
113                 }
114
115                 if (new_cnt == 0) {
116                         /* EOF */
117                         if (start+cnt == _length) {
118                                 /* we really did hit the end */
119                                 nread = cnt;
120                         }
121                         break;
122                 }
123
124                 nread += new_cnt;
125         }
126
127         if (nread < cnt) {
128                 return -1;
129         } else {
130                 return 0;
131         }
132 }
133         
134
135 nframes_t
136 CoreAudioSource::read_unlocked (Sample *dst, nframes_t start, nframes_t cnt) const
137 {
138         nframes_t file_cnt;
139         AudioBufferList abl;
140
141         abl.mNumberBuffers = 1;
142         abl.mBuffers[0].mNumberChannels = n_channels;
143
144         if (start > _length) {
145
146                 /* read starts beyond end of data, just memset to zero */
147                 
148                 file_cnt = 0;
149
150         } else if (start + cnt > _length) {
151                 
152                 /* read ends beyond end of data, read some, memset the rest */
153                 
154                 file_cnt = _length - start;
155
156         } else {
157                 
158                 /* read is entirely within data */
159
160                 file_cnt = cnt;
161         }
162
163         if (file_cnt != cnt) {
164                 nframes_t delta = cnt - file_cnt;
165                 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
166         }
167
168         if (file_cnt) {
169
170                 if (n_channels == 1) {
171                         if (safe_read (dst, start, file_cnt, abl) == 0) {
172                                 _read_data_count = cnt * sizeof (Sample);
173                                 return cnt;
174                         }
175                         return 0;
176                 }
177         }
178
179         Sample* interleave_buf = get_interleave_buffer (file_cnt * n_channels);
180         
181         if (safe_read (interleave_buf, start, file_cnt, abl) != 0) {
182                 return 0;
183         }
184
185         _read_data_count = cnt * sizeof(float);
186
187         Sample *ptr = interleave_buf + _channel;
188         
189         /* stride through the interleaved data */
190         
191         for (uint32_t n = 0; n < file_cnt; ++n) {
192                 dst[n] = *ptr;
193                 ptr += n_channels;
194         }
195
196         return cnt;
197 }
198
199 float
200 CoreAudioSource::sample_rate() const
201 {
202         CAStreamBasicDescription client_asbd;
203
204         try {
205                 client_asbd = af.GetClientDataFormat ();
206         } catch (CAXException& cax) {
207                 error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
208                 return 0.0;
209         }
210
211         return client_asbd.mSampleRate;
212 }
213
214 int
215 CoreAudioSource::update_header (nframes_t when, struct tm&, time_t)
216 {
217         return 0;
218 }
219
220 int
221 CoreAudioSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
222 {
223         FSRef ref; 
224         ExtAudioFileRef af = 0;
225         size_t size;
226         CFStringRef name;
227         int ret = -1;
228
229         if (FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0) != noErr) {
230                 goto out;
231         }
232         
233         if (ExtAudioFileOpen(&ref, &af) != noErr) {
234                 goto out;
235         }
236         
237         AudioStreamBasicDescription absd;
238         memset(&absd, 0, sizeof(absd));
239         size = sizeof(AudioStreamBasicDescription);
240         if (ExtAudioFileGetProperty (af, kExtAudioFileProperty_FileDataFormat, &size, &absd) != noErr) {
241                 goto out;
242         }
243         
244         _info.samplerate = absd.mSampleRate;
245         _info.channels   = absd.mChannelsPerFrame;
246
247         size = sizeof(_info.length);
248         if (ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length) != noErr) {
249                 goto out;
250         }
251         
252         size = sizeof(CFStringRef);
253         if (AudioFormatGetProperty(kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name) != noErr) {
254                 goto out;
255         }
256
257         _info.format_name = CFStringRefToStdString(name);
258
259         // XXX it would be nice to find a way to get this information if it exists
260
261         _info.timecode = 0;
262         ret = 0;
263         
264   out:
265         ExtAudioFileDispose (af);
266         return ret;
267         
268 }