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: IpatchDLS2
22  * @short_description: DLS version 2 instrument file object
23  * @see_also:
24  * @stability: Stable
25  *
26  * Object type for DLS version 2 format instruments.
27  */
28 #include <stdio.h>
29 #include <string.h>
30 #include <glib.h>
31 #include <glib-object.h>
32 #include "IpatchDLS2.h"
33 #include "IpatchDLSFile.h"
34 #include "IpatchDLS2Info.h"
35 #include "IpatchDLS2Region.h"
36 #include "IpatchTypeProp.h"
37 #include "IpatchVirtualContainer_types.h"
38 #include "version.h"
39 #include "ipatch_priv.h"
40 
41 /* non-INFO properties (INFO properties use their FOURCC int values) */
42 enum
43 {
44     PROP_0,
45     PROP_VERSION
46 };
47 
48 static void ipatch_dls2_class_init(IpatchDLS2Class *klass);
49 static void ipatch_dls2_init(IpatchDLS2 *dls);
50 static void ipatch_dls2_finalize(GObject *gobject);
51 static void ipatch_dls2_set_property(GObject *object, guint property_id,
52                                      const GValue *value, GParamSpec *pspec);
53 static void ipatch_dls2_get_property(GObject *object, guint property_id,
54                                      GValue *value, GParamSpec *pspec);
55 static void ipatch_dls2_item_copy(IpatchItem *dest, IpatchItem *src,
56                                   IpatchItemCopyLinkFunc link_func,
57                                   gpointer user_data);
58 
59 static const GType *ipatch_dls2_container_child_types(void);
60 static const GType *ipatch_dls2_container_virtual_types(void);
61 static gboolean ipatch_dls2_container_init_iter(IpatchContainer *container,
62         IpatchIter *iter, GType type);
63 static void ipatch_dls2_container_make_unique(IpatchContainer *container,
64         IpatchItem *item);
65 static void ipatch_dls2_base_find_unused_locale(IpatchBase *base, int *bank,
66         int *program,
67         const IpatchItem *exclude,
68         gboolean percussion);
69 static int locale_gcompare_func(gconstpointer a, gconstpointer b);
70 static IpatchItem *
71 ipatch_dls2_base_find_item_by_locale(IpatchBase *base, int bank, int program);
72 
73 static gpointer parent_class = NULL;
74 static GType dls2_child_types[3] = { 0 };
75 static GType dls2_virt_types[4] = { 0 };
76 
77 
78 
79 /* SoundFont item type creation function */
80 GType
ipatch_dls2_get_type(void)81 ipatch_dls2_get_type(void)
82 {
83     static GType item_type = 0;
84 
85     if(!item_type)
86     {
87         static const GTypeInfo item_info =
88         {
89             sizeof(IpatchDLS2Class), NULL, NULL,
90             (GClassInitFunc)ipatch_dls2_class_init, NULL, NULL,
91             sizeof(IpatchDLS2),
92             0,
93             (GInstanceInitFunc)ipatch_dls2_init,
94         };
95 
96         item_type = g_type_register_static(IPATCH_TYPE_BASE, "IpatchDLS2",
97                                            &item_info, 0);
98     }
99 
100     return (item_type);
101 }
102 
103 static void
ipatch_dls2_class_init(IpatchDLS2Class * klass)104 ipatch_dls2_class_init(IpatchDLS2Class *klass)
105 {
106     GObjectClass *obj_class = G_OBJECT_CLASS(klass);
107     IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
108     IpatchContainerClass *container_class = IPATCH_CONTAINER_CLASS(klass);
109     IpatchBaseClass *base_class = IPATCH_BASE_CLASS(klass);
110 
111     parent_class = g_type_class_peek_parent(klass);
112 
113     obj_class->finalize = ipatch_dls2_finalize;
114     obj_class->get_property = ipatch_dls2_get_property;
115 
116     /* we use the IpatchItem item_set_property method */
117     item_class->item_set_property = ipatch_dls2_set_property;
118     item_class->copy = ipatch_dls2_item_copy;
119 
120     container_class->child_types = ipatch_dls2_container_child_types;
121     container_class->virtual_types = ipatch_dls2_container_virtual_types;
122     container_class->init_iter = ipatch_dls2_container_init_iter;
123     container_class->make_unique = ipatch_dls2_container_make_unique;
124 
125     base_class->find_unused_locale = ipatch_dls2_base_find_unused_locale;
126     base_class->find_item_by_locale = ipatch_dls2_base_find_item_by_locale;
127 
128     /* non INFO properties */
129     g_object_class_override_property(obj_class, IPATCH_DLS2_NAME, "title");
130 
131     g_object_class_install_property(obj_class, PROP_VERSION,
132                                     g_param_spec_string("version", _("Version"),
133                                             _("File version \"n.n.n.n\""),
134                                             NULL,
135                                             G_PARAM_READWRITE));
136 
137     ipatch_dls2_info_install_class_properties(obj_class);
138 
139     dls2_child_types[0] = IPATCH_TYPE_DLS2_INST;
140     dls2_child_types[1] = IPATCH_TYPE_DLS2_SAMPLE;
141 
142     dls2_virt_types[0] = IPATCH_TYPE_VIRTUAL_DLS2_MELODIC;
143     dls2_virt_types[1] = IPATCH_TYPE_VIRTUAL_DLS2_PERCUSSION;
144     dls2_virt_types[2] = IPATCH_TYPE_VIRTUAL_DLS2_SAMPLES;
145 }
146 
147 static void
ipatch_dls2_init(IpatchDLS2 * dls)148 ipatch_dls2_init(IpatchDLS2 *dls)
149 {
150     g_object_set(dls,
151                  "name", _(IPATCH_BASE_DEFAULT_NAME),
152                  "software", "libInstPatch v" IPATCH_VERSION,
153                  NULL);
154 
155     ipatch_item_clear_flags(IPATCH_ITEM(dls), IPATCH_BASE_CHANGED);
156 }
157 
158 /* function called when SoundFont is being destroyed */
159 static void
ipatch_dls2_finalize(GObject * gobject)160 ipatch_dls2_finalize(GObject *gobject)
161 {
162     IpatchDLS2 *dls = IPATCH_DLS2(gobject);
163 
164     IPATCH_ITEM_WLOCK(dls);
165 
166     ipatch_dls2_info_free(dls->info);
167     dls->info = NULL;
168 
169     g_free(dls->dlid);
170     dls->dlid = NULL;
171 
172     IPATCH_ITEM_WUNLOCK(dls);
173 
174     if(G_OBJECT_CLASS(parent_class)->finalize)
175     {
176         G_OBJECT_CLASS(parent_class)->finalize(gobject);
177     }
178 }
179 
180 static void
ipatch_dls2_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)181 ipatch_dls2_set_property(GObject *object, guint property_id,
182                          const GValue *value, GParamSpec *pspec)
183 {
184     IpatchDLS2 *dls = IPATCH_DLS2(object);
185     gboolean retval;
186 
187     if(property_id == PROP_VERSION)
188     {
189         const char *verstr;
190         guint16 msu, msl, lsu, lsl;
191 
192         verstr = g_value_get_string(value);
193 
194         if(verstr && sscanf(verstr, "%hu.%hu.%hu.%hu",
195                             &msu, &msl, &lsu, &lsl) != 4)
196         {
197             g_warning("Version property parse error");
198             return;
199         }
200 
201         IPATCH_ITEM_WLOCK(dls);
202 
203         if(verstr)
204         {
205             ipatch_item_set_flags(dls, IPATCH_DLS2_VERSION_SET);
206             dls->ms_version = (guint32)msu << 16 || msl;
207             dls->ls_version = (guint32)lsu << 16 || lsl;
208         }
209         else
210         {
211             ipatch_item_clear_flags(dls, IPATCH_DLS2_VERSION_SET);
212         }
213 
214         IPATCH_ITEM_WUNLOCK(dls);
215     }
216     else
217     {
218         IPATCH_ITEM_WLOCK(dls);
219         retval = ipatch_dls2_info_set_property(&dls->info, property_id, value);
220         IPATCH_ITEM_WUNLOCK(dls);
221 
222         /* check of "title" property needs to be notified, values do not need
223         to be sent, since its a read only property */
224         if(property_id == IPATCH_DLS2_NAME)
225             ipatch_item_prop_notify((IpatchItem *)dls, ipatch_item_pspec_title,
226                                     value, NULL);
227 
228         if(!retval)
229         {
230             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
231             return;
232         }
233     }
234 }
235 
236 static void
ipatch_dls2_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)237 ipatch_dls2_get_property(GObject *object, guint property_id,
238                          GValue *value, GParamSpec *pspec)
239 {
240     IpatchDLS2 *dls;
241     gboolean retval;
242     char *s;
243 
244     g_return_if_fail(IPATCH_IS_DLS2(object));
245     dls = IPATCH_DLS2(object);
246 
247     if(property_id == PROP_VERSION)
248     {
249         gboolean version_set;
250         guint32 ms, ls;
251 
252         IPATCH_ITEM_RLOCK(dls);
253 
254         version_set = (ipatch_item_get_flags(dls) & IPATCH_DLS2_VERSION_SET) > 0;
255         ms = dls->ms_version;
256         ls = dls->ls_version;
257 
258         IPATCH_ITEM_RUNLOCK(dls);
259 
260         if(version_set)
261         {
262             s = g_strdup_printf("%u.%u.%u.%u", ms >> 16, ms & 0xFFFF,
263                                 ls >> 16, ls & 0xFFFF);
264             g_value_take_string(value, s);
265         }
266         else
267         {
268             g_value_set_string(value, NULL);
269         }
270     }
271     else				/* INFO property or invalid */
272     {
273         IPATCH_ITEM_RLOCK(dls);
274         retval = ipatch_dls2_info_get_property(dls->info, property_id, value);
275         IPATCH_ITEM_RUNLOCK(dls);
276 
277         if(!retval)
278         {
279             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
280         }
281     }
282 }
283 
284 static void
ipatch_dls2_item_copy(IpatchItem * dest,IpatchItem * src,IpatchItemCopyLinkFunc link_func,gpointer user_data)285 ipatch_dls2_item_copy(IpatchItem *dest, IpatchItem *src,
286                       IpatchItemCopyLinkFunc link_func, gpointer user_data)
287 {
288     IpatchDLS2 *src_dls, *dest_dls;
289     IpatchItem *newitem;
290     GHashTable *repl_samples;
291     GSList *p;
292 
293     src_dls = IPATCH_DLS2(src);
294     dest_dls = IPATCH_DLS2(dest);
295 
296     /* create item replacement hash */
297     repl_samples = g_hash_table_new(NULL, NULL);
298 
299     IPATCH_ITEM_RLOCK(src_dls);
300 
301     dest_dls->ms_version = src_dls->ms_version;
302     dest_dls->ls_version = src_dls->ls_version;
303 
304     if(ipatch_item_get_flags(src_dls) & IPATCH_DLS2_VERSION_SET)
305     {
306         ipatch_item_set_flags(dest_dls, IPATCH_DLS2_VERSION_SET);
307     }
308 
309     if(IPATCH_BASE(src_dls)->file)
310     {
311         ipatch_base_set_file(IPATCH_BASE(dest_dls), IPATCH_BASE(src_dls)->file);
312     }
313 
314     dest_dls->info = ipatch_dls2_info_duplicate(src_dls->info);
315 
316     if(src_dls->dlid)
317     {
318         dest_dls->dlid = g_memdup(src_dls->dlid, IPATCH_DLS_DLID_SIZE);
319     }
320 
321     p = src_dls->samples;
322 
323     while(p)			/* duplicate samples */
324     {
325         newitem = ipatch_item_duplicate((IpatchItem *)(p->data));
326         g_return_if_fail(newitem != NULL);
327 
328         dest_dls->samples = g_slist_prepend(dest_dls->samples, newitem);
329         ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_dls));
330 
331         /* add to sample pointer replacement hash */
332         g_hash_table_insert(repl_samples, p->data, newitem);
333 
334         /* FIXME - phase linked groups? */
335 
336         p = g_slist_next(p);
337     }
338 
339     p = src_dls->insts;
340 
341     while(p)			/* duplicate instruments */
342     {
343         newitem = ipatch_item_duplicate_replace((IpatchItem *)(p->data),
344                                                 repl_samples);
345         g_return_if_fail(newitem != NULL);
346 
347         dest_dls->insts = g_slist_prepend(dest_dls->insts, newitem);
348         ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_dls));
349 
350         p = g_slist_next(p);
351     }
352 
353     IPATCH_ITEM_RUNLOCK(src_dls);
354 
355     dest_dls->insts = g_slist_reverse(dest_dls->insts);
356     dest_dls->samples = g_slist_reverse(dest_dls->samples);
357 
358     g_hash_table_destroy(repl_samples);
359 }
360 
361 static const GType *
ipatch_dls2_container_child_types(void)362 ipatch_dls2_container_child_types(void)
363 {
364     return (dls2_child_types);
365 }
366 
367 static const GType *
ipatch_dls2_container_virtual_types(void)368 ipatch_dls2_container_virtual_types(void)
369 {
370     return (dls2_virt_types);
371 }
372 
373 /* container is locked by caller */
374 static gboolean
ipatch_dls2_container_init_iter(IpatchContainer * container,IpatchIter * iter,GType type)375 ipatch_dls2_container_init_iter(IpatchContainer *container,
376                                 IpatchIter *iter, GType type)
377 {
378     IpatchDLS2 *dls = IPATCH_DLS2(container);
379 
380     if(g_type_is_a(type, IPATCH_TYPE_DLS2_INST))
381     {
382         ipatch_iter_GSList_init(iter, &dls->insts);
383     }
384     else if(g_type_is_a(type, IPATCH_TYPE_DLS2_SAMPLE))
385     {
386         ipatch_iter_GSList_init(iter, &dls->samples);
387     }
388     else
389     {
390         g_critical("Invalid child type '%s' for parent of type '%s'",
391                    g_type_name(type), g_type_name(G_OBJECT_TYPE(container)));
392         return (FALSE);
393     }
394 
395     return (TRUE);
396 }
397 
398 static void
ipatch_dls2_container_make_unique(IpatchContainer * container,IpatchItem * item)399 ipatch_dls2_container_make_unique(IpatchContainer *container,
400                                   IpatchItem *item)
401 {
402     IpatchDLS2 *dls = IPATCH_DLS2(container);
403     gboolean perc;
404     char *name, *newname;
405 
406     IPATCH_ITEM_WLOCK(dls);
407 
408     if(IPATCH_IS_DLS2_INST(item))
409     {
410         int bank, newbank, program, newprogram;
411         ipatch_dls2_inst_get_midi_locale(IPATCH_DLS2_INST(item),
412                                          &bank, &program);
413         newbank = bank;
414         newprogram = program;
415 
416         perc = (ipatch_item_get_flags(item) & IPATCH_DLS2_INST_PERCUSSION) != 0;
417 
418         ipatch_base_find_unused_midi_locale(IPATCH_BASE(dls),
419                                             &newbank, &newprogram, item, perc);
420 
421         if(bank != newbank || program != newprogram)
422             ipatch_dls2_inst_set_midi_locale(IPATCH_DLS2_INST(item),
423                                              newbank, newprogram);
424     }
425     else if(!IPATCH_IS_DLS2_SAMPLE(item))
426     {
427         g_critical("Invalid child type '%s' for IpatchDLS2 object",
428                    g_type_name(G_TYPE_FROM_INSTANCE(item)));
429         IPATCH_ITEM_WUNLOCK(dls);
430         return;
431     }
432 
433     g_object_get(item, "name", &name, NULL);
434     newname = ipatch_dls2_make_unique_name(dls, G_TYPE_FROM_INSTANCE(item),
435                                            name, NULL);
436 
437     if(!name || strcmp(name, newname) != 0)
438     {
439         g_object_set(item, "name", newname, NULL);
440     }
441 
442     IPATCH_ITEM_WUNLOCK(dls);
443 
444     g_free(name);
445     g_free(newname);
446 }
447 
448 /* base method to find an unused MIDI bank:program locale */
449 static void
ipatch_dls2_base_find_unused_locale(IpatchBase * base,int * bank,int * program,const IpatchItem * exclude,gboolean percussion)450 ipatch_dls2_base_find_unused_locale(IpatchBase *base, int *bank,
451                                     int *program, const IpatchItem *exclude,
452                                     gboolean percussion)
453 {
454     IpatchDLS2 *dls = IPATCH_DLS2(base);
455     GSList *locale_list = NULL;
456     IpatchDLS2Inst *inst;
457     GSList *p;
458     guint32 b, n;			/* Stores current bank and preset number */
459     guint32 lbank, lprogram;
460 
461     /* fill array with bank and preset numbers */
462     IPATCH_ITEM_RLOCK(dls);
463     p = dls->insts;
464 
465     while(p)
466     {
467         inst = (IpatchDLS2Inst *)(p->data);
468 
469         /* only add to locale list if not the exclude item */
470         if((gpointer)inst != (gpointer)exclude)
471             locale_list = g_slist_prepend(locale_list, GUINT_TO_POINTER
472                                           ((inst->bank << 16) | inst->program));
473 
474         p = g_slist_next(p);
475     }
476 
477     IPATCH_ITEM_RUNLOCK(dls);
478 
479     if(!locale_list)
480     {
481         *program = 0;
482         return;
483     }
484 
485     locale_list = g_slist_sort(locale_list, (GCompareFunc)locale_gcompare_func);
486 
487     b = (guint32) * bank;
488     n = (guint32) * program;
489 
490     /* loop through sorted list of bank:programs */
491     p = locale_list;
492 
493     while(p)
494     {
495         lprogram = GPOINTER_TO_UINT(p->data);
496         lbank = lprogram >> 16;
497         lprogram &= 0xFFFF;
498 
499         if(lbank > b || (lbank == b && lprogram > n))
500         {
501             break;
502         }
503 
504         if(lbank >= b)
505         {
506             if(++n > 127)
507             {
508                 n = 0;
509                 b++;
510             }
511         }
512 
513         p = g_slist_delete_link(p, p);  /* delete and advance */
514     }
515 
516     *bank = b;
517     *program = n;
518 
519     if(p)
520     {
521         g_slist_free(p);    /* free remainder of list */
522     }
523 }
524 
525 /* function used to do a temporary sort on preset list for
526    ipatch_dls2_base_find_unused_locale */
527 static int
locale_gcompare_func(gconstpointer a,gconstpointer b)528 locale_gcompare_func(gconstpointer a, gconstpointer b)
529 {
530     return (GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b));
531 }
532 
533 static IpatchItem *
ipatch_dls2_base_find_item_by_locale(IpatchBase * base,int bank,int program)534 ipatch_dls2_base_find_item_by_locale(IpatchBase *base, int bank, int program)
535 {
536     IpatchDLS2Inst *inst;
537 
538     inst = ipatch_dls2_find_inst(IPATCH_DLS2(base), NULL, bank, program, NULL);
539     return ((IpatchItem *)inst);
540 }
541 
542 /**
543  * ipatch_dls2_new:
544  *
545  * Create a new DLS base object.
546  *
547  * Returns: New DLS base object with a reference count of 1. Caller
548  * owns the reference and removing it will destroy the item.
549  */
550 IpatchDLS2 *
ipatch_dls2_new(void)551 ipatch_dls2_new(void)
552 {
553     return (IPATCH_DLS2(g_object_new(IPATCH_TYPE_DLS2, NULL)));
554 }
555 
556 /**
557  * ipatch_dls2_set_file:
558  * @dls: DLS to set file object of
559  * @file: File object to use with the DLS object
560  *
561  * Sets the file object of a DLS object. DLS files are kept open
562  * for sample data that references the file. This function sets a
563  * DLS object's authoritive file. A convenience function, as
564  * ipatch_base_set_file() does the same thing (albeit without more specific
565  * type casting).
566  */
567 void
ipatch_dls2_set_file(IpatchDLS2 * dls,IpatchDLSFile * file)568 ipatch_dls2_set_file(IpatchDLS2 *dls, IpatchDLSFile *file)
569 {
570     g_return_if_fail(IPATCH_IS_DLS2(dls));
571     g_return_if_fail(IPATCH_IS_DLS_FILE(file));
572 
573     ipatch_base_set_file(IPATCH_BASE(dls), IPATCH_FILE(file));
574 }
575 
576 /**
577  * ipatch_dls2_get_file:
578  * @dls: DLS object to get file object of
579  *
580  * Gets the file object of a DLS. The returned DLS file object's
581  * reference count has been incremented. The caller owns the reference and is
582  * responsible for removing it with g_object_unref.
583  * A convenience function as ipatch_base_get_file() does the same thing
584  * (albeit without more specific type casting).
585  *
586  * Returns: (transfer full): The DLS file object or %NULL if @dls is not open. Remember
587  * to unref the file object with g_object_unref() when done with it.
588  */
589 IpatchDLSFile *
ipatch_dls2_get_file(IpatchDLS2 * dls)590 ipatch_dls2_get_file(IpatchDLS2 *dls)
591 {
592     IpatchFile *file;
593 
594     g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
595 
596     file = ipatch_base_get_file(IPATCH_BASE(dls));
597 
598     if(file)
599     {
600         return (IPATCH_DLS_FILE(file));
601     }
602     else
603     {
604         return (NULL);
605     }
606 }
607 
608 /**
609  * ipatch_dls2_get_info:
610  * @dls: DLS to get info from
611  * @fourcc: FOURCC integer id of INFO to get
612  *
613  * Get a DLS info string by FOURCC integer ID (integer representation of
614  * a 4 character RIFF chunk ID, see #IpatchRiff).
615  *
616  * Returns: New allocated info string value or %NULL if no info with the
617  * given @fourcc ID. String should be freed when finished with it.
618  */
619 char *
ipatch_dls2_get_info(IpatchDLS2 * dls,guint32 fourcc)620 ipatch_dls2_get_info(IpatchDLS2 *dls, guint32 fourcc)
621 {
622     char *val;
623 
624     g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
625 
626     IPATCH_ITEM_RLOCK(dls);
627     val = ipatch_dls2_info_get(dls->info, fourcc);
628     IPATCH_ITEM_RUNLOCK(dls);
629 
630     return (val);
631 }
632 
633 /**
634  * ipatch_dls2_set_info:
635  * @dls: DLS to set info of
636  * @fourcc: FOURCC integer ID of INFO to set
637  * @val: (nullable): Value to set info to or %NULL to unset (clear) info.
638  *
639  * Sets an INFO value in a DLS object.
640  *
641  * Emits changed signal on DLS object.
642  */
643 void
ipatch_dls2_set_info(IpatchDLS2 * dls,guint32 fourcc,const char * val)644 ipatch_dls2_set_info(IpatchDLS2 *dls, guint32 fourcc, const char *val)
645 {
646     GValue newval = { 0 }, oldval = { 0 };
647 
648     g_return_if_fail(IPATCH_IS_DLS2(dls));
649 
650     g_value_init(&newval, G_TYPE_STRING);
651     g_value_set_static_string(&newval, val);
652 
653     g_value_init(&oldval, G_TYPE_STRING);
654     g_value_take_string(&oldval, ipatch_dls2_get_info(dls, fourcc));
655 
656     IPATCH_ITEM_WLOCK(dls);
657     ipatch_dls2_info_set(&dls->info, fourcc, val);
658     IPATCH_ITEM_WUNLOCK(dls);
659 
660     ipatch_dls2_info_notify((IpatchItem *)dls, fourcc, &newval, &oldval);
661 
662     /* need to do title notify? */
663     if(fourcc == IPATCH_DLS2_NAME)
664         ipatch_item_prop_notify((IpatchItem *)dls, ipatch_item_pspec_title,
665                                 &newval, &oldval);
666 
667     g_value_unset(&oldval);
668     g_value_unset(&newval);
669 }
670 
671 /**
672  * ipatch_dls2_make_unique_name:
673  * @dls: DLS object
674  * @child_type: A child type of @dls to search for a unique name in
675  * @name: (nullable): An initial name to use or NULL
676  * @exclude: (nullable): An item to exclude from search or NULL
677  *
678  * Generates a unique name for the given @child_type in @dls. The @name
679  * parameter is used as a base and is modified, by appending a number, to
680  * make it unique (if necessary). The @exclude parameter is used to exclude
681  * an existing @dls child item from the search.
682  *
683  * MT-Note: To ensure that an item is actually unique before being
684  * added to a DLS object, ipatch_container_add_unique() should be
685  * used.
686  *
687  * Returns: A new unique name which should be freed when finished with it.
688  */
689 char *
ipatch_dls2_make_unique_name(IpatchDLS2 * dls,GType child_type,const char * name,const IpatchItem * exclude)690 ipatch_dls2_make_unique_name(IpatchDLS2 *dls, GType child_type,
691                              const char *name, const IpatchItem *exclude)
692 {
693     GSList **list, *p;
694     char *curname, *numptr;
695     const char *temp;
696     guint count = 2, info_ofs, len;
697 
698     g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
699 
700     if(g_type_is_a(child_type, IPATCH_TYPE_DLS2_INST))
701     {
702         list = &dls->insts;
703         info_ofs = G_STRUCT_OFFSET(IpatchDLS2Inst, info);
704 
705         if(!name || !*name)
706         {
707             name = _("New Instrument");
708         }
709     }
710     else if(g_type_is_a(child_type, IPATCH_TYPE_DLS2_SAMPLE))
711     {
712         list = &dls->samples;
713         info_ofs = G_STRUCT_OFFSET(IpatchDLS2Sample, info);
714 
715         if(!name || !*name)
716         {
717             name = _("New Sample");
718         }
719     }
720     else
721     {
722         g_critical("Invalid child type '%s' of parent type '%s'",
723                    g_type_name(child_type), g_type_name(G_OBJECT_TYPE(dls)));
724         return (NULL);
725     }
726 
727     len = strlen(name);
728 
729     /* allocate string size + 10 chars for number + zero termination */
730     curname = g_malloc0(len + 10 + 1);
731     strcpy(curname, name);	/* copy name */
732     numptr = curname + len; /* pointer to end of name to concat number */
733 
734     IPATCH_ITEM_RLOCK(dls);
735 
736     p = *list;
737 
738     while(p)	/* check for duplicate */
739     {
740         IPATCH_ITEM_RLOCK(p->data);  /* MT - Recursive LOCK */
741 
742         if(p->data != exclude)
743         {
744             temp = ipatch_dls2_info_peek
745                    (G_STRUCT_MEMBER(IpatchDLS2Info *, p->data, info_ofs),
746                     IPATCH_DLS2_NAME);
747 
748             if(temp && strcmp(temp, curname) == 0)
749             {
750                 /* duplicate name */
751                 IPATCH_ITEM_RUNLOCK(p->data);
752 
753                 sprintf(numptr, "%u", count++);
754 
755                 p = *list;		/* start over */
756                 continue;
757             }
758         }
759 
760         IPATCH_ITEM_RUNLOCK(p->data);
761         p = g_slist_next(p);
762     }
763 
764     IPATCH_ITEM_RUNLOCK(dls);
765 
766     curname = g_realloc(curname, strlen(curname) + 1);
767     return (curname);
768 }
769 
770 /**
771  * ipatch_dls2_find_inst:
772  * @dls: DLS object to search in
773  * @name: (nullable): Name of instrument to find or %NULL to match any name
774  * @bank: MIDI bank number of instrument to search for or -1 to not search by
775  *   MIDI bank:program numbers
776  * @program: MIDI program number of instrument to search for, only used
777  *   if @bank is 0-128
778  * @exclude: (nullable): A instrument to exclude from the search or %NULL
779  *
780  * Find a instrument by name or bank:program MIDI numbers. If instrument @name
781  * and @bank:@program are specified then match for either condition.
782  * If a instrument is found its reference count is incremented before it
783  * is returned. The caller is responsible for removing the reference
784  * with g_object_unref() when finished with it.
785  *
786  * Returns: (transfer full): The matching instrument or %NULL if not found. Remember to unref
787  * the item when finished with it.
788  */
789 IpatchDLS2Inst *
ipatch_dls2_find_inst(IpatchDLS2 * dls,const char * name,int bank,int program,const IpatchDLS2Inst * exclude)790 ipatch_dls2_find_inst(IpatchDLS2 *dls, const char *name, int bank,
791                       int program, const IpatchDLS2Inst *exclude)
792 {
793     IpatchDLS2Inst *inst;
794     gboolean bynum = FALSE;
795     const char *temp = NULL;
796     GSList *p;
797 
798     g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
799 
800     /* if bank and program are valid, then search by number */
801     if(bank >= 0 && program >= 0 && program < 128)
802     {
803         bynum = TRUE;
804     }
805 
806     IPATCH_ITEM_RLOCK(dls);
807     p = dls->insts;
808 
809     while(p)
810     {
811         inst = (IpatchDLS2Inst *)(p->data);
812         IPATCH_ITEM_RLOCK(inst);	/* MT - Recursive LOCK */
813 
814         if(inst != exclude
815                 && ((bynum && inst->bank == bank && inst->program == program)
816                     || (name && (temp = ipatch_dls2_info_peek
817                                         (inst->info, IPATCH_DLS2_NAME))
818                         && strcmp(temp, name) == 0)))
819         {
820             g_object_ref(inst);
821             IPATCH_ITEM_RUNLOCK(inst);
822             IPATCH_ITEM_RUNLOCK(dls);
823             return (inst);
824         }
825 
826         IPATCH_ITEM_RUNLOCK(inst);
827         p = g_slist_next(p);
828     }
829 
830     IPATCH_ITEM_RUNLOCK(dls);
831 
832     return (NULL);
833 }
834 
835 /**
836  * ipatch_dls2_find_sample:
837  * @dls: DLS object to search in
838  * @name: Name of sample to find
839  * @exclude: (nullable): A sample to exclude from the search or %NULL
840  *
841  * Find a sample by @name in a SoundFont. If a sample is found its
842  * reference count is incremented before it is returned. The caller
843  * is responsible for removing the reference with g_object_unref()
844  * when finished with it.
845  *
846  * Returns: (transfer full): The matching sample or %NULL if not found. Remember to unref
847  * the item when finished with it.
848  */
849 IpatchDLS2Sample *
ipatch_dls2_find_sample(IpatchDLS2 * dls,const char * name,const IpatchDLS2Sample * exclude)850 ipatch_dls2_find_sample(IpatchDLS2 *dls, const char *name,
851                         const IpatchDLS2Sample *exclude)
852 {
853     IpatchDLS2Sample *sample;
854     const char *temp = NULL;
855     GSList *p;
856 
857     g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
858     g_return_val_if_fail(name != NULL, NULL);
859 
860     IPATCH_ITEM_RLOCK(dls);
861     p = dls->samples;
862 
863     while(p)
864     {
865         sample = (IpatchDLS2Sample *)(p->data);
866         IPATCH_ITEM_RLOCK(sample);  /* MT - Recursive LOCK */
867 
868         if(sample != exclude && (temp = ipatch_dls2_info_peek
869                                         (sample->info, IPATCH_DLS2_NAME))
870                 && strcmp(temp, name) == 0)
871         {
872             g_object_ref(sample);
873             IPATCH_ITEM_RUNLOCK(sample);
874             IPATCH_ITEM_RUNLOCK(dls);
875             return (sample);
876         }
877 
878         IPATCH_ITEM_RUNLOCK(sample);
879         p = g_slist_next(p);
880     }
881 
882     IPATCH_ITEM_RUNLOCK(dls);
883 
884     return (NULL);
885 }
886 
887 /**
888  * ipatch_dls2_get_region_references:
889  * @sample: Sample to locate referencing regions of.
890  *
891  * Get list of regions referencing an #IpatchDLS2Sample.
892  *
893  * Returns: (transfer full): New item list containing #IpatchDLS2Region objects
894  * that refer to @sample. The returned list has a reference count of 1 which
895  * the caller owns, unreference it when done.
896  */
897 IpatchList *
ipatch_dls2_get_region_references(IpatchDLS2Sample * sample)898 ipatch_dls2_get_region_references(IpatchDLS2Sample *sample)
899 {
900     IpatchDLS2 *dls;
901     IpatchDLS2Region *region;
902     IpatchList *refitems;
903     IpatchIter iter, region_iter;
904     IpatchDLS2Inst *inst;
905     IpatchItem *pitem;
906     gboolean success;
907 
908     g_return_val_if_fail(IPATCH_IS_DLS2_SAMPLE(sample), NULL);
909 
910     pitem = ipatch_item_get_parent(IPATCH_ITEM(sample));
911     g_return_val_if_fail(IPATCH_IS_DLS2(pitem), NULL);
912     dls = IPATCH_DLS2(pitem);
913 
914     refitems = ipatch_list_new();
915 
916     IPATCH_ITEM_RLOCK(dls);
917 
918     success = ipatch_container_init_iter((IpatchContainer *)dls, &iter,
919                                          IPATCH_TYPE_DLS2_INST);
920     g_return_val_if_fail(success != FALSE, NULL);
921 
922     inst = ipatch_dls2_inst_first(&iter);
923 
924     while(inst)			/* loop over instruments */
925     {
926         IPATCH_ITEM_RLOCK(inst);	/* ## embedded lock */
927 
928         success = ipatch_container_init_iter((IpatchContainer *)dls,
929                                              &region_iter,
930                                              IPATCH_TYPE_DLS2_INST);
931         g_return_val_if_fail(success != FALSE, NULL);
932 
933         region = ipatch_dls2_region_first(&region_iter);
934 
935         while(region)		/* loop over regions */
936         {
937             if(ipatch_dls2_region_peek_sample(region) == sample)
938             {
939                 g_object_ref(region);  /* ++ ref region for new iterator */
940                 refitems->items = g_list_prepend(refitems->items, region);
941             }
942 
943             region = ipatch_dls2_region_next(&region_iter);
944         }
945 
946         IPATCH_ITEM_RUNLOCK(inst);
947 
948         inst = ipatch_dls2_inst_next(&iter);
949     }
950 
951     IPATCH_ITEM_RUNLOCK(dls);
952 
953     return (refitems);
954 }
955