Clean up and hopefully fix handling of logarithmic plugin parameters (fixes #3769).
[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                                 _read_data_count = cnt * sizeof (Sample);
178                                 return cnt;
179                         }
180                         return 0;
181                 }
182         }
183
184         Sample* interleave_buf = get_interleave_buffer (file_cnt * n_channels);
185
186         if (safe_read (interleave_buf, start, file_cnt, abl) != 0) {
187                 return 0;
188         }
189
190         _read_data_count = cnt * sizeof(float);
191
192         Sample *ptr = interleave_buf + _channel;
193
194         /* stride through the interleaved data */
195
196         for (framecnt_t n = 0; n < file_cnt; ++n) {
197                 dst[n] = *ptr;
198                 ptr += n_channels;
199         }
200
201         return cnt;
202 }
203
204 float
205 CoreAudioSource::sample_rate() const
206 {
207         CAStreamBasicDescription client_asbd;
208
209         try {
210                 client_asbd = af.GetClientDataFormat ();
211         } catch (CAXException& cax) {
212                 error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
213                 return 0.0;
214         }
215
216         return client_asbd.mSampleRate;
217 }
218
219 int
220 CoreAudioSource::update_header (framepos_t, struct tm&, time_t)
221 {
222         return 0;
223 }
224
225 int
226 CoreAudioSource::get_soundfile_info (string path, SoundFileInfo& _info, string&)
227 {
228         FSRef ref;
229         ExtAudioFileRef af = 0;
230         size_t size;
231         CFStringRef name;
232         int ret = -1;
233
234         if (FSPathMakeRef ((UInt8*)path.c_str(), &ref, 0) != noErr) {
235                 goto out;
236         }
237
238         if (ExtAudioFileOpen(&ref, &af) != noErr) {
239                 goto out;
240         }
241
242         AudioStreamBasicDescription absd;
243         memset(&absd, 0, sizeof(absd));
244         size = sizeof(AudioStreamBasicDescription);
245         if (ExtAudioFileGetProperty (af, kExtAudioFileProperty_FileDataFormat, &size, &absd) != noErr) {
246                 goto out;
247         }
248
249         _info.samplerate = absd.mSampleRate;
250         _info.channels   = absd.mChannelsPerFrame;
251
252         size = sizeof(_info.length);
253         if (ExtAudioFileGetProperty(af, kExtAudioFileProperty_FileLengthFrames, &size, &_info.length) != noErr) {
254                 goto out;
255         }
256
257         size = sizeof(CFStringRef);
258         if (AudioFormatGetProperty(kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name) != noErr) {
259                 goto out;
260         }
261
262         _info.format_name = "";
263
264         if (absd.mFormatID == kAudioFormatLinearPCM) {
265                 if (absd.mFormatFlags & kAudioFormatFlagIsBigEndian) {
266                         _info.format_name += "big-endian";
267                 } else {
268                         _info.format_name += "little-endian";
269                 }
270
271                 char buf[32];
272                 snprintf (buf, sizeof (buf), " %" PRIu32 " bit", absd.mBitsPerChannel);
273                 _info.format_name += buf;
274                 _info.format_name += '\n';
275
276                 if (absd.mFormatFlags & kAudioFormatFlagIsFloat) {
277                         _info.format_name += "float";
278                 } else {
279                         if (absd.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
280                                 _info.format_name += "signed";
281                         } else {
282                                 _info.format_name += "unsigned";
283                         }
284                         /* integer is typical, do not show it */
285                 }
286
287                 if (_info.channels > 1) {
288                         if (absd.mFormatFlags & kAudioFormatFlagIsNonInterleaved) {
289                                 _info.format_name += " noninterleaved";
290                         }
291                         /* interleaved is the normal case, do not show it */
292                 }
293
294                 _info.format_name += ' ';
295         }
296
297         switch (absd.mFormatID) {
298         case kAudioFormatLinearPCM:
299                 _info.format_name += "PCM";
300                 break;
301
302         case kAudioFormatAC3:
303                 _info.format_name += "AC3";
304                 break;
305
306         case kAudioFormat60958AC3:
307                 _info.format_name += "60958 AC3";
308                 break;
309
310         case kAudioFormatMPEGLayer1:
311                 _info.format_name += "MPEG-1";
312                 break;
313
314         case kAudioFormatMPEGLayer2:
315                 _info.format_name += "MPEG-2";
316                 break;
317
318         case kAudioFormatMPEGLayer3:
319                 _info.format_name += "MPEG-3";
320                 break;
321
322         case kAudioFormatAppleIMA4:
323                 _info.format_name += "IMA-4";
324                 break;
325
326         case kAudioFormatMPEG4AAC:
327                 _info.format_name += "AAC";
328                 break;
329
330         case kAudioFormatMPEG4CELP:
331                 _info.format_name += "CELP";
332                 break;
333
334         case kAudioFormatMPEG4HVXC:
335                 _info.format_name += "HVXC";
336                 break;
337
338         case kAudioFormatMPEG4TwinVQ:
339                 _info.format_name += "TwinVQ";
340                 break;
341
342         /* these really shouldn't show up, but we should do something
343            somewhere else to make sure that doesn't happen. until
344            that is guaranteed, print something anyway.
345         */
346
347         case kAudioFormatTimeCode:
348                 _info.format_name += "timecode";
349                 break;
350
351         case kAudioFormatMIDIStream:
352                 _info.format_name += "MIDI";
353                 break;
354
355         case kAudioFormatParameterValueStream:
356                 _info.format_name += "parameter values";
357                 break;
358         }
359
360         // XXX it would be nice to find a way to get this information if it exists
361
362         _info.timecode = 0;
363         ret = 0;
364
365   out:
366         ExtAudioFileDispose (af);
367         return ret;
368
369 }
370
371 void
372 CoreAudioSource::set_path (const string& p)
373 {
374         FileSource::set_path (p);
375 }