/*
 * libInstPatch
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1
 * of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */
/**
 * SECTION: IpatchDLS2
 * @short_description: DLS version 2 instrument file object
 * @see_also:
 * @stability: Stable
 *
 * Object type for DLS version 2 format instruments.
 */
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include "IpatchDLS2.h"
#include "IpatchDLSFile.h"
#include "IpatchDLS2Info.h"
#include "IpatchDLS2Region.h"
#include "IpatchTypeProp.h"
#include "IpatchVirtualContainer_types.h"
#include "version.h"
#include "ipatch_priv.h"

/* non-INFO properties (INFO properties use their FOURCC int values) */
enum
{
    PROP_0,
    PROP_VERSION
};

static void ipatch_dls2_class_init(IpatchDLS2Class *klass);
static void ipatch_dls2_init(IpatchDLS2 *dls);
static void ipatch_dls2_finalize(GObject *gobject);
static void ipatch_dls2_set_property(GObject *object, guint property_id,
                                     const GValue *value, GParamSpec *pspec);
static void ipatch_dls2_get_property(GObject *object, guint property_id,
                                     GValue *value, GParamSpec *pspec);
static void ipatch_dls2_item_copy(IpatchItem *dest, IpatchItem *src,
                                  IpatchItemCopyLinkFunc link_func,
                                  gpointer user_data);

static const GType *ipatch_dls2_container_child_types(void);
static const GType *ipatch_dls2_container_virtual_types(void);
static gboolean ipatch_dls2_container_init_iter(IpatchContainer *container,
        IpatchIter *iter, GType type);
static void ipatch_dls2_container_make_unique(IpatchContainer *container,
        IpatchItem *item);
static void ipatch_dls2_base_find_unused_locale(IpatchBase *base, int *bank,
        int *program,
        const IpatchItem *exclude,
        gboolean percussion);
static int locale_gcompare_func(gconstpointer a, gconstpointer b);
static IpatchItem *
ipatch_dls2_base_find_item_by_locale(IpatchBase *base, int bank, int program);

static gpointer parent_class = NULL;
static GType dls2_child_types[3] = { 0 };
static GType dls2_virt_types[4] = { 0 };



/* SoundFont item type creation function */
GType
ipatch_dls2_get_type(void)
{
    static GType item_type = 0;

    if(!item_type)
    {
        static const GTypeInfo item_info =
        {
            sizeof(IpatchDLS2Class), NULL, NULL,
            (GClassInitFunc)ipatch_dls2_class_init, NULL, NULL,
            sizeof(IpatchDLS2),
            0,
            (GInstanceInitFunc)ipatch_dls2_init,
        };

        item_type = g_type_register_static(IPATCH_TYPE_BASE, "IpatchDLS2",
                                           &item_info, 0);
    }

    return (item_type);
}

static void
ipatch_dls2_class_init(IpatchDLS2Class *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);
    IpatchItemClass *item_class = IPATCH_ITEM_CLASS(klass);
    IpatchContainerClass *container_class = IPATCH_CONTAINER_CLASS(klass);
    IpatchBaseClass *base_class = IPATCH_BASE_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->finalize = ipatch_dls2_finalize;
    obj_class->get_property = ipatch_dls2_get_property;

    /* we use the IpatchItem item_set_property method */
    item_class->item_set_property = ipatch_dls2_set_property;
    item_class->copy = ipatch_dls2_item_copy;

    container_class->child_types = ipatch_dls2_container_child_types;
    container_class->virtual_types = ipatch_dls2_container_virtual_types;
    container_class->init_iter = ipatch_dls2_container_init_iter;
    container_class->make_unique = ipatch_dls2_container_make_unique;

    base_class->find_unused_locale = ipatch_dls2_base_find_unused_locale;
    base_class->find_item_by_locale = ipatch_dls2_base_find_item_by_locale;

    /* non INFO properties */
    g_object_class_override_property(obj_class, IPATCH_DLS2_NAME, "title");

    g_object_class_install_property(obj_class, PROP_VERSION,
                                    g_param_spec_string("version", _("Version"),
                                            _("File version \"n.n.n.n\""),
                                            NULL,
                                            G_PARAM_READWRITE));

    ipatch_dls2_info_install_class_properties(obj_class);

    dls2_child_types[0] = IPATCH_TYPE_DLS2_INST;
    dls2_child_types[1] = IPATCH_TYPE_DLS2_SAMPLE;

    dls2_virt_types[0] = IPATCH_TYPE_VIRTUAL_DLS2_MELODIC;
    dls2_virt_types[1] = IPATCH_TYPE_VIRTUAL_DLS2_PERCUSSION;
    dls2_virt_types[2] = IPATCH_TYPE_VIRTUAL_DLS2_SAMPLES;
}

