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: IpatchSF2
22 * @short_description: SoundFont instrument file object
23 * @see_also: #IpatchSF2Preset, #IpatchSF2Inst, #IpatchSF2Sample
24 * @stability: Stable
25 *
26 * SoundFont version 2 instrument file object. Parent to #IpatchSF2Preset,
27 * #IpatchSF2Inst and #IpatchSF2Sample objects.
28 */
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <glib.h>
33 #include <glib-object.h>
34 #include "IpatchParamProp.h"
35 #include "IpatchSF2.h"
36 #include "IpatchSF2File.h"
37 #include "IpatchSF2Preset.h"
38 #include "IpatchSF2Zone.h"
39 #include "IpatchTypeProp.h"
40 #include "IpatchVirtualContainer_types.h"
41 #include "ipatch_priv.h"
42 #include "version.h"
43
44 /* the Info properties below shouldn't conflict, since they are composed of
45 their 4 byte RIFF chunk ID */
46 enum
47 {
48 PROP_0,
49 PROP_SAMPLES_24BIT /* indicates that samples should be saved in 24 bit */
50 };
51
52 /* !! Keep in order with IpatchSF2InfoType in IpatchSF2.h */
53 static guint info_ids[] =
54 {
55 IPATCH_SF2_VERSION, IPATCH_SF2_ENGINE, IPATCH_SF2_NAME,
56 IPATCH_SF2_ROM_NAME, IPATCH_SF2_ROM_VERSION, IPATCH_SF2_DATE,
57 IPATCH_SF2_AUTHOR, IPATCH_SF2_PRODUCT, IPATCH_SF2_COPYRIGHT,
58 IPATCH_SF2_COMMENT, IPATCH_SF2_SOFTWARE
59 };
60
61 /* parameter specs for each info property */
62 static GParamSpec *info_prop_pspecs[IPATCH_SF2_INFO_COUNT];
63
64
65 #define ERR_MSG_INVALID_INFO_ID_1 "Invalid SoundFont info ID (%d)"
66
67 static void ipatch_sf2_class_init(IpatchSF2Class *klass);
68 static void ipatch_sf2_init(IpatchSF2 *sfont);
69 static void ipatch_sf2_finalize(GObject *gobject);
70 static void ipatch_sf2_set_property(GObject *object, guint property_id,
71 const GValue *value, GParamSpec *pspec);
72 static void ipatch_sf2_get_property(GObject *object, guint property_id,
73 GValue *value, GParamSpec *pspec);
74 static void ipatch_sf2_item_copy(IpatchItem *dest, IpatchItem *src,
75 IpatchItemCopyLinkFunc link_func,
76 gpointer user_data);
77 static void ipatch_sf2_info_hash_foreach(gpointer key, gpointer value,
78 gpointer user_data);
79
80 static const GType *ipatch_sf2_container_child_types(void);
81 static const GType *ipatch_sf2_container_virtual_types(void);
82 static gboolean ipatch_sf2_container_init_iter(IpatchContainer *container,
83 IpatchIter *iter, GType type);
84 static void ipatch_sf2_container_make_unique(IpatchContainer *container,
85 IpatchItem *item);
86 static void ipatch_sf2_base_find_unused_locale(IpatchBase *base, int *bank,
87 int *program,
88 const IpatchItem *exclude,
89 gboolean percussion);
90 static int locale_gcompare_func(gconstpointer a, gconstpointer b);
91 static IpatchItem *
92 ipatch_sf2_base_find_item_by_locale(IpatchBase *base, int bank, int program);
93 static void ipatch_sf2_real_set_info(IpatchSF2 *sf, IpatchSF2InfoType id,
94 const char *val);
95 static void ipatch_sf2_foreach_info_GHFunc(gpointer key, gpointer value,
96 gpointer data);
97 static int ipatch_sf2_info_array_qsort(const void *a, const void *b);
98
99 static gpointer parent_class = NULL;
100
101 static GType sf2_child_types[4] = { 0 };
102 static GType sf2_virt_types[6] = { 0 };
103
104
105 /* SoundFont item type creation function */
106 GType
ipatch_sf2_get_type(void)107 ipatch_sf2_get_type(void)
108 {
109 static GType item_type = 0;
110
111 if(!item_type)
112 {
113 static const GTypeInfo item_info =
114 {
115 sizeof(IpatchSF2Class), NULL, NULL,
116 (GClassInitFunc)ipatch_sf2_class_init, NULL, NULL,
117 sizeof(IpatchSF2), 0,
118 (GInstanceInitFunc)ipatch_sf2_init,
119 };
120
121 item_type = g_type_register_static(IPATCH_TYPE_BASE, "IpatchSF2",
122 &item_info, 0);
123 }
124
125 return (item_type);
126 }
127
128 static void
ipatch_sf2_class_init(IpatchSF2Class * klass)129 ipatch_sf2_class_init(IpatchSF2Class *klass)
130 {
131 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
132 IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
133 IpatchContainerClass *container_class = IPATCH_CONTAINER_CLASS(klass);
134 IpatchBaseClass *base_class = IPATCH_BASE_CLASS(klass);
135 GParamSpec **sp = &info_prop_pspecs[0];
136
137 parent_class = g_type_class_peek_parent(klass);
138
139 obj_class->finalize = ipatch_sf2_finalize;
140 obj_class->get_property = ipatch_sf2_get_property;
141
142 /* we use the IpatchItem item_set_property method */
143 item_class->item_set_property = ipatch_sf2_set_property;
144 item_class->copy = ipatch_sf2_item_copy;
145
146 container_class->child_types = ipatch_sf2_container_child_types;
147 container_class->virtual_types = ipatch_sf2_container_virtual_types;
148 container_class->init_iter = ipatch_sf2_container_init_iter;
149 container_class->make_unique = ipatch_sf2_container_make_unique;
150
151 base_class->find_unused_locale = ipatch_sf2_base_find_unused_locale;
152 base_class->find_item_by_locale = ipatch_sf2_base_find_item_by_locale;
153
154 g_object_class_install_property(obj_class, PROP_SAMPLES_24BIT,
155 g_param_spec_boolean("samples-24bit", _("Samples 24bit"),
156 _("Enable 24 bit samples"), FALSE, G_PARAM_READWRITE));
157
158 g_object_class_override_property(obj_class, IPATCH_SF2_NAME, "title");
159
160 *sp = g_param_spec_string("version", _("Version"),
161 _("SoundFont version (\"major.minor\")"), "2.01", G_PARAM_READWRITE);
162 g_object_class_install_property(obj_class, IPATCH_SF2_VERSION, *sp++);
163
164 *sp = ipatch_param_set(g_param_spec_string("engine", _("Engine"),
165 _("Sound synthesis engine identifier"), "EMU8000", G_PARAM_READWRITE),
166 "string-max-length", 255, NULL);
167 g_object_class_install_property(obj_class, IPATCH_SF2_ENGINE, *sp++);
168
169 *sp = ipatch_param_set(g_param_spec_string("name", _("Name"),
170 _("SoundFont name"), NULL, G_PARAM_READWRITE),
171 "string-max-length", 255, NULL);
172 g_object_class_install_property(obj_class, IPATCH_SF2_NAME, *sp++);
173
174 *sp = ipatch_param_set(g_param_spec_string("rom-name", _("ROM name"),
175 _("ROM name identifier"), NULL, G_PARAM_READWRITE),
176 "string-max-length", 255, NULL);
177 g_object_class_install_property(obj_class, IPATCH_SF2_ROM_NAME, *sp++);
178
179 *sp = ipatch_param_set(g_param_spec_string("rom-version", _("ROM version"),
180 _("ROM version \"major.minor\""), NULL, G_PARAM_READWRITE),
181 "string-max-length", 255, NULL);
182 g_object_class_install_property(obj_class, IPATCH_SF2_ROM_VERSION, *sp++);
183
184 *sp = ipatch_param_set(g_param_spec_string("date", _("Date"),
185 _("Creation date"), NULL, G_PARAM_READWRITE),
186 "string-max-length", 255, NULL);
187 g_object_class_install_property(obj_class, IPATCH_SF2_DATE, *sp++);
188
189 *sp = ipatch_param_set(g_param_spec_string("author", _("Author"),
190 _("Author of SoundFont"), NULL, G_PARAM_READWRITE),
191 "string-max-length", 255, NULL);
192 g_object_class_install_property(obj_class, IPATCH_SF2_AUTHOR, *sp++);
193
194 *sp = ipatch_param_set(g_param_spec_string("product", _("Product"),
195 _("Product SoundFont is intended for"), NULL, G_PARAM_READWRITE),
196 "string-max-length", 255, NULL);
197 g_object_class_install_property(obj_class, IPATCH_SF2_PRODUCT, *sp++);
198
199 *sp = ipatch_param_set(g_param_spec_string("copyright", _("Copyright"),
200 _("Copyright"), NULL, G_PARAM_READWRITE),
201 "string-max-length", 255, NULL);
202 g_object_class_install_property(obj_class, IPATCH_SF2_COPYRIGHT, *sp++);
203
204 *sp = ipatch_param_set(g_param_spec_string("comment", _("Comments"),
205 _("Comments"), NULL, G_PARAM_READWRITE),
206 "string-max-length", 65535, NULL);
207 g_object_class_install_property(obj_class, IPATCH_SF2_COMMENT, *sp++);
208
209 *sp = ipatch_param_set(g_param_spec_string("software", _("Software"),
210 _("Software 'created by:modified by'"), NULL, G_PARAM_READWRITE),
211 "string-max-length", 255, NULL);
212 g_object_class_install_property(obj_class, IPATCH_SF2_SOFTWARE, *sp);
213
214 sf2_child_types[0] = IPATCH_TYPE_SF2_PRESET;
215 sf2_child_types[1] = IPATCH_TYPE_SF2_INST;
216 sf2_child_types[2] = IPATCH_TYPE_SF2_SAMPLE;
217
218 sf2_virt_types[0] = IPATCH_TYPE_VIRTUAL_SF2_MELODIC;
219 sf2_virt_types[1] = IPATCH_TYPE_VIRTUAL_SF2_PERCUSSION;
220 sf2_virt_types[2] = IPATCH_TYPE_VIRTUAL_SF2_INST;
221 sf2_virt_types[3] = IPATCH_TYPE_VIRTUAL_SF2_SAMPLES;
222 sf2_virt_types[4] = IPATCH_TYPE_VIRTUAL_SF2_ROM;
223 }
224
225 static void
ipatch_sf2_init(IpatchSF2 * sfont)226 ipatch_sf2_init(IpatchSF2 *sfont)
227 {
228 sfont->ver_major = 2;
229 sfont->ver_minor = 1;
230
231 /* we convert 4 char info IDs to INTs, also set up hash table to free
232 allocated string values */
233 sfont->info = g_hash_table_new_full(NULL, NULL, NULL,
234 (GDestroyNotify)g_free);
235
236 /* set required SoundFont info to default values */
237 ipatch_sf2_set_info(sfont, IPATCH_SF2_NAME, _(IPATCH_BASE_DEFAULT_NAME));
238 ipatch_sf2_set_info(sfont, IPATCH_SF2_ENGINE, IPATCH_SF2_DEFAULT_ENGINE);
239 ipatch_sf2_set_info(sfont, IPATCH_SF2_SOFTWARE,
240 "libInstPatch v" IPATCH_VERSION ":");
241
242 ipatch_item_clear_flags(IPATCH_ITEM(sfont), IPATCH_BASE_CHANGED);
243 }
244
245 /* function called when SoundFont is being destroyed */
246 static void
ipatch_sf2_finalize(GObject * gobject)247 ipatch_sf2_finalize(GObject *gobject)
248 {
249 IpatchSF2 *sf = IPATCH_SF2(gobject);
250
251 IPATCH_ITEM_WLOCK(sf);
252
253 g_hash_table_destroy(sf->info); /* destroy SoundFont info */
254 sf->info = NULL;
255
256 IPATCH_ITEM_WUNLOCK(sf);
257
258 if(G_OBJECT_CLASS(parent_class)->finalize)
259 {
260 G_OBJECT_CLASS(parent_class)->finalize(gobject);
261 }
262 }
263
264 static void
ipatch_sf2_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)265 ipatch_sf2_set_property(GObject *object, guint property_id,
266 const GValue *value, GParamSpec *pspec)
267 {
268 IpatchSF2 *sf;
269 gboolean b;
270
271 g_return_if_fail(IPATCH_IS_SF2(object));
272 sf = IPATCH_SF2(object);
273
274 if(property_id == PROP_SAMPLES_24BIT)
275 {
276 b = g_value_get_boolean(value);
277
278 if(b)
279 {
280 ipatch_item_set_flags(IPATCH_ITEM(sf), IPATCH_SF2_SAMPLES_24BIT);
281 }
282 else
283 {
284 ipatch_item_clear_flags(IPATCH_ITEM(sf), IPATCH_SF2_SAMPLES_24BIT);
285 }
286 }
287 else if(property_id == IPATCH_SF2_VERSION
288 || property_id == IPATCH_SF2_ROM_VERSION)
289 {
290 guint major, minor;
291
292 if(sscanf(g_value_get_string(value), "%u.%u",
293 &major, &minor) != 2)
294 {
295 g_critical("SoundFont version property parse error");
296 return;
297 }
298
299 if(property_id == IPATCH_SF2_VERSION)
300 {
301 IPATCH_ITEM_WLOCK(sf);
302 sf->ver_major = major;
303 sf->ver_minor = minor;
304 IPATCH_ITEM_WUNLOCK(sf);
305 }
306 else
307 {
308 IPATCH_ITEM_WLOCK(sf);
309 sf->romver_major = major;
310 sf->romver_minor = minor;
311 IPATCH_ITEM_WUNLOCK(sf);
312 }
313 }
314 else if(ipatch_sf2_info_id_is_valid(property_id))
315 {
316 ipatch_sf2_real_set_info(sf, property_id, g_value_get_string(value));
317
318 /* need to do a title property notify? */
319 if(property_id == IPATCH_SF2_NAME)
320 ipatch_item_prop_notify((IpatchItem *)sf, ipatch_item_pspec_title,
321 value, NULL);
322 }
323 else
324 {
325 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
326 }
327 }
328
329 static void
ipatch_sf2_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)330 ipatch_sf2_get_property(GObject *object, guint property_id,
331 GValue *value, GParamSpec *pspec)
332 {
333 IpatchSF2 *sf;
334 char *s;
335
336 g_return_if_fail(IPATCH_IS_SF2(object));
337 sf = IPATCH_SF2(object);
338
339 if(property_id == PROP_SAMPLES_24BIT)
340 {
341 g_value_set_boolean(value, (ipatch_item_get_flags(IPATCH_ITEM(sf))
342 & IPATCH_SF2_SAMPLES_24BIT) != 0);
343 }
344 else if(property_id == IPATCH_SF2_VERSION
345 || property_id == IPATCH_SF2_ROM_VERSION)
346 {
347 int major, minor;
348
349 IPATCH_ITEM_RLOCK(sf);
350
351 if(property_id == IPATCH_SF2_VERSION)
352 {
353 major = sf->ver_major;
354 minor = sf->ver_minor;
355 }
356 else
357 {
358 major = sf->romver_major;
359 minor = sf->romver_minor;
360 }
361
362 IPATCH_ITEM_RUNLOCK(sf);
363
364 s = g_strdup_printf("%d.%d", major, minor);
365 g_value_take_string(value, s);
366 }
367 else if(ipatch_sf2_info_id_is_valid(property_id))
368 {
369 g_value_take_string(value, ipatch_sf2_get_info(sf, property_id));
370 }
371 else
372 {
373 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
374 }
375 }
376
377 /* item copy function, note that this is an #IpatchBase derived object, so
378 link_func is not used */
379 static void
ipatch_sf2_item_copy(IpatchItem * dest,IpatchItem * src,IpatchItemCopyLinkFunc link_func,gpointer user_data)380 ipatch_sf2_item_copy(IpatchItem *dest, IpatchItem *src,
381 IpatchItemCopyLinkFunc link_func,
382 gpointer user_data)
383 {
384 IpatchSF2 *src_sf, *dest_sf;
385 IpatchItem *newitem;
386 GHashTable *repl_samples, *repl_insts;
387 gboolean has_linked = FALSE;
388 GSList *p;
389
390 src_sf = IPATCH_SF2(src);
391 dest_sf = IPATCH_SF2(dest);
392
393 /* create item replacement hash */
394 repl_samples = g_hash_table_new(NULL, NULL);
395 repl_insts = g_hash_table_new(NULL, NULL);
396
397 IPATCH_ITEM_RLOCK(src_sf);
398
399 if(ipatch_item_get_flags(src) & IPATCH_SF2_SAMPLES_24BIT)
400 {
401 ipatch_item_set_flags(dest, IPATCH_SF2_SAMPLES_24BIT);
402 }
403 else
404 {
405 ipatch_item_clear_flags(dest, IPATCH_SF2_SAMPLES_24BIT);
406 }
407
408 dest_sf->ver_major = src_sf->ver_major;
409 dest_sf->ver_minor = src_sf->ver_minor;
410 dest_sf->romver_major = src_sf->romver_major;
411 dest_sf->romver_minor = src_sf->romver_minor;
412
413 if(IPATCH_BASE(src_sf)->file)
414 {
415 ipatch_base_set_file(IPATCH_BASE(dest_sf), IPATCH_BASE(src_sf)->file);
416 }
417
418 /* duplicate the info variables */
419 g_hash_table_foreach(src_sf->info, ipatch_sf2_info_hash_foreach, dest_sf);
420
421 p = src_sf->samples;
422
423 while(p) /* duplicate samples */
424 {
425 /* ++ ref new duplicate sample, !! sample list takes it over */
426 newitem = ipatch_item_duplicate((IpatchItem *)(p->data));
427 dest_sf->samples = g_slist_prepend(dest_sf->samples, newitem);
428 ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_sf));
429
430 /* add to sample pointer replacement hash */
431 g_hash_table_insert(repl_samples, p->data, newitem);
432
433 /* check if sample is stereo linked */
434 if(ipatch_sf2_sample_peek_linked((IpatchSF2Sample *)newitem))
435 {
436 has_linked = TRUE;
437 }
438
439 p = g_slist_next(p);
440 }
441
442 /* if any linked samples exist, we must replace old linked pointers,
443 duplicate "replace" hash wont work since samples reference items in the
444 same list */
445 if(has_linked)
446 {
447 IpatchSF2Sample *linked;
448
449 p = dest_sf->samples;
450
451 while(p)
452 {
453 IpatchSF2Sample *sample = (IpatchSF2Sample *)(p->data);
454
455 linked = ipatch_sf2_sample_peek_linked(sample);
456
457 if(linked)
458 {
459 linked = g_hash_table_lookup(repl_samples, linked);
460 ipatch_sf2_sample_set_linked(sample, linked);
461 }
462
463 p = g_slist_next(p);
464 }
465 }
466
467 p = src_sf->insts;
468
469 while(p) /* duplicate instruments */
470 {
471 /* ++ ref new duplicate instrument, !! instrument list takes it over
472 * duplicate instrument and replace referenced sample pointers. */
473 newitem = ipatch_item_duplicate_replace((IpatchItem *)(p->data),
474 repl_samples);
475 dest_sf->insts = g_slist_prepend(dest_sf->insts, newitem);
476 ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_sf));
477
478 /* add to instrument pointer replacement hash */
479 g_hash_table_insert(repl_insts, p->data, newitem);
480
481 p = g_slist_next(p);
482 }
483
484 p = src_sf->presets;
485
486 while(p) /* duplicate presets */
487 {
488 /* ++ ref new duplicate preset, !! preset list takes it over
489 * duplicate preset and replace referenced instrument pointers. */
490 newitem = ipatch_item_duplicate_replace((IpatchItem *)(p->data),
491 repl_insts);
492 dest_sf->presets = g_slist_prepend(dest_sf->presets, newitem);
493 ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_sf));
494
495 p = g_slist_next(p);
496 }
497
498 IPATCH_ITEM_RUNLOCK(src_sf);
499
500 dest_sf->presets = g_slist_reverse(dest_sf->presets);
501 dest_sf->insts = g_slist_reverse(dest_sf->insts);
502 dest_sf->samples = g_slist_reverse(dest_sf->samples);
503
504 g_hash_table_destroy(repl_samples);
505 g_hash_table_destroy(repl_insts);
506 }
507
508 static void
ipatch_sf2_info_hash_foreach(gpointer key,gpointer value,gpointer user_data)509 ipatch_sf2_info_hash_foreach(gpointer key, gpointer value,
510 gpointer user_data)
511 {
512 char *val = (char *)value;
513 IpatchSF2 *dup = (IpatchSF2 *)user_data;
514
515 ipatch_sf2_set_info(dup, GPOINTER_TO_UINT(key), val);
516 }
517
518 static const GType *
ipatch_sf2_container_child_types(void)519 ipatch_sf2_container_child_types(void)
520 {
521 return (sf2_child_types);
522 }
523
524 static const GType *
ipatch_sf2_container_virtual_types(void)525 ipatch_sf2_container_virtual_types(void)
526 {
527 return (sf2_virt_types);
528 }
529
530 /* container is locked by caller */
531 static gboolean
ipatch_sf2_container_init_iter(IpatchContainer * container,IpatchIter * iter,GType type)532 ipatch_sf2_container_init_iter(IpatchContainer *container,
533 IpatchIter *iter, GType type)
534 {
535 IpatchSF2 *sfont = IPATCH_SF2(container);
536
537 if(g_type_is_a(type, IPATCH_TYPE_SF2_PRESET))
538 {
539 ipatch_iter_GSList_init(iter, &sfont->presets);
540 }
541 else if(g_type_is_a(type, IPATCH_TYPE_SF2_INST))
542 {
543 ipatch_iter_GSList_init(iter, &sfont->insts);
544 }
545 else if(g_type_is_a(type, IPATCH_TYPE_SF2_SAMPLE))
546 {
547 ipatch_iter_GSList_init(iter, &sfont->samples);
548 }
549 else
550 {
551 g_critical("Invalid child type '%s' for parent of type '%s'",
552 g_type_name(type), g_type_name(G_OBJECT_TYPE(container)));
553 return (FALSE);
554 }
555
556 return (TRUE);
557 }
558
559 static void
ipatch_sf2_container_make_unique(IpatchContainer * container,IpatchItem * item)560 ipatch_sf2_container_make_unique(IpatchContainer *container, IpatchItem *item)
561 {
562 IpatchSF2 *sfont = IPATCH_SF2(container);
563 char *name, *newname;
564
565 IPATCH_ITEM_WLOCK(sfont);
566
567 if(IPATCH_IS_SF2_PRESET(item))
568 {
569 int bank, newbank, program, newprogram;
570 ipatch_sf2_preset_get_midi_locale(IPATCH_SF2_PRESET(item),
571 &bank, &program);
572 newbank = bank;
573 newprogram = program;
574
575 ipatch_base_find_unused_midi_locale(IPATCH_BASE(sfont),
576 &newbank, &newprogram, item,
577 newbank == 128);
578
579 if(bank != newbank || program != newprogram)
580 ipatch_sf2_preset_set_midi_locale(IPATCH_SF2_PRESET(item),
581 newbank, newprogram);
582 }
583 else if(!IPATCH_IS_SF2_INST(item) && !IPATCH_IS_SF2_SAMPLE(item))
584 {
585 g_critical("Invalid child type '%s' for IpatchSF2 object",
586 g_type_name(G_TYPE_FROM_INSTANCE(item)));
587 return;
588 }
589
590 g_object_get(item, "name", &name, NULL);
591 newname = ipatch_sf2_make_unique_name(sfont, G_TYPE_FROM_INSTANCE(item),
592 name, NULL);
593
594 if(!name || strcmp(name, newname) != 0)
595 {
596 g_object_set(item, "name", newname, NULL);
597 }
598
599 IPATCH_ITEM_WUNLOCK(sfont);
600
601 g_free(name);
602 g_free(newname);
603 }
604
605 /* base method to find an unused MIDI bank:program locale */
606 static void
ipatch_sf2_base_find_unused_locale(IpatchBase * base,int * bank,int * program,const IpatchItem * exclude,gboolean percussion)607 ipatch_sf2_base_find_unused_locale(IpatchBase *base, int *bank,
608 int *program, const IpatchItem *exclude,
609 gboolean percussion)
610 {
611 IpatchSF2 *sf = IPATCH_SF2(base);
612 GSList *locale_list = NULL;
613 IpatchSF2Preset *pset;
614 GSList *p;
615 guint b, n; /* Stores current bank and program number */
616 guint lbank, lprogram;
617
618 if(percussion)
619 {
620 *bank = 128;
621 }
622
623 /* fill array with bank and program numbers */
624 IPATCH_ITEM_RLOCK(sf);
625 p = sf->presets;
626
627 while(p)
628 {
629 pset = (IpatchSF2Preset *)(p->data);
630
631 /* only add to locale list if not the exclude item */
632 if((gpointer)pset != (gpointer)exclude)
633 locale_list = g_slist_prepend(locale_list, GUINT_TO_POINTER
634 (((guint32)pset->bank << 16)
635 | pset->program));
636
637 p = g_slist_next(p);
638 }
639
640 IPATCH_ITEM_RUNLOCK(sf);
641
642 if(!locale_list)
643 {
644 return;
645 }
646
647 locale_list = g_slist_sort(locale_list, (GCompareFunc)locale_gcompare_func);
648
649 b = *bank;
650 n = *program;
651
652 /* loop through sorted list of bank:programs */
653 p = locale_list;
654
655 while(p)
656 {
657 lprogram = GPOINTER_TO_UINT(p->data);
658 lbank = lprogram >> 16;
659 lprogram &= 0xFFFF;
660
661 if(lbank > b || (lbank == b && lprogram > n))
662 {
663 break;
664 }
665
666 if(lbank >= b)
667 {
668 if(++n > 127)
669 {
670 n = 0;
671 b++;
672 }
673 }
674
675 p = g_slist_delete_link(p, p); /* delete and advance */
676 }
677
678 *bank = b;
679 *program = n;
680
681 if(p)
682 {
683 g_slist_free(p); /* free remainder of list */
684 }
685 }
686
687 /* function used to do a temporary sort on preset list for
688 ipatch_sf2_base_find_unused_locale */
689 static int
locale_gcompare_func(gconstpointer a,gconstpointer b)690 locale_gcompare_func(gconstpointer a, gconstpointer b)
691 {
692 return (GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b));
693 }
694
695 static IpatchItem *
ipatch_sf2_base_find_item_by_locale(IpatchBase * base,int bank,int program)696 ipatch_sf2_base_find_item_by_locale(IpatchBase *base, int bank, int program)
697 {
698 IpatchSF2Preset *preset;
699
700 preset = ipatch_sf2_find_preset(IPATCH_SF2(base), NULL, bank, program, NULL);
701 return ((IpatchItem *)preset);
702 }
703
704 /**
705 * ipatch_sf2_new:
706 *
707 * Create a new SoundFont base object.
708 *
709 * Returns: New SoundFont base object with a reference count of 1. Caller
710 * owns the reference and removing it will destroy the item.
711 */
712 IpatchSF2 *
ipatch_sf2_new(void)713 ipatch_sf2_new(void)
714 {
715 return (IPATCH_SF2(g_object_new(IPATCH_TYPE_SF2, NULL)));
716 }
717
718 /**
719 * ipatch_sf2_set_file:
720 * @sf: SoundFont to set file object of
721 * @file: File object to use with the SoundFont.
722 *
723 * Sets the file object of a SoundFont. SoundFont files are kept open
724 * for sample data that references the file. This function sets a
725 * SoundFonts authoritive file object. A convenience function, as
726 * ipatch_base_set_file() does the same thing (albeit without more specific
727 * type casting).
728 */
729 void
ipatch_sf2_set_file(IpatchSF2 * sf,IpatchSF2File * file)730 ipatch_sf2_set_file(IpatchSF2 *sf, IpatchSF2File *file)
731 {
732 g_return_if_fail(IPATCH_IS_SF2(sf));
733 g_return_if_fail(IPATCH_IS_SF2_FILE(file));
734
735 ipatch_base_set_file(IPATCH_BASE(sf), IPATCH_FILE(file));
736 }
737
738 /**
739 * ipatch_sf2_get_file:
740 * @sf: SoundFont to get file object of
741 *
742 * Gets the file object of a SoundFont. The returned SoundFont file object's
743 * reference count has incremented. The caller owns the reference and is
744 * responsible for removing it with <function>g_object_unref()</function>.
745 * A convenience function as ipatch_base_get_file() does the same thing
746 * (albeit without more specific type casting).
747 *
748 * Returns: (transfer full): The SoundFont file object or %NULL if @sf is not open. Remember
749 * to unref the file object with <function>g_object_unref()</function> when
750 * done with it.
751 */
752 IpatchSF2File *
ipatch_sf2_get_file(IpatchSF2 * sf)753 ipatch_sf2_get_file(IpatchSF2 *sf)
754 {
755 IpatchFile *file;
756
757 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
758
759 file = ipatch_base_get_file(IPATCH_BASE(sf));
760
761 if(file)
762 {
763 return (IPATCH_SF2_FILE(file));
764 }
765 else
766 {
767 return (NULL);
768 }
769 }
770
771 /**
772 * ipatch_sf2_get_info:
773 * @sf: SoundFont to get info from
774 * @id: RIFF FOURCC id
775 *
776 * Get a SoundFont info string by RIFF FOURCC ID.
777 *
778 * Returns: New allocated info string value or %NULL if no info with the
779 * given @id. String should be freed when finished with it.
780 */
781 char *
ipatch_sf2_get_info(IpatchSF2 * sf,IpatchSF2InfoType id)782 ipatch_sf2_get_info(IpatchSF2 *sf, IpatchSF2InfoType id)
783 {
784 char *val;
785
786 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
787
788 IPATCH_ITEM_RLOCK(sf);
789 val = g_hash_table_lookup(sf->info, GUINT_TO_POINTER(id));
790
791 if(val)
792 {
793 val = g_strdup(val);
794 }
795
796 IPATCH_ITEM_RUNLOCK(sf);
797
798 return (val);
799 }
800
801 /**
802 * ipatch_sf2_set_info:
803 * @sf: SoundFont to set info of
804 * @id: RIFF FOURCC id
805 * @val: Value to set info to or %NULL to unset (clear) info.
806 *
807 * Set SoundFont info. Validates @id and ensures @val does not exceed
808 * the maximum allowed length for the given info type.
809 *
810 * Emits changed signal on SoundFont.
811 */
812 void
ipatch_sf2_set_info(IpatchSF2 * sf,IpatchSF2InfoType id,const char * val)813 ipatch_sf2_set_info(IpatchSF2 *sf, IpatchSF2InfoType id, const char *val)
814 {
815 GParamSpec *pspec;
816 GValue oldval = { 0 }, newval = { 0 };
817 int i;
818
819 g_return_if_fail(IPATCH_IS_SF2(sf));
820
821 for(i = 0; i < G_N_ELEMENTS(info_ids); i++)
822 if(info_ids[i] == id)
823 {
824 break;
825 }
826
827 if(i == G_N_ELEMENTS(info_ids))
828 {
829 g_return_if_fail(ipatch_sf2_info_id_is_valid(id)); /* for debugging */
830 return;
831 }
832
833 pspec = info_prop_pspecs[i];
834
835 g_value_init(&oldval, G_TYPE_STRING);
836 g_value_take_string(&oldval, ipatch_sf2_get_info(sf, id));
837
838 ipatch_sf2_real_set_info(sf, id, val);
839
840 g_value_init(&newval, G_TYPE_STRING);
841 g_value_set_static_string(&newval, val);
842
843 ipatch_item_prop_notify((IpatchItem *)sf, pspec, &newval, &oldval);
844
845 /* need to do a title property notify? */
846 if(id == IPATCH_SF2_NAME)
847 ipatch_item_prop_notify((IpatchItem *)sf, ipatch_item_pspec_title,
848 &newval, &oldval);
849
850 g_value_unset(&oldval);
851 g_value_unset(&newval);
852 }
853
854 /* the real set info by id routine, user routine does a property notify */
855 static void
ipatch_sf2_real_set_info(IpatchSF2 * sf,IpatchSF2InfoType id,const char * val)856 ipatch_sf2_real_set_info(IpatchSF2 *sf, IpatchSF2InfoType id,
857 const char *val)
858 {
859 char *newval = NULL;
860 guint maxlen;
861
862 maxlen = ipatch_sf2_get_info_max_size(id);
863
864 /* value exceeds max length? */
865 if(maxlen > 0 && val && strlen(val) > maxlen - 1)
866 {
867 g_warning("IpatchSF2Info string with id '%.4s' truncated",
868 (char *)&id);
869 newval = g_strndup(val, maxlen - 1);
870 }
871 else if(val)
872 {
873 newval = g_strdup(val);
874 }
875
876 /* we set up the hash table to free old string values */
877 IPATCH_ITEM_WLOCK(sf);
878
879 if(newval)
880 {
881 g_hash_table_insert(sf->info, GINT_TO_POINTER(id), newval);
882 }
883 else
884 {
885 g_hash_table_remove(sf->info, GINT_TO_POINTER(id));
886 }
887
888 IPATCH_ITEM_WUNLOCK(sf);
889
890 /* newval has been taken over by hash table */
891 }
892
893 /* a bag for ipatch_sf2_get_info_array() */
894 typedef struct
895 {
896 int count;
897 IpatchSF2Info *info;
898 } InfoArrayBag;
899
900 /**
901 * ipatch_sf2_get_info_array: (skip)
902 * @sf: SoundFont to get all info strings from
903 *
904 * Get all string info (not IPATCH_SF2_VERSION or IPATCH_SF2_ROM_VERSION)
905 * from a SoundFont object. The array is sorted in the order recommended
906 * by the SoundFont standard for saving.
907 *
908 * Returns: A newly allocated array of info structures terminated by
909 * an array member with 0 valued <structfield>id</structfield>
910 * field. Remember to free the array with ipatch_sf2_free_info_array()
911 * when finished with it.
912 */
913 IpatchSF2Info *
ipatch_sf2_get_info_array(IpatchSF2 * sf)914 ipatch_sf2_get_info_array(IpatchSF2 *sf)
915 {
916 IpatchSF2Info *array;
917 InfoArrayBag bag;
918
919 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
920
921 /* allocate max expected info elements + 1 for terminator */
922 array = g_malloc((IPATCH_SF2_INFO_COUNT + 1) * sizeof(IpatchSF2Info));
923
924 bag.count = 0;
925 bag.info = array;
926
927 IPATCH_ITEM_RLOCK(sf);
928 g_hash_table_foreach(sf->info, ipatch_sf2_foreach_info_GHFunc, &bag);
929 IPATCH_ITEM_RUNLOCK(sf);
930
931 qsort(array, bag.count, sizeof(IpatchSF2Info),
932 ipatch_sf2_info_array_qsort);
933
934 /* terminate array */
935 array[bag.count].id = 0;
936 array[bag.count].val = NULL;
937
938 /* re-allocate for actual size */
939 return (g_realloc(array, (bag.count + 1) * sizeof(IpatchSF2Info)));
940 }
941
942 static void
ipatch_sf2_foreach_info_GHFunc(gpointer key,gpointer value,gpointer data)943 ipatch_sf2_foreach_info_GHFunc(gpointer key, gpointer value, gpointer data)
944 {
945 InfoArrayBag *bag = (InfoArrayBag *)data;
946
947 /* shouldn't happen, but just in case */
948 if(bag->count >= IPATCH_SF2_INFO_COUNT)
949 {
950 return;
951 }
952
953 /* store the info ID and string in the info array */
954 bag->info[bag->count].id = GPOINTER_TO_UINT(key);
955 bag->info[bag->count].val = g_strdup((char *)value);
956 bag->count++; /* advance to next index */
957 }
958
959 /* sorts an info array according to recommended SoundFont order */
960 static int
ipatch_sf2_info_array_qsort(const void * a,const void * b)961 ipatch_sf2_info_array_qsort(const void *a, const void *b)
962 {
963 IpatchSF2Info *ainfo = (IpatchSF2Info *)a;
964 IpatchSF2Info *binfo = (IpatchSF2Info *)b;
965 int andx, bndx;
966
967 /* find index in info ID array */
968 for(andx = 0; andx < G_N_ELEMENTS(info_ids); andx++)
969 if(info_ids[andx] == ainfo->id)
970 {
971 break;
972 }
973
974 for(bndx = 0; bndx < G_N_ELEMENTS(info_ids); bndx++)
975 if(info_ids[bndx] == binfo->id)
976 {
977 break;
978 }
979
980 return (andx - bndx);
981 }
982
983 /**
984 * ipatch_sf2_free_info_array: (skip)
985 * @array: SoundFont info array
986 *
987 * Frees an info array returned by ipatch_sf2_get_info_array().
988 */
989 void
ipatch_sf2_free_info_array(IpatchSF2Info * array)990 ipatch_sf2_free_info_array(IpatchSF2Info *array)
991 {
992 int i;
993 g_return_if_fail(array != NULL);
994
995 for(i = 0; array[i].id; i++)
996 {
997 g_free(array[i].val);
998 }
999
1000 g_free(array);
1001 }
1002
1003 /**
1004 * ipatch_sf2_info_id_is_valid: (skip)
1005 * @id: RIFF FOURCC id (see #IpatchSF2InfoType)
1006 *
1007 * Check if a given RIFF FOURCC id is a valid SoundFont info id.
1008 *
1009 * Returns: %TRUE if @id is a valid info id, %FALSE otherwise
1010 */
1011 gboolean
ipatch_sf2_info_id_is_valid(guint32 id)1012 ipatch_sf2_info_id_is_valid(guint32 id)
1013 {
1014 int i;
1015
1016 for(i = 0; i < G_N_ELEMENTS(info_ids) ; i++)
1017 if(info_ids[i] == id)
1018 {
1019 return (TRUE);
1020 }
1021
1022 return (FALSE);
1023 }
1024
1025 /**
1026 * ipatch_sf2_get_info_max_size: (skip)
1027 * @infotype: An info enumeration
1028 *
1029 * Get maximum chunk size for info chunks.
1030 *
1031 * NOTE: Max size includes terminating NULL character so subtract one from
1032 * returned value to get max allowed string length.
1033 *
1034 * Returns: Maximum info chunk size or 0 if invalid @infotype. Subtract one
1035 * to get max allowed string length (if returned value was not 0).
1036 */
1037 int
ipatch_sf2_get_info_max_size(IpatchSF2InfoType infotype)1038 ipatch_sf2_get_info_max_size(IpatchSF2InfoType infotype)
1039 {
1040 if(!ipatch_sf2_info_id_is_valid(infotype))
1041 {
1042 return (0);
1043 }
1044
1045 if(infotype == IPATCH_SF2_COMMENT) /* comments can have up to 64k bytes */
1046 {
1047 return (65536);
1048 }
1049
1050 if(infotype == IPATCH_SF2_VERSION /* versions take up 4 bytes */
1051 || infotype == IPATCH_SF2_ROM_VERSION)
1052 {
1053 return (4);
1054 }
1055
1056 return (256); /* all other valid info types allow 256 bytes max */
1057 }
1058
1059 /**
1060 * ipatch_sf2_find_preset:
1061 * @sf: SoundFont to search in
1062 * @name: (nullable): Name of preset to find or %NULL to match any name
1063 * @bank: MIDI bank number of preset to search for or -1 to not search by
1064 * MIDI bank:program numbers
1065 * @program: MIDI program number of preset to search for, only used
1066 * if @bank is 0-128
1067 * @exclude: (nullable): A preset to exclude from the search or %NULL
1068 *
1069 * Find a preset by name or bank:preset MIDI numbers. If preset @name
1070 * and @bank:@program are specified then match for either condition.
1071 * If a preset is found its reference count is incremented before it
1072 * is returned. The caller is responsible for removing the reference
1073 * with g_object_unref() when finished with it.
1074 *
1075 * Returns: (transfer full): The matching preset or %NULL if not found. Remember to unref
1076 * the item when finished with it.
1077 */
1078 IpatchSF2Preset *
ipatch_sf2_find_preset(IpatchSF2 * sf,const char * name,int bank,int program,const IpatchSF2Preset * exclude)1079 ipatch_sf2_find_preset(IpatchSF2 *sf, const char *name, int bank,
1080 int program, const IpatchSF2Preset *exclude)
1081 {
1082 IpatchSF2Preset *pset;
1083 gboolean bynum = FALSE;
1084 GSList *p;
1085
1086 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
1087
1088 /* if bank and program are valid, then search by number */
1089 if(bank >= 0 && bank <= 128 && program >= 0 && program < 128)
1090 {
1091 bynum = TRUE;
1092 }
1093
1094 IPATCH_ITEM_RLOCK(sf);
1095 p = sf->presets;
1096
1097 while(p)
1098 {
1099 pset = (IpatchSF2Preset *)(p->data);
1100 IPATCH_ITEM_RLOCK(pset); /* MT - Recursive LOCK */
1101
1102 if(pset != exclude /* if exclude is NULL it will never == pset */
1103 && ((bynum && pset->bank == bank && pset->program == program)
1104 || (name && strcmp(pset->name, name) == 0)))
1105 {
1106 g_object_ref(pset);
1107 IPATCH_ITEM_RUNLOCK(pset);
1108 IPATCH_ITEM_RUNLOCK(sf);
1109 return (pset);
1110 }
1111
1112 IPATCH_ITEM_RUNLOCK(pset);
1113 p = g_slist_next(p);
1114 }
1115
1116 IPATCH_ITEM_RUNLOCK(sf);
1117
1118 return (NULL);
1119 }
1120
1121 /**
1122 * ipatch_sf2_find_inst:
1123 * @sf: SoundFont to search in
1124 * @name: Name of Instrument to find
1125 * @exclude: (nullable): An instrument to exclude from the search or %NULL
1126 *
1127 * Find an instrument by @name in a SoundFont. If a matching instrument
1128 * is found, its reference count is incremented before it is returned.
1129 * The caller is responsible for removing the reference with g_object_unref()
1130 * when finished with it.
1131 *
1132 * Returns: (transfer full): The matching instrument or %NULL if not found. Remember to unref
1133 * the item when finished with it.
1134 */
1135 IpatchSF2Inst *
ipatch_sf2_find_inst(IpatchSF2 * sf,const char * name,const IpatchSF2Inst * exclude)1136 ipatch_sf2_find_inst(IpatchSF2 *sf, const char *name,
1137 const IpatchSF2Inst *exclude)
1138 {
1139 IpatchSF2Inst *inst;
1140 GSList *p;
1141
1142 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
1143 g_return_val_if_fail(name != NULL, NULL);
1144
1145 IPATCH_ITEM_RLOCK(sf);
1146 p = sf->insts;
1147
1148 while(p)
1149 {
1150 inst = (IpatchSF2Inst *)(p->data);
1151 IPATCH_ITEM_RLOCK(inst); /* MT - Recursive LOCK */
1152
1153 if(inst != exclude && strcmp(inst->name, name) == 0)
1154 {
1155 g_object_ref(inst);
1156 IPATCH_ITEM_RUNLOCK(inst);
1157 IPATCH_ITEM_RUNLOCK(sf);
1158 return (inst);
1159 }
1160
1161 IPATCH_ITEM_RUNLOCK(inst);
1162 p = g_slist_next(p);
1163 }
1164
1165 IPATCH_ITEM_RUNLOCK(sf);
1166
1167 return (NULL);
1168 }
1169
1170 /**
1171 * ipatch_sf2_find_sample:
1172 * @sf: SoundFont to search in
1173 * @name: Name of sample to find
1174 * @exclude: (nullable): A sample to exclude from the search or %NULL
1175 *
1176 * Find a sample by @name in a SoundFont. If a sample is found its
1177 * reference count is incremented before it is returned. The caller
1178 * is responsible for removing the reference with g_object_unref()
1179 * when finished with it.
1180 *
1181 * Returns: (transfer full): The matching sample or %NULL if not found. Remember to unref
1182 * the item when finished with it.
1183 */
1184 IpatchSF2Sample *
ipatch_sf2_find_sample(IpatchSF2 * sf,const char * name,const IpatchSF2Sample * exclude)1185 ipatch_sf2_find_sample(IpatchSF2 *sf, const char *name,
1186 const IpatchSF2Sample *exclude)
1187 {
1188 IpatchSF2Sample *sample;
1189 GSList *p;
1190
1191 g_return_val_if_fail(IPATCH_IS_SF2(sf), NULL);
1192 g_return_val_if_fail(name != NULL, NULL);
1193
1194 IPATCH_ITEM_RLOCK(sf);
1195 p = sf->samples;
1196
1197 while(p)
1198 {
1199 sample = (IpatchSF2Sample *)(p->data);
1200 IPATCH_ITEM_RLOCK(sample); /* MT - Recursive LOCK */
1201
1202 if(sample != exclude && strcmp(sample->name, name) == 0)
1203 {
1204 g_object_ref(sample);
1205 IPATCH_ITEM_RUNLOCK(sample);
1206 IPATCH_ITEM_RUNLOCK(sf);
1207 return (sample);
1208 }
1209
1210 IPATCH_ITEM_RUNLOCK(sample);
1211 p = g_slist_next(p);
1212 }
1213
1214 IPATCH_ITEM_RUNLOCK(sf);
1215
1216 return (NULL);
1217 }
1218
1219 /**
1220 * ipatch_sf2_get_zone_references:
1221 * @item: Item to locate referencing zones of, must be of type #IpatchSF2Inst
1222 * or #IpatchSF2Sample and be parented to an #IpatchSF2 object.
1223 *
1224 * Get list of zones referencing an IpatchSF2Inst or IpatchSF2Sample.
1225 *
1226 * Returns: (transfer full): New object list containing #IpatchSF2Zone objects that
1227 * refer to @item. The new list object has a reference count of 1
1228 * which the caller owns, unreference to free the list.
1229 */
1230 IpatchList *
ipatch_sf2_get_zone_references(IpatchItem * item)1231 ipatch_sf2_get_zone_references(IpatchItem *item)
1232 {
1233 IpatchList *reflist, *itemlist, *zonelist;
1234 IpatchSF2 *sf;
1235 IpatchSF2Zone *zone;
1236 IpatchIter iter, zone_iter;
1237 IpatchItem *pitem;
1238
1239 g_return_val_if_fail(IPATCH_IS_SF2_INST(item) || IPATCH_IS_SF2_SAMPLE(item), NULL);
1240
1241 pitem = ipatch_item_get_parent(item);
1242 g_return_val_if_fail(IPATCH_IS_SF2(pitem), NULL);
1243 sf = IPATCH_SF2(pitem);
1244
1245 /* ++ ref item list */
1246 if(IPATCH_IS_SF2_INST(item)) /* is an instrument? */
1247 {
1248 itemlist = ipatch_sf2_get_presets(sf);
1249 }
1250 else
1251 {
1252 itemlist = ipatch_sf2_get_insts(sf); /* its a sample */
1253 }
1254
1255 reflist = ipatch_list_new(); /* ++ ref new list */
1256
1257 ipatch_list_init_iter(itemlist, &iter);
1258 pitem = ipatch_item_first(&iter);
1259
1260 while(pitem) /* loop on item list */
1261 {
1262 /* ++ ref new zone list */
1263 zonelist = ipatch_container_get_children((IpatchContainer *)(pitem),
1264 IPATCH_TYPE_SF2_ZONE);
1265 ipatch_list_init_iter(zonelist, &zone_iter);
1266
1267 zone = ipatch_sf2_zone_first(&zone_iter);
1268
1269 while(zone)
1270 {
1271 if(ipatch_sf2_zone_peek_link_item(zone) == item)
1272 {
1273 g_object_ref(zone); /* ++ ref zone for new list */
1274 reflist->items = g_list_prepend(reflist->items, zone);
1275 }
1276
1277 zone = ipatch_sf2_zone_next(&zone_iter);
1278 }
1279
1280 g_object_unref(zonelist); /* -- unref zone list */
1281 pitem = ipatch_item_next(&iter);
1282 }
1283
1284 g_object_unref(itemlist); /* -- unref item list */
1285
1286 return (reflist); /* !! caller takes over reference */
1287 }
1288
1289 /* In theory there is still a chance of duplicates if another item's name is
1290 set to the generated unique one (by another thread) while in this routine */
1291
1292 /**
1293 * ipatch_sf2_make_unique_name:
1294 * @sfont: SoundFont item
1295 * @child_type: A child type of @sfont to search for a unique name in
1296 * @name: (nullable): An initial name to use or %NULL
1297 * @exclude: (nullable): An item to exclude from search or %NULL
1298 *
1299 * Generates a unique name for the given @child_type in @sfont. The @name
1300 * parameter is used as a base and is modified, by appending a number, to
1301 * make it unique (if necessary). The @exclude parameter is used to exclude
1302 * an existing @sfont child item from the search.
1303 *
1304 * MT-Note: To ensure that an item is actually unique before being
1305 * added to a SoundFont object, ipatch_container_add_unique() should be
1306 * used.
1307 *
1308 * Returns: A new unique name which should be freed when finished with it.
1309 */
1310 char *
ipatch_sf2_make_unique_name(IpatchSF2 * sfont,GType child_type,const char * name,const IpatchItem * exclude)1311 ipatch_sf2_make_unique_name(IpatchSF2 *sfont, GType child_type,
1312 const char *name, const IpatchItem *exclude)
1313 {
1314 GSList **list, *p;
1315 char curname[IPATCH_SFONT_NAME_SIZE + 1];
1316 int name_ofs;
1317 int count = 2;
1318
1319 g_return_val_if_fail(IPATCH_IS_SF2(sfont), NULL);
1320
1321 if(child_type == IPATCH_TYPE_SF2_PRESET)
1322 {
1323 list = &sfont->presets;
1324 name_ofs = G_STRUCT_OFFSET(IpatchSF2Preset, name);
1325
1326 if(!name)
1327 {
1328 name = _("New Preset");
1329 }
1330 }
1331 else if(child_type == IPATCH_TYPE_SF2_INST)
1332 {
1333 list = &sfont->insts;
1334 name_ofs = G_STRUCT_OFFSET(IpatchSF2Inst, name);
1335
1336 if(!name)
1337 {
1338 name = _("New Instrument");
1339 }
1340 }
1341 else if(child_type == IPATCH_TYPE_SF2_SAMPLE)
1342 {
1343 list = &sfont->samples;
1344 name_ofs = G_STRUCT_OFFSET(IpatchSF2Sample, name);
1345
1346 if(!name)
1347 {
1348 name = _("New Sample");
1349 }
1350 }
1351 else
1352 {
1353 g_critical(IPATCH_CONTAINER_ERRMSG_INVALID_CHILD_2,
1354 g_type_name(child_type), g_type_name(IPATCH_TYPE_SF2));
1355 return (NULL);
1356 }
1357
1358 g_strlcpy(curname, name, sizeof(curname));
1359
1360 IPATCH_ITEM_RLOCK(sfont);
1361
1362 p = *list;
1363
1364 while(p) /* check for duplicate */
1365 {
1366 IPATCH_ITEM_RLOCK(p->data); /* MT - Recursive LOCK */
1367
1368 if(p->data != exclude
1369 && strcmp(G_STRUCT_MEMBER(char *, p->data, name_ofs),
1370 curname) == 0)
1371 {
1372 /* duplicate name */
1373 IPATCH_ITEM_RUNLOCK(p->data);
1374
1375 ipatch_strconcat_num(name, count++, curname, sizeof(curname));
1376
1377 p = *list; /* start over */
1378 continue;
1379 }
1380
1381 IPATCH_ITEM_RUNLOCK(p->data);
1382 p = g_slist_next(p);
1383 }
1384
1385 IPATCH_ITEM_RUNLOCK(sfont);
1386
1387 return (g_strdup(curname));
1388 }
1389