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