static void
ipatch_dls2_init(IpatchDLS2 *dls)
{
    g_object_set(dls,
                 "name", _(IPATCH_BASE_DEFAULT_NAME),
                 "software", "libInstPatch v" IPATCH_VERSION,
                 NULL);

    ipatch_item_clear_flags(IPATCH_ITEM(dls), IPATCH_BASE_CHANGED);
}

/* function called when SoundFont is being destroyed */
static void
ipatch_dls2_finalize(GObject *gobject)
{
    IpatchDLS2 *dls = IPATCH_DLS2(gobject);

    IPATCH_ITEM_WLOCK(dls);

    ipatch_dls2_info_free(dls->info);
    dls->info = NULL;

    g_free(dls->dlid);
    dls->dlid = NULL;

    IPATCH_ITEM_WUNLOCK(dls);

    if(G_OBJECT_CLASS(parent_class)->finalize)
    {
        G_OBJECT_CLASS(parent_class)->finalize(gobject);
    }
}

static void
ipatch_dls2_set_property(GObject *object, guint property_id,
                         const GValue *value, GParamSpec *pspec)
{
    IpatchDLS2 *dls = IPATCH_DLS2(object);
    gboolean retval;

    if(property_id == PROP_VERSION)
    {
        const char *verstr;
        guint16 msu, msl, lsu, lsl;

        verstr = g_value_get_string(value);

        if(verstr && sscanf(verstr, "%hu.%hu.%hu.%hu",
                            &msu, &msl, &lsu, &lsl) != 4)
        {
            g_warning("Version property parse error");
            return;
        }

        IPATCH_ITEM_WLOCK(dls);

        if(verstr)
        {
            ipatch_item_set_flags(dls, IPATCH_DLS2_VERSION_SET);
            dls->ms_version = (guint32)msu << 16 || msl;
            dls->ls_version = (guint32)lsu << 16 || lsl;
        }
        else
        {
            ipatch_item_clear_flags(dls, IPATCH_DLS2_VERSION_SET);
        }

        IPATCH_ITEM_WUNLOCK(dls);
    }
    else
    {
        IPATCH_ITEM_WLOCK(dls);
        retval = ipatch_dls2_info_set_property(&dls->info, property_id, value);
        IPATCH_ITEM_WUNLOCK(dls);

        /* check of "title" property needs to be notified, values do not need
        to be sent, since its a read only property */
        if(property_id == IPATCH_DLS2_NAME)
            ipatch_item_prop_notify((IpatchItem *)dls, ipatch_item_pspec_title,
                                    value, NULL);

        if(!retval)
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            return;
        }
    }
}

static void
ipatch_dls2_get_property(GObject *object, guint property_id,
                         GValue *value, GParamSpec *pspec)
{
    IpatchDLS2 *dls;
    gboolean retval;
    char *s;

    g_return_if_fail(IPATCH_IS_DLS2(object));
    dls = IPATCH_DLS2(object);

    if(property_id == PROP_VERSION)
    {
        gboolean version_set;
        guint32 ms, ls;

        IPATCH_ITEM_RLOCK(dls);

        version_set = (ipatch_item_get_flags(dls) & IPATCH_DLS2_VERSION_SET) > 0;
        ms = dls->ms_version;
        ls = dls->ls_version;

        IPATCH_ITEM_RUNLOCK(dls);

        if(version_set)
        {
            s = g_strdup_printf("%u.%u.%u.%u", ms >> 16, ms & 0xFFFF,
                                ls >> 16, ls & 0xFFFF);
            g_value_take_string(value, s);
        }
        else
        {
            g_value_set_string(value, NULL);
        }
    }
    else				/* INFO property or invalid */
    {
        IPATCH_ITEM_RLOCK(dls);
        retval = ipatch_dls2_info_get_property(dls->info, property_id, value);
        IPATCH_ITEM_RUNLOCK(dls);

        if(!retval)
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        }
    }
}

