1 /*
2  * libInstPatch
3  * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public License
7  * as published by the Free Software Foundation; version 2.1
8  * of the License only.
9  *
10  * This library 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 Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA or on the web at http://www.gnu.org.
19  */
20 /**
21  * SECTION: IpatchSF2Zone
22  * @short_description: Abstract base class for SoundFont zones
23  * @see_also:
24  * @stability: Stable
25  *
26  * Zones are children of #IpatchSF2Preset and #IpatchSF2Inst and define
27  * synthesis parameters and a linked item (#IpatchSF2Inst in the case of
28  * #IpatchSF2PZone and #IpatchSF2Sample in the case of #IpatchSF2IZone).
29  */
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <string.h>
33 #include <glib.h>
34 #include <glib-object.h>
35 #include "IpatchSF2Zone.h"
36 #include "IpatchSF2PZone.h"
37 #include "IpatchSF2.h"
38 #include "IpatchSF2Gen.h"
39 #include "IpatchSF2Mod.h"
40 #include "IpatchSF2ModItem.h"
41 #include "IpatchParamProp.h"
42 #include "IpatchRange.h"
43 #include "IpatchUnit.h"
44 #include "builtin_enums.h"
45 #include "ipatch_priv.h"
46 
47 static void ipatch_sf2_zone_mod_item_iface_init
48 (IpatchSF2ModItemIface *moditem_iface);
49 static void ipatch_sf2_zone_class_init(IpatchSF2ZoneClass *klass);
50 static void ipatch_sf2_zone_finalize(GObject *gobject);
51 static void ipatch_sf2_zone_get_title(IpatchSF2Zone *zone, GValue *value);
52 static void ipatch_sf2_zone_set_property(GObject *object, guint property_id,
53         const GValue *value, GParamSpec *pspec);
54 static void ipatch_sf2_zone_get_property(GObject *object,
55         guint property_id, GValue *value,
56         GParamSpec *pspec);
57 static void ipatch_sf2_zone_item_copy(IpatchItem *dest, IpatchItem *src,
58                                       IpatchItemCopyLinkFunc link_func,
59                                       gpointer user_data);
60 static void ipatch_sf2_zone_item_remove_full(IpatchItem *item, gboolean full);
61 static void ipatch_sf2_zone_link_item_title_notify(IpatchItemPropNotify *info);
62 
63 /* generator property IDs go before these */
64 enum
65 {
66     PROP_0,
67     PROP_TITLE,
68     PROP_MODULATORS
69 };
70 
71 
72 /* For passing between class init and mod item interface init */
73 static GParamSpec *modulators_spec = NULL;
74 
75 
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(IpatchSF2Zone,ipatch_sf2_zone,IPATCH_TYPE_ITEM,G_IMPLEMENT_INTERFACE (IPATCH_TYPE_SF2_MOD_ITEM,ipatch_sf2_zone_mod_item_iface_init))76 G_DEFINE_ABSTRACT_TYPE_WITH_CODE(IpatchSF2Zone, ipatch_sf2_zone, IPATCH_TYPE_ITEM,
77                                  G_IMPLEMENT_INTERFACE(IPATCH_TYPE_SF2_MOD_ITEM, ipatch_sf2_zone_mod_item_iface_init))
78 
79 
80 static void
81 ipatch_sf2_zone_class_init(IpatchSF2ZoneClass *klass)
82 {
83     GObjectClass *obj_class = G_OBJECT_CLASS(klass);
84     IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
85 
86     obj_class->finalize = ipatch_sf2_zone_finalize;
87     obj_class->get_property = ipatch_sf2_zone_get_property;
88 
89     item_class->copy = ipatch_sf2_zone_item_copy;
90     item_class->item_set_property = ipatch_sf2_zone_set_property;
91     item_class->remove_full = ipatch_sf2_zone_item_remove_full;
92 
93     g_object_class_override_property(obj_class, PROP_TITLE, "title");
94     g_object_class_override_property(obj_class, PROP_MODULATORS, "modulators");
95     modulators_spec = g_object_class_find_property(obj_class, "modulators");
96 }
97 
98 /* mod item interface initialization */
99 static void
ipatch_sf2_zone_mod_item_iface_init(IpatchSF2ModItemIface * moditem_iface)100 ipatch_sf2_zone_mod_item_iface_init(IpatchSF2ModItemIface *moditem_iface)
101 {
102     moditem_iface->modlist_ofs = G_STRUCT_OFFSET(IpatchSF2Zone, mods);
103 
104     /* cache the modulators property for fast notifications */
105     moditem_iface->mod_pspec = modulators_spec;
106 }
107 
108 static void
ipatch_sf2_zone_init(IpatchSF2Zone * zone)109 ipatch_sf2_zone_init(IpatchSF2Zone *zone)
110 {
111 }
112 
113 static void
ipatch_sf2_zone_finalize(GObject * gobject)114 ipatch_sf2_zone_finalize(GObject *gobject)
115 {
116     IpatchSF2Zone *zone = IPATCH_SF2_ZONE(gobject);
117     IpatchItem *link = NULL;
118     GSList *p;
119 
120     IPATCH_ITEM_WLOCK(zone);
121 
122     if(zone->item)
123     {
124         link = zone->item;
125         g_object_unref(zone->item);
126         zone->item = NULL;
127     }
128 
129     p = zone->mods;
130 
131     while(p)
132     {
133         ipatch_sf2_mod_free((IpatchSF2Mod *)(p->data));
134         p = g_slist_next(p);
135     }
136 
137     g_slist_free(zone->mods);
138     zone->mods = NULL;
139 
140     IPATCH_ITEM_WUNLOCK(zone);
141 
142     if(link)
143         ipatch_item_prop_disconnect_matched(link, ipatch_item_pspec_title,
144                                             ipatch_sf2_zone_link_item_title_notify, zone);
145 
146     if(G_OBJECT_CLASS(ipatch_sf2_zone_parent_class)->finalize)
147     {
148         G_OBJECT_CLASS(ipatch_sf2_zone_parent_class)->finalize(gobject);
149     }
150 }
151 
152 static void
ipatch_sf2_zone_get_title(IpatchSF2Zone * zone,GValue * value)153 ipatch_sf2_zone_get_title(IpatchSF2Zone *zone, GValue *value)
154 {
155     IpatchItem *ref;
156     char *s = NULL;
157 
158     g_object_get(zone, "link-item", &ref, NULL);  /* ++ ref zone linked item */
159 
160     if(ref)
161     {
162         g_object_get(ref, "name", &s, NULL);
163         g_object_unref(ref);	/* -- unref zone linked item */
164     }
165 
166     g_value_take_string(value, s);
167 }
168 
169 static void
ipatch_sf2_zone_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)170 ipatch_sf2_zone_set_property(GObject *object, guint property_id,
171                              const GValue *value, GParamSpec *pspec)
172 {
173     IpatchSF2Zone *zone = IPATCH_SF2_ZONE(object);
174     IpatchSF2ModList *list;
175 
176     if(property_id == PROP_MODULATORS)
177     {
178         list = (IpatchSF2ModList *)g_value_get_boxed(value);
179         ipatch_sf2_mod_item_set_mods(IPATCH_SF2_MOD_ITEM(zone), list,
180                                      IPATCH_SF2_MOD_NO_NOTIFY);
181     }
182     else
183     {
184         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
185         return;
186     }
187 }
188 
189 static void
ipatch_sf2_zone_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)190 ipatch_sf2_zone_get_property(GObject *object, guint property_id,
191                              GValue *value, GParamSpec *pspec)
192 {
193     IpatchSF2Zone *zone = IPATCH_SF2_ZONE(object);
194     IpatchSF2ModList *list;
195 
196     if(property_id == PROP_TITLE)
197     {
198         ipatch_sf2_zone_get_title(zone, value);
199     }
200     else if(property_id == PROP_MODULATORS)
201     {
202         list = ipatch_sf2_mod_item_get_mods(IPATCH_SF2_MOD_ITEM(zone));
203         g_value_take_boxed(value, list);
204     }
205     else
206     {
207         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
208         return;
209     }
210 }
211 
212 static void
ipatch_sf2_zone_item_copy(IpatchItem * dest,IpatchItem * src,IpatchItemCopyLinkFunc link_func,gpointer user_data)213 ipatch_sf2_zone_item_copy(IpatchItem *dest, IpatchItem *src,
214                           IpatchItemCopyLinkFunc link_func,
215                           gpointer user_data)
216 {
217     IpatchSF2Zone *src_zone, *dest_zone;
218     IpatchItem *refitem;
219     IpatchSF2Mod *mod;
220     GSList *p;
221 
222     src_zone = IPATCH_SF2_ZONE(src);
223     dest_zone = IPATCH_SF2_ZONE(dest);
224 
225     IPATCH_ITEM_RLOCK(src_zone);
226 
227     refitem = IPATCH_ITEM_COPY_LINK_FUNC(dest, src_zone->item,
228                                          link_func, user_data);
229 
230     if(refitem)
231     {
232         ipatch_sf2_zone_set_link_item(dest_zone, refitem);
233     }
234 
235     p = src_zone->mods;
236 
237     while(p)			/* duplicate modulators */
238     {
239         mod = ipatch_sf2_mod_duplicate((IpatchSF2Mod *)(p->data));
240         dest_zone->mods = g_slist_prepend(dest_zone->mods, mod);
241         p = g_slist_next(p);
242     }
243 
244     /* duplicate generator array */
245     memcpy(&dest_zone->genarray, &src_zone->genarray,
246            sizeof(IpatchSF2GenArray));
247 
248     IPATCH_ITEM_RUNLOCK(src_zone);
249 
250     dest_zone->mods = g_slist_reverse(dest_zone->mods);
251 }
252 
253 static void
ipatch_sf2_zone_item_remove_full(IpatchItem * item,gboolean full)254 ipatch_sf2_zone_item_remove_full(IpatchItem *item, gboolean full)
255 {
256     if(full)
257     {
258         ipatch_sf2_zone_set_link_item(IPATCH_SF2_ZONE(item), NULL);
259     }
260 
261     if(IPATCH_ITEM_CLASS(ipatch_sf2_zone_parent_class)->remove_full)
262     {
263         IPATCH_ITEM_CLASS(ipatch_sf2_zone_parent_class)->remove_full(item, full);
264     }
265 }
266 
267 /**
268  * ipatch_sf2_zone_first: (skip)
269  * @iter: Patch item iterator containing #IpatchSF2Zone items
270  *
271  * Gets the first item in a zone iterator. A convenience
272  * wrapper for ipatch_iter_first().
273  *
274  * Returns: The first zone in @iter or %NULL if empty.
275  */
276 IpatchSF2Zone *
ipatch_sf2_zone_first(IpatchIter * iter)277 ipatch_sf2_zone_first(IpatchIter *iter)
278 {
279     GObject *obj;
280     g_return_val_if_fail(iter != NULL, NULL);
281 
282     obj = ipatch_iter_first(iter);
283 
284     if(obj)
285     {
286         return (IPATCH_SF2_ZONE(obj));
287     }
288     else
289     {
290         return (NULL);
291     }
292 }
293 
294 /**
295  * ipatch_sf2_zone_next: (skip)
296  * @iter: Patch item iterator containing #IpatchSF2Zone items
297  *
298  * Gets the next item in a zone iterator. A convenience wrapper
299  * for ipatch_iter_next().
300  *
301  * Returns: The next zone in @iter or %NULL if at the end of
302  *   the list.
303  */
304 IpatchSF2Zone *
ipatch_sf2_zone_next(IpatchIter * iter)305 ipatch_sf2_zone_next(IpatchIter *iter)
306 {
307     GObject *obj;
308     g_return_val_if_fail(iter != NULL, NULL);
309 
310     obj = ipatch_iter_next(iter);
311 
312     if(obj)
313     {
314         return (IPATCH_SF2_ZONE(obj));
315     }
316     else
317     {
318         return (NULL);
319     }
320 }
321 
322 /**
323  * ipatch_sf2_zone_set_link_item:
324  * @zone: Zone to set zone item of
325  * @item: (nullable): New item for zone to use
326  *
327  * Sets the referenced item of a zone (a #IpatchSF2Inst for preset zones,
328  * #IpatchSF2Sample for instrument zones). The type specific item set routines
329  * for each zone type may be preferred, as this one doesn't do strict type
330  * checking.
331  */
332 void
ipatch_sf2_zone_set_link_item(IpatchSF2Zone * zone,IpatchItem * item)333 ipatch_sf2_zone_set_link_item(IpatchSF2Zone *zone, IpatchItem *item)
334 {
335     GValue oldval = { 0 }, newval = { 0 };
336     IpatchItem *olditem;
337 
338     if(!ipatch_sf2_zone_set_link_item_no_notify(zone, item, &olditem))
339     {
340         return;
341     }
342 
343     g_value_init(&oldval, G_TYPE_OBJECT);
344     g_value_take_object(&oldval, olditem);	/* !! take over reference */
345 
346     g_value_init(&newval, G_TYPE_OBJECT);
347     g_value_set_object(&newval, (GObject *)item);
348 
349     ipatch_item_prop_notify_by_name((IpatchItem *)zone, "link-item",
350                                     &newval, &oldval);
351     g_value_unset(&oldval);
352     g_value_unset(&newval);
353 }
354 
355 /**
356  * ipatch_sf2_zone_set_link_item_no_notify:
357  * @zone: Zone to set zone item of
358  * @item: (nullable): New item for zone to use
359  * @olditem: (out) (optional) (transfer full): Pointer to store old item pointer or %NULL to ignore.
360  *   Caller owns reference if specified.
361  *
362  * Like ipatch_sf2_zone_set_link_item() but performs no property or item
363  * change notifications for "link-item" property (shouldn't normally be used outside of derived types),
364  * and the old value can be retrieved with the @olditem parameter.
365  *
366  * Returns: TRUE if property was changed, FALSE otherwise (invalid inputs)
367  */
368 gboolean
ipatch_sf2_zone_set_link_item_no_notify(IpatchSF2Zone * zone,IpatchItem * item,IpatchItem ** olditem)369 ipatch_sf2_zone_set_link_item_no_notify(IpatchSF2Zone *zone, IpatchItem *item,
370                                         IpatchItem **olditem)
371 {
372     IpatchItem *oldie;
373     GValue old_title = { 0 }, new_title = { 0 };
374 
375     if(olditem)
376     {
377         *olditem = NULL;    /* in case of failure */
378     }
379 
380     g_return_val_if_fail(IPATCH_IS_SF2_ZONE(zone), FALSE);
381     g_return_val_if_fail(!item || IPATCH_IS_ITEM(item), FALSE);
382 
383     ipatch_item_get_property_fast(IPATCH_ITEM(zone), ipatch_item_pspec_title, &old_title);        // ++ alloc value
384 
385     if(item)
386     {
387         g_object_ref(item);    /* ref for zone */
388     }
389 
390     IPATCH_ITEM_WLOCK(zone);
391     oldie = zone->item;
392     zone->item = item;
393     IPATCH_ITEM_WUNLOCK(zone);
394 
395     /* remove "title" notify on old item */
396     if(oldie)
397         ipatch_item_prop_disconnect_matched(oldie, ipatch_item_pspec_title,
398                                             ipatch_sf2_zone_link_item_title_notify, zone);
399 
400     /* add a prop notify on link-item "title" so zone can notify it's title also */
401     if(item)
402         ipatch_item_prop_connect(item, ipatch_item_pspec_title,
403                                  ipatch_sf2_zone_link_item_title_notify, NULL, zone);
404 
405     /* either caller takes reference to old item or we unref it, unref outside of lock */
406     if(olditem)
407     {
408         *olditem = oldie;
409     }
410     else if(oldie)
411     {
412         g_object_unref(oldie);
413     }
414 
415     ipatch_item_get_property_fast(IPATCH_ITEM(zone), ipatch_item_pspec_title, &new_title);      // ++ alloc value
416     ipatch_item_prop_notify((IpatchItem *)zone, ipatch_item_pspec_title, &old_title, &new_title);
417 
418     g_value_unset(&old_title);    // -- free value
419     g_value_unset(&new_title);    // -- free value
420 
421     return (TRUE);
422 }
423 
424 /* property notify for when link-item "title" property changes */
425 static void
ipatch_sf2_zone_link_item_title_notify(IpatchItemPropNotify * info)426 ipatch_sf2_zone_link_item_title_notify(IpatchItemPropNotify *info)
427 {
428     /* notify that zone's title changed */
429     IpatchItem *zone = (IpatchItem *)(info->user_data);
430     ipatch_item_prop_notify_by_name(zone, "title", info->new_value, info->old_value);
431 }
432 
433 /**
434  * ipatch_sf2_zone_get_link_item:
435  * @zone: Zone to get referenced item of
436  *
437  * Gets the referenced item from a zone (a #IpatchSF2Inst for preset zones,
438  * #IpatchSF2Sample for instrument zones). The type specific item set routines
439  * for each zone type may be preferred, as this one doesn't do strict type
440  * checking. The returned item's reference count is incremented and the caller
441  * is responsible for unrefing it with g_object_unref().
442  *
443  * Returns: (transfer full): Zone's referenced item or %NULL if global zone. Remember to
444  * unreference the item with g_object_unref() when done with it.
445  */
446 IpatchItem *
ipatch_sf2_zone_get_link_item(IpatchSF2Zone * zone)447 ipatch_sf2_zone_get_link_item(IpatchSF2Zone *zone)
448 {
449     IpatchItem *item;
450 
451     g_return_val_if_fail(IPATCH_IS_SF2_ZONE(zone), NULL);
452 
453     IPATCH_ITEM_RLOCK(zone);
454     item = zone->item;
455 
456     if(item)
457     {
458         g_object_ref(item);
459     }
460 
461     IPATCH_ITEM_RUNLOCK(zone);
462 
463     return (item);
464 }
465 
466 /**
467  * ipatch_sf2_zone_peek_link_item: (skip)
468  * @zone: Zone to get referenced item of
469  *
470  * Like ipatch_sf2_zone_get_link_item() but does not add a reference to
471  * the returned item. This function should only be used if a reference
472  * of the returned item is ensured or only the pointer value is of
473  * interest.
474  *
475  * Returns: (transfer none): Zone's linked item. Remember that the item has NOT been referenced.
476  */
477 IpatchItem *
ipatch_sf2_zone_peek_link_item(IpatchSF2Zone * zone)478 ipatch_sf2_zone_peek_link_item(IpatchSF2Zone *zone)
479 {
480     g_return_val_if_fail(IPATCH_IS_SF2_ZONE(zone), NULL);
481     return (zone->item);	/* atomic read */
482 }
483