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: IpatchSF2VoiceCache
22  * @short_description: SoundFont voice cache object
23  * @see_also:
24  * @stability: Stable
25  *
26  * This is used for pre-processing instruments into arrays of SoundFont
27  * compatible voices which can then be accessed very quickly without
28  * multi-thread locking or other issues (during synthesis for example).
29  */
30 #include <stdarg.h>
31 #include <glib.h>
32 #include <glib-object.h>
33 
34 #include "IpatchSF2VoiceCache.h"
35 #include "IpatchSF2Mod.h"
36 #include "IpatchTypeProp.h"
37 #include "i18n.h"
38 
39 /* SoundFont voice native sample format */
40 #define VOICE_SAMPLE_FORMAT  \
41     (IPATCH_SAMPLE_16BIT | IPATCH_SAMPLE_MONO | IPATCH_SAMPLE_ENDIAN_HOST)
42 
43 static void ipatch_sf2_voice_cache_class_init(IpatchSF2VoiceCacheClass *klass);
44 static void ipatch_sf2_voice_cache_init(IpatchSF2VoiceCache *cache);
45 static void ipatch_sf2_voice_cache_finalize(GObject *gobject);
46 
47 static gpointer parent_class = NULL;
48 static IpatchSF2Voice def_voice;	/* default voice structure */
49 
50 
51 /* default selection info */
52 static IpatchSF2VoiceSelInfo default_sel_info[] =
53 {
54     { IPATCH_SF2_VOICE_SEL_NOTE },
55     { IPATCH_SF2_VOICE_SEL_VELOCITY }
56 };
57 
58 GType
ipatch_sf2_voice_cache_get_type(void)59 ipatch_sf2_voice_cache_get_type(void)
60 {
61     static GType item_type = 0;
62 
63     if(!item_type)
64     {
65         static const GTypeInfo item_info =
66         {
67             sizeof(IpatchSF2VoiceCacheClass), NULL, NULL,
68             (GClassInitFunc)ipatch_sf2_voice_cache_class_init, NULL, NULL,
69             sizeof(IpatchSF2VoiceCache),
70             0,
71             (GInstanceInitFunc)ipatch_sf2_voice_cache_init,
72         };
73 
74         item_type = g_type_register_static(G_TYPE_OBJECT,
75                                            "IpatchSF2VoiceCache", &item_info, 0);
76 
77         /* install voice update function type property */
78         ipatch_type_install_property
79         (g_param_spec_pointer("sf2voice-update-func", "sf2voice-update-func",
80                               "sf2voice-update-func", G_PARAM_READWRITE));
81     }
82 
83     return (item_type);
84 }
85 
86 static void
ipatch_sf2_voice_cache_class_init(IpatchSF2VoiceCacheClass * klass)87 ipatch_sf2_voice_cache_class_init(IpatchSF2VoiceCacheClass *klass)
88 {
89     GObjectClass *obj_class = G_OBJECT_CLASS(klass);
90 
91     parent_class = g_type_class_peek_parent(klass);
92 
93     obj_class->finalize = ipatch_sf2_voice_cache_finalize;
94 
95     /* initialize default voice structure */
96     def_voice.sample_data = NULL;
97     def_voice.sample_store = NULL;
98     def_voice.sample_size = 0;
99     def_voice.loop_start = 0;
100     def_voice.loop_end = 0;
101     def_voice.rate = 44100;
102     def_voice.root_note = 60;
103     def_voice.fine_tune = 0;
104     def_voice.reserved = 0;
105 
106     ipatch_sf2_gen_array_init(&def_voice.gen_array, FALSE, FALSE);
107     def_voice.mod_list = NULL;
108 
109     def_voice.range_index = 0;    /* init in ipatch_sf2_voice_cache_add_voice */
110 }
111 
112 static void
ipatch_sf2_voice_cache_init(IpatchSF2VoiceCache * cache)113 ipatch_sf2_voice_cache_init(IpatchSF2VoiceCache *cache)
114 {
115     cache->sel_info = g_memdup(default_sel_info, sizeof(default_sel_info));
116     cache->sel_count = 2;		/* default for velocity and note selection */
117     cache->voices = g_array_new(FALSE, FALSE, sizeof(IpatchSF2Voice));
118     cache->ranges = NULL;		/* initialized later */
119     cache->default_mods = ipatch_sf2_mod_list_duplicate
120                           (ipatch_sf2_mod_list_get_default());
121     cache->default_loop_type = IPATCH_SAMPLE_LOOP_STANDARD;
122 }
123 
124 static void
ipatch_sf2_voice_cache_finalize(GObject * gobject)125 ipatch_sf2_voice_cache_finalize(GObject *gobject)
126 {
127     IpatchSF2VoiceCache *cache = IPATCH_SF2_VOICE_CACHE(gobject);
128     IpatchSF2Voice *voice;
129     guint i;
130 
131     g_free(cache->sel_info);
132 
133     /*-------- free IpatchSF2Voice field members -------------------------------*/
134     for(i = 0; i < cache->voices->len; i++)
135     {
136         voice = &g_array_index(cache->voices, IpatchSF2Voice, i);
137 
138         /* running voice_user_data_destroy() function first ensures that the
139            function could expect that any IpatchSF2Voice field member is
140            still valid. This is necessarry if the parameter voice->user_data
141            is a pointer to one of this field.
142         */
143         if(cache->voice_user_data_destroy && voice->user_data)
144         {
145             cache->voice_user_data_destroy(voice->user_data);
146         }
147 
148         if(voice->sample_data)	/* -- unref sample data */
149         {
150             g_object_unref(voice->sample_data);
151         }
152 
153         if(voice->sample_store)	/* -- unref sample store */
154         {
155             g_object_unref(voice->sample_store);
156         }
157 
158         if(voice->mod_list)	/* free modulators */
159         {
160             ipatch_sf2_mod_list_free(voice->mod_list, TRUE);
161         }
162     }
163 
164     g_array_free(cache->voices, TRUE);
165 
166     /*-------- free IpatchSF2VoiceCache field members -----------------------*/
167 
168     /* running user_data_destroy() function first ensures that the
169        function could expect that any IpatchSF2VoiceCache field member
170        is still valid. This is necessarry if the parameter cache->user_data
171        is a pointer to one of this field.
172     */
173     if(cache->user_data_destroy && cache->user_data)
174     {
175         cache->user_data_destroy(cache->user_data);
176     }
177 
178     if(cache->ranges)
179     {
180         g_array_free(cache->ranges, TRUE);
181     }
182 
183     if(cache->default_mods)
184     {
185         ipatch_sf2_mod_list_free(cache->default_mods, TRUE);
186     }
187 
188     if(cache->override_mods)
189     {
190         ipatch_sf2_mod_list_free(cache->override_mods, TRUE);
191     }
192 
193     if(G_OBJECT_CLASS(parent_class)->finalize)
194     {
195         G_OBJECT_CLASS(parent_class)->finalize(gobject);
196     }
197 }
198 
199 /* FIXME-GIR: Voice cache interface isn't currently binding friendly */
200 
201 /**
202  * ipatch_sf2_voice_cache_new: (skip)
203  * @info: (nullable): Array of selection info structures (length should be @sel_count), use
204  *   %NULL for default selection info (MIDI note and velocity)
205  * @sel_count: Count of selection ranges for this cache (ignored if @info is %NULL)
206  *
207  * Create a new SoundFont voice cache object.  The @sel_count parameter
208  * defines the number of selection ranges for the cache.  Examples of selection
209  * ranges include MIDI note and velocity ranges for a voice.  The @info
210  * parameter should be a pointer to an array of selection info structures
211  * which describes each selection type.
212  *
213  * Returns: New SoundFont voice cache with a reference count of 1 which the
214  *   caller owns.
215  */
216 IpatchSF2VoiceCache *
ipatch_sf2_voice_cache_new(IpatchSF2VoiceSelInfo * info,int sel_count)217 ipatch_sf2_voice_cache_new(IpatchSF2VoiceSelInfo *info, int sel_count)
218 {
219     IpatchSF2VoiceCache *cache;
220 
221     g_return_val_if_fail(!info || sel_count > 0, NULL);
222 
223     cache = IPATCH_SF2_VOICE_CACHE(g_object_new(IPATCH_TYPE_SF2_VOICE_CACHE,
224                                    NULL));
225 
226     if(info)
227     {
228         g_free(cache->sel_info);
229         cache->sel_info = g_memdup(info, sizeof(IpatchSF2VoiceSelInfo) * sel_count);
230         cache->sel_count = sel_count;
231     }
232 
233     return (cache);
234 }
235 
236 /**
237  * ipatch_sf2_voice_cache_set_default_mods: (skip)
238  * @cache: Voice cache
239  * @mods: (element-type IpatchSF2Mod) (transfer full): SoundFont modulator list
240  *   to use as default (used directly)
241  *
242  * Set the default modulator list for the voice cache.  Modulator list is used
243  * directly and the allocation of the list is taken over by the voice cache.
244  */
245 void
ipatch_sf2_voice_cache_set_default_mods(IpatchSF2VoiceCache * cache,GSList * mods)246 ipatch_sf2_voice_cache_set_default_mods(IpatchSF2VoiceCache *cache,
247                                         GSList *mods)
248 {
249     g_return_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache));
250 
251     if(cache->default_mods)
252     {
253         ipatch_sf2_mod_list_free(cache->default_mods, TRUE);
254     }
255 
256     cache->default_mods = mods;
257 }
258 
259 /**
260  * ipatch_sf2_voice_cache_set_override_mods: (skip)
261  * @cache: Voice cache
262  * @mods: (element-type IpatchSF2Mod) (transfer full): SoundFont modulator list
263  *   which overrides rendered voice modulators (used directly)
264  *
265  * Set the override modulator list for the voice cache.  Modulator list is used
266  * directly and the allocation of the list is taken over by the voice cache.
267  *
268  * Since: 1.1.0
269  */
270 void
ipatch_sf2_voice_cache_set_override_mods(IpatchSF2VoiceCache * cache,GSList * mods)271 ipatch_sf2_voice_cache_set_override_mods(IpatchSF2VoiceCache *cache,
272         GSList *mods)
273 {
274     g_return_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache));
275 
276     if(cache->override_mods)
277     {
278         ipatch_sf2_mod_list_free(cache->override_mods, TRUE);
279     }
280 
281     cache->override_mods = mods;
282 }
283 
284 /**
285  * ipatch_sf2_voice_cache_add_voice: (skip)
286  * @cache: Voice cache to create voice for
287  *
288  * Adds a new initialized voice to a SoundFont voice cache.
289  *
290  * Returns: (transfer none): Pointer to new voice which is owned by the @cache.
291  * The sample pointer is set to NULL, generator array is initialized to default
292  * absolute unset values, selection ranges are set to G_MININT-G_MAXINT and
293  * all other fields are initialized to defaults.
294  */
295 IpatchSF2Voice *
ipatch_sf2_voice_cache_add_voice(IpatchSF2VoiceCache * cache)296 ipatch_sf2_voice_cache_add_voice(IpatchSF2VoiceCache *cache)
297 {
298     IpatchSF2Voice *voice;
299     int *ranges;
300     int i;
301 
302     g_return_val_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache), NULL);
303 
304     /* create range integer array if this is the first voice */
305     if(!cache->ranges)
306         cache->ranges = g_array_new(FALSE, FALSE, 2 * sizeof(int)
307                                     * cache->sel_count);
308 
309     g_array_append_val(cache->voices, def_voice);
310 
311     voice = &g_array_index(cache->voices, IpatchSF2Voice, cache->voices->len - 1);
312 
313     voice->range_index = cache->ranges->len * cache->sel_count * 2;
314 
315     /* add selection ranges for this voice to range array */
316     g_array_set_size(cache->ranges, cache->ranges->len + 1);
317 
318     /* initialize ranges to wildcard range */
319     ranges = &((int *)(cache->ranges->data))[voice->range_index];
320 
321     for(i = 0; i < cache->sel_count; i++)
322     {
323         ranges[i * 2] = G_MININT;
324         ranges[i * 2 + 1] = G_MAXINT;
325     }
326 
327     return (voice);
328 }
329 
330 /**
331  * ipatch_sf2_voice_cache_set_voice_range: (skip)
332  * @cache: Voice cache object
333  * @voice: Voice pointer in cache
334  * @sel_index: Selection index
335  * @low: Range low value
336  * @high: Range high value
337  *
338  * Set a voice selection range. Selection ranges are used for selection criteria
339  * such as MIDI velocity and note ranges.
340  */
341 void
ipatch_sf2_voice_cache_set_voice_range(IpatchSF2VoiceCache * cache,IpatchSF2Voice * voice,guint sel_index,int low,int high)342 ipatch_sf2_voice_cache_set_voice_range(IpatchSF2VoiceCache *cache,
343                                        IpatchSF2Voice *voice, guint sel_index,
344                                        int low, int high)
345 {
346     int *rangep;
347 
348     g_return_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache));
349     g_return_if_fail(voice != NULL);
350     g_return_if_fail(sel_index < (guint)cache->sel_count);
351     g_return_if_fail(low <= high);
352 
353     rangep = (int *)(cache->ranges->data);
354     rangep[voice->range_index + sel_index * 2] = low;
355     rangep[voice->range_index + sel_index * 2 + 1] = high;
356 }
357 
358 /**
359  * ipatch_sf2_voice_set_sample_data: (skip)
360  * @voice: SoundFont voice structure
361  * @sample_data: Sample data to assign to voice
362  *
363  * Assign sample data to a SoundFont voice.
364  */
365 void
ipatch_sf2_voice_set_sample_data(IpatchSF2Voice * voice,IpatchSampleData * sample_data)366 ipatch_sf2_voice_set_sample_data(IpatchSF2Voice *voice, IpatchSampleData *sample_data)
367 {
368     g_return_if_fail(voice != NULL);
369     g_return_if_fail(IPATCH_IS_SAMPLE_DATA(sample_data));
370 
371     if(voice->sample_data)
372     {
373         g_object_unref(voice->sample_data);
374     }
375 
376     voice->sample_data = g_object_ref(sample_data);	/* ++ ref sample data */
377 
378     if(voice->sample_store)
379     {
380         g_object_unref(voice->sample_store);
381     }
382 
383     voice->sample_store = NULL;
384     voice->sample_size = ipatch_sample_data_get_size(voice->sample_data);
385 }
386 
387 /**
388  * ipatch_sf2_voice_cache_sample_data: (skip)
389  * @voice: SoundFont voice structure
390  * @err: Location to store error info or %NULL to ignore
391  *
392  * Cache an already assigned sample data object of a voice.  The sample data is
393  * cached as 16 bit mono native endian format, if not already cached, and the
394  * new cached sample is assigned to the sample_store field.
395  *
396  * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
397  */
398 gboolean
ipatch_sf2_voice_cache_sample_data(IpatchSF2Voice * voice,GError ** err)399 ipatch_sf2_voice_cache_sample_data(IpatchSF2Voice *voice, GError **err)
400 {
401     g_return_val_if_fail(voice != NULL, FALSE);
402     g_return_val_if_fail(!err || !*err, FALSE);
403     g_return_val_if_fail(voice->sample_data != NULL, FALSE);
404 
405     if(voice->sample_store)
406     {
407         g_object_unref(voice->sample_store);
408     }
409 
410     /* FIXME - Do we need channel_map per voice? */
411     /* ++ ref cached sample store for voice */
412     voice->sample_store
413         = ipatch_sample_data_get_cache_sample(voice->sample_data, VOICE_SAMPLE_FORMAT,
414                 IPATCH_SAMPLE_UNITY_CHANNEL_MAP, err);
415 
416     return (voice->sample_store != NULL);
417 }
418 
419 /**
420  * ipatch_sf2_voice_copy: (skip)
421  * @dest: Destination voice to copy to (initialized to 0s or already valid)
422  * @src: Source voice to copy from
423  *
424  * Copy a source (@src) voice's information to a destination voice (@dest).
425  * Does not copy selection criteria integers in parent #IpatchSF2VoiceCache
426  * objects.
427  */
428 void
ipatch_sf2_voice_copy(IpatchSF2Voice * dest,IpatchSF2Voice * src)429 ipatch_sf2_voice_copy(IpatchSF2Voice *dest, IpatchSF2Voice *src)
430 {
431     g_return_if_fail(dest != NULL);
432     g_return_if_fail(src != NULL);
433 
434     if(dest->sample_data)
435     {
436         g_object_unref(dest->sample_data);
437     }
438 
439     if(dest->sample_store)
440     {
441         g_object_unref(dest->sample_store);
442     }
443 
444     if(src->sample_data)
445     {
446         dest->sample_data = g_object_ref(src->sample_data);
447     }
448     else
449     {
450         dest->sample_data = NULL;
451     }
452 
453     if(src->sample_store)
454     {
455         dest->sample_store = g_object_ref(src->sample_store);
456     }
457     else
458     {
459         dest->sample_store = NULL;
460     }
461 
462     dest->sample_size = src->sample_size;
463     dest->loop_start = src->loop_start;
464     dest->loop_end = src->loop_end;
465     dest->rate = src->rate;
466     dest->root_note = src->root_note;
467     dest->fine_tune = src->fine_tune;
468     dest->reserved = src->reserved;
469     dest->gen_array = src->gen_array;
470 
471     dest->mod_list = ipatch_sf2_mod_list_duplicate(src->mod_list);
472 }
473 
474 /**
475  * ipatch_sf2_voice_cache_optimize: (skip)
476  * @cache: SoundFont voice cache
477  *
478  * Can be called after all voices have been added to a voice cache.
479  * Will optimize voice cache for use with ipatch_sf2_voice_cache_select().
480  * NOTE: Currently does nothing, but will in the future..
481  */
482 void
ipatch_sf2_voice_cache_optimize(IpatchSF2VoiceCache * cache)483 ipatch_sf2_voice_cache_optimize(IpatchSF2VoiceCache *cache)
484 {
485     /* FIXME */
486 }
487 
488 /**
489  * ipatch_sf2_voice_cache_select: (skip)
490  * @cache: SoundFont voice cache
491  * @select_values: Array of select values which must be the same length as
492  *   the voice cache was initialized with.  Each selection value is tested
493  *   against each voice's selection ranges (use #IPATCH_SF2_VOICE_SEL_WILDCARD
494  *   as a wildcard selection value).
495  * @index_array: Voice indexes are stored in this array.
496  * @max_indexes: Maximum number of voices to match.  @index_array should be
497  *   at least this value in size.
498  *
499  * Stores pointers to voices matching @select_values criteria.
500  *
501  * Returns: Number of indexes stored to @index_array.
502  */
503 int
ipatch_sf2_voice_cache_select(IpatchSF2VoiceCache * cache,int * select_values,guint16 * index_array,guint16 max_indexes)504 ipatch_sf2_voice_cache_select(IpatchSF2VoiceCache *cache, int *select_values,
505                               guint16 *index_array, guint16 max_indexes)
506 {
507     IpatchSF2Voice *voice;
508     guint16 *indexp = index_array;
509     GArray *voices;
510     int *range_array;
511     int i, count, scount, sv, si, ri;
512     int matches;
513 
514     g_return_val_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache), 0);
515     g_return_val_if_fail(select_values != NULL, 0);
516     g_return_val_if_fail(index_array != NULL, 0);
517     g_return_val_if_fail(max_indexes > 0, 0);
518 
519     /* OPTME - This could be optimized, currently just iterates over array */
520 
521     count = cache->voices->len;
522     voices = cache->voices;
523     scount = cache->sel_count;
524 
525     if(!cache->ranges)    /* no ranges means no voices, return */
526     {
527         return (0);
528     }
529 
530     range_array = (int *)(cache->ranges->data);  /* array will not change */
531 
532     /* loop over all voices */
533     for(i = 0, matches = 0; i < count && matches < max_indexes; i++)
534     {
535         voice = &g_array_index(voices, IpatchSF2Voice, i);
536 
537         /* loop over selection ranges */
538         for(si = 0, ri = voice->range_index; si < scount; si++, ri += 2)
539         {
540             sv = select_values[si];
541 
542             /* break out if no match (not -1 and not in range) */
543             if(sv != -1 && !(sv >= range_array[ri] && sv <= range_array[ri + 1]))
544             {
545                 break;
546             }
547         }
548 
549         if(si == scount)		/* all range selection criteria matched? */
550         {
551             *indexp = i;	/* add voice index to index array */
552             indexp++;
553             matches++;
554         }
555     }
556 
557     return (matches);
558 }
559 
560 /**
561  * ipatch_sf2_voice_cache_updates: (skip)
562  * @cache: Voice cache to get updates for
563  * @select_values: The voice selection criteria to use, should be the same
564  *   number of select values as in @cache
565  * @cache_item: Original item @cache was created from
566  * @item: Object for which a property changed (only really makes sense if it is
567  *   a dependent item of @cache_item).
568  * @pspec: Parameter specification of property which changed
569  * @value: The new value of the property
570  * @updates: Output array to store updates to
571  * @max_updates: Size of @updates array (max possible update values).
572  *
573  * This function is used to re-calculate SoundFont effect generators for a
574  * single object property change.  Useful for real time effect changes.
575  *
576  * Returns: Number of updates stored to @updates array or -1 if not handled
577  *   (no handler for the given @item).  Will be 0 if no updates required.
578  */
579 int
ipatch_sf2_voice_cache_update(IpatchSF2VoiceCache * cache,int * select_values,GObject * cache_item,GObject * item,GParamSpec * pspec,const GValue * value,IpatchSF2VoiceUpdate * updates,guint max_updates)580 ipatch_sf2_voice_cache_update(IpatchSF2VoiceCache *cache,
581                               int *select_values, GObject *cache_item,
582                               GObject *item, GParamSpec *pspec,
583                               const GValue *value,
584                               IpatchSF2VoiceUpdate *updates,
585                               guint max_updates)
586 {
587     IpatchSF2VoiceCacheUpdateHandler handler;
588 
589     g_return_val_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache), -1);
590     g_return_val_if_fail(select_values != NULL, -1);
591     g_return_val_if_fail(G_IS_OBJECT(cache_item), -1);
592     g_return_val_if_fail(G_IS_OBJECT(item), -1);
593     g_return_val_if_fail(G_IS_PARAM_SPEC(pspec), -1);
594     g_return_val_if_fail(G_IS_VALUE(value), -1);
595     g_return_val_if_fail(updates != NULL, -1);
596 
597     ipatch_type_get(G_OBJECT_TYPE(cache_item), "sf2voice-update-func", &handler,
598                     NULL);
599 
600     if(!handler)
601     {
602         return (-1);
603     }
604 
605     if(max_updates == 0)
606     {
607         return (0);
608     }
609 
610     return handler(cache, select_values, cache_item, item, pspec, value,
611                    updates, max_updates);
612 }
613 
614 #ifdef IPATCH_DEBUG
615 /**
616  * ipatch_sf2_voice_cache_dump: (skip)
617  *
618  * Debugging function to dump a voice cache to stdout
619  */
620 void
ipatch_sf2_voice_cache_dump(IpatchSF2VoiceCache * cache,int start,int count)621 ipatch_sf2_voice_cache_dump(IpatchSF2VoiceCache *cache, int start, int count)
622 {
623     IpatchSF2Voice *voice;
624     char *selnames[] = { "Note", "Vel", "AftTch", "CC" };
625     int *range;
626     int i, si;
627 
628     g_return_if_fail(IPATCH_IS_SF2_VOICE_CACHE(cache));
629 
630     if(start == 0)
631     {
632         printf("Voice cache selection criteria:\n");
633 
634         for(i = 0; i < cache->sel_count; i++)
635         {
636             switch(cache->sel_info[i].type)
637             {
638             case IPATCH_SF2_VOICE_SEL_NOTE:
639                 printf("%d: Note\n", i);
640                 break;
641 
642             case IPATCH_SF2_VOICE_SEL_VELOCITY:
643                 printf("%d: Velocity\n", i);
644                 break;
645 
646             case IPATCH_SF2_VOICE_SEL_AFTER_TOUCH:
647                 printf("%d: After touch\n", i);
648                 break;
649 
650             case IPATCH_SF2_VOICE_SEL_MIDI_CC:
651                 printf("%d: CC %d\n", i, cache->sel_info[i].param1);
652                 break;
653             }
654         }
655     }
656 
657     for(i = start; i < start + count && i < cache->voices->len; i++)
658     {
659         voice = &g_array_index(cache->voices, IpatchSF2Voice, i);
660 
661         printf("%d (S:%d,SD:%p,SS:%p) L:%d-%d R:%d RN:%d T:%d\n ",
662                i, voice->sample_size, voice->sample_data, voice->sample_store,
663                voice->loop_start, voice->loop_end, voice->rate, voice->root_note,
664                voice->fine_tune);
665 
666         range = &g_array_index(cache->ranges, int, voice->range_index);
667 
668         for(si = 0; si < cache->sel_count; si++)
669         {
670             printf(" %s: %d-%d", selnames[cache->sel_info[si].type],
671                    range[si * 2], range[si * 2 + 1]);
672         }
673 
674         printf("\n");
675     }
676 }
677 
678 /**
679  * ipatch_sf2_voice_cache_dump_select: (skip)
680  */
681 void
ipatch_sf2_voice_cache_dump_select(IpatchSF2VoiceCache * cache,...)682 ipatch_sf2_voice_cache_dump_select(IpatchSF2VoiceCache *cache, ...)
683 {
684     IpatchSF2Voice *voice;
685     va_list args;
686     char *selnames[] = { "Note", "Vel", "AftTch", "CC" };
687     guint16 indexes[256];
688     int selvals[8];
689     int *range;
690     int count, i, vindex, si;
691 
692     va_start(args, cache);
693 
694     for(i = 0; i < cache->sel_count; i++)
695     {
696         selvals[i] = va_arg(args, int);
697     }
698 
699     va_end(args);
700 
701     count = ipatch_sf2_voice_cache_select(cache, selvals, indexes, G_N_ELEMENTS(indexes));
702 
703     printf("%d voices matched:\n", count);
704 
705     for(i = 0; i < count; i++)
706     {
707         vindex = indexes[i];
708         voice = &g_array_index(cache->voices, IpatchSF2Voice, vindex);
709 
710         printf("%d (S:%d,SD:%p,SS:%p) L:%d-%d R:%d RN:%d T:%d\n ",
711                vindex, voice->sample_size, voice->sample_data, voice->sample_store,
712                voice->loop_start, voice->loop_end, voice->rate, voice->root_note,
713                voice->fine_tune);
714 
715         range = &g_array_index(cache->ranges, int, voice->range_index);
716 
717         for(si = 0; si < cache->sel_count; si++)
718         {
719             printf(" %s: %d-%d", selnames[cache->sel_info[si].type],
720                    range[si * 2], range[si * 2 + 1]);
721         }
722 
723         printf("\n");
724     }
725 }
726 #endif
727