static void
ipatch_dls2_item_copy(IpatchItem *dest, IpatchItem *src,
                      IpatchItemCopyLinkFunc link_func, gpointer user_data)
{
    IpatchDLS2 *src_dls, *dest_dls;
    IpatchItem *newitem;
    GHashTable *repl_samples;
    GSList *p;

    src_dls = IPATCH_DLS2(src);
    dest_dls = IPATCH_DLS2(dest);

    /* create item replacement hash */
    repl_samples = g_hash_table_new(NULL, NULL);

    IPATCH_ITEM_RLOCK(src_dls);

    dest_dls->ms_version = src_dls->ms_version;
    dest_dls->ls_version = src_dls->ls_version;

    if(ipatch_item_get_flags(src_dls) & IPATCH_DLS2_VERSION_SET)
    {
        ipatch_item_set_flags(dest_dls, IPATCH_DLS2_VERSION_SET);
    }

    if(IPATCH_BASE(src_dls)->file)
    {
        ipatch_base_set_file(IPATCH_BASE(dest_dls), IPATCH_BASE(src_dls)->file);
    }

    dest_dls->info = ipatch_dls2_info_duplicate(src_dls->info);

    if(src_dls->dlid)
    {
        dest_dls->dlid = g_memdup(src_dls->dlid, IPATCH_DLS_DLID_SIZE);
    }

    p = src_dls->samples;

    while(p)			/* duplicate samples */
    {
        newitem = ipatch_item_duplicate((IpatchItem *)(p->data));
        g_return_if_fail(newitem != NULL);

        dest_dls->samples = g_slist_prepend(dest_dls->samples, newitem);
        ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_dls));

        /* add to sample pointer replacement hash */
        g_hash_table_insert(repl_samples, p->data, newitem);

        /* FIXME - phase linked groups? */

        p = g_slist_next(p);
    }

    p = src_dls->insts;

    while(p)			/* duplicate instruments */
    {
        newitem = ipatch_item_duplicate_replace((IpatchItem *)(p->data),
                                                repl_samples);
        g_return_if_fail(newitem != NULL);

        dest_dls->insts = g_slist_prepend(dest_dls->insts, newitem);
        ipatch_item_set_parent(newitem, IPATCH_ITEM(dest_dls));

        p = g_slist_next(p);
    }

    IPATCH_ITEM_RUNLOCK(src_dls);

    dest_dls->insts = g_slist_reverse(dest_dls->insts);
    dest_dls->samples = g_slist_reverse(dest_dls->samples);

    g_hash_table_destroy(repl_samples);
}

static const GType *
ipatch_dls2_container_child_types(void)
{
    return (dls2_child_types);
}

static const GType *
ipatch_dls2_container_virtual_types(void)
{
    return (dls2_virt_types);
}

/* container is locked by caller */
static gboolean
ipatch_dls2_container_init_iter(IpatchContainer *container,
                                IpatchIter *iter, GType type)
{
    IpatchDLS2 *dls = IPATCH_DLS2(container);

    if(g_type_is_a(type, IPATCH_TYPE_DLS2_INST))
    {
        ipatch_iter_GSList_init(iter, &dls->insts);
    }
    else if(g_type_is_a(type, IPATCH_TYPE_DLS2_SAMPLE))
    {
        ipatch_iter_GSList_init(iter, &dls->samples);
    }
    else
    {
        g_critical("Invalid child type '%s' for parent of type '%s'",
                   g_type_name(type), g_type_name(G_OBJECT_TYPE(container)));
        return (FALSE);
    }

    return (TRUE);
}

