working version of AU parameter change notification, presets too
[ardour.git] / libs / ardour / audio_unit.cc
1 /*
2     Copyright (C) 2006-2009 Paul Davis 
3     Some portions Copyright (C) Sophia Poirier.
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 <sstream>
22 #include <errno.h>
23 #include <string.h>
24 #include <math.h>
25
26 #include <pbd/transmitter.h>
27 #include <pbd/xml++.h>
28 #include <pbd/whitespace.h>
29 #include <pbd/pathscanner.h>
30
31 #include <glibmm/thread.h>
32 #include <glibmm/fileutils.h>
33 #include <glibmm/miscutils.h>
34
35 #include <ardour/ardour.h>
36 #include <ardour/audioengine.h>
37 #include <ardour/io.h>
38 #include <ardour/audio_unit.h>
39 #include <ardour/session.h>
40 #include <ardour/tempo.h>
41 #include <ardour/utils.h>
42
43 #include <appleutility/CAAudioUnit.h>
44 #include <appleutility/CAAUParameter.h>
45
46 #include <CoreFoundation/CoreFoundation.h>
47 #include <CoreServices/CoreServices.h>
48 #include <AudioUnit/AudioUnit.h>
49 #include <AudioToolbox/AudioUnitUtilities.h>
50
51 #include "i18n.h"
52
53 using namespace std;
54 using namespace PBD;
55 using namespace ARDOUR;
56
57 #ifndef AU_STATE_SUPPORT
58 static bool seen_get_state_message = false;
59 static bool seen_set_state_message = false;
60 static bool seen_loading_message = false;
61 static bool seen_saving_message = false;
62 #endif
63
64 AUPluginInfo::CachedInfoMap AUPluginInfo::cached_info;
65
66 static string preset_search_path = "/Library/Audio/Presets:/Network/Library/Audio/Presets";
67 static string preset_suffix = ".aupreset";
68 static bool preset_search_path_initialized = false;
69
70 static OSStatus 
71 _render_callback(void *userData,
72                  AudioUnitRenderActionFlags *ioActionFlags,
73                  const AudioTimeStamp    *inTimeStamp,
74                  UInt32       inBusNumber,
75                  UInt32       inNumberFrames,
76                  AudioBufferList*       ioData)
77 {
78         if (userData) {
79                 return ((AUPlugin*)userData)->render_callback (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
80         }
81         return paramErr;
82 }
83
84 static OSStatus 
85 _get_beat_and_tempo_callback (void*                userData,
86                               Float64*             outCurrentBeat, 
87                               Float64*             outCurrentTempo)
88 {
89         if (userData) {
90                 return ((AUPlugin*)userData)->get_beat_and_tempo_callback (outCurrentBeat, outCurrentTempo);
91         }
92     
93         return paramErr;
94 }
95
96 static OSStatus 
97 _get_musical_time_location_callback (void *     userData,
98                                      UInt32 *   outDeltaSampleOffsetToNextBeat,
99                                      Float32 *  outTimeSig_Numerator,
100                                      UInt32 *   outTimeSig_Denominator,
101                                      Float64 *  outCurrentMeasureDownBeat)
102 {
103         if (userData) {
104                 return ((AUPlugin*)userData)->get_musical_time_location_callback (outDeltaSampleOffsetToNextBeat,
105                                                                                   outTimeSig_Numerator,
106                                                                                   outTimeSig_Denominator,
107                                                                                   outCurrentMeasureDownBeat);
108         }
109         return paramErr;
110 }
111
112 static OSStatus 
113 _get_transport_state_callback (void*     userData,
114                                Boolean*  outIsPlaying,
115                                Boolean*  outTransportStateChanged,
116                                Float64*  outCurrentSampleInTimeLine,
117                                Boolean*  outIsCycling,
118                                Float64*  outCycleStartBeat,
119                                Float64*  outCycleEndBeat)
120 {
121         if (userData) {
122                 return ((AUPlugin*)userData)->get_transport_state_callback (outIsPlaying, outTransportStateChanged,
123                                                                             outCurrentSampleInTimeLine, outIsCycling,
124                                                                             outCycleStartBeat, outCycleEndBeat);
125         }
126         return paramErr;
127 }
128
129
130 static int 
131 save_property_list (CFPropertyListRef propertyList, Glib::ustring path)
132
133 {
134         CFDataRef xmlData;
135         int fd;
136
137         // Convert the property list into XML data.
138         
139         xmlData = CFPropertyListCreateXMLData( kCFAllocatorDefault, propertyList);
140
141         if (!xmlData) {
142                 error << _("Could not create XML version of property list") << endmsg;
143                 return -1;
144         }
145
146         // Write the XML data to the file.
147
148         fd = open (path.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0664);
149         while (fd < 0) {
150                 if (errno == EEXIST) {
151                         /* tell any UI's that this file already exists and ask them what to do */
152                         bool overwrite = Plugin::PresetFileExists(); // EMIT SIGNAL
153                         if (overwrite) {
154                                 fd = open (path.c_str(), O_WRONLY, 0664);
155                                 continue;
156                         } else {
157                                 return 0;
158                         }
159                 }
160                 error << string_compose (_("Cannot open preset file %1 (%2)"), path, strerror (errno)) << endmsg;
161                 CFRelease (xmlData);
162                 return -1;
163         }
164
165         size_t cnt = CFDataGetLength (xmlData);
166
167         if (write (fd, CFDataGetBytePtr (xmlData), cnt) != (ssize_t) cnt) {
168                 CFRelease (xmlData);
169                 close (fd);
170                 return -1;
171         }
172
173         close (fd);
174         return 0;
175 }
176  
177
178 static CFPropertyListRef 
179 load_property_list (Glib::ustring path) 
180 {
181         int fd;
182         CFPropertyListRef propertyList;
183         CFDataRef         xmlData;
184         CFStringRef       errorString;
185
186         // Read the XML file.
187         
188         if ((fd = open (path.c_str(), O_RDONLY)) < 0) {
189                 return propertyList;
190
191         }
192         
193         off_t len = lseek (fd, 0, SEEK_END);
194         char* buf = new char[len];
195         lseek (fd, 0, SEEK_SET);
196
197         if (read (fd, buf, len) != len) {
198                 delete [] buf;
199                 close (fd);
200                 return propertyList;
201         }
202         
203         close (fd);
204
205         xmlData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (UInt8*) buf, len, kCFAllocatorNull);
206         
207         // Reconstitute the dictionary using the XML data.
208         
209         propertyList = CFPropertyListCreateFromXMLData( kCFAllocatorDefault,
210                                                         xmlData,
211                                                         kCFPropertyListImmutable,
212                                                         &errorString);
213
214         CFRelease (xmlData);
215         delete [] buf;
216
217         return propertyList;
218 }
219
220 //-----------------------------------------------------------------------------
221 static void 
222 set_preset_name_in_plist (CFPropertyListRef plist, string preset_name)
223 {
224         if (!plist) {
225                 return;
226         }
227         CFStringRef pn = CFStringCreateWithCString (kCFAllocatorDefault, preset_name.c_str(), kCFStringEncodingUTF8);
228
229         if (CFGetTypeID (plist) == CFDictionaryGetTypeID()) {
230                 CFDictionarySetValue ((CFMutableDictionaryRef)plist, CFSTR(kAUPresetNameKey), pn);
231         }
232         
233         CFRelease (pn);
234 }
235
236 //-----------------------------------------------------------------------------
237 static std::string
238 get_preset_name_in_plist (CFPropertyListRef plist)
239 {
240         std::string ret;
241
242         if (!plist) {
243                 return ret;
244         }
245
246         if (CFGetTypeID (plist) == CFDictionaryGetTypeID()) {
247                 const void *p = CFDictionaryGetValue ((CFMutableDictionaryRef)plist, CFSTR(kAUPresetNameKey));
248                 if (p) {
249                         CFStringRef str = (CFStringRef) p;
250                         int len = CFStringGetLength(str);
251                         len =  (len * 2) + 1;
252                         char local_buffer[len];
253                         if (CFStringGetCString (str, local_buffer, len, kCFStringEncodingUTF8)) {
254                                 ret = local_buffer;
255                         }
256                 } 
257         }
258         return ret;
259 }
260
261 //--------------------------------------------------------------------------
262 // general implementation for ComponentDescriptionsMatch() and ComponentDescriptionsMatch_Loosely()
263 // if inIgnoreType is true, then the type code is ignored in the ComponentDescriptions
264 Boolean ComponentDescriptionsMatch_General(const ComponentDescription * inComponentDescription1, const ComponentDescription * inComponentDescription2, Boolean inIgnoreType);
265 Boolean ComponentDescriptionsMatch_General(const ComponentDescription * inComponentDescription1, const ComponentDescription * inComponentDescription2, Boolean inIgnoreType)
266 {
267         if ( (inComponentDescription1 == NULL) || (inComponentDescription2 == NULL) )
268                 return FALSE;
269
270         if ( (inComponentDescription1->componentSubType == inComponentDescription2->componentSubType) 
271                         && (inComponentDescription1->componentManufacturer == inComponentDescription2->componentManufacturer) )
272         {
273                 // only sub-type and manufacturer IDs need to be equal
274                 if (inIgnoreType)
275                         return TRUE;
276                 // type, sub-type, and manufacturer IDs all need to be equal in order to call this a match
277                 else if (inComponentDescription1->componentType == inComponentDescription2->componentType)
278                         return TRUE;
279         }
280
281         return FALSE;
282 }
283
284 //--------------------------------------------------------------------------
285 // general implementation for ComponentAndDescriptionMatch() and ComponentAndDescriptionMatch_Loosely()
286 // if inIgnoreType is true, then the type code is ignored in the ComponentDescriptions
287 Boolean ComponentAndDescriptionMatch_General(Component inComponent, const ComponentDescription * inComponentDescription, Boolean inIgnoreType);
288 Boolean ComponentAndDescriptionMatch_General(Component inComponent, const ComponentDescription * inComponentDescription, Boolean inIgnoreType)
289 {
290         OSErr status;
291         ComponentDescription desc;
292
293         if ( (inComponent == NULL) || (inComponentDescription == NULL) )
294                 return FALSE;
295
296         // get the ComponentDescription of the input Component
297         status = GetComponentInfo(inComponent, &desc, NULL, NULL, NULL);
298         if (status != noErr)
299                 return FALSE;
300
301         // check if the Component's ComponentDescription matches the input ComponentDescription
302         return ComponentDescriptionsMatch_General(&desc, inComponentDescription, inIgnoreType);
303 }
304
305 //--------------------------------------------------------------------------
306 // determine if 2 ComponentDescriptions are basically equal
307 // (by that, I mean that the important identifying values are compared, 
308 // but not the ComponentDescription flags)
309 Boolean ComponentDescriptionsMatch(const ComponentDescription * inComponentDescription1, const ComponentDescription * inComponentDescription2)
310 {
311         return ComponentDescriptionsMatch_General(inComponentDescription1, inComponentDescription2, FALSE);
312 }
313
314 //--------------------------------------------------------------------------
315 // determine if 2 ComponentDescriptions have matching sub-type and manufacturer codes
316 Boolean ComponentDescriptionsMatch_Loose(const ComponentDescription * inComponentDescription1, const ComponentDescription * inComponentDescription2)
317 {
318         return ComponentDescriptionsMatch_General(inComponentDescription1, inComponentDescription2, TRUE);
319 }
320
321 //--------------------------------------------------------------------------
322 // determine if a ComponentDescription basically matches that of a particular Component
323 Boolean ComponentAndDescriptionMatch(Component inComponent, const ComponentDescription * inComponentDescription)
324 {
325         return ComponentAndDescriptionMatch_General(inComponent, inComponentDescription, FALSE);
326 }
327
328 //--------------------------------------------------------------------------
329 // determine if a ComponentDescription matches only the sub-type and manufacturer codes of a particular Component
330 Boolean ComponentAndDescriptionMatch_Loosely(Component inComponent, const ComponentDescription * inComponentDescription)
331 {
332         return ComponentAndDescriptionMatch_General(inComponent, inComponentDescription, TRUE);
333 }
334
335
336 AUPlugin::AUPlugin (AudioEngine& engine, Session& session, boost::shared_ptr<CAComponent> _comp)
337         : Plugin (engine, session),
338           comp (_comp),
339           unit (new CAAudioUnit),
340           initialized (false),
341           buffers (0),
342           current_maxbuf (0),
343           current_offset (0),
344           current_buffers (0),
345         frames_processed (0)
346 {                       
347         if (!preset_search_path_initialized) {
348                 Glib::ustring p = Glib::get_home_dir();
349                 p += "/Library/Audio/Presets:";
350                 p += preset_search_path;
351                 preset_search_path = p;
352                 preset_search_path_initialized = true;
353         }
354
355         init ();
356 }
357
358 AUPlugin::AUPlugin (const AUPlugin& other)
359         : Plugin (other)
360         , comp (other.get_comp())
361         , unit (new CAAudioUnit)
362         , initialized (false)
363         , buffers (0)
364         , current_maxbuf (0)
365         , current_offset (0)
366         , current_buffers (0)
367         , frames_processed (0)
368           
369 {
370         init ();
371 }
372
373 AUPlugin::~AUPlugin ()
374 {
375         if (unit) {
376                 unit->Uninitialize ();
377         }
378
379         if (buffers) {
380                 free (buffers);
381         }
382 }
383
384 void
385 AUPlugin::discover_factory_presets ()
386 {
387         CFArrayRef presets;
388         UInt32 dataSize = 0;
389         OSStatus err = unit->GetPropertyInfo (kAudioUnitProperty_FactoryPresets,
390                                               kAudioUnitScope_Global, 0,
391                                               &dataSize, NULL);
392         if (err || !dataSize) {
393                 /* no presets? */
394                 return;
395         }
396
397         dataSize = sizeof (presets);
398
399         if ((err = unit->GetProperty (kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, (void*) &presets, &dataSize)) != 0) {
400                 cerr << "cannot get factory preset info: " << err << endl;
401                 return;
402         }
403
404         CFIndex cnt = CFArrayGetCount (presets);
405
406         for (CFIndex i = 0; i < cnt; ++i) {
407                 AUPreset* preset = (AUPreset*) CFArrayGetValueAtIndex (presets, i);
408
409                 string name = CFStringRefToStdString (preset->presetName);
410                 factory_preset_map[name] = preset->presetNumber;
411         }
412 }
413
414 void
415 AUPlugin::init ()
416 {
417         OSErr err;
418
419         try {
420                 err = CAAudioUnit::Open (*(comp.get()), *unit);
421         } catch (...) {
422                 error << _("Exception thrown during AudioUnit plugin loading - plugin ignored") << endmsg;
423                 throw failed_constructor();
424         }
425
426         if (err != noErr) {
427                 error << _("AudioUnit: Could not convert CAComponent to CAAudioUnit") << endmsg;
428                 throw failed_constructor ();
429         }
430         
431         AURenderCallbackStruct renderCallbackInfo;
432
433         renderCallbackInfo.inputProc = _render_callback;
434         renderCallbackInfo.inputProcRefCon = this;
435
436         if ((err = unit->SetProperty (kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 
437                                          0, (void*) &renderCallbackInfo, sizeof(renderCallbackInfo))) != 0) {
438                 cerr << "cannot install render callback (err = " << err << ')' << endl;
439                 throw failed_constructor();
440         }
441
442         /* tell the plugin about tempo/meter/transport callbacks in case it wants them */
443
444         HostCallbackInfo info;
445         memset (&info, 0, sizeof (HostCallbackInfo));
446         info.hostUserData = this;
447         info.beatAndTempoProc = _get_beat_and_tempo_callback;
448         info.musicalTimeLocationProc = _get_musical_time_location_callback;
449         info.transportStateProc = _get_transport_state_callback;
450         
451         //ignore result of this - don't care if the property isn't supported
452         unit->SetProperty (kAudioUnitProperty_HostCallbacks, 
453                            kAudioUnitScope_Global, 
454                            0, //elementID 
455                            &info,
456                            sizeof (HostCallbackInfo));
457
458         unit->GetElementCount (kAudioUnitScope_Global, global_elements);
459         unit->GetElementCount (kAudioUnitScope_Input, input_elements);
460         unit->GetElementCount (kAudioUnitScope_Output, output_elements);
461
462         /* these keep track of *configured* channel set up,
463            not potential set ups.
464         */
465
466         input_channels = -1;
467         output_channels = -1;
468
469         if (_set_block_size (_session.get_block_size())) {
470                 error << _("AUPlugin: cannot set processing block size") << endmsg;
471                 throw failed_constructor();
472         }
473
474         discover_parameters ();
475         discover_factory_presets ();
476
477         Plugin::setup_controls ();
478 }
479
480 void
481 AUPlugin::discover_parameters ()
482 {
483         /* discover writable parameters */
484         
485         AudioUnitScope scopes[] = { 
486                 kAudioUnitScope_Global,
487                 kAudioUnitScope_Output,
488                 kAudioUnitScope_Input
489         };
490
491         descriptors.clear ();
492
493         for (uint32_t i = 0; i < sizeof (scopes) / sizeof (scopes[0]); ++i) {
494
495                 AUParamInfo param_info (unit->AU(), false, false, scopes[i]);
496                 
497                 for (uint32_t i = 0; i < param_info.NumParams(); ++i) {
498
499                         AUParameterDescriptor d;
500
501                         d.id = param_info.ParamID (i);
502
503                         const CAAUParameter* param = param_info.GetParamInfo (d.id);
504                         const AudioUnitParameterInfo& info (param->ParamInfo());
505
506                         const int len = CFStringGetLength (param->GetName());;
507                         char local_buffer[len*2];
508                         Boolean good = CFStringGetCString(param->GetName(),local_buffer,len*2,kCFStringEncodingMacRoman);
509                         if (!good) {
510                                 d.label = "???";
511                         } else {
512                                 d.label = local_buffer;
513                         }
514
515                         d.scope = param_info.GetScope ();
516                         d.element = param_info.GetElement ();
517
518                         /* info.units to consider */
519                         /*
520                           kAudioUnitParameterUnit_Generic             = 0
521                           kAudioUnitParameterUnit_Indexed             = 1
522                           kAudioUnitParameterUnit_Boolean             = 2
523                           kAudioUnitParameterUnit_Percent             = 3
524                           kAudioUnitParameterUnit_Seconds             = 4
525                           kAudioUnitParameterUnit_SampleFrames        = 5
526                           kAudioUnitParameterUnit_Phase               = 6
527                           kAudioUnitParameterUnit_Rate                = 7
528                           kAudioUnitParameterUnit_Hertz               = 8
529                           kAudioUnitParameterUnit_Cents               = 9
530                           kAudioUnitParameterUnit_RelativeSemiTones   = 10
531                           kAudioUnitParameterUnit_MIDINoteNumber      = 11
532                           kAudioUnitParameterUnit_MIDIController      = 12
533                           kAudioUnitParameterUnit_Decibels            = 13
534                           kAudioUnitParameterUnit_LinearGain          = 14
535                           kAudioUnitParameterUnit_Degrees             = 15
536                           kAudioUnitParameterUnit_EqualPowerCrossfade = 16
537                           kAudioUnitParameterUnit_MixerFaderCurve1    = 17
538                           kAudioUnitParameterUnit_Pan                 = 18
539                           kAudioUnitParameterUnit_Meters              = 19
540                           kAudioUnitParameterUnit_AbsoluteCents       = 20
541                           kAudioUnitParameterUnit_Octaves             = 21
542                           kAudioUnitParameterUnit_BPM                 = 22
543                           kAudioUnitParameterUnit_Beats               = 23
544                           kAudioUnitParameterUnit_Milliseconds        = 24
545                           kAudioUnitParameterUnit_Ratio               = 25
546                         */
547
548                         /* info.flags to consider */
549
550                         /*
551
552                           kAudioUnitParameterFlag_CFNameRelease       = (1L << 4)
553                           kAudioUnitParameterFlag_HasClump            = (1L << 20)
554                           kAudioUnitParameterFlag_HasName             = (1L << 21)
555                           kAudioUnitParameterFlag_DisplayLogarithmic  = (1L << 22)
556                           kAudioUnitParameterFlag_IsHighResolution    = (1L << 23)
557                           kAudioUnitParameterFlag_NonRealTime         = (1L << 24)
558                           kAudioUnitParameterFlag_CanRamp             = (1L << 25)
559                           kAudioUnitParameterFlag_ExpertMode          = (1L << 26)
560                           kAudioUnitParameterFlag_HasCFNameString     = (1L << 27)
561                           kAudioUnitParameterFlag_IsGlobalMeta        = (1L << 28)
562                           kAudioUnitParameterFlag_IsElementMeta       = (1L << 29)
563                           kAudioUnitParameterFlag_IsReadable          = (1L << 30)
564                           kAudioUnitParameterFlag_IsWritable          = (1L << 31)
565                         */
566
567                         d.lower = info.minValue;
568                         d.upper = info.maxValue;
569                         d.default_value = info.defaultValue;
570
571                         d.integer_step = (info.unit & kAudioUnitParameterUnit_Indexed);
572                         d.toggled = (info.unit & kAudioUnitParameterUnit_Boolean) ||
573                                 (d.integer_step && ((d.upper - d.lower) == 1.0));
574                         d.sr_dependent = (info.unit & kAudioUnitParameterUnit_SampleFrames);
575                         d.automatable = !d.toggled && 
576                                 !(info.flags & kAudioUnitParameterFlag_NonRealTime) &&
577                                 (info.flags & kAudioUnitParameterFlag_IsWritable);
578                         
579                         d.logarithmic = (info.flags & kAudioUnitParameterFlag_DisplayLogarithmic);
580                         d.unit = info.unit;
581
582                         d.step = 1.0;
583                         d.smallstep = 0.1;
584                         d.largestep = 10.0;
585                         d.min_unbound = 0; // lower is bound
586                         d.max_unbound = 0; // upper is bound
587
588                         descriptors.push_back (d);
589                 }
590         }
591 }
592
593
594 string
595 AUPlugin::unique_id () const
596 {
597         return AUPluginInfo::stringify_descriptor (comp->Desc());
598 }
599
600 const char *
601 AUPlugin::label () const
602 {
603         return _info->name.c_str();
604 }
605
606 uint32_t
607 AUPlugin::parameter_count () const
608 {
609         return descriptors.size();
610 }
611
612 float
613 AUPlugin::default_value (uint32_t port)
614 {
615         if (port < descriptors.size()) {
616                 return descriptors[port].default_value;
617         }
618
619         return 0;
620 }
621
622 nframes_t
623 AUPlugin::latency () const
624 {
625         return unit->Latency() * _session.frame_rate();
626 }
627
628 void
629 AUPlugin::set_parameter (uint32_t which, float val)
630 {
631         if (which < descriptors.size()) {
632                 const AUParameterDescriptor& d (descriptors[which]);
633                 unit->SetParameter (d.id, d.scope, d.element, val);
634
635                 /* tell the world what we did */
636
637                 AudioUnitEvent theEvent;
638                 
639                 theEvent.mEventType = kAudioUnitEvent_ParameterValueChange;
640                 theEvent.mArgument.mParameter.mAudioUnit = unit->AU();
641                 theEvent.mArgument.mParameter.mParameterID = d.id;
642                 theEvent.mArgument.mParameter.mScope = d.scope;
643                 theEvent.mArgument.mParameter.mElement = d.element;
644                 
645                 AUEventListenerNotify (NULL, NULL, &theEvent);
646         }
647 }
648
649 float
650 AUPlugin::get_parameter (uint32_t which) const
651 {
652         float val = 0.0;
653         if (which < descriptors.size()) {
654                 const AUParameterDescriptor& d (descriptors[which]);
655                 unit->GetParameter(d.id, d.scope, d.element, val);
656         }
657         return val;
658 }
659
660 int
661 AUPlugin::get_parameter_descriptor (uint32_t which, ParameterDescriptor& pd) const
662 {
663         if (which < descriptors.size()) {
664                 pd = descriptors[which];
665                 return 0;
666         } 
667         return -1;
668 }
669
670 uint32_t
671 AUPlugin::nth_parameter (uint32_t which, bool& ok) const
672 {
673         if (which < descriptors.size()) {
674                 ok = true;
675                 return which;
676         }
677         ok = false;
678         return 0;
679 }
680
681 void
682 AUPlugin::activate ()
683 {
684         if (!initialized) {
685                 OSErr err;
686                 if ((err = unit->Initialize()) != noErr) {
687                         error << string_compose (_("AUPlugin: %1 cannot initialize plugin (err = %2)"), name(), err) << endmsg;
688                 } else {
689                         frames_processed = 0;
690                         initialized = true;
691                 }
692         }
693 }
694
695 void
696 AUPlugin::deactivate ()
697 {
698         unit->GlobalReset ();
699 }
700
701 void
702 AUPlugin::set_block_size (nframes_t nframes)
703 {
704         _set_block_size (nframes);
705 }
706
707 int
708 AUPlugin::_set_block_size (nframes_t nframes)
709 {
710         bool was_initialized = initialized;
711         UInt32 numFrames = nframes;
712         OSErr err;
713
714         if (initialized) {
715                 unit->Uninitialize ();
716                 initialized = false;
717         }
718
719         if ((err = unit->SetProperty (kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 
720                                       0, &numFrames, sizeof (numFrames))) != noErr) {
721                 cerr << "cannot set max frames (err = " << err << ')' << endl;
722                 return -1;
723         }
724
725         if (was_initialized) {
726                 activate ();
727         }
728
729         return 0;
730 }
731
732 int32_t
733 AUPlugin::configure_io (int32_t in, int32_t out)
734 {
735         AudioStreamBasicDescription streamFormat;
736
737         streamFormat.mSampleRate = _session.frame_rate();
738         streamFormat.mFormatID = kAudioFormatLinearPCM;
739         streamFormat.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked|kAudioFormatFlagIsNonInterleaved;
740
741 #ifdef __LITTLE_ENDIAN__
742         /* relax */
743 #else
744         streamFormat.mFormatFlags |= kAudioFormatFlagIsBigEndian;
745 #endif
746
747         streamFormat.mBitsPerChannel = 32;
748         streamFormat.mFramesPerPacket = 1;
749
750         /* apple says that for non-interleaved data, these
751            values always refer to a single channel.
752         */
753         streamFormat.mBytesPerPacket = 4;
754         streamFormat.mBytesPerFrame = 4;
755
756         streamFormat.mChannelsPerFrame = in;
757
758         if (set_input_format (streamFormat) != 0) {
759                 return -1;
760         }
761
762         streamFormat.mChannelsPerFrame = out;
763
764         if (set_output_format (streamFormat) != 0) {
765                 return -1;
766         }
767
768         return Plugin::configure_io (in, out);
769 }
770
771 int32_t
772 AUPlugin::can_do (int32_t in, int32_t& out)
773 {
774         // XXX as of May 13th 2008, AU plugin support returns a count of either 1 or -1. We never
775         // attempt to multiply-instantiate plugins to meet io configurations.
776
777         int32_t plugcnt = -1;
778         AUPluginInfoPtr pinfo = boost::dynamic_pointer_cast<AUPluginInfo>(get_info());
779
780         out = -1;
781
782         vector<pair<int,int> >& io_configs = pinfo->cache.io_configs;
783
784         for (vector<pair<int,int> >::iterator i = io_configs.begin(); i != io_configs.end(); ++i) {
785
786                 int32_t possible_in = i->first;
787                 int32_t possible_out = i->second;
788
789                 if (possible_out == 0) {
790                         warning << string_compose (_("AU %1 has zero outputs - configuration ignored"), name()) << endmsg;
791                         continue;
792                 }
793
794                 if (possible_in == 0) {
795
796                         /* instrument plugin, always legal but throws away inputs ...
797                         */
798
799                         if (possible_out == -1) {
800                                 /* out much match in (UNLIKELY!!) */
801                                 out = in;
802                                 plugcnt = 1;
803                         } else if (possible_out == -2) {
804                                 /* any configuration possible, pick matching */
805                                 out = in;
806                                 plugcnt = 1;
807                         } else if (possible_out < -2) {
808                                 /* explicit variable number of outputs, pick maximum */
809                                 out = -possible_out;
810                                 plugcnt = 1;
811                         } else {
812                                 /* exact number of outputs */
813                                 out = possible_out;
814                                 plugcnt = 1;
815                         }
816                 }
817                 
818                 if (possible_in == -1) {
819
820                         /* wildcard for input */
821
822                         if (possible_out == -1) {
823                                 /* out much match in */
824                                 out = in;
825                                 plugcnt = 1;
826                         } else if (possible_out == -2) {
827                                 /* any configuration possible, pick matching */
828                                 out = in;
829                                 plugcnt = 1;
830                         } else if (possible_out < -2) {
831                                 /* explicit variable number of outputs, pick maximum */
832                                 out = -possible_out;
833                                 plugcnt = 1;
834                         } else {
835                                 /* exact number of outputs */
836                                 out = possible_out;
837                                 plugcnt = 1;
838                         }
839                 }       
840                         
841                 if (possible_in == -2) {
842
843                         if (possible_out == -1) {
844                                 /* any configuration possible, pick matching */
845                                 out = in;
846                                 plugcnt = 1;
847                         } else if (possible_out == -2) {
848                                 error << string_compose (_("AU plugin %1 has illegal IO configuration (-2,-2)"), name())
849                                       << endmsg;
850                                 plugcnt = -1;
851                         } else if (possible_out < -2) {
852                                 /* explicit variable number of outputs, pick maximum */
853                                 out = -possible_out;
854                                 plugcnt = 1;
855                         } else {
856                                 /* exact number of outputs */
857                                 out = possible_out;
858                                 plugcnt = 1;
859                         }
860                 }
861
862                 if (possible_in < -2) {
863
864                         /* explicit variable number of inputs */
865
866                         if (in > -possible_in) {
867                                 /* request is too large */
868                                 plugcnt = -1;
869                         }
870
871                         if (possible_out == -1) {
872                                 /* out must match in */
873                                 out = in;
874                                 plugcnt = 1;
875                         } else if (possible_out == -2) {
876                                 error << string_compose (_("AU plugin %1 has illegal IO configuration (-2,-2)"), name())
877                                       << endmsg;
878                                 plugcnt = -1;
879                         } else if (possible_out < -2) {
880                                 /* explicit variable number of outputs, pick maximum */
881                                 out = -possible_out;
882                                 plugcnt = 1;
883                         } else {
884                                 /* exact number of outputs */
885                                 out = possible_out;
886                                 plugcnt = 1;
887                         }
888                 }
889
890                 if (possible_in == in) {
891
892                         /* exact number of inputs ... must match obviously */
893                         
894                         if (possible_out == -1) {
895                                 /* out must match in */
896                                 out = in;
897                                 plugcnt = 1;
898                         } else if (possible_out == -2) {
899                                 /* any output configuration, pick matching */
900                                 out = in;
901                                 plugcnt = -1;
902                         } else if (possible_out < -2) {
903                                 /* explicit variable number of outputs, pick maximum */
904                                 out = -possible_out;
905                                 plugcnt = 1;
906                         } else {
907                                 /* exact number of outputs */
908                                 out = possible_out;
909                                 plugcnt = 1;
910                         }
911                 }
912
913                 if (plugcnt == 1) {
914                         break;
915                 }
916
917         }
918
919         return plugcnt;
920 }
921
922 int
923 AUPlugin::set_input_format (AudioStreamBasicDescription& fmt)
924 {
925         return set_stream_format (kAudioUnitScope_Input, input_elements, fmt);
926 }
927
928 int
929 AUPlugin::set_output_format (AudioStreamBasicDescription& fmt)
930 {
931         if (set_stream_format (kAudioUnitScope_Output, output_elements, fmt) != 0) {
932                 return -1;
933         }
934
935         if (buffers) {
936                 free (buffers);
937                 buffers = 0;
938         }
939         
940         buffers = (AudioBufferList *) malloc (offsetof(AudioBufferList, mBuffers) + 
941                                               fmt.mChannelsPerFrame * sizeof(AudioBuffer));
942
943         Glib::Mutex::Lock em (_session.engine().process_lock());
944         IO::MoreOutputs (fmt.mChannelsPerFrame);
945
946         return 0;
947 }
948
949 int
950 AUPlugin::set_stream_format (int scope, uint32_t cnt, AudioStreamBasicDescription& fmt)
951 {
952         OSErr result;
953
954         for (uint32_t i = 0; i < cnt; ++i) {
955                 if ((result = unit->SetFormat (scope, i, fmt)) != 0) {
956                         error << string_compose (_("AUPlugin: could not set stream format for %1/%2 (err = %3)"),
957                                                  (scope == kAudioUnitScope_Input ? "input" : "output"), i, result) << endmsg;
958                         return -1;
959                 }
960         }
961
962         if (scope == kAudioUnitScope_Input) {
963                 input_channels = fmt.mChannelsPerFrame;
964         } else {
965                 output_channels = fmt.mChannelsPerFrame;
966         }
967
968         return 0;
969 }
970
971 uint32_t
972 AUPlugin::input_streams() const
973 {
974         if (input_channels < 0) {
975                 warning << string_compose (_("AUPlugin: %1 input_streams() called without any format set!"), name()) << endmsg;
976                 return 1;
977         }
978         return input_channels;
979 }
980
981
982 uint32_t
983 AUPlugin::output_streams() const
984 {
985         if (output_channels < 0) {
986                 warning << string_compose (_("AUPlugin: %1 output_streams() called without any format set!"), name()) << endmsg;
987                 return 1;
988         }
989         return output_channels;
990 }
991
992 OSStatus 
993 AUPlugin::render_callback(AudioUnitRenderActionFlags *ioActionFlags,
994                           const AudioTimeStamp    *inTimeStamp,
995                           UInt32       inBusNumber,
996                           UInt32       inNumberFrames,
997                           AudioBufferList*       ioData)
998 {
999         /* not much to do - the data is already in the buffers given to us in connect_and_run() */
1000
1001         if (current_maxbuf == 0) {
1002                 error << _("AUPlugin: render callback called illegally!") << endmsg;
1003                 return kAudioUnitErr_CannotDoInCurrentContext;
1004         }
1005
1006         for (uint32_t i = 0; i < current_maxbuf; ++i) {
1007                 ioData->mBuffers[i].mNumberChannels = 1;
1008                 ioData->mBuffers[i].mDataByteSize = sizeof (Sample) * inNumberFrames;
1009                 ioData->mBuffers[i].mData = (*current_buffers)[i] + cb_offset + current_offset;
1010         }
1011
1012         cb_offset += inNumberFrames;
1013
1014         return noErr;
1015 }
1016
1017 int
1018 AUPlugin::connect_and_run (vector<Sample*>& bufs, uint32_t maxbuf, int32_t& in, int32_t& out, nframes_t nframes, nframes_t offset)
1019 {
1020         AudioUnitRenderActionFlags flags = 0;
1021         AudioTimeStamp ts;
1022
1023         current_buffers = &bufs;
1024         current_maxbuf = maxbuf;
1025         current_offset = offset;
1026         cb_offset = 0;
1027
1028         buffers->mNumberBuffers = maxbuf;
1029
1030         for (uint32_t i = 0; i < maxbuf; ++i) {
1031                 buffers->mBuffers[i].mNumberChannels = 1;
1032                 buffers->mBuffers[i].mDataByteSize = nframes * sizeof (Sample);
1033                 buffers->mBuffers[i].mData = 0;
1034         }
1035
1036         ts.mSampleTime = frames_processed;
1037         ts.mFlags = kAudioTimeStampSampleTimeValid;
1038
1039         if (unit->Render (&flags, &ts, 0, nframes, buffers) == noErr) {
1040
1041                 current_maxbuf = 0;
1042                 frames_processed += nframes;
1043                 
1044                 for (uint32_t i = 0; i < maxbuf; ++i) {
1045                         if (bufs[i] + offset != buffers->mBuffers[i].mData) {
1046                                 memcpy (bufs[i]+offset, buffers->mBuffers[i].mData, nframes * sizeof (Sample));
1047                         }
1048                 }
1049                 return 0;
1050         }
1051
1052         return -1;
1053 }
1054
1055 OSStatus 
1056 AUPlugin::get_beat_and_tempo_callback (Float64* outCurrentBeat, 
1057                                        Float64* outCurrentTempo)
1058 {
1059         TempoMap& tmap (_session.tempo_map());
1060
1061         /* more than 1 meter or more than 1 tempo means that a simplistic computation 
1062            (and interpretation) of a beat position will be incorrect. So refuse to 
1063            offer the value.
1064         */
1065
1066         if (tmap.n_tempos() > 1 || tmap.n_meters() > 1) {
1067                 return kAudioUnitErr_CannotDoInCurrentContext;
1068         }
1069
1070         BBT_Time bbt;
1071         TempoMap::Metric metric = tmap.metric_at (_session.transport_frame() + current_offset);
1072         tmap.bbt_time_with_metric (_session.transport_frame() + current_offset, bbt, metric);
1073
1074         if (outCurrentBeat) {
1075                 float beat;
1076                 beat = metric.meter().beats_per_bar() * bbt.bars;
1077                 beat += bbt.beats;
1078                 beat += bbt.ticks / Meter::ticks_per_beat;
1079                 *outCurrentBeat = beat;
1080         }
1081
1082         if (outCurrentTempo) {
1083                 *outCurrentTempo = floor (metric.tempo().beats_per_minute());
1084         }
1085
1086         return noErr;
1087
1088 }
1089
1090 OSStatus 
1091 AUPlugin::get_musical_time_location_callback (UInt32*   outDeltaSampleOffsetToNextBeat,
1092                                               Float32*  outTimeSig_Numerator,
1093                                               UInt32*   outTimeSig_Denominator,
1094                                               Float64*  outCurrentMeasureDownBeat)
1095 {
1096         TempoMap& tmap (_session.tempo_map());
1097
1098         /* more than 1 meter or more than 1 tempo means that a simplistic computation 
1099            (and interpretation) of a beat position will be incorrect. So refuse to 
1100            offer the value.
1101         */
1102
1103         if (tmap.n_tempos() > 1 || tmap.n_meters() > 1) {
1104                 return kAudioUnitErr_CannotDoInCurrentContext;
1105         }
1106
1107         BBT_Time bbt;
1108         TempoMap::Metric metric = tmap.metric_at (_session.transport_frame() + current_offset);
1109         tmap.bbt_time_with_metric (_session.transport_frame() + current_offset, bbt, metric);
1110
1111         if (*outDeltaSampleOffsetToNextBeat) {
1112                 if (bbt.ticks == 0) {
1113                         /* on the beat */
1114                         *outDeltaSampleOffsetToNextBeat = 0;
1115                 } else {
1116                         *outDeltaSampleOffsetToNextBeat = (UInt32) floor (((Meter::ticks_per_beat - bbt.ticks)/Meter::ticks_per_beat) * // fraction of a beat to next beat
1117                                                                           metric.tempo().frames_per_beat(_session.frame_rate(), metric.meter())); // frames per beat
1118                 }
1119         }
1120         
1121         if (*outTimeSig_Numerator) {
1122                 *outTimeSig_Numerator = (UInt32) lrintf (metric.meter().beats_per_bar());
1123         }
1124         if (*outTimeSig_Denominator) {
1125                 *outTimeSig_Denominator = (UInt32) lrintf (metric.meter().note_divisor());
1126         }
1127
1128         if (*outCurrentMeasureDownBeat) {
1129
1130                 /* beat for the start of the bar. 
1131                    1|1|0 -> 1
1132                    2|1|0 -> 1 + beats_per_bar
1133                    3|1|0 -> 1 + (2 * beats_per_bar)
1134                    etc. 
1135                 */
1136
1137                 *outCurrentMeasureDownBeat = 1 + metric.meter().beats_per_bar() * (bbt.bars - 1);
1138         }
1139
1140         return noErr;
1141 }
1142
1143 OSStatus 
1144 AUPlugin::get_transport_state_callback (Boolean*  outIsPlaying,
1145                                         Boolean*  outTransportStateChanged,
1146                                         Float64*  outCurrentSampleInTimeLine,
1147                                         Boolean*  outIsCycling,
1148                                         Float64*  outCycleStartBeat,
1149                                         Float64*  outCycleEndBeat)
1150 {
1151         if (outIsPlaying) {
1152                 *outIsPlaying = _session.transport_rolling();
1153         }
1154
1155         if (outTransportStateChanged) {
1156                 *outTransportStateChanged = false;
1157         }
1158
1159         if (outCurrentSampleInTimeLine) {
1160                 *outCurrentSampleInTimeLine = _session.transport_frame();
1161         }
1162
1163         if (outIsCycling) {
1164                 Location* loc = _session.locations()->auto_loop_location();
1165
1166                 *outIsCycling = (loc && _session.transport_rolling() && _session.get_play_loop());
1167
1168                 if (*outIsCycling) {
1169
1170                         if (outCycleStartBeat || outCycleEndBeat) {
1171
1172                                 TempoMap& tmap (_session.tempo_map());
1173
1174                                 /* more than 1 meter means that a simplistic computation (and interpretation) of 
1175                                    a beat position will be incorrect. so refuse to offer the value.
1176                                 */
1177
1178                                 if (tmap.n_meters() > 1) {
1179                                         return kAudioUnitErr_CannotDoInCurrentContext;
1180                                 }
1181                                 
1182                                 BBT_Time bbt;
1183
1184                                 if (outCycleStartBeat) {
1185                                         TempoMap::Metric metric = tmap.metric_at (loc->start() + current_offset);
1186                                         _session.tempo_map().bbt_time_with_metric (loc->start(), bbt, metric);
1187
1188                                         float beat;
1189                                         beat = metric.meter().beats_per_bar() * bbt.bars;
1190                                         beat += bbt.beats;
1191                                         beat += bbt.ticks / Meter::ticks_per_beat;
1192                                         
1193                                         *outCycleStartBeat = beat;
1194                                 }
1195
1196                                 if (outCycleEndBeat) {
1197                                         TempoMap::Metric metric = tmap.metric_at (loc->end() + current_offset);
1198                                         _session.tempo_map().bbt_time_with_metric (loc->end(), bbt, metric);
1199                                         
1200                                         float beat;
1201                                         beat = metric.meter().beats_per_bar() * bbt.bars;
1202                                         beat += bbt.beats;
1203                                         beat += bbt.ticks / Meter::ticks_per_beat;
1204                                         
1205                                         *outCycleEndBeat = beat;
1206                                 }
1207                         }
1208                 }
1209         }
1210                 
1211         return noErr;
1212 }
1213
1214 set<uint32_t>
1215 AUPlugin::automatable() const
1216 {
1217         set<uint32_t> automates;
1218
1219         for (uint32_t i = 0; i < descriptors.size(); ++i) {
1220                 if (descriptors[i].automatable) {
1221                         automates.insert (i);
1222                 }
1223         }
1224
1225         return automates;
1226 }
1227
1228 string
1229 AUPlugin::describe_parameter (uint32_t param)
1230 {
1231         return descriptors[param].label;
1232 }
1233
1234 void
1235 AUPlugin::print_parameter (uint32_t param, char* buf, uint32_t len) const
1236 {
1237         // NameValue stuff here
1238 }
1239
1240 bool
1241 AUPlugin::parameter_is_audio (uint32_t) const
1242 {
1243         return false;
1244 }
1245
1246 bool
1247 AUPlugin::parameter_is_control (uint32_t) const
1248 {
1249         return true;
1250 }
1251
1252 bool
1253 AUPlugin::parameter_is_input (uint32_t) const
1254 {
1255         return false;
1256 }
1257
1258 bool
1259 AUPlugin::parameter_is_output (uint32_t) const
1260 {
1261         return false;
1262 }
1263
1264 XMLNode&
1265 AUPlugin::get_state()
1266 {
1267         LocaleGuard lg (X_("POSIX"));
1268         XMLNode *root = new XMLNode (state_node_name());
1269
1270 #ifdef AU_STATE_SUPPORT
1271         CFDataRef xmlData;
1272         CFPropertyListRef propertyList;
1273
1274         if (unit->GetAUPreset (propertyList) != noErr) {
1275                 return *root;
1276         }
1277
1278         // Convert the property list into XML data.
1279         
1280         xmlData = CFPropertyListCreateXMLData( kCFAllocatorDefault, propertyList);
1281
1282         if (!xmlData) {
1283                 error << _("Could not create XML version of property list") << endmsg;
1284                 return *root;
1285         }
1286
1287         /* re-parse XML bytes to create a libxml++ XMLTree that we can merge into
1288            our state node. GACK!
1289         */
1290
1291         XMLTree t;
1292
1293         if (t.read_buffer (string ((const char*) CFDataGetBytePtr (xmlData), CFDataGetLength (xmlData)))) {
1294                 if (t.root()) {
1295                         root->add_child_copy (*t.root());
1296                 }
1297         }
1298
1299         CFRelease (xmlData);
1300         CFRelease (propertyList);
1301 #else
1302         if (!seen_get_state_message) {
1303                 info << _("Saving AudioUnit settings is not supported in this build of Ardour. Consider paying for a newer version")
1304                      << endmsg;
1305                 seen_get_state_message = true;
1306         }
1307 #endif
1308         
1309         return *root;
1310 }
1311
1312 int
1313 AUPlugin::set_state(const XMLNode& node)
1314 {
1315 #ifdef AU_STATE_SUPPORT
1316         int ret = -1;
1317         CFPropertyListRef propertyList;
1318         LocaleGuard lg (X_("POSIX"));
1319
1320         if (node.name() != state_node_name()) {
1321                 error << _("Bad node sent to AUPlugin::set_state") << endmsg;
1322                 return -1;
1323         }
1324         
1325         if (node.children().empty()) {
1326                 return -1;
1327         }
1328
1329         XMLNode* top = node.children().front();
1330         XMLNode* copy = new XMLNode (*top);
1331
1332         XMLTree t;
1333         t.set_root (copy);
1334
1335         const string& xml = t.write_buffer ();
1336         CFDataRef xmlData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (UInt8*) xml.data(), xml.length(), kCFAllocatorNull);
1337         CFStringRef errorString;
1338
1339         propertyList = CFPropertyListCreateFromXMLData( kCFAllocatorDefault,
1340                                                         xmlData,
1341                                                         kCFPropertyListImmutable,
1342                                                         &errorString);
1343
1344         CFRelease (xmlData);
1345         
1346         if (propertyList) {
1347                 if (unit->SetAUPreset (propertyList) == noErr) {
1348                         ret = 0;
1349                         
1350                         /* tell the world */
1351
1352                         AudioUnitParameter changedUnit;
1353                         changedUnit.mAudioUnit = unit->AU();
1354                         changedUnit.mParameterID = kAUParameterListener_AnyParameter;
1355                         AUParameterListenerNotify (NULL, NULL, &changedUnit);
1356                 } 
1357                 CFRelease (propertyList);
1358         }
1359         
1360         return ret;
1361 #else
1362         if (!seen_set_state_message) {
1363                 info << _("Restoring AudioUnit settings is not supported in this build of Ardour. Consider paying for a newer version")
1364                      << endmsg;
1365         }
1366         return 0;
1367 #endif
1368 }
1369
1370 bool
1371 AUPlugin::load_preset (const string preset_label)
1372 {
1373 #ifdef AU_STATE_SUPPORT
1374         bool ret = false;
1375         CFPropertyListRef propertyList;
1376         Glib::ustring path;
1377         UserPresetMap::iterator ux;
1378         FactoryPresetMap::iterator fx;
1379
1380         /* look first in "user" presets */
1381
1382         if ((ux = user_preset_map.find (preset_label)) != user_preset_map.end()) {
1383         
1384                 if ((propertyList = load_property_list (ux->second)) != 0) {
1385                         if (unit->SetAUPreset (propertyList) == noErr) {
1386                                 ret = true;
1387                         }
1388                         CFRelease(propertyList);
1389                 }
1390
1391         } else if ((fx = factory_preset_map.find (preset_label)) != factory_preset_map.end()) {
1392                 
1393                 AUPreset preset;
1394                 
1395                 preset.presetNumber = fx->second;
1396                 preset.presetName = CFStringCreateWithCString (kCFAllocatorDefault, fx->first.c_str(), kCFStringEncodingUTF8);
1397                 
1398                 cerr << "Setting factory preset " << fx->second << endl;
1399
1400                 if (unit->SetPresentPreset (preset) == 0) {
1401                         ret = true;
1402
1403                         /* tell the world */
1404
1405                         AudioUnitParameter changedUnit;
1406                         changedUnit.mAudioUnit = unit->AU();
1407                         changedUnit.mParameterID = kAUParameterListener_AnyParameter;
1408                         AUParameterListenerNotify (NULL, NULL, &changedUnit);
1409                 }
1410         }
1411                 
1412         return ret;
1413 #else
1414         if (!seen_loading_message) {
1415                 info << _("Loading AudioUnit presets is not supported in this build of Ardour. Consider paying for a newer version")
1416                      << endmsg;
1417                 seen_loading_message = true;
1418         }
1419         return true;
1420 #endif
1421 }
1422
1423 bool
1424 AUPlugin::save_preset (string preset_name)
1425 {
1426 #ifdef AU_STATE_SUPPORT
1427         CFPropertyListRef propertyList;
1428         vector<Glib::ustring> v;
1429         Glib::ustring user_preset_path;
1430         bool ret = true;
1431
1432         std::string m = maker();
1433         std::string n = name();
1434         
1435         strip_whitespace_edges (m);
1436         strip_whitespace_edges (n);
1437
1438         v.push_back (Glib::get_home_dir());
1439         v.push_back ("Library");
1440         v.push_back ("Audio");
1441         v.push_back ("Presets");
1442         v.push_back (m);
1443         v.push_back (n);
1444         
1445         user_preset_path = Glib::build_filename (v);
1446
1447         if (g_mkdir_with_parents (user_preset_path.c_str(), 0775) < 0) {
1448                 error << string_compose (_("Cannot create user plugin presets folder (%1)"), user_preset_path) << endmsg;
1449                 return false;
1450         }
1451
1452         if (unit->GetAUPreset (propertyList) != noErr) {
1453                 return false;
1454         }
1455
1456         // add the actual preset name */
1457
1458         v.push_back (preset_name + preset_suffix);
1459                 
1460         // rebuild
1461
1462         user_preset_path = Glib::build_filename (v);
1463         
1464         set_preset_name_in_plist (propertyList, preset_name);
1465
1466         if (save_property_list (propertyList, user_preset_path)) {
1467                 error << string_compose (_("Saving plugin state to %1 failed"), user_preset_path) << endmsg;
1468                 ret = false;
1469         }
1470
1471         CFRelease(propertyList);
1472
1473         return ret;
1474 #else
1475         if (!seen_saving_message) {
1476                 info << _("Saving AudioUnit presets is not supported in this build of Ardour. Consider paying for a newer version")
1477                      << endmsg;
1478                 seen_saving_message = true;
1479         }
1480         return false;
1481 #endif
1482 }
1483
1484 //-----------------------------------------------------------------------------
1485 // this is just a little helper function used by GetAUComponentDescriptionFromPresetFile()
1486 static SInt32 
1487 GetDictionarySInt32Value(CFDictionaryRef inAUStateDictionary, CFStringRef inDictionaryKey, Boolean * outSuccess)
1488 {
1489         CFNumberRef cfNumber;
1490         SInt32 numberValue = 0;
1491         Boolean dummySuccess;
1492
1493         if (outSuccess == NULL)
1494                 outSuccess = &dummySuccess;
1495         if ( (inAUStateDictionary == NULL) || (inDictionaryKey == NULL) )
1496         {
1497                 *outSuccess = FALSE;
1498                 return 0;
1499         }
1500
1501         cfNumber = (CFNumberRef) CFDictionaryGetValue(inAUStateDictionary, inDictionaryKey);
1502         if (cfNumber == NULL)
1503         {
1504                 *outSuccess = FALSE;
1505                 return 0;
1506         }
1507         *outSuccess = CFNumberGetValue(cfNumber, kCFNumberSInt32Type, &numberValue);
1508         if (*outSuccess)
1509                 return numberValue;
1510         else
1511                 return 0;
1512 }
1513
1514 static OSStatus 
1515 GetAUComponentDescriptionFromStateData(CFPropertyListRef inAUStateData, ComponentDescription * outComponentDescription)
1516 {
1517         CFDictionaryRef auStateDictionary;
1518         ComponentDescription tempDesc = {0};
1519         SInt32 versionValue;
1520         Boolean gotValue;
1521
1522         if ( (inAUStateData == NULL) || (outComponentDescription == NULL) )
1523                 return paramErr;
1524         
1525         // the property list for AU state data must be of the dictionary type
1526         if (CFGetTypeID(inAUStateData) != CFDictionaryGetTypeID()) {
1527                 return kAudioUnitErr_InvalidPropertyValue;
1528         }
1529
1530         auStateDictionary = (CFDictionaryRef)inAUStateData;
1531
1532         // first check to make sure that the version of the AU state data is one that we know understand
1533         // XXX should I really do this?  later versions would probably still hold these ID keys, right?
1534         versionValue = GetDictionarySInt32Value(auStateDictionary, CFSTR(kAUPresetVersionKey), &gotValue);
1535
1536         if (!gotValue) {
1537                 return kAudioUnitErr_InvalidPropertyValue;
1538         }
1539 #define kCurrentSavedStateVersion 0
1540         if (versionValue != kCurrentSavedStateVersion) {
1541                 return kAudioUnitErr_InvalidPropertyValue;
1542         }
1543
1544         // grab the ComponentDescription values from the AU state data
1545         tempDesc.componentType = (OSType) GetDictionarySInt32Value(auStateDictionary, CFSTR(kAUPresetTypeKey), NULL);
1546         tempDesc.componentSubType = (OSType) GetDictionarySInt32Value(auStateDictionary, CFSTR(kAUPresetSubtypeKey), NULL);
1547         tempDesc.componentManufacturer = (OSType) GetDictionarySInt32Value(auStateDictionary, CFSTR(kAUPresetManufacturerKey), NULL);
1548         // zero values are illegit for specific ComponentDescriptions, so zero for any value means that there was an error
1549         if ( (tempDesc.componentType == 0) || (tempDesc.componentSubType == 0) || (tempDesc.componentManufacturer == 0) )
1550                 return kAudioUnitErr_InvalidPropertyValue;
1551
1552         *outComponentDescription = tempDesc;
1553         return noErr;
1554 }
1555
1556
1557 static bool au_preset_filter (const string& str, void* arg)
1558 {
1559         /* Not a dotfile, has a prefix before a period, suffix is aupreset */
1560
1561         bool ret;
1562         
1563         ret = (str[0] != '.' && str.length() > 9 && str.find (preset_suffix) == (str.length() - preset_suffix.length()));
1564
1565         if (ret && arg) {
1566
1567                 /* check the preset file path name against this plugin
1568                    ID. The idea is that all preset files for this plugin
1569                    include "<manufacturer>/<plugin-name>" in their path.
1570                 */
1571
1572                 Plugin* p = (Plugin *) arg;
1573                 string match = p->maker();
1574                 match += '/';
1575                 match += p->name();
1576
1577                 ret = str.find (match) != string::npos;
1578
1579                 if (ret == false) {
1580                         string m = p->maker ();
1581                         string n = p->name ();
1582                         strip_whitespace_edges (m);
1583                         strip_whitespace_edges (n);
1584                         match = m;
1585                         match += '/';
1586                         match += n;
1587                         
1588                         ret = str.find (match) != string::npos;
1589                 }
1590         }
1591         
1592         return ret;
1593 }
1594
1595 bool 
1596 check_and_get_preset_name (Component component, const string& pathstr, string& preset_name)
1597 {
1598         OSStatus status;
1599         CFPropertyListRef plist;
1600         ComponentDescription presetDesc;
1601         bool ret = false;
1602                 
1603         plist = load_property_list (pathstr);
1604
1605         if (!plist) {
1606                 return ret;
1607         }
1608         
1609         // get the ComponentDescription from the AU preset file
1610         
1611         status = GetAUComponentDescriptionFromStateData(plist, &presetDesc);
1612         
1613         if (status == noErr) {
1614                 if (ComponentAndDescriptionMatch_Loosely(component, &presetDesc)) {
1615
1616                         /* try to get the preset name from the property list */
1617
1618                         if (CFGetTypeID(plist) == CFDictionaryGetTypeID()) {
1619
1620                                 const void* psk = CFDictionaryGetValue ((CFMutableDictionaryRef)plist, CFSTR(kAUPresetNameKey));
1621
1622                                 if (psk) {
1623
1624                                         const char* p = CFStringGetCStringPtr ((CFStringRef) psk, kCFStringEncodingUTF8);
1625
1626                                         if (!p) {
1627                                                 char buf[PATH_MAX+1];
1628
1629                                                 if (CFStringGetCString ((CFStringRef)psk, buf, sizeof (buf), kCFStringEncodingUTF8)) {
1630                                                         preset_name = buf;
1631                                                 }
1632                                         }
1633                                 }
1634                         }
1635                 } 
1636         }
1637
1638         CFRelease (plist);
1639
1640         return true;
1641 }
1642
1643 std::string
1644 AUPlugin::current_preset() const
1645 {
1646         string preset_name;
1647         
1648 #ifdef AU_STATE_SUPPORT
1649         CFPropertyListRef propertyList;
1650
1651         if (unit->GetAUPreset (propertyList) == noErr) {
1652                 preset_name = get_preset_name_in_plist (propertyList);
1653                 CFRelease(propertyList);
1654         }
1655 #endif
1656         return preset_name;
1657 }
1658
1659 vector<string>
1660 AUPlugin::get_presets ()
1661 {
1662         vector<string> presets;
1663
1664 #ifdef AU_STATE_SUPPORT
1665         vector<string*>* preset_files;
1666         PathScanner scanner;
1667
1668         user_preset_map.clear ();
1669
1670         preset_files = scanner (preset_search_path, au_preset_filter, this, true, true, -1, true);
1671         
1672         if (!preset_files) {
1673                 return presets;
1674         }
1675
1676         for (vector<string*>::iterator x = preset_files->begin(); x != preset_files->end(); ++x) {
1677
1678                 string path = *(*x);
1679                 string preset_name;
1680
1681                 /* make an initial guess at the preset name using the path */
1682
1683                 preset_name = Glib::path_get_basename (path);
1684                 preset_name = preset_name.substr (0, preset_name.find_last_of ('.'));
1685
1686                 /* check that this preset file really matches this plugin
1687                    and potentially get the "real" preset name from
1688                    within the file.
1689                 */
1690
1691                 if (check_and_get_preset_name (get_comp()->Comp(), path, preset_name)) {
1692                         user_preset_map[preset_name] = path;
1693                 } 
1694
1695                 delete *x;
1696         }
1697
1698         delete preset_files;
1699
1700         /* now fill the vector<string> with the names we have */
1701
1702         for (UserPresetMap::iterator i = user_preset_map.begin(); i != user_preset_map.end(); ++i) {
1703                 presets.push_back (i->first);
1704         }
1705
1706         /* add factory presets */
1707
1708         for (FactoryPresetMap::iterator i = factory_preset_map.begin(); i != factory_preset_map.end(); ++i) {
1709                 presets.push_back (i->first);
1710         }
1711
1712 #endif
1713         
1714         return presets;
1715 }
1716
1717 bool
1718 AUPlugin::has_editor () const
1719 {
1720         // even if the plugin doesn't have its own editor, the AU API can be used
1721         // to create one that looks native.
1722         return true;
1723 }
1724
1725 AUPluginInfo::AUPluginInfo (boost::shared_ptr<CAComponentDescription> d)
1726         : descriptor (d)
1727 {
1728         type = ARDOUR::AudioUnit;
1729 }
1730
1731 AUPluginInfo::~AUPluginInfo ()
1732 {
1733         type = ARDOUR::AudioUnit;
1734 }
1735
1736 PluginPtr
1737 AUPluginInfo::load (Session& session)
1738 {
1739         try {
1740                 PluginPtr plugin;
1741
1742                 boost::shared_ptr<CAComponent> comp (new CAComponent(*descriptor));
1743                 
1744                 if (!comp->IsValid()) {
1745                         error << ("AudioUnit: not a valid Component") << endmsg;
1746                 } else {
1747                         plugin.reset (new AUPlugin (session.engine(), session, comp));
1748                 }
1749                 
1750                 plugin->set_info (PluginInfoPtr (new AUPluginInfo (*this)));
1751                 return plugin;
1752         }
1753
1754         catch (failed_constructor &err) {
1755                 return PluginPtr ();
1756         }
1757 }
1758
1759 Glib::ustring
1760 AUPluginInfo::au_cache_path ()
1761 {
1762         return Glib::build_filename (ARDOUR::get_user_ardour_path(), "au_cache");
1763 }
1764
1765 PluginInfoList
1766 AUPluginInfo::discover ()
1767 {
1768         XMLTree tree;
1769
1770         if (!Glib::file_test (au_cache_path(), Glib::FILE_TEST_EXISTS)) {
1771                 ARDOUR::BootMessage (_("Discovering AudioUnit plugins (could take some time ...)"));
1772         }
1773
1774         PluginInfoList plugs;
1775         
1776         discover_fx (plugs);
1777         discover_music (plugs);
1778         discover_generators (plugs);
1779
1780         return plugs;
1781 }
1782
1783 void
1784 AUPluginInfo::discover_music (PluginInfoList& plugs)
1785 {
1786         CAComponentDescription desc;
1787         desc.componentFlags = 0;
1788         desc.componentFlagsMask = 0;
1789         desc.componentSubType = 0;
1790         desc.componentManufacturer = 0;
1791         desc.componentType = kAudioUnitType_MusicEffect;
1792
1793         discover_by_description (plugs, desc);
1794 }
1795
1796 void
1797 AUPluginInfo::discover_fx (PluginInfoList& plugs)
1798 {
1799         CAComponentDescription desc;
1800         desc.componentFlags = 0;
1801         desc.componentFlagsMask = 0;
1802         desc.componentSubType = 0;
1803         desc.componentManufacturer = 0;
1804         desc.componentType = kAudioUnitType_Effect;
1805
1806         discover_by_description (plugs, desc);
1807 }
1808
1809 void
1810 AUPluginInfo::discover_generators (PluginInfoList& plugs)
1811 {
1812         CAComponentDescription desc;
1813         desc.componentFlags = 0;
1814         desc.componentFlagsMask = 0;
1815         desc.componentSubType = 0;
1816         desc.componentManufacturer = 0;
1817         desc.componentType = kAudioUnitType_Generator;
1818
1819         discover_by_description (plugs, desc);
1820 }
1821
1822 void
1823 AUPluginInfo::discover_by_description (PluginInfoList& plugs, CAComponentDescription& desc)
1824 {
1825         Component comp = 0;
1826
1827         comp = FindNextComponent (NULL, &desc);
1828
1829         while (comp != NULL) {
1830                 CAComponentDescription temp;
1831                 GetComponentInfo (comp, &temp, NULL, NULL, NULL);
1832
1833                 AUPluginInfoPtr info (new AUPluginInfo 
1834                                       (boost::shared_ptr<CAComponentDescription> (new CAComponentDescription(temp))));
1835
1836                 /* no panners, format converters or i/o AU's for our purposes
1837                  */
1838
1839                 switch (info->descriptor->Type()) {
1840                 case kAudioUnitType_Panner:
1841                 case kAudioUnitType_OfflineEffect:
1842                 case kAudioUnitType_FormatConverter:
1843                         continue;
1844                 case kAudioUnitType_Output:
1845                 case kAudioUnitType_MusicDevice:
1846                 case kAudioUnitType_MusicEffect:
1847                 case kAudioUnitType_Effect:
1848                 case kAudioUnitType_Mixer:
1849                 case kAudioUnitType_Generator:
1850                         break;
1851                 default:
1852                         break;
1853                 }
1854
1855                 switch (info->descriptor->SubType()) {
1856                 case kAudioUnitSubType_DefaultOutput:
1857                 case kAudioUnitSubType_SystemOutput:
1858                 case kAudioUnitSubType_GenericOutput:
1859                 case kAudioUnitSubType_AUConverter:
1860                         /* we don't want output units here */
1861                         continue;
1862                         break;
1863
1864                 case kAudioUnitSubType_DLSSynth:
1865                         info->category = "DLS Synth";
1866                         break;
1867
1868                 case kAudioUnitSubType_Varispeed:
1869                         info->category = "Varispeed";
1870                         break;
1871
1872                 case kAudioUnitSubType_Delay:
1873                         info->category = "Delay";
1874                         break;
1875
1876                 case kAudioUnitSubType_LowPassFilter:
1877                         info->category = "Low-pass Filter";
1878                         break;
1879
1880                 case kAudioUnitSubType_HighPassFilter:
1881                         info->category = "High-pass Filter";
1882                         break;
1883
1884                 case kAudioUnitSubType_BandPassFilter:
1885                         info->category = "Band-pass Filter";
1886                         break;
1887
1888                 case kAudioUnitSubType_HighShelfFilter:
1889                         info->category = "High-shelf Filter";
1890                         break;
1891
1892                 case kAudioUnitSubType_LowShelfFilter:
1893                         info->category = "Low-shelf Filter";
1894                         break;
1895
1896                 case kAudioUnitSubType_ParametricEQ:
1897                         info->category = "Parametric EQ";
1898                         break;
1899
1900                 case kAudioUnitSubType_GraphicEQ:
1901                         info->category = "Graphic EQ";
1902                         break;
1903
1904                 case kAudioUnitSubType_PeakLimiter:
1905                         info->category = "Peak Limiter";
1906                         break;
1907
1908                 case kAudioUnitSubType_DynamicsProcessor:
1909                         info->category = "Dynamics Processor";
1910                         break;
1911
1912                 case kAudioUnitSubType_MultiBandCompressor:
1913                         info->category = "Multiband Compressor";
1914                         break;
1915
1916                 case kAudioUnitSubType_MatrixReverb:
1917                         info->category = "Matrix Reverb";
1918                         break;
1919
1920                 case kAudioUnitSubType_SampleDelay:
1921                         info->category = "Sample Delay";
1922                         break;
1923
1924                 case kAudioUnitSubType_Pitch:
1925                         info->category = "Pitch";
1926                         break;
1927
1928                 case kAudioUnitSubType_NetSend:
1929                         info->category = "Net Sender";
1930                         break;
1931
1932                 case kAudioUnitSubType_3DMixer:
1933                         info->category = "3DMixer";
1934                         break;
1935
1936                 case kAudioUnitSubType_MatrixMixer:
1937                         info->category = "MatrixMixer";
1938                         break;
1939
1940                 case kAudioUnitSubType_ScheduledSoundPlayer:
1941                         info->category = "Scheduled Sound Player";
1942                         break;
1943
1944
1945                 case kAudioUnitSubType_AudioFilePlayer:
1946                         info->category = "Audio File Player";
1947                         break;
1948
1949                 case kAudioUnitSubType_NetReceive:
1950                         info->category = "Net Receiver";
1951                         break;
1952
1953                 default:
1954                         info->category = "";
1955                 }
1956
1957                 AUPluginInfo::get_names (temp, info->name, info->creator);
1958
1959                 info->type = ARDOUR::AudioUnit;
1960                 info->unique_id = stringify_descriptor (*info->descriptor);
1961
1962                 /* XXX not sure of the best way to handle plugin versioning yet
1963                  */
1964
1965                 CAComponent cacomp (*info->descriptor);
1966
1967                 if (cacomp.GetResourceVersion (info->version) != noErr) {
1968                         info->version = 0;
1969                 }
1970                 
1971                 if (cached_io_configuration (info->unique_id, info->version, cacomp, info->cache, info->name)) {
1972
1973                         /* here we have to map apple's wildcard system to a simple pair
1974                            of values. in ::can_do() we use the whole system, but here
1975                            we need a single pair of values. XXX probably means we should
1976                            remove any use of these values.
1977                         */
1978
1979                         info->n_inputs = info->cache.io_configs.front().first;
1980                         info->n_outputs = info->cache.io_configs.front().second;
1981
1982                         if (info->cache.io_configs.size() > 1) {
1983                                 cerr << "ODD: variable IO config for " << info->unique_id << endl;
1984                         }
1985
1986                         plugs.push_back (info);
1987
1988                 } else {
1989                         error << string_compose (_("Cannot get I/O configuration info for AU %1"), info->name) << endmsg;
1990                 }
1991                 
1992                 comp = FindNextComponent (comp, &desc);
1993         }
1994 }
1995
1996 bool
1997 AUPluginInfo::cached_io_configuration (const std::string& unique_id, 
1998                                        UInt32 version,
1999                                        CAComponent& comp, 
2000                                        AUPluginCachedInfo& cinfo, 
2001                                        const std::string& name)
2002 {
2003         std::string id;
2004         char buf[32];
2005
2006         /* concatenate unique ID with version to provide a key for cached info lookup.
2007            this ensures we don't get stale information, or should if plugin developers
2008            follow Apple "guidelines".
2009          */
2010
2011         snprintf (buf, sizeof (buf), "%u", (uint32_t) version);
2012         id = unique_id;
2013         id += '/';
2014         id += buf;
2015
2016         CachedInfoMap::iterator cim = cached_info.find (id);
2017
2018         if (cim != cached_info.end()) {
2019                 cinfo = cim->second;
2020                 return true;
2021         }
2022
2023         CAAudioUnit unit;
2024         AUChannelInfo* channel_info;
2025         UInt32 cnt;
2026         int ret;
2027         
2028         ARDOUR::BootMessage (string_compose (_("Checking AudioUnit: %1"), name));
2029         
2030         try {
2031
2032                 if (CAAudioUnit::Open (comp, unit) != noErr) {
2033                         return false;
2034                 }
2035
2036         } catch (...) {
2037
2038                 warning << string_compose (_("Could not load AU plugin %1 - ignored"), name) << endmsg;
2039                 cerr << string_compose (_("Could not load AU plugin %1 - ignored"), name) << endl;
2040                 return false;
2041
2042         }
2043                 
2044         if ((ret = unit.GetChannelInfo (&channel_info, cnt)) < 0) {
2045                 return false;
2046         }
2047
2048         if (ret > 0) {
2049                 /* no explicit info available */
2050
2051                 cinfo.io_configs.push_back (pair<int,int> (-1, -1));
2052
2053         } else {
2054                 
2055                 /* store each configuration */
2056                 
2057                 for (uint32_t n = 0; n < cnt; ++n) {
2058                         cinfo.io_configs.push_back (pair<int,int> (channel_info[n].inChannels,
2059                                                                    channel_info[n].outChannels));
2060                 }
2061
2062                 free (channel_info);
2063         }
2064
2065         add_cached_info (id, cinfo);
2066         save_cached_info ();
2067
2068         return true;
2069 }
2070
2071 void
2072 AUPluginInfo::add_cached_info (const std::string& id, AUPluginCachedInfo& cinfo)
2073 {
2074         cached_info[id] = cinfo;
2075 }
2076
2077 void
2078 AUPluginInfo::save_cached_info ()
2079 {
2080         XMLNode* node;
2081
2082         node = new XMLNode (X_("AudioUnitPluginCache"));
2083         
2084         for (map<string,AUPluginCachedInfo>::iterator i = cached_info.begin(); i != cached_info.end(); ++i) {
2085                 XMLNode* parent = new XMLNode (X_("plugin"));
2086                 parent->add_property ("id", i->first);
2087                 node->add_child_nocopy (*parent);
2088
2089                 for (vector<pair<int, int> >::iterator j = i->second.io_configs.begin(); j != i->second.io_configs.end(); ++j) {
2090
2091                         XMLNode* child = new XMLNode (X_("io"));
2092                         char buf[32];
2093
2094                         snprintf (buf, sizeof (buf), "%d", j->first);
2095                         child->add_property (X_("in"), buf);
2096                         snprintf (buf, sizeof (buf), "%d", j->second);
2097                         child->add_property (X_("out"), buf);
2098                         parent->add_child_nocopy (*child);
2099                 }
2100
2101         }
2102
2103         Glib::ustring path = au_cache_path ();
2104         XMLTree tree;
2105
2106         tree.set_root (node);
2107
2108         if (!tree.write (path)) {
2109                 error << string_compose (_("could not save AU cache to %1"), path) << endmsg;
2110                 unlink (path.c_str());
2111         }
2112 }
2113
2114 int
2115 AUPluginInfo::load_cached_info ()
2116 {
2117         Glib::ustring path = au_cache_path ();
2118         XMLTree tree;
2119         
2120         if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
2121                 return 0;
2122         }
2123
2124         tree.read (path);
2125         const XMLNode* root (tree.root());
2126
2127         if (root->name() != X_("AudioUnitPluginCache")) {
2128                 return -1;
2129         }
2130
2131         cached_info.clear ();
2132
2133         const XMLNodeList children = root->children();
2134
2135         for (XMLNodeConstIterator iter = children.begin(); iter != children.end(); ++iter) {
2136                 
2137                 const XMLNode* child = *iter;
2138                 
2139                 if (child->name() == X_("plugin")) {
2140
2141                         const XMLNode* gchild;
2142                         const XMLNodeList gchildren = child->children();
2143                         const XMLProperty* prop = child->property (X_("id"));
2144
2145                         if (!prop) {
2146                                 continue;
2147                         }
2148
2149                         std::string id = prop->value();
2150                         AUPluginCachedInfo cinfo;
2151
2152                         for (XMLNodeConstIterator giter = gchildren.begin(); giter != gchildren.end(); giter++) {
2153
2154                                 gchild = *giter;
2155
2156                                 if (gchild->name() == X_("io")) {
2157
2158                                         int in;
2159                                         int out;
2160                                         const XMLProperty* iprop;
2161                                         const XMLProperty* oprop;
2162
2163                                         if (((iprop = gchild->property (X_("in"))) != 0) &&
2164                                             ((oprop = gchild->property (X_("out"))) != 0)) {
2165                                                 in = atoi (iprop->value());
2166                                                 out = atoi (iprop->value());
2167                                                 
2168                                                 cinfo.io_configs.push_back (pair<int,int> (in, out));
2169                                         }
2170                                 }
2171                         }
2172
2173                         if (cinfo.io_configs.size()) {
2174                                 add_cached_info (id, cinfo);
2175                         }
2176                 }
2177         }
2178
2179         return 0;
2180 }
2181
2182 void
2183 AUPluginInfo::get_names (CAComponentDescription& comp_desc, std::string& name, Glib::ustring& maker)
2184 {
2185         CFStringRef itemName = NULL;
2186
2187         // Marc Poirier-style item name
2188         CAComponent auComponent (comp_desc);
2189         if (auComponent.IsValid()) {
2190                 CAComponentDescription dummydesc;
2191                 Handle nameHandle = NewHandle(sizeof(void*));
2192                 if (nameHandle != NULL) {
2193                         OSErr err = GetComponentInfo(auComponent.Comp(), &dummydesc, nameHandle, NULL, NULL);
2194                         if (err == noErr) {
2195                                 ConstStr255Param nameString = (ConstStr255Param) (*nameHandle);
2196                                 if (nameString != NULL) {
2197                                         itemName = CFStringCreateWithPascalString(kCFAllocatorDefault, nameString, CFStringGetSystemEncoding());
2198                                 }
2199                         }
2200                         DisposeHandle(nameHandle);
2201                 }
2202         }
2203     
2204         // if Marc-style fails, do the original way
2205         if (itemName == NULL) {
2206                 CFStringRef compTypeString = UTCreateStringForOSType(comp_desc.componentType);
2207                 CFStringRef compSubTypeString = UTCreateStringForOSType(comp_desc.componentSubType);
2208                 CFStringRef compManufacturerString = UTCreateStringForOSType(comp_desc.componentManufacturer);
2209     
2210                 itemName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ - %@ - %@"), 
2211                         compTypeString, compManufacturerString, compSubTypeString);
2212     
2213                 if (compTypeString != NULL)
2214                         CFRelease(compTypeString);
2215                 if (compSubTypeString != NULL)
2216                         CFRelease(compSubTypeString);
2217                 if (compManufacturerString != NULL)
2218                         CFRelease(compManufacturerString);
2219         }
2220         
2221         string str = CFStringRefToStdString(itemName);
2222         string::size_type colon = str.find (':');
2223
2224         if (colon) {
2225                 name = str.substr (colon+1);
2226                 maker = str.substr (0, colon);
2227                 // strip_whitespace_edges (maker);
2228                 // strip_whitespace_edges (name);
2229         } else {
2230                 name = str;
2231                 maker = "unknown";
2232         }
2233 }
2234
2235 // from CAComponentDescription.cpp (in libs/appleutility in ardour source)
2236 extern char *StringForOSType (OSType t, char *writeLocation);
2237
2238 std::string
2239 AUPluginInfo::stringify_descriptor (const CAComponentDescription& desc)
2240 {
2241         char str[24];
2242         stringstream s;
2243
2244         s << StringForOSType (desc.Type(), str);
2245         s << " - ";
2246                 
2247         s << StringForOSType (desc.SubType(), str);
2248         s << " - ";
2249                 
2250         s << StringForOSType (desc.Manu(), str);
2251
2252         return s.str();
2253 }