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: IpatchVBank
22 * @short_description: Virtual bank object
23 * @see_also:
24 * @stability: Stable
25 *
26 * Virtual banks provide the capability of creating new instrument MIDI
27 * maps from components from other files of possibly different types.
28 */
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <glib.h>
33 #include <glib-object.h>
34 #include "IpatchVBank.h"
35 #include "IpatchParamProp.h"
36 #include "i18n.h"
37 #include "misc.h"
38
39 /* !! Keep synchronized with IPATCH_VBANK_INFO_COUNT constant in IpatchVBank.h */
40 enum
41 {
42 PROP_0,
43 PROP_PARSER_VERSION, /* IVBank version of parser which wrote the file */
44 PROP_REQUIRE_VERSION, /* IVBank parser version required */
45 PROP_ENGINE, /* Sound engine "FluidSynth 1.0.x" for example */
46 PROP_NAME, /* Descriptive name of bank */
47 PROP_DATE, /* Creation date */
48 PROP_AUTHOR, /* Author */
49 PROP_COMMENT /* Comments */
50 };
51
52 #define IPATCH_VBANK_PARSER_VERSION "1.0" // Current IVBank parser version.
53
54 static void ipatch_vbank_finalize(GObject *gobject);
55 static void ipatch_vbank_set_property(GObject *object, guint property_id,
56 const GValue *value, GParamSpec *pspec);
57 static void ipatch_vbank_get_property(GObject *object, guint property_id,
58 GValue *value, GParamSpec *pspec);
59 static void ipatch_vbank_item_copy(IpatchItem *dest, IpatchItem *src,
60 IpatchItemCopyLinkFunc link_func,
61 gpointer user_data);
62
63 static const GType *ipatch_vbank_container_child_types(void);
64 static gboolean ipatch_vbank_container_init_iter(IpatchContainer *container,
65 IpatchIter *iter, GType type);
66 static void ipatch_vbank_container_make_unique(IpatchContainer *container,
67 IpatchItem *item);
68 static void ipatch_vbank_base_find_unused_locale(IpatchBase *base, int *bank,
69 int *program,
70 const IpatchItem *exclude,
71 gboolean percussion);
72 static int locale_gcompare_func(gconstpointer a, gconstpointer b);
73 static IpatchItem *
74 ipatch_vbank_base_find_item_by_locale(IpatchBase *base, int bank, int program);
75
76
77 G_DEFINE_TYPE(IpatchVBank, ipatch_vbank, IPATCH_TYPE_BASE)
78
79 static GType vbank_child_types[2] = { 0 };
80
81
82 static void
ipatch_vbank_class_init(IpatchVBankClass * klass)83 ipatch_vbank_class_init(IpatchVBankClass *klass)
84 {
85 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
86 IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
87 IpatchContainerClass *container_class = IPATCH_CONTAINER_CLASS(klass);
88 IpatchBaseClass *base_class = IPATCH_BASE_CLASS(klass);
89
90 obj_class->finalize = ipatch_vbank_finalize;
91 obj_class->get_property = ipatch_vbank_get_property;
92
93 /* we use the IpatchItem item_set_property method */
94 item_class->item_set_property = ipatch_vbank_set_property;
95 item_class->copy = ipatch_vbank_item_copy;
96
97 container_class->child_types = ipatch_vbank_container_child_types;
98 container_class->init_iter = ipatch_vbank_container_init_iter;
99 container_class->make_unique = ipatch_vbank_container_make_unique;
100
101 base_class->find_unused_locale = ipatch_vbank_base_find_unused_locale;
102 base_class->find_item_by_locale = ipatch_vbank_base_find_item_by_locale;
103
104 g_object_class_override_property(obj_class, PROP_NAME, "title");
105
106 g_object_class_install_property(obj_class, PROP_PARSER_VERSION,
107 g_param_spec_string("parser-version", _("Parser version"),
108 _("Parser version"),
109 IPATCH_VBANK_PARSER_VERSION, G_PARAM_READWRITE));
110 g_object_class_install_property(obj_class, PROP_REQUIRE_VERSION,
111 g_param_spec_string("require-version", _("Require version"),
112 _("Required parser version"),
113 IPATCH_VBANK_PARSER_VERSION, G_PARAM_READWRITE));
114 g_object_class_install_property(obj_class, PROP_ENGINE,
115 ipatch_param_set(g_param_spec_string("engine", _("Engine"),
116 _("Synthesis engine"), NULL, G_PARAM_READWRITE),
117 "string-max-length", 255, NULL));
118 g_object_class_install_property(obj_class, PROP_NAME,
119 ipatch_param_set(g_param_spec_string("name", _("Name"),
120 _("Descriptive name"), NULL, G_PARAM_READWRITE),
121 "string-max-length", 255, NULL));
122 g_object_class_install_property(obj_class, PROP_DATE,
123 ipatch_param_set(g_param_spec_string("date", _("Date"),
124 _("Creation date"), NULL, G_PARAM_READWRITE),
125 "string-max-length", 255, NULL));
126 g_object_class_install_property(obj_class, PROP_AUTHOR,
127 ipatch_param_set(g_param_spec_string("author", _("Author"),
128 _("Author of file"), NULL, G_PARAM_READWRITE),
129 "string-max-length", 255, NULL));
130 g_object_class_install_property(obj_class, PROP_COMMENT,
131 ipatch_param_set(g_param_spec_string("comment", _("Comments"),
132 _("Comments"), NULL, G_PARAM_READWRITE),
133 "string-max-length", 65535, NULL));
134
135 vbank_child_types[0] = IPATCH_TYPE_VBANK_INST;
136 }
137
138 static void
ipatch_vbank_init(IpatchVBank * vbank)139 ipatch_vbank_init(IpatchVBank *vbank)
140 {
141 g_object_set(vbank, "name", _(IPATCH_BASE_DEFAULT_NAME), NULL);
142 ipatch_item_clear_flags(IPATCH_ITEM(vbank), IPATCH_BASE_CHANGED);
143 }
144
145 /* function called when VBank is being destroyed */
146 static void
ipatch_vbank_finalize(GObject * gobject)147 ipatch_vbank_finalize(GObject *gobject)
148 {
149 IpatchVBank *vbank = IPATCH_VBANK(gobject);
150 int i;
151
152 IPATCH_ITEM_WLOCK(vbank);
153
154 for(i = 0; i < IPATCH_VBANK_INFO_COUNT; i++)
155 {
156 g_free(vbank->info[i]);
157 }
158
159 IPATCH_ITEM_WUNLOCK(vbank);
160
161 if(G_OBJECT_CLASS(ipatch_vbank_parent_class)->finalize)
162 {
163 G_OBJECT_CLASS(ipatch_vbank_parent_class)->finalize(gobject);
164 }
165 }
166
167 static void
ipatch_vbank_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)168 ipatch_vbank_set_property(GObject *object, guint property_id,
169 const GValue *value, GParamSpec *pspec)
170 {
171 IpatchVBank *vbank = IPATCH_VBANK(object);
172
173 if(property_id > PROP_0 && property_id <= IPATCH_VBANK_INFO_COUNT)
174 {
175 g_free(vbank->info[property_id - 1]);
176 vbank->info[property_id - 1] = g_value_dup_string(value);
177
178 /* need to do a title property notify? */
179 if(property_id == PROP_NAME)
180 ipatch_item_prop_notify((IpatchItem *)vbank, ipatch_item_pspec_title,
181 value, NULL);
182 }
183 else
184 {
185 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
186 }
187 }
188
189 static void
ipatch_vbank_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)190 ipatch_vbank_get_property(GObject *object, guint property_id,
191 GValue *value, GParamSpec *pspec)
192 {
193 IpatchVBank *vbank = IPATCH_VBANK(object);
194
195 if(property_id > PROP_0 && property_id <= IPATCH_VBANK_INFO_COUNT)
196 {
197 g_value_set_string(value, vbank->info[property_id - 1]);
198 }
199 else
200 {
201 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
202 }
203 }
204
205 /* item copy function, note that this is an #IpatchBase derived object, so
206 link_func is not used */
207 static void
ipatch_vbank_item_copy(IpatchItem * dest,IpatchItem * src,IpatchItemCopyLinkFunc link_func,gpointer user_data)208 ipatch_vbank_item_copy(IpatchItem *dest, IpatchItem *src,
209 IpatchItemCopyLinkFunc link_func,
210 gpointer user_data)
211 {
212 IpatchVBank *src_vbank, *dest_vbank;
213 IpatchItem *newitem;
214 GSList *p;
215 int i;
216
217 src_vbank = IPATCH_VBANK(src);
218 dest_vbank = IPATCH_VBANK(dest);
219
220 IPATCH_ITEM_RLOCK(src_vbank);
221
222 if(IPATCH_BASE(src_vbank)->file)
223 ipatch_base_set_file(IPATCH_BASE(dest_vbank),
224 IPATCH_BASE(src_vbank)->file);
225
226 /* duplicate the info variables */
227 for(i = 0; i < IPATCH_VBANK_INFO_COUNT; i++)
228 {
229 dest_vbank->info[i] = g_strdup(src_vbank->info[i]);
230 }
231
232 /* duplicate instruments */
233 for(p = src_vbank->insts; p; p = p->next)
234 {
235 /* ++ ref new duplicate instrument, !! inst list takes it over */
236 newitem = ipatch_item_duplicate((IpatchItem *)(p->data));
237 dest_vbank->insts = g_slist_prepend(dest_vbank->insts, newitem);
238 ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_vbank));
239 }
240
241 IPATCH_ITEM_RUNLOCK(src_vbank);
242
243 dest_vbank->insts = g_slist_reverse(dest_vbank->insts);
244 }
245
246 static const GType *
ipatch_vbank_container_child_types(void)247 ipatch_vbank_container_child_types(void)
248 {
249 return (vbank_child_types);
250 }
251
252 /* container is locked by caller */
253 static gboolean
ipatch_vbank_container_init_iter(IpatchContainer * container,IpatchIter * iter,GType type)254 ipatch_vbank_container_init_iter(IpatchContainer *container,
255 IpatchIter *iter, GType type)
256 {
257 IpatchVBank *vbank = IPATCH_VBANK(container);
258
259 if(g_type_is_a(type, IPATCH_TYPE_VBANK_INST))
260 {
261 ipatch_iter_GSList_init(iter, &vbank->insts);
262 }
263 else
264 {
265 g_critical("Invalid child type '%s' for parent of type '%s'",
266 g_type_name(type), g_type_name(G_OBJECT_TYPE(container)));
267 return (FALSE);
268 }
269
270 return (TRUE);
271 }
272
273 static void
ipatch_vbank_container_make_unique(IpatchContainer * container,IpatchItem * item)274 ipatch_vbank_container_make_unique(IpatchContainer *container, IpatchItem *item)
275 {
276 IpatchVBank *vbank = IPATCH_VBANK(container);
277 char *name, *newname;
278
279 IPATCH_ITEM_WLOCK(vbank);
280
281 if(IPATCH_IS_VBANK_INST(item))
282 {
283 int bank, newbank, program, newprogram;
284 ipatch_vbank_inst_get_midi_locale(IPATCH_VBANK_INST(item),
285 &bank, &program);
286 newbank = bank;
287 newprogram = program;
288
289 ipatch_base_find_unused_midi_locale(IPATCH_BASE(vbank),
290 &newbank, &newprogram, item, FALSE);
291
292 if(bank != newbank || program != newprogram)
293 ipatch_vbank_inst_set_midi_locale(IPATCH_VBANK_INST(item),
294 newbank, newprogram);
295 }
296 else
297 {
298 g_critical("Invalid child type '%s' for IpatchVBank object",
299 g_type_name(G_TYPE_FROM_INSTANCE(item)));
300 return;
301 }
302
303 g_object_get(item, "name", &name, NULL);
304 newname = ipatch_vbank_make_unique_name(vbank, name, NULL);
305
306 if(!name || strcmp(name, newname) != 0)
307 {
308 g_object_set(item, "name", newname, NULL);
309 }
310
311 IPATCH_ITEM_WUNLOCK(vbank);
312
313 g_free(name);
314 g_free(newname);
315 }
316
317 /* base method to find an unused MIDI bank:program locale */
318 static void
ipatch_vbank_base_find_unused_locale(IpatchBase * base,int * bank,int * program,const IpatchItem * exclude,gboolean percussion)319 ipatch_vbank_base_find_unused_locale(IpatchBase *base, int *bank,
320 int *program, const IpatchItem *exclude,
321 gboolean percussion)
322 {
323 IpatchVBank *vbank = IPATCH_VBANK(base);
324 GSList *locale_list = NULL;
325 IpatchVBankInst *inst;
326 GSList *p;
327 guint b, n; /* Stores current bank and program number */
328 guint lbank, lprogram;
329
330 /* fill array with bank and program numbers */
331 IPATCH_ITEM_RLOCK(vbank);
332
333 for(p = vbank->insts; p; p = p->next)
334 {
335 inst = (IpatchVBankInst *)(p->data);
336
337 /* only add to locale list if not the exclude item */
338 if((gpointer)inst != (gpointer)exclude)
339 locale_list = g_slist_prepend(locale_list, GUINT_TO_POINTER
340 (((guint32)inst->bank << 16)
341 | inst->program));
342 }
343
344 IPATCH_ITEM_RUNLOCK(vbank);
345
346 if(!locale_list)
347 {
348 return;
349 }
350
351 locale_list = g_slist_sort(locale_list, (GCompareFunc)locale_gcompare_func);
352
353 b = *bank;
354 n = *program;
355
356 /* loop through sorted list of bank:programs */
357 p = locale_list;
358
359 while(p)
360 {
361 lprogram = GPOINTER_TO_UINT(p->data);
362 lbank = lprogram >> 16;
363 lprogram &= 0xFFFF;
364
365 if(lbank > b || (lbank == b && lprogram > n))
366 {
367 break;
368 }
369
370 if(lbank >= b)
371 {
372 if(++n > 127)
373 {
374 n = 0;
375 b++;
376 }
377 }
378
379 p = g_slist_delete_link(p, p); /* delete and advance */
380 }
381
382 *bank = b;
383 *program = n;
384
385 if(p)
386 {
387 g_slist_free(p); /* free remainder of list */
388 }
389 }
390
391 /* function used to do a temporary sort on preset list for
392 ipatch_vbank_base_find_unused_locale */
393 static int
locale_gcompare_func(gconstpointer a,gconstpointer b)394 locale_gcompare_func(gconstpointer a, gconstpointer b)
395 {
396 return (GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b));
397 }
398
399 static IpatchItem *
ipatch_vbank_base_find_item_by_locale(IpatchBase * base,int bank,int program)400 ipatch_vbank_base_find_item_by_locale(IpatchBase *base, int bank, int program)
401 {
402 IpatchVBankInst *inst;
403
404 inst = ipatch_vbank_find_inst(IPATCH_VBANK(base), NULL, bank, program, NULL);
405 return ((IpatchItem *)inst);
406 }
407
408 /**
409 * ipatch_vbank_new:
410 *
411 * Create a new virtual bank base object.
412 *
413 * Returns: New IVBank base object with a reference count of 1. Caller
414 * owns the reference and removing it will destroy the item.
415 */
416 IpatchVBank *
ipatch_vbank_new(void)417 ipatch_vbank_new(void)
418 {
419 return (IPATCH_VBANK(g_object_new(IPATCH_TYPE_VBANK, NULL)));
420 }
421
422 /**
423 * ipatch_vbank_find_inst:
424 * @vbank: VBank to search in
425 * @name: (nullable): Name of instrument to find or %NULL to match any name
426 * @bank: MIDI bank number of instrument to search for or -1 to not search by
427 * MIDI bank:program numbers
428 * @program: MIDI program number of instrument to search for, only used
429 * if @bank is 0-128
430 * @exclude: (nullable): An instrument to exclude from the search or %NULL
431 *
432 * Find an instrument by name or bank:preset MIDI numbers. If instrument @name
433 * and @bank:@program are specified then match for either condition.
434 * If an instrument is found its reference count is incremented before it
435 * is returned. The caller is responsible for removing the reference
436 * with g_object_unref() when finished with it.
437 *
438 * Returns: (transfer full): The matching instrument or %NULL if not found. Remember to unref
439 * the item when finished with it.
440 */
441 IpatchVBankInst *
ipatch_vbank_find_inst(IpatchVBank * vbank,const char * name,int bank,int program,const IpatchVBankInst * exclude)442 ipatch_vbank_find_inst(IpatchVBank *vbank, const char *name, int bank,
443 int program, const IpatchVBankInst *exclude)
444 {
445 IpatchVBankInst *inst;
446 gboolean bynum = FALSE;
447 GSList *p;
448
449 g_return_val_if_fail(IPATCH_IS_VBANK(vbank), NULL);
450
451 /* if bank and program are valid, then search by number */
452 if(bank >= 0 && bank <= 128 && program >= 0 && program < 128)
453 {
454 bynum = TRUE;
455 }
456
457 IPATCH_ITEM_RLOCK(vbank);
458
459 for(p = vbank->insts; p; p = p->next)
460 {
461 inst = (IpatchVBankInst *)(p->data);
462
463 IPATCH_ITEM_RLOCK(inst); /* MT - Recursive LOCK */
464
465 if(inst != exclude /* if exclude is NULL it will never == pset */
466 && ((bynum && inst->bank == bank && inst->program == program)
467 || (name && strcmp(inst->name, name) == 0)))
468 {
469 g_object_ref(inst);
470 IPATCH_ITEM_RUNLOCK(inst);
471 IPATCH_ITEM_RUNLOCK(vbank);
472 return (inst);
473 }
474
475 IPATCH_ITEM_RUNLOCK(inst);
476 }
477
478 IPATCH_ITEM_RUNLOCK(vbank);
479
480 return (NULL);
481 }
482
483 /* In theory there is still a chance of duplicates if another item's name is
484 set to the generated unique one (by another thread) while in this routine */
485
486 /**
487 * ipatch_vbank_make_unique_name:
488 * @vbank: VBank item
489 * @name: (nullable): An initial name to use or %NULL
490 * @exclude: (nullable): An item to exclude from search or %NULL
491 *
492 * Generates a unique instrument name for @vbank. The @name
493 * parameter is used as a base and is modified, by appending a number, to
494 * make it unique (if necessary). The @exclude parameter is used to exclude
495 * an existing @vbank instrument from the search.
496 *
497 * MT-Note: To ensure that an item is actually unique before being
498 * added to a VBank object, ipatch_container_add_unique() should be
499 * used.
500 *
501 * Returns: A new unique name which should be freed when finished with it.
502 */
503 char *
ipatch_vbank_make_unique_name(IpatchVBank * vbank,const char * name,const IpatchVBankInst * exclude)504 ipatch_vbank_make_unique_name(IpatchVBank *vbank, const char *name,
505 const IpatchVBankInst *exclude)
506 {
507 char curname[IPATCH_VBANK_INST_NAME_SIZE + 1];
508 IpatchVBankInst *inst;
509 int count = 2;
510 GSList *p;
511
512 g_return_val_if_fail(IPATCH_IS_VBANK(vbank), NULL);
513
514 if(!name)
515 {
516 name = _("New Instrument");
517 }
518
519 g_strlcpy(curname, name, sizeof(curname));
520
521 IPATCH_ITEM_RLOCK(vbank);
522
523 /* check for duplicate */
524 for(p = vbank->insts; p; p = p->next)
525 {
526 inst = (IpatchVBankInst *)(p->data);
527
528 IPATCH_ITEM_RLOCK(inst); /* MT - Recursive LOCK */
529
530 if(p->data != exclude && strcmp(inst->name, curname) == 0)
531 {
532 /* duplicate name */
533 IPATCH_ITEM_RUNLOCK(inst);
534
535 ipatch_strconcat_num(name, count++, curname, sizeof(curname));
536
537 p = vbank->insts; /* start over */
538 continue;
539 }
540
541 IPATCH_ITEM_RUNLOCK(inst);
542 }
543
544 IPATCH_ITEM_RUNLOCK(vbank);
545
546 return (g_strdup(curname));
547 }
548