static void
ipatch_dls2_container_make_unique(IpatchContainer *container,
                                  IpatchItem *item)
{
    IpatchDLS2 *dls = IPATCH_DLS2(container);
    gboolean perc;
    char *name, *newname;

    IPATCH_ITEM_WLOCK(dls);

    if(IPATCH_IS_DLS2_INST(item))
    {
        int bank, newbank, program, newprogram;
        ipatch_dls2_inst_get_midi_locale(IPATCH_DLS2_INST(item),
                                         &bank, &program);
        newbank = bank;
        newprogram = program;

        perc = (ipatch_item_get_flags(item) & IPATCH_DLS2_INST_PERCUSSION) != 0;

        ipatch_base_find_unused_midi_locale(IPATCH_BASE(dls),
                                            &newbank, &newprogram, item, perc);

        if(bank != newbank || program != newprogram)
            ipatch_dls2_inst_set_midi_locale(IPATCH_DLS2_INST(item),
                                             newbank, newprogram);
    }
    else if(!IPATCH_IS_DLS2_SAMPLE(item))
    {
        g_critical("Invalid child type '%s' for IpatchDLS2 object",
                   g_type_name(G_TYPE_FROM_INSTANCE(item)));
        IPATCH_ITEM_WUNLOCK(dls);
        return;
    }

    g_object_get(item, "name", &name, NULL);
    newname = ipatch_dls2_make_unique_name(dls, G_TYPE_FROM_INSTANCE(item),
                                           name, NULL);

    if(!name || strcmp(name, newname) != 0)
    {
        g_object_set(item, "name", newname, NULL);
    }

    IPATCH_ITEM_WUNLOCK(dls);

    g_free(name);
    g_free(newname);
}

/* base method to find an unused MIDI bank:program locale */
static void
ipatch_dls2_base_find_unused_locale(IpatchBase *base, int *bank,
                                    int *program, const IpatchItem *exclude,
                                    gboolean percussion)
{
    IpatchDLS2 *dls = IPATCH_DLS2(base);
    GSList *locale_list = NULL;
    IpatchDLS2Inst *inst;
    GSList *p;
    guint32 b, n;			/* Stores current bank and preset number */
    guint32 lbank, lprogram;

    /* fill array with bank and preset numbers */
    IPATCH_ITEM_RLOCK(dls);
    p = dls->insts;

    while(p)
    {
        inst = (IpatchDLS2Inst *)(p->data);

        /* only add to locale list if not the exclude item */
        if((gpointer)inst != (gpointer)exclude)
            locale_list = g_slist_prepend(locale_list, GUINT_TO_POINTER
                                          ((inst->bank << 16) | inst->program));

        p = g_slist_next(p);
    }

    IPATCH_ITEM_RUNLOCK(dls);

    if(!locale_list)
    {
        *program = 0;
        return;
    }

    locale_list = g_slist_sort(locale_list, (GCompareFunc)locale_gcompare_func);

    b = (guint32) * bank;
    n = (guint32) * program;

    /* loop through sorted list of bank:programs */
    p = locale_list;

    while(p)
    {
        lprogram = GPOINTER_TO_UINT(p->data);
        lbank = lprogram >> 16;
        lprogram &= 0xFFFF;

        if(lbank > b || (lbank == b && lprogram > n))
        {
            break;
        }

        if(lbank >= b)
        {
            if(++n > 127)
            {
                n = 0;
                b++;
            }
        }

        p = g_slist_delete_link(p, p);  /* delete and advance */
    }

    *bank = b;
    *program = n;

    if(p)
    {
        g_slist_free(p);    /* free remainder of list */
    }
}

/* function used to do a temporary sort on preset list for
   ipatch_dls2_base_find_unused_locale */
static int
locale_gcompare_func(gconstpointer a, gconstpointer b)
{
    return (GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b));
}

static IpatchItem *
ipatch_dls2_base_find_item_by_locale(IpatchBase *base, int bank, int program)
{
    IpatchDLS2Inst *inst;

    inst = ipatch_dls2_find_inst(IPATCH_DLS2(base), NULL, bank, program, NULL);
    return ((IpatchItem *)inst);
}

/**
 * ipatch_dls2_new:
 *
 * Create a new DLS base object.
 *
 * Returns: New DLS base object with a reference count of 1. Caller
 * owns the reference and removing it will destroy the item.
 */
IpatchDLS2 *
ipatch_dls2_new(void)
{
    return (IPATCH_DLS2(g_object_new(IPATCH_TYPE_DLS2, NULL)));
}

/**
 * ipatch_dls2_set_file:
 * @dls: DLS to set file object of
 * @file: File object to use with the DLS object
 *
 * Sets the file object of a DLS object. DLS files are kept open
 * for sample data that references the file. This function sets a
 * DLS object's authoritive file. A convenience function, as
 * ipatch_base_set_file() does the same thing (albeit without more specific
 * type casting).
 */
