1 /*
2  * libInstPatch
3  * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
4  *
5  * Author of this file: (C) 2012 BALATON Zoltan <balaton@eik.bme.hu>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; version 2.1
10  * of the License only.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  * 02110-1301, USA or on the web at http://www.gnu.org.
21  */
22 /**
23  * SECTION: IpatchSLI
24  * @short_description: Spectralis instrument file object
25  * @see_also:
26  *
27  * Object type for Spectralis format instruments.
28  */
29 #include <stdlib.h>
30 #include <string.h>
31 #include <glib.h>
32 #include <glib-object.h>
33 #include "IpatchSLI.h"
34 #include "IpatchSLIFile.h"
35 #include "IpatchSLIZone.h"
36 #include "IpatchParamProp.h"
37 #include "IpatchTypeProp.h"
38 #include "IpatchVirtualContainer_types.h"
39 #include "version.h"
40 #include "ipatch_priv.h"
41 
42 /* properties */
43 enum
44 {
45     PROP_0,
46     PROP_TITLE
47 };
48 
49 static void ipatch_sli_class_init(IpatchSLIClass *klass);
50 static void ipatch_sli_init(IpatchSLI *sli);
51 static void ipatch_sli_get_title(IpatchSLI *sli, GValue *value);
52 static void ipatch_sli_get_property(GObject *object, guint property_id,
53                                     GValue *value, GParamSpec *pspec);
54 static void ipatch_sli_item_copy(IpatchItem *dest, IpatchItem *src,
55                                  IpatchItemCopyLinkFunc link_func,
56                                  gpointer user_data);
57 
58 static const GType *ipatch_sli_container_child_types(void);
59 static const GType *ipatch_sli_container_virtual_types(void);
60 static gboolean ipatch_sli_container_init_iter(IpatchContainer *container,
61         IpatchIter *iter, GType type);
62 static void ipatch_sli_container_make_unique(IpatchContainer *container,
63         IpatchItem *item);
64 static void ipatch_sli_parent_file_prop_notify(IpatchItemPropNotify *info);
65 
66 static gpointer parent_class = NULL;
67 static GType sli_child_types[3] = { 0 };
68 static GType sli_virt_types[3] = { 0 };
69 
70 
71 /* Spectralis item type creation function */
72 GType
ipatch_sli_get_type(void)73 ipatch_sli_get_type(void)
74 {
75     static GType item_type = 0;
76 
77     if(!item_type)
78     {
79         static const GTypeInfo item_info =
80         {
81             sizeof(IpatchSLIClass), NULL, NULL,
82             (GClassInitFunc)ipatch_sli_class_init, NULL, NULL,
83             sizeof(IpatchSLI),
84             0,
85             (GInstanceInitFunc)ipatch_sli_init,
86         };
87 
88         item_type = g_type_register_static(IPATCH_TYPE_BASE, "IpatchSLI",
89                                            &item_info, 0);
90     }
91 
92     return (item_type);
93 }
94 
95 static void
ipatch_sli_class_init(IpatchSLIClass * klass)96 ipatch_sli_class_init(IpatchSLIClass *klass)
97 {
98     GObjectClass *obj_class = G_OBJECT_CLASS(klass);
99     IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
100     IpatchContainerClass *container_class = IPATCH_CONTAINER_CLASS(klass);
101 
102     parent_class = g_type_class_peek_parent(klass);
103 
104     obj_class->get_property = ipatch_sli_get_property;
105 
106     item_class->copy = ipatch_sli_item_copy;
107 
108     container_class->child_types = ipatch_sli_container_child_types;
109     container_class->virtual_types = ipatch_sli_container_virtual_types;
110     container_class->init_iter = ipatch_sli_container_init_iter;
111     container_class->make_unique = ipatch_sli_container_make_unique;
112 
113     g_object_class_override_property(obj_class, PROP_TITLE, "title");
114 
115     sli_child_types[0] = IPATCH_TYPE_SLI_INST;
116     sli_child_types[1] = IPATCH_TYPE_SLI_SAMPLE;
117 
118     sli_virt_types[0] = IPATCH_TYPE_VIRTUAL_SLI_INST;
119     sli_virt_types[1] = IPATCH_TYPE_VIRTUAL_SLI_SAMPLES;
120 }
121 
122 static void
ipatch_sli_init(IpatchSLI * sli)123 ipatch_sli_init(IpatchSLI *sli)
124 {
125     ipatch_item_clear_flags(IPATCH_ITEM(sli), IPATCH_BASE_CHANGED);
126     /* add a prop notify on file-name so sli can notify it's title also */
127     ipatch_item_prop_connect_by_name(IPATCH_ITEM(sli), "file-name",
128                                      ipatch_sli_parent_file_prop_notify, NULL,
129                                      sli);
130 }
131 
132 static void
ipatch_sli_get_title(IpatchSLI * sli,GValue * value)133 ipatch_sli_get_title(IpatchSLI *sli, GValue *value)
134 {
135     char *filename;
136     gchar *s;
137 
138     filename = ipatch_base_get_file_name(IPATCH_BASE(sli));
139     s = (filename ? g_path_get_basename(filename) : NULL);
140     free(filename);
141 
142     if(!s || *s == G_DIR_SEPARATOR || *s == '.')
143     {
144         g_free(s);
145         s = g_strdup(_(IPATCH_BASE_DEFAULT_NAME));
146     }
147 
148     g_value_take_string(value, s);
149 }
150 
151 static void
ipatch_sli_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)152 ipatch_sli_get_property(GObject *object, guint property_id,
153                         GValue *value, GParamSpec *pspec)
154 {
155     IpatchSLI *sli;
156 
157     g_return_if_fail(IPATCH_IS_SLI(object));
158     sli = IPATCH_SLI(object);
159 
160     switch(property_id)
161     {
162     case PROP_TITLE:
163         ipatch_sli_get_title(sli, value);
164         break;
165 
166     default:
167         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
168         break;
169     }
170 }
171 
172 static void
ipatch_sli_item_copy(IpatchItem * dest,IpatchItem * src,IpatchItemCopyLinkFunc link_func,gpointer user_data)173 ipatch_sli_item_copy(IpatchItem *dest, IpatchItem *src,
174                      IpatchItemCopyLinkFunc link_func, gpointer user_data)
175 {
176     IpatchSLI *src_sli, *dest_sli;
177     IpatchItem *newitem;
178     GHashTable *repl_samples;
179     GSList *p;
180 
181     src_sli = IPATCH_SLI(src);
182     dest_sli = IPATCH_SLI(dest);
183 
184     /* create item replacement hash */
185     repl_samples = g_hash_table_new(NULL, NULL);
186 
187     IPATCH_ITEM_RLOCK(src_sli);
188 
189     if(IPATCH_BASE(src_sli)->file)
190     {
191         ipatch_base_set_file(IPATCH_BASE(dest_sli), IPATCH_BASE(src_sli)->file);
192     }
193 
194     p = src_sli->samples;
195 
196     while(p)			/* duplicate samples */
197     {
198         /* ++ ref new duplicate sample, !! sample list takes it over */
199         newitem = ipatch_item_duplicate((IpatchItem *)(p->data));
200         dest_sli->samples = g_slist_prepend(dest_sli->samples, newitem);
201         ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_sli));
202 
203         /* add to sample pointer replacement hash */
204         g_hash_table_insert(repl_samples, p->data, newitem);
205 
206         p = g_slist_next(p);
207     }
208 
209     p = src_sli->insts;
210 
211     while(p)			/* duplicate instruments */
212     {
213         /* ++ ref new duplicate instrument, !! instrument list takes it over
214         * duplicate instrument and replace referenced sample pointers. */
215         newitem = ipatch_item_duplicate_replace((IpatchItem *)(p->data),
216                                                 repl_samples);
217         dest_sli->insts = g_slist_prepend(dest_sli->insts, newitem);
218         ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_sli));
219 
220         p = g_slist_next(p);
221     }
222 
223     IPATCH_ITEM_RUNLOCK(src_sli);
224 
225     dest_sli->insts = g_slist_reverse(dest_sli->insts);
226     dest_sli->samples = g_slist_reverse(dest_sli->samples);
227 
228     g_hash_table_destroy(repl_samples);
229 }
230 
231 static const GType *
ipatch_sli_container_child_types(void)232 ipatch_sli_container_child_types(void)
233 {
234     return (sli_child_types);
235 }
236 
237 static const GType *
ipatch_sli_container_virtual_types(void)238 ipatch_sli_container_virtual_types(void)
239 {
240     return (sli_virt_types);
241 }
242 
243 /* container is locked by caller */
244 static gboolean
ipatch_sli_container_init_iter(IpatchContainer * container,IpatchIter * iter,GType type)245 ipatch_sli_container_init_iter(IpatchContainer *container,
246                                IpatchIter *iter, GType type)
247 {
248     IpatchSLI *sli = IPATCH_SLI(container);
249 
250     if(g_type_is_a(type, IPATCH_TYPE_SLI_INST))
251     {
252         ipatch_iter_GSList_init(iter, &sli->insts);
253     }
254     else if(g_type_is_a(type, IPATCH_TYPE_SLI_SAMPLE))
255     {
256         ipatch_iter_GSList_init(iter, &sli->samples);
257     }
258     else
259     {
260         g_critical("Invalid child type '%s' for parent of type '%s'",
261                    g_type_name(type), g_type_name(G_OBJECT_TYPE(container)));
262         return (FALSE);
263     }
264 
265     return (TRUE);
266 }
267 
268 static void
ipatch_sli_container_make_unique(IpatchContainer * container,IpatchItem * item)269 ipatch_sli_container_make_unique(IpatchContainer *container,
270                                  IpatchItem *item)
271 {
272     IpatchSLI *sli = IPATCH_SLI(container);
273     char *name, *newname;
274 
275     if(!(IPATCH_IS_SLI_INST(item) || IPATCH_IS_SLI_SAMPLE(item)))
276     {
277         g_critical("Invalid child type '%s' for IpatchSLI object",
278                    g_type_name(G_TYPE_FROM_INSTANCE(item)));
279         return;
280     }
281 
282     IPATCH_ITEM_WLOCK(sli);
283 
284     g_object_get(item, "name", &name, NULL);
285     newname = ipatch_sli_make_unique_name(sli, G_TYPE_FROM_INSTANCE(item),
286                                           name, NULL);
287 
288     if(!name || strcmp(name, newname) != 0)
289     {
290         g_object_set(item, "name", newname, NULL);
291     }
292 
293     IPATCH_ITEM_WUNLOCK(sli);
294 
295     g_free(name);
296     g_free(newname);
297 }
298 
299 /* property notify for when parent's "file-name" property changes */
300 static void
ipatch_sli_parent_file_prop_notify(IpatchItemPropNotify * info)301 ipatch_sli_parent_file_prop_notify(IpatchItemPropNotify *info)
302 {
303     IpatchItem *sli = (IpatchItem *)(info->user_data);
304     /* notify that SLI's title has changed */
305     ipatch_item_prop_notify(sli, ipatch_item_pspec_title,
306                             info->new_value, info->old_value);
307 }
308 
309 /**
310  * ipatch_sli_new:
311  *
312  * Create a new Spectralis base object.
313  *
314  * Returns: New Spectralis base object with a reference count of 1. Caller
315  * owns the reference and removing it will destroy the item.
316  */
317 IpatchSLI *
ipatch_sli_new(void)318 ipatch_sli_new(void)
319 {
320     return (IPATCH_SLI(g_object_new(IPATCH_TYPE_SLI, NULL)));
321 }
322 
323 /**
324  * ipatch_sli_set_file:
325  * @sli: SLI object to set file object of
326  * @file: File object to use with the SLI object
327  *
328  * Sets the file object of a SLI object. SLI files are kept open
329  * for sample data that references the file. This function sets a
330  * Spectralis object's authoritive file. A convenience function, as
331  * ipatch_base_set_file() does the same thing (albeit without more specific
332  * type casting).
333  */
334 void
ipatch_sli_set_file(IpatchSLI * sli,IpatchSLIFile * file)335 ipatch_sli_set_file(IpatchSLI *sli, IpatchSLIFile *file)
336 {
337     g_return_if_fail(IPATCH_IS_SLI(sli));
338     g_return_if_fail(IPATCH_IS_SLI_FILE(file));
339 
340     ipatch_base_set_file(IPATCH_BASE(sli), IPATCH_FILE(file));
341 }
342 
343 /**
344  * ipatch_sli_get_file:
345  * @sli: SLI object to get file object of
346  *
347  * Gets the file object of a SLI object. The returned SLI file object's
348  * reference count has been incremented. The caller owns the reference and is
349  * responsible for removing it with g_object_unref.
350  * A convenience function as ipatch_base_get_file() does the same thing
351  * (albeit without more specific type casting).
352  *
353  * Returns: (transfer full): The SLI file object or %NULL if @sli is not open. Remember
354  * to unref the file object with g_object_unref() when done with it.
355  */
356 IpatchSLIFile *
ipatch_sli_get_file(IpatchSLI * sli)357 ipatch_sli_get_file(IpatchSLI *sli)
358 {
359     IpatchFile *file;
360 
361     g_return_val_if_fail(IPATCH_IS_SLI(sli), NULL);
362 
363     file = ipatch_base_get_file(IPATCH_BASE(sli));
364 
365     if(file)
366     {
367         return (IPATCH_SLI_FILE(file));
368     }
369     else
370     {
371         return (NULL);
372     }
373 }
374 
375 /**
376  * ipatch_sli_make_unique_name:
377  * @sli: SLI object
378  * @child_type: A child type of @sli to search for a unique name in
379  * @name: (nullable): An initial name to use or %NULL
380  * @exclude: (nullable): An item to exclude from search or %NULL
381  *
382  * Generates a unique name for the given @child_type in @sli. The @name
383  * parameter is used as a base and is modified, by appending a number, to
384  * make it unique (if necessary). The @exclude parameter is used to exclude
385  * an existing @sli child item from the search.
386  *
387  * MT-Note: To ensure that an item is actually unique before being
388  * added to a SLI object, ipatch_container_add_unique() should be
389  * used.
390  *
391  * Returns: A new unique name which should be freed when finished with it.
392  */
393 char *
ipatch_sli_make_unique_name(IpatchSLI * sli,GType child_type,const char * name,const IpatchItem * exclude)394 ipatch_sli_make_unique_name(IpatchSLI *sli, GType child_type,
395                             const char *name, const IpatchItem *exclude)
396 {
397     GSList **list, *p;
398     char curname[IPATCH_SLI_NAME_SIZE + 1];
399     guint name_ofs, count = 2;
400 
401     g_return_val_if_fail(IPATCH_IS_SLI(sli), NULL);
402 
403     if(g_type_is_a(child_type, IPATCH_TYPE_SLI_INST))
404     {
405         list = &sli->insts;
406         name_ofs = G_STRUCT_OFFSET(IpatchSLIInst, name);
407 
408         if(!name || !*name)
409         {
410             name = _("New Instrument");
411         }
412     }
413     else if(g_type_is_a(child_type, IPATCH_TYPE_SLI_SAMPLE))
414     {
415         list = &sli->samples;
416         name_ofs = G_STRUCT_OFFSET(IpatchSLISample, name);
417 
418         if(!name || !*name)
419         {
420             name = _("New Sample");
421         }
422     }
423     else
424     {
425         g_critical("Invalid child type '%s' of parent type '%s'",
426                    g_type_name(child_type), g_type_name(G_OBJECT_TYPE(sli)));
427         return (NULL);
428     }
429 
430     g_strlcpy(curname, name, sizeof(curname));
431 
432     IPATCH_ITEM_RLOCK(sli);
433 
434     p = *list;
435 
436     while(p)	/* check for duplicate */
437     {
438         IPATCH_ITEM_RLOCK(p->data);  /* MT - Recursive LOCK */
439 
440         if(p->data != exclude
441                 && strcmp(G_STRUCT_MEMBER(char *, p->data, name_ofs),
442                           curname) == 0)
443         {
444             /* duplicate name */
445             IPATCH_ITEM_RUNLOCK(p->data);
446 
447             ipatch_strconcat_num(name, count++, curname, sizeof(curname));
448 
449             p = *list;		/* start over */
450             continue;
451         }
452 
453         IPATCH_ITEM_RUNLOCK(p->data);
454         p = g_slist_next(p);
455     }
456 
457     IPATCH_ITEM_RUNLOCK(sli);
458 
459     return (g_strdup(curname));
460 }
461 
462 /**
463  * ipatch_sli_find_inst:
464  * @sli: SLI to search in
465  * @name: Name of Instrument to find
466  * @exclude: (nullable): An instrument to exclude from the search or %NULL
467  *
468  * Find an instrument by @name in an SLI object. If a matching instrument
469  * is found, its reference count is incremented before it is returned.
470  * The caller is responsible for removing the reference with g_object_unref()
471  * when finished with it.
472  *
473  * Returns: (transfer full): The matching instrument or %NULL if not found. Remember to unref
474  * the item when finished with it.
475  */
476 IpatchSLIInst *
ipatch_sli_find_inst(IpatchSLI * sli,const char * name,const IpatchSLIInst * exclude)477 ipatch_sli_find_inst(IpatchSLI *sli, const char *name,
478                      const IpatchSLIInst *exclude)
479 {
480     IpatchSLIInst *inst;
481     GSList *p;
482 
483     g_return_val_if_fail(IPATCH_IS_SLI(sli), NULL);
484     g_return_val_if_fail(name != NULL, NULL);
485 
486     IPATCH_ITEM_RLOCK(sli);
487     p = sli->insts;
488 
489     while(p)
490     {
491         inst = (IpatchSLIInst *)(p->data);
492         IPATCH_ITEM_RLOCK(inst);	/* MT - Recursive LOCK */
493 
494         if(inst != exclude && strcmp(inst->name, name) == 0)
495         {
496             g_object_ref(inst);
497             IPATCH_ITEM_RUNLOCK(inst);
498             IPATCH_ITEM_RUNLOCK(sli);
499             return (inst);
500         }
501 
502         IPATCH_ITEM_RUNLOCK(inst);
503         p = g_slist_next(p);
504     }
505 
506     IPATCH_ITEM_RUNLOCK(sli);
507 
508     return (NULL);
509 }
510 
511 /**
512  * ipatch_sli_find_sample:
513  * @sli: SLI to search in
514  * @name: Name of sample to find
515  * @exclude: (nullable): A sample to exclude from the search or %NULL
516  *
517  * Find a sample by @name in a SLI object. If a sample is found its
518  * reference count is incremented before it is returned. The caller
519  * is responsible for removing the reference with g_object_unref()
520  * when finished with it.
521  *
522  * Returns: (transfer full): The matching sample or %NULL if not found. Remember to unref
523  * the item when finished with it.
524  */
525 IpatchSLISample *
ipatch_sli_find_sample(IpatchSLI * sli,const char * name,const IpatchSLISample * exclude)526 ipatch_sli_find_sample(IpatchSLI *sli, const char *name,
527                        const IpatchSLISample *exclude)
528 {
529     IpatchSLISample *sample;
530     GSList *p;
531 
532     g_return_val_if_fail(IPATCH_IS_SLI(sli), NULL);
533     g_return_val_if_fail(name != NULL, NULL);
534 
535     IPATCH_ITEM_RLOCK(sli);
536     p = sli->samples;
537 
538     while(p)
539     {
540         sample = (IpatchSLISample *)(p->data);
541         IPATCH_ITEM_RLOCK(sample);  /* MT - Recursive LOCK */
542 
543         if(p->data != exclude && strcmp(sample->name, name) == 0)
544         {
545             g_object_ref(sample);
546             IPATCH_ITEM_RUNLOCK(sample);
547             IPATCH_ITEM_RUNLOCK(sli);
548             return (p->data);
549         }
550 
551         IPATCH_ITEM_RUNLOCK(sample);
552         p = g_slist_next(p);
553     }
554 
555     IPATCH_ITEM_RUNLOCK(sli);
556 
557     return (NULL);
558 }
559 
560 /**
561  * ipatch_sli_get_zone_references:
562  * @sample: Sample to locate referencing zones of.
563  *
564  * Get list of zones referencing an IpatchSLISample.
565  *
566  * Returns: (transfer full): New item list containing #IpatchSLIZone objects
567  * that refer to @sample. The returned list has a reference count of 1 which
568  * the caller owns, unreference to free the list.
569  */
570 IpatchList *
ipatch_sli_get_zone_references(IpatchSLISample * sample)571 ipatch_sli_get_zone_references(IpatchSLISample *sample)
572 {
573     IpatchList *reflist, *instlist, *zonelist;
574     IpatchSLI *sli;
575     IpatchSLIZone *zone;
576     IpatchIter iter, zone_iter;
577     IpatchItem *pitem;
578 
579     g_return_val_if_fail(IPATCH_IS_SLI_SAMPLE(sample), NULL);
580     pitem = ipatch_item_get_parent(IPATCH_ITEM(sample));
581     g_return_val_if_fail(IPATCH_IS_SLI(pitem), NULL);
582     sli = IPATCH_SLI(pitem);
583 
584     reflist = ipatch_list_new();	/* ++ ref new list */
585     instlist = ipatch_sli_get_insts(sli);  /* ++ ref instlist */
586 
587     ipatch_list_init_iter(instlist, &iter);
588     pitem = ipatch_item_first(&iter);
589 
590     while(pitem)  /* loop over instruments  */
591     {
592         zonelist = ipatch_sli_inst_get_zones(pitem);  /* ++ ref new zone list */
593         ipatch_list_init_iter(zonelist, &zone_iter);
594 
595         zone = ipatch_sli_zone_first(&zone_iter);
596 
597         while(zone)
598         {
599             if(ipatch_sli_zone_peek_sample(zone) == sample)
600             {
601                 g_object_ref(zone);  /* ++ ref zone for new list */
602                 reflist->items = g_list_prepend(reflist->items, zone);
603             }
604 
605             zone = ipatch_sli_zone_next(&zone_iter);
606         }
607 
608         g_object_unref(zonelist);  /* -- unref zone list */
609         pitem = ipatch_item_next(&iter);
610     }
611 
612     g_object_unref(instlist);  /* -- unref instlist */
613 
614     /* reverse list to preserve order since we prepended */
615     reflist->items = g_list_reverse(reflist->items);
616 
617     return (reflist); /* !! caller takes over reference */
618 }
619