try not thinning when loading old-school automation lists
[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 #include <inttypes.h>
23
24 #include "pbd/error.h"
25 #include "ardour/coreaudiosource.h"
26 #include "ardour/utils.h"
27
28 #include <appleutility/CAAudioFile.h>
29 #include <appleutility/CAStreamBasicDescription.h>
30
31 #include "i18n.h"
32
33 #include <AudioToolbox/AudioFormat.h>
34
35 using namespace std;
36 using namespace ARDOUR;
37 using namespace PBD;
38
39 CoreAudioSource::CoreAudioSource (Session& s, const XMLNode& node)
40         : Source (s, node)
41         , AudioFileSource (s, node)
42 {
43         init_cafile ();
44 }
45
46 CoreAudioSource::CoreAudioSource (Session& s, const string& path, int chn, Flag flags)
47         /* files created this way are never writable or removable */
48         : Source (s, DataType::AUDIO, path, Source::Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy))),
49                 AudioFileSource (s, path,
50                         Source::Flag (flags & ~(Writable|Removable|RemovableIfEmpty|RemoveAtDestroy)))
51 {
52         _channel = chn;
53         init_cafile ();
54 }
55
56 void
57 CoreAudioSource::init_cafile ()
58 {
59         /* note that we temporarily truncated _id at the colon */
60         try {
61                 af.Open(_path.c_str());
62
63                 CAStreamBasicDescription file_format (af.GetFileDataFormat());
64                 n_channels = file_format.NumberChannels();
65
66                 if (_channel >= n_channels) {
67                         error << string_compose("CoreAudioSource: file only contains %1 channels; %2 is invalid as a channel number (%3)", n_channels, _channel, name()) << endmsg;
68                         throw failed_constructor();
69                 }
70
71                 _length = af.GetNumberFrames();
72
73                 CAStreamBasicDescription client_format (file_format);
74
75                 /* set canonial form (PCM, native float packed, 32 bit, with the correct number of channels
76                    and interleaved (since we plan to deinterleave ourselves)
77                 */
78
79                 client_format.SetCanonical(client_format.NumberChannels(), true);
80                 af.SetClientFormat (client_format);
81
82         } catch (CAXException& cax) {
83
84                 error << string_compose(_("CoreAudioSource: cannot open file \"%1\" for %2"),
85                                         _path, (writable() ? "read+write" : "reading")) << endmsg;
86                 throw failed_constructor ();
87         }
88 }
89
90 CoreAudioSource::~CoreAudioSource ()
91 {
92 }
93
94 int
95 CoreAudioSource::safe_read (Sample* dst, framepos_t start, framecnt_t cnt, AudioBufferList& abl) const
96 {
97         framecnt_t nread = 0;
98
99         while (nread < cnt) {
100
101                 try {
102                         af.Seek (start+nread);
103                 } catch (CAXException& cax) {
104                         error << string_compose("CoreAudioSource: %1 to %2 (%3)", cax.mOperation, start+nread, _name.val().substr (1)) << endmsg;
105                         return -1;
106                 }
107
108                 UInt32 new_cnt = cnt - nread;
109
110                 abl.mBuffers[0].mDataByteSize = new_cnt * n_channels * sizeof(Sample);
111                 abl.mBuffers[0].mData = dst + nread;
112
113                 try {
114                         af.Read (new_cnt, &abl);
115                 } catch (CAXException& cax) {
116                         error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
117                         return -1;
118                 }
119
120                 if (new_cnt == 0) {
121                         /* EOF */
122                         if (start+cnt == _length) {
123                                 /* we really did hit the end */
124                                 nread = cnt;
125                         }
126                         break;
127                 }
128
129                 nread += new_cnt;
130         }
131
132         if (nread < cnt) {
133                 return -1;
134         } else {
135                 return 0;
136         }
137 }
138
139
140 framecnt_t
141 CoreAudioSource::read_unlocked (Sample *dst, framepos_t start, framecnt_t cnt) const
142 {
143         framecnt_t file_cnt;
144         AudioBufferList abl;
145
146         abl.mNumberBuffers = 1;
147         abl.mBuffers[0].mNumberChannels = n_channels;
148
149         if (start > _length) {
150
151                 /* read starts beyond end of data, just memset to zero */
152
153                 file_cnt = 0;
154
155         } else if (start + cnt > _length) {
156
157                 /* read ends beyond end of data, read some, memset the rest */
158
159                 file_cnt = _length - start;
160
161         } else {
162
163                 /* read is entirely within data */
164
165                 file_cnt = cnt;
166         }
167
168         if (file_cnt != cnt) {
169                 frameoffset_t delta = cnt - file_cnt;
170                 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
171         }
172
173         if (file_cnt) {
174
175                 if (n_channels == 1) {
176                         if (safe_read (dst, start, file_cnt, abl) == 0) {
177                                 return cnt;
178                         }
179                         return 0;
180                 }
181         }
182
183         Sample* interleave_buf = get_interleave_buffer (file_cnt * n_channels);
184
185         if (safe_read (interleave_buf, start, file_cnt, abl) != 0) {
186                 return 0;
187         }
188
189         Sample *ptr = interleave_buf + _channel;
190
191         /* stride through the interleaved data */
192
193         for (framecnt_t n = 0; n < file_cnt; ++n) {
194                 dst[n] = *ptr;
195                 ptr += n_channels;
196         }
197
198         return cnt;
199 }
200
201 float
202 CoreAudioSource::sample_rate() const
203 {
204         CAStreamBasicDescription client_asbd;
205
206         try {
207                 client_asbd = af.GetClientDataFormat ();
208         } catch (CAXException& cax) {
209                 error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
210                 return 0.0;
211         }
212
213         return client_asbd.mSampleRate;
214 }
215
216 int
217 CoreAudioSource::update_header (framepos_t, struct tm&, time_t)
218 {
219         return 0;
220 }
221
222 int
223 CoreAudioSource::get_soundfile_info (string path, SoundFileInfo& _info, string&)
224 {
225         FSRef ref;
226         ExtAudioFileRef af = 0;
227         UInt32 size;
228         CFStringRef name;
229         int ret = -1;
230
231         if (FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0) != noErr) {
232                 goto out;
233         }
234
235         if (ExtAudioFileOpen(&ref, &af) != noErr) {
236                 goto out;
237         }
238
239         AudioStreamBasicDescription absd;
240         memset(&absd, 0, sizeof(absd));
241         size = sizeof(AudioStreamBasicDescription);
242         if (ExtAudioFileGetProperty (af, kExtAudioFileProperty_FileDataFormat, &size, &absd) != noErr) {
243                 goto out;
244         }
245
246         _info.samplerate = absd.mSampleRate;
247         _info.channels   = absd.mChannelsPerFrame;
248
249         size = sizeof(_info.length);
250         if (ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length) != noErr) {
251                 goto out;
252         }
253
254         size = sizeof(CFStringRef);
255         if (AudioFormatGetProperty(kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name) != noErr) {
256                 goto out;
257         }
258
259         _info.format_name = "";
260
261         if (absd.mFormatID == kAudioFormatLinearPCM) {
262                 if (absd.mFormatFlags & kAudioFormatFlagIsBigEndian) {
263                         _info.format_name += "big-endian";
264                 } else {
265                         _info.format_name += "little-endian";
266                 }
267
268                 char buf[32];
269                 snprintf (buf, sizeof (buf), " %" PRIu32 " bit", absd.mBitsPerChannel);
270                 _info.format_name += buf;
271                 _info.format_name += '\n';
272
273                 if (absd.mFormatFlags & kAudioFormatFlagIsFloat) {
274                         _info.format_name += "float";
275                 } else {
276                         if (absd.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
277                                 _info.format_name += "signed";
278                         } else {
279                                 _info.format_name += "unsigned";
280                         }
281                         /* integer is typical, do not show it */
282                 }
283
284                 if (_info.channels > 1) {
285                         if (absd.mFormatFlags & kAudioFormatFlagIsNonInterleaved) {
286                                 _info.format_name += " noninterleaved";
287                         }
288                         /* interleaved is the normal case, do not show it */
289                 }
290
291                 _info.format_name += ' ';
292         }
293
294         switch (absd.mFormatID) {
295         case kAudioFormatLinearPCM:
296                 _info.format_name += "PCM";
297                 break;
298
299         case kAudioFormatAC3:
300                 _info.format_name += "AC3";
301                 break;
302
303         case kAudioFormat60958AC3:
304                 _info.format_name += "60958 AC3";
305                 break;
306
307         case kAudioFormatMPEGLayer1:
308                 _info.format_name += "MPEG-1";
309                 break;
310
311         case kAudioFormatMPEGLayer2:
312                 _info.format_name += "MPEG-2";
313                 break;
314
315         case kAudioFormatMPEGLayer3:
316                 _info.format_name += "MPEG-3";
317                 break;
318
319         case kAudioFormatAppleIMA4:
320                 _info.format_name += "IMA-4";
321                 break;
322
323         case kAudioFormatMPEG4AAC:
324                 _info.format_name += "AAC";
325                 break;
326
327         case kAudioFormatMPEG4CELP:
328                 _info.format_name += "CELP";
329                 break;
330
331         case kAudioFormatMPEG4HVXC:
332                 _info.format_name += "HVXC";
333                 break;
334
335         case kAudioFormatMPEG4TwinVQ:
336                 _info.format_name += "TwinVQ";
337                 break;
338
339         /* these really shouldn't show up, but we should do something
340            somewhere else to make sure that doesn't happen. until
341            that is guaranteed, print something anyway.
342         */
343
344         case kAudioFormatTimeCode:
345                 _info.format_name += "timecode";
346                 break;
347
348         case kAudioFormatMIDIStream:
349                 _info.format_name += "MIDI";
350                 break;
351
352         case kAudioFormatParameterValueStream:
353                 _info.format_name += "parameter values";
354                 break;
355         }
356
357         // XXX it would be nice to find a way to get this information if it exists
358
359         _info.timecode = 0;
360         ret = 0;
361
362   out:
363         ExtAudioFileDispose (af);
364         return ret;
365
366 }
367
368 void
369 CoreAudioSource::set_path (const string& p)
370 {
371         FileSource::set_path (p);
372 }