void
ipatch_dls2_set_file(IpatchDLS2 *dls, IpatchDLSFile *file)
{
    g_return_if_fail(IPATCH_IS_DLS2(dls));
    g_return_if_fail(IPATCH_IS_DLS_FILE(file));

    ipatch_base_set_file(IPATCH_BASE(dls), IPATCH_FILE(file));
}

/**
 * ipatch_dls2_get_file:
 * @dls: DLS object to get file object of
 *
 * Gets the file object of a DLS. The returned DLS file object's
 * reference count has been incremented. The caller owns the reference and is
 * responsible for removing it with g_object_unref.
 * A convenience function as ipatch_base_get_file() does the same thing
 * (albeit without more specific type casting).
 *
 * Returns: (transfer full): The DLS file object or %NULL if @dls is not open. Remember
 * to unref the file object with g_object_unref() when done with it.
 */
IpatchDLSFile *
ipatch_dls2_get_file(IpatchDLS2 *dls)
{
    IpatchFile *file;

    g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);

    file = ipatch_base_get_file(IPATCH_BASE(dls));

    if(file)
    {
        return (IPATCH_DLS_FILE(file));
    }
    else
    {
        return (NULL);
    }
}

/**
 * ipatch_dls2_get_info:
 * @dls: DLS to get info from
 * @fourcc: FOURCC integer id of INFO to get
 *
 * Get a DLS info string by FOURCC integer ID (integer representation of
 * a 4 character RIFF chunk ID, see #IpatchRiff).
 *
 * Returns: New allocated info string value or %NULL if no info with the
 * given @fourcc ID. String should be freed when finished with it.
 */
char *
ipatch_dls2_get_info(IpatchDLS2 *dls, guint32 fourcc)
{
    char *val;

    g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);

    IPATCH_ITEM_RLOCK(dls);
    val = ipatch_dls2_info_get(dls->info, fourcc);
    IPATCH_ITEM_RUNLOCK(dls);

    return (val);
}

/**
 * ipatch_dls2_set_info:
 * @dls: DLS to set info of
 * @fourcc: FOURCC integer ID of INFO to set
 * @val: (nullable): Value to set info to or %NULL to unset (clear) info.
 *
 * Sets an INFO value in a DLS object.
 *
 * Emits changed signal on DLS object.
 */
void
ipatch_dls2_set_info(IpatchDLS2 *dls, guint32 fourcc, const char *val)
{
    GValue newval = { 0 }, oldval = { 0 };

    g_return_if_fail(IPATCH_IS_DLS2(dls));

    g_value_init(&newval, G_TYPE_STRING);
    g_value_set_static_string(&newval, val);

    g_value_init(&oldval, G_TYPE_STRING);
    g_value_take_string(&oldval, ipatch_dls2_get_info(dls, fourcc));

    IPATCH_ITEM_WLOCK(dls);
    ipatch_dls2_info_set(&dls->info, fourcc, val);
    IPATCH_ITEM_WUNLOCK(dls);

    ipatch_dls2_info_notify((IpatchItem *)dls, fourcc, &newval, &oldval);

    /* need to do title notify? */
    if(fourcc == IPATCH_DLS2_NAME)
        ipatch_item_prop_notify((IpatchItem *)dls, ipatch_item_pspec_title,
                                &newval, &oldval);

    g_value_unset(&oldval);
    g_value_unset(&newval);
}

/**
 * ipatch_dls2_make_unique_name:
 * @dls: DLS object
 * @child_type: A child type of @dls to search for a unique name in
 * @name: (nullable): An initial name to use or NULL
 * @exclude: (nullable): An item to exclude from search or NULL
 *
 * Generates a unique name for the given @child_type in @dls. The @name
 * parameter is used as a base and is modified, by appending a number, to
 * make it unique (if necessary). The @exclude parameter is used to exclude
 * an existing @dls child item from the search.
 *
 * MT-Note: To ensure that an item is actually unique before being
 * added to a DLS object, ipatch_container_add_unique() should be
 * used.
 *
 * Returns: A new unique name which should be freed when finished with it.
 */
