Apply panners/automation patch from torbenh (Panner is-a Processor).
[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                 
80                 error << string_compose(_("CoreAudioSource: cannot open file \"%1\" for %2"), 
81                                         _path, (writable() ? "read+write" : "reading")) << endmsg;
82                 throw failed_constructor ();
83         }
84 }
85
86 CoreAudioSource::~CoreAudioSource ()
87 {
88         GoingAway (); /* EMIT SIGNAL */
89 }
90
91 int
92 CoreAudioSource::safe_read (Sample* dst, nframes_t start, nframes_t cnt, AudioBufferList& abl) const
93 {
94         nframes_t nread = 0;
95
96         while (nread < cnt) {
97                 
98                 try {
99                         af.Seek (start+nread);
100                 } catch (CAXException& cax) {
101                         error << string_compose("CoreAudioSource: %1 to %2 (%3)", cax.mOperation, start+nread, _name.substr (1)) << endmsg;
102                         return -1;
103                 }
104                 
105                 UInt32 new_cnt = cnt - nread;
106                 
107                 abl.mBuffers[0].mDataByteSize = new_cnt * n_channels * sizeof(Sample);
108                 abl.mBuffers[0].mData = dst + nread;
109                         
110                 try {
111                         af.Read (new_cnt, &abl);
112                 } catch (CAXException& cax) {
113                         error << string_compose("CoreAudioSource: %1 (%2)", cax.mOperation, _name);
114                         return -1;
115                 }
116
117                 if (new_cnt == 0) {
118                         /* EOF */
119                         if (start+cnt == _length) {
120                                 /* we really did hit the end */
121                                 nread = cnt;
122                         }
123                         break;
124                 }
125
126                 nread += new_cnt;
127         }
128
129         if (nread < cnt) {
130                 return -1;
131         } else {
132                 return 0;
133         }
134 }
135         
136
137 nframes_t
138 CoreAudioSource::read_unlocked (Sample *dst, nframes_t start, nframes_t cnt) const
139 {
140         nframes_t file_cnt;
141         AudioBufferList abl;
142
143         abl.mNumberBuffers = 1;
144         abl.mBuffers[0].mNumberChannels = n_channels;
145
146         if (start > _length) {
147
148                 /* read starts beyond end of data, just memset to zero */
149                 
150                 file_cnt = 0;
151
152         } else if (start + cnt > _length) {
153                 
154                 /* read ends beyond end of data, read some, memset the rest */
155                 
156                 file_cnt = _length - start;
157
158         } else {
159                 
160                 /* read is entirely within data */
161
162                 file_cnt = cnt;
163         }
164
165         if (file_cnt != cnt) {
166                 nframes_t delta = cnt - file_cnt;
167                 memset (dst+file_cnt, 0, sizeof (Sample) * delta);
168         }
169
170         if (file_cnt) {
171
172                 if (n_channels == 1) {
173                         if (safe_read (dst, start, file_cnt, abl) == 0) {
174                                 _read_data_count = cnt * sizeof (Sample);
175                                 return cnt;
176                         }
177                         return 0;
178                 }
179         }
180
181         Sample* interleave_buf = get_interleave_buffer (file_cnt * n_channels);
182         
183         if (safe_read (interleave_buf, start, file_cnt, abl) != 0) {
184                 return 0;
185         }
186
187         _read_data_count = cnt * sizeof(float);
188
189         Sample *ptr = interleave_buf + _channel;
190         
191         /* stride through the interleaved data */
192         
193         for (uint32_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 (nframes_t when, struct tm&, time_t)
218 {
219         return 0;
220 }
221
222 int
223 CoreAudioSource::get_soundfile_info (string path, SoundFileInfo& _info, string& error_msg)
224 {
225         FSRef ref; 
226         ExtAudioFileRef af = 0;
227         size_t 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 = CFStringRefToStdString(name);
260
261         // XXX it would be nice to find a way to get this information if it exists
262
263         _info.timecode = 0;
264         ret = 0;
265         
266   out:
267         ExtAudioFileDispose (af);
268         return ret;
269         
270 }