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 version(AU):
50 
51 import core.stdc.stdio: snprintf;
52 
53 import std.string;
54 import std.exception: assumeUnique;
55 
56 import derelict.carbon;
57 
58 import dplug.core.nogc;
59 
60 //-----------------------------------------------------------------------------
61 // The following defines and implements CoreFoundation-like handling of
62 // an AUPreset container object:  CFAUPreset
63 //-----------------------------------------------------------------------------
64 
65 enum UInt32 kCFAUPreset_CurrentVersion = 0;
66 
67 struct CFAUPreset
68 {
69     AUPreset auPreset;
70     uint version_;
71     CFAllocatorRef allocator;
72     CFIndex retainCount;
73 }
74 
75 alias CFAUPresetRef = void*;
76 
77 //-----------------------------------------------------------------------------
78 // create an instance of a CFAUPreset object
79 CFAUPresetRef CFAUPresetCreate(CFAllocatorRef inAllocator, SInt32 inPresetNumber, CFStringRef inPresetName) nothrow @nogc
80 {
81     CFAUPreset * newPreset = cast(CFAUPreset*) CFAllocatorAllocate(inAllocator, CFAUPreset.sizeof, 0);
82     if (newPreset != null)
83     {
84         newPreset.auPreset.presetNumber = inPresetNumber;
85         newPreset.auPreset.presetName = null;
86         // create our own a copy rather than retain the string, in case the input string is mutable,
87         // this will keep it from changing under our feet
88         if (inPresetName != null)
89             newPreset.auPreset.presetName = CFStringCreateCopy(inAllocator, inPresetName);
90         newPreset.version_ = kCFAUPreset_CurrentVersion;
91         newPreset.allocator = inAllocator;
92         newPreset.retainCount = 1;
93     }
94     return cast(CFAUPresetRef)newPreset;
95 }
96 
97 extern(C)
98 {
99     //-----------------------------------------------------------------------------
100     // retain a reference of a CFAUPreset object
101     CFAUPresetRef CFAUPresetRetain(void* inPreset) nothrow @nogc
102     {
103         if (inPreset != null)
104         {
105             CFAUPreset * incomingPreset = cast(CFAUPreset*) inPreset;
106             // retain the input AUPreset's name string for this reference to the preset
107             if (incomingPreset.auPreset.presetName != null)
108                 CFRetain(incomingPreset.auPreset.presetName);
109             incomingPreset.retainCount += 1;
110         }
111         return inPreset;
112     }
113 
114     //-----------------------------------------------------------------------------
115     // release a reference of a CFAUPreset object
116     void CFAUPresetRelease(void* inPreset) nothrow @nogc
117     {
118         CFAUPreset* incomingPreset = cast(CFAUPreset*) inPreset;
119         // these situations shouldn't happen
120         if (inPreset == null)
121             return;
122         if (incomingPreset.retainCount <= 0)
123             return;
124 
125         // first release the name string, CF-style, since it's a CFString
126         if (incomingPreset.auPreset.presetName != null)
127             CFRelease(incomingPreset.auPreset.presetName);
128         incomingPreset.retainCount -= 1;
129         // check if this is the end of this instance's life
130         if (incomingPreset.retainCount == 0)
131         {
132             // wipe out the data so that, if anyone tries to access stale memory later, it will be (semi)invalid
133             incomingPreset.auPreset.presetName = null;
134             incomingPreset.auPreset.presetNumber = 0;
135             // and finally, free the memory for the CFAUPreset struct
136             CFAllocatorDeallocate(incomingPreset.allocator, cast(void*)inPreset);
137         }
138     }
139 
140     //-----------------------------------------------------------------------------
141     // This function is called when an item (an AUPreset) is added to the CFArray,
142     // or when a CFArray containing an AUPreset is retained.
143     const(void)* CFAUPresetArrayRetainCallBack(CFAllocatorRef inAllocator, const(void)* inPreset) nothrow @nogc
144     {
145         return CFAUPresetRetain(cast(void*)inPreset); // casting const away here!
146     }
147 
148     //-----------------------------------------------------------------------------
149     // This function is called when an item (an AUPreset) is removed from the CFArray
150     // or when the array is released.
151     // Since a reference to the data belongs to the array, we need to release that here.
152     void CFAUPresetArrayReleaseCallBack(CFAllocatorRef inAllocator, const(void)* inPreset) nothrow @nogc
153     {
154         CFAUPresetRelease(cast(void*)inPreset); // casting const away here!
155     }
156 
157     //-----------------------------------------------------------------------------
158     // This function is called when someone wants to compare to items (AUPresets)
159     // in the CFArray to see if they are equal or not.
160     // For our AUPresets, we will compare based on the preset number and the name string.
161     Boolean CFAUPresetArrayEqualCallBack(const(void)* inPreset1, const(void)* inPreset2) nothrow @nogc
162     {
163         AUPreset * preset1 = cast(AUPreset*) inPreset1;
164         AUPreset * preset2 = cast(AUPreset*) inPreset2;
165         // the two presets are only equal if they have the same preset number and
166         // if the two name strings are the same (which we rely on the CF function to compare)
167         return (preset1.presetNumber == preset2.presetNumber) &&
168                 (CFStringCompare(preset1.presetName, preset2.presetName, 0) == kCFCompareEqualTo);
169     }
170 
171     //-----------------------------------------------------------------------------
172     // This function is called when someone wants to get a description of
173     // a particular item (an AUPreset) as though it were a CF type.
174     // That happens, for example, when using CFShow().
175     // This will create and return a CFString that indicates that
176     // the object is an AUPreset and tells the preset number and preset name.
177     CFStringRef CFAUPresetArrayCopyDescriptionCallBack(const(void)* inPreset) nothrow
178     {
179         AUPreset * preset = cast(AUPreset*) inPreset;
180         return CFStringCreateWithFormat(kCFAllocatorDefault, null,
181                                         CFStrLocal.fromString("AUPreset:\npreset number = %d\npreset name = %@"),
182                                         cast(int)preset.presetNumber, preset.presetName);
183     }
184 }
185 
186 CFArrayCallBacks getCFAUPresetArrayCallBacks() nothrow @nogc
187 {
188     CFArrayCallBacks result;
189     with(result)
190     {
191         version_ = 0; // currently, 0 is the only valid version value for this
192         retain = &CFAUPresetArrayRetainCallBack;
193         release = &CFAUPresetArrayReleaseCallBack;
194         copyDescription = &CFAUPresetArrayCopyDescriptionCallBack;
195         equal = &CFAUPresetArrayEqualCallBack;
196     }
197     return result;
198 }
199 
200 
201 // CoreFoundation helpers
202 
203 struct CFStrLocal
204 {
205 nothrow:
206 @nogc:
207 
208     CFStringRef parent;
209     alias parent this;
210 
211     @disable this();
212     @disable this(this);
213 
214     static fromString(const(char)[] str)
215     {
216         CFStrLocal s = void;
217         s.parent = toCFString(str);
218         return s;
219     }
220 
221     ~this() nothrow
222     {
223         CFRelease(parent);
224     }
225 }
226 
227 
228 /// Creates a CFString from an int give up its ownership.
229 CFStringRef convertIntToCFString(int number) nothrow @nogc
230 {
231     char[16] str;
232     snprintf(str.ptr, str.length, "%d", number); 
233     return CFStringCreateWithCString(null, str.ptr, kCFStringEncodingUTF8);
234 }
235 
236 /// Creates a CFString from a string and give up its ownership.
237 CFStringRef toCFString(const(char)[] str) nothrow @nogc
238 {
239     return CFStringCreateWithCString(null, CString(str).storage, kCFStringEncodingUTF8);
240 }
241 
242 /// Create string from a CFString, and give up its ownership.
243 /// Such a string must be deallocated with `free`/`freeSlice`.
244 /// It is guaranteed to finish with a terminal zero character ('\0').
245 string mallocStringFromCFString(CFStringRef cfStr) nothrow @nogc
246 {
247     int n = cast(int)CFStringGetLength(cfStr) + 1;
248     char[] buf = mallocSlice!char(n);
249     CFStringGetCString(cfStr, buf.ptr, n, kCFStringEncodingUTF8);
250     return assumeUnique(buf);
251 }
252 
253 void putNumberInDict(CFMutableDictionaryRef pDict, const(char)[] key, void* pNumber, CFNumberType type) nothrow @nogc
254 {
255     CFStrLocal cfKey = CFStrLocal.fromString(key);
256 
257     CFNumberRef pValue = CFNumberCreate(null, type, pNumber);
258     CFDictionarySetValue(pDict, cfKey, pValue);
259     CFRelease(pValue);
260 }
261 
262 void putStrInDict(CFMutableDictionaryRef pDict, const(char)[] key, const(char)[] value) nothrow @nogc
263 {
264     CFStrLocal cfKey = CFStrLocal.fromString(key);
265     CFStrLocal cfValue = CFStrLocal.fromString(value);
266     CFDictionarySetValue(pDict, cfKey, cfValue);
267 }
268 
269 void putDataInDict(CFMutableDictionaryRef pDict, const(char)[] key, ubyte[] pChunk) nothrow @nogc
270 {
271     CFStrLocal cfKey = CFStrLocal.fromString(key);
272 
273     CFDataRef pData = CFDataCreate(null, pChunk.ptr, cast(CFIndex)(pChunk.length));
274     CFDictionarySetValue(pDict, cfKey, pData);
275     CFRelease(pData);
276 }
277 
278 
279 bool getNumberFromDict(CFDictionaryRef pDict, const(char)[] key, void* pNumber, CFNumberType type) nothrow @nogc
280 {
281     CFStrLocal cfKey = CFStrLocal.fromString(key);
282 
283     CFNumberRef pValue = cast(CFNumberRef) CFDictionaryGetValue(pDict, cfKey);
284     if (pValue)
285     {
286         CFNumberGetValue(pValue, type, pNumber);
287         return true;
288     }
289     return false;
290 }
291 
292 /// Get a string in a dictionnary, and give up its ownership.
293 bool getStrFromDict(CFDictionaryRef pDict, const(char)[] key, out string value) nothrow @nogc
294 {
295     CFStrLocal cfKey = CFStrLocal.fromString(key);
296 
297     CFStringRef pValue = cast(CFStringRef) CFDictionaryGetValue(pDict, cfKey);
298     if (pValue)
299     {
300         value = mallocStringFromCFString(pValue);
301         return true;
302     }
303     return false;
304 }
305 
306 /// Gets data from a CFDataRef dictionnary entry and give up its ownership.
307 /// Must be deallocated with `free`/`freeSlice`.
308 bool getDataFromDict(CFDictionaryRef pDict, string key, out ubyte[] pChunk) nothrow @nogc
309 {
310     CFStrLocal cfKey = CFStrLocal.fromString(key);
311     CFDataRef pData = cast(CFDataRef) CFDictionaryGetValue(pDict, cfKey);
312     if (pData)
313     {
314         int n = cast(int)CFDataGetLength(pData);
315         pChunk = ( CFDataGetBytePtr(pData)[0..n] ).mallocDup;
316         return true;
317     }
318     return false;
319 }