char *
ipatch_dls2_make_unique_name(IpatchDLS2 *dls, GType child_type,
                             const char *name, const IpatchItem *exclude)
{
    GSList **list, *p;
    char *curname, *numptr;
    const char *temp;
    guint count = 2, info_ofs, len;

    g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);

    if(g_type_is_a(child_type, IPATCH_TYPE_DLS2_INST))
    {
        list = &dls->insts;
        info_ofs = G_STRUCT_OFFSET(IpatchDLS2Inst, info);

        if(!name || !*name)
        {
            name = _("New Instrument");
        }
    }
    else if(g_type_is_a(child_type, IPATCH_TYPE_DLS2_SAMPLE))
    {
        list = &dls->samples;
        info_ofs = G_STRUCT_OFFSET(IpatchDLS2Sample, info);

        if(!name || !*name)
        {
            name = _("New Sample");
        }
    }
    else
    {
        g_critical("Invalid child type '%s' of parent type '%s'",
                   g_type_name(child_type), g_type_name(G_OBJECT_TYPE(dls)));
        return (NULL);
    }

    len = strlen(name);

    /* allocate string size + 10 chars for number + zero termination */
    curname = g_malloc0(len + 10 + 1);
    strcpy(curname, name);	/* copy name */
    numptr = curname + len; /* pointer to end of name to concat number */

    IPATCH_ITEM_RLOCK(dls);

    p = *list;

    while(p)	/* check for duplicate */
    {
        IPATCH_ITEM_RLOCK(p->data);  /* MT - Recursive LOCK */

        if(p->data != exclude)
        {
            temp = ipatch_dls2_info_peek
                   (G_STRUCT_MEMBER(IpatchDLS2Info *, p->data, info_ofs),
                    IPATCH_DLS2_NAME);

            if(temp && strcmp(temp, curname) == 0)
            {
                /* duplicate name */
                IPATCH_ITEM_RUNLOCK(p->data);

                sprintf(numptr, "%u", count++);

                p = *list;		/* start over */
                continue;
            }
        }

        IPATCH_ITEM_RUNLOCK(p->data);
        p = g_slist_next(p);
    }

    IPATCH_ITEM_RUNLOCK(dls);

    curname = g_realloc(curname, strlen(curname) + 1);
    return (curname);
}

/**
 * ipatch_dls2_find_inst:
 * @dls: DLS object to search in
 * @name: (nullable): Name of instrument to find or %NULL to match any name
 * @bank: MIDI bank number of instrument to search for or -1 to not search by
 *   MIDI bank:program numbers
 * @program: MIDI program number of instrument to search for, only used
 *   if @bank is 0-128
 * @exclude: (nullable): A instrument to exclude from the search or %NULL
 *
 * Find a instrument by name or bank:program MIDI numbers. If instrument @name
 * and @bank:@program are specified then match for either condition.
 * If a instrument is found its reference count is incremented before it
 * is returned. The caller is responsible for removing the reference
 * with g_object_unref() when finished with it.
 *
 * Returns: (transfer full): The matching instrument or %NULL if not found. Remember to unref
 * the item when finished with it.
 */
IpatchDLS2Inst *
ipatch_dls2_find_inst(IpatchDLS2 *dls, const char *name, int bank,
                      int program, const IpatchDLS2Inst *exclude)
{
    IpatchDLS2Inst *inst;
    gboolean bynum = FALSE;
    const char *temp = NULL;
    GSList *p;

    g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);

    /* if bank and program are valid, then search by number */
    if(bank >= 0 && program >= 0 && program < 128)
    {
        bynum = TRUE;
    }

    IPATCH_ITEM_RLOCK(dls);
    p = dls->insts;

    while(p)
    {
        inst = (IpatchDLS2Inst *)(p->data);
        IPATCH_ITEM_RLOCK(inst);	/* MT - Recursive LOCK */

        if(inst != exclude
                && ((bynum && inst->bank == bank && inst->program == program)
                    || (name && (temp = ipatch_dls2_info_peek
                                        (inst->info, IPATCH_DLS2_NAME))
                        && strcmp(temp, name) == 0)))
        {
            g_object_ref(inst);
            IPATCH_ITEM_RUNLOCK(inst);
            IPATCH_ITEM_RUNLOCK(dls);
            return (inst);
        }

        IPATCH_ITEM_RUNLOCK(inst);
        p = g_slist_next(p);
    }

    IPATCH_ITEM_RUNLOCK(dls);

    return (NULL);
}

