1 /*
2     Destroy FX AU Utilities is a collection of helpful utility functions
3     for creating and hosting Audio Unit plugins.
4     Copyright (C) 2003-2008  Sophia Poirier
5     All rights reserved.
6 
7     Redistribution and use in source and binary forms, with or without
8     modification, are permitted provided that the following conditions
9     are met:
10 
11     *   Redistributions of source code must retain the above
12         copyright notice, this list of conditions and the
13         following disclaimer.
14     *   Redistributions in binary form must reproduce the above
15         copyright notice, this list of conditions and the
16         following disclaimer in the documentation and/or other
17         materials provided with the distribution.
18     *   Neither the name of Destroy FX nor the names of its
19         contributors may be used to endorse or promote products
20         derived from this software without specific prior
21         written permission.
22 
23     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26     FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27     COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34     OF THE POSSIBILITY OF SUCH DAMAGE.
35 
36     To contact the author, please visit http://destroyfx.org/
37     and use the contact form.
38 */
39 // this is a modified version of dfx-au-utilities.h keeping only CFAUPreset related functionality
40 
41 /**
42 Audio Unit plug-in client. Port of Destroy FX AU Utilities.
43 
44 Copyright: Copyright (C) 2003-2008  Sophia Poirier
45            Copyright (C) 2016 Guillaume Piolat
46 */
47 module dplug.au.dfxutil;
48 
49 import core.stdc.stdio: snprintf;
50 
51 import std..string;
52 import std.exception: assumeUnique;
53 
54 import derelict.carbon;
55 
56 import dplug.core.nogc;
57 
58 //-----------------------------------------------------------------------------
59 // The following defines and implements CoreFoundation-like handling of
60 // an AUPreset container object:  CFAUPreset
61 //-----------------------------------------------------------------------------
62 
63 enum UInt32 kCFAUPreset_CurrentVersion = 0;
64 
65 struct CFAUPreset
66 {
67     AUPreset auPreset;
68     uint version_;
69     CFAllocatorRef allocator;
70     CFIndex retainCount;
71 }
72 
73 alias CFAUPresetRef = void*;
74 
75 //-----------------------------------------------------------------------------
76 // create an instance of a CFAUPreset object
77 CFAUPresetRef CFAUPresetCreate(CFAllocatorRef inAllocator, SInt32 inPresetNumber, CFStringRef inPresetName) nothrow @nogc
78 {
79     CFAUPreset * newPreset = cast(CFAUPreset*) CFAllocatorAllocate(inAllocator, CFAUPreset.sizeof, 0);
80     if (newPreset != null)
81     {
82         newPreset.auPreset.presetNumber = inPresetNumber;
83         newPreset.auPreset.presetName = null;
84         // create our own a copy rather than retain the string, in case the input string is mutable,
85         // this will keep it from changing under our feet
86         if (inPresetName != null)
87             newPreset.auPreset.presetName = CFStringCreateCopy(inAllocator, inPresetName);
88         newPreset.version_ = kCFAUPreset_CurrentVersion;
89         newPreset.allocator = inAllocator;
90         newPreset.retainCount = 1;
91     }
92     return cast(CFAUPresetRef)newPreset;
93 }
94 
95 extern(C)
96 {
97     //-----------------------------------------------------------------------------
98     // retain a reference of a CFAUPreset object
99     CFAUPresetRef CFAUPresetRetain(void* inPreset) nothrow @nogc
100     {
101         if (inPreset != null)
102         {
103             CFAUPreset * incomingPreset = cast(CFAUPreset*) inPreset;
104             // retain the input AUPreset's name string for this reference to the preset
105             if (incomingPreset.auPreset.presetName != null)
106                 CFRetain(incomingPreset.auPreset.presetName);
107             incomingPreset.retainCount += 1;
108         }
109         return inPreset;
110     }
111 
112     //-----------------------------------------------------------------------------
113     // release a reference of a CFAUPreset object
114     void CFAUPresetRelease(void* inPreset) nothrow @nogc
115     {
116         CFAUPreset* incomingPreset = cast(CFAUPreset*) inPreset;
117         // these situations shouldn't happen
118         if (inPreset == null)
119             return;
120         if (incomingPreset.retainCount <= 0)
121             return;
122 
123         // first release the name string, CF-style, since it's a CFString
124         if (incomingPreset.auPreset.presetName != null)
125             CFRelease(incomingPreset.auPreset.presetName);
126         incomingPreset.retainCount -= 1;
127         // check if this is the end of this instance's life
128         if (incomingPreset.retainCount == 0)
129         {
130             // wipe out the data so that, if anyone tries to access stale memory later, it will be (semi)invalid
131             incomingPreset.auPreset.presetName = null;
132             incomingPreset.auPreset.presetNumber = 0;
133             // and finally, free the memory for the CFAUPreset struct
134             CFAllocatorDeallocate(incomingPreset.allocator, cast(void*)inPreset);
135         }
136     }
137 
138     //-----------------------------------------------------------------------------
139     // This function is called when an item (an AUPreset) is added to the CFArray,
140     // or when a CFArray containing an AUPreset is retained.
141     const(void)* CFAUPresetArrayRetainCallBack(CFAllocatorRef inAllocator, const(void)* inPreset) nothrow @nogc
142     {
143         return CFAUPresetRetain(cast(void*)inPreset); // casting const away here!
144     }
145 
146     //-----------------------------------------------------------------------------
147     // This function is called when an item (an AUPreset) is removed from the CFArray
148     // or when the array is released.
149     // Since a reference to the data belongs to the array, we need to release that here.
150     void CFAUPresetArrayReleaseCallBack(CFAllocatorRef inAllocator, const(void)* inPreset) nothrow @nogc
151     {
152         CFAUPresetRelease(cast(void*)inPreset); // casting const away here!
153     }
154 
155     //-----------------------------------------------------------------------------
156     // This function is called when someone wants to compare to items (AUPresets)
157     // in the CFArray to see if they are equal or not.
158     // For our AUPresets, we will compare based on the preset number and the name string.
159     Boolean CFAUPresetArrayEqualCallBack(const(void)* inPreset1, const(void)* inPreset2) nothrow @nogc
160     {
161         AUPreset * preset1 = cast(AUPreset*) inPreset1;
162         AUPreset * preset2 = cast(AUPreset*) inPreset2;
163         // the two presets are only equal if they have the same preset number and
164         // if the two name strings are the same (which we rely on the CF function to compare)
165         return (preset1.presetNumber == preset2.presetNumber) &&
166                 (CFStringCompare(preset1.presetName, preset2.presetName, 0) == kCFCompareEqualTo);
167     }
168 
169     //-----------------------------------------------------------------------------
170     // This function is called when someone wants to get a description of
171     // a particular item (an AUPreset) as though it were a CF type.
172     // That happens, for example, when using CFShow().
173     // This will create and return a CFString that indicates that
174     // the object is an AUPreset and tells the preset number and preset name.
175     CFStringRef CFAUPresetArrayCopyDescriptionCallBack(const(void)* inPreset) nothrow
176     {
177         AUPreset * preset = cast(AUPreset*) inPreset;
178         return CFStringCreateWithFormat(kCFAllocatorDefault, null,
179                                         CFStrLocal.fromString("AUPreset:\npreset number = %d\npreset name = %@"),
180                                         cast(int)preset.presetNumber, preset.presetName);
181     }
182 }
183 
184 CFArrayCallBacks getCFAUPresetArrayCallBacks() nothrow @nogc
185 {
186     CFArrayCallBacks result;
187     with(result)
188     {
189         version_ = 0; // currently, 0 is the only valid version value for this
190         retain = &CFAUPresetArrayRetainCallBack;
191         release = &CFAUPresetArrayReleaseCallBack;
192         copyDescription = &CFAUPresetArrayCopyDescriptionCallBack;
193         equal = &CFAUPresetArrayEqualCallBack;
194     }
195     return result;
196 }
197 
198 
199 // CoreFoundation helpers
200 
201 struct CFStrLocal
202 {
203 nothrow:
204 @nogc:
205 
206     CFStringRef parent;
207     alias parent this;
208 
209     @disable this();
210     @disable this(this);
211 
212     static fromString(const(char)[] str)
213     {
214         CFStrLocal s = void;
215         s.parent = toCFString(str);
216         return s;
217     }
218 
219     ~this() nothrow
220     {
221         CFRelease(parent);
222     }
223 }
224 
225 
226 /// Creates a CFString from an int give up its ownership.
227 CFStringRef convertIntToCFString(int number) nothrow @nogc
228 {
229     char[16] str;
230     snprintf(str.ptr, str.length, "%d", number); 
231     return CFStringCreateWithCString(null, str.ptr, kCFStringEncodingUTF8);
232 }
233 
234 /// Creates a CFString from a string and give up its ownership.
235 CFStringRef toCFString(const(char)[] str) nothrow @nogc
236 {
237     return CFStringCreateWithCString(null, CString(str).storage, kCFStringEncodingUTF8);
238 }
239 
240 /// Create string from a CFString, and give up its ownership.
241 /// Such a string must be deallocated with `free`/`freeSlice`.
242 /// It is guaranteed to finish with a terminal zero character ('\0').
243 string mallocStringFromCFString(CFStringRef cfStr) nothrow @nogc
244 {
245     int n = cast(int)CFStringGetLength(cfStr) + 1;
246     char[] buf = mallocSlice!char(n);
247     CFStringGetCString(cfStr, buf.ptr, n, kCFStringEncodingUTF8);
248     return assumeUnique(buf);
249 }
250 
251 void putNumberInDict(CFMutableDictionaryRef pDict, const(char)[] key, void* pNumber, CFNumberType type) nothrow @nogc
252 {
253     CFStrLocal cfKey = CFStrLocal.fromString(key);
254 
255     CFNumberRef pValue = CFNumberCreate(null, type, pNumber);
256     CFDictionarySetValue(pDict, cfKey, pValue);
257     CFRelease(pValue);
258 }
259 
260 void putStrInDict(CFMutableDictionaryRef pDict, const(char)[] key, const(char)[] value) nothrow @nogc
261 {
262     CFStrLocal cfKey = CFStrLocal.fromString(key);
263     CFStrLocal cfValue = CFStrLocal.fromString(value);
264     CFDictionarySetValue(pDict, cfKey, cfValue);
265 }
266 
267 void putDataInDict(CFMutableDictionaryRef pDict, const(char)[] key, ubyte[] pChunk) nothrow @nogc
268 {
269     CFStrLocal cfKey = CFStrLocal.fromString(key);
270 
271     CFDataRef pData = CFDataCreate(null, pChunk.ptr, cast(CFIndex)(pChunk.length));
272     CFDictionarySetValue(pDict, cfKey, pData);
273     CFRelease(pData);
274 }
275 
276 
277 bool getNumberFromDict(CFDictionaryRef pDict, const(char)[] key, void* pNumber, CFNumberType type) nothrow @nogc
278 {
279     CFStrLocal cfKey = CFStrLocal.fromString(key);
280 
281     CFNumberRef pValue = cast(CFNumberRef) CFDictionaryGetValue(pDict, cfKey);
282     if (pValue)
283     {
284         CFNumberGetValue(pValue, type, pNumber);
285         return true;
286     }
287     return false;
288 }
289 
290 /// Get a string in a dictionnary, and give up its ownership.
291 bool getStrFromDict(CFDictionaryRef pDict, const(char)[] key, out string value) nothrow @nogc
292 {
293     CFStrLocal cfKey = CFStrLocal.fromString(key);
294 
295     CFStringRef pValue = cast(CFStringRef) CFDictionaryGetValue(pDict, cfKey);
296     if (pValue)
297     {
298         value = mallocStringFromCFString(pValue);
299         return true;
300     }
301     return false;
302 }
303 
304 /// Gets data from a CFDataRef dictionnary entry and give up its ownership.
305 /// Must be deallocated with `free`/`freeSlice`.
306 bool getDataFromDict(CFDictionaryRef pDict, string key, out ubyte[] pChunk) nothrow @nogc
307 {
308     CFStrLocal cfKey = CFStrLocal.fromString(key);
309     CFDataRef pData = cast(CFDataRef) CFDictionaryGetValue(pDict, cfKey);
310     if (pData)
311     {
312         int n = cast(int)CFDataGetLength(pData);
313         pChunk = ( CFDataGetBytePtr(pData)[0..n] ).mallocDup;
314         return true;
315     }
316     return false;
317 }