/**
 * ipatch_dls2_find_sample:
 * @dls: DLS object to search in
 * @name: Name of sample to find
 * @exclude: (nullable): A sample to exclude from the search or %NULL
 *
 * Find a sample by @name in a SoundFont. If a sample is found its
 * reference count is incremented before it is returned. The caller
 * is responsible for removing the reference with g_object_unref()
 * when finished with it.
 *
 * Returns: (transfer full): The matching sample or %NULL if not found. Remember to unref
 * the item when finished with it.
 */
IpatchDLS2Sample *
ipatch_dls2_find_sample(IpatchDLS2 *dls, const char *name,
                        const IpatchDLS2Sample *exclude)
{
    IpatchDLS2Sample *sample;
    const char *temp = NULL;
    GSList *p;

    g_return_val_if_fail(IPATCH_IS_DLS2(dls), NULL);
    g_return_val_if_fail(name != NULL, NULL);

    IPATCH_ITEM_RLOCK(dls);
    p = dls->samples;

    while(p)
    {
        sample = (IpatchDLS2Sample *)(p->data);
        IPATCH_ITEM_RLOCK(sample);  /* MT - Recursive LOCK */

        if(sample != exclude && (temp = ipatch_dls2_info_peek
                                        (sample->info, IPATCH_DLS2_NAME))
                && strcmp(temp, name) == 0)
        {
            g_object_ref(sample);
            IPATCH_ITEM_RUNLOCK(sample);
            IPATCH_ITEM_RUNLOCK(dls);
            return (sample);
        }

        IPATCH_ITEM_RUNLOCK(sample);
        p = g_slist_next(p);
    }

    IPATCH_ITEM_RUNLOCK(dls);

    return (NULL);
}

/**
 * ipatch_dls2_get_region_references:
 * @sample: Sample to locate referencing regions of.
 *
 * Get list of regions referencing an #IpatchDLS2Sample.
 *
 * Returns: (transfer full): New item list containing #IpatchDLS2Region objects
 * that refer to @sample. The returned list has a reference count of 1 which
 * the caller owns, unreference it when done.
 */
IpatchList *
ipatch_dls2_get_region_references(IpatchDLS2Sample *sample)
{
    IpatchDLS2 *dls;
    IpatchDLS2Region *region;
    IpatchList *refitems;
    IpatchIter iter, region_iter;
    IpatchDLS2Inst *inst;
    IpatchItem *pitem;
    gboolean success;

    g_return_val_if_fail(IPATCH_IS_DLS2_SAMPLE(sample), NULL);

    pitem = ipatch_item_get_parent(IPATCH_ITEM(sample));
    g_return_val_if_fail(IPATCH_IS_DLS2(pitem), NULL);
    dls = IPATCH_DLS2(pitem);

    refitems = ipatch_list_new();

    IPATCH_ITEM_RLOCK(dls);

    success = ipatch_container_init_iter((IpatchContainer *)dls, &iter,
                                         IPATCH_TYPE_DLS2_INST);
    g_return_val_if_fail(success != FALSE, NULL);

    inst = ipatch_dls2_inst_first(&iter);

    while(inst)			/* loop over instruments */
    {
        IPATCH_ITEM_RLOCK(inst);	/* ## embedded lock */

        success = ipatch_container_init_iter((IpatchContainer *)dls,
                                             &region_iter,
                                             IPATCH_TYPE_DLS2_INST);
        g_return_val_if_fail(success != FALSE, NULL);

        region = ipatch_dls2_region_first(&region_iter);

        while(region)		/* loop over regions */
        {
            if(ipatch_dls2_region_peek_sample(region) == sample)
            {
                g_object_ref(region);  /* ++ ref region for new iterator */
                refitems->items = g_list_prepend(refitems->items, region);
            }

            region = ipatch_dls2_region_next(&region_iter);
        }

        IPATCH_ITEM_RUNLOCK(inst);

        inst = ipatch_dls2_inst_next(&iter);
    }

    IPATCH_ITEM_RUNLOCK(dls);

    return (refitems);
}
